I am a high school student trying to model a 2D Elastic Collision between two billiard balls. I used vector rotation as shown in this video and I also referenced this website as well. Despite that, the collisions weren't working specifically when the two balls collide having the same x value (a vertical collision).
As I tried to fix that edge case issue, I noticed that the change in momentum in the y direction gets larger and larger as the collision vector becomes more vertical, when it should be zero. This issue is only present with momentum in the y direction, not in the x direction.
My code (apologies in advance if it's too convoluted):
#main.py
import pygame
import numpy as np
from math import sin, cos, atan2, degrees as deg
g = 9.81
m = .17
r = 10
timeInterval = 1/60
def getValue(value):
absValue = np.abs(value)/value
return absValue
def checkCollison(Ball):
global prior
collided = []
instances = Ball.instances
for a in range(len(instances)):
for b in range(len(instances) - 1, a, -1):
ballA, ballB = instances[a], instances[b]
dist = ((ballA.x - ballB.x)**2 + (ballA.y - ballB.y)**2 + (ballA.z - ballB.z)**2)**0.5
if dist <= 2*r and not(f"{a}{b}" in collided) and dist != 0:
BallCollision(ballA, ballB, (ballA.vx, ballA.vy), (ballB.vx, ballB.vy))
collided.append(f"{a}{b}")
def BallCollision(b1, b2, u1, u2):
deltaX = b2.x - b1.x
deltaY = b2.y - b1.y
angle = atan2(deltaY, deltaX)
print(deg(angle))
# Rotate it to where the collision line is parallel to the horizontal
u1x = b1.vx * cos(angle) + b1.vy * sin(angle)
u1y = b1.vy * cos(angle) - b1.vx * sin(angle)
u2x = b2.vx * cos(angle) + b2.vy * sin(angle)
u2y = b2.vy * cos(angle) - b2.vx * sin(angle)
v1x = ((b1.m - b2.m) / (b1.m + b2.m)) * u1x + ((2 * b2.m) / (b1.m + b2.m)) * u2x
v1y = u1y
v2x = ((2 * b1.m) / (b1.m + b2.m)) * u1x + ((b2.m - b1.m) / (b1.m + b2.m)) * u2x
v2y = u2y
midpointX = (b1.x + b2.x) / 2
midpointY = (b1.y + b2.y) / 2
b1.x += (b1.x - midpointX)/2
b1.y += (b1.y - midpointY)/2
b2.x += (b2.x - midpointX)/2
b2.y += (b2.y - midpointY)/2
#Rotate back
v1x = v1x * cos(angle) - v1y * sin(angle)
v1y = v1y * cos(angle) + v1x * sin(angle)
v2x = v2x * cos(angle) - v2y * sin(angle)
v2y = v2y * cos(angle) + v2x * sin(angle)
print("change in x momentum: ", b1.vx + b2.vx - v1x - v2x)
print("change in y momentum: ", b1.vy + b2.vy - v1y - v2y)
b1.vx, b1.vy = v1x, v1y
b2.vx, b2.vy = v2x, v2y
class Ball(pygame.sprite.Sprite):
instances = []
def __init__(self, x, y, z, vx, vy, vz, ax, ay, az, m):
super(Ball, self).__init__()
self.__class__.instances.append(self)
self.x, self.y, self.z = x, y, z
self.vx, self.vy, self.vz = vx, vy, vz
self.ax, self.ay, self.az = ax, ay, az
self.m = m
def motion(self):
Ax = 0
Ay = 0
self.vx = self.vx + Ax * timeInterval
self.vy = self.vy + Ay * timeInterval
self.vz = self.vy #+ Az * timeInterval
self.x = self.x + self.vx * timeInterval + 1 / 2 * (Ax) * (timeInterval ** 2)
self.y = self.y + self.vy * timeInterval + 1 / 2 * (Ay) * (timeInterval ** 2)
self.z = self.y + self.vy * timeInterval #+ 1 / 2 * (Ay) * (timeInterval ** 2)
checkCollison(Ball)
#game.py
from main import *
WIDTH, HEIGHT = 1000, 500
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
FPS = 60
WHITE = (255, 255, 255)
ball1 = Ball(*(100,100,0,0,100,0,0,0,0, m))
ball2 = Ball(*(115,200,0,0,0,0,0,0,0, m))
def draw_window():
WIN.fill(WHITE)
ball1.motion()
ball2.motion()
pygame.draw.circle(WIN, (0,0,0), (ball1.x,ball1.y), r)
pygame.draw.circle(WIN, (0,0,0), (ball2.x,ball2.y), r)
pygame.display.update()
def main():
clock = pygame.time.Clock()
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
draw_window()
pygame.quit()
if __name__ == "__main__":
main()
The problem with your code lies in the way you rotate back your velocities in your BallCollision
function:
#Rotate back
v1x = v1x * cos(angle) - v1y * sin(angle)
v1y = v1y * cos(angle) + v1x * sin(angle)
v2x = v2x * cos(angle) - v2y * sin(angle)
v2y = v2y * cos(angle) + v2x * sin(angle)
In these lines you overwrite your variables for v1x
and v2x
but still try to use them in the next line for your rotation. However the fix is of course not difficult, you just need to use different variables names. For example the following code fixed "vertical" collisions for me:
#Rotate back
newv1x = v1x * cos(angle) - v1y * sin(angle)
newv1y = v1y * cos(angle) + v1x * sin(angle)
newv2x = v2x * cos(angle) - v2y * sin(angle)
newv2y = v2y * cos(angle) + v2x * sin(angle)
print("change in x momentum: ", b1.vx + b2.vx - newv1x - newv2x)
print("change in y momentum: ", b1.vy + b2.vy - newv1y - newv2y)
b1.vx, b1.vy = newv1x, newv1y
b2.vx, b2.vy = newv2x, newv2y