Chapter 5. Nodes and Node Types

An application based on IRIS Performer usually reads in a visual database from a file, then builds an internal data structure incorporating the information in that database. This data structure is called a scene graph; each element in the data structure is called a node. This chapter describes the types of nodes IRIS Performer uses to represent scenes internally. IRIS Performer provides many functions for creating, querying, modifying, and traversing scene graphs; such functions are also described here.

Note that this chapter focuses on the data types themselves rather than instances of those types. Chapter 6, “Database Traversal,” includes discussion of traversing sample scene graphs, in terms of actual objects rather than abstract data types. Objects in a scene graph (which is to say, instances of node types) have a hierarchical relationship based on the way the scene is laid out. The node types themselves, on the other hand, have an entirely different kind of hierarchical relationship based on the ways in which each node type is like other node types. (For instance, the pfBillboard node type is a subclass of the general pfNode node type.) This concept is discussed in more detail in the section of this chapter titled “Attribute Inheritance”.

Nodes

A scene is represented by a graph of nodes. libpf provides several specialized node types that encapsulate many graphics and visual simulation features. These node types belong to a type hierarchy (which is distinct from the database hierarchy described in Chapter 6). The following sections describe the node types.

Attribute Inheritance

The basic element of a scene hierarchy is the node. While IRIS Performer supplies many specific types of nodes, it also uses a concept called class inheritance, which allows different node types to share attributes. For example, many types of nodes have children, but different node types perform different types of operations on their children. Only one set of routines is provided for adding and removing children, but all the node types can use these routines in the same manner to perform these common operations. This reduces the number of routines required and imposes a logical structure on the node types. This logical structure is also known as a class hierarchy, but it should not be confused with the scene hierarchy of nodes—the derivation hierarchy contains data types, not objects.

IRIS Performer's node hierarchy begins with the pfNode class, just as the overall class hierarchy begins with the pfObject type (see “Nodes”). All node types are subordinate to pfNode, and they inherit pfNode's attributes and the libpf routines for setting and getting attributes. In general, a node type inherits the attributes and routines of all its parent nodes in the type hierarchy.

Figure 5-1 shows the type hierarchy for the libpf node types.

Figure 5-1. Nodes in the IRIS Performer Hierarchy


Table 5-1 lists the basic node class and gives a simple description for each node type.

Table 5-1. IRIS Performer Node Types

Node Type

Node Class

Description

pfNode

Abstract

Basic node type

pfGroup

Branch

Groups zero or more children

pfScene

Root

Parent of the visual database

pfSCS

Branch

Static Coordinate System

pfDCS

Branch

Dynamic Coordinate System

pfSwitch

Branch

Selects among multiple children

pfSequence

Branch

Sequences through its children

pfLOD

Branch

Level-of-detail node

pfLayer

Branch

Renders coplanar geometry

pfLightPoint

Leaf

Contains light point specifications

pfLightSource

Leaf

Contains specifications for a light source

pfGeode

Leaf

Contains geometric specifications

pfBillboard

Leaf

Rotates geometry to face the eyepoint

pfPartition

Branch

Partitions geometry for efficient intersections

pfMorph

Branch

Morphs attribute data of its children

pfText

Leaf

Renders 2D and 3D text


pfNode

As discussed in “Attribute Inheritance”, all libpf nodes are arranged in a type hierarchy, which defines the inheritance of capabilities. The base node class is pfNode; all other nodes inherit the attributes of a pfNode. A pfNode is an abstract class, meaning that a pfNode can never be explicitly created by the application. Its purpose is to provide a root to the type hierarchy and to define the attributes that are common to all node types.

pfNode Attributes

The following pfNode attributes are inherited by all other libpf node types:

  • Node name

  • Parent list

  • Bounding geometry

  • Intersection and traversal masks

  • Callback functions and data

  • User data

Bounding geometry, intersection masks, user data, and callbacks are advanced topics that are discussed in Chapter 6, “Database Traversal.”

The routines that set, get, and otherwise manipulate these attributes can be used by all libpf node types, as indicated by the keyword `Node' in the routine names. Nodes used as arguments to pfNode routines must be cast to pfNode* to match parameter prototypes, as shown in this example:

pfNodeName((pfNode*) dcs, "rotor_rotation");

However, you usually don't need to do this casting explicitly. When you use the C API and compile with the –ansi flag (which is the usual way to compile IRIS Performer applications), libpf provides macro wrappers around pfNode routines that automatically perform argument casting for you. When you use the C++ API, such type casting is not necessary.

pfNode Operations

In addition to sharing attributes, certain basic operations are provided for all node types. They include:

New 

Create and return a handle to a new node.

Get 

Get node attributes.

Set 

Set node attributes.

Find 

Find a node based on its name.

Print 

Print node data.

Copy 

Copy node data.

Delete 

Delete a node.

The Set operation is implied in the node attribute name. The names of the attribute-getting functions contain the string “Get”.

An Example of Scene Creation

Example 5-1 illustrates the creation of a scene that includes two different kinds of pfNodes. (For information about pfScene nodes, see “pfScene Nodes”; for information about pfDCS nodes, see “pfDCS Nodes”.)

Example 5-1. Making a Scene

pfScene *scene;
pfDCS *dcs1, *dcs2;

scene = pfNewScene();         /* Create a new scene node */
dcs1 = pfNewDCS();            /* Create a new DCS node */
dcs2 = pfNewDCS();            /* Create a new DCS node */
pfCopy(dcs2, dcs1);           /* Copy all node attributes */
                              /*        from dcs1 to dcs2 */
pfNodeName(scene, "Scene_Graph_Root"); /* Name scene node */
pfNodeName(dcs1,"DCS_1");     /* Name dcs1 */
pfNodeName(dcs2,"DCS_2");     /* Name dcs2 */
...
/* Use a pfGet*() routine to determine node name */
printf("Name of first DCS node is %s.", pfGetNodeName(dcs1));
...
/* Recursively free this node if it's no longer referenced */
pfDelete(scene);
...


pfGroup

In addition to inheriting the pfNode attributes described in the “pfNode” section of this chapter, a pfGroup also maintains a list of zero or more child nodes that are accessed and manipulated using group operators. Children of a pfGroup can be either branch or leaf nodes. Traversals process the children of a pfGroup in left-to-right order.

Table 5-2 lists the pfGroup functions, with a description and a visual interpretation of each.

Table 5-2. pfGroup Functions

Function Name

Description

Diagram

pfAddChild(group, child)

Appends child to the list for group.

pfInsertChild(group, index, child)

Inserts child before the child whose place in the list is index.

pfRemoveChild(group, child)

Detaches child from the list and shifts the list to fill the vacant spot. Returns 1 if child was removed. Returns 0 if child was not found in the list. Note that the “removed” node is only detached, not deleted.



.

pfGetNumChildren(group)

Returns the number of children in group.

pfGroup nodes can organize a database hierarchy either logically or spatially. For example, if your database contains a model of a town, a logical organization might be to group all house models under a single pfGroup. However, this kind of organization is less efficient than a spatial organization, which arranges geometry by location. A spatial organization improves culling and intersection performance; in the example of the town, spatial organization would consist of grouping houses with their local terrain geometry instead of with each other. Chapter 6 describes how to spatially organize your database for best performance.

The code fragment in Example 5-2 illustrates building a hierarchy using pfGroup nodes.

Example 5-2. Hierarchy Construction Using Group Nodes

scene = pfNewScene();

/* The following loop constructs a sample hierarchy by
 * adding children to several different types of group
 * nodes.  Notice that in this case the terrain was broken
 * up spatially into a 4x4 grid, and a switch node is used
 * to cause only one vehicle per terrain node to be
 * traversed.
 */

for(j = 0; j < 4; j++)
    for(i = 0; i < 4; i++)
    {
        pfGroup *spatial_terrain_block = pfNewGroup();
        pfSCS *house_offset = pfNewSCS();
        pfSCS *terrain_block_offset = pfNewSCS();
        pfDCS *car_position = pfNewDCS();
        pfDCS *tank_position = pfNewDCS();
        pfDCS *heli_position = pfNewDCS();
        pfSwitch *current_vehicle_type;
        pfGeode *heli, *car, *tank;

        pfAddChild(scene, spatial_terrain_block);
        pfAddChild(spatial_terrain_block,
                   terrain_block_offset);
        pfAddChild(spatial_terrain_block, house_offset);
        pfAddChild(spatial_terrain_block,
                   current_vehicle_type);
        pfAddChild(current_vehicle_type, car_position);
        pfAddChild(current_vehicle_type, tank_position);
        pfAddChild(current_vehicle_type, heli_position);
        pfAddChild(car_position, car);
        pfAddChild(tank_position, tank);
        pfAddChild(heli_position, heli);
    }

...

/* The following shows how one might use IRIS Performer to
 * manipulate the scene graph at run time by adding and
 * removing children from branch nodes in the scene graph.
 */

for(j = 0; j < 4; j++)
    for(i = 0; i < 4; i++)
    {
        pfGroup *this_terrain;
        this_terrain = pfGetChild(scene, j*4 + i);
        if (pfGetNumChildren(this_terrain) > 2)
            this_tank = pfGetChild(this_terrain, 2);
        if (is_tank_disable(this_tank))
        {
            pfRemoveChild(this_terrain, this_tank);
            pfAddChild(disabled_tanks, this_tank);
        }
    }
...


Working With Nodes

This section describes the basic concepts involved in working with nodes. It explains how shared instancing can be used to create multiple copies of an object, and how changes made to a parent node propagate down to its children. A sample program that illustrates these concepts is presented at the end of the chapter.

Instancing

A scene graph is typically constructed at application initialization time by creating and adding new nodes to the graph. If a node is added to two or more parents it is termed instanced and is shared by all its parents. Instancing is a powerful mechanism that saves memory and makes modeling easier. libpf supports two kinds of instancing, which are described in the following sections.

Shared Instancing

Shared instancing is the result of simply adding a node to multiple parents. If an instanced node has children, then the entire subgraph rooted by the node is considered to be instanced. Each parent shares the node; thus, modifications to the instanced node or its subgraph are experienced by all parents. Shared instances can be nested—that is, an instance can itself instance other nodes.

In the following sample code, group0 and group1 share a node:

pfAddChild(group0, node);
pfAddChild(group1, node);

Figure 5-2 shows the structure created by this code. Before the instancing operation, the two groups and the node to be shared all exist independently, as shown in the left portion of the figure. After the two function calls shown above, the two groups both reference the same shared hierarchy. (If the original groups referenced other nodes, those nodes would remain unchanged.) Note that each of the group nodes considers the shared hierarchy to be its own child.

Figure 5-2. Shared Instances


Cloned Instancing

In many situations shared instancing isn't desirable. Consider a subgraph that represents a model of an airplane with articulations for ailerons, elevator, rudder, and landing gear. Shared instances of the model result in multiple planes that share the same articulations. Consequently, it's impossible for one plane to be flying with its landing gear retracted while another is on a runway with its landing gear down.

Cloned instancing provides the solution to this problem by cloning—creating new copies of variable nodes in the subgraph. Leaf nodes containing geometry are not cloned and are shared to save memory. Cloning the airplane model generates new articulation nodes, which can be modified independently of any other cloned instance. The cloning operation, pfClone(), is actually a traversal and is described in detail in Chapter 6.

Figure 5-3 shows the result of cloned instancing. As in the previous figure, the left half of the drawing represents the situation before the operation, and the right half shows the result of the operation.

Figure 5-3. Cloned Instancing


The cloned instancing operation constructs new copies of each internal node of the shared hierarchy, but uses the same shared instance of all the leaf nodes. In use, this is an important distinction, because the number of internal nodes may be relatively few, while the number and content of geometry-containing leaf nodes is often quite extensive.

Nodes G1 and G2 in Figure 5-3 are the groups that form the root nodes after the cloned instancing operation is complete. Node P is the parent or root node of the instanced object, and D is a dynamic coordinate system contained within it. Nodes A, B, and C are the leaf geometry nodes; they are shared rather than copied.

The code in Example 5-3 shows how to create cloned instances.

Example 5-3. Creating Cloned Instances

pfGroup *g1, *g2, *p;
pfDCS *d;
pfGeode *a, *b, *c;

...
/* Create initial instance of scene hierarchy of p under
 * group g1: add a DCS to p, then add three pfGeode nodes
 * under the DCS.
 */
pfAddChild(g1,p);
pfAddChild(p,d);
pfAddChild(d,a);
pfAddChild(d,b);
pfAddChild(d,c);

...
/* Create cloned instance version of p under g2 */
pfAddChild(g2, pfClone(p,0));
/* Notice that pfGeodes are cloned by instancing rather than
 * copying.  Also notice that the second argument to
 * pfClone() is 0; that argument is currently required by
 * IRIS Performer to be zero.
 */
...


Bounding Volumes

libpf uses bounding volumes for culling and to improve intersection performance. libpf computes bounding volumes for all nodes in a database hierarchy unless the bound is explicitly set by the application. The bounding volume of a branch node encompasses the spatial extent of all its children. libpf automatically recomputes bounds when children are modified.

By default, bounding volumes are dynamic; that is, libpf automatically recomputes them when children are modified. For instance, in Example 5-4 when the DCS is rotated nothing more needs to be done to update the bounding volume for g1.

Example 5-4. Automatically Updating a Bounding Volume

pfAddChild(g1,dcs);
pfAddChild(dcs, helicopter);

...

pfDCSRot(dcs, heading+10.0f, pitch,roll);
...
pfDCSRot(dcs, heading, pitch - 5.0f, roll + 2.0f);

In some cases, you may not want bounding volumes to be recomputed automatically. For example, in a merry-go-round with horses moving up and down, you know that the horses stay within a certain volume. Using pfNodeBSphere(), you can specify a bounding sphere within which the horse always remains and tell IRIS Performer that the bounding volume is “static”—not to be updated no matter what happens to the node's children. You can always force an update by setting the bounding volume to NULL with pfNodeBSphere(), as follows:

pfNodeBSphere(node, NULL, NULL, PFBOUND_STATIC);

At the lowest level, within pfGeoSets, bounding volumes are maintained as axially-aligned boxes. When you add a pfGeoSet to a pfGeode or directly invoke pfGetGSetBBox() on the pfGeoSet, a bounding box is created for the pfGeoSet. Neither the bounding box of the pfGeoSet nor the bounding volume of the pfGeode is updated if the geometry changes inside the pfGeoSet. You can force an update by setting the pfGeoSet bounding box and then the pfGeode bounding volume to a NULL bounding box, as follows:

  • Recompute the pfGeoSet bounding box from the internal geometry:

    pfGSetBBox(gset, NULL);
    

  • Recompute the pfGeode bounding volume from the bounding boxes of its pfGeoSets:

    pfNodeBSphere(geode, NULL, PFBOUND_DYNAMIC);
    

Node Types

This section describes the node types and the functions for working with each node type.

pfScene Nodes

A pfScene is a root node that is the parent of a visual database. Use pfNewScene() to create a new scene node. Before the scene can be drawn, you must call pfChanScene(channel, scene) to attach it to a pfChannel.

Any nodes that are within the graph that is parented by a pfScene are culled and drawn once the pfScene is attached to a pfChannel. Because pfScene is a group, it uses pfGroup routines; however, a pfScene cannot be the child of any other node. The following statement adds a pfGroup to a scene:

pfAddChild(scene, root);

In the simplest case, the pfScene is the only node you need to add. Once you have a pfPipe, pfChannel, and pfScene, you have all the necessary elements for generating graphics using IRIS Performer.

pfScene Default Rendering State

pfScene nodes may specify a global pfGeoState that all other pfGeoStates in nodes below the pfScene will inherit from. Specification of this scene pfGeoState is done via the function pfSceneGState(). This functionality allows for the subtle optimization of pushing the most frequently used pfGeoState attributes for a particular scene graph into a global state and having the individual states inherit these attributes rather than specify them. This can save Performer work during culling (by having to `unwrap' fewer pfGeoStates) and thus possibly increase frame rate. There are several database utility functions in libpfdu designed to help with this optimization. pfdMakeSceneGState() returns an `optimal' pfGeoState based on a list of pfGeoStates. pfdOptimizeGStateList() takes an existing global pfGeoState, a new global pfGeoState, and a list of pfGeoState's that should be optimized and cause all attributes of pfGeoStates in the list of pfGeoStates to be inherited if they are the same as the attribute in the new global pfGeoState. Lastly pfdMakeSharedScene() will cause this optimization to happen for all of the pfGeoStates under the pfScene that was passed into the function. For more information on pfGeoStates see Chapter 10, “libpr Basics,” which discusses libpr in more detail. For more information of the creation and optimization of databases see Chapter 9, “Importing Databases,” which discusses building database converters and libpfdu.

pfSCS Nodes

A pfSCS is a branch node that represents a static coordinate system. A pfSCS node contains a fixed modeling transformation matrix that cannot be changed once it is created. pfSCS nodes are useful for positioning models within a database. For example, a house that is modeled at the origin should be placed in the world with a pfSCS because houses rarely move during program execution.

Use pfNewSCS(matrix) to create a new pfSCS using the transformation defined by matrix. To find out what matrix was used to create a given pfSCS, call pfGetSCSMat().

For best graphics performance, matrices passed to pfSCS nodes (and the pfDCS node type described in the next section) should be orthonormal (translations, rotations, and uniform scales). Nonuniform scaling requires renormalization of normals in the graphics pipe. Projections and other non-affine transformations are not supported.

While pfSCS nodes are useful in modeling, using too many of them can reduce culling, rendering, and intersection performance. For this reason, libpf provides the pfFlatten() traversal. pfFlatten() will traverse a scene graph and apply static transformations directly to geometry to eliminate the overhead associated with managing the transformations. pfFlatten() is described in detail in Chapter 6, “Database Traversal.”

pfDCS Nodes

A pfDCS is a branch node that represents a dynamic coordinate system. Use a pfDCS when you want to apply an initial transformation to a node and also change the transformation during the application. Use a pfDCS to articulate moving parts and to show object motion.

Use pfNewDCS() to create a new pfDCS. The initial transformation of a pfDCS is the identity matrix. Subsequent transformations are set by specifying a new transformation matrix, or by replacing the rotation, scale, or translation in the current transformation matrix. The pfDCS transforms each child C(i) to C(i)*Scale*Rotation*Translation.

Table 5-3 lists functions for manipulating a pfDCS, including rotating, scaling, and translating the children of the pfDCS.

Table 5-3. DCS Transformations

Function Name

Description

pfNewDCS

Create a new pfDCS node.

pfDCSTrans

Set the translation coordinates to x, y, z.

pfDCSRot

Set the rotation transformation to h, p, r.

pfDCSCoord

Rotate and translate by coord.

pfDCSScale

Scale by a uniform scale factor.

pfDCSMat

Use a matrix for transformations.

pfGetDCSMat

Retrieve the current matrix for a given pfDCS.


pfSwitch Nodes

A pfSwitch is a branch node that selects one, all, or none of its children. Use pfNewSwitch() to return a handle to a new pfSwitch. To select all the children, use the PFSWITCH_ON argument to pfSwitchVal(). Deselect all the children (turning the switch off) using PFSWITCH_OFF. To select a single child, give the index of the child from the child list. To find out the current value of a given switch, call pfGetSwitchVal(). Example 5-5 (in the “pfSequence Nodes” section) illustrates a use of pfSwitch nodes to control pfSequence nodes.

pfSequence Nodes

A pfSequence is a pfGroup that sequences through a range of its children, drawing each child for a specified duration. Each child in a sequence can be thought of as a frame in an animation. A sequence can consist of any number of children, and each child has its own duration. You can control whether an entire sequence repeats from start to end, repeats from end to start, or terminates.

Use pfNewSeq() to create and return a handle to a new pfSequence. Once the pfSequence has been created, use the group function pfAddChild() to add the children that you want to animate.

Table 5-4 describes the functions for working with pfSequences.

Table 5-4. pfSequence Functions

Function

Description

pfNewSeq

Create a new pfSequence node.

pfSeqTime

Set the length of time to display a frame.

pfGetSeqTime

Find out the time allotted for a given frame.

pfSeqInterval

Set the range of frames and sequence type.

pfGetSeqInterval

Find out interval parameters.

pfSeqDuration

Control the speed and number of repetitions of the entire sequence.

pfGetSeqDuration

Retrieve speed and repetition information for the sequence.

pfSeqMode

Start, stop, pause, and resume the sequence.

pfGetSeqMode

Find out the sequence's current mode.

pfGetSeqFrame

Get the current frame.

Example 5-5 demonstrates a possible use of both switches and sequences. First, sequences are set up to contain animation sequences for explosions, fire, and smoke; then a switch is used to control which sequences are currently active.

Example 5-5. Using pfSwitch and pfSequence Nodes

pfSwitch *s;
pfSequence *explosion1_seq, *explosion2_seq, *fire_seq,
           *smoke_seq;
...
s = pfNewSwitch();
explosion1_seq = pfNewSeq();
explosion2_seq = pfNewSeq();
fire_seq = pfNewSeq();
smoke_seq = pfNewSeq();

pfAddChild(s, explosion1_seq);
pfAddChild(s, explosion2_seq);
pfAddChild(s, fire_seq);
pfAddChild(s, smoke_seq);
pfSwitchVal(s, PFSWITCH_OFF);
...
if (direct_hit)
{
    pfSwitchVal(s, PFSWITCH_ON); /* Select all sequences */

    /* Set first explosion sequence to go double normal
     * speed and repeat 3 times. */
    pfSeqMode(explosion1_seq, PFSEQ_START);
    pfSeqDuration(explosion1_seq, 2.0f, 3);

    /* Set second explosion sequence to display first child
     * of sequence for 2 seconds before continuing. */
    pfSeqMode(explosion2_seq, PFSEQ_START);
    pfSeqTime(explosion2, 0.0f, 2.0f);

    /* Set fire to wait on first frame of sequence until .3
     * seconds after second explosion. */
    pfSeqMode(fire_seq, PFSEQ_START);
    pfSeqTime(fire_seq, 0.0f, 2.3f);

    /* Set smoke to wait until .1 seconds after fire. */
    pfSeqMode(smoke_seq, PFSEQ_START);
    pfSeqTime(smoke_seq, 0.0f, 2.4f);
}
else if (explosion && (expl_type == 0))
{
    pfSeqMode(explosion1_seq, PFSEQ_START);
    pfSwitchVal(s, 0);
}
else if (explosion && (expl_type == 1))
{
    pfSeqMode(explosion2_seq, PFSEQ_START);
    pfSwitchVal(s, 1);
}
else if (fire_is_burning)
{
    pfSeqMode(fire_seq, PFSEQ_START);
    pfSwitchVal(s, 2);
}
else if (smoking)
{
    pfSeqMode(smoke_seq, PFSEQ_START);
    pfSwitchVal(s, 3);
}
else
    pfSwitchVal(s, PFSWITCH_OFF);
...


pfLOD Nodes

A pfLOD is a level-of-detail node. Level-of-detail switching is an advanced concept that is discussed in Chapter 7, “Frame and Load Control.” A level-of-detail node specifies how its children are to be displayed, based on the visual range from the channel's viewpoint. Each child has a defined range, and the entire pfLOD has a defined center.

Table 5-5 describes the functions for working with pfLODs.

Table 5-5. pfLOD Functions

Function

Description

pfNewLOD

Create a level of detail node.

pfLODRange

Set a range at which to use a specified child node.

pfGetLODRange

Find out the range for a given node.

pfLODCenter

Set the pfLOD center.

pfGetLODCenter

Retrieve the pfLOD center.

pfLODTransition

Set the width of a specified transition.

pfGetLODTransition

Get the width of a specified transition.


pfLayer Nodes

A pfLayer is a leaf node that resolves the visual priority of coplanar geometry. A pfLayer allows the application to define a set of base geometry and a set of layer geometry (sometimes called decal geometry). The base geometry and the decal geometry should be coplanar, and the decal geometry must lie within the extent of the base polygons.

Table 5-6 describes the functions for working with pfLayers.

Table 5-6. pfLayer Functions

Function

Description

pfNewLayer

Create a pfLayer node.

pfLayerMode

Specify a hardware mode to use in drawing decals.

pfGetLayerMode

Get current mode.

pfLayerBase

Specify the child containing base geometry.

pfGetLayerBase

Find out which child contains base geometry.

pfLayerDecal

Specify the child containing decal geometry.

pfGetLayerDecal

Find out which child contains decal geometry.

pfLayer nodes can be used to overlay any sort of markings on a given polygon and are important to avoid flimmering. Example 5-6 demonstrates how to display runway markings as a decal above of a coplanar runway. This example uses the performance mode PFDECAL_BASE_FAST for layering; as described in the reference page, other available modes are PFDECAL_BASE_HIGH_QUALITY, PFDECAL_BASE_DISPLACE, and PFDECAL_BASE_STENCIL.

Example 5-6. Marking a Runway With a pfLayer Node

pfLayer *layer;
pfGeode *runway, *runway_markings;

...
/* avoid flimmering of runway and runway_markings */
layer = pfNewLayer();
pfLayerBase(layer, runway);
pfLayerDecal(layer, runway_markings);
pfLayerMode(layer, PFDECAL_BASE_FAST);


pfLightPoint Nodes

A pfLightPoint is a leaf node that represents a light point or set of light points. Light points don't provide overall illumination for a scene; rather, they are discrete points that emanate light, such as street lights and runway edge lights. A pfLightPoint can contain one or several light points that share common attributes, such as color, intensity, direction, and shape.

The pfLPointState mechanism provides a more comprehensive approach to light point simulation. pfLightPoint nodes are retained only for backward compatibility with previous releases of IRIS Performer. New applications should use pfLPointStates.

Table 5-7 describes the functions for working with pfLightPoints.

Table 5-7. pfLightPoint Functions

Function

Description

pfNewLPoint

Create a new pfLightPoint node.

pfLPointSize

Set the size of the points in a node.

pfGetLPointSize

Find out the size of the points in a node.

pfLPointColor

Set the color of a specified point.

pfGetLPointColor

Find out the color of a point.

pfLPointRot

Set the direction and rotation.

pfGetLPointRot

Find out the direction a point is facing.

pfLPointShape

Set the horizontal and vertical envelope.

pfGetLPointShape

Find out the shape of a point.

pfLPointPos

Set the position of each point.

pfGetLPointPos

Find out the position of a point.

Example 5-7 demonstrates some operations on light points.

Example 5-7. Setting Up Light Points

pfLightPoint *lp;
pfVec4 red;
pfVec3 pos;
...
/* create a string of ten light points */
lp = pfNewLPoint(10);

/* set the size in screen pixels for each light */
pfLPointSize(lp, 1.0f);

/* set the light point color */
pfSetVec4(red, 1.0f, 0.0f, 0.0f, 1.0f);
pfLPointColor(lp, red);

/* set the direction the light points face */
pfLPointRot(lp, 270.0f, 30.0f, 0.0f);

/* Set the position of light point 5 */
pfSetVec3(pos,10.0f,10.0f,0.0f);
pfLPointPos(lp, 5, pos);
...


pfLightSource Nodes

A pfLightSource, unlike a pfLightPoint, provides light for a scene's geometry but is itself invisible.

Illuminating the geometry in a scene graph requires a light source which the graphics hardware uses to compute surface shading. While the libpr primitive pfLight (see “Lighting”) provides a hardware light source, it isn't a pfNode and so cannot benefit from the features provided by a scene graph, such as transformation hierarchy, switches, and sequenced animations.

The pfLightSource node allows you to add lighting information to your scene graph. pfLightSource is multiply inherited; it's derived from both pfNode and pfLight, so you can use a pfLightSource* as an argument to a function that requires a pfNode* or pfLight*, as illustrated in Example 5-8.

Example 5-8. pfLightSource Pointers and Multiple Inheritance

pfLightSource *lsource = pfNewLSource();

/* Set color to red */
pfLightColor(lsource, 1.0f, 0.0f, 0.0f);

/* Add light source to scene */
pfAddChild(scene, lsource);

In this example lsource is the pfLight* argument to pfLightColor() and the pfNode* argument to pfAddChild().

The scope, or area of influence, of a pfLightSource is global and isn't affected by its location in the scene graph unless it is culled during the cull traversal. If not culled, it illuminates everything in the pfScene of which it is a member and doesn't affect anything outside the pfScene besides custom geometry rendered by the application in a channel draw callback.

A pfLightSource does inherit transformations and switch values from its parents in the scene graph. Thus you can attach a light source to a moving object and easily turn it on and off. Example 5-9 shows how you might configure the headlights of a moving car:

Example 5-9. Car Headlights as pfLightSource Nodes

static pfNode* initCarGeometry(void);

    pfDCS *car = pfNewDCS();
    pfLightSource *leftHeadlight = pfNewLSource();
    pfLightSource *rightHeadlight = pfNewLSource();

    /*
     * Set white light color. This isn't really necessary
     * since pfLightSources are white by default.
     */
    pfLightColor(leftHeadlight, 1.0f, 1.0f, 1.0f);
    pfLightColor(rightHeadlight, 1.0f, 1.0f, 1.0f);

    /* Position headlights on front of car as local light
     * sources. */
    pfLightPos(leftHeadlight, -1.0f, 0.0f, 0.0f, 1.0f);
    pfLightPos(rightHeadlight, 1.0f, 0.0f, 0.0f, 1.0f);

    /*
     * Configure headlights as spotlights pointing in same
     * direction as car: +Y direction. Set the spotlights
     * to have falloff of 1 and angular spread of 30 degrees.
     */
    pfSpotLightDir(leftHeadlight, 0.0f, 1.0f, 0.0f);
    pfSpotLightCone(leftHeadlight, 1.0f, 30.0f);
    pfSpotLightDir(rightHeadlight, 0.0f, 1.0f, 0.0f);
    pfSpotLightCone(rightHeadlight, 1.0f, 30.0f);

    /*
     * Turn on headlights. This isn't really necessary since 
     * pfLightSources are on by default.
     */
    pfLightOn(leftHeadlight);
    pfLightOn(rightHeadlight);

    /* Create car geometry and add to car DCS */
    pfAddChild(car, initCarGeometry());

    /* Attach headlights to moving car. */
    pfAddChild(car, leftHeadlight);
    pfAddChild(car, rightHeadlight);

In Example 5-9, both the position and spotlight direction of the light sources are transformed by the combination of the car's pfDCS and all other pfDCS or pfSCS nodes above it in the scene graph. Note that we specified to pfLightPos() that the pfLightSource have a location with the fourth coordinate nonzero. This defines a local light source; an infinite light source has zero for its fourth coordinate. An infinite light source has only a direction, not a location; it emits parallel rays because it is considered infinitely far away. (The sun, for example, is approximated as an infinite light source.) Transformations change only the direction of an infinite light source, not its position.

Also note in Example 5-9 that a pfSwitch isn't necessary to turn a pfLightSource on or off; you can directly switch a pfLightSource with pfLightOn() and pfLightOff().

pfLightSources are treated specially when drawing a scene graph. All paths leading to pfLightSources in a scene are traversed before the scene is traversed. (For information about paths, see “Paths Through the Scene Graph” in Chapter 6.) This special, initial traversal guarantees that the graphics hardware will be configured with all pfLightSources so that they will affect all geometry in the scene. In addition, these light sources are set up before the channel draw callback is invoked, so they affect all custom geometry rendered by the application both before and after pfDraw(). (See “pfNode Cull and Draw Callbacks” in Chapter 6 for more on callbacks.)

By default, pfLightSource traversal is enabled and carried out by the cull traversal. If you wish to bypass this traversal and thereby ignore all pfLightSources in your scene, then set the PFCULL_IGNORE_LSOURCES bit in the mode passed to pfChanTravMode() for the PFTRAV_CULL traversal.

A pfLightSource has no default bounding volume, though you can assign it one with pfNodeBSphere(). If a pfLightSource is assigned a bounding volume it becomes subject to view frustum culling and will be discarded if it is completely outside the view frustum of the pfChannel. If the light source is found to be within the view frustum or it has an empty bounding volume (the default), the pfLightSource will affect everything in view. By setting the appropriate bounding sphere it is possible to roughly specify a light source with limited influence.

Table 5-8 describes the functions for working with pfLightSources.

Table 5-8. pfLightSource Functions

Function

Description

pfNewLSource

Create a new pfLightSource node.

pfLightAmbient

Set the ambient color for a pfLight.

pfGetLightAmbient

Get the ambient color for a pfLight.

pfLightColor

Set the color for a pfLight.

pfGetLightColor

Find out the color of a pfLight.

pfSpotLightDir

Aim a spotlight in the given direction.

pfGetSpotLightDir

Determine the direction a spotlight is pointing.

pfSpotLightCone

Set the width of a spotlight cone.

pfGetSpotLightCone

Find out the width of a spotlight cone.

pfLightPos

Set the position of a light.

pfGetLightPos

Get the position of a light.

pfIsLightOn

Determine whether a light is on or off.

pfLightOn

Enable a light; execute saved-up modifications to the light.

pfLightOff

Disable a light.


pfGeode Nodes

pfGeode is short for geometry node and is the primary node for defining geometry in libpf. A pfGeode contains a list of geometry structures called pfGeoSets, which are part of the IRIS Performer libpr library. pfGeoSets encapsulate graphics state and geometry and are described in the “Geometry Sets” section of Chapter 10, “libpr Basics.” It is important to understand that pfGeoSets are not nodes but are simply elements of a pfGeode.

Table 5-9 describes the functions for working with pfGeodes.

Table 5-9. pfGeode Functions

Function

Description

pfNewGeode

Create a pfGeode.

pfAddGSet

Add a pfGeoSet.

pfRemoveGSet

Remove a pfGeoSet.

pfInsertGSet

Insert a pfGeoSet.

pfReplaceGSet

Replace a pfGeoSet.

pfGetGSet

Supply a pointer to the specified pfGeoSet.

pfGetNumGSets

Determine how many pfGeoSets are in the given pfGeode.

Example 5-10 shows how to attach several pfGeoSets to a pfGeode.

Example 5-10. Adding pfGeoSets to a pfGeode

pfGeode *car1;
pfGeoSet *muffler, *frame, *windows, *seats, *tires;

muffler = read_in_muffler_geometry();
frame = read_in_frame_geometry();
seats = read_in_seat_geometry();
tires = read_in_tire_geometry();

pfAddGSet(car1, muffler);
pfAddGSet(car1, frame);
pfAddGSet(car1, seats);
...


pfText Nodes

A pfText node is libpf leaf node that contains a set of libpr pfStrings that should be rendered based on the libpf cull and draw traversals. In this sense a pfText is similar to a pfGeode except that it renders 3-dimensional text through the libpr pfString and pfFont mechanisms rather than rendering standard 3-dimensional geometry via libpr pfGeoSet and pfGeoState functionality. pfText nodes are useful for displaying 3-dimensional text and other collections of geometry from a fixed index list. Table 5-10 lists the major pfText functions.

Table 5-10. pfText Functions

Function

Description

pfNewText

Create a pfText.

pfAddString

Add a pfString.

pfRemoveString

Remove a pfString.

pfInsertString

Insert a pfString.

pfReplaceString

Replace a pfString.

pfGetString

Supply a pointer to the specified pfString.

pfGetNumStrings

Determine how many pfStrings are in the given pfText.

Using the pfText facility is easy. Example 5-11 shows how a pfFont is defined, how pfStrings are created that reference that font, and then how those pfStrings are added to a pfText node for display. See the description of pfStrings and pfFonts in Chapter 10, “libpr Basics,” for information on setting up individual strings to input into a pfText node

Example 5-11. Adding pfStrings to a pfText


int nStrings,i;
char tmpBuf[8192];
char fontName[128];
pfFont *fnt = NULL;
/* Create a new text node
pfText *txt = pfNewText();

/* Read in font using libpfdu utility function */
scanf(“%s”,fontName);
fnt = pfdLoadFont(“type1”,fontName,PFDFONT_EXTRUDED);

/* Cant render pfText or libpr pfString without a pfFont */
if (fnt == NULL)
    pfNotify(PFNFY_WARN,PFNFY_PRINT,
             ”No Such Font - %s\n”,fontName);

/* Read nStrings text strings from standard input and */
/* Attach them to a pfText */
scanf(“%d”,&nStrings);
for(i=0;i<nStrings;i++)
{
    char c;
    int j=0;
    int done = 0;
    pfString *curStr = NULL;

    while(done < 2) /* READ STRING - END on `||' */
    {
        c = getchar();
        if (c == `|')
            done++;
        else
            done = 0;
        tmpBuf[j++] = c;
    }
    tmpBuf[PF_MAX2(j-2,0)] = `\0';

/* Create new libpr pfString structure to attach to pfText */
    curStr = pfNewString(pfGetSharedArena());

/* Set the font for the libpr pfString */
    pfStringFont(curStr, fnt);

/* Assign the char string to the pfString */
    pfStringString(curStr, tmpBuf);

/* Add this libpr pfString to the pfText node */
/* Like adding a libpr pfGeoSet to a pfGeode */
    pfAddString(txt, curStr);
}
pfAddChild(SceneGroup, txt);

pfBillboard Nodes

A pfBillboard is a pfGeode that rotates its children's geometry to follow the view direction or the eyepoint. Billboards are useful for portraying complex objects that are roughly symmetrical in one or more axes. The billboard rotates to always present the same image to the viewer using far fewer polygons than a solid model uses. In this way, billboards reduce both transformation and pixel fill demands on the graphics subsystem at the expense of some additional host processing. A classic example is a textured billboard of a single quadrilateral representing a tree.

Because a pfBillboard is also a pfGeode, you can pass a pfBillboard argument to any pfGeode routine. To add geometry, call pfAddGSet() (see “pfGeode Nodes”). Each pfGeoSet in the pfBillboard is treated as a separate piece of billboard geometry; each one turns so that it always faces the eye point.

pfBillboards can be either constrained to rotate about an axis, as is done for a tree or a lamp post, or constrained only by a point, as when simulating a cloud or a puff of smoke. Specify the rotation mode by calling pfBboardMode(); specify the rotational axis by calling pfBboardAxis(). Since rotating the geometry to the eyepoint doesn't fully constrain the orientation of a point-rotating billboard, modes are available to use the additional degree of freedom to align the billboard in eye space or world space. Usually the normals of billboards are specified to be parallel to the rotational axis to avoid lighting anomalies.

pfFlatten() is highly recommended for billboards. If a billboard lies beneath a pfSCS or pfDCS, an additional transformation is done for each billboard. This can have a substantial performance impact on the cull process, where billboards are transformed.

Table 5-11 describes the functions for working with pfBillboards.

Table 5-11. pfBillboard Functions

Function

Description

pfNewBboard

Create a pfBillboard node.

pfBboardPos

Set a billboard's position.

pfGetBboardPos

Find out a billboard's position.

pfBboardAxis

Specify the rotation or alignment axis.

pfGetBboardAxis

Find out the rotation or alignment axis.

pfBboardMode

Specify a billboard's rotation type.

pfGetBboardMode

Find out a billboard's rotation type.

Example 5-12 demonstrates the construction of a pfBillboard node. The code can be found in /usr/share/Performer/src/pguide/libpf/C/billboard.c.

Example 5-12. Setting Up a pfBillboard

static pfVec2 BBTexCoords[] ={{0.0f, 0.0f},
                              {1.0f, 0.0f},
                              {1.0f, 1.0f},
                              {0.0f, 1.0f}};

static pfVec3 BBVertCoords[4] = /* XZ plane for pt bboards */
                         {{-0.5f, 0.0f, 0.0f},
                          { 0.5f, 0.0f, 0.0f},
                          { 0.5f, 0.0f, 1.0f},
                          {-0.5f, 0.0f, 1.0f}};

static pfVec3 BBAxes[4] = {{1.0f, 0.0f, 0.0f}, /* X */
                           {0.0f, 1.0f, 0.0f}, /* Y */
                           {0.0f, 0.0f, 1.0f}, /* Z */
                           {0.0f, 0.0f, 1.0f}}; /*world Zup*/

static int BBPrimLens[] = { 4 };

static pfVec4 BBColors[] = {{1.0, 1.0, 1.0, 1.0}};


/* Convert static data to pfMalloc'ed data */
static void*
memdup(void *mem, size_t bytes, void *arena)
{
    void *data = pfMalloc(bytes, arena);
    memcpy(data, mem, bytes);
    return data;
}

/* For pedagogical use only. Reasonable performance
 * requires more then one pfGeoSet per pfBillboard.
 */

pfBillboard*
MakeABill(pfVec3 pos, pfGeoState *gst, long bbType)
{
    pfGeoSet *gset;
	    pfGeoState *gstate;
    pfBillboard *bill;
    void *arena = pfGetSharedArena();

	    gset = pfNewGSet(arena);
	    gstate = pfNewGState(arena);

    pfGStateMode(gstate, PFSTATE_ENLIGHTING, PF_OFF);
	    pfGStateMode(gstate, PFSTATE_ENTEXTURE, PF_ON);
    /*.... Create/load texture map for billboard... */
    pfGStateAttr(gstate, PFSTATE_TEXTURE, texture);
    pfGSetGState(gset, gstate);

    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, 
	        memdup(BBVertCoords, sizeof(BBVertCoords), arena), 
        NULL);
    pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_PER_VERTEX, 
	        memdup(BBTexCoords, sizeof(BBTexCoords), arena), 
        NULL);

	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL,
	        memdup(BBColors, sizeof(BBColors), arena), 
        NULL);

    pfGSetPrimLengths(gset, 
        (int*)memdup(BBPrimLens, sizeof(BBPrimLens), arena));
    pfGSetPrimType(gset, PFGS_QUADS);
    pfGSetNumPrims(gset, 1);
    pfGSetGState(gset, gst);
 
    bill = pfNewBboard();
    switch (bbType)
    {
    case PF_X: /* axial rotate */
    case PF_Y:
    case PF_Z:
        pfBboardAxis(bill, BBAxes[bbType]);
        pfBboardMode(bill, PFBB_ROT, PFBB_AXIAL_ROT);
        break;
    case 3: /* point rotate */
        pfBboardAxis(bill, BBAxes[bbType]);
        pfBboardMode(bill, PFBB_ROT, PFBB_POINT_ROT_WORLD);
        break;
    }
    pfAddGSet(bill, gset);
    pfBboardPos(bill, 0, pos);

    return bill;
}


pfPartition Nodes

A pfPartition is a pfGroup that organizes the scene graphs of its children into a static data structure that can be more efficient for intersections. Currently, partitions are only useful for data that lies more or less on an XY plane, such as terrain. A pfPartition would therefore be inappropriate for a skyscraper model.

Partition construction comes in two phases. After a piece of the scene graph has been placed under the pfPartition, pfBuildPart() examines the spatial arrangement of geometry beneath the pfPartition and determines an appropriate origin and spacing for the grid. Because the search is exhaustive, this examination can be time-consuming the first time through. Once a good partitioning is determined, the search space can be restricted for future database loads using the partition attributes.

The second phase is invoked by pfUpdatePart(), which distributes the pfGeoSets under the pfPartition into cells in the spatial partition created by pfBuildPart(). pfUpdatePart() needs to be called if any geometry under the pfPartition node changes.

During intersection traversal, the segments in a pfSegSet (see “Intersection Requests: pfSegSets” in Chapter 6) are scan-converted onto the grid, yielding faster access to those pfGeoSets that potentially intersect the segment. A pfPartition can be made to function as a normal pfGroup during intersection traversal by OR-ing PFTRAV_IS_NO_PART into the intersection traversal mode in the pfSegSet.

Table 5-12 describes the functions for working with pfPartitions.

Table 5-12. pfPartition Functions

Function

Description

pfNewPart

Create a pfPartition.

pfPartVal

Set the desired pfPartition value.

pfGetPartVal

Find out the attributes of specified value.

pfPartAttr

Set the desired pfPartition attribute.

pfGetPartAttr

Find out the attributes of specified attribute.

pfBuildPart

Construct a spatial partitioning based on the attributes.

pfUpdatePart

Traverse the partition's children and incorporate changes.

pfGetPartType

Determine what kind of partition is being used.

Example 5-13 demonstrates setting up and using a pfPartition node.

Example 5-13. Setting Up a Partition

pfGroup *terrain;
pfPartition *partition;
pfScene *scene;

...

terrain = read_in_grid_aligned_terrain();

...

/* create a default partitioning of a terrain grid */
partition = pfNewPart();
pfAddChild(scene, partition);
pfAddChild(partition, terrain);
pfBuildPart(partition);

...

/* use the partitions to perform efficient intersections
 * of sets of segments with the terrain */
for(i = 0; i < numVehicles; i++)
    pfNodeIsectSegs(partition, vehicle_segment_set[i],
                    hit_struct);
...


Sample Program

The sample program shown in Example 5-14 demonstrates scene graph construction, shared instancing, and transformation inheritance. The program uses IRIS Performer objects and functions that are described fully in later chapters.

This program reads the names of two objects from the command line, although defaults are supplied if file names are not given. These files are loaded and a second instance of each object is created. In each case, this instance is made to orbit the original object, and the second pair are also placed in orbit around the first. This program is “inherit.c” and is part of the suite of IRIS Performer Programmer's Guide example programs.

Example 5-14. Inheritance demonstration program

/*
 * inherit.c - transform inheritance example
 */

#include <math.h>
#include <Performer/pf.h>
#include <Performer/pfdu.h>

int
main(int argc, char *argv[])
{
    pfPipe *pipe;
    pfPipeWindow *pw;
    pfScene *scene;
    pfChannel *chan;
    pfCoord view;
    float z, s, c;
    pfNode	 *model1, *model2;
    pfDCS	 *node1, *node2;
    pfDCS	 *dcs1, *dcs2, *dcs3, *dcs4;
    pfSphere sphere;
    char	 *file1, *file2;

    /* choose default objects of none specified */
    file1 = (argc > 1) ? argv[1] : “blob.nff”;
    file2 = (argc > 1) ? argv[1] : “torus.nff”;

    /* Initialize Performer */
    pfInit();

    pfFilePath(
    “.”
	    “:./data”
	    “:../data”
	    “:../../data”
	    “:../../../data”
	    “:../../../../data”
	    “:/usr/share/Performer/data”);

    /* Single thread for simplicity */
    pfMultiprocess(PFMP_DEFAULT);

    /* Load all loader DSO's before pfConfig() forks */
    pfdInitConverter(file1);
    pfdInitConverter(file2);

    /* Configure */
    pfConfig();

    /* Load the files */
    if ((model1 = pfdLoadFile(file1)) == NULL)
    {
        pfExit();
        exit(-1);
    }
    if ((model2 = pfdLoadFile(file2)) == NULL)
    {
        pfExit();
        	exit(-1);
    }

    /* scale models to unit size */
    node1 = pfNewDCS();
    pfAddChild(node1, model1);
    pfGetNodeBSphere(model1, &sphere);
    if (sphere.radius > 0.0f)
        pfDCSScale(node1, 1.0f/sphere.radius);

    node2 = pfNewDCS();
    pfAddChild(node2, model2);
    pfGetNodeBSphere(model2, &sphere);
    if (sphere.radius > 0.0f)
        pfDCSScale(node2, 1.0f/sphere.radius);

    /* Create the hierarchy */
    dcs4 = pfNewDCS();
    pfAddChild(dcs4, node1);
    pfDCSScale(dcs4, 0.5f);

    dcs3 = pfNewDCS();
    pfAddChild(dcs3, node1);
    pfAddChild(dcs3, dcs4);

    dcs1 = pfNewDCS();
    pfAddChild(dcs1, node2);

    dcs2 = pfNewDCS();
    pfAddChild(dcs2, node2);
    pfDCSScale(dcs2, 0.5f);
    pfAddChild(dcs1, dcs2);

    scene = pfNewScene();
    pfAddChild(scene, dcs1);
    pfAddChild(scene, dcs3);
    pfAddChild(scene, pfNewLSource());

    /* Configure and open GL window */
    pipe = pfGetPipe(0);
    pw = pfNewPWin(pipe);
    pfPWinType(pw, PFPWIN_TYPE_X);
    pfPWinName(pw, “IRIS Performer”);
    pfPWinOriginSize(pw, 0, 0, 500, 500);
    pfOpenPWin(pw);

    chan = pfNewChan(pipe);
    pfChanScene(chan, scene);

    pfSetVec3(view.xyz, 0.0f, 0.0f, 15.0f);
    pfSetVec3(view.hpr, 0.0f, -90.0f, 0.0f);
    pfChanView(chan, view.xyz, view.hpr);

    /* Loop through various transformations of the DCS's */
    for (z = 0.0f; z < 1084; z += 4.0f)
    {
        pfDCSRot(dcs1,
        (z < 360) ? (int) z % 360 : 0.0f,
        (z > 360 && z < 720) ? (int) z % 360 : 0.0f,
        (z > 720) ? (int) z % 360 : 0.0f);

        pfSinCos(z, &s, &c);
        pfDCSTrans(dcs2, 1.0f * c, 1.0f * s, 0.0f);

        pfDCSRot(dcs3, z, 0, 0);
        pfDCSTrans(dcs3, 4.0f * c, 4.0f * s, 4.0f * s);
        pfDCSRot(dcs4, 0, 0, z);
        pfDCSTrans(dcs4, 1.0f * c, 1.0f * s, 0.0f);

        pfFrame();
    }

    /* show objects static for three seconds */
    sleep(3);

    pfExit();
    exit(0);
}