学ぶ ・ 3D と映像の数学

コースの幾何学 — スタジアム曲線とベクトル

3D のコースは「直線 2 本と半円 2 つ」でできた曲線です。曲線を距離でパラメータ表示する方法、接線と法線、右回りへの鏡映、3D の向きを決める回転角まで、高校数学のベクトルで説明します。

このページで学ぶこと
  • 曲線を「スタートからの距離 ss」で表すパラメータ表示
  • 半円部分の点と接線 (進行方向) の式の導き方
  • 外向きの法線を「差」で求める方法
  • 左右反転 (鏡映) で右回りのコースを作る
  • 3D の物体を、あるベクトルの向きに回す角度 atan2⁡\operatorname{atan2}
  • このモデルでは「外を回る不利」がないこと

スタジアム曲線

実際の競馬場の形は場ごとに違いますが、3D では陸上トラックと同じ スタジアム曲線 で近似しています。

  • 直線の長さ SS … 各競馬場の「最後の直線」の値 (東京 525.9m など)
  • コーナーの半径 R=110+0.1 SR = 110 + 0.1\,S … 直線が長い競馬場ほど少し大きく
  • 1 周の長さ L=2S+2πRL = 2S + 2\pi R

東京なら R=110+52.59=162.59R = 110 + 52.59 = 162.59m、L=1051.8+1021.6=2073.4L = 1051.8 + 1021.6 = 2073.4m です (実際の東京の芝コースは 1 周 2,083m なので、近い値になっています)。

距離でのパラメータ表示

コース上の点を「内ラチ沿いにスタート地点から ss メートル進んだ点」で指定します。内ラチから外へ oo メートル離れた点の座標 P(s,o)=(x,z)P(s, o) = (x, z) は、4 つの区間で式が変わります (上から見て xx が右、zz が手前)。r=R+or = R + o と置きます。

区間ss の範囲位置 (x,z)(x, z)進行方向 TT
ホームストレッチ0≤s<S0 \le s < S(−S/2+s, r)(-S/2 + s,\ r)(1,0)(1, 0)
1・2 コーナーS≤s<S+πRS \le s < S + \pi R(S/2+rcos⁡p, rsin⁡p)(S/2 + r\cos p,\ r\sin p)(sin⁡p, −cos⁡p)(\sin p,\ -\cos p)
向こう正面S+πR≤s<2S+πRS + \pi R \le s < 2S + \pi R(S/2−u, −r)(S/2 - u,\ -r)(−1,0)(-1, 0)
3・4 コーナー残り(−S/2+rcos⁡p, rsin⁡p)(-S/2 + r\cos p,\ r\sin p)(sin⁡p, −cos⁡p)(\sin p,\ -\cos p)

1・2 コーナーでは p=π/2−(s−S)/Rp = \pi/2 - (s - S)/R、3・4 コーナーでは p=−π/2−(s−2S−πR)/Rp = -\pi/2 - (s - 2S - \pi R)/R、向こう正面では u=s−(S+πR)u = s - (S + \pi R) です。ss は LL で割った余りを使うので、1 周を超える距離 (3,000m など) も同じ式で扱えます。

else if (s < S + Math.PI * R) {
  const p = Math.PI / 2 - (s - S) / R;
  x = S / 2 + r * Math.cos(p); z = r * Math.sin(p); dx = Math.sin(p); dz = -Math.cos(p);
}

半円の式の導き方

中心 (S/2,0)(S/2, 0)、半径 rr の円周上の点は、角度 pp を使って (S/2+rcos⁡p, rsin⁡p)(S/2 + r\cos p,\ r\sin p) と書けます。コーナーの入り口 (s=Ss = S) で真下 (ホームストレッチ側、p=π/2p = \pi/2)、出口 (s=S+πRs = S + \pi R) で真上 (p=−π/2p = -\pi/2) なので、角度は π/2\pi/2 から −π/2-\pi/2 へ、ss に比例して減ります。内ラチ (半径 RR) に沿って長さ ℓ\ell 進むと角度は ℓ/R\ell / R (ラジアン) 変わるので

p=π2−s−SRp = \frac{\pi}{2} - \frac{s - S}{R}

進行方向は位置を pp で微分した向きの反対 (pp は減っていくため) で、

ddp(rcos⁡p, rsin⁡p)=(−rsin⁡p, rcos⁡p)  ⟹  T=(sin⁡p, −cos⁡p)\frac{d}{dp}(r\cos p,\ r\sin p) = (-r\sin p,\ r\cos p) \;\Longrightarrow\; T = (\sin p,\ -\cos p)

長さは sin⁡2p+cos⁡2p=1\sin^2 p + \cos^2 p = 1 なので、そのまま 単位ベクトル (長さ 1) です。ホームストレッチの終わり (p=π/2p = \pi/2) では T=(1,0)T = (1, 0) となり、直線部分の進行方向とつながります。

さらに深く外を回っても損をしない — このモデルの単純化

ss は内ラチ (半径 RR) に沿った距離です。外へ oo メートル離れた馬も同じ ss で位置が決まるので、コーナーでは半径 r=R+or = R + o の円を、内ラチの馬と同じ角度だけ進みます。実際に移動する距離は r/Rr/R 倍、つまり 外の馬ほど速く動いている ことになります。

o=10o = 10m、R=162.6R = 162.6m なら 6% 速い計算です。実際の競馬では外を回る馬はその分だけ長い距離を走るので不利 (「距離ロス」) ですが、このモデルでは走破タイムが先に決まっているので、外を回っても損をしません。映像の見た目にだけ影響する単純化です。

外向きの法線 — 「差」で求める

スタンドやカメラを「コースの外側」に置くには、外を向いた単位ベクトル (法線) NN が必要です。アプリは、同じ ss で oo を 0 と 1 にした 2 点の差を使っています。

outward(s) {
  const a = this.at(s, 0), b = this.at(s, 1);
  return new THREE.Vector3(b.x - a.x, 0, b.z - a.z).normalize();
}
N(s)=P(s,1)−P(s,0)∥P(s,1)−P(s,0)∥N(s) = \frac{P(s, 1) - P(s, 0)}{\lVert P(s, 1) - P(s, 0) \rVert}

直線部分では zz 方向に 1 ずれるだけ、コーナーでは半径方向に 1 ずれるので、どちらも「コースに垂直で外向き」になります。式を区間ごとに書き分けずに済むのが、この方法の利点です。

右回り — 鏡に映す

日本の競馬場は右回り (時計回り) が多く、東京・新潟・中京などは左回りです。上の式は左回りなので、右回りのコースは xx 座標の符号を反転して作ります。

return { x: x * mirror, z, dx: dx * mirror, dz };
(x,z)  ↦  (−x,z)(x, z) \;\mapsto\; (-x, z)

これは zz 軸を鏡とする 鏡映 です。鏡映は長さを変えないので、ss がそのまま距離を表すという性質は保たれます。一方で「時計回り / 反時計回り」は逆になるので、左回りの曲線が右回りの曲線になります。

スタートとゴールの位置

ゴールはホームストレッチの 85% の位置 G=0.85 SG = 0.85\,S に置き、スタートはそこから距離 DD だけ戻った点です。

sstart=(G−D) mod Ls_\text{start} = (G - D) \bmod L

東京 2,400m なら G=447.0G = 447.0、(447.0−2400) mod 2073.4=120.4(447.0 - 2400) \bmod 2073.4 = 120.4。スタート地点はホームストレッチ上、ゴール板の 326.6m 手前になり、1 周と少しを走ることになります (実際の東京芝 2,400m もスタンド前の発走で、1 周と少しです)。

ばんえい (帯広) は直線コースなので、コーナーを持たない (−S/2+s, o)(-S/2 + s,\ o) だけを使います。

3D の向き — atan2

3D の物体 (馬・発馬機・ゴール線) を、ある水平ベクトル v=(vx,vz)v = (v_x, v_z) の向きに回すには、yy 軸 (上下) まわりの回転角 θ\theta を求めます。yy 軸まわりに θ\theta 回すと、

(1,0,0)↦(cos⁡θ, 0, −sin⁡θ),(0,0,1)↦(sin⁡θ, 0, cos⁡θ)(1, 0, 0) \mapsto (\cos\theta,\ 0,\ -\sin\theta), \qquad (0, 0, 1) \mapsto (\sin\theta,\ 0,\ \cos\theta)

になります (three.js の右手系)。

  • 馬は前方が +z+z なので、(sin⁡θ,cos⁡θ)=(dx,dz)(\sin\theta, \cos\theta) = (d_x, d_z) を解いて θ=atan2⁡(dx,dz)\theta = \operatorname{atan2}(d_x, d_z)
  • 発馬機やゴール線は長い方が +x+x なので、(cos⁡θ,−sin⁡θ)=(vx,vz)(\cos\theta, -\sin\theta) = (v_x, v_z) を解いて θ=atan2⁡(−vz,vx)\theta = \operatorname{atan2}(-v_z, v_x)
const alongX = v => Math.atan2(-v.z, v.x);
// ...
h.root.rotation.y = Math.atan2(p.dx, p.dz);

atan2⁡(y,x)\operatorname{atan2}(y, x) は「点 (x,y)(x, y) の角度」を −π-\pi〜π\pi で返す関数で、arctan⁡(y/x)\arctan(y/x) と違って x=0x = 0 でも使え、4 つの象限を正しく区別できます。

まとめ
  • コースは直線 2 本 + 半円 2 つ。R=110+0.1SR = 110 + 0.1S、L=2S+2πRL = 2S + 2\pi R
  • 位置は内ラチ沿いの距離 ss と、内ラチからの距離 oo で決まる
  • 半円の進行方向 (sin⁡p,−cos⁡p)(\sin p, -\cos p) は、位置を微分して得られる単位ベクトル
  • 外向きの法線は「o=1o = 1 と o=0o = 0 の差」で、区間を問わず求まる
  • 右回りは xx の符号を反転 (鏡映) するだけ
  • 回転角は atan2⁡\operatorname{atan2}。このモデルでは外を回る距離ロスはない
練習問題阪神の 1 周

阪神 (直線 473.6m) の RR と LL を求めてください。

答えを見る

R=110+47.36=157.36R = 110 + 47.36 = 157.36m、L=947.2+2π×157.36=947.2+988.7=1935.9L = 947.2 + 2\pi \times 157.36 = 947.2 + 988.7 = 1935.9m。

練習問題コーナーのどこ?

東京 (S=525.9S = 525.9、R=162.59R = 162.59) で s=800s = 800 の点は、どの区間の何度の位置ですか。

答えを見る

S=525.9≤800<S+πR=1036.7S = 525.9 \le 800 < S + \pi R = 1036.7 なので 1・2 コーナー。p=π/2−(800−525.9)/162.59=1.5708−1.6858=−0.115p = \pi/2 - (800 - 525.9)/162.59 = 1.5708 - 1.6858 = -0.115 ラジアン (約 −6.6°)。コーナーのちょうど真ん中 (p=0p = 0) を少し過ぎた所です。