Reputation: 318
I am trying to simulate an elastic collision in pygame between two balls. The problem is, the balls sometimes don't follow the proper behaviour and sometimes "vibrate" or "stick" to each other or they even just go through each other when a collision happens. I don't know whether this has something to do with the equation I used (should be pretty standard but I really don't know). Here is my code:
import pygame
import random
import math
def checkcirclecollide(x1, y1, r1, x2, y2, r2):
return (x1 - x2)**2 + (y1 - y2)**2 == (r1 + r2)**2
def ballcollision(m1, m2, v1, v2):
v2f = (2*m1*v1+m2*v2-m1*v2)/(m1+m2)
v1f = (m1*v1+m2*v2-m2*v2f)/m1
return v1f, v2f
class Ball:
def __init__(self, x, y, vx, vy, r, m):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.r = r
self.m = m
def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
if x!=None: self.x = x
if y!=None: self.y = y
if vx!=None: self.vx = vx
if vy!=None: self.vy = vy
if r!=None: self.r = r
if m!=None: self.m = m
pygame.init()
screen = pygame.display.set_mode((500, 500))
x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)
animationTimer = pygame.time.Clock()
balls = []
num_balls = 2
for x in range(num_balls):
balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5))
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
break
for ball in balls:
pygame.draw.circle(screen, (200, 0, 0), (ball.x, ball.y), ball.r)
if ball.x-ball.r<=0 or ball.x+ball.r>=500:
ball.change_attribute(vx = -ball.vx)
if ball.y-ball.r<=0 or ball.y+ball.r>=500:
ball.change_attribute(vy = -ball.vy)
for other in balls:
if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)
ball.change_attribute(vx = new_v1x, vy = new_v1y)
other.change_attribute(vx = new_v2x, vy = new_v2y)
ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
animationTimer.tick(100)
pygame.display.update()
screen.fill((0, 0, 0))
Upvotes: 1
Views: 1124
Reputation: 820
I did some modifications to your script. The modifications I made are explained on the script itself. As Kinetic Energy is outside of my field of expertise, I have no idea to say if it's really working, maybe it still needs a few modifications. But at least the balls do not merge or stick to the walls anymore.
Also, I added custom colors for each ball. They are much better for testing and see where and how they collide to each other.
import pygame
import random
import math
# From: http://www.jeffreythompson.org/collision-detection/circle-circle.php
def checkcirclecollide(x1, y1, r1, x2, y2, r2):
distX = x1 - x2
distY = y1 - y2
distance = math.sqrt( (distX * distX) + (distY * distY) )
return (distance <= (r1 + r2))
# Kinectic Energy.. I'm sorry, I have no idea what this does..
def ballcollision(m1, m2, v1, v2):
v2f = (2*m1*v1 + m2*v2 - m1*v2) / (m1+m2)
v1f = (m1*v1 + m2*v2 - m2*v2f)/m1
return v1f, v2f
# When two balls collide, there's no guarantee they are touching
# by their extremities.
#
# As they have different speeds, they might enter into one's
# space when they collide. So before/after applying the kinetic energy,
# we must firstly separate the balls so they do not merge to
# each other.
#
# Basically this happens:
# __ __
# / /\ \
# \__\/__/
#
# What does it do? It separates each ball once they merge:
# ___ ___
# / \ / \
# \___/ \___/
#
# This function should only be called when both balls are
# colliding to each other.
def separate_balls(ball, other):
# Get the Opposite direction
angle = - math.atan2(ball.y - other.y, ball.x - other.x)
# Calculate distance between both balls
distX = ball.x - other.x
distY = ball.y - other.y
distance = math.sqrt( (distX * distX) + (distY * distY) )
diffR = ball.r + other.r - distance
diffR *= 0.5
# Separate each ball by half of distance
ball.x += math.cos(angle) * diffR
ball.y += math.sin(angle) * diffR
other.x -= math.cos(angle) * diffR
other.y -= math.sin(angle) * diffR
class Ball:
def __init__(self, x, y, vx, vy, r, m, color):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.r = r
self.m = m
# This is better for testing
self.color = color
def change_attribute(self, x=None, y=None, vx=None, vy=None, r=None, m=None):
if x!=None: self.x = x
if y!=None: self.y = y
if vx!=None: self.vx = vx
if vy!=None: self.vy = vy
if r!=None: self.r = r
if m!=None: self.m = m
pygame.init()
screen = pygame.display.set_mode((500, 500))
x, y = random.randint(11, 489), random.randint(11, 489)
vx, vy = random.randint(-5, 5), random.randint(-5, 5)
animationTimer = pygame.time.Clock()
balls = []
num_balls = 2
# Now I added random colors!
for x in range(num_balls):
balls.append(Ball(random.randint(11, 489), random.randint(11, 489), random.randint(-5, 5), random.randint(-5, 5), random.randint(30, 50), random.randint(1, 4)*5, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))))
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
break
for ball in balls:
pygame.draw.circle(screen, ball.color, (ball.x, ball.y), ball.r)
if ball.x-ball.r-ball.vx<=0 or ball.x+ball.r+ball.vx>=500:
# Avoid sticking at walls
if (ball.x<=ball.r):
ball.x-=(ball.vx-1)
elif (ball.x>=500-ball.r):
ball.x+=(ball.vx-1)
ball.change_attribute(vx = -ball.vx)
if ball.y-ball.r-ball.vy<=0 or ball.y+ball.r+ball.vy>=500:
# Avoid sticking at walls
if (ball.y<=ball.r):
ball.y-=(ball.vy-1)
elif (ball.y>=500-ball.r):
ball.y+=(ball.vy-1)
ball.change_attribute(vy = -ball.vy)
for other in balls:
if other != ball and checkcirclecollide(ball.x, ball.y, ball.r, other.x, other.y, other.r):
new_v1x, new_v2x = ballcollision(ball.m, other.m, ball.vx, other.vx)
new_v1y, new_v2y = ballcollision(ball.m, other.m, ball.vy, other.vy)
# Save current position
originalX = ball.x
originalY = ball.y
# Calculate a hint of next position
lookAheadX = ball.x + ball.vx
lookAheadY = ball.y + ball.vy
# Push hint position
ball.change_attribute(x = lookAheadX, y = lookAheadY)
# Separate both balls from each other
separate_balls(ball, other)
# Pop hint position to original one
ball.change_attribute(x = originalX, y = originalY)
ball.change_attribute(vx = new_v1x, vy = new_v1y)
other.change_attribute(vx = new_v2x, vy = new_v2y)
ball.change_attribute(x = ball.x+ball.vx, y = ball.y+ball.vy)
animationTimer.tick(100)
pygame.display.update()
screen.fill((0, 0, 0))
Also, this is the idea I used on separate_balls()
. I'm not very good with Paint, but here we go:
x
on the picture is diffR
on the script. I use this value to move each ball away from each other once they merge.
Upvotes: 1