直線・平面の方程式とレイキャスティング
「銃弾は壁のどこに着弾したか?」FPSゲームの射撃判定とLiDARセンサーの衝突幾何学
- 1
- 直線のベクトル方程式 p = a + t d は、起点 a から方向ベクトル d に沿ってパラメータ t だけ進んだすべての点の軌跡を表す
- 2
- 平面の方程式 n · (p - a) = 0 は、平面上の基準点 a と法線ベクトル n に直交するすべての点 p の集合である
- 3
- FPSゲームの即着弾判定(Hitscan)や3DCGのレイトレーシングでは、直線の方程式を平面・球の方程式に連立代入し、未知数 t を解くことで着弾座標をミリ秒未満で算出している
なぜこれを学ぶのか? — 現実社会とテクノロジーでの価値
なぜ生まれたのか?
空間内を飛ぶ光線や弾丸が「どの障害物のどの地点に、何秒後に衝突するか」を代数方程式で瞬時に解くために生み出された。
身につく武器・思考力
幾何学的な直線をパラメータ t の1次関数として定式化し、平面の方程式と連立させて交点をミリ秒未満で算出するアルゴリズム思考が身につく。
実社会・ITでの活躍現場
- •『Apex』や『VALORANT』などFPSゲームの即着弾(Hitscan)判定
- •映画やゲームのリアルタイム・レイトレーシング(光線と物体の交差判定)
- •自動運転車や掃除ロボットのLiDARセンサーによる障害物検知
- •3DCG制作ソフトでマウスクリックした3Dオブジェクトの選択判定
Layer 1 — 直感・シミュレーター
銃口(光線の発射起点)と射撃角度、障害物(ターゲット壁)の角度や位置をスライダーで動かしてみよう。光線 が壁の平面と交差するパラメータ がリアルタイムに解かれ、着弾点に赤いターゲットマークが瞬時に表示されるレイキャスティングの幾何計算を体感できる。
光線 p(t) = (0, 0, 0) + t(1, 1, 0) が平面 x + y + z = 10 と交差するパラメータ t の値はいくつか?
Layer 2 — 定義・受験・証明
定義
空間内の直線と平面のベクトル方程式: - 直線のベクトル方程式: 通る点 、方向ベクトル に対し、媒介変数 を用いて
- 平面のベクトル方程式: 通る点 、法線ベクトル に対し、内積を用いて
公式・数理モデル
証明・導出
直線と平面の交点パラメータ の導出: 直線の式 を平面の方程式 に直接代入する。
内積の分配法則を展開すると、
ここで (光線が平面と平行でない)の場合、両辺を割ることで
が一意に求まる。 であれば前方に交点(着弾点)が存在し、交点座標は としてただちに決定される。
押さえておきたいポイント
- 直線の方程式における は「距離スケーリング係数」。 なら前方、 なら銃口の後ろを意味する
- のときは光線と壁が平行であり、交差しない(着弾しない)
- 平面の方程式の係数 は、そのまま「平面に直交する法線ベクトル」の成分を表している
- 点と平面の距離公式: も内積の射影から美しく導かれる
本トピックの内容・数理モデルは、文部科学省学習指導要領(算数・数学・情報)および国際標準カリキュラムに準拠し、エンジニアリング現場の実装事例と照らし合わせてMath World Lab編集部により検証・作成されています。
Layer 3 — 工学応用・現場実装
FPSゲームの射撃判定(Hitscan Raycasting)と3D着弾検出アルゴリズム
プレイヤーのカメラ位置 と視線方向 から放たれる無限光線(Ray)と、マップ内の壁ポリゴン(平面)との交点を計算し、最小の正の を持つ最近傍の壁に着弾痕(デカール)を生成するエンジンコード。
1type Vec3 = { x: number; y: number; z: number };23interface HitResult {4 hit: boolean;5 distanceT: number;6 hitPoint: Vec3;7}89// 内積10const dot = (a: Vec3, b: Vec3) => a.x * b.x + a.y * b.y + a.z * b.z;1112// 光線と平面の交差判定 (Ray-Plane Intersection)13function intersectRayPlane(14 rayOrigin: Vec3, // 銃口 a15 rayDir: Vec3, // 射撃方向 d (正規化済み)16 planePoint: Vec3, // 平面上の基準点 p017 planeNormal: Vec3 // 平面の単位法線 n18): HitResult {19 const denom = dot(planeNormal, rayDir);2021 // 分母がほぼ 0 の場合、光線と壁は平行(ヒットしない)22 if (Math.abs(denom) < 1e-6) {23 return { hit: false, distanceT: 0, hitPoint: { x: 0, y: 0, z: 0 } };24 }2526 // t = (n · (p0 - a)) / (n · d)27 const diff = { x: planePoint.x - rayOrigin.x, y: planePoint.y - rayOrigin.y, z: planePoint.z - rayOrigin.z };28 const t = dot(planeNormal, diff) / denom;2930 // t < 0 はプレイヤーの後ろ側にあるため除外31 if (t < 0) {32 return { hit: false, distanceT: t, hitPoint: { x: 0, y: 0, z: 0 } };33 }3435 // 着弾点: p = a + t * d36 return {37 hit: true,38 distanceT: t,39 hitPoint: {40 x: rayOrigin.x + t * rayDir.x,41 y: rayOrigin.y + t * rayDir.y,42 z: rayOrigin.z + t * rayDir.z,43 },44 };45}4647// 例: 原点から X軸方向に撃ち、X=50 にある壁にヒット48const origin = { x: 0, y: 1.5, z: 0 }; // 目の高さ 1.5m49const dir = { x: 1, y: 0, z: 0 }; // 真東に射撃50const wallPoint = { x: 50, y: 0, z: 0 };51const wallNormal = { x: -1, y: 0, z: 0 }; // プレイヤー向きの壁法線5253const hit = intersectRayPlane(origin, dir, wallPoint, wallNormal);54console.log(`着弾判定: ${hit.hit}, 距離: ${hit.distanceT.toFixed(1)}m`);55console.log(`着弾座標: (${hit.hitPoint.x}, ${hit.hitPoint.y}, ${hit.hitPoint.z})`);56// 着弾判定: true, 距離: 50.0m, 着弾座標: (50, 1.5, 0)
次に学ぶならコレ!おすすめトピック
この単元と深い繋がりを持つ、あわせて学ぶと理解が深まるおすすめ単元