Search code examples
c#physicsgame-physics

How to resolve a 2D collision with an immovable object?


I am creating a 2D physics engine, and I am having trouble with a certain type of collision between movable and immovable objects. What I mean by movable is that the (x,y) values can change, not that the frame of reference can or can't change.

For example, a ball hitting a wall would be something that is movable colliding with something immovable.

ball and wall collision

I believe that I need to use something like Normal Force in this situation, but I am no sure how that would be used for finding the outcome of the collision.

Here is the code that I have so far. This code is for collision between two moving entities, but I need to add a case for when one is not moving:

private static void UpdateEntities(PhysicsEntity a, PhysicsEntity b)
    {
        var collisionAngle = Math.Atan2(a.Position.Y - b.Position.Y, a.Position.X - b.Position.X);
        var angleA = a.Velocity.Direction - collisionAngle;
        var angleB = b.Velocity.Direction - collisionAngle;

        var vAx = a.Velocity.Magnitude * Math.Cos(angleA);
        var vAy = a.Velocity.Magnitude * Math.Sin(angleA);
        var vBx = b.Velocity.Magnitude * Math.Cos(angleB);
        var vBy = b.Velocity.Magnitude * Math.Sin(angleB);

        var vfAx = ((vAx * (a.Mass - b.Mass) + 2 * b.Mass * vBx) / (a.Mass + b.Mass)) * a.Material.Elasticity;
        var vfBx = ((vBx * (b.Mass - a.Mass) + 2 * a.Mass * vAx) / (a.Mass + b.Mass)) * b.Material.Elasticity;
        var vfAy = vAy * a.Material.Elasticity;
        var vfBy = vBy * b.Material.Elasticity;

        var magA = Math.Sqrt(Math.Pow(vfAx, 2) + Math.Pow(vfAy, 2));
        var magB = Math.Sqrt(Math.Pow(vfBx, 2) + Math.Pow(vfBy, 2));
        var dirA = Math.Atan2(vfAy, vfAx) + collisionAngle;
        var dirB = Math.Atan2(vfBy, vfBx) + collisionAngle;

        a.Velocity.X = magA * Math.Cos(dirA);
        a.Velocity.Y = magA * Math.Sin(dirA);
        b.Velocity.X = magB * Math.Cos(dirB);
        b.Velocity.Y = magB * Math.Sin(dirB);

    }

I tried setting the velocity of the immovable object to the opposite of the movable object's velocity, but that caused things to phase into each other.


Solution

  • I was able to get help from a friend and we worked out this algorithm to have objects reflect off of immovable objects during a collision.

    Modified Original Function:

    private static void UpdateEntities(PhysicsEntity a, PhysicsEntity b)
        {
            var collisionAngle = Math.Atan2(a.Position.Y - b.Position.Y, a.Position.X - b.Position.X);
    
            if (a.IsMoveable && b.IsMoveable)
            {
                var angleA = a.Velocity.Direction - collisionAngle;
                var angleB = b.Velocity.Direction - collisionAngle;
    
                var vAx = a.Velocity.Magnitude * Math.Cos(angleA);
                var vAy = a.Velocity.Magnitude * Math.Sin(angleA);
                var vBx = b.Velocity.Magnitude * Math.Cos(angleB);
                var vBy = b.Velocity.Magnitude * Math.Sin(angleB);
    
                var vfAx = ((vAx * (a.Mass - b.Mass) + 2 * b.Mass * vBx) / (a.Mass + b.Mass)) * a.Material.Elasticity;
                var vfBx = ((vBx * (b.Mass - a.Mass) + 2 * a.Mass * vAx) / (a.Mass + b.Mass)) * b.Material.Elasticity;
                var vfAy = vAy * a.Material.Elasticity;
                var vfBy = vBy * b.Material.Elasticity;
    
                var magA = Math.Sqrt(Math.Pow(vfAx, 2) + Math.Pow(vfAy, 2));
                var magB = Math.Sqrt(Math.Pow(vfBx, 2) + Math.Pow(vfBy, 2));
                var dirA = Math.Atan2(vfAy, vfAx) + collisionAngle;
                var dirB = Math.Atan2(vfBy, vfBx) + collisionAngle;
    
                a.Velocity.X = magA * Math.Cos(dirA);
                a.Velocity.Y = magA * Math.Sin(dirA);
                b.Velocity.X = magB * Math.Cos(dirB);
                b.Velocity.Y = magB * Math.Sin(dirB);
            }
            else
            {
                var sign = Math.Sign(collisionAngle);
                collisionAngle *= sign;
                while (collisionAngle > Math.PI/2)
                {
                    collisionAngle -= Math.PI / 2;
                }
                collisionAngle *= sign;
                if (a.IsMoveable)
                {
                    Reflection(ref a, b, collisionAngle);
                }
                else
                {
                    Reflection(ref b, a, collisionAngle);
                }
            }
        }
    

    Reflection Function:

            private static void Reflection(ref PhysicsEntity movable, PhysicsEntity immovable, double collisionAngle)
        {
            if (Math.Abs(collisionAngle - Math.PI / 2) < Universe.Epsilon)
            {
                // take the velocity vector, rotate it 180 degrees, scale it
                movable.Velocity.X *= -1;
                movable.Velocity.Y *= -1;
            }
            else if (Math.Abs(movable.Position.Y - immovable.Position.Y) < Universe.Epsilon ||
                     (movable.Position.X > movable.CollisionPoint.X ^ movable.Position.Y < movable.CollisionPoint.Y))
            {
                //take velocity vector, rotate CCW by 2*collisionAngle, scale it
                var rotateAngle = 2 * collisionAngle;
                var xPrime = movable.Velocity.X * Math.Cos(rotateAngle) - movable.Velocity.Y * Math.Sin(rotateAngle);
                var yPrime = movable.Velocity.Y * Math.Cos(rotateAngle) - movable.Velocity.X * Math.Sin(rotateAngle);
                movable.Velocity.X = xPrime;
                movable.Velocity.Y = yPrime;
            }
            else
            {
                //take the vector, rotate it CCW by 360-2*collisionAngle, scale it
                var rotateAngle = 2 * (Math.PI - collisionAngle);
                var xPrime = movable.Velocity.X * Math.Cos(rotateAngle) - movable.Velocity.Y * Math.Sin(rotateAngle);
                var yPrime = movable.Velocity.Y * Math.Cos(rotateAngle) - movable.Velocity.X * Math.Sin(rotateAngle);
                movable.Velocity.X = xPrime;
                movable.Velocity.Y = yPrime;
            }
    
            movable.Velocity.X *= movable.Material.Elasticity;
            movable.Velocity.Y *= movable.Material.Elasticity;
        }