TestIntersection call in UsdImagingGLEngine - How To Use & What To Expect

Hi,

Can anyone give an example of how to use this TestIntersection function? I’m assuming that the idea it to get a cheap ray-cast style surface hit, but i’m not quite sure how to use it.

What do the view and projection matrices mean in the context of this function? Are you meant to create a very narrow frustum that it will “look” down and effectively emulate a “ray”?

Cheers for any advice here.

  • Simon.

I played with this in the past. I no longer have any example code, but yes, you do use the matrices to make a narrow frustum.

Jerry

Here’s a worked example for you. Note that the camera class referenced here is not a GfCamera, but an engine camera that uses physical (real world) metrics, not scene referred metrics. It uses an OpenGL style camera, so has an invert in it to make it compatible with Hydra. So depending on how your camera works you may or may not need an inverse there.

_self->params refers to the Hydra rendering params structure, and _self->engine refers to an HdEngine object, like UsdGLImagingEngine.

        GfFrustum cameraFrustum;
        cameraFrustum.SetPositionAndRotationFromMatrix(
                            GfMatrix4d(camera->GetViewMatrix().GetInverse()));
        cameraFrustum.SetProjectionType(pxr::GfFrustum::ProjectionType::Perspective);
        double targetAspect = double(width) / double(height);
        float filmbackWidthMM = camera->GetCamera().sensor.aperture.y.mm;
        double hFOVInRadians = 2.0 * atan(0.5 * filmbackWidthMM /
                                          camera->GetCamera().optics.focal_length.mm);
        double fov = (180.0 * hFOVInRadians) / M_PI;
        cameraFrustum.SetPerspective(fov, targetAspect, _self->znear, _self->zfar);
     
        GfVec2d clickedPoint(_self->pick_x / width, _self->pick_y / height);
        
        clickedPoint[0] =  2.0 * clickedPoint[0] - 1.0;
        clickedPoint[1] = -2.0 * clickedPoint[1] + 1.0;
        //printf("testing intersection at %f %f\n", clickedPoint[0], clickedPoint[1]);
        
        // "clickedPoint" is normalized to the image viewport size, but if the image
        // is cropped to the camera viewport, the image viewport won't fill the
        // whole window viewport.  Clicking outside the image will produce
        // normalized coordinates > 1 or < -1; in this case, we should skip
        // picking.

        if (abs(clickedPoint[0]) > 1.0 || abs(clickedPoint[1]) > 1.0) {
            selection->ClearSelection();
        }
        else {
            pxr::GfVec2d size(1.0 / width, 1.0 / height);
            GfFrustum pixelFrustum = cameraFrustum.ComputeNarrowedFrustum(clickedPoint, size);
            
            GfVec3d outHitPoint;
            GfVec3d outHitNormal;
            SdfPath outHitPrimPath;
            SdfPath outHitInstancerPath;
            int outHitInstanceIndex;
            _self->params.enableIdRender = true;
            _self->hitValid = _self->engine->TestIntersection(pixelFrustum.ComputeViewMatrix(),
                                                       pixelFrustum.ComputeProjectionMatrix(),
                                                       stage->GetPseudoRoot(),
                                                       _self->params,
                                                       &outHitPoint, &outHitNormal,
                                                       &outHitPrimPath, &outHitInstancerPath,
                                                       &outHitInstanceIndex);

            // hit generation indicates that a Test has been run
            _self->hitGeneration++;
            if (_self->hitValid) {
                printf("prim %s\n", outHitPrimPath.GetString().c_str());
                _self->selectedPrim = outHitPrimPath;
                _self->hitPoint = outHitPoint;
                _self->hitNormal = outHitNormal;
            }
            else {
                selection->ClearSelection();
            }
        }

That’s brilliant Nick - thanks. Between that and looking at the python code in USDView I can get it working now. I use the frustum from the current scene camera rather than creating one like you do (and I need to still handle when the rendered image is not locked to the camera aspect) but it does now work in principle. I need to get my head around what renderparams to send down to the call too to ensure it remains efficient.

Interestingly, I noticed that the intersection tests are quite poor in some cases (using usd view) , but in a weird way. I have a test scene with a sphere of radius 2 at the origin, and another with a radius 1 next to it and the hit on the smaller one is perfect, but on the larger one it is quite poor. I was not expecting it to be fine on the smaller geometry, but not on the larger (essentially the same) one. It seems to be ignoring the radius entirely (I get the same intersection point with a radius of 4). I’ve attached a screenshot showing where the tooltip pops up (the red dot) which is using the TestIntersection code in UsdView. If I hover on any of the cubes or small sphere’s its perfect.

Any idea on why that could be? I know this is a contrived example, but I assume it’s valid USD, so maybe it’s a bug that would not normally see with “real” model data. The spheres are defined in code, but is written out in the udsa file (which is then loaded into UsdView) as:

def Sphere "sphere1" (
   prepend apiSchemas = ["MaterialBindingAPI"]
   )
   {
      rel material:binding = </root/mat/material>
      double radius = 2
   }
  ..
def Sphere "sphere2" (
   prepend apiSchemas = ["MaterialBindingAPI"]
   )
   {
      rel material:binding = </root/mat/material>
      double radius = 1
      double3 xformOp:translate = (0, 0, 3.5)
      uniform token[] xformOpOrder = ["xformOp:translate"]
   }
   .. and so on
  • Simon.

Hi, I just tested this, and it does indeed appear that hit testing does not take the radius of the sphere into account. If I leave radius at 1, and instead use the scale transform op to change the size of the sphere, hit testing works properly. This behavior occurs with or without a material binding, so that aspect is unrelated.

Would you mind reporting this issue at the github repo as a bug please? And if you wouldn’t mind attaching your test file as well, it’ll make it easy for whoever tackles this issue to repro.

In the mean time, perhaps you can use xformOp::scale on your spheres to unblock the work you want to do.

Done. Filed under:

Thanks for the help Nick, much appreciated!