点と直線の衝突
点と直線の衝突判定はベクトルの外積を使って判定します。
しかし動きを滑らかにするとまるで衝突しません。 これは点と点の衝突でも解説したように、座標が小数点以下の細かい数値が完全一致しないからです。
考え方
$\vec{a}$:直線の向きを表すベクトル
$\vec{b}$:直線上の任意の場所から●に向かうベクトル
上のグラフの●をドラッグして動かしてみて欲しいのですが、点が直線に近づくほど2つのベクトルが平行になっていくのがわかると思います。
点が直線上にある(つまり当たっている)かどうかは、2つのベクトルが平行になるっているかどうかを調べる事で判定します。
2つのベクトルが平行かどうか
端的に言ってしまえば、2つのベクトルの外積が0だったら、その2つのベクトルは平行になります。
なんで外積が0だと平行?
ベクトルの外積うんぬんについてはベクトルの外積を見てもらうとして、 外積の計算方法は以下のようになっています。
$\vec{ a } \times \vec{ b } = |\vec{ a }||\vec{ b }|sin(\theta) $
外積の計算式を見ると、最後に$\sin(\theta)$をかけています。
こいつがミソでして、2つのベクトルが平行なとき、2つのベクトルの間の角度は0度か180度になり、そして$sin(0°)$と$sin(180°)$はどちらも0なのです。
0をかけたらどんな値だって0になるので、2つのベクトルが平行なとき、外積は常に0になります。
結論
- ベクトルの外積が0ということは
- ベクトルが平行になっているということ
- ベクトルが平行になるというのは、点が直線の上にあるという事である。
- よって、ベクトルの外積が0の時、点と直線は衝突している。
サンプルコード
/**
* 点と直線の衝突判定
* @param p 点
* @param line 直線
*/
function intercect(p:Vector2, line:Line)
{
// 直線の方向を表すベクトルをaとする
const a = line.v;
// 直線上の1点からpに向かうベクトルをbとする(ベクトルの引き算で算出)
const b = Vector2.sub(p, line.p);
// aとbの外積を取る
const cross = Vector2.cross(a, b);
// 外積が0だったら当たっている
return (cross === 0);
}
外積が0と完全一致することは小数点誤差などもありほぼ起こりえない問題はありますが、外積の値が-0.01 ~ 0.01だったらのように判定に少し遊びを持たせると当たりやすくなります。
余談:$y=ax+b$の直線の式で判定できるか?
例えば、$y=3x+2$という直線の式があり、点A$(1, 1)$が直線と当たっているかどうか
これは直線の式に点の$x$の値を代入してみて、結果が点の$y$と一致するかどうかを見ればいけそうです。
- $y=3(1)+2$
- $y=3+2$
- $y=5$
点の$y$座標は1だが、直線の方程式の結果は5で異なるため点$(1, 1)$は直線$y=3x+2$の上にはないとわかります。
この方法は簡単で良さそうですが、$y=ax+b$の式だと垂直な直線が表現できないという問題があります。 垂直な直線以外であれば、この方法で判定できるが垂直が扱えないというのは残念なことです。