ZerOx4Cの日記だったもの

インポートだけしました

媒介変数を使ってすり抜けない当たり判定

事の始まりはWikipediaをウロウロしていたとき、
衝突判定 - Wikipedia
ここの一番下の方に動く球の衝突判定について、媒介変数t(0 < t < 1)を使って云々と書かれているのを発見。
ここで、そうじゃん媒介変数じゃん!と思いついたわけです。


Wikipediaが言ってるのは、t(0 < t < 1)で1フレームを無限に分割して衝突の起こる時間範囲を見つけるということ。
そのフレームの何割の部分で衝突が始まり、そして終わるのかを媒介変数を使うと求めることができるよということです。
この考え方は球だけでなく円や矩形にも利用できるので、とりあえず手っ取り早い矩形に利用してみようと思いました。


やっていることは、通常の判定の座標の部分に、速度 * tを含めて計算するということです。
そして、tが0より大きくて1より小さいときに衝突すると判定された場合、そのフレーム内で衝突しているということが分かります。
このときのtの最小値が衝突開始のタイミングなので、速度 * tの最小値を新しい速度にすると、めり込むことも無くなります。すごい!


前提として、AxはAのX座標、AvxはAの速度のX成分、AwはAの幅、AhはAの高さを表すとします。
(あとのAyとかBxとかはいちいち書く必要ないよね。)


ここで「すり抜ける当たり判定」の条件はこうなると思います。
(Ax-Bw\;<\;Bx)\;\wedge\;(Bx\;<\;Ax+Aw)\;\wedge\;(Ay-Bh\;<\;By)\;\wedge\;(By\;<\;Ay+Ah)
Wikipediaのと形は少々違うけど同じ式です。


この条件に速度*tを加えると、
(Ax+Avx*t-Bw\;<\;Bx+Bvx*t)\;\wedge\;(Bx+Bvx*t\;<\;Ax+Avx*t+Aw)\;\wedge\;(Ay+Avy*t-Bh\;<\;By+Bvy*t)\;\wedge\;(By+Bvy*t\;<\;Ay+Avy*t+Ah)


tを求めるように式変形すると、
(t(Avx-Bvx)\;<\;Bx-Ax+Bw)\;\wedge\;(Bx-Ax-Aw\;<\;t(Avx-Bvx))\;\wedge\;(t(Avy-Bvy)\;<\;By-Ay+Bh)\;\wedge\;(By-Ay-Ah\;<\;t(Avy-Bvy))
ここで(Avx-Bvx)(Avy-Bvy)で割るんだけど、符号によって不等号が反転するので面倒だけど頑張る・・・と何パターンも書く必要があるので、ここは端折って
(t\;<\;P)\;\wedge\;(Q\;<\;t)\;\wedge\;(t\;<\;R)\;\wedge\;(S\;<\;t)
ってなる。PQRSの中身は、\frac{Bx-Ax+Bw}{Avx-Bvx}\frac{Bx-Ax-Aw}{Avx-Bvx}\frac{By-Ay+Bh}{Avy-Bvy}\frac{By-Ay-Ah}{Avy-Bvy}のいずれか。


最終的に、4つの式と[tex:0

Vector deltaVelocity = A.Velocity - B.Velocity; // 分母は何回も出てくるので予め計算
float xtMin, xtMax, ytMin, ytMax, tMin, tMax;

xtMin = (B.Location.X - A.Location.X - A.Size.Width) / deltaVelocity.X; // X軸についてのtの上限
xtMax = (B.Location.X - A.Location.X + B.Size.Width) / deltaVelocity.X; // X軸についてのtの下限
// 分母の符号が負だった場合の不等号反転への対処
if (deltaVelocity.X < 0) {
	float swap = xtMin;
	xtMin = xtMax;
	xtMax = swap;
}

ytMin = (B.Location.Y - A.Location.Y - A.Size.Height) / deltaVelocity.Y; // Y軸についてのtの上限
ytMax = (B.Location.Y - A.Location.Y + B.Size.Height) / deltaVelocity.Y; // Y軸についてのtの下限
// 分母の符号が負だった場合の不等号反転への対処
if (deltaVelocity.Y < 0) {
	float swap = ytMin;
	ytMin = ytMax;
	ytMax = swap; // ytMinになっていたのをmiyoshiさんの指摘で訂正しました。やっちまったぜ☆
	// http://d.hatena.ne.jp/ZerOx4C/20101019/1287421365 にて発見、訂正していたのを思い出しましたw
	// http://d.hatena.ne.jp/ZerOx4C/20101020/1287573482 に完成版を載せてあるので
}

// 下限同士、上限同士で範囲を絞り込む
tMin = Math.Max(xtMin, ytMin);
tMax = Math.Min(xtMax, ytMax);

// tMin < t < tMax と 0 < t < 1 に共通範囲があれば衝突
if (tMin < tMax && tMin < 1 && 0 < tMax) {
	// この下にある速度調整処理用にtの下限が0未満の場合は0に設定
	tMin = tMin < 0 ? 0 : tMin;
	xtMin = xtMin < 0 ? 0 : xtMin;
	ytMin = ytMin < 0 ? 0 : ytMin;

	// X軸方向の衝突の場合、速度のX成分のみを補正
	if (tMin == xtMin) {
		A.Velocity.X *= tMin;
		B.Velocity.X *= tMin;
	}
	// Y軸方向の衝突の場合、速度のY成分のみを補正
	if (tMin == ytMin) {
		A.Velocity.Y *= tMin;
		B.Velocity.Y *= tMin;
	}
	// ※ちなみに、お互いに頂点同士が衝突した場合はX成分、Y成分の両方を補正している
}

とりあえずこれでAとBがぶつかったらピッタリに押し戻される処理になってると思う。一回書いたから多分大丈夫。
そしてこの記事を書くのに疲れました。