主頁 | 自己紹介 | 日記 | 農業 | 台所 | 電算機 | | 本棚 | | Git

Xlibで遊んでみる4

前回: Xlibで遊んでみる3

言語: C言語
ソースコード: git

衝突判定とその処理

これまでは一つの四角形だけを描画していたが、今回は複数の四角形を作成して動かしてみた。ランダムな場所にランダムな運動量で動かして、他のものやウィンドウの縁とぶつかったら跳ね返るようにした。

回転しない四角形どうしの衝突判定は簡単である。x軸方向とy軸方向の両方に重なりがあれば衝突している:

struct square {
	float ppx, ppy; // previous position
	float px, py;   // current position
	float vx, vy;   // velocity
	int w, h;       // width and height
};

int
test_collision(struct square *s1, struct square* s2)
{
	return s1->px < s2->px + s2->w && s2->px < s1->px + s1->w &&
	       s2->py < s1->py + s1->h && s1->py < s2->py + s2->h;
}

衝突後の処理は多少めんどくさかった。衝突した時は既にめりこんでいるので、まずはそれぞれをめりこんだ距離の半分ずつずらして衝突を解消するようにした。この際、x軸方向にぶつかったのか、y軸方向にぶつかったのかで、それぞれの軸方向にひっぺがすようにしている。二つの四角形の各軸に関するめりこんだ距離lapxlapyと各軸に関する相対速度rel_vxrel_vyの比を比べればどちらの軸方向にぶつかったかが分かるはずである、多分 :

void
handle_collision_mm(struct square *s1, struct square *s2)
{
	if (!test_collision(s1, s2))
		return;

	float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px);
	float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py);
	float rel_vx = max(s1->vx - s2->vx, s2->vx - s1->vx);
	float rel_vy = max(s1->vy - s2->vy, s2->vy - s1->vy);

	if (lapx / rel_vx < lapy / rel_vy) {
		if (s1->px + s1->w < s2->px + s2->w / 2) {
			s1->px -= lapx / 2;
			s2->px += lapx / 2;
		} else {
			s1->px += lapx / 2;
			s2->px -= lapx / 2;
		}
	} else {
		if (s1->py + s1->h < s2->py + s2->h / 2) {
			s1->py -= lapy / 2;
			s2->py += lapy / 2;
		} else {
			s1->py += lapy / 2;
			s2->py -= lapy / 2;
		}
	}
}

衝突は弾性衝突として、衝突したそれぞれの四角形の速度を更新した。質量は四角形の面積として計算している。衝突後の速度はエネルギー保存則と運動量保存則から導いたのでしんどかった。

void
handle_collision_elastic(struct square *s1, struct square *s2)
{
	if(!test_collision(s1, s2))
		return;

	float v1, v2;
	float m1 = s1->w * s1->h;
	float m2 = s2->w * s2->h;

	float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px);
	float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py);

	if (lapx < lapy) {
		v1 = s1->vx;
		v2 = s2->vx;
		s1->vx = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1;
		s2->vx = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2;
	} else {
		v1 = s1->vy;
		v2 = s2->vy;
		s1->vy = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1;
		s2->vy = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2;
	}

	handle_collision_mm(s1, s2);
}

サブティック

この名前が適切かどうか分からないが、前のフレームから次のフレームまでの時間をさらに何等分かして衝突判定の制度を上げた(マクロは括弧でかこって分かりにくいバグを防げとどこかに書いていたのでそうすることにした):

#define SUB_TIC (4)

void
game_play(void)
{
	/* ... */
	while (next_menu == GAME_PLAY) {
		/* ... */
		for (int j = 0; j < SUB_TICK; j++) {
			for (int i = 0; i < NUM_SQUARE; i++)
				next_tick(&square[i], 1000 * 1000 * 1000 / FPS / SUB_TICK);

			for (int i = 0; i < NUM_SQUARE; i++)
				for (int j = i + 1; j < NUM_SQUARE; j++) {
					handle_collision_elastic(&square[i], &square[j]);
					/* ... */
				}
			/* ... */
		}
		/* ... */
	}
	/* ... */
}

完成品

git

参考

次の記事: Xlibで遊んでみる5