1. 無職の学び舎
  2. >
  3. 無職はゲーム数学の勉強をする
  4. >
  5. 直線と半直線の衝突

直線と半直線の衝突

ここでは直線と半直線の衝突についてまとめます。

直線と半直線の衝突は直線と直線の衝突点を調べる方法と同じ計算をしますのでまずそちらの記事を理解している前提で記載していきます。

また、今回の直線と半直線の判定について、互いに平行だった時は当たってないという扱いにしています。

平行なときでも、重なってたら当たっている事にしたいという場合は直線と直線の衝突にて、直線が重なっている場合の判定を記載しているのでそちらの記事を参照下さい。(直線と半直線でも判定方法は同じなので)

考え方

直線と直線の場合は、2つの直線が平行でなければ必ず重なるので当たっていると判定できました。

しかし、直線と半直線の場合、半直線は一方が端となって切れている線なので、平行じゃないからといって当たっているとは限りません。

これは平行じゃないけど当たっていないですよね。

では直線と半直線の衝突について考えていきます。

ひとまず、半直線の方向ベクトルを $\vec{v_{2}}$ 、交点を $P$ とします。

ここで、$\vec{v_{2}}$ をどれだけ伸ばせば $P$ に届くかという事を考えます。

このどれだけ伸ばせばという値を $t$ として、 $t$ 倍した $\vec{v_{2}}$ を $t\vec{v_{2}}$ と書いておきます。

そうすると、直線と半直線が重なっている場合、$t$ は必ずプラスの値にならないといけません。(マイナスの値だと $t\vec{v_{2}}$ が $P$ に届くことはないため)

半直線が逆を向いていたときのケースも見てみます。

この場合、$t$ はマイナスの値になるはずです。($\vec{v_{2}}$ を伸ばして直線にあてるにはマイナスをかけるしかないため)

というわけで、直線と半直線の衝突判定では、まず $t$ を求めて、$t$ がプラスかマイナスかで衝突判定を行います。

そしてこの $t$ をどうやって求めるかは直線と直線の衝突点に記載していますので詳しくはそちらの記事を参照ください。

こちらには概要だけ記載しておきます。

直線上の任意の点を $A$、半直線の始点を $B$、交点を$P$、$A$ から $B$ に向かうベクトルを $\vec{v}$、直線の方向ベクトルを $\vec{v_{1}}$、半直線の方向ベクトルを $\vec{v_{2}}$ とします。

この時、$t$ は以下の計算で求められます。

$t = \frac{ \vec{v} \times \vec{v_{1}} } { \vec{v_{1}} \times \vec{v_{2}} } $

また、直線と半直線の交点 $P$ は $B$ に $t \vec{v_{2}}$ を足した場所になるので

$P = B + t\vec{v_{2}}$

となります。

サンプルコード

/**
 * 直線と半直線の衝突
 * @param line 直線
 * @param ray 半直線
 */
function intercect(line:Line, ray:Ray) 
{
  // 衝突判定の結果オブジェクト
  const result = {
    hit: false,
    pos: Vector2.zero,
  }
 
  // 直線の向きをv1、半直線の向きをv2として取得
  const v1 = line.v;
  const v2 = ray.v;
 
  // 直線と半直線の外積が0なら平行なので、当たってない判定とする
  const cross = Vector2.cross(v1, v2);
  if (Math.abs(cross) < Define.EPSILON) return result;
 
  // v2をどれだけ伸ばしたら 直線上の点になるかの値を t として tを求める
  const v = Vector2.sub(ray.p, line.p);
  const t = Vector2.cross(v, v1) / cross;
 
  // t が マイナスだったら当たっていない
  if (t < 0) return result;
 
  // t がプラスなら当たっているので
  result.hit = true;
 
  // 衝突点は 半直線の始点に t*v2 を足した場所
  result.pos = Vector2.add(ray.p, v2.clone().times(t));
 
  return result;
}