SoRayPickAction( C++ | Java | .NET ) finds objects along a ray from the camera through a point on the near plane of the view volume. This ray is typically specified by giving the coordinates of a window-space pixel through which it passes. SoRayPickAction( C++ | Java | .NET ) traverses the scene graph you apply the action to and then returns the paths to all shapes along the picking ray, sorted from nearest to farthest. The picking action is primarily interested in geometry, transformation, and shape nodes.
Tip: The SoSelection( C++ | Java | .NET ) node picks objects automatically. You don't need to explicitly use the pick action to select objects. The SoHandleEvent action also performs picking automatically. In addition, the SoEventCallback( C++ | Java | .NET ) node allows you to register a callback function that is invoked whenever a certain event (such as a mouse press) occurs over a specified object. See Chapter 10, Handling Events and Selection for more information on SoSelection( C++ | Java | .NET ), SoHandleEvent, and SoEventCallback( C++ | Java | .NET ). |
By default, all objects in the scene graph are pickable (even invisible and transparent objects). To make an object or group of objects invisible to the pick action, insert an SoPickStyle( C++ | Java | .NET ) node in the scene graph and set its style field to UNPICKABLE. Anything that follows in the scene graph cannot be picked until the SoPickStyle( C++ | Java | .NET ) node is reset to SHAPE (to pick points on the shape objects in the scene) or BOUNDING_BOX (to pick points on the bounding boxes for the objects in the scene). BOUNDING_BOX pick style is most often used for SoText3( C++ | Java | .NET ) nodes. The pick style, like all other properties, is saved and restored by SoSeparator( C++ | Java | .NET ) groups.
The constructor for SoRayPickAction( C++ | Java | .NET ) has one parameter, the viewport region (a required parameter).
An example of creating an instance of SoRayPickAction( C++ | Java | .NET ) is
SbViewportRegion myViewport; SoRayPickAction myPickAction(myViewport);
SbViewportRegion myViewport = new SbViewportRegion(); SoRayPickAction myPickAction = new SoRayPickAction(myViewport);
SbViewportRegion myViewport = new SbViewportRegion(); SoRayPickAction myPickAction = new SoRayPickAction(myViewport);
The viewport region is used to compute the bounding boxes for screen-aligned objects such as SoText2( C++ | Java | .NET ).
Before you apply the picking action, you can set the following parameters:
Ray to pick along
Whether to return all objects along the ray, or only the closest one
The picking ray can be specified in one of two ways: either specify a window point and a radius, or specify a point and a direction in world space. The first method is the more typical for interactive programs, since you are generally most interested in the area underneath the cursor.
Before you apply the picking action, use the setPoint() and setRadius() methods to set the ray to be used for picking.
The ray to pick along is typically specified in viewport coordinates, where (0, 0) is the lower left corner of the viewport and (vpWidth-1, vpHeight-1) is the upper right corner (see Figure 8.4, “ Cone Representing the Picking Ray for a Perspective Camera ”). In the figure, the viewport is 1000 by 1000. The near plane of the camera maps to the picking viewport.
To make it easier to pick lines and points, the ray can be augmented to be a cone (for a perspective camera; see Figure 8.4, “ Cone Representing the Picking Ray for a Perspective Camera ”) or a cylinder (for an orthographic camera). Use the setRadius() method to control the size of this cone or cylinder where it intersects the near plane of the camera. (The default radius is 5 pixels.) Things that are picked must fall within this cone (or cylinder), as follows:
For points and lines, if any part of the shape falls within this cone, it is picked. (A sphere drawn with LINES draw-style is still picked as a solid sphere.)
For all other shapes, the ray itself must intersect the shape for it to be picked.
You can also specify the picking ray by specifying a world-space ray along which to pick. The ray is defined as a starting point, a direction vector, and a near distance and far distance for the picked objects. No radius is used. For example:
SbViewportRegion viewport(400, 300); SbVec2s cursorPosition(250, 125); SoRayPickAction myPickAction(viewport); myPickAction.setRay(SbVec3f(0.0, 0.0, 0.0), // starting point SbVec3f(0.0, 0.0, -1.0)); // direction vector
SbViewportRegion viewport = new SbViewportRegion(400, 300); SbVec2s cursorPosition = new SbVec2s(250, 125); SoRayPickAction myPickAction = new SoRayPickAction(viewport); myPickAction.SetRay(new SbVec3f(0.0f, 0.0f, 0.0f), // starting point new SbVec3f(0.0f, 0.0f, -1.0f)); // direction vector
SbViewportRegion viewport = new SbViewportRegion((short)400, (short)300); SbVec2s cursorPosition = new SbVec2s((short)250, (short)125); SoRayPickAction myPickAction = new SoRayPickAction(viewport); myPickAction.setRay(new SbVec3f(0.0f, 0.0f, 0.0f), // starting point new SbVec3f(0.0f, 0.0f, -1.0f)); // direction vector
This example uses the default near and far distances, which disables clipping to the near and far planes.
The picking action can be applied to either a node, a path, or a path list. To apply the picking action to the root node of a scene graph:
pickAction->apply(rootNode);
pickAction.Apply(rootnode);
pickAction.apply(rootnode);
The results of the pick are stored in an SoPickedPoint( C++ | Java | .NET ) (for the first hit) or an SoPickedPointList( C++ | .NET ) (for information on all hit objects). Use the methods on SoPickedPoint( C++ | Java | .NET ) to obtain this information.
An SoPickedPoint( C++ | Java | .NET ) represents a point on the surface of an object that was picked. The picked point contains the point of intersection, the surface normal and texture coordinates at that point, the index into the current set of materials, and the path to the object that was intersected. Use the following methods on SoPickedPoint( C++ | Java | .NET ) to obtain this information:
returns the intersection point, in world space. | |
returns the surface normal at the intersected point, in world space. | |
returns the texture coordinates at the intersection point, in image space. | |
returns the index into the current set of materials that is used at the intersection point. If the materials are interpolated between vertices, the index corresponds to the material at the closest vertex. | |
returns the path to the object that was intersected. |
For example:
SoPath *pathToPickedObject; const SoPickedPoint *myPickedPoint = myPickAction.getPickedPoint(); if (myPickedPoint != NULL) pathToPickedObject = myPickedPoint->getPath();
SoPath pathToPickedObject; SoPickedPoint myPickedPoint = myPickAction.GetPickedPoint(); if (myPickedPoint != null) pathToPickedObject = myPickedPoint.GetPath();
SoPath pathToPickedObject; SoPickedPoint myPickedPoint = myPickAction.getPickedPoint(); if (myPickedPoint != null) pathToPickedObject = myPickedPoint.getPath();
Figure 8.5, “ Path to Picked Point and Detail List ” shows the path returned by an SoRayPickAction( C++ | Java | .NET ) (which can be obtained with the getPath() method on SoPickedPoint( C++ | Java | .NET )). This path contains a pointer to each node in the path to the picked object. Use the following methods on SoPickedPoint( C++ | Java | .NET ) to obtain information about the pick in the object space of a particular node in the path chain. You pass in a pointer to the node you are interested in, or use the default (NULL) to obtain information about the tail of the path:
Each node in the picked path may have an associated SoDetail( C++ | Java | .NET ) in which it can store additional information about the pick. For some classes, this associated SoDetail( C++ | Java | .NET ) is NULL. Table 8.2, “Classes That Store an SoDetail” shows the classes that store information in a subclass of SoDetail( C++ | Java | .NET ).
Figure 8.6, “ Detail Classes ” shows the class tree for SoDetail( C++ | Java | .NET ).
Use the getDetail() method onSoPickedPoint( C++ | Java | .NET ) to return the detail for a given node in the picked path. This method takes a pointer to a node in the picked path. It returns information for the tail of the path if NULL or no node is specified. For example, to determine whether a cylinder was hit and, if so, whether it was the top part of the cylinder, the code would be as follows:
const SoDetail *pickDetail = myPickedPoint->getDetail(); if (pickDetail != NULL && pickDetail->getTypeId() == SoCylinderDetail::getClassTypeId()) { // Picked object is a cylinder SoCylinderDetail *cylDetail = (SoCylinderDetail *) pickDetail; // See if top of the cylinder was hit if (cylDetail->getPart() == SoCylinder::TOP) { printf("Top of cylinder was hit\n"); } }
SoDetail pickDetail = myPickedPoint.GetDetail(); if (pickDetail != null && pickDetail is SoCylinderDetail) { // Picked object is a cylinder SoCylinderDetail cylDetail = (SoCylinderDetail) pickDetail; // See if top of the cylinder was hit if (cylDetail.GetPart() == (int)SoCylinder.PartType.TOP) { Console.WriteLine("Top of cylinder was hit"); } }
SoDetail pickDetail = myPickedPoint.getDetail(); if (pickDetail != null && pickDetail instanceof SoCylinderDetail) { // Picked object is a cylinder SoCylinderDetail cylDetail = (SoCylinderDetail) pickDetail; // See if top of the cylinder was hit if (cylDetail.getPart() == SoCylinder.PartType.TOP.getValue()) { System.out.println("Top of cylinder was hit"); } }
The following fragment shows how you could find the closest vertex to the hit point of a face-based shape using an SoFaceDetail( C++ | Java | .NET ). An SoFaceDetail( C++ | Java | .NET ) contains an array of SoPointDetails. You can examine these details to find the coordinates of the point closest to the hit point by using the getCoordinateIndex() method on SoPointDetail( C++ | Java | .NET ). Finding the node that contains the coordinates is left to the application. (You can create a search action, apply it to the picked path, and ask for the last SoCoordinate3( C++ | Java | .NET ) node in the path. But you also need to know something about the structure of your graph—for example, whether it contains Override flags or Ignore flags that may affect the search.)
// This function finds the closest vertex to an intersection // point on a shape made of faces, passed in the // "pickedPoint" argument. It returns the SoCoordinate3 node // containing the vertex's coordinates in the "coordNode" // argument and the index of the vertex in that node in the // "closestIndex" argument. If the shape is not made of faces // or there were any other problems, this returns FALSE. static SbBool findClosestVertex(const SoPickedPoint *pickedPoint, SoCoordinate3 *&coordNode, int &closestIndex) { const SoDetail *pickDetail = pickedPoint->getDetail(); if (pickDetail != NULL && pickDetail->getTypeId() == SoFaceDetail::getClassTypeId()) { // Picked object is made of faces SoFaceDetail *faceDetail = (SoFaceDetail *) pickDetail; // Find the coordinate node that is used for the faces. // Assume that it's the last SoCoordinate3 node traversed // before the picked shape. SoSearchAction mySearchAction; mySearchAction.setType(SoCoordinate3::getClassTypeId()); mySearchAction.setInterest(SoSearchAction::LAST); mySearchAction.apply(pickedPoint->getPath()); if (mySearchAction.getPath() != NULL) { // We found one coordNode = (SoCoordinate3 *) mySearchAction.getPath()->getTail(); // Get the intersection point in the object space // of the picked shape SbVec3f objIntersect = pickedPoint->getObjectPoint(); // See which of the points of the face is the closest // to the intersection point float minDistance = 1e12; closestIndex = -1; for (int i = 0; i < faceDetail->getNumPoints(); i++) { int pointIndex = faceDetail->getPoint(i)->getCoordinateIndex(); float curDistance = (coordNode->point[pointIndex] - objIntersect).length(); if (curDistance < minDistance) { closestIndex = pointIndex; minDistance = curDistance; } } if (closestIndex >= 0) return TRUE; } } return FALSE; }
static bool findClosestVertex(SoPickedPoint pickedPoint, SoCoordinate3 coordNode, int closestIndex) { SoDetail pickDetail = pickedPoint.GetDetail(); if (pickDetail != null && pickDetail.GetType() == typeof(SoFaceDetail)) { // Picked object is made of faces SoFaceDetail faceDetail = (SoFaceDetail) pickDetail; // Find the coordinate node that is used for the faces. // Assume that it's the last SoCoordinate3 node traversed // before the picked shape. SoSearchAction mySearchAction = new SoSearchAction(); mySearchAction.SetType(typeof(SoCoordinate3)); mySearchAction.SetInterest(SoSearchAction.Interests.LAST); mySearchAction.Apply(pickedPoint.GetPath()); if (mySearchAction.GetPath() != null) // We found one { coordNode = (SoCoordinate3) mySearchAction.GetPath().GetTail(); // Get the intersection point in the object space // of the picked shape SbVec3f objIntersect = pickedPoint.GetObjectPoint(); // See which of the points of the face is the closest // to the intersection point float minDistance = 1e12f; closestIndex = -1; for (int i = 0; i < faceDetail.GetNumPoints(); i++) { int pointIndex = faceDetail.GetPoint(i).GetCoordinateIndex(); float curDistance = (coordNode.point[pointIndex] - objIntersect).Length(); if (curDistance < minDistance) { closestIndex = pointIndex; minDistance = curDistance; } } if (closestIndex >= 0) return true; } } return false; }
static boolean findClosestVertex(SoPickedPoint pickedPoint, SoCoordinate3 coordNode, int closestIndex) { SoDetail pickDetail = pickedPoint.getDetail(); if (pickDetail != null && pickDetail instanceof SoFaceDetail) { // Picked object is made of faces SoFaceDetail faceDetail = (SoFaceDetail) pickDetail; // Find the coordinate node that is used for the faces. // Assume that it's the last SoCoordinate3 node traversed // before the picked shape. SoSearchAction mySearchAction = new SoSearchAction(); mySearchAction.setNodeClass(SoCoordinate3.class); mySearchAction.setInterest(SoSearchAction.Interests.LAST); mySearchAction.apply(pickedPoint.getPath()); if (mySearchAction.getPath() != null) // We found one { coordNode = (SoCoordinate3) mySearchAction.getPath().regular.getTail(); // Get the intersection point in the object space // of the picked shape SbVec3f objIntersect = pickedPoint.getObjectPoint(); // See which of the points of the face is the closest // to the intersection point float minDistance = 1e12f; closestIndex = -1; for (int i = 0; i < faceDetail.getNumPoints(); i++) { int pointIndex = faceDetail.getPoint(i).getCoordinateIndex(); float curDistance = (coordNode.point.getValueAt(pointIndex).minus( objIntersect)).length(); if (curDistance < minDistance) { closestIndex = pointIndex; minDistance = curDistance; } } if (closestIndex >= 0) return true; } } return false; }
Example 8.2, “ Writing the Path to the Picked Object ” shows setting up the pick action and writing the path to the picked object to stdout.
Example 8.2. Writing the Path to the Picked Object
SbBool
writePickedPath (SoNode *root,
const SbViewportRegion &viewport,
const SbVec2s &cursorPosition)
{
SoRayPickAction myPickAction(viewport);
// Set an 8-pixel wide region around the pixel
myPickAction.setPoint(cursorPosition);
myPickAction.setRadius(8.0);
// Start a pick traversal
myPickAction.apply(root);
const SoPickedPoint *myPickedPoint =
myPickAction.getPickedPoint();
if (myPickedPoint == NULL)
return FALSE; // no object was picked
// Write out the path to the picked object
SoWriteAction myWriteAction;
myWriteAction.apply(myPickedPoint->getPath());
return TRUE;
}
bool writePickedPath(SoNode root,
SbViewportRegion viewport,
SbVec2s cursorPosition)
{
SoRayPickAction myPickAction = new SoRayPickAction(viewport);
// Set an 8-pixel wide region around the pixel
myPickAction.SetPoint(cursorPosition);
myPickAction.SetRadius(8.0f);
// Start a pick traversal
myPickAction.Apply(root);
SoPickedPoint myPickedPoint = myPickAction.GetPickedPoint();
if (myPickedPoint == null) return false;
// Write out the path to the picked object
SoWriteAction myWriteAction = new SoWriteAction();
myWriteAction.Apply(myPickedPoint.GetPath());
return true;
}
boolean writePickedPath(SoNode root,
SbViewportRegion viewport,
SbVec2s cursorPosition)
{
SoRayPickAction myPickAction = new SoRayPickAction(viewport);
// Set an 8-pixel wide region around the pixel
myPickAction.setPoint(cursorPosition);
myPickAction.setRadius(8.0f);
// Start a pick traversal
myPickAction.apply(root);
SoPickedPoint myPickedPoint = myPickAction.getPickedPoint();
if (myPickedPoint == null) return false;
// Write out the path to the picked object
SoWriteAction myWriteAction = new SoWriteAction();
myWriteAction.apply(myPickedPoint.getPath());
return true;
}