6

I have a a program where circles can bounce into one another. I followed the directions from here for rotating the vectors and scaling the magnitudes based on the collision angle: http://www.vobarian.com/collisions/2dcollisions2.pdf

I wrote this code in python (the 0 index indicates the x coordinate):

norm_vect = [(object2.pos[0] - object1.pos[0]), (object2.pos[1] - object1.pos[1])]
unit = sqrt((norm_vect[0]**2) + (norm_vect[1]**2))
unit_vect = [float(norm_vect[0]) / unit, float(norm_vect[1]) /unit]
tan_vect = [-unit_vect[1], unit_vect[0]]
vel1 = object1.vel
vel2 = object2.vel
vel1_norm = vel1[0] * unit_vect[0] + vel1[1] * unit_vect[1]
vel1_tan = vel1[0] * tan_vect[0] + vel1[1] * tan_vect[1]
vel2_norm = vel2[0] * unit_vect[0] + vel2[1] * unit_vect[1]
vel2_tan = vel2[0] * tan_vect[0] + vel2[1] * tan_vect[1]
new_vel1_norm = (vel1_norm * (object1.mass - object2.mass) + 2 * object2.mass * vel2_norm) / (object1.mass + object2.mass)
new_vel2_norm = (vel2_norm * (object2.mass - object1.mass) + 2 * object1.mass * vel1_norm) / (object1.mass + object2.mass)
new_norm_vect1 = [new_vel1_norm * float(unit_vect[0]), new_vel1_norm * float(unit_vect[1])]
new_norm_vect2 = [new_vel2_norm * float(unit_vect[0]), new_vel2_norm * float(unit_vect[1])]
new_tan_vect1 = [new_vel1_norm * float(tan_vect[0]), new_vel1_norm * float(tan_vect[1])]
new_tan_vect2 = [new_vel2_norm * float(tan_vect[0]), new_vel2_norm * float(tan_vect[1])]

# Now update the object's velocity
object1.vel = [new_norm_vect1[0] + new_tan_vect1[0], + new_norm_vect1[1] + new_tan_vect1[1]]
object2.vel = [new_norm_vect2[0] + new_tan_vect2[0], + new_norm_vect2[1] + new_tan_vect2[1]]

The problem is that it works sometimes, but not othertimes. Can anyone tell me why? It seems like if the balls collide at the right angle then their exit trajectories swap or something. I wrote this in codeskulptor browser: http://www.codeskulptor.org/#user39_8q0Xdp3Y4s_2.py

Can anyone point out where I went wrong?

EDIT: Could it be the way that I process the collision? Here is the steps:

    1) Draw the balls on the screen
    2) Create set of unique pairs of collidable objects 
    3) For each ball, move the ball's position 1 frame forward according to the velocity:
        ->1) Check to see if the ball is hitting a wall
        ->2) For each pairset, if the ball in question is a member of the pair:
             -->1) If distance between centers is less than sum of radii:
                    -->1)  Calculate rebound trajectories
                    ---2)  Find N such that position + rebound trajectory *N is out of collision zone
Djones4822
  • 577
  • 3
  • 6
  • 23

1 Answers1

3

The online simulation is really cool! I did not study your complete code in detail, just the snippet you posted in your question. From a quick glance, you correctly compute the tangential and normal unit vectors, the old normal and tangential velocities, and the new normal velocity. But after that, you seem to get lost a bit. As explained in the document about the collisions, the tangential velocities do not change during the collision, so there is no need to calculate new_tan_vect1/2. I also don't understand why you are calculating new_norm_vect1, the normal vector does not change during the collision.

Some other small remarks:

  • why do you use float() all over your code? This is normally not needed. If the reason for this is to get correct results for division, you should really add a from __future__ import division at the top of your code, since you seem to be using Python2. See this old question for more info.

  • What you call norm_vect is actually the un-normalized normal vector, and what you call unit_vect is actually the normalized normal unit vector. I would just call both norm_vect, to make the difference between normal and tangential more clear. A unit vector is any vector with length 1, so using that for the normal vector is a bit misleading.

  • If you are planning to do more of these kind of simulations, you should consider learning about numpy. This allows you to write vectorized calculations, instead of writing out all the equations for x and y by hand. E.g. norm_vect = pos2 - pos1; norm_vect /= np.linalg.norm(norm_vect) or object1.vel = norm_vect * new_vel1_norm + tang_vect * vel1_tang.

I would write your snippet should more or less like this (untested code):

from __future__ import division  # move this to the top of your program

# calculate normal and tangential unit vectors
norm_vect = [(object2.pos[0] - object1.pos[0]), 
             (object2.pos[1] - object1.pos[1])]  # stil un-normalized!
norm_length = sqrt((norm_vect[0]**2) + (norm_vect[1]**2))
norm_vect = [norm_vect[0] / norm_length, 
             norm_vect[1] / norm_length]  # do normalization
tang_vect = [-norm_vect[1], norm_vect[0]]  # rotate norm_vect by 90 degrees

# normal and tangential velocities before collision
vel1 = object1.vel
vel2 = object2.vel
vel1_norm = vel1[0] * norm_vect[0] + vel1[1] * norm_vect[1]
vel1_tang = vel1[0] * tang_vect[0] + vel1[1] * tang_vect[1]
vel2_norm = vel2[0] * norm_vect[0] + vel2[1] * norm_vect[1]
vel2_tang = vel2[0] * tang_vect[0] + vel2[1] * tang_vect[1]

# calculate velocities after collision
new_vel1_norm = (vel1_norm * (object1.mass - object2.mass) 
    + 2 * object2.mass * vel2_norm) / (object1.mass + object2.mass)
new_vel2_norm = (vel2_norm * (object2.mass - object1.mass) 
    + 2 * object1.mass * vel1_norm) / (object1.mass + object2.mass)
# no need to calculate new_vel_tang, since it does not change

# Now update the object's velocity
object1.vel = [norm_vect[0] * new_vel1_norm + tang_vect[0] * vel1_tang, 
               norm_vect[1] * new_vel1_norm + tang_vect[1] * vel1_tang]
object2.vel = [norm_vect[0] * new_vel2_norm + tang_vect[0] * vel2_tang, 
               norm_vect[1] * new_vel2_norm + tang_vect[1] * vel2_tang]

I renamed some of the variables to make it more clear.

Community
  • 1
  • 1
Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • Well now I feel like I'm screwing something up in python because the velocities change but for some reason, the global class state isn't being changed. See here: http://www.codeskulptor.org/#user39_8q0Xdp3Y4s_3.py Could it be that I can't import from `__future__` because codeskulptor is an extremely limited implementation of Python just so that people can learn the basics of interaction (simpleGUI module created by the professors of the Coursera Class along with codeskulptor) – Djones4822 Mar 19 '15 at 18:30
  • http://www.codeskulptor.org/#user39_8q0Xdp3Y4s_4.py <- that one is cleaner but the issue is still there and I have no clue why. I think it's because my collision() function called by a class method. I'm not sure how to implement the pairwise collision check inside of the class which is why I pulled it out – Djones4822 Mar 19 '15 at 18:44
  • 1
    Looking at the velocities you print in the collision function, it seemed that when a light ball was colliding with a heavy ball, the velocity was flipping as it should, but then directly afterwards it was flipping again in the opposite direction so effectively nothing was happening. The error was in your `match_objects` function, you add every pair twice, once forwards and once backwards. Every collision is then detected twice, from object1 and for object2, these two cancel each other. This seems to work (balls do get stuck sometimes): http://www.codeskulptor.org/#user39_sV5TmqJSlAYGeOG.py – Bas Swinckels Mar 19 '15 at 20:29
  • Ahhhhh that makes tons of sense. I thought that within a set() that (a,b) was the same as (b,a) so it wouldn't be added twice. Thank you so much! You've been extremely helpful!! – Djones4822 Mar 19 '15 at 20:55
  • 1
    No, a tuple(a,b) is different from a tuple(b,a). One more thing I would change: in `collision`, you remove a pair from the set while iterating over the set. Mutating a sequence while iterating can lead to [bad things](http://www.gossamer-threads.com/lists/python/python/1004659). Instead of looping over all pairs from within the `move_frame` function of a ball object, I would do an explicit iteration over all pairs in the main loop of the simulation, something like `for i in range(n-1): for j in range(1+1, n): check_pair(i, j) ...`. This would save the trouble of removing pairs from a set. – Bas Swinckels Mar 19 '15 at 21:03