One of the most important group of algorithms inside game engines are intersection algorithms. Even though there are plenty of sites showing code for intersection algorithms, there are only a few describing the math backround behind this, and algorithm derivation. To understand this article you need to know just a bit about linear algebra.
Primitive description
Let's start with a mathematical description of primitives we're going to intersect. For this article I've used just ray (line), plane, sphere, axis-aligned bounding box and triangle ... these should be enough for a basic game engine.
Ray
This is the simplest (and very useful) primitive you can think about. Let us define it with two 4-component numbers, point (further referred as origin) and direction. Mathematically the definition is: Where:
- x represents any point on a line
- o represents some exact specified point on line (e.g. origin)
- d represents direction vector of line
- t is parameter within some specified range
The range specifies whether our line is infinite in both directions (+d and -d), whether it's just in a single direction, or whether it's finite. Writing a ray object in some programming language is straight-forward of course, you should end up with something like this: class Ray { public: float4 origin; float4 direction; <> <> };
Constructor and methods are straight-forward (and should be also inline for performance reasons).
Plane
Another basic primitive. There are more ways to define a plane, but we will derive one that is well known and used. What is a plane? It's a point set that contains every such point in space, that when we create vector from that point to our known point on a plane, that vector has to be perpendicular to plane normal. And we know that perpendicular vectors are those whose dot product is equal to zero. So: Where:
- n is unit vector in the plane normal direction
- x is every point on the plane
- p is our specified point on the plane
Expanding this yields: Substitute: And we get something very familiar: Which is a plane equation (so we proved that the definition we use is actually a plane). Definition in programming language could be like this: class Plane { public: float4 point; float4 normal; <> <> };
A very simple definition. Let's jump ahead.
Sphere
Yet another basic primitive, that is widely used. The definition is surface containing all points in distance from sphere's center less-or-equal to sphere's radius. We will define it using an analytic equation: Where:
- x, y and z are coordinates of point in sphere
- p represents sphere's center
- r represents sphere's radius
A sphere class is again very trivial: class Sphere { public: float4 center; float radius; <> <> };
So much for sphere; simple, yet very useful primitive.
Axis-aligned Box
From here on I'll call this one just AABB (which stands for Axis-Aligned Bounding Box). Definition is very simple, as we work in Cartesian coordinate system, we have perpendicular axes X, Y and Z - AABB is defined as volume between minimum and maximum point in this system, the sides are made by planes orthogonal to planes formed by all 3 combinations of 2 base axis (XY, XZ and YZ). Mathematically we can define it as: Where:
- min_p stands for AABB minimum point
- max_p stands for AABB maximum point
- x represents points inside AABB
In programming language it can look like: class AABB { public: float4 min_p; float4 max_p; <> <> };
Let's jump ahead to last object we're going to use.
Triangle
The first and only primitive in this article that is going to be defined by more than 2 values, yet very important. Most of the game worlds are described by triangles, most of the simulation use objects described as triangles, and every more (or less) complex 3D object can be decomposed into triangles. They're awesome; as opposed to other boring N-gons, triangles are never concave! Let's define triangle with 3 points, then any point on triangle can be described as: With conditions: Where:
- x is every point in triangle
- A, B, C are points defining triangle
- a, ss, ? are so called barycentric coordinates (e.g. parameters in the equation above)
A triangle inside of a program could be defined as: class Triangle { public: float4 p[3]; <> <> };
Intersections
So now that we have defined some of the basic primitives, it's time to derive intersection equation and also intersection algorithms between the primitives. As I want to keep the article short (and I don't want to storm you with zillion of equations and exhausting equation solving), I will just show you 4 derivations - Ray-Plane (analytic derivation), Ray-Sphere (geometric derivation), Ray-AABB (tricky derivation) and Ray-Triangle (more complex analytic derivation). You should get an idea of how the intersection equation can be derived and in the end you should be able to derive the rest of them on your own.
Ray-Plane intersection
I will derive Ray-Plane intersection analytically, as this is one of the most simple approaches to the derivation. When we want to derive some intersection algorithm analytically, we should write equations of those 2 objects right away, so (I intentionally used commonly known plane equation, instead of our definition): Now we're looking for a point that lies on both, the ray and the plane - so we're looking for x. So we first write the first equation in scalar form: Let's substitute these 3 to plane equation: It's time to reverse substitution we did when "deriving" plane equation, e.g.: And transformed to vector form we get: But we can optimize this a little bit: Voila, that's what we have been looking for! So basically we need just subtraction, 2 dot products and 1 division. In program this can look like (assuming we want to intersect planes in front of ray origin in direction of ray): bool IntrRayPlane(const Ray* r, const Plane* p, float* t) { float denom = dot(p->normal, r->direction); // Test whether ray and plane aren't parallel if(fabsf(dotND) < EPSILON) return false; *t = -(dot(p->normal, r->origin - p->point) / denom); return ((*t) > 0.0f); }
Almost as simple as the equation. Let's jump ahead, time for spheres!
Ray-Sphere intersection
I could perform analytic derivation like in the Ray-Plane intersection derivation, although Ray-Sphere intersection is just one of the cases that can be easily derived using geometry. Let's start with an image: We're now looking for 2 distances: Finding C-O is quite straightforward, it's just simple: Another thing we need to find is distance along the ray to point X, but as we know ray direction, it's just simple projection of L. Right now we should look again at the image, we can see 3 important right angle triangle, one formed by {d, OX and OC}, another one {d, r, PX} and last one {d, r, P'X}. We need to know distance PX (respectively P'X, they're equal). So let's use Pythagorean theorem twice: And our 2 hit distances are: Time for the code (searching just nearest hit): bool IntrRaySphere(const Ray* r, const Sphere* s, float* t) { float rad2 = s->radius * s->radius; float4 L = s->center - r->origin; float tPX = dot(L, r->direction); if(tPX < 0.0) return false; float dsq = dot(L, L) - tPX * tPX; if(dsq > rad2) return false; float thit = sqrt(rad2 - dsq); *t = tPX - thit; if(*t < 0.0f) *t = tPX + thit; return ((*t) < 0.0f); }
Just a side note. Analytic derivation in this case is also very trivial, you will end up with quadratic equation, where determinant is negative in case of no hit, zero in case of single point hit (e.g. tangent line), and positive in case of secant. Let's jump ahead to boxes!
Ray-AABB intersection
Another important one, although a bit tricky. Let's start with an image again: You can see, that box in 2D is formed by 2 slabs, horizontal and vertical. 3D is analogous to this, but of course it's formed by 3 slabs. For each slab we want to find minimum distance and maximum distance, e.g.: Where a and b represents minimum (respectively maximum) point of our AABB. What we get is the minimum and maximum distance to each slab. Now we have to correctly order the distances along the ray direction, this is quite simple: Where min/max is defined as: Entry/exit point is then defined as maximum of near distances (see the image), thus: Where hmax/hmin function is horizontal minimum/maximum, defined as: Of course a hit only occurs when the exit point is larger than zero, and the exit point is further than the entry point (if it isn't, we missed the AABB). Time to code: bool IntrRayAABB(const Ray* r, const AABB* b, float* enter, float* exit) { float4 tmin = (b->minimum - r->origin) / r->direction; float4 tmax = (b->maximum - r->origin) / r->direction; float4 tnear = f4min(tmin, tmax); float4 tfar = f4min(tmin, tmax); *enter = max(max(tnear.x, 0.0f), max(tnear.y, tnear.z)); *exit = min(tfar.x, min(tfar.y, tfar.z)); return (*exit > 0.0f && *enter < *exit); }
It can be a little more optimized, although I'll leave that to reader as an exercise.
Ray-Triangle intersection
I'd like to say, there is like a dozen of ways to intersect a ray with a triangle. Lots of them don't even use a triangle defined by 3 points, and re-define it in better suited way. I'll show you how to derive plain good old Barycentric test with one huge advantage, you don't have to precompute a triangle in any way, just use the standard definition with 3 points. Let's start with equations we already have: The triangle equation actually is equal to this: And let's arrange it like this: Substituting line equation for x yields: Those are 3 equations with 3 unknown variables. Instead of thinking about some more clever way, let's solve it in brute force manner with Cramer's rule. First let's re-arrange it to better form: For clarity let's substitute e1 for B-A, e2 for C-A and p for o-A. Now it's time to apply Cramer's rule, e.g.: But matrix determinant can be written as scalar triple product, so: I know this might look scary right now, but it really is simple. For those that understand code easier than math I also add code: bool IntrRayTriangle( const Ray* r, const Triangle* t, float* thit, float* u, float* v) { float4 e1 = t->v[1] - t->v[0]; float4 e2 = t->v[2] - t->v[0]; float4 pvec = cross(r->direction, e2); float det = dot(e1, pvec); if(det > -EPSILON && det < EPSILON) return false; float inv_det = 1.0f / det; float4 tvec = r->origin - t->v[0]; *u = dot(tvec, pvec) * inv_det; if(*u < 0.0f || *u > 1.0f) return false; float4 qvec = cross(tvec, e1); *v = dot(r->direction, qvec) * inv_det; if(*v < 0.0f || *u + *v > 1.0f) return false; *thit = dot(e2, qvec) * inv_det; return (*thit > 0.0f); }
Conclusion
Intersection algorithms, as I've said, are core algorithms for practically every game engine! And not just game engines, ray-tracers, physics simulators, collision detection libraries, even some AI simulations use them, etc. If you made it here, then I have to say thanks for reading my article, and I hope you've learned something new. 9 Apr 2013: Initial release
Notes:
Mirror posted on my site (with fixed formatting) - https://www.otte.cz/new/page/articles/post/10
I did some grammar clean up but didn't mess around too much with the various definitions. Anyone with a better understanding of the terms can help the author clean those up if they feel it necessary.