C++/Ray Tracing Intersection

This tutorial helps you determine if a ray passes through a triangle. This type of situation is usefull for collision detection between bullets and objects in the world as well as laser-sight on a gun. This tutorial assumes you have a decent understanding of 3D maths and are familiar with some of the basic DirectX maths functions.

THE RAY



I had a terrain class in my C++ game engine that used ray-tracing to determine the height at specific points on the terrain. The start of the ray was the character height above the terrain and the end was some arbitrary point like 1000 units down along the y axis. I performed a collision check with the triangle the character was on and it returned the intersection point; the height on the terrain used to position the character. I used the 3D maths functions that came with the DirectX 8.1 SDK to make my life a lot easier but you still have to know the basics of 3D maths to understand things!

Figure: A ray passing through a triangle

First off you need the start and stop points (or position vectors) of the ray. Think of the ray like the laser-sight on a gun, the start point is on the gun barrel and the stop point is some far away distance; you could use the size of your entire world for this distance if you wanted, so far the gun and ray point in the same direction. The ray is simply the direction vector from the start position to the stop position.

D3DXVECTOR3 start(gunx,guny,gunz);	// ray start position
D3DXVECTOR3 stop(endx,endy,endz);	// ray stop position
D3DXVECTOR3 ray = stop-start;		// ray difference vector

Now you need to cycle through every single triangle in your entire world (each 3D object at a time) and look for an intersection between your ray and every single triangle. Each triangle will give you 3 position vectors representing the three corners and using this you can create an infinite plane.

// create infinite plane
D3DXPLANE vPlane;
D3DXPlaneFromPoints(&vPlane,
&D3DXVECTOR3(triA.x,triA.y,triA.z),
&D3DXVECTOR3(triB.x,triB.y,triB.z),
&D3DXVECTOR3(triC.x,triC.y,triC.z));

// infinite plane normal
D3DXVECTOR3 vPlaneNormal(vPlane.a,vPlane.b,vPlane.c);

The plane is basically at the same position and orientation of the triangle but spans infinitely in all directions. The normal of the plane is another directional vector like the ray that lets you determine the planes direction. Now you need to use a dot product to first off check that the ray is not running parallel along side the plane because if it is then there won't be any intersection.

D3DXVec3Dot(&vPlaneNormal,&ray)

If the result of that is 0 then the ray and plane normal are perpendicular which means the ray and plane are parallel. However if this is not the case then there should be an intersection.

if(D3DXVec3Dot(&vPlaneNormal,&ray)==0)
{
   return 0.0f;
} else {
   float amount = -(D3DXPlaneDotCoord(&vPlane,&start))
                         / D3DXVec3Dot(&vPlaneNormal,&ray);

   D3DXVECTOR3 iPoint = start+(ray*amount);
}	

So if there's an intersection then you can calculate how far into the ray from the start position you must traverse before you reach the intersection point (amount). That little bit of code there does that for you and the resulting iPoint position vector is the intersection point. So iPoint.x, iPoint.y and iPoint.z are your intersection coordinates.

THE INTERSECTION POINT



However your not done yet, your using an infinte plane so the intersection could have occured anywhere on the plane. Your looking to see if this intersection occurred within the triangle and not the entire infinite plane.

One method of determining if a point lies within a 3D triangle is the sum of all angles method. Imagine a triangle with points A, B and C. Now picture a point lying on it 'i'. If this point truly lies within the triangle then the sum of all angles CiA, BiC and AiB would be exactly 360 degrees or 2Pi depending on your units. What i did was calculate 3 direction vectors iA, iB and iC then by finding the angles between these vectors using a dot product we could determine is the sum of angles is 360 degrees.

D3DXVECTOR3 IA(triA.x - iPoint.x, triA.y - iPoint.y, triA.z - iPoint.z);
D3DXVECTOR3 IB(triB.x - iPoint.x, triB.y - iPoint.y, triB.z - iPoint.z);
D3DXVECTOR3 IC(triC.x - iPoint.x, triC.y - iPoint.y, triC.z - iPoint.z);

triA, triB and triC are the position vectors of each point on the triangle. Now at this point we can calculate the angles. You may see a function called 'mag()' below, this basically means the magnitude of the vector. So a vector A with points A.x, A.y and A.z would have a magnitude of sqrt(sqr(A.x)+sqr(A.y)+sqr(A.z))

float ang_IAIB = acos(D3DXVec3Dot(&IA,&IB)/(mag(IA)*mag(IB)));	// IA and IB angle
float ang_IBIC = acos(D3DXVec3Dot(&IB,&IC)/(mag(IB)*mag(IC)));	// IB and IC angle
float ang_ICIA = acos(D3DXVec3Dot(&IC,&IA)/(mag(IC)*mag(IA)));	// IC and IA angle

float sumOfAngles = ang_IAIB + ang_IBIC + ang_ICIA;

if the sumOfAngles is roughly equal to 360 degrees or 2Pi (about 6.28) then my friend the intersection point lies within the triangle and you have a hit. Now you gotta do this for each and every triangle in the scene... Good Luck!

THE AFTERMATH



So basically you can perform these calculations in the background and once the player presses the fire button you can determine the intersection point straight away and position your bullet hole, reduce the target objects life or whatever.

Another thing you should note is that you may have multiple intersections with multiple objects during your check. You could us a simple distance check to determine the closest intersection with your ray from the gun to find the triangle that 'got shot first'. Heck you could even have a bullet that passes through enemies and use all the intersection points as bullet entrance/exit wounds.

If you run into any problems understanding this stuff then just ask a question and i'll see what i can do. There are tons of tutorials covering this stuff on the web with pictures to help you visualise the whole process.

Hope that helped!


updated 13.10.06