21

在为 Internet Explorer 开发基于其自己的 VML 格式的 SVG 实现时,我遇到了将 SVG 椭圆弧转换为 VML 椭圆弧的问题。

在 VML 中,圆弧由以下公式给出:椭圆上两点的两个角度和半径长度;在 SVG 中,圆弧由以下公式给出:椭圆上两点的两对坐标和椭圆边界框的大小

所以,问题是:如何将椭圆上两点的角度表示为两对坐标。一个中间问题可能是:如何通过椭圆曲线上一对点的坐标找到椭圆的中心。

更新:让我们有一个前提条件,即通常放置一个椭圆(其半径平行于线性坐标系轴),因此不应用旋转。

更新:这个问题与 svg:ellipse 元素无关,而是与 svg:path 元素中的“a”椭圆弧命令有关(SVG 路径:椭圆弧曲线命令

4

5 回答 5

27

所以解决方案在这里:

椭圆的参数化公式:

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))

现在可以很容易地求解初始的四方程系统,并且可以找到所有角度以及日食中心坐标。

于 2008-10-13T16:13:34.883 回答
8

您发布的椭圆曲线弧链接包括椭圆弧实施说明的链接。

在那里,您将找到从端点参数化到中心参数化的转换方程。

这是我对这些方程的 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);
于 2012-07-13T08:55:19.200 回答
3

椭圆不能仅由两点定义。甚至一个圆(一个特殊的椭圆)也由三个点定义。

即使有三个点,你也会有无限的椭圆穿过这三个点(想想:旋转)。

请注意,边界框表示椭圆的中心,并且很可能假设其长轴和短轴平行于 x,y(或 y,x)轴。

于 2008-10-13T14:03:50.353 回答
1

中间问题相当简单......你没有。您从边界框计算出椭圆的中心(即,只要椭圆在框内居中,框的中心就是椭圆的中心)。

对于您的第一个问题,我将查看Wikipedia上提供的椭圆方程的极坐标形式。您还需要计算椭圆的偏心率。

或者你可以从边界框中暴力破解值......如果一个点位于椭圆上并匹配角度,并遍历边界框中的每个点。

于 2008-10-13T14:05:34.063 回答
0

基于 Rikki 回答的 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;
  }

于 2020-04-12T09:08:16.200 回答