Picking with Triangle Accuracy Sample

This sample shows you how to access vertex position data from a model. It then uses that information to implement a per-triangle picking test.

Sample Overview

This sample builds on the ideas presented in the Picking sample. If you are unfamiliar with the process of converting between world space and screen space and checking for intersection between rays and bounding primitives, you should start with the Picking sample, which explains these basic concepts.

This sample extends the approximate bounding box tests from the Picking sample to perform an accurate per-triangle picking test. It loops over all the triangles in the model and performs a ray-to-triangle intersection test for each one. It uses a custom content processor to extract the model vertex position data into a format that can be easily accessed at run time.

Sample Controls

This sample uses the following keyboard and gamepad controls.

Action Keyboard control Gamepad control
Move the cursor N/A Left thumbstick
Move the camera

UP ARROW, DOWN ARROW, LEFT ARROW, RIGHT ARROW or W, A, S, D

Right thumbstick
Zoom in and out Z, X Left and right triggers
Reset the camera R Click the right thumbstick
Exit ESC or ALT+F4 BACK

On Windows, you can also use the mouse to move the cursor.

How the Sample Works

The TrianglePickingPipeline project implements a custom content processor called TrianglePickingProcessor. This derives from the built-in ModelProcessor class, and overrides the Process method. It uses the recursive FindVertices method to scan over the tree of input geometry, storing all the vertex positions into a list (three entries per triangle). It then chains to the base class ModelProcessor.Process method to convert the input NodeContent object into a ModelContent instance, and attaches the list of vertex positions to the Tag property of the output ModelContent.

To make use of this custom processor, the TrianglePickingPipeline assembly is added as a reference to the content project, as described in Using a Custom Importer or Content Processor. In the properties for each model asset, you can now set the Content Processor field to TrianglePickingProcessor. If you forget to do this when adding a new model, you will get an exception when trying to pick against that model because the Tag property will not contain the neccessary vertex position data.

The RayIntersectsModel method in Game.cs implements the picking. The implementation transforms the input ray by the inverse of the model transform matrix. This action is neccessary because the input ray is in world space, but our model data is stored in object space. Normally, you would have to transform all the model data by the model transform matrix. This moves it into world space before testing it against the ray. However, that transform can be slow if there are a lot of triangles in the model. Therefore, you should do the opposite. Transforming the ray by the inverse model transform moves it into object space. In object space, you can test it directly against your model data. Since there is only one ray, but typically many triangles, this approach can be much faster.

To keep things efficient, the RayIntersectsModel method starts by applying a bounding sphere test. This means it tests each triangle only if the ray intersects the bounding sphere. This enables you to trivially reject many models without even looking at their triangle data. The sample displays a list—in the upper left corner of the screen—of all the models that passed this bounding sphere test. If you move the cursor near a model, you will notice that sometimes the approximate bounding sphere test passes even when the more accurate triangle test does not find any intersection. But when the cursor is far away, this bounding test can quickly reject any need to examine the model more carefully.

If the bounding sphere test passes, RayIntersectsModel then goes into a loop, calling the RayIntersectsTriangle method once for every triangle in the model. It keeps track of the closest triangle yet discovered so it can always return the closest match if the ray intersects more than one triangle.

Extending the Sample

This sample loops over all the triangles in a model, calling RayIntersectsTriangle once for each. If there are too many triangles, this can be slow. To perform fast collision detection against more detailed models, you could organize the collision triangles into some kind of hierarchical structure such as an octree. This enables the use of more efficient intersection algorithms.

Sometimes you might use different versions of your geometry for collision and rendering. For example, you might want to display a high-detail mesh, but use an approximated version with fewer triangles for your collision tests. You could do this entirely in the content processor, without requiring any run-time code changes at all. You could change the FindVertices method to store only vertex positions from meshes labeled with "collision" in their names. Then you could remove these meshes from the input tree so they won't be picked up as part of the visible model. You could then create files containing two different versions of each mesh. One version would be for drawing, while the other would be a specially-labeled version for the collision system.