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.|
A defined primitive type set with pfGSetPrimType().
The function pfGSetNumPrims() specifies the number of primitives in the pfGeoSet.
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.
To create a pfGeoSet from the shared memory arena, use the following line of code:
pfGeoSet *pfNewGSet(void *arena)
void pfGSetPrimType(pfGeoSet *gset, int type);
type is one of the primitives provided by OpenGL Performer:
PFGS_FLAT_* primitives are flat-shaded. PFGS_POLYS draws polygons of arbitrary vertex lengths.
Figure 7-1 shows some of these primitives.
The numbers in Figure 7-1 show the order in which the vertex attributes should appear in the attribute array.
void pfGSetNumPrims(pfGeoSet *gset, int num);
num is the number of primitives.
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 = 8; lengths = 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.|
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)
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.
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”.
void setAttr(int attr, int bind, void *alist, ushort *ilist);
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.
Per vertex— PFGS_PER_VERTEX
For all the primitives in the pfGeoSet—PFGS_OVERALL
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.
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.
Indexing can save system memory, but rendering performance is often lost.
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.
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.
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:
For more information on packed arrays on Windows systems, see following the examples:
Also, see Chapter 8, “Geometry ,” in the OpenGL Performer Programmer's Guide.
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.
The result is shown in Figure 7-5.
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 );
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.
Table 7-2 shows the libpf utilities that generate pfGeoSets of l arger geometric shapes.
Utilities That Create the Geometry
extern pfGeoSet * pfdNewSphere(int ntris, void *arena)
extern pfGeoSet * pfdNewCylinder(int ntris, void *arena)
extern pfGeoSet * pfdNewCone(int ntris, void *arena)
extern pfGeoSet * pfdNewCube(void *arena)
Unit square base,
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
Without end caps
extern pfGeoSet * pfdNewPipe (float botRadius, float topRadius, int ntris, void *arena)
Unit circle facing +Z,
extern pfGeoSet * pfdNewCircle (int ntris, void *arena)
Unit circle in Z=0
extern pfGeoSet * pfdNewRing (int ntris, void *arena);