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の移動ベクトル」を求めます。
- Bの移動ベクトルの直交(法線)ベクトルNを求める
- 法線ベクトルNを正規化(N')
- Aの移動ベクトルをVaとすると、(-Va・N')*N'で法線ベクトルへの-Vaの投影ベクトルPを求められる
- Va+Pで「衝突後のAの移動ベクトル」が求められる
という感じですね。
ただ問題は、
- ぶつけられるベクトルBを「動かない平面」として考えている
というところですね。
通常、相手がすごい勢いでぶつかってきたら、
自分はめちゃめちゃ吹っ飛びますw
それを今回はやってませんね。
あと、ボール同士の衝突時に「押し戻し」をしていないから、
物体同士の「めり込み」が発生し、移動量が蓄積して
スーパーボールのように飛んでいってしまいますが…(´Д`;
まあ、次回への課題ということで…。