3次ベジェ曲線の制御点を求める
3次ベジェ曲線で円弧を近似します。
円弧は単位円の部分集合とし、円弧の角度を とします。
両端点 は円弧と一致するものとします。
両端点の接線ベクトルは円弧の接線ベクトルと方向が同じものであるとします。
が と同じ方向であることから とおくと、 となることが分かります。
(2024/12/10:記事を読み直していて、 の導出について書いたほうがええなって思ったので、本記事に追記しました。)
の 成分をそれぞれ とおきます。
曲線上の点 は円弧上にあるとします。
式 を変形していきます。
式 の分母ですが、 の範囲で ですが、 で となるので、 としてあります。
そこで、 のときは、式 を変形していきます。
式 を併せて、以下のように書けます。
式 により、 は にのみ依存することが分かりました。
が求まったので、3次ベジェ曲線の制御点の座標が定まりました。
円弧の中心からの距離が最大となるパラメータの値
円弧の中心からの距離が最大となるパラメータ の値を求めてみます。
円弧の中心は原点なので、距離の2乗は以下のようになります。
式 を で微分して と置きます。
はかなり複雑な式なります。(なので、記載しません(できません)。)
式 を について解いた結果は、以下となります。(この解法については、本記事の下側に記載します。)
式 の は円弧上の点であり、円弧の中心からの距離は極小値(最小値) を取ります。
の時、円弧の中心からの距離は極大値(最大値)を取ります。
式 は参考サイトと違って見えますが、合っていると思います。
度の時の を計算してみます。
角度 | 長さ |
45度 | 1.00000424552873 |
90度 | 1.00027253000743 |
135度 | 1.00314575375998 |
180度 | 1.01835015443463 |
225度 | 1.07638171145720 |
270度 | 1.27635594382687 |
上の表より、3次ベジェ曲線で円弧を近似するときはが90度ぐらいで、区切るのが良さそうです。
グラフは以下になります。
式 (3) のSymPyによる解法
式を立てた後に 式を解いた後に と戻しているのはそうしないと SymPy では答えがキレイにならないためです。
import sympy sympy.init_printing() sympy.var("px p0 p1 p2 p3 t theta kappa alpha") px = (1-t)**3 * p0 + 3 * (1-t) ** 2 * t * p1 + 3 * (1-t) * t ** 2 * p2 + t ** 3 * p3 px = px.subs([ (p0, sympy.cos(theta)), (p1, sympy.cos(theta) + kappa * sympy.sin(theta)), (p2, 1), (p3, 1), (t, sympy.Rational(1,2)) ]) eq = sympy.Eq(px, sympy.cos(theta * sympy.Rational(1,2))) eq = eq.subs([ (theta, 4 * alpha) ]) solve = sympy.solve (eq, kappa) simplified = solve[0].subs([ (alpha, theta * sympy.Rational(1,4)) ]) display(solve) display(simplified)
コードの実行結果
式 (7) のSymPyによる解法
SymPyでは式 は のままだと について解けないようです。(参考サイトによるとmaximaなら解けます。)
に適当な値を代入すれば について解くことができます。
import sympy sympy.init_printing() sympy.var("px py p0_x p0_y p1_x p1_y p2_x p2_y p3_x p3_y t theta kappa alpha") px = (1-t)**3 * p0_x + 3 * (1-t) ** 2 * t * p1_x + 3 * (1-t) * t ** 2 * p2_x + t ** 3 * p3_x py = (1-t)**3 * p0_y + 3 * (1-t) ** 2 * t * p1_y + 3 * (1-t) * t ** 2 * p2_y + t ** 3 * p3_y len2 = px ** 2 + py ** 2 len2 = len2.subs([ (p0_x, sympy.cos(theta)), (p0_y, sympy.sin(theta)), (p1_x, sympy.cos(theta) + kappa * sympy.sin(theta)), (p1_y, sympy.sin(theta) - kappa * sympy.cos(theta)), (p2_x, 1), (p2_y, kappa), (p3_x, 1), (p3_y, 0), (kappa, sympy.Rational(4,3) * sympy.tan(theta / 4)), (theta, sympy.pi * 0.5) # change here ]) len2d = sympy.diff(len2, t) len2d = sympy.simplify(len2d) eq = sympy.Eq(len2d, 0) solve = sympy.solve(eq, t) display(solve)
コードの実行結果
式 (10) のSymPyによる解法
import sympy sympy.init_printing() sympy.var("px py p0_x p0_y p1_x p1_y p2_x p2_y p3_x p3_y t theta kappa alpha") px = (1-t)**3 * p0_x + 3 * (1-t) ** 2 * t * p1_x + 3 * (1-t) * t ** 2 * p2_x + t ** 3 * p3_x py = (1-t)**3 * p0_y + 3 * (1-t) ** 2 * t * p1_y + 3 * (1-t) * t ** 2 * p2_y + t ** 3 * p3_y len2 = px ** 2 + py ** 2 len2 = len2.subs([ (p0_x, sympy.cos(theta)), (p0_y, sympy.sin(theta)), (p1_x, sympy.cos(theta) + kappa * sympy.sin(theta)), (p1_y, sympy.sin(theta) - kappa * sympy.cos(theta)), (p2_x, 1), (p2_y, kappa), (p3_x, 1), (p3_y, 0), (kappa, sympy.Rational(4,3) * sympy.tan(theta / 4)), (t, sympy.Rational(1,2) +(sympy.sqrt(3) / 6)) ]) len2 = sympy.simplify(len2) len = sympy.sqrt(len2) lensub = len.subs([ (theta, sympy.pi * 0.5), # change here ]) print(lensub.evalf()) print("\\begin{eqnarray}") print("&&" + sympy.latex(len2) + "\\\\") print("\\end{eqnarray}")
コードの実行結果
1.00027253000743
\begin{eqnarray}
&&\frac{10 \sin{\left(\theta \right)} \tan{\left(\frac{\theta}{4} \right)}}{27} - \frac{4 \cos{\left(\theta \right)} \tan^{2}{\left(\frac{\theta}{4} \right)}}{27} + \frac{11 \cos{\left(\theta \right)}}{54} + \frac{8 \tan^{2}{\left(\frac{\theta}{4} \right)}}{27} + \frac{43}{54}\\
\end{eqnarray}