在将Internet Explorer的SVG实现基于其自己的VML格式时,我遇到了将SVG椭圆弧转换为VML椭圆弧的问题。
在VML中,弧由下式给出:椭圆上的两个点和半径的长度为两个角, 在SVG中,弧由下式给出:椭圆上的两个点的两对坐标和椭圆边界框的大小
所以,问题是:如何将椭圆上的两个点的角度表示为两对坐标。 一个中间问题可能是:如何通过曲线上一对点的坐标找到椭圆的中心。
更新:让我们有一个前提条件,说正常放置椭圆(其半径与线性坐标系轴平行),因此不会应用旋转。
更新:此问题与svg:ellipse元素无关,而与svg中的“a”椭圆弧命令无关:路径元素(SVG Paths: The elliptical arc curve commands)
答案 0 :(得分:23)
所以解决方案就在这里:
椭圆的参数化公式:
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)))
或者,简单(使用trig和inverse trig函数的组合):
a2 = arcsin(r2 / (1 / sqrt(1 + (r1/r2)^2)))
甚至更简单:
a2 = arcsin(sqrt(r1^2 + r2^2))
现在可以轻松解决最初的四方程系统,并且可以找到所有角度以及日食中心坐标。
答案 1 :(得分:5)
您发布的椭圆曲线弧链接包含link to elliptical arc implementation notes。
在那里,你会找到conversion from endpoint to centre parameterisation的等式。
以下是我从an interactive demo of elliptical arc paths获取的等式的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);
答案 2 :(得分:1)
椭圆不能仅由两个点定义。即使是一个圆(一个特殊的套管椭圆)也由三个点定义。
即使有三个点,你也会有无限的椭圆经过这三个点(想想:旋转)。
请注意,边界框表示椭圆的中心,并且很可能假设其主轴和次轴与x,y(或y,x)轴平行。
答案 3 :(得分:1)
中间问题相当容易......你没有。您可以从边界框中计算出椭圆的中心(即,只要椭圆在框中居中,框的中心就是椭圆的中心)。
关于你的第一个问题,我会看一下椭圆方程的极坐标形式,可在Wikipedia上找到。您还需要计算出椭圆的偏心率。
或者你可以对边界框中的值进行强制执行...如果一个点位于椭圆上并匹配角度,则计算出来,然后遍历边界框中的每个点。
答案 4 :(得分:0)
TypeScript实现基于Rikki的答案。
默认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;
}