3D Hit Testing

How to do 3D hit testing has come up a bit recently in the forums but essentially it isn’t any different than 2D hit testing which is described on MSDN here. You can either start with a 2D point on the Viewport3D or a 3D point on a Visual3D.

Starting at the Viewport3D level is straightforward. Suppose you have a mouse down handler and you want it to hit test into 3D:

 public void MouseHitTest(object sender, MouseButtonEventArgs args)
{
    Point mousePos = args.GetPosition(viewport3d);
    PointHitTestParameters hitParams = new PointHitTestParameters(mousePos);
    VisualTreeHelper.HitTest(viewport3d, null, ResultCallback, hitParams);
}

Visual3D is slightly trickier because a point is no longer sufficient in 3D space. You also need to specify a direction. An origin point plus a direction is known as a ray. It will look something like this:

     RayHitTestParameters hitParams = 
        new RayHitTestParameters(
            new Point3D(0, 0, 0),
            new Vector3D(1, 0, 0)
            );
    VisualTreeHelper.HitTest(visual3d, null, ResultCallback, hitParams);

The only difference is the HitTestParameters.

The second argument to HitTest is a filter which allows you to control which parts of the tree get tested. Skipping part of the tree is a perf win of course, but generally you’ll want to hit test everything and this is covered well in the MSDN article so I won’t do it here.

The third argument, the result callback, is where the action happens. We’ve hit something and we’ve decided to tell you about it. It could be 3D or it could be 2D. It’s up to you to decide what you’re looking for and when/if you want to stop.

 public HitTestResultBehavior ResultCallback(HitTestResult result)
{
    // Did we hit 3D?
    RayHitTestResult rayResult = result as RayHitTestResult; 
    if (rayResult != null) 
    { 
        // Did we hit a MeshGeometry3D?
        RayMeshGeometry3DHitTestResult rayMeshResult = 
            rayResult as RayMeshGeometry3DHitTestResult;
                                                                                          
        if (rayMeshResult != null) 
        { 
            // Yes we did!
        } 
    } 
    
    return HitTestResultBehavior.Continue; 
}

RayMeshGeometry3DHitTestResult has MeshHit, PointHit, and DistanceToRayOrigin properties which tell you pretty much everything you want to know. It also has the triangle indices and weights (a.k.a. barycentric coordinates) so you can do things like figure out the texture coordinate of the point hit.

Performance Tip: As you mouse over 3D we do hit testing behind your back because there could be interactive 2D on the 3D or a 2D layer underneath the 3D (empty Viewport3D space is considered transparent for hit testing purposes). If you know you don’t need either of those, disable hit testing on the Viewport3D by setting its IsHitTestVisible property to false. This is an old tip but a good one :)

Sneaky Performance Tip: Can’t disable hit testing and it’s still eating up a bunch of CPU? Override HitTestCore() and, while the mouse is moving, only really issue the hit test every two or three (or more!) requests. This will introduce lag of course but that may be acceptable to your application.

-- Jordan