Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

How to Derive a Traverser

Operations on an NVIDIA Scenegraph (NVSG) are typically performed by means of a Traverser. In this operation a traverser, starting at the scene's root node, iterates a scenegraph and performs a defined operation for each object (nodes, etc.) in the scenegraph.

Using Traverser Base Classes

In its core, the NVSGSDK provides a set of different traversers for performing different tasks. When customing or inventing operations on an NVSG, the developer must derive from the appropriate traverser. With regard to the type of operations that a traverser can perform on an NVSG, we distinguish between read-only traversers and read-write traversers.

The uppermost abstract base class is Traverser . A parameter to it's constructor controls whether it's a read-only traverser or a read-write traverser. Some derived traversers like the RenderTraverser use it as a read-only traverser. Others like the AppTraverser use it as a read-write traverser. Traverser provides iterating functionality as well as per-object locking functionality, described as follows:

Iterating Functionality

The Traverser covers base iteration over a constant NVSG. Derived traversers can always fall back on this base functionality for iterating 'known' NVSG objects, or objects that are part of the original NVSGSDK. For a new NVSG object that was invented by a third party developer (see How to Derive an NVSG Object), iteration for that object must be implemented by a derived traverser.

Per-Object Locking Functionality

Per-object locking allows for multiple read-only traversers to run in parallel in a multithreaded environment. Unlike the base iterating functionality, per-object locking is available for known NVSG objects as well as new NVSG objects invented by a third party developer.

Deriving a Traverser for Known NVSG Objects

In many cases developers don't need to extend the set of available NVSG Objects but only need to add a new operation to perform on an NVSG using a derived traverser. The new traverser should inherit from an already existing Traverser that matches most of the requirements. If none exist, derive directly from Traverser and, depending on whether the new traverser should perform read-only or read-write operations on an NVSG, it's constructor should call the constructor of Traverser with the appropriate flag.

// Example of a custom read-only traverser

#include "nvtraverser/Traverser.h"

class MyTraverser : public Traverser
{
  public:
    MyTraverser() : Traverser( true ) {}    // use Traverser in read-only mode
  // ...
};

To add a new operation for a certain NVSG object, the developer needs to overload the matching handler routine for that object.

// MyTraverser.h 
// Example for adding a custom operation for a Group 

#include "nvtraverser/Traverser.h" // base class definition

// forward declare class Group to satisfy includees
namespace nvsg
{
  class Group;
}

class MyTraverser : public nvtraverser::Traverser
{
  // ...
protected:
  // handleGroup override
  NVSG_API virtual void handleGroup(const nvsg::Group * p);  
};

The base classes provide a handler routine for each known and concrete NVSG object. The base implementations of these handler routines do not operate on the particular object but instead iterate over that object in a correct manner. For iterating purposes, it is strongly recommended to always fall back on the base implementation for overloaded handler routines.

// MyTraverser.cpp implementation file
#include "MyTraverser.h"
#include "nvsg/Group.h" // definition of Group

using namespace nvtraverser;
using namespace nvsg;

void MyTraverser::handleGroup(const Group * p)
{
  // ... add custom functionality
  
  // iterate subsequent objects by calling the base implementation
  Traverser::handleGroup(p);
  
  // ... add some more custom functionality
}

Deriving a Traverser for Newly Invented NVSG Objects

One can think of situations were an extended functionality requires a newly invented NVSG object (see also How to Derive an NVSG Object). Deriving a custom traverser for traversing newly invented NVSG objects requires additional steps.

The new traverser must provide a new handler routine for the new object, and add it to the base class' handler routine table by means of the base class' member function addObjectHandler .

Example of a custom traverser that handles a newly invented NVSG object that inherits from Group:

// NewObject.h
// #include "nvsg/Group.h"

// A NewGroup 'is a' Group
class NewGroup : public nvsg::Group
{
  // ...
}; 

// MyTraverser.h file
#include "nvtraverser/Traverser.h" // base class definition

// forward declare class NewGroup to satisfy includees
class NewGroup;
// forward declare class CullData to satisfy includees
namespace nvsg
{
  class CullData;
}

class MyTraverser : public nvtraverser::Traverser
{
  // ...
  protected:
    // constructor to deal with initial work
    MyTraverser();
    // clean up
    virtual ~MyTraverser(); 
    
    // overloadable handler for NewGroup
    NVSG_API virtual void handleNewGroup(const NewGroup * p);
  
  private:
    // implementation details that handle iterating over a NewObject 
    void a_private_function_that_handles_iteration(const NewGroup * p);  
    void a_private_function_that_handles_conditional_iteration(const NewGroup * p, const nvsg::CullData * p);  
};

// - - - - - - - - - - - - - - - - - - - - - - - -
// MyTraverser.cpp file
#include "MyTraverser.h"
#include "NewObject.h" // NewObject definition

MyTraverser::MyTraverser()
{
  // ...
  // add the new handler routine to the base classes handler table
  addObjectHandler( 
    OC_NEWGROUP   // object code for the new object NewGroup
                  // this must be provided by the author of NewGroup
  , &MyTraverser::handleNewGroup // handler routine  
  );    
  // ...            
}

A handler routine for a newly invented NVSG object must correctly iterate over the new object in order to ensure that the new object is traversed in a correct manner as part of the NVSG. If, for example, the new NVSG object inherits from Group , the handler routine must take care of iterating through the child nodes. Or, for a concrete Node , the actual cull tree also needs to be correctly maintained while iterating over the new object.

// MyTraverser.cpp

// ...

void MyTraverser::handleNewGroup(const NewGroup * p)
{
  // at entry, call overloadable preTraverseGroup  
  preTraverseGroup(p);

  // get the topmost cull tree
  const CullData  * cd = m_cullStack.top();

  if ( ! cd )
  {
    //  if there's no cull data, just traverse
    
    // ... traverse children
    a_private_function_that_handles_iteration(p); 
  }
  else if ( cd->getCullCode() == CC_IN )
  {
    //  this group is trivial in, so just push a NULL pointer on cull stack and traverse
    m_cullStack.push( NULL );
    // ... traverse children
    a_private_function_that_handles_iteration(p); 
    m_cullStack.pop();
  }
  else
  {
    //  this group is at least partly visible, so traverse through all children that are not out
    a_private_function_that_handles_conditional_iteration(p, cd); 
  }
  
  // call overloadable postTraverseGroup right before leaving
  postTraverseGroup(p);
}

If, as in the previous example, the newly invented NVSG object inherits from Group, the corresponding handler routine must iterate or conditionally iterate over the Group's child nodes. In order to keep the child nodes locked while being traversed, it is strongly recommended to call the base class' member function traverseObject for each child node that should be traversed, instead of just calling the corresponding handler routine for the particular child node.

// MyTraverser.cpp

// bad implementation:
void MyTraverser::a_private_function_that_handles_iteration(const NewGroup * p)
{
  // unconditionally traverse all child nodes
  for ( size_t i=0; i<p->getNumberOfChildren(); ++i )
  {
    // bad idea: identifying a child node's type this way doesn't perform well!
    switch ( p->getChild(i)->getObjectCode() )
    {
    case OC_GEONODE: // handle a GeoNode child
      handleGeoNode(p->getChild(i)); // bad idea: child GeoNode will not be locked while being traversed!
                                     // dangerous in multithreaded environments! 
      break;
    case ... // handle other typed children
    ...
    }
  }
}

// good implementation:
void MyTraverser::a_private_function_that_handles_iteration(const NewGroup * p)
{
  // unconditionally traverse all child nodes
  for ( size_t i=0; i<p->getNumberOfChildren(); ++i )
  {
    traverseObject(p->getChild(i)); // good idea for two reasons:
                                    // (1) fast lookup the corresponding handler routine
                                    // (2) the child node is locked while beeing traversed 
  }
}  

Back to


Generated on Tue Mar 1 13:20:36 2005 for NVSGSDK by NVIDIA