Friday, April 16, 2010

Circle Collision Detection - Linear Trajectories

Calculating the time of impact or time of closest approach of two moving circles or spheres is a common problem in game physics. The solution to this problem can be used to solve the collisions of billiard balls on a pool table, the collision of a bullet fired with high velocity at a target in the distance, or a number of other common game scenarios in which the exact time and location of impact must be known with a high degree of accuracy.

It's a simple enough problem to solve as long as we enforce a couple key constraints. First, we assume that the circles or spheres will be moving at some constant linear velocity. Secondly, we assume that they don't change size; their radius remains constant. In the future, I'll talk about how to solve collisions between circles or spheres with non-linear trajectories and variable radii. But, for now, let's set up the problem with these constraints in mind.

Diagram 1: Two moving circles collide at some point in the future.

The first step to solving this problem is to write down the functions that describe the linear trajectories of the objects. If we let Pa and Pb be the initial positions of two circles or spheres, A and B, and let Va and Vb be the linear velocity of A and B, then their position at any time, t, are the linear parametric functions A(t) and B(t):

Next, we need the function that describes the distance between the two trajectories at any time, t. That function is simply the Euclidean distance between A(t) and B(t) minus the sum of the circle's radii, Ra and Rb.
The goal is to find time, t, where d(t) = 0. In other words, we need to find the roots of d(t). As it turns out, d(t) is quadratic, so the roots will be easy to find. We can simply expand the equation, find the quadratic terms, and then use the quadratic formula to find the roots.

We start the expansion by setting d(t) to zero and replacing the straight bracket term with the Euclidean formula.
The first step we're forced to make is to move the (Ra + Rb) term to the left side and then square both sides to eliminate the square root.
Next, we'll substitute in the equations for A(t) and B(t), combine like terms, and then collapse the position and velocity terms into single variables. This will give us a very simple equation that we can easily expand into a quadratic form.
Now we can expand the right-hand side without it being too gnarly. We'll also move the (Ra+Rb) term back to the right side. An important thing to remember here is that Pab and Vab are vector terms, so when we expand the square we use the Dot Product instead of multiplication. This is perfectly valid for two reasons: First, the dot product of a vector dotted with itself is the length of the vector squared (i.e. a.a = |a|*|a|). Second, the dot product operation is distributive (i.e. a.(b+c) = a.b + a.c). So, when we expand the right-hand side, here is what we get:

It's a nice, neat quadratic equation with simple terms. We can now pump the terms into the quadratic formula and find the roots.
There are three cases that arise from the quadratic formula. The first case is where there are no real roots. The second case is when there is exactly one real root. And the third, and final, case is where there are exactly two real roots. These cases are decided by the value of the quadratic discriminant.
Case #1: Discriminant is negative.
If the discriminant is negative, then d(t) has no real roots. The circles do not intersect at any time in the past or future. They either pass each other or are traveling with parallel trajectories. In this case, two imaginary roots are created. These complex roots are conjugates of each other. In the special case of quadratic functions, they are always precisely the same distance away from the critical point that marks the time where the distance function is minimized. Therefore, the time of closest approach is literally the average of these two imaginary roots.

However, a better way to get the location of the critical point is to take the first-derivative of the function, set it equal to zero, and solve for t. Either way, we'll get the same answer in this case.
Time, t, in this case is the time of closest approach of the two circles or spheres.

Diagram 2: The circles never intersect.

Case #2: Discriminant is zero.
If the discriminant is zero, then there is exactly one real root. That means that the circles or spheres just barely graze each other at a single point. In this case, everything under the square root in the quadratic formula goes away, and you're left with the same answer as in case #1.
But, the meaning of this time is different than in the first case. In this case, t is the exact time of the collision event.
Diagram 3: The circles graze each other.

Case #3: Discriminant is positive.
If the discriminant is positive, then there are exactly two real roots. That means that the circles or spheres pass through each other at some point in the past or future. The roots are the times when the distance between the object's surfaces equals zero. The root that produces the smallest time is the initial impact.
Time, t, in this case is the time of the initial impact.
Diagram 4: The circles hit head-on and will pass through each other.

Conclusion

In all three cases, it's worth noting that the time that is calculated can be negative, which means that the event occurred in the past. But, because most games are only concerned with future collision events, you may want to include a conditional statement in your program that returns a "no collision" response in that case.

I would like to start a series of posts describing in detail the time of closest approach of other primitives, including cones and planes. I may write a few articles on difficult collision detection problems solved via Lagrangian methods and iterative optimization techniques.

Please feel free to e-mail me or post a comment here in this blog if you find any errors or have any concerns with the material presented here. I always try to be accurate with everything I say, but I'm not a mathematician. Sometimes I end up being right, but for the wrong reasons. And, sometimes, I'm just totally wrong. :)

Demo
I created an interactable demonstration program coded in C# for Microsoft's XNA framework. You can download that demo from the Google Code archive here:

Thank you for reading. I hope you've found this information useful. :)

Josh said...

Finally, a coherent example with formulas and accompanying explanation. For whatever reason, I simply could not get the method described in "3D Math Primer for Graphics and Game Development" by Dunn to work, but the instructions shown here worked great!

Brian Stone said...

Hey Josh, thanks! I'm glad you found it useful. :)

Johan Torp said...

Nice post! Saved me from the hassle of documenting my solution, just pointed to this page :)

Anonymous said...

using the later time is handy for colliding with the inside of pipes (and half-pipes etc.)

also I think this technique is cheap enough to be used as a pre-processing step for more precise collision detection

(many games try and use grids but then miss collisions due to velocity)

This is a nice article..
Its very easy to understand ..

c#, dot.net, php tutorial

Thanks a lot..!

Anonymous said...

This demonstration is just awesome! I like it very much!!

Noman Waseem said...

Absolutely amazing job. I just ate up your post like cake. No really, thank you!

Anonymous said...

Awesome article. Thanks a lot.

Codders said...

This is a great job...
I have a question. I'm trying calculating cue ball collision. But i don't understand how to use acceleration of cueballs.I tried to use opposite direction of velocity vector as acceleration vector instead of friction force. But dont work. What can i do?

Complex Plane said...

Circle making is a easy task just draw a curve in response to a fix line from a central point and all points are the the same distance from the center.
(x-a)2 + (y-b)2 = r2 is standard equation of a circle. where r is radius.

Anonymous said...

Hey, so if my velocity is in vector form v=(vx,vy); Am I correct that in order to use this formula, I have to resolve the velocities: v_resolve=sqrt(vx.^2 + vy.^2), in order to find t?

Anonymous said...

excellent - loved it

Dmitry said...

I've created a small geogebra applet for this.
http://www.geogebratube.org/material/show/id/45894

Anonymous said...

I am trying to implement this logic in pool table. But there is a problem with this logic....If your cue ball is in the middle of two other balls, and if my target is one of the two balls, this logic returns true for both the balls. It happens, only if both the balls are exactly opposite to each other.

Anonymous said...

This is my logic to find the intersection
function circleLineIntersect(x1, y1, x2, y2, cx, cy, cr )
local deltaY = (cy - y1);
local deltaX = (cx - x1);
local circleAngleInDeg = math.atan2(deltaY, deltaX) * (180/math.pi)

local dx = x2 - x1
local dy = y2 - y1
local a = dx * dx + dy * dy
local b = 2 * (dx * (x1 - cx) + dy * (y1 - cy))
local c = cx * cx + cy * cy
c = c + (x1 * x1 + y1 * y1)
c = c - (2 * (cx * x1 + cy * y1))
c = c - (cr * cr)
local bb4ac = b * b - 4 * a * c

if(bb4ac < 0 )then
return false -- No collision
else
return true --Collision
end
end

bob said...

Thank you so much for this post. I was totally lost on this problem (read: very close but on the wrong track.) I could not imagine a better explaination of this situation! I hope you do more posts in this vein in the future but I know how it is. Thank you!

Teflo said...

Very nice post!

But it seems I can't bring it to work for small scaled velocities ( < 1). Is there any fix for this? I checked my code a thousand times and it seems to be correct, espacially as it works for larger velocities.

Teflo said...

Oh, I'm sorry, I solved it.

I'm using this code just in special cases for performance reasons. It happened that the circles were already overlapping. To get this algorithm to work I had to separate them first.

bob said...

Thank you so much for this post. I muddled for so long trying to solve this on my own. This is a perfect demonstration.

Anonymous said...

I may be mistaken but somethings look wrong.
1. Position is defined by x & y vals and not a single val.
2. If you consider the distance formula, the inside of the root will look differently.

Anonymous said...

Last comment was mine. Sorry, didn't know u were using vectors. Sorry about that.

krishx007 said...