|
Last edited by hbghlyj 2023-3-7 22:28 calculate the start and end angles of an SVG path arc command
implementation note
椭圆上的任意点$(x,y)$可以表示为:
\[\begin{pmatrix}x\\y\end{pmatrix}=\begin{pmatrix}\cos\varphi&-\sin\varphi\\\sin\varphi&\cos\varphi\end{pmatrix}\pmatrix{r_x\\&r_y}\begin{pmatrix}\cos\theta\\\sin\theta\end{pmatrix}+\begin{pmatrix}c_x\\c_y\end{pmatrix}\]$(c_x,c_y)$为椭圆中心, $r_x,r_y$为椭圆半轴长, $\varphi$为椭圆轴的倾角, $\theta$为参数.
椭圆弧的起点$(x_1,y_1)$和终点$(x_2,y_2)$用$\theta_1$和$\theta_2=\theta_1+\Delta\theta$表示为:
\begin{gather*}
\begin{pmatrix}x_1\\y_1\end{pmatrix}=\begin{pmatrix}\cos\varphi&-\sin\varphi\\\sin\varphi&\cos\varphi\end{pmatrix}\pmatrix{r_x\\&r_y}\begin{pmatrix}\cos\theta_1\\\sin\theta_1\end{pmatrix}+\begin{pmatrix}c_x\\c_y\end{pmatrix}\\
\begin{pmatrix}x_2\\y_2\end{pmatrix}=\begin{pmatrix}\cos\varphi&-\sin\varphi\\\sin\varphi&\cos\varphi\end{pmatrix}\pmatrix{r_x\\&r_y}\begin{pmatrix}\cos\theta_2\\\sin\theta_2\end{pmatrix}+\begin{pmatrix}c_x\\c_y\end{pmatrix}
\end{gather*}
因为$\theta_{1,2}\in(-\pi,\pi]$, 定义$\Delta\theta=\theta_1-\theta_2$, 则 $-2\pi<\Delta\theta\le2\pi$.
large arc flag $f_A$定义为
\[f_A=\begin{cases}1&\text{if }\left|\Delta\theta\right|>\pi\\\text{0}&\text{if }\left|\Delta\theta\right|\leq\pi\end{cases}\]
sweep flag $f_S$定义为
\[f_S=\begin{cases}1&\text{if }\Delta\theta>0\\\text{0}&\text{if }\Delta\theta<0\end{cases}\]
给定
\[
x_1, y_1, x_2, y_2, f_A, f_S, r_x, r_y, φ
\]
如何求
\[
c_x, c_y, θ_1, \Delta θ
\]
在svg2tikz.py有下面的代码
- # The calc_arc function is based on the calc_arc function in the
- # paths_svg2obj.py script bundled with Blender 3D
- # Copyright (c) jm soler juillet/novembre 2004-april 2007,
- def calc_arc(cp: Point, r_i: Point, ang, fa, fs, pos: Point):
- """
- Calc arc paths
- """
- ang = math.radians(ang)
- r = Point(abs(r_i.x), abs(r_i.y))
- pos.x = abs((cos(ang) * (cp.x - pos.x) + sin(ang) * (cp.y - pos.y)) * 0.5) ** 2.0
- pos.y = abs((cos(ang) * (cp.y - pos.y) - sin(ang) * (cp.x - pos.x)) * 0.5) ** 2.0
- rp = Point(
- pos.x / (r.x**2.0) if abs(r.x) > 0.0 else 0.0,
- pos.y / (r.y**2.0) if abs(r.y) > 0.0 else 0.0,
- )
- p_l = rp.x + rp.y
- if p_l > 1.0:
- p_l = p_l**0.5
- r.x *= p_l
- r.y *= p_l
- car = Point(
- cos(ang) / r.x if abs(r.x) > 0.0 else 0.0,
- cos(ang) / r.y if abs(r.y) > 0.0 else 0.0,
- )
- sar = Point(
- sin(ang) / r.x if abs(r.x) > 0.0 else 0.0,
- sin(ang) / r.y if abs(r.y) > 0.0 else 0.0,
- )
- p0 = Point(car.x * cp.x + sar.x * cp.y, (-sar.y) * cp.x + car.y * cp.y)
- p1 = Point(car.x * pos.x + sar.x * pos.y, (-sar.y) * pos.x + car.y * pos.y)
- hyp = (p1.x - p0.x) ** 2 + (p1.y - p0.y) ** 2
- if abs(hyp) > 0.0:
- s_q = 1.0 / hyp - 0.25
- else:
- s_q = -0.25
- s_q = max(0.0, s_q)
- s_f = s_q**0.5
- if fs == fa:
- s_f *= -1
- c = Point(
- 0.5 * (p0.x + p1.x) - s_f * (p1.y - p0.y),
- 0.5 * (p0.y + p1.y) + s_f * (p1.x - p0.x),
- )
- ang_0 = atan2(p0.y - c.y, p0.x - c.x)
- ang_1 = atan2(p1.y - c.y, p1.x - c.x)
- ang_arc = ang_1 - ang_0
- if ang_arc < 0.0 and fs == 1:
- ang_arc += 2.0 * mpi
- elif ang_arc > 0.0 and fs == 0:
- ang_arc -= 2.0 * mpi
- ang0 = math.degrees(ang_0)
- ang1 = math.degrees(ang_1)
- if ang_arc > 0:
- if ang_0 < ang_1:
- pass
- else:
- ang0 -= 360
- else:
- if ang_0 < ang_1:
- ang1 -= 360
- return ang0, ang1, r
Copy the Code |
|