0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

放物線を二次ベジェ曲線で表してみよう。【python】【SVG pathタグ Qコマンド】

Last updated at Posted at 2025-04-07

はじめに

理系教科の指導にあたってしばしば問題の作図が必要とされます。私はよくsvgファイルを手打ちして作図するのですが、pathタグ内で放物線を描画するコマンドはありません。そこで、二次ベジェ曲線コマンドQを用いることで解決しようと思います(画期的だと思いましたが元をたどると自明な話でしたね(笑))。描画したい放物線の始点と終点と頂点の情報から、その放物線を二次ベジェ曲線で表したときの制御点を求め、その放物線の描画コマンドを出力するプログラムをpythonで作成します。

二次ベジェ曲線について軽い説明

二次ベジェ曲線は
座標平面上の3点(始点$P_0$, 制御点$P_1$, 終点$P_2$)を用いた

\vec{B}(t) = (1 - t)^2 \vec{P}_0 + 2(1 - t)t \vec{P}_1 + t^2 \vec{P}_2

の式で表されます( $t$ は $0 \leqq t \leqq 1$を満たす媒介変数)。
$N+1$個の制御点をもつベジェ曲線を$N$次曲線といいます。ベクターグラフィックなどでしばしば用いられます。

放物線(二次関数)について軽い説明

放物線(二次関数)は一般形$y=ax^2+bx+c$、標準形$y=a(x-p)^2+q$などで表される図形です。標準形は比例定数$a$によって放物線の凸の方向やその緩やかさが読み取れるほか、頂点が$(p, q)$で表せるので非常に便利です。

SVG(XML)でのQコマンドの仕様

Qコマンドは二次ベジェ曲線を描画します。現在の座標から

Q <制御点のx座標> <制御点のy座標> <終点のx座標> <終点のy座標>

で描画できます。y軸は下方向が正となります。

本題

始点と終点と頂点を指定して制御点を求める

始点と終点および頂点から、制御点を求め、目標の放物線のコマンドを出力するプログラムを作ってみます。注意点として、指定した始点と終点を通り、指定した頂点を頂点に持つ放物線が存在することが条件です。

svg出力.py
def InputPos(PosName):
    RtnList = []
    while len(RtnList) != 2:
        RtnList = list(map(float, input(PosName + "の座標を空白区切りで指定:").split()))
        if len(RtnList) != 2: print("正しく入力してください。")
    return RtnList
P_0 = InputPos("始点")
P_2 = InputPos("終点")
Peak = InputPos("頂点")

a = (P_0[1] - P_2[1]) / ((P_0[0] - P_2[0]) * (P_0[0] + P_2[0] - 2 * Peak[0])) if P_0[0] == P_2[0] else 1
sign = (a * (P_0[0] - Peak[0]) * (P_2[0] - Peak[0])) / abs(a * (P_0[0] - Peak[0]) * (P_2[0] - Peak[0])) if P_0[0] != Peak[0] else 1
P_1 = [0.5 * (P_0[0] + P_2[0]), sign * ((P_0[1] - Peak[1]) * (P_2[1] - Peak[1])) ** 0.5 + Peak[1]]
print("\n-----目標のQコマンド-----")
print(f"M {P_0[0]} {P_0[1]} Q {P_1[0]} {P_1[1]} {P_2[0]} {P_2[1]}")
print("-------------------------")

こんな感じでいいのではないでしょうか。
始点$P_0(x_0, y_0)$および終点$P_2(x_2, y_2)$とすると、制御点$P_1$は

P_1\left( \frac{x_0 + x_2}{2},\ \pm\sqrt{(y_0 - q)(y_2 - q)} + q \right)

と表されます。ここでy座標の符号については、aの符号及び放物線の軸が始点と終点のx座標の間に入っているかどうかによって変化します。もう一度書きますが、注意点として、このプログラムが正しく機能する条件は、始点と終点および頂点が正しい場合です。

正しい例

$P_0(-5, 5)$, $P_2(0, 7.5)$, 頂点$(-3, 3)$の場合
この場合は該当する放物線が存在するため、正しい制御点$P_1(-2.5, 0)$を出力してくれます。

間違った例

$P_0(-5, 5)$, $P_2(0, 10)$, 頂点$(-3, 3)$の場合
この場合、該当する放物線は存在せず、間違った制御点を出力します。

この他にも正しい出力がされない例として、頂点が始点と終点のy座標の間にある場合があり、バリデーションを施していないことに注意してください。

私はこの条件で満足なのですが、もう少し使いやすいプログラムも書いてみたいと思います。

始点と終点と頂点のx座標のみ(=軸)を指定して制御点を求める

次は、始点と終点および頂点のx座標のみ(=軸)を指定することにします。

svg出力(改良).py
def InputPos(PosName):
    RtnList = []
    while len(RtnList) != 2:
        RtnList = list(map(float, input(PosName + "の座標を空白区切りで指定:").split()))
        if len(RtnList) != 2: print("正しく入力してください。")
    return RtnList
P_0 = InputPos("始点")
P_2 = InputPos("終点")
p = float(input("頂点のx座標(=軸)を指定:"))

if P_0[0] == p and P_2[0] == p: print("指定した3点が同じ点です。")
else:
    a: float
    q: float
    if P_0[1] == P_2[1]:
        q = float(input("頂点のy座標を指定:"))
        a = (P_0[1] - q) / ((P_0[0] - p) ** 2)
    else:
        a = (P_0[1] - P_2[1]) / ((P_0[0] - P_2[0]) * (P_0[0] + P_2[0] - 2 * p))
        q = P_0[1] - a * ((P_0[0] - p) ** 2)
    Peak = [p, q]
    sign = (a * (P_0[0] - Peak[0]) * (P_2[0] - Peak[0])) / abs(a * (P_0[0] - Peak[0]) * (P_2[0] - Peak[0])) if P_0[0] != Peak[0] else 1
    P_1 = [0.5 * (P_0[0] + P_2[0]), sign * ((P_0[1] - Peak[1]) * (P_2[1] - Peak[1])) ** 0.5 + Peak[1]]
    print("\n-----目標のQコマンド-----")
    print(f"M {P_0[0]} {P_0[1]} Q {P_1[0]} {P_1[1]} {P_2[0]} {P_2[1]}")
    print("-------------------------")

こうすることで、頂点のy座標は勝手に見つけてくれますし、間違った出力もされません。
一応説明を付しておくと、$y=a(x-p)^2+q$式における$a$と$q$を求めています。
始点$P_0(x_0, y_0)$および終点$P_2(x_2, y_2)$として、

a =\frac{y_0 + y_2}{(x_0 - x_2)(x_0 + x_2 - 2p)}
q = y_0 - a(x_0 - p)^2

です。前式は2式 $y_0=a(x_0-p)^2+q$ および $y_2=a(x_2-p)^2+q$ を連立させ$a$について解くと導かれ、後式は$y_0=a(x_0-p)^2+q$を$q$について解くと導かれます。

さいごに

いい感じですね。二次関数が一般形で表されている場合は、さらに頂点を求めるプログラムもあればいいかと思います。
それでは、みなさんも良き放物線ライフを送りましょう🌈🌈

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?