AS3, this elastic collision calculation for circles doesn't work

9 posts

Flag Post

Hey.

http://www.fastswf.com/dUrdpWQ

They stick together and sometimes get infinite speed.
When they collide properly it seems they only invert their speed.

public function testCollision(c1:Circle, c2:Circle):void
{
	var xDist:Number = Math.abs(c2.x - c1.x);
	var yDist:Number = Math.abs(c2.y - c1.y);
	var radialDist:Number = Math.sqrt(xDist * xDist + yDist * yDist);
	if (radialDist < c1.radius + c2.radius) {
		c1.xSpeed = (c1.xSpeed * (c1.mass - c2.mass) + (2 * c2.mass * c2.xSpeed)) / (c1.mass + c2.mass);
		c1.ySpeed = (c1.ySpeed * (c1.mass - c2.mass) + (2 * c2.mass * c2.ySpeed)) / (c1.mass + c2.mass);
		c2.xSpeed = (c2.xSpeed * (c2.mass - c1.mass) + (2 * c1.mass * c1.xSpeed)) / (c1.mass + c2.mass);
		c2.ySpeed = (c2.ySpeed * (c2.mass - c1.mass) + (2 * c1.mass * c1.ySpeed)) / (c1.mass + c2.mass);
		
		var collisionPointX:Number = ((c1.x * c2.radius) + (c2.x * c1.radius)) / (c1.radius + c2.radius);
		var collisionPointY:Number = ((c1.y * c2.radius) + (c2.y * c1.radius)) / (c1.radius + c2.radius);
	}
}

And

public function get mass():Number
{
	return 4 / 3 * Math.PI * radius * radius * radius;
}

I understand I should place the circles so that they don’t collide anymore (though it might not be the only problem here). You see I already got the collision point. I just don’t know how I should actually place the circles. Actually, on second thought, this should not be necessary. There must be a problem with the speed change.

Thanks in advance!

(E) http://www.imada.sdu.dk/~rolf/Edu/DM815/E10/2dcollisions.pdf

 
Flag Post

One point of nitpick, you are colliding spheres, not circles, even though they are constrained to a 2D plane.

The speed change equations can’t be right. In the simplified case of two spheres of the same radius, they exchange their speeds (c1.mass == c2.mass so the first term is 0, and 2 cx.mass / (c1.mass + c2.mass) == 1 and drops off so you’re left — OH GOSH I FOUND IT!

Dude, you’re changing c1.xSpeed in the first equation and then using the new value in the third one! Same with equations 2 and 4.

The other problem, to get back to what I was typing about the two spheres of equal mass, is that simply switching the speeds in not accurate: If one of the spheres is at rest then any sphere of equal mass hitting it would subsequently be at rest, which anyone who’s ever played pool can tell you is not right. Only a fully head-on collision should do that.

What you need to do to fix the physics (once you’ve fixed the logic) is to ensure conservation of momentum. And then it should work.

Edit: All the stuff you need is in that pdf you linked. Unfortunately you stopped at page 1, which is for 1-D collisions. The stuff you want is what comprises the rest of the document. It’s beautifully explained too, great find.

 
Flag Post

Ah of course… I thought, if there’s a pool game the circles are supposed to look like spheres.

Thank you for the fast reply! It works now, but looking at the collisions I don’t think it looks very realistic at times. How should I eventually fix the physics, as you mentioned?

Edit: Ah, okay I will try to fix it by reading the rest of the pdf :)

Edit 2: Luck is, we learned about vectors at school today! =D

 
Flag Post

So I did try to write collision handling while reading the pdf.

Doesn’t work quite well yet.. http://www.fastswf.com/MY274h4

And here’s a block of code!

public function testCollision(c1:Circle, c2:Circle):void
{
	var xDist:Number = Math.abs(c2.x - c1.x);
	var yDist:Number = Math.abs(c2.y - c1.y);
	var radialDist:Number = Math.sqrt(xDist * xDist + yDist * yDist);
	if (radialDist < c1.radius + c2.radius) {
		// STEP 1: FIND NORMAL AND TANGENT
		// --
		var unitNormal:Point = new Point(c2.x - c1.x, c2.y - c1.y); //delta, not unit vector yet
		var normalLength:Number = Math.sqrt(unitNormal.x * unitNormal.x + unitNormal.y * unitNormal.y); // |normal|
		unitNormal.x = unitNormal.x / normalLength; // normal / |normal|
		unitNormal.y = unitNormal.y / normalLength;
		var unitTangent:Point = new Point( - unitNormal.y, unitNormal.x); //unit tangent where the circles collide
		
		// Step 3: Find tangent-speed and normal-speed (scalar)
		var normalSpeed1:Number = unitNormal.x * c1.xSpeed + unitNormal.y * c1.ySpeed;
		var tangentSpeed1:Number = unitTangent.x * c1.xSpeed + unitTangent.y * c1.ySpeed;
		var normalSpeed2:Number = unitNormal.x * c2.xSpeed + unitNormal.y * c2.ySpeed;
		var tangentSpeed2:Number = unitTangent.x * c2.xSpeed + unitTangent.y * c2.ySpeed;
		// Step 5: Find new normal speed (using 1D elastic collision calculation)
		normalSpeed1 = ( normalSpeed1 * (c1.mass - c2.mass) + 2 * c2.mass * normalSpeed2) / (c1.mass + c2.mass);
		normalSpeed2 = ( normalSpeed2 * (c2.mass - c1.mass) + 2 * c1.mass * normalSpeed1) / (c2.mass + c1.mass);
		
		// Step 6: 
		var finNormalSpeed1:Point = new Point(unitNormal.x * normalSpeed1, unitNormal.y * normalSpeed1);
		var finTangentSpeed1:Point = new Point(unitTangent.x * tangentSpeed1, unitTangent.y * tangentSpeed1);
		var finNormalSpeed2:Point = new Point(unitNormal.x * normalSpeed2, unitNormal.y * normalSpeed2);
		var finTangentSpeed2:Point = new Point(unitTangent.x * tangentSpeed2, unitTangent.y * tangentSpeed2);
		
		//Step 7: Find the final speed vector
		c1.xSpeed = finNormalSpeed1.x + finTangentSpeed1.x;
		c1.ySpeed = finNormalSpeed1.y + finTangentSpeed1.y;
		c2.xSpeed = finNormalSpeed2.x + finTangentSpeed2.x;
		c2.ySpeed = finNormalSpeed2.y + finTangentSpeed2.y;
		
	}
}
 
Flag Post

I wrote my circle v circle collision based on this, its written for java but its basically the same.

 
Flag Post
if (CollisionHelper.BallTestHit(BallPosition,Ball3Position,Ball.Width/2,Ball3.Width/2))
            {
                double CollisionAngle = CollisionHelper.BallHitAngle(BallPosition, Ball3Position, Ball.Width/2, Ball3.Width/2);

                double BallMovementAngle = Math.Atan2(BallYVelocity, BallXVelocity);
                double Ball3MovementAngle = Math.Atan2(Ball3YVelocity, Ball3XVelocity);

                double BallMovementMagnitude = Math.Pow(BallXVelocity * BallXVelocity + BallYVelocity * BallYVelocity, 0.5);
                double Ball3MovementMagnitude = Math.Pow(Ball3XVelocity * Ball3XVelocity + Ball3YVelocity * Ball3YVelocity, 0.5);

                double BallParallelMovement = BallMovementMagnitude * Math.Cos(CollisionAngle - BallMovementAngle);
                double BallPerpendicularMovement = BallMovementMagnitude * Math.Sin(CollisionAngle - BallMovementAngle);
                double Ball3ParallelMovement = Ball3MovementMagnitude * Math.Cos(CollisionAngle - Ball3MovementAngle);
                double Ball3PerpendicularMovement = Ball3MovementMagnitude * Math.Sin(CollisionAngle - Ball3MovementAngle);

                double newBallParallelMovement = Ball3ParallelMovement;
                double newBallPerpendicularMovement = BallPerpendicularMovement;
                double newBall3ParallelMovement = BallParallelMovement;
                double newBall3PerpendicularMovement = Ball3PerpendicularMovement;

                double newBallMovementAngle = Math.Atan2(newBallPerpendicularMovement, newBallParallelMovement);
                double newBall3MovementAngle = Math.Atan2(newBall3PerpendicularMovement, newBall3ParallelMovement);

                double newBallMovementMagnitude = Math.Pow(newBallPerpendicularMovement * newBallPerpendicularMovement + newBallParallelMovement * newBallParallelMovement, 0.5);
                double newBall3MovementMagnitude = Math.Pow(newBall3PerpendicularMovement * newBall3PerpendicularMovement + newBall3ParallelMovement * newBall3ParallelMovement, 0.5);

                double BallAngToVertical = 90 - CollisionAngle - newBallMovementAngle;
                double Ball3AngToVertical = 90 - CollisionAngle - newBall3MovementAngle;

                double newBallXVelocity = newBallMovementMagnitude * Math.Sin(BallAngToVertical);
                double newBallYVelocity = newBallMovementMagnitude * Math.Cos(BallAngToVertical);
                double newBall3XVelocity = newBall3MovementMagnitude * Math.Sin(Ball3AngToVertical);
                double newBall3YVelocity = newBall3MovementMagnitude * Math.Cos(Ball3AngToVertical);

                BallXVelocity = (float)newBallXVelocity;
                BallYVelocity = (float)newBallYVelocity;
                Ball3XVelocity = (float)newBall3XVelocity;
                Ball3YVelocity = (float)newBall3YVelocity;

                //Repelling Ball3 so it stays outside of Ball1
                double difX = (BallPosition.X - Ball.Width / 2) - (Ball3Position.X - Ball3.Width / 2);
                double difY = (BallPosition.Y - Ball.Width / 2) - (Ball3Position.Y - Ball3.Width / 2);
                double dist = Math.Pow(difX * difX + difY * difY, 0.5);

                double intersection = (Ball.Width + Ball3.Width) / 2 - dist;

                double newBall3XVel = intersection*Math.Cos(CollisionAngle)*1.1;
                double newBall3YVel = intersection*Math.Sin(CollisionAngle)*1.1;

                Ball3Position.X -= (float)newBall3XVel;
                Ball3Position.Y -= (float)newBall3YVel;
            }

Here is a 2D ball elastic collision I made in C#. However, the balls are of equal mass, so they simply swap parallel speed and keep perpendicular speed. You will need to change that if your balls are of different masses.
It might be a bit confusing, but ball and ball3 are the things that are colliding. I’m not sure why…

 
Flag Post

I don’t immediately see the problem, but where do the equations in step 5 come from?

Here’s how I was taught to do this, for frictionless circle-circle collisions (i.e. no angular momentum transfer to worry about):

  • Transform the frame of reference into a zero momentum frame. The velocity of this frame is v0 = (m1u1 + m2u2)÷(m1 + m2). From this point on, use u’1 = u1v0 and likewise for the second ball.
  • Now apply conservation of energy and momentum.
    – Conservation of momentum: remember we are in the zero momentum frame, so
    m1v’1 + m2v’2 = 0
    … using v’n for the ZMF velocity after the collision
    – Conservation of energy, allowing for the elasticity parameter e which specifies how much energy is lost (0 = total loss, 1 = totally elastic):
    m1|v’1+ m2|v’2|² = e(m1|u’1+ m2|u’2|²)
    (Note that I missed out the ½ in ½mv² because you can multiply the whole equation by 2 to get rid of it.)
  • Substitute v’1 from the first equation (as [m2v’2]÷m1) into the second:
    m1|[m2v’2]÷m1]|² + m2|v’2|² = e(m1|u’1+ m2|u’2|²)
    (m2²÷m1)|v’2+ m2|v’2|² = e(m1|u’1+ m2|u’2|²)
    m2|v’2|² (1 + m2÷m1) = e(m1|u’1+ m2|u’2|²)
    |v’2|² = [em1÷(m1m2 + m2²)] × (m1|u’1+ m2|u’2|²)
    … and from equational symmetry
    |v’1|² = [em2÷(m1m2 + m1²)] × (m1|u’1+ m2|u’2|²) (equation E)
  • We further know that the impulse is directly along the collision normal n, that is
    v’1 = u’1 + k1n and
    v’2 = u’2 - k2n
    (Note that because the impulse is the same on the two objects, k1m1 = k2m2; call this J (for impulse, yes I know, but I is taken for the inertia tensor).
    v’1 = u’1 + Jn÷m1
    v’2 = u’2 - Jn÷m2
    However, we can solve for k1 and come back to this.)

… therefore
|v’1|² = |u’1|² + k1²|n|² + 2k1(u’1.n) (equation V)

  • |n|² = 1 by definition (it is a unit normal) so by equating this and the expression for |v’1|² from the conservation of momentum (equations V and E) we get:
     k1² + 2k1(u’1.n) + |u’1|² = [em2÷(m1m2 + m1²)] × (m1|u’1+ m2|u’2|²)
    This looks awful but it’s actually just a quadratic equation in k1, with all the terms being things which we have before starting:
    k1² + k1[2(u’1.n)] + (|u’1- [em2÷(m1m2 + m1²)] × (m1|u’1+ m2|u’2|²)) = 0
  • Throw that at the quadratic solving equation to find k1:
    k1 = (-b±√(b²-4ac))÷2a
    … with a = 1, b = 2(u’1.n) and c = (|u’1- [em2÷(m1m2 + m1²)] × (m1|u’1+ m2|u’2|²))
    b²-4ac = 4(u’1.n)² – 4c
            = 4((u’1.n)² – c)
    => √(b²-4ac) = 2√((u’1.n)² – c)
    => k1 = ((-u’1.n) ± √((u’1.n)² – c))
  • Simplify out c:
    c = |u’1- [em2÷(m1m2 + m1²)] × (m1|u’1+ m2|u’2|²)
    … but from being in the ZMF, |u’2|² = (m1²÷m2²)|u’1|² (same as the substitution in step 3)
    => c = |u’1- [em2÷(m1m2 + m1²)] × (m1|u’1+ (m1²÷m2)|u’1|²)
    = |u’1- [em2÷(m1m2 + m1²)] ×|u’1|²(m1 + (m1²÷m2))
    = |u’1|²[1 - e[m2÷(m1m2 + m1²)][m1 + (m1²÷m2)]]
    = |u’1|²[1 - e]
  • Therefore (and this is the conclusion of all that maths)
    k1 = ((-u’1.n) ± √((u’1.n)² – |u’1|²[1 - e]))
  • Sanity check: if u’1 = -an, e=1, u’2 = an and m1 = m2 ( =m) then k1 should equal u’2 - u’1 = 2a (because we know velocities are swapped in this scenario)
    k1 = a ± √a²-c = a±√[a²-a²(1-e)]
       = a±√((2-e)a²) = 2a (or zero, which will always be a valid answer, i.e. no collision)
  • Once you have k1 then you can simply calculate J (=m1k1) and use the impulse equations to find the v’vectors. Then transform out of the ZMF back into the initial reference frame by adding v0.

Note that if e=1, c will always be 0 and therefore k1 = -2u’1.n, which is quite a shortcut.

Here’s some AS3, assuming you have a basic vector class:

/** Processes a frictionless collision between two spheres
* Input: two velocity vectors and masses, a collision normal and an elasticity constant (0-1)
* Output: a vector of two velocity vectors after the collision
**/
function sphereCollision(u1:Vector2, u2:Vector2, n:Vector2, m1:Number, m2:Number, e:Number)
     : Vector.<Vector2> {
 var v0:Vector2 = u1.Multiple(m1).Add(u2.Multiple(m2)).Multiple(1 / (m1+m2));
 var uprime1:Vector2 = u1.Subtract(v0),
  uprime2:Vector2 = u2.Subtract(v0);
 var c:Number = uprime1.Mag2() * (1 - e);
 var u1dotn = uprime1.Dot(n);
 var k1:Number = u1dotn + Math.sqrt(u1dotn*u1dotn - c);
 var J:Number = k1 * m1;
 var k2:Number = J / m2;
 
 var v1:Vector2 = uprime1.Add(n.Multiple(k1)).Add(v0),
  v2:Vector2 = uprime2.Add(n.Multiple(k2)).Add(v0);

 return new <Vector2> [ v1, v2 ];
}

(written for clarity against the above maths, not efficiency of local variables or operations)

Wow, it’s too long since I’ve done this stuff. That took a long time to remember.

 
Flag Post

Thanks for a lot of informative replies!

BobJanova when you mentioned step 5, I looked over it and realized I first change normalSpeed1 and use the changed normalSpeed1 calculating normalSpeed2.

So I will change that and if I can’t get my code to work, I will try one of your suggestions :)

Edit: Yep, that did fix it! (I’m pretty sure it looks good http://www.fastswf.com/zZ4vOsc (just bugs rarely but don’t min that I’ll fix it))

BobJanova you wrote alot and thank you very much for that. I will read through it and try to understand it! (So it’s not wasted :) )
Ah man I feel you deserve a medal for writing all that!

Edit: What is zmf?

 
Flag Post
Originally posted by ErlendHL:

Edit: What is zmf?

Zero momentum frame, mentioned in his first point.