SVG弧形路径,如何确定起点、终点和中间点的扫描方向和大弧标记

11
在SVG中,我想构建一个函数,该函数返回路径属性“arc element”的所有参数。给定起始点、终点和途经点(曲线上的 3 个点根据定义在圆上),我只对圆弧感兴趣(rx == ry)。
我可以很容易地计算出中心和半径,但我正在努力解决 2 个标志,是否有明确的定义来比较 3 个点之间的拓扑结构,例如角度或彼此之间的距离来设置这些标志?
我知道标志的含义,最小 vs 最大的弧,顺时针 vs 逆时针的扫描标志。
2个回答

12

可以通过查看SVG路径弧线段的起点(S)、中间点(V)和终点(E)形成的角度来确定该弧线段的大圆弧标志和扫描标志。

对于大圆弧标志:

  • 如果|∠SVE| > π/2,则flag = 0
  • 如果|∠SVE| < π/2,则flag = 1
    • 您正在确定以V为中心的角度是锐角还是钝角。
    • 如果那个角是“尖的”(即锐角),那么你将会沿着圆走最长的路线。

对于扫描标志:

  • 如果∠ESV > 0,则flag = 0
  • 如果∠ESV < 0,则flag = 1
    • 您正在确定S到E直线的哪一侧是V点。
    • 当您从S望向E时,如果V在右侧,那么您将会沿着圆逆时针移动。

请注意以下几点:

  • 构成角度的点的顺序很重要:对于大圆弧标志和扫描标志,顺序为S-V-E和E-S-V。
  • 所有角度必须在-π和π之间,即在-180°和180°之间。
  • 绝对值用于确定大圆弧标志,但不用于扫描标志。

本答案底部的演示代码展示了这些计算。直接回答OP问题的唯一重要代码是前两行:

const lgArcFl = (S,V,E) => Math.abs(angle(S,V,E)) > pi/2 ? 0 : 1;
const sweepFl = (S,V,E) =>          angle(E,S,V)  > 0    ? 0 : 1;
要使用演示,请点击“运行代码片段”按钮,然后按顺序3次单击矩形以定位起点(S)、中间点(V)和终点(E)。演示将计算半径,然后绘制可能的所有4个弧,即 0,0, 0,1, 1,01,1 的各种组合。大弧和小弧分别为蓝色和红色,而顺时针和逆时针弧分别为实线和虚线。正确的弧将用黄色突出显示。绘制完弧之后,您可以再次单击3次以重复操作。请注意,连续两次在同一位置点击或在一条直线上连续单击3次不会产生任何弧,这在几何上是可以预料的。

请注意,在我最初发布此答案时(2016年11月5日),演示可以在Chrome、Opera和Safari浏览器中工作,但无法在Firefox浏览器中工作。(我没有检查过Explorer或Edge,也没有检查任何移动端浏览器。)我怀疑这可能是因为我使用了ES6/ES2015代码。然而,在代码编辑器中点击“使用BabelJS / ES2015”按钮会使代码无法使用,原因我不明白。因此,如果您无法让演示工作,请尝试使用其他浏览器。

const lgArcFl = (S,V,E) => Math.abs(angle(S,V,E)) > pi/2 ? 0 : 1;
const sweepFl = (S,V,E) =>          angle(E,S,V)  > 0    ? 0 : 1;

const angle = ([a,b],[c,d],[e,f]) => (Math.atan2(f-d,e-c)-Math.atan2(b-d,a-c)+3*pi)%(2*pi)-pi;
const qs = sel => document.querySelector(sel), pi = Math.PI, pts = [];
const radius = ([a,b],[c,d],[e,f]) => {
  const g=c-a,h=2*(c-e)/g,i=d-b,j=c*c+d*d,k=j-a*a-b*b,l=(j-e*e-f*f-h*k/2)/(2*(d-f)-h*i);
  return Math.hypot(a+(i*l-k/2)/g,b-l);
};
const mkArc = (arc, [sx, sy], [ex, ey], r, lg, sw) => arc.setAttribute('d',
  `M ${sx} ${sy} A ${r} ${r} 0 ${lg} ${sw} ${ex} ${ey}`);
const calcArcs = (S,V,E) => {
  const args = [S, E, radius(S,V,E)];
  [[0,0],[0,1],[1,0],[1,1]].forEach(([lg,sw]) => mkArc(qs(`#arc${lg}${sw}`), ...args, lg, sw));
  mkArc(qs(`#arc`), ...args, lgArcFl(S,V,E), sweepFl(S,V,E));
};
let ptNum = 0;
qs('svg').addEventListener('click', evt => {
  const x = evt.x - 10, y = evt.y - 10;
  pts[ptNum] = [x, y];
  qs('#pt' + ptNum).setAttribute('transform', `translate(${x},${y})`);
  if (ptNum++ === 2) {
    calcArcs(...pts);
    ptNum = 0;
  }
});
text {
  font-family: courier;
  font-size: 18px;
  fill: black;
  stroke: none;
  transform: translate(-5px,5px);
}
#arc00, #arc01 {
  stroke: red;
}
#arc10, #arc11 {
  stroke: blue;
}
#arc00, #arc10 {
  stroke-width: 4;
  stroke-dasharray: 5,5;
}
#arc01, #arc11 {
  stroke-width: 2;
}
rect {
  fill: none;
  stroke: black;
  height: 200px;
  width: 600px;
}
<svg height="200" width="600">
  <defs>
    <path id="arc" />
    <circle id="circ" cx="0" cy="0" r="10" />
  </defs>
  <g fill="none">
    <use xlink:href="#arc" stroke="black" stroke-width="12" />
    <use xlink:href="#arc" stroke="#ff8"  stroke-width="10" />
    <path id="arc00" />
    <path id="arc01" />
    <path id="arc10" />
    <path id="arc11" />
  </g>
  <g fill="#ff8" stroke="black">
    <g id="pt0" transform="translate(-99,0)"><use xlink:href="#circ" /><text>S</text></g>
    <g id="pt1" transform="translate(-99,0)"><use xlink:href="#circ" /><text>V</text></g>
    <g id="pt2" transform="translate(-99,0)"><use xlink:href="#circ" /><text>E</text></g>
  </g>
  <rect />
</svg>


1

我最近在svg圆弧方面做了一些工作,并使用以下内容获取了圆弧扫描和d值。这可能会有所帮助。

//---x1,y1 and x2,y2 are the two end points---
    function polarToCartesian(centerX, centerY,radius, angleInDegrees)
    {
        var angleInRadians = (angleInDegrees) * Math.PI / 180.0;

        return {
        x: centerX + (radius * Math.cos(angleInRadians)),
        y: centerY + (radius * Math.sin(angleInRadians))
        };
    }
    var startAngle = 180/Math.PI*Math.atan2(y1-cy, x1-cx);
    var endAngle =  180/Math.PI*Math.atan2(y2-cy, x2-cx);

    StartPnt = polarToCartesian(cx, cy, radius, startAngle);
    EndPnt = polarToCartesian(cx, cy,  radius, endAngle);
    ArcSweep = endAngle - startAngle <= 180 ? "0" : "1";

    var d = [
    "M", StartPnt.x, StartPnt.y,
    "A", radius, radius, 0, ArcSweep, 0, EndPnt.x, EndPnt.y
    ].join(" ");

3
这不正确。SVG 弧是“A radx rady xrot-deg largearc sweep endx endy”。你所谓的 ArcSweep 其实是 largeArc 标志,如果角度大于 PI 则为“1”。sweep 标志决定了弧是顺时针还是逆时针绘制,并反射弧线与起点/终点之间的直线。 - Marvin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接