Search code examples
pythongeometrypygamegame-physics

In an elastic collision between two two-dimensional bodies, is total speed (magnitude of velocity) between the bodies conserved?


Question

In a closed system where moving 2-dimensional circular bodies (they have mass and velocity attributes) collide with one another with perfect elasticity, is total velocity speed (magnitude of velocity) of all the bodies within the system conserved?

Background

I'm implementing a simple 2-D physics engine in Python based on the collision resolution method outlined in this Stack Overflow question. My expectation is that total speed (the sum of the length of the velocity vectors associated with each body) should remain constant between 2 bodies when they collide and I designed a unit test for my resolution method based on this expectation. But I find my test failing. So I want to make sure first that my assumption is correct.

If it is correct, I invite you to review my code and explain why the test is failing:

Collision Resolution

class Physics:

    @staticmethod
    def reflect_colliding_circles(
        (c1_x, c1_y, c1_vx, c1_vy, c1_r, c1_m, c1_e),
        (c2_x, c2_y, c2_vx, c2_vy, c2_r, c2_m, c2_e)):
        # inverse masses, mtd, restitution
        im1 = 1.0 / c1_m
        im2 = 1.0 / c2_m
        mtd = Physics.calculate_mtd((c1_x, c1_y, c1_r), (c2_x, c2_y, c2_r))
        normal_mtd = mtd.normalized()
        restitution = c1_e * c2_e

        # impact speed
        v = vec2d(c1_vx, c1_vy) - vec2d(c2_vx, c2_vy)
        vn = v.dot(normal_mtd)

        # circle moving away from each other already -- return
        # original velocities
        if vn > 0.0:
            return vec2d(c1_vx, c1_vy), vec2d(c2_vx, c2_vy)

        # collision impulse
        i = (-1.0 * (1.0 + restitution) * vn) / (im1 + im2)
        impulse = normal_mtd * i

        # change in momentun
        new_c1_v = vec2d(c1_vx, c1_vy) + (impulse * im1)
        new_c2_v = vec2d(c2_vx, c2_vy) - (impulse * im2)

        return new_c1_v, new_c2_v

    @staticmethod
    def calculate_mtd((c1_x, c1_y, c1_r), (c2_x, c2_y, c2_r)):
        """source: https://stackoverflow.com/q/345838/1093087"""
        delta = vec2d(c1_x, c1_y) - vec2d(c2_x, c2_y)
        d = delta.length
        mtd = delta * (c1_r + c2_r - d) / d
        return mtd

Unit Test

def test_conservation_of_velocity_in_elastic_collisions(self):
    for n in range(10):
        r = 2
        m = 10
        e = 1.0

        c1_pos = vec2d(0, 0)
        c1_v = vec2d(random.randint(-100,100), random.randint(-100,100))

        c2_delta = vec2d(random.randint(-100,100), random.randint(-100,100))
        c2_delta.length = random.randint(50, 99) * r / 100.0
        c2_pos = c1_pos + c2_delta
        c2_v = vec2d(random.randint(-100,100), random.randint(-100,100))

        c1_np, c2_np = Physics.translate_colliding_circles(
            (c1_pos.x, c1_pos.y, r, m),
            (c2_pos.x, c2_pos.y, r, m))

        c1_nv, c2_nv = Physics.reflect_colliding_circles(
            (c1_np.x, c1_np.y, c1_v.x, c1_v.y, r, m, e),
            (c2_np.x, c2_np.y, c2_v.x, c2_v.y, r, m, e))

        old_v = c1_v.length + c2_v.length
        new_v = c1_nv.length + c2_nv.length

        self.assertTrue(Physics.circles_overlap(
            (c1_pos.x, c1_pos.y, r), (c2_pos.x, c2_pos.y, r)))
        self.assertTrue(old_v - new_v < old_v * .01)

I'm using this pygame vector class: http://www.pygame.org/wiki/2DVectorClass


Solution

  • Total momentum is conserved, regardless of how elastic the collision is. Total velocity is obviously not. Strictly speaking, velocity is a vector quantity, and it is rather easy to see that it will change, as a vector quantity: for example, a ball that elastically bounces off an immovable perpendicular wall changes its velocity to its opposite.