10.7. Selection

The SoSelection SoSelection SoSelection node provides several additional features that relate to the topic of user interaction. These features include managing the selection list (introduced in the section called “Selection Policy”), highlighting the selected objects, and the use of user-written callback functions that are invoked when the selection list changes. The following sections describe each of these features.

Managing the Selection List

The SoSelection SoSelection SoSelection node keeps a list of paths to objects that have been selected. This list is called the selection list. Typically, the user selects an object or objects and then performs an operation on the selected objects, such as copying them, deleting them, or setting their color.

Each path in the selection list begins with the selection node and ends with the selected object. Objects can be added to and removed from the selection list in one of two ways:

  • By the event-processing mechanism of the selection object itself. This mechanism is based on the current selection policy.

  • By methods on SoSelection SoSelection SoSelection that allow you to select, deselect, toggle, and clear objects from the selection list. You need to use these methods only if you want to manage the selection list directly.

The methods on SoSelection SoSelection SoSelection that are available for direct management of the selection list are as follows:

select(path)

adds a path to the selection list

deselect (path)

removes a path from the selection list

toggle(path)

toggles a path in the selection list (that is, adds the path if it is not already in the list, or removes the path if it is in the list)

deselectAll()

removes all paths from the selection list

isSelected ()

returns TRUE if the passed path is in the selection list

getNumSelected()

returns the length of the selection list

getList()

returns the selection list

getPath(index)

returns one item (path) in the selection list

For convenience, you can provide these methods with a node instead of a path. If the node is instanced multiple times in the scene graph, the path to the first instance of the node is used.

For example, suppose each of the objects in the scene graph has a name associated with it, such as a car part. The user selects the object by clicking on a name from a list displayed on the screen (“hubcap”). Your program then uses this name, finds the path to the selected object, and adds this path to the selection list. Example 10.3, “ Using a Motif-Style List to Select Objects shows using a Motif-style list to select objects in this manner. This example shows selecting and deselecting objects using a Motif-style list that contains names for four objects (cube, sphere, cone, cylinder).

Example 10.3.  Using a Motif-Style List to Select Objects

//  The scene graph has 4 objects which may be
//  selected by picking with the left mouse button
//  (use shift key to extend the selection to more
//  than one object).
// 
//  Hitting the up arrow key will increase the size of
//  each selected object; hitting down arrow will decrease
//  the size of each selected object.
//
//  This also demonstrates selecting objects from a Motif-style
//  list, and calling select/deselect functions on the
//  SoSelection node to change the selection. Use the Shift
//  key to extend the selection (i.e. pick more than one
//  item in the list.)
...
enum objects {
        CUBE,
        SPHERE,
        CONE,
        CYL,
        NUM_OBJECTS
};

static char *objectNames[] = {
        "Cube",
        "Sphere",
        "Cone",
        "Cylinder"
};
...
   cube->setName(objectNames[CUBE]);
   sphere->setName(objectNames[SPHERE]);
   cone->setName(objectNames[CONE]);
   cyl->setName(objectNames[CYL]);
...
   // Create a table of object names
   XmString *table = new XmString[NUM_OBJECTS];
   for (i=0; i<NUM_OBJECTS; i++) {
       table[i] = XmStringCreate(objectNames[i], 
                                 XmSTRING_DEFAULT_CHARSET);
   }

   // Create the list widget
   n = 0;
   XtSetArg(args[n], XmNitems, table);
   n++;
   XtSetArg(args[n], XmNitemCount, NUM_OBJECTS);
   n++;
   XtSetArg(args[n], XmNselectionPolicy, XmEXTENDED_SELECT);
   n++;

   motifList = XmCreateScrolledList(shell, "funcList", args, n);
   XtAddCallback(motifList, XmNextendedSelectionCallback,
      (XtCallbackProc) myListPickCB, (XtPointer) selection);
...
   // Clear the selection node, then loop through the list
   // and reselect
   selection->deselectAll();

   // Update the SoSelection based on what is selected in
   // the list.  We do this by extracting the string
   // from the selected XmString, and searching for the 
   // object of that name.
   for (int i = 0; i < listData->selected_item_count; i++) {
      mySearchAction.setName(
               SoXt::decodeString(listData->selected_items[i]));
      mySearchAction.apply(selection);
      selection->select(mySearchAction.getPath());
   }
  
            
          
//  The scene graph has 4 objects which may be
//  selected by picking with the left mouse button
//  (use shift key to extend the selection to more
//  than one object).
// 
//  Hitting the up arrow key will increase the size of
//  each selected object; hitting down arrow will decrease
//  the size of each selected object.
//
//  This also demonstrates selecting objects from a Motif-style
//  list, and calling select/deselect functions on the
//  SoSelection node to change the selection. Use the Shift
//  key to extend the selection (i.e. pick more than one
//  item in the list.)
  
final int        CUBE            = 0;
final int        SPHERE          = 1;
final int        CONE            = 2;
final int        CYL             = 3;
final int        NUM_OBJECTS     = 4;

String           objectNames[]   = { "Cube", "Sphere", "Cone", "Cylinder" };

cube.setName(objectNames[CUBE]);
sphere.setName(objectNames[SPHERE]);
cone.setName(objectNames[CONE]);
cyl.setName(objectNames[CYL]);

// Create the list
list = new List(NUM_OBJECTS, true);
for (int i = 0; i < NUM_OBJECTS; i++)
  objectList.add(objectNames[i]);

objectList.addItemListener(new ListPick(selectionRoot));

// Clear the selection node, then loop through the list and reselect
selection.deselectAll();

// Update the SoSelection based on what is selected in the list.
String selected[] = list.getSelectedItems();
for (int i = 0; i < selected.length; i++)
{
  mySearchAction.setName(selected[i]);
  mySearchAction.apply(selection);
  selection.select(mySearchAction.getPath());
}


Another example of how the selection list might be used is that the user selects several objects and wants to make all of them twice their original size. Here, you would call getList() or getPath() for each of the selected objects. Then you would find the appropriate SoTransform SoTransform SoTransform node in the path for each object and modify its scaleFactor field. Example 10-4 is an example of using the selection list in this way.

Example 10.4.  Using the Selection List

// Scale each object in the selection list
void
myScaleSelection(SoSelection *selection, float sf)
{
   SoPath *selectedPath;
   SoTransform *xform;
   SbVec3f scaleFactor;
   int i,j;

   // Scale each object in the selection list

   for (i = 0; i < selection->getNumSelected(); i++) {
      selectedPath = selection->getPath(i);
      xform = NULL;

      // Look for the shape node, starting from the tail of the 
      // path.  Once we know the type of shape, we know which 
      // transform to modify
      for (j=0; j < selectedPath->getLength() && 
                (xform == NULL); j++) {
         SoNode *n = (SoNode *)selectedPath->getNodeFromTail(j);
         if (n->isOfType(SoCube::getClassTypeId())) {
            xform = cubeTransform;
         } else if (n->isOfType(SoCone::getClassTypeId())) {
            xform = coneTransform;
         } else if (n->isOfType(SoSphere::getClassTypeId())) {
            xform = sphereTransform;
         } else if (n->isOfType(SoCylinder::getClassTypeId())) {
            xform = cylTransform;
         }
      }
      // Apply the scale
      scaleFactor = xform->scaleFactor.getValue();
      scaleFactor *= sf;
      xform->scaleFactor.setValue(scaleFactor);
   }
}
  
// Scale each object in the selection list
void myScaleSelection(SoSelection selection, float sf)
{
    SoPath selectedPath;
    SoTransform xform
    int i, j;

    // Scale each object in the selection list
    for (i = 0; i < selection.GetNumSelected(); i++)
    {
        selectedPath = selection.GetPath(i);
        xform = null;

        // Look for the shape node, starting from the tail of the
        // path.  Once we know the type of shape, we know which
        // transform to modify
        for (j = 0; j < selectedPath.GetLength() && (xform == null); j++)
        {
            SoNode n = (SoNode)selectedPath.GetNodeFromTail(j);
            if (n is SoCube)
            {
                xform = cubeTransform;
            }
            else if (n is SoCone)
            {
                xform = coneTransform;
            }
            else if (n is SoSphere)
            {
                xform = sphereTransform;
            }
            else if (n is SoCylinder)
            {
                xform = cylTransform;
            }
        }

        // Apply the scale
        xform.scaleFactor.Value *= sf;
    }
}
  
// Scale each object in the selection list
void myScaleSelection(SoSelection selection, float sf)
{
  SoPath selectedPath;
  SoTransform xform;
  SbVec3f scaleFactor = new SbVec3f();
  int i, j;

  // Scale each object in the selection list
  for (i = 0; i < selection.getNumSelected(); i++)
  {
    selectedPath = selection.getPath(i);
    xform = null;
    // Look for the shape node, starting from the tail of the
    // path. Once we know the type of shape, we know which
    // transform to modify
    for (j = 0; (j < selectedPath.regular.getLength()) && (xform == null); j++)
    {
      SoNode n = (SoNode) selectedPath.regular.getNodeFromTail(j);
      if (n instanceof SoCube)
        xform = cubeTransform;
      else if (n instanceof SoCone)
        xform = coneTransform;
      else if (n instanceof SoSphere)
        xform = sphereTransform;
      else if (n instanceof SoCylinder)
        xform = cylTransform;
    }

    // Apply the scale
    scaleFactor = xform.scaleFactor.getValue();
    scaleFactor.multiply(sf);
    xform.scaleFactor.setValue(scaleFactor);
  }
}
  


Highlighting Selected Objects

Usually, when objects are selected, they are highlighted or treated in some other special way to distinguish them from unselected objects. With the SoXtRenderArea , Inventor provides two highlight styles. You can choose to have highlighted objects drawn in wireframe with a particular color, line pattern, and line width, or you can have selected objects drawn with a wireframe bounding box surrounding each object. The type of highlight can be set on SoXtRenderArea . The default highlight style is no highlight.

Figure 10.6, “ Highlight Classes ” shows the class tree for the highlighting classes. Because highlighting objects are simply another way to render the scene, Inventor highlights are derived from the SoGLRenderAction SoGLRenderAction SoGLRenderAction . To create your own custom highlights, see The Inventor Toolmaker, Chapter 11.

Highlight Classes

Figure 10.6.  Highlight Classes



To specify which highlight to use, pass a highlight to the setGLRenderAction() method on SoXtRenderArea . The action will render highlights for selected objects in the scene. Note that whenever you create a new highlight and pass it to the render area, you are responsible for deleting the highlight after the render area is destroyed. The render area will not delete it for you.

Tip: The redraw sensor employed by the render area does not trigger a redraw when the selection changes. Use the redrawOnSelectionChanges() convenience method on SoXtRenderArea to ensure that a redraw occurs when the selection changes. Pass the selection node that should be monitored for changes.

How Highlighting Occurs

First, the highlight render action renders the scene graph. Then it renders the path for each object in the selection list. For SoLineHighlightRender- Action, the selected objects are drawn in wireframe, with the specified color, line pattern, and line width. The following methods are available for SoLineHighlightRenderAction SoLineHighlightRenderAction SoLineHighlightRenderAction :

setColor (color)

specifies the highlight color

setLinePattern (pattern)

specifies the line pattern of the highlight

setLineWidth (width)

specifies the line width of the highlight

For SoBoxHighlightRenderAction SoBoxHighlightRenderAction SoBoxHighlightRenderAction , the selected objects are drawn with a wireframe box surrounding them, using the specified color, line pattern, and line width. Methods for SoBoxHighlightRenderAction SoBoxHighlightRenderAction SoBoxHighlightRenderAction are the same as for SoLineHighlightRenderAction SoLineHighlightRenderAction SoLineHighlightRenderAction .

Custom Highlighting

If you want highlight styles other than the line and box highlight styles provided by Inventor, you can do either of the following:

  • Create a new subclass from SoGLRenderAction SoGLRenderAction SoGLRenderAction and pass it to renderArea->setGLRenderAction()

  • Specify NULL for addSelectionHighlight() and then use selection callback functions, described in the following section, to add geometry, draw-style, and other required nodes to the scene graph for highlighting selected objects. A common highlighting technique is to use selection callbacks to add a manipulator to selected objects.

See The Inventor Toolmaker, Chapter 10, for a detailed explanation of creating your own highlight.

Callback Functions for Selection Changes

The SoSelection SoSelection SoSelection class has several types of callback functions associated with it: selection callbacks, deselection callbacks, a pick filter callback, start callbacks, and finish callbacks. For example, you might write a callback function that puts a trackball around an object every time it is selected. This function would be a selection callback function. You would probably write a second callback function to remove the trackball when the object is deselected. This function would be a deselection callback function. The pick filter callback function is invoked whenever an object is picked and is about to be selected or deselected. This function allows you to truncate the selection path at a certain object type, such as a node kit (see Example 10.8, “ Selecting Node Kits).

A start callback function is called whenever the selection is about to change, and a finish callback function is called when the selection is finished changing. These functions are useful for implementing undo and redo features. When the selection is about to change, you can save the current selection in an undo buffer. To undo a change, you restore this saved information.

Another example of a user callback function for selected objects would be a function that checks to see if the material editor is on the screen when an object is selected. If it is, then the function finds the material node affecting the selected object and attaches the material editor to that node.

The following methods allow you to specify what happens when an object is selected and deselected:

addSelectionCallback(functionName, userData)

removeSelectionCallback(functionName, userData) are invoked whenever an object is selected.

addDeselectionCallback(functionName, userData) removeDeselectionCallback(functionName, userData)

are invoked whenever an object is deselected.

setPickFilterCallback(functionName, userData)

is invoked whenever an object is picked and is about to be selected or deselected. This function allows you to truncate the selection path at a certain object type.

addStartCallback(functionName, userData)

removeStartCallback(functionName, userData) are invoked whenever the selection list is about to change.

addFinishCallback(functionName, userData)

removeFinishCallback(functionName, userData) are invoked when the selection list is finished changing.

These methods allow you to pass in a callback function and a pointer to user data. If you specify NULL for the pick filter callback function, whatever is picked will be selected and deselected.

Example 10.5, “ Using Selection Callback Functions illustrates the use of selection callback functions. The scene graph in this example has a sphere and a 3D text object. A selection node is placed at the top of the scene graph. When an object is selected, a selection callback is invoked to change the material color of that object.

Example 10.5.  Using Selection Callback Functions

#include <X11/Intrinsic.h>
#include <Inventor/Sb.h>
#include <Inventor/SoInput.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoPickStyle.h>
#include <Inventor/nodes/SoSelection.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoText3.h>
#include <Inventor/nodes/SoTransform.h>

// global data
SoMaterial *textMaterial, *sphereMaterial;
static float reddish[] = {1.0, 0.2, 0.2};
static float white[] = {0.8, 0.8, 0.8};

// This routine is called when an object gets selected. 
// We determine which object was selected, and change 
// that object's material color.
void
mySelectionCB(void *, SoPath *selectionPath)
{
   if (selectionPath->getTail()->
            isOfType(SoText3::getClassTypeId())) { 
      textMaterial->diffuseColor.setValue(reddish);
   } else if (selectionPath->getTail()->
            isOfType(SoSphere::getClassTypeId())) {
      sphereMaterial->diffuseColor.setValue(reddish);
   }
}

// This routine is called whenever an object gets deselected. 
// We determine which object was deselected, and reset 
// that object's material color.
void
myDeselectionCB(void *, SoPath *deselectionPath)
{
   if (deselectionPath->getTail()->
            isOfType(SoText3::getClassTypeId())) {
      textMaterial->diffuseColor.setValue(white);
   } else if (deselectionPath->getTail()->
            isOfType(SoSphere::getClassTypeId())) {
      sphereMaterial->diffuseColor.setValue(white);
   }
}

void
main(int argc, char **argv)
{
   // Initialize Inventor and Xt
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   // Create and set up the selection node
   SoSelection *selectionRoot = new SoSelection;
   selectionRoot->ref();
   selectionRoot->policy = SoSelection::SINGLE;
   selectionRoot-> addSelectionCallback(mySelectionCB);
   selectionRoot-> addDeselectionCallback(myDeselectionCB);

   // Create the scene graph
   SoSeparator *root = new SoSeparator;
   selectionRoot->addChild(root);

   SoPerspectiveCamera *myCamera = new SoPerspectiveCamera;
   root->addChild(myCamera);
   root->addChild(new SoDirectionalLight);

   // Add a sphere node
   SoSeparator *sphereRoot = new SoSeparator;
   SoTransform *sphereTransform = new SoTransform;
   sphereTransform->translation.setValue(17., 17., 0.);
   sphereTransform->scaleFactor.setValue(8., 8., 8.);
   sphereRoot->addChild(sphereTransform);

   sphereMaterial = new SoMaterial;
   sphereMaterial->diffuseColor.setValue(.8, .8, .8);
   sphereRoot->addChild(sphereMaterial);
   sphereRoot->addChild(new SoSphere);
   root->addChild(sphereRoot);

   // Add a text node
   SoSeparator *textRoot = new SoSeparator;
   SoTransform *textTransform = new SoTransform;
   textTransform->translation.setValue(0., -1., 0.);
   textRoot->addChild(textTransform);

   textMaterial = new SoMaterial;
   textMaterial->diffuseColor.setValue(.8, .8, .8);
   textRoot->addChild(textMaterial);
   SoPickStyle *textPickStyle = new SoPickStyle;
   textPickStyle->style.setValue(SoPickStyle::BOUNDING_BOX);
   textRoot->addChild(textPickStyle);
   SoText3 *myText = new SoText3;
   myText->string = "rhubarb";
   textRoot->addChild(myText);
   root->addChild(textRoot);

   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);
   myRenderArea->setSceneGraph(selectionRoot);
   myRenderArea->setTitle("My Selection Callback");
   myRenderArea->show();

   // Make the camera see the whole scene
   const SbViewportRegion myViewport = 
            myRenderArea->getViewportRegion();
   myCamera->viewAll(root, myViewport, 2.0);

   SoXt::show(myWindow);
   SoXt::mainLoop();
}
      
using OIV.Inventor.Nodes;
using OIV.Inventor.Win;
using OIV.Inventor;

namespace _10_5_SelectionCB
{
    public partial class MainForm : Form
    {
        SoWinRenderArea myRenderArea;

        public MainForm()
        {
            InitializeComponent();
            CreateSample();
        }

        // global data
        SoMaterial textMaterial, sphereMaterial;
        float[] reddish = new float[] { 1.0f, 0.2f, 0.2f };  // Color when selected
        float[] white = new float[] { 0.8f, 0.8f, 0.8f };    // Color when not selected

        // This routine is called when an object gets selected. 
        // We determine which object was selected, and change 
        // that objects material color.
        void mySelectionCB(SoPath path)
        {
            if (path.GetTail().GetType() == typeof(SoText3))
            {
                textMaterial.diffuseColor.SetValue(reddish);
            }
            else if (path.GetTail().GetType() == typeof(SoSphere))
            {
                sphereMaterial.diffuseColor.SetValue(reddish);
            }
        }

        // This routine is called whenever an object gets deselected. 
        // We determine which object was deselected, and reset 
        // that objects material color.
        void myDeselectionCB(SoPath path)
        {
            if (path.GetTail().GetType() == typeof(SoText3))
            {
                textMaterial.diffuseColor.SetValue(white);
            }
            else if (path.GetTail().GetType() == typeof(SoSphere))
            {
                sphereMaterial.diffuseColor.SetValue(white);
            }
        }

        public void CreateSample()
        {
            // Create and set up the selection node
            SoSelection selectionRoot = new SoSelection();
            selectionRoot.policy.SetValue((int)SoSelection.Policies.SINGLE);
            selectionRoot.Selection += new SoSelection.PathCB(mySelectionCB);
            selectionRoot.Deselection += new SoSelection.PathCB(myDeselectionCB);

            // Create the scene graph
            SoSeparator root = new SoSeparator();
            selectionRoot.AddChild(root);

            SoPerspectiveCamera myCamera = new SoPerspectiveCamera();
            root.AddChild(myCamera);
            root.AddChild(new SoDirectionalLight());

            // Add a sphere node
            SoSeparator sphereRoot = new SoSeparator();
            SoTransform sphereTransform = new SoTransform();
            sphereTransform.translation.SetValue(17.0f, 17.0f, 0.0f);
            sphereTransform.scaleFactor.SetValue(8.0f, 8.0f, 8.0f);
            sphereRoot.AddChild(sphereTransform);

            sphereMaterial = new SoMaterial();
            sphereMaterial.diffuseColor.SetValue(0.8f, 0.8f, 0.8f);
            sphereRoot.AddChild(sphereMaterial);
            sphereRoot.AddChild(new SoSphere());
            root.AddChild(sphereRoot);

            // Add a text node
            SoSeparator textRoot = new SoSeparator();
            SoTransform textTransform = new SoTransform();
            textTransform.translation.SetValue(0.0f, -1.0f, 0.0f);
            textRoot.AddChild(textTransform);

            textMaterial = new SoMaterial();
            textMaterial.diffuseColor.SetValue(0.8f, 0.8f, 0.8f);
            textRoot.AddChild(textMaterial);
            SoPickStyle textPickStyle = new SoPickStyle();
            textPickStyle.style.SetValue((int)SoPickStyle.Styles.BOUNDING_BOX);
            textRoot.AddChild(textPickStyle);
            SoText3 myText = new SoText3();
            myText.stringField.SetValue("rhubarb");
            textRoot.AddChild(myText);
            root.AddChild(textRoot);

            myRenderArea = new SoWinRenderArea(this, "", true, true, true);
            myRenderArea.SetSceneGraph(selectionRoot);
            myRenderArea.SetTitle("My Selection Callback");

            // Make the camera see the whole scene
            SbViewportRegion myViewport = myRenderArea.GetViewportRegion();
            myCamera.ViewAll(root, myViewport, 2.0f);
        }
    }
}
    
import tools.*;

import com.openinventor.inventor.nodes.*;
import com.openinventor.inventor.awt.*;
import com.openinventor.inventor.*;
import com.openinventor.inventor.misc.callbacks.*;

import java.awt.*;

public class Main extends DemoInventor
{

  // global data
  static final float REDDISH[] = { 1.0f, 0.2f, 0.2f }; // Color when selected
  static final float WHITE[]   = { 0.8f, 0.8f, 0.8f }; // Color when not selected

  SoMaterial textMaterial;
  SoMaterial sphereMaterial;

  public static void main(String[] args)
  {
    Main applet = new Main();
    DemoInventor.isAnApplet = false;
    applet.start();
    demoMain(applet, "My Selection Callback");
  }

  public void start()
  {
    super.start();

    // Create and set up the selection node
    SoSelection selectionRoot = new SoSelection();
    selectionRoot.policy.setValue(SoSelection.Policies.SINGLE);
    selectionRoot.addSelectionCallback(new MySelectionCB());
    selectionRoot.addDeselectionCallback(new MyDeselectionCB());

    // Create the scene graph
    SoSeparator root = new SoSeparator();

    SoPerspectiveCamera myCamera = new SoPerspectiveCamera();

    // Add a sphere node
    SoSeparator sphereRoot = new SoSeparator();
    SoTransform sphereTransform = new SoTransform();
    sphereTransform.translation.setValue(17.0f, 17.0f, 0.0f);
    sphereTransform.scaleFactor.setValue(8.0f, 8.0f, 8.0f);
    sphereMaterial = new SoMaterial();
    sphereMaterial.diffuseColor.setValue(0.8f, 0.8f, 0.8f);
    {
      sphereRoot.addChild(sphereTransform);
      sphereRoot.addChild(sphereMaterial);
      sphereRoot.addChild(new SoSphere());
    }

    // Add a text node
    SoSeparator textRoot = new SoSeparator();
    SoTransform textTransform = new SoTransform();
    textTransform.translation.setValue(0.0f, -1.0f, 0.0f);
    textMaterial = new SoMaterial();
    textMaterial.diffuseColor.setValue(0.8f, 0.8f, 0.8f);
    SoPickStyle textPickStyle = new SoPickStyle();
    textPickStyle.style.setValue(SoPickStyle.Styles.BOUNDING_BOX);
    SoText3 myText = new SoText3();
    myText.string.setValue("rhubarb");
    {
      textRoot.addChild(textTransform);
      textRoot.addChild(textMaterial);
      textRoot.addChild(textPickStyle);
      textRoot.addChild(myText);
    }

    { // Assemble scene graph
      root.addChild(myCamera);
      root.addChild(new SoDirectionalLight());
      root.addChild(sphereRoot);
      root.addChild(textRoot);
      selectionRoot.addChild(root);
    }

    SwRenderArea myRenderArea = new SwRenderArea();
    myRenderArea.setSceneGraph(selectionRoot);

    // Make the camera see the whole scene
    SbViewportRegion myViewport = myRenderArea.getViewportRegion();
    myCamera.viewAll(root, myViewport, 2.0f);

    setLayout(new BorderLayout());
    add(myRenderArea, BorderLayout.CENTER);
  }

  class MySelectionCB extends SoSelectionPathCB
  {
    public void invoke(SoPath p)
    {
      if (p.regular.getTail() instanceof SoText3)
      {
        textMaterial.diffuseColor.setValue(REDDISH);
      }
      else if (p.regular.getTail() instanceof SoSphere)
      {
        sphereMaterial.diffuseColor.setValue(REDDISH);
      }
    }
  }

  class MyDeselectionCB extends SoSelectionPathCB
  {
    public void invoke(SoPath p)
    {
      if (p.regular.getTail() instanceof SoText3)
      {
        textMaterial.diffuseColor.setValue(WHITE);
      }
      else if (p.regular.getTail() instanceof SoSphere)
      {
        sphereMaterial.diffuseColor.setValue(WHITE);
      }
    }
  }
}
    


Pick Filter Callback (Advanced)

The pick filter callback returns a path for the new object to be selected, deselected, or toggled:

typedef SoPath *SoSelectionPickCB(void *userData, SoDetail *d);
void  setPickFilterCallback(SoSelectionPickCB *f,
	void *userData = NULL,
	SbBool callOnlyIfSelectable = TRUE);
      
SoPath pickFilterCB(SoPickedPoint pick);
SetPickFilterCallback(new SoSelection.PickCB(pickFilterCB));
      
class PickFilterCB extends SoSelectionPickCB
{
  public SoPath invoke(SoPickedPoint pick) {}
}
void setPickFilterCallback(SoSelectionPickCB f,
                           Object userData,
                           boolean callOnlyIfSelectable);
      

This callback can look at the picked point to see what was picked and return a path to whatever the selection policy is to be applied to. It can truncate the picked path so that it ends in a particular type of node. If an unselectable object is picked, the pick filter callback determines how that information is used. When the callback is set, the application passes in a Boolean value that specifies whether the callback is called only if the object is selectable, or is called for all objects. The pick filter callback can then return one of the following:

  • NULL—the selection behaves as if nothing were picked (for SINGLE and SHIFT selection policies, this clears the selection list).

  • Path—this path will be selected or deselected according to the selection policy. It must pass through the selection node.

  • Path not passing through the selection node—the selection ignores this pick event and no change is made to the selection list.

  • Path containing only the selection node—applies the selection policy as though nothing were picked, but continues traversal.

Examples 10-6 through 10-8 illustrate sample pick filter callbacks the application could use.

Example 10.6, “ Creating a Top-Level Selection Policy shows the use of the pick filter callback to implement a top-level selection policy. Rather than selecting the actual node that was picked, it always selects the topmost group beneath the selection node. Figure 10.7, “ Top-Level Selection Policy (left) and Default Selection Policy (right) shows the two viewers created by this example.

Top-Level Selection Policy (left) and Default Selection Policy (right)

Figure 10.7.  Top-Level Selection Policy (left) and Default Selection Policy (right)



Example 10.6.  Creating a Top-Level Selection Policy

#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>

#include <Inventor/SoDB.h>
#include <Inventor/SoInput.h>
#include <Inventor/SoPath.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/actions/SoBoxHighlightRenderAction.h>
#include <Inventor/misc/SoPickedPoint.h>
#include <Inventor/nodes/SoSelection.h>

// Pick the topmost node beneath the selection node
SoPath *
pickFilterCB(void *, const SoPickedPoint *pick)
{    
   // See which child of selection got picked
   SoPath *p = pick->getPath();
   int i;
   for (i = 0; i < p->getLength() - 1; i++) {
      SoNode *n = p->getNode(i);
      if (n->isOfType(SoSelection::getClassTypeId()))
         break;
   }
   // Copy 2 nodes from the path:
   // selection and the picked child
   return p->copy(i, 2);
}


void
main(int argc, char *argv[])
{
   // Initialization
   Widget mainWindow = SoXt::init(argv[0]);
    
   // Open the data file
   SoInput in;   
   char *datafile = "parkbench.iv";
   if (! in.openFile(datafile)) {
      fprintf(stderr, "Cannot open %s for reading.\n",
              datafile);
      return;
   }

   // Read the input file
   SoNode *n;
   SoSeparator *sep = new SoSeparator;
   while ((SoDB::read(&in, n) != FALSE) && (n != NULL))
      sep->addChild(n);
   
   // Create two selection roots - one will use the pick filter.
   SoSelection *topLevelSel = new SoSelection;
   topLevelSel->addChild(sep);
   topLevelSel->setPickFilterCallback(pickFilterCB);

   SoSelection *defaultSel = new SoSelection;
   defaultSel->addChild(sep);

   // Create two viewers, one to show the pick filter for top
   // level selection, the other to show default selection.
   SoXtExaminerViewer *viewer1 = new
            SoXtExaminerViewer(mainWindow);
   viewer1->setSceneGraph(topLevelSel);
   viewer1->setGLRenderAction(new SoBoxHighlightRenderAction());
   viewer1->redrawOnSelectionChange(topLevelSel);
   viewer1->setTitle("Top Level Selection");

   SoXtExaminerViewer *viewer2 = new SoXtExaminerViewer();
   viewer2->setSceneGraph(defaultSel);
   viewer2->setGLRenderAction(new SoBoxHighlightRenderAction());
   viewer2->redrawOnSelectionChange(defaultSel);
   viewer2->setTitle("Default Selection");

   viewer1->show();
   viewer2->show();
   
   SoXt::show(mainWindow);
   SoXt::mainLoop();
}
                      
using OIV.Inventor.Nodes;
using OIV.Inventor.Win;
using OIV.Inventor.Win.Viewers;
using OIV.Inventor;
using OIV.Inventor.Actions;

namespace _10_6_PickFilterTopLevel
{
    public partial class MainForm : Form
    {
        static bool firstCreated = false;
        
        SoWinExaminerViewer viewer1;
        SoWinExaminerViewer viewer2;

        public MainForm()
        {
            InitializeComponent();
            if (!firstCreated)
            {
                firstCreated = true;
                CreateSample();
            }
        }

        // Pick the topmost node beneath the selection node
        SoPath pickFilterCB(SoPickedPoint pick)
        {
            // See which child of selection got picked
            SoPath p = pick.GetPath();
            int i;
            for (i = 0; i < p.GetLength() - 1; i++)
            {
                SoNode n = p.GetNode(i);
                if (n.GetType() == typeof(SoSelection))
                    break;
            }

            // Copy 2 nodes from the path:
            // selection and the picked child
            return p.Copy(i, 2);
        }

        public void CreateSample()
        {
            // Open the data file
            SoInput input = new SoInput();
            String datafile = "../../../../../data/parkbench.iv";
            if (!input.OpenFile(datafile))
            {
                Console.WriteLine("Cannot open for reading : " + datafile);
            }
            else
            {
                // Read the input file                
                SoSeparator sep = SoDB.ReadAll(datafile);

                // Create two selection roots - one will use the pick filter.
                SoSelection topLevelSel = new SoSelection();
                topLevelSel.AddChild(sep);
                topLevelSel.SetPickFilterCallback(new SoSelection.PickCB(pickFilterCB));

                SoSelection defaultSel = new SoSelection();
                defaultSel.AddChild(sep);

                // Create two viewers, one to show the pick filter for top level
                // selection, the other to show default selection.
                viewer1 = new SoWinExaminerViewer(this, "", true,
                    SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER);

                viewer1.SetSceneGraph(topLevelSel);
                viewer1.SetGLRenderAction(new SoBoxHighlightRenderAction());
                viewer1.RedrawOnSelectionChange(topLevelSel);
                viewer1.SetTitle("Top Level Selection");

                MainForm otherForm = new MainForm();
                viewer2 = new SoWinExaminerViewer(otherForm, "", true,
                    SoWinFullViewer.BuildFlags.BUILD_ALL, SoWinViewer.Types.BROWSER);
                viewer2.SetSceneGraph(defaultSel);
                viewer2.SetGLRenderAction(new SoBoxHighlightRenderAction());
                viewer2.RedrawOnSelectionChange(defaultSel);
                viewer2.SetTitle("Default Selection");

                otherForm.Show();
            }
        }
    }
}
                    
import tools.*;

import com.openinventor.inventor.awt.*;
import com.openinventor.inventor.*;
import com.openinventor.inventor.nodes.*;
import com.openinventor.inventor.actions.*;
import com.openinventor.inventor.misc.callbacks.*;

import javax.swing.*;
import java.awt.*;

public class Main extends DemoInventor
{

  public static void main(String[] args)
  {
    Main applet = new Main();
    DemoInventor.isAnApplet = false;
    applet.start();
    demoMain(applet, "Pick Filter Callback");
  }

  public void start()
  {
    super.start();

    // Open the data file
    SoInput in = new SoInput();
    String data_file = m_prefix + "../../../../data/models/parkbench.iv";
    if (!in.openFile(data_file))
    {
      System.err.println("Cannot open " + data_file + " for reading.");
      System.exit(-1);
    }

    // Read the input file
    SoNode n;
    SoSeparator sep = new SoSeparator();
    while ((n = SoDB.readNode(in)) != null)
      sep.addChild(n);

    // Create two selection roots - one will use the pick filter.
    SoSelection topLevelSel = new SoSelection();
    topLevelSel.addChild(sep);
    topLevelSel.setPickFilterCallback(new PickFilterCB());

    SoSelection defaultSel = new SoSelection();
    defaultSel.addChild(sep);

    // Create two viewers, one to show the pick filter for top level
    // selection, the other to show default selection.
    SwSimpleViewer viewer1 = new SwSimpleViewer();
    viewer1.setSceneGraph(topLevelSel);
    viewer1.getArea().setGLRenderAction(new SoBoxHighlightRenderAction());
    viewer1.getArea().redrawOnSelectionChange(topLevelSel);

    SwSimpleViewer viewer2 = new SwSimpleViewer();
    viewer2.setSceneGraph(defaultSel);
    viewer2.getArea().setGLRenderAction(new SoBoxHighlightRenderAction());
    viewer2.getArea().redrawOnSelectionChange(defaultSel);

    Font f = new Font(null, 1, 15);
    JPanel panel1 = new JPanel(new BorderLayout());
    JLabel label1 = new JLabel("Top Level Selection", SwingConstants.CENTER);
    label1.setFont(f);
    panel1.add(label1, BorderLayout.NORTH);
    panel1.add(viewer1, BorderLayout.CENTER);

    JPanel panel2 = new JPanel(new BorderLayout());
    JLabel label2 = new JLabel("Default Selection", SwingConstants.CENTER);
    label2.setFont(f);
    panel2.add(label2, BorderLayout.NORTH);
    panel2.add(viewer2, BorderLayout.CENTER);

    setLayout(new GridLayout(1, 2));
    add(panel1);
    add(panel2);
  }

  class PickFilterCB extends SoSelectionPickCB
  {
    public SoPath invoke(SoPickedPoint pick)
    {
      // See which child of selection got picked
      SoPath p = pick.getPath();
      int i;
      for (i = 0; i < p.regular.getLength() - 1; i++)
      {
        SoNode n = p.regular.getNode(i);
        if (n instanceof SoSelection)
          break;
      }

      // Copy 2 nodes from the path:
      // selection and the picked child
      return p.regular.copy(i, 2);
    }
  }
}
                    


Example 10.7, “ Picking through Manipulators shows the use of the pick filter callback to pick through manipulators. In the complete example, the scene graph contains the text “Open Inventor.” Clicking the left mouse on an object selects it and adds a manipulator to it. Clicking again deselects it and removes the manipulator. The pick filter is used to deselect the object rather than select the manipulator.

Example 10.7.  Picking through Manipulators

SoPath *
pickFilterCB(void *, const SoPickedPoint *pick)
{
   SoPath *filteredPath = NULL;
    
   // See if the picked object is a manipulator. 
   // If so, change the path so it points to the object the 
   // manip is attached to.
   SoPath *p = pick->getPath();
   SoNode *n = p->getTail();
   if (n->isOfType(SoTransformManip::getClassTypeId())) {
      // Manip picked! We know the manip is attached
      // to its next sibling. Set up and return that path.
      int manipIndex = p->getIndex(p->getLength() - 1);
      filteredPath = p->copy(0, p->getLength() - 1);
      filteredPath->append(manipIndex + 1); // get next sibling
   }
   else filteredPath = p;
    
   return filteredPath;
}
                      
SoPath pickFilterCB(SoPickedPoint point)
{
    SoPath filteredPath = null;

    // See if the picked object is a manipulator. 
    // If so, change the path so it points to the object the manip
    // is attached to.
    SoPath p = point.GetPath();
    SoNode n = p.GetTail();
    if (n.GetType() == typeof(SoTransformManip))
    {
        // Manip picked! We know the manip is attached
        // to its next sibling. Set up and return that path.
        int manipIndex = p.GetIndex(p.GetLength() - 1);
        filteredPath = p.Copy(0, p.GetLength() - 1);
        filteredPath.Append(manipIndex + 1); // get next sibling
    }
    else filteredPath = p;

    return filteredPath;
}
                    
class PickFilterCB extends SoSelectionPickCB
{
  public SoPath invoke(SoPickedPoint pick)
  {
    SoPath filteredPath = null;

    // See if the picked object is a manipulator. 
    // If so, change the path so it points to the object the manip
    // is attached to.
    SoPath p = pick.getPath();
    SoNode n = p.regular.getTail();
    if (n instanceof SoTransformManip)
    {
      // Manip picked! We know the manip is attached
      // to its next sibling. Set up and return that path.
      int manipIndex = p.regular.getIndex(p.regular.getLength() - 1);
      filteredPath = p.regular.copy(0, p.regular.getLength() - 1);
      filteredPath.regular.append(manipIndex + 1); // get next sibling
    }
    else filteredPath = p;

    return filteredPath;
  }
}
                    


Example 10.8, “ Selecting Node Kits illustrates using the pick filter callback to truncate the pick path at a node kit. This filter facilitates editing the attributes of objects because the node kit takes care of the part creation details.

Example 10.8.  Selecting Node Kits

// Truncate the pick path so a nodekit is selected
SoPath *
pickFilterCB(void *, const SoPickedPoint *pick)
{    
   // See which child of selection got picked
   SoPath *p = pick->getPath();
   int i;
   for (i = p->getLength() - 1; i >= 0; i--) {
      SoNode *n = p->getNode(i);
      if (n->isOfType(SoShapeKit::getClassTypeId()))
         break;
   }
   
   // Copy the path down to the nodekit
   return p->copy(0, i+1);
}
                      
// Truncate the pick path so a nodekit is selected
SoPath pickFilterCB(SoPickedPoint point)
{
  // See which child of selection got picked
  SoPath p = point.GetPath();
  int i;
  for (i = p.GetLength() - 1; i >= 0; i--)
  {
    SoNode n = p.GetNode(i);
    if (n.GetType() == typeof(SoShapeKit))
      break;
  }

  // Copy the path down to the nodekit
  return p.Copy(0, i + 1);
}
                    
// Truncate the pick path so a nodekit is selected
class PickFilterCB extends SoSelectionPickCB {
  public SoPath invoke(SoPickedPoint pick)
  {
    // See which child of selection got picked
    SoPath p = pick.getPath();
    int i;
    for (i = p.regular.getLength() - 1; i >= 0; i--)
    {
      SoNode n = p.regular.getNode(i);
      if (n instanceof SoShapeKit)
        break;
    }

    // Copy the path down to the nodekit
    return p.regular.copy(0, i + 1);
  }
}