SGI Techpubs Library

IRIX 6.5  »  Books  »  Developer  »  
OpenGL Optimizer Programmer's Guide: An Open API for Large-Model Visualization
(document number: 007-2852-002 / published: 1998-06-09)    table of contents  |  additional info  |  download
find in page

Chapter 12. Traversing a Large Scene Graph

This chapter and Chapter 13, “Manipulating Triangles and Rebuilding Renderable Objects,” discuss methods to efficiently manipulate (parts of) a scene graph with extensible traversers. The OpenGL Optimizer tools fall in two general categories:

  • Tools that essentially focus on the scene graph manipulation, which are discussed in this chapter

  • Tools that coordinate scene-graph tasks as well as other tasks in a multiprocessor environment, which are discussed in the next chapter

You define OpenGL Optimizer traversals with callbacks held in a traversal object. The control provided by the callbacks allows you to do the following:

  • Specify the effect when a traverser visits a node.

  • Control the progress of the traversal, that is, which node to visit next.

  • Delete the traversal object when you are through with it.

This chapter consists of the following sections:

Traversals and Callbacks: General Features

Traversing a scene graph means “visiting” nodes in some sequence and invoking a callback as each node is visited. Callbacks allow you to perform operations whenever a node is visited during a traversal; for example, you can count nodes, render objects, or compute the volume of objects in a scene.

OpenGL Optimizer provides tools for two scene-graph traversal sequences: depth first or breadth first.

Depth-First Traversal Sequence

To picture depth-first traversals, imagine the path you would take if the links between nodes in a scene graph were hallways and you walk through the scene graph holding your right hand on a wall. Nodes would be rooms, and you would continue to hold your hand on the wall as you walked through the room. Callbacks are made each time you enter a room, except when the hand-on-the-wall rule returns you to a parent node before visiting all its children: a callback is made when you first “descend” into the parent node and after you “ascend” from the last child.

Figure 12-1 shows a depth-first traversal of a simple scene graph. The solid circles in the figure indicate pre-node callbacks, which are implemented when a traversal first visits a node. The solid squares indicate post-node callbacks, which are implemented as a traversal leaves a node.

Figure 12-1. Depth-First, Left-to-Right Traversal of a Simple Scene Graph

Figure 12-1 Depth-First, Left-to-Right Traversal of a Simple Scene Graph

Notice that a depth-first traversal visits each parent node twice, once before and once after visiting its children. A depth-first traversal is inherently sequential and so cannot be reasonably executed by more than one process; the ordering of actions, particularly when parents are visited after their children, is best maintained by one process.

Breadth-First Traversal Sequence

The central concept of a breadth-first traversal is that the traverser visits the nodes at a given level and proceeds to a lower level in the scene graph after all the nodes at a higher level have been visited. Figure 12-2 shows a breadth-first traversal of a simple scene graph. The solid circles in the figure indicate per-node callbacks, which are implemented when a traverser first visits a node.

Figure 12-2. A Breadth-First Traversal of a Simple Scene Graph

Figure 12-2 A Breadth-First Traversal of a Simple Scene Graph

Some features such as a multiprocess traversal or nodes with multiple parents, can complicate the sequence of nodes visited in a bread-first traversal. In those cases, the simple left-right, top-to-bottom sequence may not hold exactly.

When a breadth-first traversal is executed by several processes, or when nodes in the graph have several parents, a simple rule guarantees a reasonable sequence of events: the traversal does not visit children until it visits at least one of the parents. Whenever a parent node is encountered by a traverser, it places the node's children at the end of the processing queue.

Callbacks During a Traversal

During a traversal an instance of an action object performs and specifies the following basic operation:

  1. Call a begin() method to establish any context you might want for the traversal.

  2. Visit the scene-graph nodes in sequence.

  3. Perform the appropriate callback at each node and determine how the traversal is to proceed.

  4. Delete or retain the action object as specified by the return value of the action object's member function end().

You have two controls over how a traversal proceeds:

  • The return values of the node-visiting callbacks, which allow you to continue, stop, or remove the children of a node from the traversal.

  • The node argument of the callback, which is passed by reference, and provides great freedom in determining the specific node that is next in the traversal.

Controlling a Traversal With the Callback Return Value opTravDisp

The possible return values of callbacks, and the method apply() which initiates a traversal callback sequence, are set by the enumerated type opTravDisp whose values determine whether the traversal should continue, skip over the children of the current node, or stop.

This is the type definition for opTravDisp:

typedef enum {opTravCont=0, opTravPrune=1, opTravStop=2} opTravDisp;

Specifying Deletion of Traversal Object Storage: opActionDisp

After you complete a traversal, you can keep the object for subsequent use, or free storage assigned to the traversal object. For example, you may repeatedly use a cull traverser, invoking it each frame, but you may perform a tessellation traversal only once.

To specify whether a traversal object remains in memory after the traversal stops, specify the return value of the last callback, end(). The possible values are set by the enumerated type opActionDisp. This is the declaration for opActionDisp:

typedef enum {opDeleteThis, opKeepThis} opActionDisp;

Depth-First Traversals: opDFTravAction

The class opDFTravAction is used for a depth-first traversal of the scene graph. Parent nodes are visited at least twice, before and after their children are visited with a different callback for each visit (see “Depth-First Traversal Sequence”).

Class Declaration for opDFTravAction

The class has the following main methods:

class opDFTravAction : public opAction
{
public:
opDFTravAction();
virtual ~opDFTravAction();

opTravDisp apply(csNode *root);

virtual void         begin   (csNode *& , const opActionInfo&);
virtual opTravDisp   preNode (csNode *&, const opActionInfo&);
virtual opTravDisp   postNode(csNode *&, const opActionInfo&);
virtual opActionDisp end     (csNode *&, const opActionInfo&);
};

Methods in opDFTravAction

apply() 

Initiates a traversal below root.

The following table lists callbacks, where they are applied, and what they do (see also “Depth-First Traversal Sequence”):

Table 12-1. opDFTravAction Callbacks

Callback

When Applied

Notes

begin()

Before the traverser visits any node.

The csNode argument is the root of the traversal. If the argument equals NULL, the tree is empty and no traversal will begin. The default for begin() does nothing.

preNode()

Before visiting a node for the first time or for each visit to a node before visiting its children. The latter case occurs, for example, when a parent is itself the child of two parents; thus a traverser could visit the node twice during a traversal and apply preNode() each time before visiting the children.

The default for preNode() returns opTravCont, and thus simply continues the traversal.

postNode()

After visiting a node's children.

The default for postNode() returns opTravCont and thus simply continues the traversal.

end( node, info )

Once the traversal is completed or halted by a callback.

The node argument is the root of the scene graph. The default for end() cleans up by returning opDeleteThis, thus deleting the opDFTravAction. To avoid deletion, define end() to return the value opKeepThis.

Note the following two features of the arguments you pass to preNode(), postNode(), and end():

  • The csNode pointer, which also appears as an argument for all of the callbacks, is passed by reference; thus you can change its value. This is useful when the scene graph changes during a traversal, typically when nodes have been added. The traverser “decides” where to go next by assuming the traversal is complete up to the current csNode.

  • The class opActionInfo, which appears as an argument for all the callback functions, is valid only if the traversal is initiated by the thread manager. opActionInfo is discussed in the section “Difference Between Interprocess Control Methods”.

Breadth-First Traversals: opBFTravAction

The class opBFTravAction is for a breadth-first traversal, which can be performed on one or several processors. All nodes are visited only once, typically, in contrast with an opDFTravAction, for which parent nodes are typically visited at least twice.

Class Declaration for opBFTravAction

The class has the following main methods:

class opBFTravAction : public opAction
{
public:
opBFTravAction();
virtual ~opBFTravAction();
  
opTravDisp apply(csNode *root);
void     applyMP(csNode *root, 
                 opThreadMgr *tm, 
                 const opTIDSet& tids = opTIDSet::opAllTIDs,
                 opPriority p = Optimizer::opDefaultPriority);

virtual void         begin  (csNode *&, const opActionInfo& );
virtual opTravDisp   perNode(csNode *&, const opActionInfo& ):
virtual opActionDisp end    (csNode *&, const opActionInfo& );
};

Methods in opBFTravAction

apply() 

Initiates a traversal.

applyMP() 

Initiates a traversal on several threads using a thread manager. See “Overview of the Thread Manager”.

The following table lists callbacks, where they are applied, and what they do (see also “Breadth-First Traversal Sequence”):

Table 12-2. opBFTravAction Callbacks

Callback

When Applied

Notes

begin()

Before the traverser visits any node.

The csNode argument is the root of the traversal. If the argument equals NULL, the tree is empty and no traversal will begin. The default for begin() does nothing.

perNode()

Is applied as the traverser visits each node

A return value of opTravStop stops the traversal at the current node. A return value of opTravStop is equivalent to opTravPrune, thus eliminating from the traversal whatever children the current node may have. The default for perNode() returns opTravPrune and thus skips any of the node's children.

end( node, info )

Once the traversal is completed or halted by a callback.

The node argument is the root of the scene graph. The default for end() cleans up by returning opDeleteThis, thus deleting the opDFTravAction. To avoid deletion, define end() to return the value opKeepThis.

The callbacks are applied at these points of the traversal (see ):

As for an opDFTravAction, the scene-graph-node callback arguments can be modified to change the course of the traversal and opActionInfo arguments are only valid if the traversal is initiated in a multi-threaded context by a thread manager.

Sample Traversal Function From the opoptimize Sample Application

The following code fragment illustrates the use of a traverser, and also shows how simplification traversal works.

OpenGL Optimizer does not provide a simplification traversal class, instead, an application can design its own traversal class to meet particular needs. The example below provides one approach: It defines a simplification traversal function that returns the root of a new, simplified scene graph.

The example performs two checks that are not usually needed for a traverser, but are necessary for a simplifier:

  • Some node-checking to determine whether a node is a csShape, and so could contain a csGeoSet to simplify

  • Some code to further check whether the csShape actually contains a csGeoSet.

These lines of code are taken from simplify.cxx and main.cxx in /usr/share/Optimizer/src/apps/opoptimize.

Create a Simplifier

 

 

See “Creating LODs: opSRASimplify”.

 

static opSRASimplify simplifier;

 

Create a Traversal Object

 

 

 

Derive an opDFTravAction

 

 

class SimplifyGeoSet : public opDFTravAction

{

public:

opTravDisp PreNode(csNode *&, const opActionInfo&);

opSRASimpParam *userData;

csGroup *simpObj;

};

 

Specify Effect of Callback

 

 

 

Define the callback preNode().

 

opTravDisp SimplifyGeoSet::PreNode(
csNode *&node, const opActionInfo &)

Specify Effect of Callback (cont.)

 

 

 

Set the return value to continue the traversal, thus visiting every node.

 

{

opTravDisp rv = opTravCont;

Test if a node is a csShape, and thus may have a csGeoSet to simplify.

 

if ((node->getType())->
isDerivedFrom(csShape::getClassType()))

{

Simplify all csGeoSets in the csShape by using an opSRASimplify (see “Creating LODs: opSRASimplify”).

csShape *shape = (csShape*)node;

for (int i = 0; i < shape->getGeometryCount(); i++)
{
csGeometry *g= shape->getGeometry(i);
if (
g &&
g->getType()->isDerivedFrom(
csGeoSet::getClassType()
)
)

{

csGeoSet *simpGSet, *gset = (csGeoSet*)g;
int status;
simplifier.settings(userData);
// If simplifier didn't change input geoset,
// then original input geoset is returned.

simpGSet =
simplifier.decimateGeoSet(gset, &status);

Place the simplifications in new csShapes with the same appearance as the originals.

 

// Whether or not the gset changed,
// add it to the group
// XXX Need clone since tree gets flattened

csShape *simpShape = (csShape *)new csShape;
simpShape->setAppearance(
shape->getAppearance()
);

// Add simplified geoset.

simpShape->setGeometry(i,simpGSet);
simpObj->addChild(simpShape);

}
}
}
return rv;

}

 

Define the SimplifyTraversal Function

 

 

 

The application simplify then uses SimplifyGeoSet() to define a simplify-traversal function, simplifyTree().

 

csGroup *simplifyTree(csGroup *obj, opSRASimpParam *userData)

{

csSphereBound sphere;

 

obj->getSphereBound(sphere);

csGroup *simpObj = new csGroup;

SimplifyGeoSet *action = new SimplifyGeoSet;
action->userData = userData;
action->simpObj = simpObj;

action->apply(obj);

return simpObj;

}

 

Use the Function: Here, Add Simplified Graph to an LOD

 

 

 

The application opoptimize calls simplifyTree() and adds the simplified graph as a child of an LOD node. addLODChild() is defined in /usr/share/Optimizer/src/apps/opoptimize/addL OD.cxx.

 

csGroup *simpObj = simplifyTree(root, parameters);// Set child0 as default LOD to be drawn
root = addLODChild(root,simpObj,0);


Traversing a Scene Graph and Applying a csDispatch: opDispatchAction

The class opDispatchAction is a csAction that, as it traverses a scene graph, applies a csDispatch to each node in a scene graph.

Recall that a csAction is a Cosmo3D object for traversing a scene-graph. The class csDispatch is an object designed to follow the “Visitor Behavioral Pattern,” which provides a convenient way to organize and define operations on scene graph elements. The Visitor Behavioral Pattern is described in Design Patterns, listed in “Recommended Background Reading”. A csDispatch is a “Visitor,” and subclasses are “Concrete Visitors.” This pattern is also used in Open Inventor; see The Inventor Toolmaker. For more information about csAction and csDispatch, see Cosmo 3D Programmer's Guide.

An example of an opDispatchAction is the tool for gathering scene graph statistics; see “Getting Statistics About a Scene Graph: opTriStats”.

Methods in opDispatchAction

apply(csNode *node) 


Is inherited from csAction. A call to apply() traverses the scene graph below node.

opDispatchAction(csDispatch *d) 


Constructs the class and specifies the csDispatch to be applied during the traversal begun by a call to apply().

OpenGL Optimizer Programmer's Guide: An Open API for Large-Model Visualization
(document number: 007-2852-002 / published: 1998-06-09)    table of contents  |  additional info  |  download

    Front Matter
    About This Guide
    Part I. Getting Started
    Part II. High-Level Strategic Tools for Fast Rendering
    Part III. Specific Tools for Fast Rendering
    Part IV. Managing and Rendering Higher-Order Geometric Primitives
    Part V. Traversers, Low-Level Geometry Processing, and Multiprocessing
    Part VI. Utilities and Troubleshooting
    Part VII. Appendices
    Glossary
    Index


home/search | what's new | help