点と楕円の衝突
考え方
点と円の当たり判定は簡単でしたが、楕円との衝突判定になると突然難しくなります。
まず楕円のままでは判定が出来ないので、楕円を正円に戻す事を考えます。
グラフ右の[start]をクリックし楕円が正円に戻る様子をご覧ください。
- 楕円を原点に移動する(平行移動)
- 楕円を回転していない状態(軸と平行)に戻す。
- 楕円のy半径をx半径と同じになるように伸縮する。
この時、衝突判定をしたい点(●)に対して、楕円にしたことと同じことをします。
こうなると、ただの点と円の衝突判定になります。
しかも円は原点にあるため、単純に原点から点(●)の距離と、楕円の半径を比較するだけでよくなります。
実際には楕円を移動して正円に戻すというわけではなく、楕円を正円に戻すための移動、回転、伸縮をを点( ● )に対して行い、移動した点の座標と楕円のX半径を比較して判定します。
衝突判定
楕円の中心を$C(C_{x}, C_{y})$、$x半径$ を $r_{x}$、$y半径$ を $r_{y}$、回転量を $\theta$ 、衝突判定をとりたい点を $P(P_{x}, P_{y})$ とします。
楕円と $P$ の衝突を判定するために、楕円を正円に戻した際の $P'(P'_{x}, P'_{y})$ を求めます。
平行移動
楕円を原点に移動する平行移動は、楕円の中心座標を引けばよいので
$P'_{x} = P_{x} - C_{x}$
$P'_{y} = P_{y} - C_{y}$
となります。
回転
平行移動した$点P$ を 原点を中心に $-\theta$ 回転させます。
$P'_{x} = (P_{x} - C_{x})cos(-\theta) - (P_{y} - C_{y}) sin(-\theta)$
$P'_{y} = (P_{x} - C_{x})sin(-\theta) + (P_{y} - C_{y}) cos(-\theta)$
ベクトルの回転に関する計算方法はベクトルの回転に記載しています。
$\theta$ の符号を逆転させて 以下のように式を整理します。
$P'_{x} = (P_{x} - C_{x})cos(\theta) + (P_{y} - C_{y}) sin(\theta)$
$P'_{y} = -(P_{x} - C_{x})sin(\theta) + (P_{y} - C_{y}) cos(\theta)$
$\theta$ の符号を反転させた際の$sin$と$cos$は以下のように対応します、グラフで見るとわかりやすいかもしれません。
$sin(-\theta) = -sin(\theta)$
$cos(-\theta) = cos(\theta)$
拡縮
最後に楕円の $y半径$ を $x半径$ に合わせるための変換をかけます。
$y半径$ に何をかければ $x半径$ になるかを $t$ とすると、 $t$ は以下の計算より簡単に求まります。
$tr_{y} = r_{x} $
$t = \frac{r_{x}}{r_{y}}$
また拡縮するのは $y方向$ のみになるので、最終的な $P'$ は以下になります。
$P'_{x} = (P_{x} - C_{x})cos(\theta) + (P_{y} - C_{y}) sin(\theta)$
$P'_{y} = \frac{r_{x}}{r_{y}}(-(P_{x} - C_{x})sin(\theta) + (P_{y} - C_{y}) cos(\theta)) $
$P'$ が求まったので、後は 原点から $P'$ までの長さと、楕円の半径 $r_{x}$ を比較するだけになります。
サンプルコード
/**
* 点と楕円が当たっているかどうか
* @param point 点
* @param ellipse 楕円
*/
export function isHit(point:Vector2, ellipse:Ellipse)
{
// 点と楕円の衝突は、楕円を正円に戻した状態で判定する事を考える
// 楕円を平行移動して原点に戻し、
// 回転は逆回転して軸と平行にし
// Y半径をX半径に合わせるように伸縮させる
// 上記の作用を点に対して行い、原点から点の距離と楕円のX半径との距離を比較する
const p = point;
const e = ellipse;
const sin = Math.sin(e.rad);
const cos = Math.cos(e.rad);
const { rx, ry } = ellipse;
const px = (p.x - e.p.x) * cos + (p.y - e.p.y) * sin;
const py = (rx / ry) * (-(p.x - e.p.x) * sin + (p.y - e.p.y) * cos);
return (px * px + py * py) < rx * rx;
}