Search code examples
c#xna2dcollisiongeometry

XNA- How do I calculate the velocities of 2D circles after they collided with each other?


I have been Looking into making circles collide with each other and this is what I came up with so far:

foreach(Circle circle in circles)
    foreach(Circle circle2 in circles)
            {
                bool HasCollided = false;
                if(circle.ID != circle2.ID)
                {
                    double squareRoot =(Math.Pow(((int)circle.position.X - (int)circle2.position.X), 2) + Math.Pow((int)circle.position.Y - (int)circle2.position.Y, 2));
                    double distanceBetweenCentres = Math.Sqrt(squareRoot);
                    if(distanceBetweenCentres < (circle.radius + circle2.radius))HasCollided = true;                           
                    else if(distanceBetweenCentres> (circle.radius + circle2.radius)) HasCollided = false;
                    if(HasCollided == true)
                    {
                        double newVelX1 = ((circle.velocity.X * (circle.Mass - circle2.Mass) + (2 * circle2.Mass * circle2.velocity.X)) / (circle.Mass + circle2.Mass));
                        double newVelY1 = ((circle.velocity.Y * (circle.Mass - circle2.Mass) + (2 * circle2.Mass * circle2.velocity.Y)) / (circle.Mass + circle2.Mass));

                        double newVelX2 = ((circle2.velocity.X * (circle2.Mass - circle.Mass) + (2 * circle.Mass * circle.velocity.Y)) / (circle2.Mass + circle.Mass));
                        double newVelY2 = ((circle2.velocity.Y * (circle2.Mass - circle.Mass) + (2 * circle.Mass * circle.velocity.Y)) / (circle2.Mass + circle.Mass));

                        circle.velocity = new Vector2((int)newVelX1, (int)newVelY1);
                        circle2.velocity = new Vector2((int)newVelX2, (int)newVelY2);
                    }
                }
            }
        }

The code does work out when the circles are colliding, however the equation for the final velocity seems to make the circles either jump around the screen or slowly fall through each other, I tried finding a solution online but I couldn't get it to work. Any suggestions on what I could do to fix it?

new Code and GIF to show what it does:

 if(circle.ID < circle2.ID)
                {
                    double squareRoot =(Math.Pow(((int)circle.position.X - (int)circle2.position.X), 2) + Math.Pow((int)circle.position.Y - (int)circle2.position.Y, 2));
                    double distanceBetweenCentres = Math.Sqrt(squareRoot);
                    if(distanceBetweenCentres < (circle.radius + circle2.radius))HasCollided = true;                           
                    else if(distanceBetweenCentres> (circle.radius + circle2.radius)) HasCollided = false;
                    if(HasCollided == true)
                    {
                        double newVelX1 = ((circle.velocity.X * (circle.Mass - circle2.Mass) + (2 * circle2.Mass * circle2.velocity.X)) / (circle.Mass + circle2.Mass));
                        double newVelY1 = ((circle.velocity.Y * (circle.Mass - circle2.Mass) + (2 * circle2.Mass * circle2.velocity.Y)) / (circle.Mass + circle2.Mass));

                        double newVelX2 = ((circle2.velocity.X * (circle2.Mass - circle.Mass) + (2 * circle.Mass * circle.velocity.Y)) / (circle2.Mass + circle.Mass));
                        double newVelY2 = ((circle2.velocity.Y * (circle2.Mass - circle.Mass) + (2 * circle.Mass * circle.velocity.Y)) / (circle2.Mass + circle.Mass));

                        circle.velocity = new Vector2((float)newVelX1, (float)newVelY1);
                        circle2.velocity = new Vector2((float)newVelX2, (float)newVelY2);
                    }
                }

New gif after changing code again


Solution

  • If they are jumping around there is a good chance that it is because they haven't separated by the time you do the next collision check. Try moving the circles from the impact point by their radius.

    As an example using the first circle:

    Cp = Cp + Normalize(Ip - Cp) * Cr
    

    Where Ip is Impact Position, Cp is Circle Position and Cr is Circle Radius. If it appears to be pushing the circle further into the collision, swap Ip and Cp before normalization.

    EDIT: Came up with this after running the code myself:

    Issue 1: In newVelX2 you are comparing CircleVelocity.Y instead of .X

    Issue 2: You aren't checking for previously evaluated pairs. I solved this with the following code: `

    for (int c1Index = 0; c1Index < RigidBodies.Count; c1Index++)
     {
          for (int c2Index = c1Index + 1; c2Index < RigidBodies.Count; c2Index++)
          {
               HandleCollisions(RigidBodies[c1Index], RigidBodies[c2Index]);
          }
     }
    

    This prevents you from evaluating a pair twice. What I mean is, if following your code with 3 circles in the list:

    Outer Loop = Circle0
    Inner Loop = Circle0 <-- C0 is skipped
    Inner Loop = Circle1 <-- C0 and C1 pair evaluated
    Inner Loop = Circle2 <-- C0 and C2 pair evaluated
    Outer Loop = Circle1 
    Inner Loop = Circle0 <-- C1 and C0 pair evaluated *PROBLEM: We already did this pair*
    Inner Loop = Circle1 <-- C1 is skipped
    Inner Loop = Circle2 <-- C1 and C2 pair evaluated
    Outer Loop = Circle2 
    Inner Loop = Circle0 <-- C2 and C0 pair evaluated *PROBLEM: We already did this pair*
    Inner Loop = Circle1 <-- C2 and C1 pair evaluated *PROBLEM: We already did this pair*
    Inner Loop = Circle2 <-- C2 is skipped
    

    `

    My code resolves this by having the inner loop begin at one past the currently evaluated circle, because anything before it has already been handled. Another option is to maintain a list of evaluated pairs, which obviously is more memory intensive, but completes the job.

    For my demo I didn't have to separate the circles first. However, if you spawn the circles inside one another, they will be stuck like that. Therefore if you anticipate circles sometimes getting stuck together, you may want to keep the pseudo code from my original answer.

    Additionally, the below code shows a more accurate response mechanism which I adapted from this link:

     private void HandleCollisions(Circle Circle1, Circle Circle2)
        {
            if (!HaveCollided(Circle1, Circle2))
                return;
    
            Vector2D distance = Circle1.Position - Circle2.Position;
            Vector2D normalVector = distance.Normalize();
    
            Vector2D tangentVector = normalVector.GetTangentVector();
    
            float c1VelocityAlongNormal = VectorMath.CalculateDotProduct(normalVector, Circle1.Velocity);
            float c2VelocityAlongNormal = VectorMath.CalculateDotProduct(normalVector, Circle2.Velocity);
    
            float c1VelocityAlongTan = VectorMath.CalculateDotProduct(tangentVector, Circle1.Velocity);
            float c2VelocityAlongTan = VectorMath.CalculateDotProduct(tangentVector, Circle2.Velocity);
    
            float massSum = Circle1.Mass + Circle2.Mass;
    
            float c1Speed = (c1VelocityAlongNormal * (Circle1.Mass - Circle2.Mass) + (2.0f * Circle2.Mass * c2VelocityAlongNormal) / massSum);
            float c2Speed = (c2VelocityAlongNormal * (Circle1.Mass - Circle2.Mass) + (2.0f * Circle1.Mass * c1VelocityAlongNormal) / massSum);
    
            Vector2D c1FinalVelocity = normalVector * c1Speed;
            Vector2D c2FinalVelocity = normalVector * c2Speed;
    
            Vector2D c1Tangent = tangentVector * c1VelocityAlongTan;
            Vector2D c2Tangent = tangentVector * c2VelocityAlongTan;
    
            Circle1.Velocity = c1FinalVelocity + c1Tangent;
            Circle2.Velocity = c2FinalVelocity + c2Tangent;
        }
    
        **FROM VECTOR2D CLASS**
        class Vector2D
    {
        public float X, Y;
    
        public Vector2D()
        {
            this.X = 0.0f;
            this.Y = 0.0f;
        }
    
        public Vector2D(float X, float Y)
        {
            this.X = X;
            this.Y = Y;
        }
    
        public Vector2D(Vector2D vectorToCopy)
        {
            this.X = vectorToCopy.X;
            this.Y = vectorToCopy.Y;
        }
    
        public float CalculateMagnitudeSquared()
        {
            return (this.X * this.X) + (this.Y * this.Y);
        }
    
        public float CalculateMagnitude()
        {
            return (float)Math.Sqrt((this.X * this.X) + (this.Y * this.Y));
        }
    
        public Vector2D GetTangentVector()
        {
            return new Vector2D(-this.Y, this.X).Normalize();
        }
    
        public Vector2D Normalize()
        {
            return new Vector2D(this / CalculateMagnitude());
        }
    
        public static Vector2D operator+(Vector2D thisVector, Vector2D otherVector)
        {
            return new Vector2D(thisVector.X + otherVector.X, thisVector.Y + otherVector.Y);
        }
    
        public static Vector2D operator-(Vector2D thisVector, Vector2D otherVector)
        {
            return new Vector2D(thisVector.X - otherVector.X, thisVector.Y - otherVector.Y);
        }
    
        public static Vector2D operator*(Vector2D thisVector, Vector2D otherVector)
        {
            return new Vector2D(thisVector.X * otherVector.X, thisVector.Y * otherVector.Y);
        }
    
        public static Vector2D operator*(Vector2D thisVector, float scalar)
        {
            return new Vector2D(thisVector.X * scalar, thisVector.Y * scalar);
        }
    
        public static Vector2D operator/(Vector2D thisVector, Vector2D otherVector)
        {
            return new Vector2D(thisVector.X / otherVector.X, thisVector.Y / otherVector.Y);
        }
    
        public static Vector2D operator/(Vector2D thisVector, float scalar)
        {
            return new Vector2D(thisVector.X / scalar, thisVector.Y / scalar);
        }
    }
    

    This isn't the neatest code... just threw it together to test this stuff.

    Any further issues, let me know