直線と線分の衝突
ここでは直線と線分の衝突についてまとめます。
直線と線分の衝突は直線と直線の交点の考え方が基本になりますので、まずはこちらの記事をお読みください。
また、今回の直線と線分の判定について、互いに平行だった時は当たってないという扱いにしています。
平行なときでも、重なってたら当たっている事にしたいという場合は直線と直線の衝突にて、直線が重なっている場合の判定を記載しているのでそちらの記事を参照下さい。(直線と半直線でも判定方法は同じなので)
考え方
直線と直線の場合は、2つの直線が平行でなければ必ず重なるので当たっていると判定できました。
しかし、直線と線分の場合、線分は長さのある線なので、平行じゃないからといって当たっているとは限りません。
これは平行じゃないけど当たっていないですよね。
では直線と線分の衝突について考えていきます。
ひとまず、線分の方向、及び長さを表すベクトルを $\vec{v_{2}}$ 、交点を $P$ とします。
ここで、$\vec{v_{2}}$ をどれだけ伸ばせば(もしくは縮めれば) $P$ に届くかという事を考えます。
このどれだけ伸ばせばという値を $t$ として、 $t$ 倍した $\vec{v_{2}}$ を $t\vec{v_{2}}$ と書いておきます。
そうすると、直線と線分が重なっている場合、$t$ は 0~1 の間の数にならなければいけません。
もし $t$ が 1を超えていたら、$t\vec{v_{2}}$ は 線分からはみ出してしまいますし、 $t$ が マイナスの場合も同様です。
実際に $t$ が 1を超えるケースを見てみましょう。
この場合、$t$ は 1を超える数になります。($\vec{v_{2}}$ を伸ばして直線にあてるには1倍以上にしないといけないため)
というわけで、直線と半直線の衝突判定では、まず $t$ を求めて、$t$ が0から1の範囲に収まっているかどうかを判定します。
そしてこの $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 seg 線分
*/
function intercect(line:Line, seg:Segment)
{
// 衝突判定の結果オブジェクト
const result = {
hit: false,
pos: Vector2.zero,
}
// 直線の向きをv1、線分の向きをv2として取得
const v1 = line.v;
const v2 = seg.v;
// 直線と線分の外積が0なら平行なので、当たってない判定とする
const cross = Vector2.cross(v1, v2);
if (Math.abs(cross) < Define.EPSILON) return result;
// v2をどれだけ伸ばしたら 直線上の点になるかの値を t として tを求める
const v = Vector2.sub(seg.p1, line.p);
const t = Vector2.cross(v, v1) / cross;
// t が 0 ~ 1 の範囲に収まっていなければ当たっていない
if (t < 0 || 1 < t) return result;
// t が 0~1なら当たっている
result.hit = true;
// 衝突点は 半直線の始点に t*v2 を足した場所
result.pos = Vector2.add(seg.p1, v2.clone().times(t));
return result;
}