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( C++ | Java | .NET ) 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( C++ | Java | .NET ).
SoGroup( C++ | Java | .NET ), 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:
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( C++ | Java | .NET ), which has public children only.
SoNode( C++ | Java | .NET ) provides a virtual getChildren() method that returns NULL by default. To implement a new node with hidden children, you need to do the following:
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( C++ | Java | .NET ) 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.
For SoGroup( C++ | Java | .NET ), 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( C++ | Java | .NET ) 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( C++ | Java | .NET ) 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( C++ | Java | .NET ) all use traverseChildren(). The write method for SoGroup( C++ | Java | .NET ), 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.
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( C++ | Java | .NET ) 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( C++ | Java | .NET ). We just have to define the traversal behavior for the other actions. Most of the other actions can be handled by the traverseChildren() method.
The class header for the Alternate node is shown in Example 2-5.
Example 2.7. 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();
// Clean up type information.
static void exitClass();
// 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.8. 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"); } void Alternate::exitClass() { // Unregister type id variables SO__NODE_EXIT_CLASS(Alternate); } // 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); }