I'm trying to program a simple simulator of elastic collisions (on a single dimension, moving of rectilinear motion along an x axis). After several tests, I've managed to write this function to check collisions:
def block_collisions(self, block1, block2):
""" check whether the two passed block objects happen to collide """
# calculating a collision tollerance threshold under which the potential collision is considered to happen
collision_tolerance = 2*round(abs(block1.v) + abs(block2.v), 0) + 5
# storing initial velocities of the two blocks (since the new velocities od the blocks are to be
# calculated independently and at the same time)
block1_initialV = block1.v
block2_initialV = block2.v
if block1.block.colliderect(block2.block):
if ([block1, block2] not in self.collisions) and ([block2, block1] not in self.collisions):
self.collisions.append([block1, block2])
# the collision can happn only on the left and right side of the blocks since it is a rectilinear motion
if abs(block2.block.right - block1.block.left) < collision_tolerance:
self.collision_sound.play()
# calculating new velocities
if self.is_elastic:
block1.v = (2*block2.m*block2_initialV + (block1.m - block2.m)*block1_initialV) / (block1.m + block2.m)
block2.v = (2*block1.m*block1_initialV + (block2.m - block1.m)*block2_initialV) / (block1.m + block2.m)
if abs(block2.block.left - block1.block.right) < collision_tolerance:
self.collision_sound.play()
# calculating new velocities
if self.is_elastic:
block1.v = (2*block2.m*block2_initialV + (block1.m - block2.m)*block1_initialV) / (block1.m + block2.m)
block2.v = (2*block1.m*block1_initialV + (block2.m - block1.m)*block2_initialV) / (block1.m + block2.m)
if block1.x <= block2.x:
block1.x = block2.x
if block1.x <= block2.x:
block1.x = block2.x
The function takes as arguments two Block class (which I've written) objects. Its main attributes are block current and initial positions: block.x, block.x0; current and initial velocities: block.v, block.v0; and the Pygame rect object used to represent the actual rectangle onto the screen: block.block. The variable self.collisions is a list containing all the collided pairs detected during a single iteration of the mainloop (thus it is emptied each cycle). To ensure that a single collision is detected just once, it is checked if the pair [block1, block2] or [block2, block1] is already inside this list. Another collision detection function is the one which detects wall collisions.:
def wall_collisions(self, block):
""" check whether the a block happens to collide with a particular wall """
# the wall is assumed to have an ideal infinite mass, hence, the new velocity of the wall is 0, while the velocity of
# the block is the same, opposite in direction
if self.l_wall:
if block.block.left <= self.l_wall:
self.collision_sound.play()
block.v *= -1
if self.r_wall:
if block.block.right >= self.r_wall:
self.collision_sound.play()
block.v *= -1
Where self.l_wall, self.r_wall are two values that sets the position of the wall, for example self.l_wall = 20, self.r_wall = window_width - 20. Both these function are placed inside a mainloop, in a chunk that has the following syntax:
while self.__running:
...
for b_ in self.blocks:
# check block collisions
for b__ in self.blocks:
if not b_ == b__:
block_collisions(b_, b__)
# check wall collisions
wall_collisions(b_)
self.collisions = []
The issue with these functions is that even if the result works for most of the cases, it is a bit glitchy and buggy:
let there be two blocks on the screen, one with a mass that is one order of magnitude bigger than the other. When the smaller block happens to be between a wall and the other block moving to the same wall, because of the great amount of bounces, the smaller seems to flashing and overlap both with the wall and with the block (the physical result remains acceptable, but the overall representation seems inaccurate).
let the bigger block be more than one order of magnitude bigger than the other. In the situation described above, the bigger block doesn't stop in time and squeezes the smaller one in the wall (the distance between the wall and the bigger block becomes smaller than the side of the smaller one). Yet even in this case the physical result is acceptable, since the problem is graphic and not methodological.
let velocities be high (>= 20m/s = 20 * (50px / 60frames) = 16.7 px/frame, 50px = 1m, 60frames = 1s) and the difference between mass be great (m1 / m2 >= 100 iff m1 > m2), considering the block directed one to the other, they tend to overlap, thus the smaller one manages in some way to penetrate the bigger one. After this awkward event, collisions continue being detected and hence the smaller block is trapped within the other.
Does anyone understand these issues I've encountered and know how to fix it? Or does anyone know better and more precise methods to check for collisions?