2.7. Creating a Group Node

This example illustrates how to create a group node subclass. (It is unlikely, however, that you'll need to create a new group class.) Our example class, Alternate , traverses every other child (that is, child 0, then child 2, and so on). Since, like the base SoGroup SoGroup SoGroup class, it has no fields, this example also illustrates how to create a node with no fields.

If you do create a new group class, you will probably need to define a new traversal behavior for it. You may be able to inherit some of the traversal behavior from the parent class. Most groups define a protected traverseChildren() method that implements their “typical” traversal behavior. Your new group can probably inherit the read() and write() methods from SoGroup SoGroup SoGroup .

Child List

SoGroup SoGroup SoGroup , and all classes derived from it, store their children in an instance of SoChildList. This extender class provides useful methods for group classes, including the traverse() method, which has three forms:

traverse(action)

traverses all children in the child list

traverse(action, firstChild, lastChild)

traverses the children from first child to last child, inclusive

traverse(action, childIndex)

traverses one child with the specified index

Hidden Children

If you want your new node to have children, but you don't want to grant public access to the child list, you can implement the node to have hidden children. Node kits are an example of groups within the Inventor library that have hidden children. Because node kits have a specific internal structure, access to the children needs to be restricted. If you want the node to have hidden children, it should not be derived from SoGroup SoGroup SoGroup , which has public children only.

SoNode SoNode SoNode provides a virtual getChildren() method that returns NULL by default. To implement a new node with hidden children, you need to do the following:

  1. Maintain an SoChildList for the node. This list can be a hierarchy of nodes.

  2. Implement a getChildren() method that returns a pointer to the child list. (SoPath uses getChildren() to maintain paths.)

Using the Path Code

Recall that an action can be applied to a node, a single path, or a path list. Before a group can traverse its children, it needs to know what the action has been applied to. The getPathCode() method of SoAction SoAction SoAction returns an enumerated value that indicates whether the action is being applied to a path and, if so, where this group node is in relation to the path or paths. The values returned by getPathCode() are as follows:

NO_PATH

the action is not being applied to a path (that is, the action is applied to a node)

BELOW_PATH

this node is at or below the last node in the path chain

OFF_PATH

this node is not on the path chain (the node is to the left of the path; it needs to be traversed if it affects the nodes in the path)

IN_PATH

the node is in the chain of the path (but is not the last node)

Figure 2.1, “ Using the Path Code for Groups shows five group nodes. Assume the render action is being applied to the path shown. Groups A and C are considered IN_PATH. Group B is OFF_PATH, and Groups D and E are BELOW_PATH.

Using the Path Code for Groups

Figure 2.1.  Using the Path Code for Groups



For SoGroup SoGroup SoGroup , if the group's path code is NO_PATH, BELOW_PATH, or OFF_PATH, it traverses all of its children. (Even if a node is OFF_PATH, you need to traverse it because it affects the nodes in the path to its right. Note, though, that if an SoSeparator SoSeparator SoSeparator is OFF_PATH, you do not need to traverse it because it will not have any effect on the path.) If a node is IN_PATH, you may not need to traverse all children in the group, since children to the right of the action path do not affect the nodes in the path. In this case, getPathCode() returns the indices of the children that need to be traversed. The traverseChildren() method for SoGroup SoGroup SoGroup looks like this:

void
SoGroup::traverseChildren(SoAction *action)
{
   int           numIndices;
   const   int   *indices;

   if (action->getPathCode(numIndices, indices) 
       == SoAction::IN_PATH) 
       children.traverse(action, 0, indices[numIndices - 1]);
           // traverse all children up to and including the last
           // child to traverse

   else
      children.traverse(action); // traverse all children
}

The GL render, callback, handle event, pick, and search methods for SoGroup SoGroup SoGroup all use traverseChildren(). The write method for SoGroup SoGroup SoGroup , which can be inherited by most subclasses, tests each node in the group before writing it out. The get matrix method does not use traverseChildren() because it doesn't need to traverse as much. If the path code for a group is NO_PATH or BELOW_PATH, it does not traverse the children. Here is the code for SoGroup::getMatrix():

void
SoGroup::getMatrix(SoGetMatrixAction *action)
{
   int         numIndices;
   const int   *indices;

   switch (action->getPathCode(numIndices, indices)) {
      case SoAction::NO_PATH:
      case SoAction::BELOW_PATH:
         break;
      case SoAction::IN_PATH:
         children.traverse(action, 0, indices[numIndices - 1]);
         break;
      case SoAction::OFF_PATH:
         children.traverse(action);
         break;
      }
}

If a node is IN_PATH, the getMatrix() method traverses all the children in the group up to and including the last child in the action path (but not the children to the right of the path). If a node is OFF_PATH, the getMatrix() method traverses all the children in the group, since they can affect what is in the path.

What Happens If an Action Is Terminated?

Some actions, such as the GL render, handle event, and search actions, can terminate prematurely—for example, when the node to search for has been found. The SoAction SoAction SoAction class has a flag that indicates whether the action has terminated. The SoChildList class checks this flag automatically, so this termination is built into the SoChildList::traverse() methods, and the group traversal methods do not need to check the flag.

The new Alternate class can inherit the read and write methods from SoGroup SoGroup SoGroup . We just have to define the traversal behavior for the other actions. Most of the other actions can be handled by the traverseChildren() method.

Alternate Node

The class header for the Alternate node is shown in Example 2-5.

Example 2.5.  Alternate.h

#include <Inventor/nodes/SoGroup.h>
// SoGroup.h includes SoSubNode.h; no need to include it again.

class Alternate : public SoGroup {

   SO_NODE_HEADER(Alternate);

 public:
   // Initializes this class.
   static void   initClass();

   // Default constructor
   Alternate();

   // Constructor that takes approximate number of children as
   // a hint
   Alternate::Alternate(int numChildren);

 protected:
   // Generic traversal of children for any action.
   virtual void  doAction(SoAction *action);

   // These implement supported actions.
   virtual void  getBoundingBox(SoGetBoundingBoxAction *action);
   virtual void  GLRender(SoGLRenderAction *action);
   virtual void  handleEvent(SoHandleEventAction *action);
   virtual void  pick(SoPickAction *action);
   virtual void  getMatrix(SoGetMatrixAction *action);
   virtual void  search(SoSearchAction *action);

 private:
   // Destructor
   virtual ~Alternate();
};


The Alternate class source code is shown in Example 2-6.

Example 2.6.  Alternate.c++

#include <Inventor/misc/SoChildList.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/actions/SoGetBoundingBoxAction.h>
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/actions/SoHandleEventAction.h>
#include <Inventor/actions/SoPickAction.h>
#include <Inventor/actions/SoSearchAction.h>
#include "Alternate.h"

SO_NODE_SOURCE(Alternate);

// This initializes the Alternate class.

void
Alternate::initClass()
{
   // Initialize type id variables
   SO_NODE_INIT_CLASS(Alternate, SoGroup, "Group"); 
}

// Constructor
Alternate::Alternate()
{
   SO_NODE_CONSTRUCTOR(Alternate);
}
// Constructor that takes approximate number of children.

Alternate::Alternate(int numChildren) : SoGroup(numChildren)
{
   SO_NODE_CONSTRUCTOR(Alternate);
}

// Destructor

Alternate::~Alternate()
{
}

// Each of these implements an action by calling the standard
// traversal method. Note that (as in the Glow node source) we 
// prefix the call to doAction() with the name of the class, to 
// avoid problems with derived classes.

void
Alternate::getBoundingBox(SoGetBoundingBoxAction *action)
{
   Alternate::doAction(action);
}

void
Alternate::GLRender(SoGLRenderAction *action)
{
   Alternate::doAction(action);
}

void
Alternate::handleEvent(SoHandleEventAction *action)
{
   Alternate::doAction(action);
}

void
Alternate::pick(SoPickAction *action)
{
   Alternate::doAction(action);
}

// This implements the traversal for the SoGetMatrixAction,
// which is handled a little differently: it does not traverse
// below the root node or tail of the path it is applied to.
// Therefore, we need to compute the matrix only if this group
// is in the middle of the current path chain or is off the path
// chain (since the only way this could be true is if the group
// is under a group that affects the chain).

void
Alternate::getMatrix(SoGetMatrixAction *action)
{
   int         numIndices;
   const int   *indices;

   // Use SoAction::getPathCode() to determine where this group
   // is in relation to the path being applied to (if any).
   switch (action->getPathCode(numIndices, indices)) {

    case SoAction::NO_PATH:
    case SoAction::BELOW_PATH:
     // If there's no path, or we're off the end, do nothing.
     break;

    case SoAction::OFF_PATH:
    case SoAction::IN_PATH:
     // If we are in the path chain or we affect nodes in the
     // path chain, traverse the children.
     Alternate::doAction(action);
     break;
   }
}

// This implements the traversal for the SoSearchAction, which
// is also a little different. The search action is set to
// either traverse all nodes in the graph or just those that
// would be traversed during normal traversal. We need to check
// that flag before traversing our children.

void
Alternate::search(SoSearchAction *action)
{
   // If the action is searching everything, then traverse all
   // of our children as SoGroup would.
   if (action->isSearchingAll())
     SoGroup::search(action);

   else {
     // First, make sure this node is found if we are searching
     // for Alternate (or group) nodes.
     SoNode::search(action);

     // Traverse the children in our usual way.
     Alternate::doAction(action);
   }
}

// This implements typical action traversal for an Alternate
// node, skipping every other child.

void
Alternate::doAction(SoAction *action)
{
   int         numIndices;
   const int   *indices;

   // This will be set to the index of the last (rightmost)
   // child to traverse.
   int         lastChildIndex;

   // If this node is in a path, see which of our children are
   // in paths, and traverse up to and including the rightmost
   // of these nodes (the last one in the "indices" array).
   if (action->getPathCode(numIndices, indices) ==
      SoAction::IN_PATH)
     lastChildIndex = indices[numIndices - 1];

   // Otherwise, consider all of the children.
   else
     lastChildIndex = getNumChildren() - 1;

   // Now we are ready to traverse the children, skipping every
   // other one. For the SoGetBoundingBoxAction, however, we
   // have to do some extra work in between each pair of
   // children - we have to make sure the center points get
   // averaged correctly.
   if (action->isOfType(
         SoGetBoundingBoxAction::getClassTypeId())) {
     SoGetBoundingBoxAction *bba =
       (SoGetBoundingBoxAction *) action;
     SbVec3f  totalCenter(0.0, 0.0, 0.0);
     int      numCenters = 0;

     for (int i = 0; i <= lastChildIndex; i += 2) {
       children->traverse(bba, i);

       // If the traversal set a center point in the action,
       // add it to the total and reset for the next child.
       if (bba->isCenterSet()) {
         totalCenter += bba->getCenter();
         numCenters++;
         bba->resetCenter();
       }
     }
     // Now, set the center to be the average. Since the
     // centers were already transformed, there's no need to
     // transform the average.
     if (numCenters != 0)
       bba->setCenter(totalCenter / numCenters, FALSE);
   }

   // For all other actions, just traverse every other child.
   else
     for (int i = 0; i <= lastChildIndex; i += 2)
       children->traverse(action, i);
}