直線と円の交点
直線と円の交点の考え方
直線と円の交点の求め方は少し複雑なので、まずは基本となる考え方から解説します。
直線と円が交わっているとします。
この時、直線と円の交点は2箇所あるので、1つを $P$、もう一つを $P'$ とします。
円の中心を $C$、$C$ と直線の最近傍点を $H$ とします。
直線の方向を表すベクトルを正規化したもの(長さを1にしたもの)を ${\vec{n}}$ としておきます。
こうすると交点 $P$ は $H$ に ${\vec{n}}$ を伸ばしたベクトルを足した位置に来ます。
また交点 $P'$ は $H$ に $\vec{n}$ を逆向きに伸ばしたベクトルを足した位置に来ます。
$\vec{n}$ をどれだけ伸ばせばよいかは、$H$ から $P$ までの長さが分かればいけそうです。(求め方は後ほど)
手順が長いですが、直線と円はこのような流れで求めていきます。
直線と円の交点の計算
円の中心を $C$、$C$ と 直線の最近傍点を $H$、直線と円の交点を $P, P'$、円の半径を $r$、直線の向きベクトルを正規化したものを $\vec{n}$ とします。
まず$H$の座標を求めなければなりませんが、$H$ は円の中心($C$)と直線の最近傍点を求めれば求まります。
点と直線の最近傍点の求め方については、点と直線の最近傍点で詳しく解説していますので、そちらをご覧ください。
$H$ の座標がわかったら、次に $C$ から $H$に向かうベクトル $\vec{CH}$ を算出します。
$\vec{CH}$ は $H$ から $C$ の座標を引き算すれば求められますし、ベクトルが求まれば、そのベクトルの長さも計算でだせます。
$ \vec{CH} = H - C $
次に $HP$ の長さを求めます。(図に記載はないですが、便宜上、$HP$ の長さを $t$ と書くことにします)
ここで 点 $C, H, P$ を結んだ三角形に着目します。
$\triangle CHP$ は直角三角形であり、斜辺の長さは円の半径なので $r$、また三角形の高さは、先ほど求めたベクトル $\vec{CH}$ の長さ($|\vec{CH}|$)です。
知りたい $t$ は三角形の底辺($HP$)の長さに該当します。
直角三角形で2辺の長さが分かっていれば三平方の定理から残りの辺の長さを計算する事ができます。
三平方の定理:直角三角形の底辺を $a$、高さを$b$、斜辺を$c$ としたとき
$c^2 = a^2 + b^2$
これを $a$(底辺) について解くと
$c^2 = a^2 + b^2$
$a^2 = c^2 - b^2$
$a = \sqrt{c^2 - b^2}$
この式に代入して $HP$ の長さを求めます。
今回、底辺に該当するのが $t$($HP$の長さ)、高さが $|\vec{CH}|$、斜辺が $r$ とすると
$t = \sqrt{r^2 - |\vec{CH}|^2}$ となります。
ここまでくれば後は簡単です。
$P$ の位置は $H$ に $\vec{n}$ を $t$ 倍したベクトル ($t\vec{n}$) を足した位置になります。
$P'$ の位置は $H$ に $\vec{n}$ を $-t$ 倍したベクトル ($-t\vec{n}$) を足した位置になります。
まとめると以下になります。
$P = H + t\vec{n}$
$P' = H - t\vec{n}$
$t = \sqrt{r^2 - |\vec{CH}|^2}$
これで直線と円の交点が求まりました、しかし、残念ながら衝突判定的にはまだ考える事が残っています。
というのも、直線と円が接していないときは交点が存在しないですし、直線と円がぴったり接している時は交点が1しかありません。
なので、この直線と円が接していないとき、直線と円がぴったり接している時の判定について、それぞれ考えていきます。
直線と円が接していないとき
直線と円が接していないときは交点が存在しないので、交点を求める事ができません。
プログラム的には交点を求める前に、直線と円が接触しているかどうかを判定し、接触していなければそこで処理を抜けます。
直線と円が接触しているかの判定方法は直線と円の衝突に記載していますのでそちらの記事をご覧ください。
直線と円が接しているとき
直線と円が1点で接しているときは当然ですが交点が1つになります。
この場合、$CH$ の長さと円の半径が一致するはずです。
$|\vec{CH}| = r $ ならば 直線と円の交点は1つに定まる。
また直線と円の交点は $H$ になりますので、複雑な計算をする必要なく円の中心と直線の最近傍点を求めるだけとなります。
サンプルコード
/**
* 直線と円の衝突
* @param line 直線
* @param circle 円
*/
export function intercect(line:Line, circle:Circle)
{
// 衝突判定の結果オブジェクト
const result:IIntercectResult = {
hit: false,
pos: [],
nearest: Vector2.zero, // 最近傍点
}
// 円の中心を c とする
const c = circle.p;
// c と直線の最近傍点 h を求める
const h = PointAndLine.getNearestPoint(circle.p, line);
result.nearest = h;
// c -> h に向かうベクトルを hp とする
const hp = Vector2.sub(h, circle.p);
// hp の長さを hp_len とする
const hp_len = hp.magnitude;
// hp_len が 円の半径より大きければあたっていない
if (circle.r < hp_len) return result;
// ここにきたら直線と円はあたっている
result.hit = true;
// 円と直線の交点を求めていく
// まずは直線と円の交点が1つの場合を考える
// 直線と円が接している時、 hp_len === r であり、接点は中心と直線の最近傍点になる。
if (circle.r === hp_len)
{
result.pos.push(h);
return result;
}
// 直線と円が交差している(接点が2つあると場合)
// h から 接点までの距離を t とおき、三平方の定理から t を導く
const t = Math.sqrt(circle.r**2 - hp_len**2);
// 直線の方向ベクトルを正規化したものを t倍したベクトルを tv とすると
// 交点1:p + sv
// 交点2:p - sv
const tv = line.v.normalize.times(t);
result.pos.push(Vector2.add(h, tv));
result.pos.push(Vector2.sub(h, tv))
return result;
}