Chapter 7. Creating Geometry with pfGeoSets

OpenGL Performer provides pfGeoSets for holding low-level geometric descriptions of objects. A pfGeoSet is a collection of like-geometric primitives, such as points, line segments, triangle strips, triangles, or triangle fans. The primitives in a pfGeoSet share a state description for texture, material, and other surface attributes in a pfGeoState.

By combining multiple pfGeoSets you can create a complex object, such as a house, car, or terrain. By manipulating the vertices of pfGeoSet elements, you can create a dynamic object, such as ocean waves.

This chapter describes how to create geometric surfaces and place them in the scene graph in the following sections:

For more information about pfGeoState, see Chapter 8, “Specifying the Appearance of Geometry with pfState and pfGeoState”.


Note: OpenGL Performer provides a richer alternative to the pfGeoSet class: the pfGeoArray class. To find out how it is used, see the OpenGL Performer Programmer's Guide.


pfGeoSet Overview

A pfGeoSet is a collection of one or more like primitives, such as lines or triangles. These primitives are arranged in a way that forms a geometric surface.

pfGeoSets contain:

  • A defined primitive type set with pfGSetPrimType().

  • The function pfGSetNumPrims() specifies the number of primitives in the pfGeoSet.

  • For stripped primitives, such as triangle strips, the number of vertices in each strip is set with a lengths array using pfGSetPrimLengths().

  • Vertex coordinate attribute lists, with optional corresponding index lists set with pfGSetAttr().

  • The kind of attribute binding, also specified in pfGSetAttr(), determines whether attributes are specified as follows:

    • Per vertex: PFGS_PER_VERTEX

    • Per primitive: PFGS_PER_PRIM

    • For all the primitives in the pfGeoSet: PFGS_OVERALL

  • A reference to a pfGeoState specified with pfGSetGState(), which specifies the surface appearance (lighting material, texture, transparency, etc.) of the geometry.

A simple example of pfGeoSet creation and rendering demonstrating the concepts in this chapter can be found at /usr/share/Performer/src/pguide/libpr/gset.c on IRIX and Linux systems and at %PFROOT%\Src\pguide\libpr\gset.c on Windows systems.

For information about optimizing pfGeoSet performance, see “Optimizing Geometry Performance” in Chapter 15.

Creating a pfGeoSet

The following sections describe how to create a pfGeoSet.

Creating a pfGeoSet Object

To create a pfGeoSet from the shared memory arena, use the following line of code:

pfGeoSet *pfNewGSet(void *arena)

Setting the Primitive Type

Use the following pfGeoSet method to specify the type of primitive in the pfGeoSet:

void pfGSetPrimType(pfGeoSet *gset, int type);

type is one of the primitives provided by OpenGL Performer:

  • PFGS_POINTS

  • PFGS_LINES

  • PFGS_LINESTRIPS

  • PFGS_FLAT_LINESTRIPS

  • PFGS_TRIS

  • PFGS_QUADS

  • PFGS_TRISTRIPS

  • PFGS_FLAT_TRISTRIPS

  • PFGS_TRIFANS

  • PFGS_FLAT_TRIFANS

  • PFGS_POLYS

PFGS_FLAT_* primitives are flat-shaded. PFGS_POLYS draws polygons of arbitrary vertex lengths.

Figure 7-1 shows some of these primitives.

Figure 7-1. Primitives

Primitives

The numbers in Figure 7-1 show the order in which the vertex attributes should appear in the attribute array.

Setting the Number of Primitives

Use the following pfGeoSet method to specify the number of primitives in the pfGeoSet:

void pfGSetNumPrims(pfGeoSet *gset, int num);

num is the number of primitives.

Setting the Number of Vertices Per Stripped Primitive

When using one of the following pfGeoSet primitives, which have an arbitrary number of vertices, you must define the number of vertices of each primitive in the pfGeoSet:

  • PFGS_LINESTRIPS

  • PFGS_FLAT_LINESTRIPS

  • PFGS_TRISTRIPS

  • PFGS_FLAT_TRISTRIPS

  • PFGS_TRIFANS

  • PFGS_FLAT_TRIFANS

  • PFGS_POLYS

Use pfGSetPrimLengths() to specify the number of vertices of each primitive in the pfGeoSet:

void pfGSetPrimLengths(pfGeoSet* gset, int *lengths);

lengths is an array of the number of strips in a pfGeoSet. Each element of the lengths array is the number of vertices in a corresponding strip. For example:

lengths[0] = 8;
lengths[1] = 5;

These lines of code mean that the number 0 primitive has 8 vertices, and the number 1 primitive has 5 vertices. Use pfGetGSetPrimLength() to return the length of an individual primitive from the lengths array.


Note: pfGetGSetPrimLength() checks for NULL or negative lengths.


Attributes of pfGeoSet Primitives

The vertex information of the primitives in a pfGeoSet are described by attribute lists. Each element in an attribute list contains information for a single vertex. The vertex attributes for all vertices of all primitives of a pfGeoSet are stored in separate arrays, according to the following attribute types:

  • Vertices—pfVec3 coordinates (required)

  • Colors—pfVec4 colors

  • Normals—pfVec3 normals

  • Texture coordinates—pfVec2 texture coordinates

A pfGeoSet has at least one array pfVec3 of vertex coordinates. Optional attributes include colors, normals, and texture coordinates. Arrays holding these attributes for the vertices of the pfGeoSet are specified for the pfGeoSet using pfGSetAttr(). Figure 7-2 shows the arrays of attributes.

Figure 7-2. Arrays of Stripped Primitives

Arrays of Stripped Primitives

Indexes for indexed attributes are in separate index arrays. For more information about indexed attributes, see “Indexed Arr ays”.

You can also put all vertex attributes of a pfGeoSet in a single-packed attribute array. You may use packed attribute arrays for performance reasons or for specifying specialized custom formats of data. For more information about packed attribute arrays, see “Packed Attributes”.

Setting the Attributes

To set the attributes of a pfGeoSet, use pfGSetAttr() as follows:

void setAttr(int attr, int bind, void *alist, ushort *ilist);

attr specifies the attribute array to set. The tokens for the different attribute lists are:

  • PFGS_COLOR4

  • PFGS_NORMAL3

  • PFGS_TEXCOORD2

  • PFGS_COORD3

  • PFGS_PACKED_ATTRS

bind is the binding type. The tokens are listed in Table 7-1.

alist is a pointer to an attribute array for appropriate, corresponding data.

ilist is a pointer to an index array for used to access the attribute array.

Attribute Bindings

A ttribute bindings specify whether attributes are as follows:

  • Per vertex— PFGS_PER_VERTEX

  • Per primitive—PFGS_PER_PRIM

  • For all the primitives in the pfGeoSet—PFGS_OVERALL

  • Unspecified—PFGS_OFF

For example, you can specify the following:

  • A unique color for each vertex (PFGS_PER_VERTEX).

  • A unique color for each primitive (PFGS_PER_PRIM).

  • One color for all primitives in the pfGeoSet (PFGS_OVERALL).

  • An unspecified color (PFGS_OFF).

Table 7-1 shows the possible bindings per attribute type.

Table 7-1. Possible Bindings Per Attribute Type


Binding Type


Colors


Normals

Texture Coordinates


Vertices

PFGS_OFF

Yes

Yes

Yes

No

PFGS_OVERALL

Yes

Yes

No

No

PFGS_PER_PRIM

Yes

Yes

No

No

PFGS_PER_VERTEX

Yes

Yes

Yes

Yes


Indexed Arr ays

A cube has 6 sides; together those sides have 24 vertices. In a vertex array, you could specify the primitives in the cube using 24 vertices. However, most of those vertices overlap. If more than 1 primitive can refer to the same vertex, the number of vertices can be streamlined to 8. To get more than 1 primitive to refer to the same vertex, use an index; 3 vertices of 3 primitives use the same index, which points to the same vertex information. Adding the index array adds an extra step in the determination of the attribute, as shown in Figure 7-3.

Figure 7-3. Indexing Arrays

Indexing Arrays

Indexing can save system memory, but rendering performance is often lost.

Whether or not attributes should be indexed depends on how many vertices in a geometry are shared:

  • If attributes are shared by many primitives, the attributes should be indexed.

  • If attributes are not shared by many primitives, the attributes should be handled sequentially.

Consider the following two examples in Figure 7-4, in which each dot marks a vertex.

Figure 7-4. Deciding whether to Index Attributes

Deciding whether to Index Attributes

In the triangle strip, each vertex is shared by 2 adjoining triangles. In the square, the same vertex is shared by 8 triangles. Consider moving these vertices when, for example, morphing the object. If the vertices were not indexed in the square, the application would have to search for and alter 8 triangles to change one vertex. In the case of the square, it is much more efficient to index the attributes.

On the other hand, if the attributes in the triangle strip were indexed, because each vertex is shared by only 2 triangles, the index search time would exceed the time required to simply update the vertices sequentially. In the case of the triangle strip, rendering is improved by handling the attributes sequentially.

The choice of using indexed or sequential attributes applies to all of the primitives in a pfGeoSet. All of the primitives within one pfGeoSet must be referenced sequentially or by index; you cannot mix the two.

Packed Attributes

Using p acked attributes is an optimized way of sending formatted data to the graphics pipeline under OpenGL operation. Using packed attributes can help host traversal performance because they remove subroutine call overhead. Packed attributes can also reduce memory usage because they allow for the format specification of attributes such as normals, texture coordinates as floats, and colors as unsigned bytes. Some small additional overhead might be incurred by the geometry subsystem of the graphics pipeline, which has to unpack the data.

The packed attribute array holds the currently bound per-vertex attribute data packed into a single non-indexed array and is specified with the matching format of the data with pfGSetAttr() as follows:

pfGSetAttr(gset, PFGS_PACKED_ATTRS, PFGS_PA_C4UBN3ST2F /*the format*/);

V ertex coordinate attributes can be placed in this array and do not need to be duplicated in their regular arrays. Specify NULL for the attribute list to pfGSetAttr(). Vertex coordinates themselves must always be provided in the normal vertex coordinate list. They can, based on the packed format, be duplicated in the packed array.

To create packed attributes, you can use the utility pfuTravCreatePackedAttrs(), which traverses a scene graph to create packed attributes according to the specified format for pfGeoSets and, optionally, pfDelete redundant attribute arrays. This utility packs the pfGeoSet attributes using pfuFillGSetPackedAttrs(). To then render geometry with packed attributes, use the pfGSetDrawMode(PFGS_PACKED_ATTRS) method when using OpenGL.

For more information on packed arrays on IRIX and Linux systems, see the following examples:

  •  /usr/share/Performer/src/pguide/libpr/C/packedattrs.c

  •  /usr/share/Performer/src/sample/C/perfly.c

  •  /usr/share/Performer/src/sample/C++/perfly/perfly.C

For more information on packed arrays on Windows systems, see following the examples:

  • %PFROOT%\Src\pguide\libpr\C\packedattrs.c

  • %PFROOT%\Src\sample\C\perfly.c

  • %PFROOT%\Src\sample\C++\perfly\perfly.C

Also, see Chapter 8, “Geometry ,” in the OpenGL Performer Programmer's Guide.

Drawing and Printing a pfGeoSet

pfGeoSets are the lowest-level OpenGL Performer object that can be rendered. To directly draw a pfGeoSet, use pfDrawGset().

pfGeoSets are pfObjects, so they can have their contents printed for debugging with different levels of verbosity via the pfObject routine, pfPrint().You can also use other pfObject methods, such as pfCopy() and pfDelete(), can also be used with pfGeoSets.

Placing Geometry in a Scene Graph

You incorporate pfGeoSets into scene graphs using a pfGeode leaf node. A single pfGeode node can have multiple pfGeoSets associated with it if you use the pfAddGSet() method.

To place geometry in a scene graph, follow these steps:

  1. Create a pfGeoSet with pfNewGSet().

  2. Attach the pfGeoSet to a pfGeoState using pfGSetGstate().

  3. Add the pfGeoSet to a pfGeode node in a scene graph using pfAddGSet().

The result is shown in Figure 7-5.

Figure 7-5. Geometry Objects

Geometry Objects

To create a pfGeode node, associate two pfGeoSets with it, and attach the node to the scene graph using code similar to the following:

pfGeode *geode = pfNewGeode();
pfAddSet( geode, gSet1 );
pfAddSet( geode, gSet2 );
pfAddChild( GeodesParentNode, geode );

The structure of a scene graph impacts the performance of your application. For more information, see “Arrangement of Nodes” in Chapter 6.

Creating Common Geometric Objects

libpfdu provides routines to generate pfGeoSets for common shapes, including

  • Sphere

  • Cube

  • Pyramid

  • Cylinder

  • Cone

When creating these shapes, you can specify the number of triangles that comprise them. For example, the following method sets the number of triangles comprising the sphere to 200:

pfGeoSet *sphere = pfdNewSphere(200, arena);

This method returns the number of vertices and normals in the shape.

The more triangles, the smoother the curves but the slower the rendering; fewer triangles allow faster rendering but produce more jagged curves.

Utilities to Create Common Geometric Objects

Table 7-2 shows the libpf utilities that generate pfGeoSets of l arger geometric shapes.

Table 7-2. Common Geometric Objects

Geometry

Properties

Utilities That Create the Geometry

Sphere

Unit

extern pfGeoSet * pfdNewSphere(int ntris, void *arena)

 

Radius=1,
from Z=-1 to Z=1

extern pfGeoSet * pfdNewCylinder(int ntris, void *arena)

 

Radius=1,
from Z=0 to Z=1

extern pfGeoSet * pfdNewCone(int ntris, void *arena)

Cube

Unit

extern pfGeoSet * pfdNewCube(void *arena)

Pyramid

Unit square base,
from Z=0 to Z=1

extern pfGeoSet * pfdNewPyramid (void *arena)

 

Z=0 to Z=1

extern pfGeoSet * pfdNewArrow (int ntris, void *arena)

 

Z=-1 to Z=1

extern pfGeoSet * pfdNewDoubleArrow (int ntris, void *arena)

Cylinder

Without end caps
and variable radii

extern pfGeoSet * pfdNewPipe (float botRadius, float topRadius, int ntris, void *arena)

Circle

Unit circle facing +Z, filled

extern pfGeoSet * pfdNewCircle (int ntris, void *arena)

 

Unit circle in Z=0 plane, lines

extern pfGeoSet * pfdNewRing (int ntris, void *arena);