Как рассчитать центр эллипса по двум точкам и размерам радиуса

Работая над реализацией SVG для Internet Explorer, основанной на собственном формате VML, я столкнулся с проблемой преобразования эллиптической дуги SVG в эллиптическую дугу VML.

В VML дуга задается: двумя углами для двух точек на эллипсе и длинами радиусов, в SVG дуга задается: двумя парами координат для двух точек на эллипсе и размерами граничного прямоугольника эллипса.

Итак, вопрос: как выразить углы двух точек на эллипсе двумя парами их координат. Промежуточным вопросом может быть: как найти центр эллипса по координатам пары точек на его кривой.

Обновление: давайте предположим, что эллипс размещается нормально (его радиусы параллельны оси линейной системы координат), поэтому вращение не применяется.

Обновление: этот вопрос не относится к элементу svg: ellipse, а скорее к команде "a" эллиптической дуги в элементе svg: path (Пути SVG: команды для кривой эллиптической дуги)


person Sergey Ilinsky    schedule 13.10.2008    source источник


Ответы (5)


Итак, решение здесь:

Параметризованная формула эллипса:

x = x0 + a * cos(t)
y = y0 + b * sin(t)

Подставим к нему известные координаты двух точек:

x1 = x0 + a * cos(t1)
x2 = x0 + a * cos(t2)
y1 = y0 + b * sin(t1)
y2 = y0 + b * sin(t2)

Теперь у нас есть система уравнений с 4-мя переменными: центр эллипса (x0 / y0) и два угла t1, t2

Вычтем уравнения, чтобы избавиться от координат центра:

x1 - x2 = a * (cos(t1) - cos(t2))
y1 - y2 = b * (sin(t1) - sin(t2))

Это можно переписать (с формулами тождеств произведения к сумме) как:

(x1 - x2) / (2 * a) = sin((t1 + t2) / 2) * sin((t1 - t2) / 2)
(y2 - y1) / (2 * b) = cos((t1 + t2) / 2) * sin((t1 - t2) / 2)

Заменим некоторые уравнения:

r1: (x1 - x2) / (2 * a)
r2: (y2 - y1) / (2 * b)
a1: (t1 + t2) / 2
a2: (t1 - t2) / 2

Тогда мы получим простую систему уравнений:

r1 = sin(a1) * sin(a2)
r2 = cos(a1) * sin(a2)

Разделив первое уравнение на второе, получим:

a1 = arctan(r1/r2)

Добавление этого результата к первому уравнению дает:

a2 = arcsin(r2 / cos(arctan(r1/r2)))

Или просто (используя композиции триггерных и обратных триггерных функций):

a2 = arcsin(r2 / (1 / sqrt(1 + (r1/r2)^2)))

или даже проще:

a2 = arcsin(sqrt(r1^2 + r2^2))

Теперь исходная система из четырех уравнений может быть легко решена и могут быть найдены все углы, а также координаты центра затмения.

person Sergey Ilinsky    schedule 13.10.2008
comment
Чтобы быть явным: t1 = a1 + a2, t2 = a1-a2, x0 = x1 - a cos (t1), y0 = y1 - b sin (t1) - person AndrewS; 10.08.2012
comment
Не могли бы вы опубликовать пример преобразования дуги SVG в дугу VML? например A80 80 0 1 0 200 200 создает большую дугу по часовой стрелке с радиусом от 80 до 200 200. Как это можно было воспроизвести в VML? Спасибо! - person DeadPassive; 17.07.2013
comment
Кроме того, правильно ли y0 = y1 - b * sin (t1) или это должно быть sin (t2)? - person DeadPassive; 17.07.2013
comment
Будьте осторожны с последним шагом, если r2 внутри sqrt избавится от его знака, что в некоторых случаях даст плохие результаты! - person Taco de Wolff; 02.11.2016

Опубликованная вами ссылка на дугу эллиптической кривой включает ссылку на примечания по реализации эллиптической дуги .

Там вы найдете уравнения для преобразования из конечной точки в параметризацию центра .

Вот моя реализация этих уравнений в JavaScript, взятая из интерактивного демонстрация путей эллиптической дуги с использованием Sylvester.js для выполнения матричных и векторных вычислений.

// Calculate the centre of the ellipse
// Based on http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
var x1 = 150;  // Starting x-point of the arc
var y1 = 150;  // Starting y-point of the arc
var x2 = 400;  // End x-point of the arc
var y2 = 300;  // End y-point of the arc
var fA = 1;    // Large arc flag
var fS = 1;    // Sweep flag
var rx = 100;  // Horizontal radius of ellipse
var ry =  50;  // Vertical radius of ellipse
var phi = 0;   // Angle between co-ord system and ellipse x-axes

var Cx, Cy;

// Step 1: Compute (x1′, y1′)
var M = $M([
               [ Math.cos(phi), Math.sin(phi)],
               [-Math.sin(phi), Math.cos(phi)]
            ]);
var V = $V( [ (x1-x2)/2, (y1-y2)/2 ] );
var P = M.multiply(V);

var x1p = P.e(1);  // x1 prime
var y1p = P.e(2);  // y1 prime


// Ensure radii are large enough
// Based on http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
// Step (a): Ensure radii are non-zero
// Step (b): Ensure radii are positive
rx = Math.abs(rx);
ry = Math.abs(ry);
// Step (c): Ensure radii are large enough
var lambda = ( (x1p * x1p) / (rx * rx) ) + ( (y1p * y1p) / (ry * ry) );
if(lambda > 1)
{
    rx = Math.sqrt(lambda) * rx;
    ry = Math.sqrt(lambda) * ry;
}


// Step 2: Compute (cx′, cy′)
var sign = (fA == fS)? -1 : 1;
// Bit of a hack, as presumably rounding errors were making his negative inside the square root!
if((( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) )) < 1e-7)
    var co = 0;
else
    var co = sign * Math.sqrt( ( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) ) );
var V = $V( [rx*y1p/ry, -ry*x1p/rx] );
var Cp = V.multiply(co);

// Step 3: Compute (cx, cy) from (cx′, cy′)
var M = $M([
               [ Math.cos(phi), -Math.sin(phi)],
               [ Math.sin(phi),  Math.cos(phi)]
            ]);
var V = $V( [ (x1+x2)/2, (y1+y2)/2 ] );
var C = M.multiply(Cp).add(V);

Cx = C.e(1);
Cy = C.e(2);
person Rikki    schedule 13.07.2012

Эллипс не может быть определен только двумя точками. Даже круг (особый эллипс в корпусе) определяется тремя точками.

Даже с тремя точками вы получите бесконечные эллипсы, проходящие через эти три точки (подумайте: вращение).

Обратите внимание, что ограничивающая рамка предлагает центр эллипса и, скорее всего, предполагает, что его большая и малая оси параллельны осям x, y (или y, x).

person tzot    schedule 13.10.2008
comment
Действительно, я забыл добавить об этом примечание. Сделаю сейчас. - person Sergey Ilinsky; 13.10.2008

Промежуточный вопрос довольно прост ... вы этого не сделаете. Вы определяете центр эллипса из ограничивающего прямоугольника (а именно, центр прямоугольника является центром эллипса, пока эллипс находится в центре прямоугольника).

Что касается вашего первого вопроса, я бы посмотрел на полярную форму уравнения эллипса, которая доступна на Википедии < / а>. Вам также нужно будет определить эксцентриситет эллипса.

Или вы можете подобрать значения из ограничивающей рамки ... работать, если точка лежит на эллипсе и совпадает с углом, и перебирать каждую точку в ограничивающей рамке.

person workmad3    schedule 13.10.2008
comment
Я говорю об эллиптических дугах в svg: path, а не об эллипсе. Что касается математики, я сделал около трех работ по решению уравнений как в полярных, так и в линейных системах координат, но все равно не повезло. - person Sergey Ilinsky; 13.10.2008
comment
Это должен быть все тот же процесс. Эллиптическая дуга - это просто часть эллипса с двумя «ограничивающими точками» эллипса. Ограничивающего прямоугольника должно хватить, чтобы дать вам всю информацию для построения полного эллипса. Затем вам просто нужно ввести углы в уравнение. - person workmad3; 13.10.2008
comment
Опять же, я неправильно сформулировал вопрос. Дан не ограничивающий прямоугольник, а размеры радиуса. - person Sergey Ilinsky; 13.10.2008
comment
ну, указанные радиусы позволят вам сформулировать ограничивающий прямоугольник. А именно, радиус x позволит вам получить значения + x и -x, и то же самое для y. Их комбинации дадут вам все точки ограничивающего прямоугольника. Если центр не указан, то вывод - 0,0 - person workmad3; 13.10.2008
comment
Это проблема, центр не находится в 0,0 (его действительно можно найти где угодно), и промежуточным шагом может быть поиск координаты центра, позже будет легче вычислить углы (с учетом координат точек и размеров ограничивающих прямоугольник) - person Sergey Ilinsky; 13.10.2008

Реализация TypeScript на основе ответа Рикки.

DOMMatrix и DOMPoint по умолчанию используются для вычислений (протестировано в последней версии Chrome v.80) вместо внешней библиотеки.

 ellipseCenter(
    x1: number,
    y1: number,
    rx: number,
    ry: number,
    rotateDeg: number,
    fa: number,
    fs: number,
    x2: number,
    y2: number
  ): DOMPoint {
    const phi = ((rotateDeg % 360) * Math.PI) / 180;
    const m = new DOMMatrix([
      Math.cos(phi),
      -Math.sin(phi),
      Math.sin(phi),
      Math.cos(phi),
      0,
      0,
    ]);
    let v = new DOMPoint((x1 - x2) / 2, (y1 - y2) / 2).matrixTransform(m);
    const x1p = v.x;
    const y1p = v.y;
    rx = Math.abs(rx);
    ry = Math.abs(ry);
    const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
    if (lambda > 1) {
      rx = Math.sqrt(lambda) * rx;
      ry = Math.sqrt(lambda) * ry;
    }
    const sign = fa === fs ? -1 : 1;
    const div =
      (rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p) /
      (rx * rx * y1p * y1p + ry * ry * x1p * x1p);

    const co = sign * Math.sqrt(Math.abs(div));

    // inverse matrix b and c
    m.b *= -1;
    m.c *= -1;
    v = new DOMPoint(
      ((rx * y1p) / ry) * co,
      ((-ry * x1p) / rx) * co
    ).matrixTransform(m);
    v.x += (x1 + x2) / 2;
    v.y += (y1 + y2) / 2;
    return v;
  }

person Ievgen Naida    schedule 12.04.2020