Chapter 19. Dynamic Data

Making your data dynamic allows your scenes to change. Geometries can change location, orientation, color, texture, or change into different things altogether. This chapter explains how to create dynamic structures that can generate and manipulate their own dynamic data using pfFlux, pfEngine, and pfFCS. A pfEngine computes the changes and a pfFlux is a container for the output of the pfEngines.

pfFlux

A pfFlux is a container for dynamic data that enables multiprocessed generation and use of data in the scene graph. A pfFlux internally consists of multiple buffers of data, each of which is associated with a frame number. The numbering allows multiple processes to each have a copy of the data containing the frame on which they are working. Multiple reader processes can share a copy of the current results or use the frame of results that is appropriate for that process. Figure 19-1 illustrates how pfFlux and processes use frame numbers.

Figure 19-1. How pfFlux and Processes Use Frame Numbers

How pfFlux and Processes Use Frame Numbers

The pfFlux in the figure has four buffers. Each buffer shows the frame number associated with it. The figure shows four processes reading from the pfFlux. The process with frame 12 reads from a buffer with frame 12—the newest available buffer and an exact match to the process frame number. The process with frame number 7 reads from a buffer with frame 5–the latest buffer that is not too new for frame 7. Both processes with frame 4 share a buffer with frame number 4—an exact match.

One popular use of a pfFlux is to manage the attributes of a pfGeoSet. Each of the attribute lists of a pfGeoSet can independently be a pfFlux. This means that an application can change the position of geometry vertices on the fly in a safe, multiprocess manner.

Other uses include using a pfFlux as a matrix of a pfFCS node. This enables modifying the transformation of some target geometry from an asynchronous process in a safe, multiprocess manner.

Creating and Deleting a pfFlux

The  pfNewFlux() funtion creates and returns a handle to a pfFlux. Each pfFlux buffer is a pfFluxMemory. When you create a pfFlux, you specify the number of buffers it contains as well as the size of each buffer. The pfFlux buffers are automatically allocated from the same arena as the parent pfFlux. When creating a pfFlux, you must specify how many buffers this pfFlux contains. Instead of specifying an exact number, you may specify the symbol PFFLUX_DEFAULT_NUM_BUFFERS. In this case, the system will pick an appropriate number of buffers based on the multiprocess configuration of your application (set by pfMultiprocess). You can globally redefine this default number of buffers for successive creation of pfFluxes using pfFluxDefaultNumBuffers(). If you change the default number of buffers, the effect takes place only for pfFluxes created after the change.


Note: OpenGL Performer sets the default number of pfFlux buffers when pfConfig() is called according to the number and type of processes requested with pfMultiprocess(). Generally, the number of buffers is equal to one more than the number of running processes that typically might generate pfFlux data or use pfFluxed results. LPOINT processes are not included in the count; but DBASE, ISECT, and COMPUTE processes and additional graphic pipes, creating additional DRAW and possibly CULL processes, do add to the default number of pfFlux buffers.

You can return the number of buffers in a pfFlux and the number of bytes in the buffers using the pfGetFluxDefaultNumBuffers() and pfGetFluxDataSize() routines, respectively.

To delete a pfFlux, as with all pfObjects, use pfDelete().

Initializing the Buffers

Generally, pfFlux buffers do not require initialization because they are fully recomputed for every frame. One case where you would want to initialize them is when the data in the buffers is static. For example, if you have a pfFlux of coordinates where most do not change, rather than continuously updating this static data, it is far more efficient to initialize the buffers. You cannot depend on the order in which you get pfFlux buffers at run time and coordinates in the buffer cannot assume results from the previous frame. If an element in the buffer is ever to be dynamic, it should always be recomputed.

OpenGL Performer provides two ways of initializing or setting the data held in the pfFlux buffers:

  • pfFluxInitData() to provide a template

  • pfFluxCallDataFunc() to provide a callback function

You can immediately initialize all pfFlux buffers by calling the pfFluxInitData() routine and providing a template data buffer that will be directly copied into the pfFlux buffers.

In the argument of pfFluxCallDataFunc(), you pass a pointer to data and a pointer to a callback function that operates on the buffers in a pfFlux. This function will be immediately called on each buffer of the pfFlux.

An initialization callback can also be provided when the pfFlux is created as shown in the following:

pfFlux *flux = new pfFlux(initFunc, PFFLUX_DEFAULT_NUM_BUFFERS);

The function will be called for each flux buffer. If the pfFlux is not created with enough buffers and a new pfFluxMemory buffer must be created at run time, the callback function will be called. For better and more reliable performance, you generally want to ensure that you have given yourself enough buffers up front.

pfFlux Buffers

A pfFlux buffer is of type pfFluxMemory. Each buffer consists of the following:

  • Header

  • Data

The header portion consists of the following:

  • Pointer to the data portion of the pfFluxMemory

  • Frame number set automatically or explicitly by pfFluxFrame()

  • Set of flags, including read and write

The data portion of a pfFluxMemory contains one frame of information. The pfGetFluxMemory() function returns a pointer to the data portion of a pfFluxMemory. Figure 19-2 demonstrates these relationships.

Figure 19-2. pfFlux Buffer Structure

pfFlux Buffer Structure

To return the parent pfFlux containing the buffer, use  pfGetFFlux(), which returns NULL if the specified data is not part of a pfFlux. This is useful to find out, for example, if an attribute buffer of a pfGeoSet is a pfFlux.

At a given moment, a pfFlux buffer is either readable or writable, but never both. When a buffer is writable, its data can be changed. When a buffer is readable, its data should not be changed because there might be other processes reading that same buffer that would then be immediately affected by any changes. For performance reasons, a buffer marked readable is not locked.

Reading pfFlux Buffers

To get the current results for reading from a pfFlux, you can use the pfGetFluxCurData() method with code similar to the following C example:

pfVec3 *cur_verts;
 
cur_verts = (pfVec3*)pfGetFluxCurData(flux);

In this example, pfGetFluxCurData() does the following:

  • Finds a readable buffer in the pfFlux, flux, whose frame number is the closest to the current flux frame number but not greater then the current flux frame number.

  • Returns a pointer, cur_verts, to that buffer.

If you have an old copy of a data buffer from a previous call to pfGetFluxCurData() and now just want to update to a new version and no longer have a pointer to the parent flux, you can use pfGetFluxWritableData() and provide your data pointer. For performance reasons, it is better to save your pfFlux pointer and not depend on this convenience.

When pfGetFluxCurData() is called, it is expected to hold previous results of computation. This computation might be done explicitly by your code in another process or might be done automatically if the pfFlux is the destination of a pfEngine. Additionally, the pfEngine computation might be triggered immediately if the pfFlux is a pfEngine destination and if the pfFlux mode PFFLUX_ON_DEMAND mode is set by the pfFluxMode() function and if the client pfEngine sources are dirty. pfEngine details are discussed more later in this chapter.

Writing to pfFlux Buffers

When you want to write data to a pfFlux buffer, use the pfGetFluxWritableData() function with code similar to the following C++ example:

pfVec3 *verts;
int i, num_verts;
 
verts = (pfVec3*)flux->getWritableData();
 
/* Set all verts to 1.0, 2.0, 3.0 */
num_verts = flux->getDataSize() / sizeof(pfVec3);
for (i = 0; i < num_verts; i++)
    verts[i].set(1.0f, 2.0f, 3.0f);
 
flux->writeComplete();

When pfGetFluxWritableData() is called, pfFlux searches for the buffer whose frame number is equal to the pfFlux frame number. There are three possible results:

  • If there is a match and the buffer is writable, pfGetFluxWritableData() returns a pointer to that buffer.

  • If there is a match but the buffer is readable, pfGetFluxWritableData() causes one of the following actions to occur, according to whether the PFLUX_WRITE_ONCE mode specified in pfFluxMode() is set to the following:

    PF_ON 

    The pfGetFluxWritableData() function returns NULL if there is already a readable buffer with a frame number that matches the current flux frame number.

    PF_OFF 

    The pfGetFluxWritableData() function returns a pointer to the readable data buffer.

  • If there is no match, an unused buffer is made writable and its frame buffer number is set to that of the current pfFlux frame number.

If pfGetFluxWritableData() is called again for the same frame and the PFFLUX_WRITE_ONCE mode is PF_ON (off by default), information is not copied again into the same buffer; instead, NULL is returned. In this way, the mode can prevent modified data from being destroyed by a second call to pfGetFluxWritableData() and the NULL return value can be used to avoid needlessly recomputing unused data.

The pfFluxWriteComplete() function should be called when computation for a writable pfFlux buffer is complete. This method changes the specified buffer from writable to readable.


Note: OpenGL Performer computes a cache of geometric attributes for each pfGeoSet. It uses this cache when intersecting line segments with the pfGeoSet. When using a pfFlux as the PFGS_COORD3 attribute of a pfGeoSet, the cache is not notified if pfFlux changes; so, intersection is incorrect. In order to avoid this problem, the application should always disable caching in pfGeoSets with pfFluxed PFGS_COORD3 attributes. Use the pfGeoSet function pfGSetIsectMask(gset, PFTRAV_IS_UNCACHE, ...) to disable caching.


Coordinating pfFlux and Connected pfEngines

The pfFluxes maintain pointers to the following:

  • pfEngines, called source engines, whose destinations are this pfFlux.

  • A list of pfEngines, called client engines, that use this pfFlux as a source.

To return a handle to these engines, use pfGetFluxSrcEngine() and pfGetFluxClientEngine(), respectively.

To return the number of connected source and client pfEngines, use pfGetFluxNumSrcEngines() and pfGetFluxNumClientEngines(), respectively.

The pfFluxWriteComplete() function triggers client pfEngine evaluation according to whether the PFFLUX_PUSH mode specified in pfFluxMode() is set to the following:

PF_ON 

The pfEngineEvaluate() function is performed on its client pfEngines to push the results through a chain of computation.

PF_OFF 

The pfEngineSrcChanged() function is performed on its clients to tell those pfEngines that they have dirty sources.

Triggering pfFlux Evaluation

pfFlux evaluation is really a convenient one-step combined evaluation of a source pfEngine combined with getting and completing a writable buffer of data. Triggering the evaluation of pfFlux may trigger the evaluation of the pfFlux's source pfEngines, particularly if the PFFLUX_ON_DEMAND mode is set to PF_ON.

To explicitly trigger a pfFlux, the evaluate method can be used with a mask that is provided and passed through evaluation chains to potentially limit which pfFluxes in a chain are evaluated. For a pfFlux to be evaluated, both of the following conditions must be satisfied:

  • The bits in the current mask must match any of the bits in the evaluation mask associated with a pfFlux.

  • The pfFluxEvaluate() or pfFluxEvaluateEye() function is called.

The mask is a bitmask that you use to trigger the evaluation of the source pfEngines of a pfFlux. The functions pfFluxMask() and pfGetFluxMask() set and get, respectively, the evaluation mask of a pfFlux. The default mask is PFFLUX_BASIC_MASK. Masks enable selective evaluation of pfFlux source pfEngines.

The pfFluxEvaluate() function triggers an evaluation of pfFlux source pfEngines if any of the bits in the current mask match any of the bits in the evaluation mask of the pfFlux.

The pfFluxEvaluateEye() function is the same as pfFluxEvaluate() but also makes it easy to pass the current eyepoint through a chain of computation. This is a particularly common case in scene graphs and one very common use is morphing level of detail based on distance from the current eyepoint.

Calling pfFluxEvaluate() or pfFluxEvaluateEye() is the equivalent of calling pfEngineEvaluate() on the source engines followed by calling pfFluxWriteComplete() on the pfFlux.

For more information about pfEngine, see “pfEngine”.

Synchronized Flux Evaluation

There are times when you want to ensure that the dynamic data for a given frame in a number of pfFluxes becomes readable at precisely the same time. Particularly when computations are so complicated that they must be completed over multiple frames, you might want the computations of vertices for the different pfGeoSets for a given frame to be made usable at the same time. Consider the following example:

Suppose that you are using pfASD and you need shapes comprised of multiple pfGeoSets to move together in response to morphing terrain. One pfGeoSet might encapsulate the texture of a roof, another a wall, another a window, and another a door. Since a pfGeoSet can only encompass one texture, each of these parts of a house must remain separate pfGeoSets whose data must be encapsulated in separate pfFluxes. If, for rendering, the roof of one frame was used with the wall of another, it might appear that the house is breaking apart.

OpenGL Performer uses sync flux groups to ensure that the same frame of data is being used from all fluxes in a group.

Synchronizing pfFluxes with Flux Sync Groups

You can add one or more pfFluxes to a flux sync group using the pfFlux pfFluxSyncGroup() function and providing the integer identifier of the desired group. To get the flux sync group identifier of a pfFlux, use the pfGetFluxSyncGroup() function.


Note: OpenGL Performer does not maintain a list of pfFluxes in a flux group; instead, an internal field in pfFlux identifies the flux sync group to which it belongs. For this reason, there is no way to get a list from OpenGL Performer of all of the fluxes in a sync group.

For convenience, a flux sync group can be identified by a string name, set with pfGetFluxSyncGroupName(). The unsigned integer identifier can be obtained from the string name using pfGetFluxNamedSyncGroup(). The pfGetFluxNamedSyncGroup() function also automatically generates a new sync group identifier for a new name. The identifier of the first flux sync group you create is automatically one.


Note: Once you name a sync group, the name cannot be changed.

The pfGetFluxNumNamedSyncGroups() function returns the number of named sync groups.

Enabling a Flux Sync Group

To enable group synchronization of the pfFluxs in the flux sync group, enable the sync group using pfFluxEnableSyncGroup(). You can disable group synchronization using pfFluxDisableSyncGroup().

The pfGetFluxEnableSyncGroup() function returns whether or not a sync group is enabled.

Initially, all pfFluxes are all part of flux sync group 0, which can never be enabled.

Evaluating a Synchronized Flux Group

After all of the pfFluxes in an enabled flux sync group are calculated, you must call pfFluxSyncGroupReady() to specify that the fluxes are ready to be read.

When pfFrame() is called, pfFluxSyncComplete() is called on flux sync groups, which does the following:

  • Marks the writable buffers with the current flux frame number.

  • Makes their writable buffers readable.

Figure 19-3 is a timing diagram that shows a typical use for sync groups.

Figure 19-3. Timing Diagram Showing the Use of Sync Groups

Timing Diagram Showing the Use of Sync Groups

The diagram shows calls to pfFluxWriteComplete() on three pfFluxes during the course of a single asynchronous frame 7 (for example, a COMPUTE frame). Although the main processes APP, CULL, and DRAW run in frame numbers higher than 7, they cannot access the pfFlux results of the asynchronous process until the first pfFrame() after the call to pfFluxSyncGroupReady(). APP, CULL, and DRAW start seeing the results at frame 13. In this way an application can synchronize multiple results generated by an asynchronous frame. They all become visible at exactly the same frame boundary.


Note: Using sync groups adds some processing to pfFrame. When a pfFlux is not a member of a sync group, OpenGL Performer spends no per-frame processing on it. The only time a pfFlux consumes CPU time is during its API calls. However, when a pfFlux is on a sync group, OpenGL Performer incurs extra processing time at frames where the pfFlux data becomes available to the APP process (the pfFrame() following a call to pfFluxSyncGroupReady()).


Fluxed Geosets

Most often you use dynamic data to change the attributes, location, or the orientation of geometries. Morphing geometries, for example, is a matter of repositioning the vertices of a geometry.

You can, however, use dynamic data to change the higher level description of geometries. You might, for example, create a geometry editor that adds or subtracts triangles to and from geometries.


Note: While OpenGL Performer is set up to flux any object derived from pfMemory, only pfGeode and pfGeoSet are currently modified to accept fluxed and unfluxed forms without any special pfFlux method.

To dynamically change a pfGeoSet, you must operate on the data held in the pfFlux buffers usually when the buffers are initialized.

Example 19-1 shows how to turn the data portion of a pfFluxMemory into a fluxed pfGeoSet using pfFluxedGSetInit().

Example 19-1. Fluxed pfGeoSet

 
int make_fluxed_gset(pfFluxMemory *fmem)
{
    pfGeoSet *gset;
    pfVec3 *coords;
 
    if (fmem == NULL)
         return pfFluxedGSetInit(fmem);
 
    pfFluxedGSetInit(fmem);
 
    gset = (pfGeoSet*)fmem->getData();
 
    gset->setPrimType(PFGS_TRIS);
 
    ... // finish initializing pfGeoSet
 
    return 0;
}
 
main()
{
    pfFlux *fluxed_gset;
    pfGeoSet *gset;

    pfInit();
    pfMultiprocess(PFMP_DEFAULT);
    pfConfig();
 
    fluxed_gset = new pfFlux(make_fluxed_gset,
        PFFLUX_DEFAULT_NUM_BUFFERS);
    gset = (pfGeoSet*)fluxed_gset->getCurData();
 
    ...
}


Fluxed Coordinate Systems

A pfFCS is similar to a pfDCS node in that both nodes contain dynamic data. In addition to pfDCS functionality, however, a pfFCS uses a pfFlux to hold its matrix. This fluxed matrix can then be computed by a pfEngine, potentially asynchronously. This is the structure shown in Figure 19-4.

Figure 19-4. pfEngine Driving a pfFlux That Animates a pfFCS Node

pfEngine Driving a pfFlux That Animates a pfFCS Node

In this figure, the pfEngine performs calculations on the data input from multiple sources, including a pfMemory, a container for static data, a pfFlux, and an additional source pfEngine. The main pfEngine sends its resulting matrix to a pfFlux. The pfFlux is attached to the pfFCS with the pfFCS pfFlux() function. This flux then can do the following:

  • Trigger the pfEngine to directly recompute its data if in PFFLUX_ON_DEMAND mode when the pfFCS calls pfGetFluxCurData() on the flux

  • Use frame accurate results with other fluxes if a member of a flux sync group

  • Contain the matrix for the pfFCS node to produce a transformed coordinate system for the current frame for children of the node in the scene graph.

Since a pfFCS node is of type pfGroup, you can connect many nodes to it. This functionality is valuable if many geometries need to share a transformation, such as the moving limbs of a walking character whose overall location is changing every frame.

Unlike pfDCS nodes, pfFCS nodes do not detect changes in their matrix. A change to the matrix can move the children of a pfFCS node and change their visibility. This means that changing a pfFCS matrix may result in a visible node being culled out or invisible nodes being drawn. Both potential results are undesirable. In order to avoid them, an application has two options:

  • If the objects under the pfFCS node move in some known and confined space, the application can pre-calculate the bounding sphere of that space and set the bounding sphere of the pfFCS node statically to that sphere. In this way, changes to the pfFCS matrix will not change the visibility of the objects under the pfFCS node. The smaller the extent of the motion, the more efficient this method is. If there is no prior knowledge of the extent, this method is very wasteful because the application has to set a very large bounding sphere that is always visible.

  • In the APP process, change the bounding sphere of the pfFCS node every frame. This is not very desirable. If an application changes the bounding sphere every frame, it may just as well use a pfDCS node instead of a pfFCS node.

For an example of pfEngine, pfFlux, and pfFCS use, see fcs_engine.C in sample/pguide/libpf/C++.

Replacing pfCycleBuffer with pfFlux

Prior to version 2.2 of OpenGL Performer, the customary way of manipulating dynamic data was to use a pfCycleBuffer. Morphing was accomplished using pfCycleBuffer and pfMorph. While pfCycleBuffer and pfMorph are still supported for compatibility, they are obsoleted by pfEngine and pfFlux.


Note: If your applications do not contain pfCycleBuffer or pfMorph, skip to the next section. This section explains how to replace pfCycleBuffer and pfMorph with pfFlux and pfEngine.


pfFlux Differences

A pfFlux is similar to, but far more powerful than, a pfCycleBuffer:

  • Where pfCycleBuffer is unaware of the nodes driving it, pfFlux is aware of its parent nodes. When attached to a pfEngine, for example, a pfFlux can trigger a pfEngine to recompute its output.

  • A pfFlux provides a mechanism for updating data in processes that are completely asynchronous to the main APP, CULL, and DRAW pipeline stages.

Converting to pfFlux

To convert from pfCycleBuffer to pfFlux, use code similar to the following:

/* Replace pfCyclebuffer creation with pfFlux: */
pfFlux *flux = pfFlux(size, PFFLUX_DEFAULT_NUM_BUFFERS);
 
/* replace getting of read-only data
* pfCBufGetCurData() for read becomes:
*/
curData = flux->getCurData();
 
/* replace getting of data to edit.*/
 
/* get writable buffer BEFORE editing data */
data = flux->getWritableData();
 
/* ... edit data */
 
/* declare data edited after editing for the frame is done.
* Replace pfCBufferChanged(pfCycleBuffer *cbuf) becomes:
*/
flux->WriteComplete();

pfEngine

A pfEngine performs calculations. The source can either be static data, such as a pfMemory, or dynamic data, such as a pfFlux. The destination of a pfEngine is fed into a pfFlux, as shown in Figure 19-4.

Creating and Deleting Engines

The constructor for pfEngine requires that you specify the computation type of pfEngine you are creating. OpenGL Performer provides many types of engines, each performing a different calculation on the input data. Table 19-1 describes the engine types:

Table 19-1. pfEngine Types

Engine

Description

PFENG_SUM

Sums the input array of floats into a destination array of floats.

PFENG_MORPH

Morphs between the input data

PFENG_BLEND

Is a lightweight version of PFENG_MORPH.

PFENG_TRANSFORM

Translates a set of data with a matrix operation.

PFENG_ALIGN

Generates a translation matrix from the input data.

PFENG_MATRIX

Generates a matrix of type rotation, translation, scale, non-uniform scaling, or combine, using pre or post multiplication.

PFENG_ANIMATE

Same as PFENG_MATRIX, except that the sources are arrays of animation frames.

PFENG_BBOX

Computes the bounding box of the input array of data.

PFENG_TIME

Takes a time, in seconds, and makes it into a frame number that is useful in driving the frame source of PFENG_MORPH, PFENG_BLEND, and PFENG_ANIMATE.

PFENG_STROBE

Switches iteration sets of floats, called items, between an on and off based on time.

PFENG_USER_FUNCTION

User-defined function.

The pfGetEngineFunction() function returns the pfEngine type. These engine types are described in further detail in “Setting Engine Types and Modes”.

To delete a pfEngine, as with all pfObjects, use pfDelete().

Setting Engine Types and Modes

Table 19-1 lists the different types of pfEngines supplied by OpenGL Performer. Many of the pfEngines have different modes of operation. For example, PFENG_ANIMATE offers the following modes:

  • PFENG_ANIMATE_ROT

  • PFENG_ANIMATE_TRANS

  • PFENG_ANIMATE_SCALE_UNIFORM

  • PFENG_ANIMATE_SCALE_XYZ

  • PFENG_ANIMATE_BASE_MATRIX

All of these modes specify what the engine changes. PFENG_ANIMATE_ROT, for example, specifies that the engine rotates a geometry, PFENG_ANIMATE_TRANS translates a geometry, and PFENG_ANIMATE_SCALE_UNIFORM scales a geometry.

The pfEngineMode() function sets the mode value; pfGetEngineMode() returns the mode value.

The following sections describe the engine types and their mode values, if any.

PFENG_SUM Engines

A PFENG_SUM engine adds arrays of floats to form a destination array of floats. One use for PFENG_SUM is aligning geometries to height, such as buildings, to a pfASD terrain.

Since PFENG_SUM can have as few as one source, it can be used to simply copy data from one location to another.

PFENG_MORPH Engines

Morphing is the smooth transition from one appearance to another. The effect is achieved through the reorientation of the vertices in pfGeoSets. Morphing can refer to geometries and their attributes. For example, you could “morph” the following:

  • Color to simulate a flickering fire.

  • Texture coordinates to simulate rippling ocean waves.

  • Coordinates to make a 3D model of a face smile or frown.

A PFENG_MORPH engine sets the destination of the pfEngine to a weighted sum of its sources. To specify the weighting, you use one of the following:

  • PFENG_MORPH_WEIGHTS

  • PFENG_MORPH_FRAME

The token PFENG_MORPH_WEIGHTS contains an array of floats or weighting values. PFENG_MORPH_SRC(n) is also an array; there is one element for each pfEngine source. To create the morph weighting, element 0 of PFENG_MORPH_WEIGHTS is multiplied by PFENG_MORPH_SRC(0); element 1 is multiplied by PFENG_MORPH_SRC(1), and so on.

PFENG_MORPH_FRAME contains a single float. This float specifies the weighting between two pfEngine sources, PFENG_MORPH_SRC(n). The integer portion of the float specifies the first of the two consecutive sources, and the decimal portion of the float specifies the weighting between those sources. For example, a PFENG_MORPH_FRAME of 3.8 would mean PFENG_MORPH_SRC(3) * 0.2 + PFENG_MORPH_SRC(4) * 0.8.

For an example of a morph engine, see morph_engine.C in sample/pguide/libpf/C++.

PFENG_BLEND Engines

A PFENG_BLEND engine is a lightweight version of PFENG_MORPH. PFENG_BLEND sets the pfEngine destination to a weighted sum of elements of the pfEngine sources.

To specify the weighting, you use one of the following:

  • PFENG_BLEND_WEIGHTS

  • PFENG_BLEND_FRAME

These weighting mechanisms work identically to those in “PFENG_MORPH Engines”, with the following exception: the source, PFENG_BLEND_SRC(n), should contain iteration elements. Each element is a set of floats, called items. The number of elements in each set is called a stride. So, the iteration items for pfEngine source 0 begin at items number 0 * stride; for source 1, at items number 1 * stride, and so on.

PFENG_BLEND_WEIGHTS contains an array of floats, one for each of the pfEngine sources, PFENG_BLEND_SRC(n).

PFENG_BLEND_WEIGHTS(0) is multiplied by the items starting at PFENG_BLEND_SRC(0*stride); PFENG_BLEND_WEIGHTS(1) is multiplied by the items starting at PFENG_BLEND_SRC(1*stride) and so on.

PFENG_BLEND_FRAME contains a single float. This float specifies the weighting between two consecutive PFENG_BLEND_SRC(n) sources. The integer portion of the float specifies the first of the two consecutive sources, and the decimal portion of the float specifies the weighting between those sources. For example, a PFENG_BLEND_FRAME of 3.8 would mean PFENG_BLEND_SRC(3) * 0.2 + PFENG_BLEND_SRC(4) * 0.8.

For an example of a blend engine, see blend_engine.C in sample/pguide/libpf/C++.

PFENG_TRANSFORM Engines

A PFENG_TRANSFORM engine transforms the PFENG_TRANSFORM_SRC(n) array of floats by the matrix contained in PFENG_TRANSFORM_MATRIX.

PFENG_ALIGN Engines

The following describes the PFENG_ALIGN engines:

  • PFENG_ALIGN generates an alignment matrix based on the sources. One use for PFENG_ALIGN is to align moving objects, such as vehicles, to a pfASD.

  • PFENG_ALIGN_POSITION determines the translational portion of the matrix. If PFENG_ALIGN_POSITION is NULL, the translational portion of the matrix is set to all zeros.

  • PFENG_ALIGN_NORMAL and PFENG_ALIGN_AZIMUTH determine the rotation portion of the matrix. If either are NULL, the rotation portion of the matrix is set to all zeros.

PFENG_MATRIX Engines

A PFENG_MATRIX engine generates a matrix based on its sources. Applied to vertex coordinates of pfGeoSets, these routines provide the same mathematical effect as using pfDCSs in a scene graph. However, these pfEngines use the host to compute the vertices and, thus, eliminate the need for matrices to be evaluated in the graphics pipeline. This trade-off might produce a faster rendering frame rate if there are sufficient host CPU resources for the computation involved.

The following tokens specify the kind of action performed by the PFENG_MATRIX engine:

PFENG_MATRIX_RO
 

Rotates a geometry according to heading, pitch, and roll values; the equivalent is pfDCSRot().

PFENG_MATRIX_TRANS
 

Transforms a geometry; the equivalent is pfDCSTrans().

PFENG_MATRIX_SCALE_UNIFORM
 

Uniformly scales a geometry; the equivalent is pfDCSScale().

PFENG_MATRIX_SCALE_XYZ
 

Non-uniformly scales a geometry; the equivalent is pfDCSScaleXYZ(x,y,z).

PFENG_MATRIX_BASE_MATRIX
 

Contains a pfMatrix that is either pre- or post-multiplied against the matrix generated by the other sources, depending on the PFENG_MATRIX_MODE mode set by the pfEngineMode() function; PF_OFF specifies pre-multiplication; PF_ON specifies post-multiplication.

Any or all of the sources can be NULL, in which case they have no effect on the resulting matrix.

PFENG_ANIMATE Engines

A PFENG_ANIMATE engine animates a matrix based on its sources. This engine type is similar to PFENG_MATRIX, but instead of using single values for rotation, translation, and scaling, PFENG_ANIMATE uses arrays of values.

To specify the weighting, you use one of the following:

  • PFENG_ANIMATE_WEIGHTS

  • PFENG_ANIMATE_FRAME

PFENG_ANIMATE_WEIGHTS contains a float for each of the values in the rotation, translation, and scale sources. PFENG_ANIMATE_SRC(n) is also an array; there is one element for each pfEngine source.

To create an animation, element zero of PFENG_ANIMATE_WEIGHTS is multiplied by PFENG_ANIMATE_SRC(0); element one is multiplied by PFENG_ANIMATE_SRC(1), and so on.

PFENG_ANIMATE_FRAME contains a single float. This float specifies the weighting between two of the values in the rotation, translation, and scale sources. The integer portion of the float specifies the first of the two consecutive values, and the fractional portion of the float specifies the weighting between those values. For example, a PFENG_ANIMATE_FRAME of 3.8 would mean PFENG_ANIMATE_SRC(3) * 0.2 + PFENG_ANIMATE_SRC(4) * 0.8.

The following tokens specify the kind of action performed by the PFENG_ANIMATE engine:

PFENG_ANIMATE_ROT
 

Rotates a geometry according to heading, pitch, and roll values; the equivalent is pfDCSRot().

PFENG_ANIMATE_TRANS
 

Transforms a geometry; the equivalent is pfDCSTrans().

PFENG_ANIMATE_SCALE_UNIFORM
 

Uniformly scales a geometry; the equivalent is pfDCSScale().

PFENG_ANIMATE_SCALE_XYZ
 

Non-uniformly scales a geometry; the equivalent is pfDCSScaleXYZ(x, y, z).

PFENG_ANIMATE_BASE_MATRIX
 

Contains a pfMatrix that is either pre- or post-multiplied against the matrix generated by the other sources depending on the PFENG_MATRIX_MODE mode set by the pfEngine function pfEngineMode().

Any or all of the sources can be NULL, in which case they have no effect on the resulting matrix.

For an example of an animate engine, see fcs_animate.C in sample/pguide/libpf/C++.

For more information about animation, see “Animating a Geometry ”.

PFENG_BBOX Engines

A PFENG_BBOX engine generates a bounding box that contains the coordinates of the pfEngine source, PFENG_BBOX_SRC.

PFENG_TIME Engines

A PFENG_TIME engine takes a time in seconds and converts it to a frame number, which can drive the sources of the following engine types: PFENG_MORPH, PFENG_BLEND, and PFENG_ANIMATE.

PFENG_TIME_TIME is the source time in seconds. This is usually connected to the pfFlux returned from pfGetFluxFrame().

PFENG_TIME_SCALE contains four floats that are used to modify the incoming time:

  • PFENG_TIME_SCALE[0] is an initial offset.

  • PFENG_TIME_SCALE[1] is a scale factor.

  • PFENG_TIME_SCALE[2] is a range.

  • PFENG_TIME_SCALE[3] is a final offset.

The PFENG_TIME_MODE mode, set with pfEngineMode(), determines how the destination number moves between its start and end point. The two mode values include the following:

  • PFENG_TIME_CYCLE makes the destination number go from start to end then restarts again at the beginning.

  • PFENG_TIME_SWING makes the destination number go back and forth between the start and end.


    Note: This mode is related to the interval mode of a pfSequence.


If the PFENG_TIME_TRUNC mode is set to PF_ON, the result is truncated.

PFENG_STROBE Engines

A PFENG_STROBE engine switches iteration sets of floats, called items, between ON and OFF based on time. One use of PFENG_STROBE is light point animations.

PFENG_STROBE_TIME is the source time in seconds. This data is usually connected to the pfFlux returned from pfGetFluxFrame().

PFENG_STROBE_TIMING contains iterations sets of three floats:

  • PFENG_STROBE_TIMING[n*stride + 0] is the ON duration.

  • PFENG_STROBE_TIMING[n*stride + 1] is the OFF duration.

  • PFENG_STROBE_TIMING[n*stride + 2] is an offset.

PFENG_STROBE_ON contains iteration sets of floats, called items.

PFENG_STROBE_OFF contains iteration sets of floats, called items. If PFENG_STROBE_OFF is NULL, all off states are 0.0.

For an example of animation using a strobe engine, see strobe_engine.C in sample/pguide/libpf/C++.

PFENG_USER_FUNCTION Engines

When a pfEngine is of type PFENG_USER_FUNCTION, you specify the function of the pfEngine using pfEngineUserFunction(). The pfGetEngineUserFunction() function returns the pfEngine type.

For an example of animation using a user-defined engine, see user_engine.C in sample/pguide/libpf/C++.

Setting Engine Sources and Destinations

The pfEngine sources can be any number of objects, including pfFluxes, pfMemorys, and pfEngines. The sources provide the input data for the pfEngine. pfEngine destinations are pfFluxes, which contain the pfEngine output.

The pfEngineSrc() and pfEngineDst() functions set the pfEngine sources and destination, respectively; pfGetEngineSrc() and pfGetEngineDst() return the pfEngine sources and destination, respectively. Function pfGetEngineNumSrcs() returns the number of sources. A pfEngine can only have one destination.

Setting Engine Masks

The pfEngine masks are bit masks that provide a means of selectively triggering pfEngines. Only those pfEngines that have masks that match the evaluation mask can be triggered, as shown in the evaluation function:

pfEngineEvaluate(int mask)

The  pfEngineMask() and pfGetEngineMask() functions set and return pfEngine masks, respectively.

Setting Engine Iterations

You can make pfEngines repeat their calculations, called iterations, when operating on an array of data instead of on a single piece of data. You also specify the unit of data, called an item, for example, a vector would have three items per unit. For example, if you want to add two arrays of 100 pfVec3s each, you set iteration to 100 and item to 3.

The pfEngineIterations() and pfGetEngineIterations() functions set and get iterations, respectively.

Setting Engine Ranges

There are times when you might like to reduce computation needs by not evaluating engines, for example, when the eyepoint is far from the geometry being animated by a pfEngine.

You establish the location of an animated geometry and an area located around the geometry using pfEngineEvaluationRange(). Only when the eye position is within that area can the pfEngine be evaluated.

The range functionality is only enabled when the PFENG_RANGE mode, created by pfEngineMode(), is set to PF_ON; the default is PF_OFF.

Evaluating pfEngines

To evaluate a pfEngine, you use one of the forms of pfEngineEvaluate() or pfFluxEvaluate().

For more information about pfFluxEvaluate(), see “Triggering pfFlux Evaluation”.

The two forms of the evaluate functions are the following:

pfEngineEvaluate(pfEngine* _engine, int mask)
pfEngineEvaluateEye(pfEngine* _engine, int mask, pfVec3 eye_pos)
pfFluxEvaluate(pfFlux* _flux, int mask)
pfFluxEvaluateEye(pfFlux* _flux, int mask, pfVec3 eye_pos)

For more information about mask, see “Setting Engine Masks”.

The second form of the function specifies the location of the viewer. The engine is only evaluated if the location of the viewer, eye_pos, is within the range of the pfEngine, set by pfEngineEvaluationRange(). For more information about the range of a pfEngine, see “Setting Engine Ranges”.


Note: The eye position has no effect on evaluation of the pfEngine if the PFENG_RANGE_CHECK mode is PF_OFF, the default.


Animating a Geometry

To animate a geometry using a combination of pfFlux, pfFCS, and pfEngines nodes, use the following guidelines:

  1. Initialize and populate the pfFluxes.

  2. Connect pfMemory or pfFluxes as the sources of a pfEngine.

  3. Connect a pfFlux to the output destination of the pfEngine.

  4. Connect the pfFlux containing the output of the pfEngine to the scene graph in one of three ways:

    • Connect it as an attribute of a pfGeoSet using pfGSetAttr().

    • Connect it as the bounding box of a pfGeoSet using pfGSetBound(), where the mode argument is set to PFBOUND_FLUX.

    • Connect it directly to a pfFCS using pfFCSFlux().

  5. Set up any needed flux sync groups for synchronizing transformations.

This scenario is the simplest set up; it is represented graphically in Figure 19-4. More complicated scenarios include chaining pfEngines together or running multiple geometries off of one pfFCS node.

The following C++ code sample provides an implementation of the animation procedure:

Example 19-2. Connecting Engines and Fluxes

// create the nodes
pfFlux *myData1 = new pfFlux(100 * sizeof(pfVec3));
pfFlux *myData2 = new pfFlux(100 * sizeof(pfVec3));
pfEngine *myEngine = new pfEngine(PFENG_SUM);
pfFlux *engineOutput = new pfFlux(100 * sizeof(pfVec3));
pfFCS myFCS = new pfFCS();
pfGeode myGeode = new pfGeode();
 
// initialize and populate the flux nodes 
myData1->init();
myData2->init();
 
// attach the pfFlux nodes as the source of the pfEngine
myEngine->setSrc(0, myData1, 0, 3);
myEngine->setSrc(0, myData12, 0, 3);
 
// attach a pfFlux to the output of the pfEngine
myEngine->setDst(engineOutput, 0, 3);
myEngine->iterations(100, 3);
 
// connect the pfFlux output node to the scenegraph
myFCS->setFlux(engineOutput);
// attach child geometry to be tranformed by the FCS
myFCS->addChild(myGeode);
 
... 
// compute the data in the source pfFluxes to the engine
float *current = (float *)myData1->getWritableData();
... // compute data 
myData1->writeComplete();