コースの幾何学 — スタジアム曲線とベクトル 3D のコースは「直線 2 本と半円 2 つ」でできた曲線です。曲線を距離でパラメータ表示する方法、接線と法線、右回りへの鏡映、3D の向きを決める回転角まで、高校数学のベクトルで説明します。
曲線を「スタートからの距離 s s s 」で表すパラメータ表示
半円部分の点と接線 (進行方向) の式の導き方
外向きの法線を「差」で求める方法
左右反転 (鏡映) で右回りのコースを作る
3D の物体を、あるベクトルの向きに回す角度 atan2 \operatorname{atan2} atan2
このモデルでは「外を回る不利」がないこと
# スタジアム曲線
実際の競馬場の形は場ごとに違いますが、3D では陸上トラックと同じ スタジアム曲線 で近似しています。
直線の長さ S S S … 各競馬場の「最後の直線」の値 (東京 525.9m など)
コーナーの半径 R = 110 + 0.1 S R = 110 + 0.1\,S R = 110 + 0.1 S … 直線が長い競馬場ほど少し大きく
1 周の長さ L = 2 S + 2 π R L = 2S + 2\pi R L = 2 S + 2 π R
東京なら R = 110 + 52.59 = 162.59 R = 110 + 52.59 = 162.59 R = 110 + 52.59 = 162.59 m、L = 1051.8 + 1021.6 = 2073.4 L = 1051.8 + 1021.6 = 2073.4 L = 1051.8 + 1021.6 = 2073.4 m です (実際の東京の芝コースは 1 周 2,083m なので、近い値になっています)。
# 距離でのパラメータ表示
コース上の点を「内ラチ沿いにスタート地点から s s s メートル進んだ点」で指定します。内ラチから外へ o o o メートル離れた点の座標 P ( s , o ) = ( x , z ) P(s, o) = (x, z) P ( s , o ) = ( x , z ) は、4 つの区間で式が変わります (上から見て x x x が右、z z z が手前)。r = R + o r = R + o r = R + o と置きます。
区間 s s s の範囲位置 ( x , z ) (x, z) ( x , z ) 進行方向 T T T ホームストレッチ 0 ≤ s < S 0 \le s < S 0 ≤ s < S ( − S / 2 + s , r ) (-S/2 + s,\ r) ( − S /2 + s , r ) ( 1 , 0 ) (1, 0) ( 1 , 0 ) 1・2 コーナー S ≤ s < S + π R S \le s < S + \pi R S ≤ s < S + π R ( S / 2 + r cos p , r sin p ) (S/2 + r\cos p,\ r\sin p) ( S /2 + r cos p , r sin p ) ( sin p , − cos p ) (\sin p,\ -\cos p) ( sin p , − cos p ) 向こう正面 S + π R ≤ s < 2 S + π R S + \pi R \le s < 2S + \pi R S + π R ≤ s < 2 S + π R ( S / 2 − u , − r ) (S/2 - u,\ -r) ( S /2 − u , − r ) ( − 1 , 0 ) (-1, 0) ( − 1 , 0 ) 3・4 コーナー 残り ( − S / 2 + r cos p , r sin p ) (-S/2 + r\cos p,\ r\sin p) ( − S /2 + r cos p , r sin p ) ( sin p , − cos p ) (\sin p,\ -\cos p) ( sin p , − cos p )
1・2 コーナーでは p = π / 2 − ( s − S ) / R p = \pi/2 - (s - S)/R p = π /2 − ( s − S ) / R 、3・4 コーナーでは p = − π / 2 − ( s − 2 S − π R ) / R p = -\pi/2 - (s - 2S - \pi R)/R p = − π /2 − ( s − 2 S − π R ) / R 、向こう正面では u = s − ( S + π R ) u = s - (S + \pi R) u = s − ( S + π R ) です。s s s は L L L で割った余りを使うので、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) ( S /2 , 0 ) 、半径 r r r の円周上の点は、角度 p p p を使って ( S / 2 + r cos p , r sin p ) (S/2 + r\cos p,\ r\sin p) ( S /2 + r cos p , r sin p ) と書けます。コーナーの入り口 (s = S s = S s = S ) で真下 (ホームストレッチ側、p = π / 2 p = \pi/2 p = π /2 )、出口 (s = S + π R s = S + \pi R s = S + π R ) で真上 (p = − π / 2 p = -\pi/2 p = − π /2 ) なので、角度は π / 2 \pi/2 π /2 から − π / 2 -\pi/2 − π /2 へ、s s s に比例して減ります。内ラチ (半径 R R R ) に沿って長さ ℓ \ell ℓ 進むと角度は ℓ / R \ell / R ℓ / R (ラジアン) 変わるので
p = π 2 − s − S R p = \frac{\pi}{2} - \frac{s - S}{R} p = 2 π − R s − S
進行方向は位置を p p p で微分した向きの反対 (p p p は減っていくため) で、
d d p ( r cos p , r sin p ) = ( − r sin p , r cos 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) d p d ( r cos p , r sin p ) = ( − r sin p , r cos p ) ⟹ T = ( sin p , − cos p )
長さは sin 2 p + cos 2 p = 1 \sin^2 p + \cos^2 p = 1 sin 2 p + cos 2 p = 1 なので、そのまま 単位ベクトル (長さ 1) です。ホームストレッチの終わり (p = π / 2 p = \pi/2 p = π /2 ) では T = ( 1 , 0 ) T = (1, 0) T = ( 1 , 0 ) となり、直線部分の進行方向とつながります。
さらに深く 外を回っても損をしない — このモデルの単純化 s s s は内ラチ (半径 R R R ) に沿った距離です。外へ o o o メートル離れた馬も同じ s s s で位置が決まるので、コーナーでは半径 r = R + o r = R + o r = R + o の円を、内ラチの馬と同じ角度だけ進みます。実際に移動する距離は r / R r/R r / R 倍、つまり 外の馬ほど速く動いている ことになります。
o = 10 o = 10 o = 10 m、R = 162.6 R = 162.6 R = 162.6 m なら 6% 速い計算です。実際の競馬では外を回る馬はその分だけ長い距離を走るので不利 (「距離ロス」) ですが、このモデルでは走破タイムが先に決まっているので、外を回っても損をしません。映像の見た目にだけ影響する単純化です。
# 外向きの法線 — 「差」で求める
スタンドやカメラを「コースの外側」に置くには、外を向いた単位ベクトル (法線 ) N N N が必要です。アプリは、同じ s s s で o o o を 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} N ( s ) = ∥ P ( s , 1 ) − P ( s , 0 )∥ P ( s , 1 ) − P ( s , 0 )
直線部分では z z z 方向に 1 ずれるだけ、コーナーでは半径方向に 1 ずれるので、どちらも「コースに垂直で外向き」になります。式を区間ごとに書き分けずに済むのが、この方法の利点です。
# 右回り — 鏡に映す
日本の競馬場は右回り (時計回り) が多く、東京・新潟・中京などは左回りです。上の式は左回りなので、右回りのコースは x x x 座標の符号を反転して作ります。
return { x: x * mirror, z, dx: dx * mirror, dz };
( x , z ) ↦ ( − x , z ) (x, z) \;\mapsto\; (-x, z) ( x , z ) ↦ ( − x , z )
これは z z z 軸を鏡とする 鏡映 です。鏡映は長さを変えないので、s s s がそのまま距離を表すという性質は保たれます。一方で「時計回り / 反時計回り」は逆になるので、左回りの曲線が右回りの曲線になります。
# スタートとゴールの位置
ゴールはホームストレッチの 85% の位置 G = 0.85 S G = 0.85\,S G = 0.85 S に置き、スタートはそこから距離 D D D だけ戻った点です。
s start = ( G − D ) m o d L s_\text{start} = (G - D) \bmod L s start = ( G − D ) mod L
東京 2,400m なら G = 447.0 G = 447.0 G = 447.0 、( 447.0 − 2400 ) m o d 2073.4 = 120.4 (447.0 - 2400) \bmod 2073.4 = 120.4 ( 447.0 − 2400 ) mod 2073.4 = 120.4 。スタート地点はホームストレッチ上、ゴール板の 326.6m 手前になり、1 周と少しを走ることになります (実際の東京芝 2,400m もスタンド前の発走で、1 周と少しです)。
ばんえい (帯広) は直線コースなので、コーナーを持たない ( − S / 2 + s , o ) (-S/2 + s,\ o) ( − S /2 + s , o ) だけを使います。
# 3D の向き — atan2
3D の物体 (馬・発馬機・ゴール線) を、ある水平ベクトル v = ( v x , v z ) v = (v_x, v_z) v = ( v x , v z ) の向きに回すには、y y y 軸 (上下) まわりの回転角 θ \theta θ を求めます。y y y 軸まわりに θ \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) ( 1 , 0 , 0 ) ↦ ( cos θ , 0 , − sin θ ) , ( 0 , 0 , 1 ) ↦ ( sin θ , 0 , cos θ )
になります (three.js の右手系)。
馬は前方が + z +z + z なので、( sin θ , cos θ ) = ( d x , d z ) (\sin\theta, \cos\theta) = (d_x, d_z) ( sin θ , cos θ ) = ( d x , d z ) を解いて θ = atan2 ( d x , d z ) \theta = \operatorname{atan2}(d_x, d_z) θ = atan2 ( d x , d z )
発馬機やゴール線は長い方が + x +x + x なので、( cos θ , − sin θ ) = ( v x , v z ) (\cos\theta, -\sin\theta) = (v_x, v_z) ( cos θ , − sin θ ) = ( v x , v z ) を解いて θ = atan2 ( − v z , v x ) \theta = \operatorname{atan2}(-v_z, v_x) θ = 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) atan2 ( y , x ) は「点 ( x , y ) (x, y) ( x , y ) の角度」を − π -\pi − π 〜π \pi π で返す関数で、arctan ( y / x ) \arctan(y/x) arctan ( y / x ) と違って x = 0 x = 0 x = 0 でも使え、4 つの象限を正しく区別できます。
コースは直線 2 本 + 半円 2 つ。R = 110 + 0.1 S R = 110 + 0.1S R = 110 + 0.1 S 、L = 2 S + 2 π R L = 2S + 2\pi R L = 2 S + 2 π R
位置は内ラチ沿いの距離 s s s と、内ラチからの距離 o o o で決まる
半円の進行方向 ( sin p , − cos p ) (\sin p, -\cos p) ( sin p , − cos p ) は、位置を微分して得られる単位ベクトル
外向きの法線は「o = 1 o = 1 o = 1 と o = 0 o = 0 o = 0 の差」で、区間を問わず求まる
右回りは x x x の符号を反転 (鏡映) するだけ
回転角は atan2 \operatorname{atan2} atan2 。このモデルでは外を回る距離ロスはない
阪神 (直線 473.6m) の R R R と L L L を求めてください。
答えを見る R = 110 + 47.36 = 157.36 R = 110 + 47.36 = 157.36 R = 110 + 47.36 = 157.36 m、L = 947.2 + 2 π × 157.36 = 947.2 + 988.7 = 1935.9 L = 947.2 + 2\pi \times 157.36 = 947.2 + 988.7 = 1935.9 L = 947.2 + 2 π × 157.36 = 947.2 + 988.7 = 1935.9 m。
東京 (S = 525.9 S = 525.9 S = 525.9 、R = 162.59 R = 162.59 R = 162.59 ) で s = 800 s = 800 s = 800 の点は、どの区間の何度の位置ですか。
答えを見る S = 525.9 ≤ 800 < S + π R = 1036.7 S = 525.9 \le 800 < S + \pi R = 1036.7 S = 525.9 ≤ 800 < S + π R = 1036.7 なので 1・2 コーナー。p = π / 2 − ( 800 − 525.9 ) / 162.59 = 1.5708 − 1.6858 = − 0.115 p = \pi/2 - (800 - 525.9)/162.59 = 1.5708 - 1.6858 = -0.115 p = π /2 − ( 800 − 525.9 ) /162.59 = 1.5708 − 1.6858 = − 0.115 ラジアン (約 −6.6°)。コーナーのちょうど真ん中 (p = 0 p = 0 p = 0 ) を少し過ぎた所です。