Chapter 6. Creating Scene Graphs

A scene graph holds the data that defines a virtual world. The scene graph includes low-level descriptions of object geometry and their appearance, as well as higher-level, spatial information, such as specifying positions, animations, and transformations of objects, as well as additional application-specific data.

Scene graph data is encapsulated in many different types of nodes. One node might contain the geometric data of an object; another node might contain the transformation to orient and position it in the virtual world.

Nodes are associated in a hierarchy that is a directed, acyclic graph. OpenGL Performer and your application can act on the scene graph to perform various complex operations efficiently, such as database intersection and rendering scenes.

This chapter describes how to create, change, load, and save scene graphs, in the following sections:

What Is a Node?

A node is a scene graph building block; a pfNode is the abstract class from which OpenGL Performer nodes are inherited. A complete OpenGL Performer scene graph is one that is rooted by a pfScene node. The most common type of node is the pfGroup node, which can take an arbitrary number of child nodes. Other node types are more discriminating, and provide structure and semantics to the operations that process them.

pfNodes are opaque classes; methods are used to get and set all member fields. For example, a few of the methods setting some node fields are included in Table 6-1.

Table 6-1. Examples of Node Fields

Node

Field

Type

Method

Description

pfSwitch

Val

float

pfSwitchVal

Selects the active child under a pfSwitch node.

pfLOD

Center

pfVec3

pfLODCenter

Sets the center for LOD evaluation.

pfLayer

Mode

int

pfLayerMode

Selects a method of coplanar object layering.

Because it is subclassed from pfUpdatable, pfNode is automatically multibuffered for multiprocessing. This feature enables pfNode subclasses to be edited safely from the application processes while OpenGL Performer is using them for scene graph operations in other processes.

Nodes in OpenGL Performer are used for describing a virtual world. Objects associated with viewing the world, such as pfChannels, are not nodes and are not placed in the scene graph. Only pfNode and its subclasses can be placed directly in the scene graph and only some of those subclasses can take child nodes.

Node Attributes

All nodes have the following attributes:

  • Parent list — node(s) from which the node is subclassed.

  • B ounding volume—volume, sphere, box, or cylinder that completely surrounds a shape and is roughly equivalent to the size of the shape. A bounding volume makes such things as collisions and culling faster to compute.

  • Name— name of the node.

  • Type— node type.

  • T raversal masks—directs a traversal to a subgraph of nodes.

  • Callback functions and data—callback functions enable the programmer to customize the behavior of certain traversals for specific nodes.

Scene Graph Nodes

The two most general classifications of node functionality are:

  • G roup nodes—associate nodes into hierarchies.

  • L eaf nodes—contain all the descriptive data of objects in the virtual world used to render them.

Group Nodes

Only group nodes can have child nodes. Each child node has an index number; the first child added to a group node has an index number of 0, the next child added has an index number of 1, and so on. The group node keeps a list of its child nodes.

A child node may have multiple parent nodes. For example, a node encapsulating a wheel might have four parent nodes, each translating the shape of the wheel to a different place in the scene (on a car), as shown in Figure 6-1.

Figure 6-1. Multiple Parent Nodes

Multiple Parent Nodes

OpenGL Performer Group Nodes

In OpenGL Performer, the pfNodes inherited from pfGroup are:

  • pfScene—root node of a scene graph.

  • pfLOD—its children represent the same shape but at different levels of resolution (LOD).

  • pfSCS—stores a static coordinate system transformation in which to place its children.

  • pfDCS—contains a changeable coordinate system in which to place its children.

  • pfFCS—contains a pfFlux for holding a coordinate system transformation that is computed by an asynchronous process.

  • pfSwitch—node that directs a traversal to one, all, or none of the child nodes.

  • pfSequence—node that directs a traversal to each of its child nodes one at a time, sequentially.

  • pfLayer—groups coplanar polygons: the first child is the base and the following children layer on top of it and one another.

  • pfPartition—group that optimizes very flat terrains.

Leaf Nodes

L eaf nodes contain the descriptive values used to render all the visual elements in the virtual world. Leaf nodes cannot have child nodes.

Special leaf nodes in OpenGL Performer include:

  • pfGeode—encapsulates general geometry in the scene graph.

  • pfLightSource—contains global light sources.

  • pfASD—contains a continuous morphing LOD surface.

  • pfBillboard—makes a slice of geometry turn to always face the viewer, which reduces the amount of rendering necessary to view a shape.

  • pfText—incorporates 3D text into a scene graph.

Creating a Scene Graph

Creating a scene graph is an iterative process of adding child nodes (leaf and group) to group nodes. Eventually, you create a tree rooted at a pfScene node.

Creating and Attaching the pfScene Node

The root node, pfScene, is the node at the “top” of the scene graph hierarchy. pfScene is a group node because child nodes must be added to it. When a traversal is applied to it, the traversal is (potentially) passed to all other nodes in the scene graph.

You create a pfScene using the following method:

pfScene* root = pfNewScene();

pfScene nodes are attached to a pfChannel using the following method:

pfChanScene(chan, root);

A pfChannel provides a view of the geometric objects defined in the scene graph. For more information about pfChannel, see Chapter 5, “Creating a Display with pfChannel”.

Adding Nodes in a Scene Graph

You can start anywhere in the hierarchy to create the scene graph. To create the hierarchy by follow these steps:

  1. Create a group node and a child node using lines of code similar to the following:

    pfGroup* myGroup = pfNewGroup();
    pfASD* childNode = pfNewASD();
    

  2. Add child nodes to the group node, as follows:

    pfAddChild(myGroup, childNode);
    

You continue making the hierarchy by repeating these steps.

Removing Nodes from a Scene Graph

To remove a node from a scene graph, use the following method:

pfRemoveChild(GroupNode, removeNode)

pfRemoveChild() returns 0 if the node is not a child of the specified group node.

When a child is removed, the index numbers of the remaining children are shifted so there is no discontinuity; all index numbers greater than the index number removed are decremented by one.

To find a specific node in a scene graph based on name and type, use pfFindNode().

Arrangement of Nodes

The hierarchy of nodes is determined by the order in which you add nodes to one another. For example, if you start with the root node, called the pfScene node, and add a child node to it, it would appear in the scene graph directly below the pfScene node.

There are no rules for grouping nodes. However, there are some important guidelines that affect the performance of an application:

  • Group nodes together for spatial coherence—put objects that are in the same basic location under the same group node.

  • Rather than extend one object, such as a runway, across the entire scene, break static objects into multiple pieces in pfGeoSets or pfNodes so that parts of the object can be culled. In this way the culling traversal does not have to consider too many nodes at any one level in the scene graph.

  • To reduce memory usage, for a potential minor performance cost, encapsulate in a separate node any objects that are used repeatedly. For example, a single node encapsulating a wheel can be referenced four times when creating a car, rather than using four nodes to encapsulate a wheel.

  • In a vertical hierarchy, place all nodes comprising an object; if the top node of the shape is culled, the remaining nodes in the hierarchy of the shape are not evaluated.

Loading a Scene Graph

A scene graph you create might contain thousands of nodes; for that reason, they are retained on disk. The nodes are paged into memory according to the location of the viewer, only those parts of the scene close to the viewer are in system memory. For more information on paging, see Chapter 12, “Database Paging”.

The explicit arrangement of data in the scene graph depends on the format used. Formats are identified by the extensions to the filenames, for example, Wavefront files use .obj and Workbench files use .dwb.

To load a scene graph file, pass the name of the file to pfdLoadFile():

pfNode *pfdLoadFile(const char *filename);

filename is the name of the scene graph or subgraph database file.

pfdLoadFile() loads scene graph data at run time from the disk and constructs a graph from the data, as shown in Figure 6-2.

Figure 6-2. Loading Scene Graphs

Loading Scene Graphs

pfdLoadFile() performs a run-time search for a DSO to load the file based on the filename suffix and calls the load routine pfdLoadFile_xxx() for the given database format. This mechanism allows OpenGL Performer to support an unlimited number of formats and to load new files in new formats or to load multiple formats at any time. The OpenGL Performer distribution includes a large number of file loaders.

Table 6-2 lists some of the more common file formats.

Table 6-2. Supported Scene Graph File Formats

Modeler

File Name Extension

Alias|Wavefront

.obj

3D Studio

.3ds

Coryphaeus

.dwb

Multigen

.flt

Inventor

.iv

Lightscape

.lsa, .lsb

Performer (native)

.pfa, .pfb

To see a complete list of supported formats, see the OpenGL Performer Programmer's Guide .

Finding Scene Graph Files

OpenGL Performer automatically looks for the file specified in pfdLoadFile() in the following directories in the following order:

  1. Current directory.

  2. Directories specified by the PFPATH environment variable.

  3. Directories specified by pfFilePath() or  pfFilePathv().

    The function pfFilePathv() is the preferred function. See section “Setting the Search Path for Database Files” in Appendix A for more details.

The last valid directory takes precedence over any before it. For example, if you had two versions of mySceneGraph.pif, one in the current directory and another in the directory specified by pfdLoadFile(), the version in pfdLoadFile() would be loaded.

Saving a Scene Graph

To save a scene graph or part of one, libpfpfb supports the following method:

int pfdStoreFile(pfNode *root, const char *filename)

root is the pfScene node or the top of the subgraph that you want to save.

filename is the name of the file in which the scene graph is stored. The same run-time search mechanism used for pfdLoadFile() is also used for pfdStoreFile() to find a file format writer for the requested format. To use pfdStoreFile() for run-time database paging and to use the fast OpenGL Performer paging format, the extension for the filename should be .pfb.

For more information about the OpenGL Performer binary format (.pfb), see “Optimizing File Loading” in Chapter 15.

Scene Graph Traversals

A traversal is a method applied to (potentially) every node in a scene graph. Each node type responds in its own way by implementing a method call. For example, a common traversal culls the scene. Each pfNode implements a cull() method so the node can respond to the traversal. Individual node instances can further customize traversal behavior with their own callbacks.

Some nodes, called group nodes, simply pass the traversal to other nodes. In some cases (pfSwitch, pfLOD), the group node passes the traversal only to selected children nodes.

Other nodes, called leaf nodes, such as a pfGeode node, either encapsulate geometry to be rendered or represent significant computation, such as pfASD.

Pipelined Traversals

Several standard traversal operations are usually necessary for basic application operation and for the efficient rendering of a scene. OpenGL Performer provides automatic and transparent mechanisms for utilizing pipelined and parallel multiprocessing for handling these different traversals. The following processes can be created by OpenGL Performer for the purpose of handling a specific traversal with its own effective copy of the scene graph nodes.

  • APP—user traversal for updating the values in the nodes.

  • CULL—evaluates application settings and eliminates the processing of any nodes out of view.

  • DRAW—renders the culled scene graph.

  • ISECT—intersects a set of line segments with the scene graph.

  • DBASE—loads new database and deletes pieces no longer needed.

Each process is allotted its own memory space and acts on the scene graph nodes that reside in the shared memory arena, as shown in Figure 6-3. The scene graph geometry, and most of the actual scene graph data, is by default shared across the different processes and is not set up for multiprocessing. For multiprocessed geometry data, you should use the pfFlux object. See Chapter 14, “Dynamic Data,” in the OpenGL Performer Programmer's Guide.

Figure 6-3. Processes Acting on Scene Graph

Processes Acting on Scene Graph

Traversal Order

Scene graphs are traversed in a depth-first, left-to-right order, as shown in Figure 6-4. At each node, some default behavior occurs. For example, a CULL stage starts a bounding sphere test to see whether the node is within the viewing frustum. Custom user pre and post-traversal callbacks on nodes are called as nodes are entered and exited.

Figure 6-4. Scene Graph Traversal Flow

Scene Graph Traversal Flow

Customizing OpenGL Performer Traversals

In addition to providing callback functions for channels, discussed in “Channel Callbacks” in Chapter 5, you can also customize the behavior of nodes by setting up callback functions for them.

You can customize the default behavior, however, by adding pre- or post-callbacks to any node. For example, you might integrate OpenGL into your application for a given node by using OpenGL in your DRAW callback functions.

Setting Up Node Callbacks

The following method enables you to set up pre- or post-callback functions for a node, with a given traversal that triggers the callback function.

void pfGetNodeTravFuncs(const pfNode* node, int which,     pfNodeTravFuncType *pre, pfNodeTravFuncType *post);

node is the node for which the callback functions apply.

which is the kind of traversal that triggers the callback function. Possible values include:

  • PFTRAV_APP

  • PFTRAV_CULL

  • PFTRAV_DRAW

  • PFTRAV_ISECT

pre and post are pointers to callback functions triggered before or after, respectively, a node is traversed. Pre-callback functions completely replace the default behavior of the node; post-callbacks modify the default behavior of the node.

Use NULL as the value when not using a pre- or post-callback function.

You can use * pfGetTravNode() can be used inside the callback function. It returns the node for which a callback function was called.

Passing Data to Traversal Callback Functions

You can pass data to pre- or post- callback functions using the following function:

void pfNodeTravData(pfNode *node, int which, void *data);

node is the node for which the callback functions apply.

which is the kind of traversal that triggers the callback function.

data is a pointer to the data, allocated from shared memory, that is passed to the callback function.

Return Values for Traversal Callback Functions

The return value for your callback function must have one of three values:

  • PFTRAV_CONT—continue with the traversal.

  • PFTRAV_PRUNE—ignore the current node and its children but continue with the traversal.

  • PFTRAV_TERM—terminate the traversal.

Sample Customized Traversals

You can create your own traversals to accomplish specific tasks, such as:

  • Creating packed attribute arrays or GL display lists for objects in the scene graph.

  • Finding the textures or nodes of a specific type in a scene graph.

  • Computing bounding geometry.

OpenGL Performer includes in a pfuTraverser utility libpfutil/trav.c to help you write your own traverser. pfuTraverser recursively traverses the nodes in a scene graph database, applying pre- and post-traversal functions to each node.

Also included in trav.c is a series of general user traversers that implement pfuTraverser, as described in Table 6-3.

Table 6-3. General User Traversals

Traversal

Description

pfuTravPrintNodes

Prints the nodes encountered in a traversal.

pfuTravCountDB

Accumulates static graphics and database statistics for the tree under the given node.

pfuTravNodeHlight

Sets a given highlighting structure on all pfGeoSets under a given node.

pfuTravNodeAttrBind

Sets a given attribute to the given bind value on every pfGeoSet under the given node.

pfuTravCalcBBox

Computes the bounding box.

pfuTravCountNumVerts

Counts the number of vertices.

pfuTravSetDListMode

Sets the display-list pfGeoSet status.

pfuTravCreatePackedAttrs

Creates packed attributes.

pfuFillGSetPackedAttrs

Sets the values of packed attributes.

pfuDelGSetAttrs

Deletes attributes.

pfuTravCachedCull

Caches CULL stages.

pfuCalcDepth

Calculates the depth of the scene graph rooted at a node. A single root node with no children is counted as having a depth of one.

pfuLowestCommonAncestor

Finds the lowest common ancestor of all nodes under node for which a given function returns true.

pfuLowestCommonAncestor OfGeoSets

Finds the lowest common ancestor node of all GeoSets under node for which a given function returns true.

pfuFindTexture

Finds the nth texture under a given node for which a given function returns true.