Chapter 8. Geometry

All libpr geometry is defined by modular units that employ a flexible specification method. These basic groups of geometric primitives are termed pfGeoSets.

Geometry Sets

A pfGeoSet is a collection of geometry that shares certain characteristics. All items in a pfGeoSet must be of the same primitive type (whether they are points, lines, or triangles) and share the same set of attribute bindings (you cannot specify colors-per-vertex for some items and colors-per-primitive for others in the same pfGeoSet). A pfGeoSet forms primitives out of lists of attributes that may be either indexed or nonindexed. An indexed pfGeoSet uses a list of unsigned short integers to index an attribute list. (See “Attributes” for information about attributes and bindings.)

Indexing provides a more general mechanism for specifying geometry than hard-wired attribute lists and also has the potential for substantial memory savings as a result of shared attributes. Nonindexed pfGeoSets are sometimes easier to construct, usually a bit faster to render, and may save memory (since no extra space is needed for index lists) in situations where vertex sharing is not possible. A pfGeoSet must be either completely indexed or completely nonindexed; it is not valid to have some attributes indexed and others nonindexed.


Note: libpf applications can include pfGeoSets in the scene graph with the pfGeode (Geometry Node).

Table 8-1 lists a subset of the routines that manipulate pfGeoSets.

Table 8-1. pfGeoSet Routines

Routine

Description

pfNewGSet()

Create a new pfGeoSet.

pfDelete()

Delete a pfGeoSet.

pfCopy()

Copy a pfGeoSet.

pfGSetGState()

Specify the pfGeoState to be used.

pfGSetGStateIndex()

Specify the pfGeoState index to be used.

pfGSetNumPrims()

Specify the number of primitive items.

pfGSetPrimType()

Specify the type of primitive.

pfGSetPrimLengths()

Set the lengths array for strip primitives.

pfGetGSetPrimLength()

Get the length for the specified strip primitive.

pfGSetAttr()

Set the attribute bindings.

pfGSetMultiAttr()

Set multi-value attributes (for example, multi-texture coordinates).

pfGSetDrawMode()

Specify draw mode (for example, flat shading or wireframe).

pfGSetLineWidth()

Set the line width for line primitives.

pfGSetPntSize()

Set the point size for point primitives.

pfGSetHlight()

Specify highlighting type for drawing.

pfDrawGSet()

Draw a pfGeoSet.

pfGSetBBox()

Specify a bounding box for the geometry.

pfGSetIsectMask()

Specify an intersection mask for pfGSetIsectSegs().

pfGSetIsectSegs()

Intersect line segments with pfGeoSet geometry.

pfQueryGSet()

Determine the number of triangles or vertices.

pfPrint()

Print the pfGeoSet contents.


Primitive Types

All primitives within a given pfGeoSet must be of the same type. To set the type of all primitives in a pfGeoSet named gset, call pfGSetPrimType(gset, type). Table 8-2 lists the primitive type tokens, the primitive types that they represent, and the number of vertices in a coordinate list for that type of primitive.

Table 8-2. Geometry Primitives

Token

Primitive Type

Number of Vertices

PFGS_POINTS

Points

numPrims

PFGS_LINES

Independent line segments

2 * numPrims

PFGS_LINESTRIPS

Strips of connected lines

Sum of lengths array

PFGS_FLAT_LINESTRIPS

Strips of flat-shaded lines

Sum of lengths array

PFGS_TRIS

Independent triangles

3 * numPrims

PFGS_TRISTRIPS

Strips of connected triangles

Sum of lengths array

PFGS_FLAT_TRISTRIPS

Strips of flat-shaded triangles

Sum of lengths array

PFGS_TRIFANS

Fan of conected triangles

Sum of lengths array

PFGS_FLAT_TRIFANS

Fan of flat-shaded triangles

Sum of lengths array

PFGS_QUADS

Independent quadrilaterals

4 * numPrims

PFGS_POLYS

Independent polygons

Sum of lengths array

The parameters in the last column denote the following:

numPrims 

The number of primitive items in the pfGeoSet, as set by pfGSetNumPrims().

lengths 

The array of strip lengths in the pfGeoSet, as set by pfGSetPrimLengths() (note that length is measured here in terms of number of vertices).

Connected primitive types (line strips, triangle strips, and polygons) require a separate array that specifies the number of vertices in each primitive. Length is defined as the number of vertices in a strip for STRIP primitives and is the number of vertices in a polygon for the POLYS primitive type. The number of line segments in a line strip is numVerts – 1, while the number of triangles in a triangle strip and polygon is numVerts – 2. Use pfGSetPrimLengths() to set the length array for strip primitives.

The number of primitives in a pfGeoSet is specified by pfGSetNumPrims(gset, num). For strip and polygon primitives, num is the number of strips or polygons in gset.

pfGeoSet Draw Mode

In addition to the primitive type, pfGSetDrawMode() further defines how a primitive is drawn. Triangles, triangle strips, quadrilaterals, and polygons can be specified as either filled or as wireframe, where only the outline of the primitive is drawn. Use the PFGS_WIREFRAME argument to enable or disable wireframe mode. Another argument, PFGS_FLATSHADE, specifies that primitives should be shaded. If flat shading is enabled, each primitive or element in a strip is shaded with a single color.

PFGS_COMPILE_GL
 

At the next draw for each pfState, compile gset's geometry into a GL display list and subsequently render the display list.

PFGS_DRAW_GLOBJ
 

Select the rendering of an already created display list but do not force a recompile.

PFGS_PACKED_ATTRS
 

Use the gset's packed attribute arrays, set with the PFGS_PACKED_ATTRS to pfGSetAttr, to render geometry with GL vertex arrays.

The pfGeoSets are normally processed in immediate mode, which means that pfDrawGSet() sends attributes from the user-supplied attribute arrays to the Graphics Pipeline for rendering. However, this kind of processing is subject to some overhead, particularly if the pfGeoSet contains few primitives. In some cases it may help to use GL display lists (this is different from the libpr display list type pfDispList) or compiled mode. In compiled mode, pfGeoSet attributes are copied from the attribute lists into a special data structure called a display list during a compilation stage. This data structure is highly optimized for efficient transfer to the graphics hardware. However, compiled mode has some major disadvantages:

  • Compilation is usually costly.

  • A GL display list must be recompiled whenever its pfGeoSet's attributes change.

  • The GL display list uses a significant amount of extra host memory.

In general, immediate mode will offer excellent performance with minimal memory usage and no restrictions on attribute volatility, which is a key aspect in may advanced applications. Despite this, experimentation may show databases or machines where compiled mode offers a performance benefit.

To enable or disable compiled mode, call pfGSetDrawMode() with the PFGS_COMPILE_GL token. When enabled, compilation is delayed until the next time the pfGeoSet is drawn with pfDrawGSet(). Subsequent calls to pfDrawGSet() will then send the compiled pfGeoSet to the graphics hardware.

To select a display list to render, without recompiling it, use pfGSetDrawMode() with the token PFGS_DRAW_GLOBJ.

Packed Attributes

Packed attributes is an optimized way of sending formatted data to the graphics pipeline under OpenGL that does not incur the same memory overead or recompilation burden as GL display lists. To render geometry with packed attributes, use the pfGSetDrawMode( PFGS_PACKED_ATTRS) method when using OpenGL. This pfGSetAttr list includes the currently bound PER_VERTEX vertex attribute data packed into a single nonindexed array. When specifying a packed attribute array, the optional vertex attributes, colors, normals, and texture coordinates, can be NULL. This array, like the other attribute arrays, is then shared betweenOpenGL Performer, the GL, and accessible by the user. Optionally, you can put your vertex coordinates in this packed array but in this case the vertices must be duplicated in the normal coordinate array because vertex coordinate data is used internally for other nondrawing operations such as intersections and computation of bounding geometry. Packed attribute arrays also allow OpenGL Performer to extend the vertex attribute types accepted by pfGeoSets. There are several base formats that expect all currently bound attributes of specified data type (unsigned byte, short, or float) to be in the attribute array. Attributes specified by the format but not bound to vertices are assumed to not be present and the present data is packed with the data for each vertex starting on a 32-bit word-aligned boundary. Then, there are several derived formats that let you put some attribute data in the packed array while leaving the rest in the normal individual coordinate attribute arrays. Table 8-3 shows the different base formats supported.

Table 8-3. pfGeoSet PACKED_ATTR Formats

Format

Description

PFGS_PA_C4UBN3ST2FV3F

Accepts all currently bound coordinate attributes; colors are unsigned bytes; normals are shorts. Vertices are duplicated in the packed attribute array.

PFGS_PA_C4UBN3ST2F

Vertices are in the normal coordinate array.

PFGS_PA_C4UBT2F

Normals and vertices are in the normal coordinate array.

PFGS_PA_C4UBN3ST2SV3F

All bound coordinate attributes are in the packed attribute array. Colors are unsigned bytes, normals are shorts, and texture coordinates are unsigned shorts.

PFGS_PA_C4UBN3ST3FV3F

Texture coordinates are 3D floats.

PFGS_PA_C4UBN3ST3SV3F

Texture coordinates are 2D shorts.

To create packed attributes, you can use the utility pfuTravCreatePackedAttrs(), which traverses a scene graph to create packed attributes for pfGeoSets and, optionally, pfDelete redundant attribute arrays. This utility packs the pfGeoSet attributes using pfuFillGSetPackedAttrs(). Examples of packed attribute usage can be seen in /usr/share/Performer/src/pguide/libpr/C/packedattrs.c and in /usr/share/Performer/src/sample/C/perfly.c and /usr/share/Performer/src/sample/C++/perfly.C.

Primitive Connectivity

A pfGeoSet requires a coordinate array that specifies the world coordinate positions of primitive vertices. This array is either indexed or not, depending on whether a coordinate index list is supplied. If the index list is supplied, it is used to index the coordinate array; if not, the coordinate array is interpreted in a sequential order.

A pfGeoSet's primitive type dictates the connectivity from vertex to vertex to define geometry. Figure 8-1 shows a coordinate array consisting of four coordinates, A, B, C, and D, and the geometry resulting from different primitive types. This example uses index lists that index the coordinate array.


Note: Flat-shaded line strip and flat-shaded triangle strip primitives have the vertices listed in the same order as for the smooth-shaded varieties.

Figure 8-1. Primitives and Connectivity


Attributes

The definition of a primitive is not complete without attributes. In addition to a primitive type and count, a pfGeoSet references four attribute arrays (see Figure 8-2):

  • Colors (red, green, blue, alpha)

  • Normals (Nx, Ny, Nz)

  • Texture coordinates (S, T)—multiple arrays for multitexture.

  • Vertex coordinates (X, Y, Z)

(A pfGeoState is also associated with each pfGeoSet; see Chapter 9, “Graphics State” for details.) The four components listed above can be specified with pfGSetAttr(). Multivalue attributes (texture coordinates) can be specified using pfGSetMultiAttr() or pfGSetAttr(). Using zero as the index parameter for pfGSetMultiAttr() is equivalent to calling pfGSetAttr(). Attributes may be set in two ways: by indexed specification—using a pointer to an array of components and a pointer to an array of indices; or by direct specification—providing a NULL pointer for the indices, which indicates that the indices are sequential from the initial value of zero. The choice of indexed or direct components applies to an entire pfGeoSet; that is, all of the supplied components within one pfGeoSet must use the same method. However, you can emulate partially indexed pfGeoSets by using indexed specification and making each nonindexed attribute's index list be a singly shared “identity mapping” index array whose elements are 0, 1, 2, 3,…, N–1, where N is the largest number of attributes in any referencing pfGeoSet. (You can share the same array for all such emulated pfGeoSets.) The direct method avoids one level of indirection and may have a performance advantage compared with indexed specification for some combinations of CPUs and graphics subsystems.


Note: Use pfMalloc() to allocate your arrays of attribute data. This allows OpenGL Performer to reference-count the arrays and delete them when appropriate. It also allows you to easily put your attribute data into shared memory for multiprocessing by specifying an arena such as pfGetSharedArena() to pfMalloc(). While perhaps convenient, it is very dangerous to specify pointers to static data for pfGeoSet attributes. Early versions of OpenGL Performer permitted this but it is strongly discouraged and may have undefined and unfortunate consequences.

Attribute arrays can be created through pfFlux to support the multiprocessed generation of the vertex data for a dynamic object, such as ocean waves, or morphing geometry. pfFlux will automatically keep separate copies of data for separate proceses so that one process can generate data while another draws it. The pfFluxed buffer can be handed directly to pfGSetAttr() or pfGSetMultiAttr(). In fact, the entire pfGeoSet can be contained in a pfFlux. Index lists cannot be pfFluxed. See Chapter 16, “Dynamic Data”, for more information on pfFlux.

Figure 8-2. pfGeoSet Structure



Note: When using multiple texture-coordinate arrays, pfGeoSet recognizes texture-coordinate arrays starting at the first array (index of 0) and ending immediately before the first index with a NULL array. In other words, specifying texture-coordinate arrays using pfGSetMultiAttr() for indices 0, 1, and 3 is equivalent to specifying texture-coordinate arrays for only indices 0 and 1. When using pfTexGen to automatically generate texture coordinates for some texture units, the application should not interleave texture units with texture coordinates and texture units with pfTexGen. Texture units with texture coordinates should come before texture units with pfTexGen. This is an implementation limitation and may be removed in future releases.


Attribute Bindings

Attribute bindings specify where in the definition of a primitive an attribute has effect. You can leave a given attribute unspecified; otherwise, its binding location is one of the following:

  • Overall (one value for the entire pfGeoSet)

  • Per primitive

  • Per vertex

Only certain binding types are supported for some attribute types.

Table 8-4 shows the attribute bindings that are valid for each type of attribute.

Table 8-4. Attribute Bindings

Binding Token

Color

Normal

Texture Coordinate

Coordinate

PFGS_OVERALL

Yes

Yes

No

No

PFGS_PER_PRIM

Yes

Yes

No

No

PFGS_PER_VERTEX

Yes

Yes

Yes

Yes

PFGS_OFF

Yes

Yes

Yes

No

Attribute lists, index lists, and binding types are all set by pfGSetAttr().

For FLAT primitives ( PFGS_FLAT_TRISTRIPS, PFGS_FLAT_TRIFANS, PFGS_FLAT_LINESTRIPS), the PFGS_PER_VERTEX binding for normals and colors has slightly different meaning. In these cases, per-vertex colors and normals should not be specified for the first vertex in each line strip or for the first two vertices in each triangle strip since FLAT primitives use the last vertex of each line segment or triangle to compute shading.

Indexed Arrays

A cube has six 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 one primitive can refer to the same vertex, the number of vertices can be streamlined to 8. The way to get more than one primitive to refer to the same vertex is to use an index; three vertices of three 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 8-3.

Figure 8-3. Indexing Arrays


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

When to Index Attributes

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

The governing principle for whether to index attributes is how many vertices in a geometry are shared. Consider the following two examples in Figure 8-4, where each dot marks a vertex.

Figure 8-4. Deciding Whether to Index Attributes


In the triangle strip, each vertex is shared by two adjoining triangles. In the square, the same vertex is shared by eight triangles. Consider the task that is required to move these vertices when, for example, morphing the object. If the vertices were not indexed, in the square, the application would have to look up and alter eight 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, since each vertex is shared by only two triangles, the index look-up time would exceed the time it would take to simply update the vertices sequentially. In the case of the triangle strip, rendering is improved by handling the attributes sequentially.

The deciding factor governing whether to index attributes relates to the number of primitives that share the same attribute: 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.

pfGeoSet Operations

There are many operations you can perform on pfGeoSets. pfDrawGSet() “draws “ the indicated pfGeoSet by sending commands and data to the Geometry Pipeline, unless OpenGL Performer's display-list mode is in effect. In display-list mode, rather than sending the data to the pipeline, the current pfDispList “captures” the pfDrawGSet() command. The given pfGeoSet is then drawn along with the rest of the pfDispList with the pfDrawDList() command.

When the PFGS_COMPILE_GL mode of a pfGeoSet is not active ( pfGSetDrawMode()), pfDrawGSet() uses rendering loops tuned for each primitive type and attribute binding combination to reduce CPU overhead in transferring the geometry data to the hardware pipeline. Otherwise, pfDrawGSet() sends a special, compiled data structure.

Table 8-1 lists other operations that you can perform on pfGeoSets. pfCopy() does a shallow copy, copying the source pfGeoSet's attribute arrays by reference and incrementing their reference counts. pfDelete() frees the memory of a pfGeoSet and its attribute arrays (if those arrays were allocated with pfMalloc() and provided their reference counts reach zero). pfPrint() is strictly a debugging utility and will print a pfGeoSet's contents to a specified destination. pfGSetIsectSegs() allows intersection testing of line segments against the geometry in a pfGeoSet; see “Intersecting with pfGeoSets” in Chapter 19 for more information on that function.

3D Text

In addition to the pfGeoSet, libpr offers two other primitives which together are useful for rendering a specific type of geometry—3D characters. See Chapter 3, “Nodes and Node Types” and the description for pfText nodes for an example of how to set up the 3D text within the context of libpf.

pfFont

The basic primitive supporting text rendering is the libpr pfFont primitive. A pfFont is essentially a collection of pfGeoSets in which each pfGeoSet represents one character of a particular font. pfFont also contain metric data, such as a per-character spacing, the 3D escapement offset used to increment a text `cursor' after the character has been drawn. Thus, pfFont maintains all of the information that is necessary to draw any and all valid characters of a font. However, note that pfFonts are passive and have little functionality on their own; for example, you cannot draw a pfFont—it simply provides the character set for the next higher-level text data object, the pfString.

Table 8-5 lists some routines that are used with a pfFont.

Table 8-5. pfFont Routines

Routine

Description

pfNewFont()

Create a new pfFont.

pfDelete()

Delete a pfFont.

pfFontCharGSet()

Set the pfGeoSet to be used for a specific character of this pfFont.

pfFontCharSpacing()

Set the 3D spacing to be used to update a text cursor after this character has been rendered.

pfFontMode()

Specify a particular mode for this pfFont.

Valid modes:

PFFONT_CHAR_SPACING—Specify whether to use fixed or variable spacings for all characters of a pfFont. Possible values are PFFONT_CHAR_SPACING_FIXED and PFFONT_CHAR_SPACING_VARIABLE, the latter being the default.

PFFONT_NUM_CHARS—Specify how many characters are in this font.

PFFONT_RETURN_CHAR—Specify the index of the character that is considered a `return' character and thus relevant to line justification.

pfFontAttr()

Specify a particular attribute of this pfFont.

Valid attributes:

PFFONT_NAME—Name of this font.

PFFONT_GSTATE—pfGeoState to be used when rendering this font.

PFFONT_BBOX—Bounding box that bounds each individual character.

PFFONT_SPACING—Set the overall character spacing if this is a fixed width font (also the spacing used if one has not been set for a particular character).


Example 8-1. Loading Characters into a pfFont

/* Setting up a pfFont */
pfFont *ReadFont(void)
{
    pfFont *fnt = pfNewFont(pfGetSharedArena());
    for(i=0;i<numCharacters;i++)
    {
        pfGeoSet* gset = getCharGSet(i);
        pfVec3* spacing = getCharSpacing(i);
        
        pfFontCharGSet(fnt, i, gset);
        pfFontCharSpacing(fnt, i, spacing);
    }
}


pfString

Simple rendering of 3D text can be done using a pfString. A pfString is an array of font indices stored as 8-bit bytes, 16-bit shorts, or 32-bit integers. Each element of the array contains an index to a particular character of a pfFont structure. A pfString can not be drawn until it has been associated with a pfFont object with a call to pfStringFont(). To render a pfString once it references a pfFont, call the function pfDrawString().

The pfString class supports the notion of `flattening' to trade off memory for faster processing time. This causes individual, noninstanced geometry to be used for each character, eliminating the cost of translating the text cursor between each character when drawing the pfString.

Example 8-2 illustrates how to set up and draw a pfString.

Example 8-2. Setting Up and Drawing a pfString

/* Create a string a rotate it for 2.5 seconds */
void
LoadAndDrawString(const char *text)
{
    pfFont *myfont = ReadMyFont();
    pfString *str = pfNewString(NULL);
    pfMatrix mat;
    float start,t;
    
    /* Use myfont as the 3-d font for this string */
    pfStringFont(str, fnt);
    
    /* Center String */
    pfStringMode(str, PFSTR_JUSTIFY, PFSTR_MIDDLE);
    
    /* Color String is Red */
    pfStringColor(str, 1.0f, 0.0f, 0.0f, 1.0f);
    
    /* Set the text of the string */
    pfStringString(str, text);
    
    /* Obtain a transform matrix to place this string */
    GetTheMatrixToPlaceTheString(mat);
    pfStringMat(str, &mat);
    
    /* optimize for draw time by flattening the transforms */
    pfFlattenString(str);
    
    /* Twirl text for 2.5 seconds */
    start = pfGetTime();
    do
    {
        pfVec4 clr;
        pfSetVec4(clr, 0.0f, 0.0f, 0.0f, 1.0f);
        
        /* Clear the screen to black */
        pfClear(PFCL_COLOR|PFCL_DEPTH, clr);
        
        t = (pfGetTime() - start)/2.5f;
        t = PF_MIN2(t, 1.0f);
        
        pfMakeRotMat(mat, t * 315.0f, 1.0f, 0.0f, 0.0f);
        pfPostRotMat(mat, mat, t * 720.0f, 0.0f, 1.0f, 0.0f);
        
        t *= t;
        pfPostTransMat(mat, mat, 0.0f, 
            150.0f * t + (1.0f - t) * 800.0f, 0.0f);
    
        pfPushMatrix();
        pfMultMatrix(mat);
        
        /* DRAW THE INPUT STRING */
        pfDrawString(str);
        
        pfPopMatrix();
        
        pfSwapWinBuffers(pfGetCurWin());
    } while(t < 2.5f);
}

Table 8-6 lists the key routines used to manage pfStrings.

Table 8-6. pfString Routines

Routine

Description

pfNewString()

Create a new pfString.

pfDelete()

Delete a pfString.

pfStringFont()

Set the pfFont to use when drawing this pfString.

pfStringString()

Set the character array that this pfString will represent or render.

pfDrawString()

Draw this pfString.

pfFlattenString()

Flatten all positional translations and the current specification matrix into individual pfGeoSets so that more memory is used, but no matrix transforms or translates have to be done between each character of the pfString.

pfStringColor()

Set the color of the pfString.

pfStringMode()

Specify a particular mode for this pfString.

Valid modes:

PFSTR_JUSTIFY — Sets the line justification and has the following possible values: PFSTR_FIRST or PFSTR_LEFT, PFSTR_MIDDLE or PFSTR_CENTER, and PFSTR_LAST or PFSTR_RIGHT.

PFSTR_CHAR_SIZE — Sets the number of bytes per character in the input string and has the following possible values: PFSTR_CHAR, PFSTR_SHORT, PFSTR_INT.

pfStringMat()

Specify a transform matrix that affects the entire character string when the pfString is drawn.

pfStringSpacingScale()

Specify a scale factor for the escapement translations that happen after each character is drawn. This routine is useful for changing the spacing between characters and even between lines.