2次元の2つの動いている物体の衝突応答

衝突テスト

ゲーム開発のための数学・物理学入門 Beginning Math and Physics for Game Programmersを参考に作ってみました。
 

import pygame
import math

class Point2D:
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def __add__(self, pos):
		return Point2D(self.x + pos.x, self.y + pos.y)
	def __iadd__(self, pos):
		return Point2D(self.x + pos.x, self.y + pos.y)
	def __sub__(self, pos):
		return Point2D(self.x - pos.x, self.y - pos.y)
	def __mul__(self, vec):
		return self.x*vec.x + self.y*vec.y
	def __eq__(self, vec):
		return (self.x==vec.x and self.y==vec.y)
	def mulScalar(self, scalar):
		return Point2D(self.x*scalar, self.y*scalar)
	def nomalize(self):
		d = math.sqrt(self.x ** 2 + self.y ** 2) * 1.0
		if(d == 0): d = 1
		return Point2D(self.x / d, self.y / d)
	def getOrthogonalVector(self):
		return Point2D(self.y, -self.x)
	def reverse(self):
		return Point2D(self.x*-1, self.y*-1)
	def getTuble(self):
		return (self.x, self.y)
	def debug(self):
		print "(x,y)=(%f,%f)" % (self.x, self.y)

class Ball:
	def __init__(self, cPos, m, r, surface):
		self.cPos = cPos # center postion
		self.prevPos = Point2D(0, 0) # previous position
		self.mov = Point2D(10, 0) # move
		self.m = m # molarity
		self.r = r # radius
		self.hold = False # mouse hold
		self.color = ()
		self.surface = surface
	def checkHold(self, pressed):
		if(pressed):
			mouseX, mouseY = pygame.mouse.get_pos()
			if(abs(mouseX - self.cPos.x) < self.r
					and abs(mouseY - self.cPos.y) < self.r):
				self.hold = True
		else:
			if(self.hold):
				self.mov = self.cPos - self.prevPos
			self.hold = False
	def checkCollision(self, ball):
		if(self.cPos == ball.cPos): return
		
		if(pow(self.cPos.x - ball.cPos.x, 2) + pow(self.cPos.y - ball.cPos.y, 2)
				<= pow(self.r + ball.r, 2)):
			nVec = ball.mov.getOrthogonalVector().nomalize()    # nomal
			projVec = nVec.mulScalar(self.mov.reverse() * nVec) # projection
			vVec = self.mov + projVec
			self.mov = vVec + projVec
	def update(self):
		self.prevPos = Point2D(self.cPos.x, self.cPos.y)
		# mouse hold
		if(self.hold):
			self.color = (255, 0, 0)
			x, y = pygame.mouse.get_pos()
			self.cPos = Point2D(x, y)
		else:
			self.mov += GRAVITY.mulScalar(self.m)
			self.color = (0, 0, 255)
			self.prevColor = (0, 0, 127)
			self.cPos += self.mov
		# hitcheck(wall)
		if(self.cPos.x < 0 + self.r):
			self.mov.x *= -0.7
			self.cPos.x = self.r
		if(self.cPos.y < 0 + self.r):
			self.mov.y *= -0.7
			self.cPos.y = self.r
		if(self.cPos.x > SCREEN.x - self.r):
			self.cPos.x = SCREEN.x - self.r
			self.mov.x *= -0.7
		if(self.cPos.y > SCREEN.y - self.r):
			self.cPos.y = SCREEN.y - self.r
			self.mov.x *= 0.9
			self.mov.y *= -0.7
		# draw
		pygame.draw.ellipse(self.surface, self.prevColor, (self.prevPos.x-self.r, self.prevPos.y-self.r, self.r*2, self.r*2))
		pygame.draw.ellipse(self.surface, self.color,     (self.cPos.x-self.r, self.cPos.y-self.r, self.r*2, self.r*2))

GRAVITY = Point2D(0, 9.8)
SCREEN = Point2D(480, 320)

def main():
	pygame.init()
	pygame.display.set_mode(SCREEN.getTuble(), 0, 32)
	screen = pygame.display.get_surface()
	#screen.set_alpha(64)
	ball1 = Ball(Point2D(0,   0), 0.005, 16, screen)
	ball2 = Ball(Point2D(100, 0), 0.01,  16, screen)
	ball3 = Ball(Point2D(200, 0), 0.02,  16, screen)
	ball4 = Ball(Point2D(300, 0), 0.04,  16, screen)
	ball5 = Ball(Point2D(400, 0), 0.08,  16, screen)

	ballList = [ball1, ball2, ball3, ball4, ball5]
	while True:
		for e in pygame.event.get():
			if(e.type == pygame.QUIT): return
		pygame.display.update()
		pygame.time.wait(10)
		screen.fill((0, 0, 0))
		
		mouseL, mouseR, mouseM = pygame.mouse.get_pressed()
		i = 0
		for ball in ballList:
			ball.update()
			ball.checkHold(mouseL)
			for j in range(len(ballList)):
				ballList[i].checkCollision(ballList[j])
			i += 1

if __name__ == "__main__":
	main()

マウスの左クリックでつかんで投げられます。
 
 
処理のポイントは、2つのボールの衝突検知・応答ですね。
 
ソースとしてはBallクラスのcheckCollisionメソッドです。
 
処理の中身としては、
ぶつけるボールをA、ぶつけられるボールをBとします。
 
検知は簡単です。
(Bの中心のX座標−Aの中心のX座標)^2+(Bの中心のX座標−Aの中心のX座標)^2≦(Aの半径+Bの半径)^2
を満たせば、衝突していますね。
 
で、応答なのですが、以下の手順で「衝突後のAの移動ベクトル」を求めます。

  1. Bの移動ベクトルの直交(法線)ベクトルNを求める
  2. 法線ベクトルNを正規化(N')
  3. Aの移動ベクトルをVaとすると、(-Va・N')*N'で法線ベクトルへの-Vaの投影ベクトルPを求められる
  4. Va+Pで「衝突後のAの移動ベクトル」が求められる

という感じですね。
 
ただ問題は、

  • ぶつけられるベクトルBを「動かない平面」として考えている

というところですね。
 
通常、相手がすごい勢いでぶつかってきたら、
自分はめちゃめちゃ吹っ飛びますw
 
それを今回はやってませんね。
 
あと、ボール同士の衝突時に「押し戻し」をしていないから、
物体同士の「めり込み」が発生し、移動量が蓄積して
スーパーボールのように飛んでいってしまいますが…(´Д`;
 
 
まあ、次回への課題ということで…。