Chapter 22. Math Routines

This chapter describes the OpenGL Performer math routines. Math routines let you create, modify, and manipulate vectors, matrices, line segments, planes, and bounding volumes such as spheres, boxes, and cylinders.

Vector Operations

A basic set of mathematical operations is provided for setting and manipulating floating point vectors of length 2, 3 , and 4 . The types of these vectors are pfVec2, pfVec3, and pfVec4, respectively. The components of a vector are denoted by PF_X, PF_Y, PF_Z, and PF_W with indices of 0, 1, 2, and 3, respectively. In the case of 4-vectors, the PF_W component acts as the homogeneous coordinate in transformations.

OpenGL Performer supplies macro equivalents for many of the routines described in this section. Inlining the macros instead of calling the routines can substantially improve performance. The C++ interface provides the same, fast performance as the inlined macros.

Table 22-1 lists the routines, what they do (in mathematical notation), and the macro equivalents (where available) for working with 3-vectors. Most of the same operations are also available for 2-vectors and 4-vectors, substituting “2” or “4” for “3” in the routine names. The only operations not available for 2-vectors are vector cross-products, point transforms, and vector transforms; the only operations unavailable for 4-vectors are vector cross-products and point transforms, that is, there are no such routines as pfCrossVec2(), pfCrossVec4(), pfXformPt2(), pfXformPt4(), or pfXformVec2().


Note: For the duration of this chapter, we use the following convention for denoting one-letter variables: bold lowercase letters represent vectors and bold uppercase letters represent matrices. “x” indicates cross product, “.” denotes dot product, and vertical bars indicate the magnitude of a vector.]


Table 22-1. Routines for 3-Vectors

Routine

Effect

Macro Equivalent

pfSetVec3( d, x, y, z )

d = (x, y, z)

PFSET_VEC3

pfCopyVec3( d, v )

d = v

PFCOPY_VEC3

pfNegateVec3( d,v )

d = -v

PFNEGATE_VEC3

pfAddVec3( d, v1, v2 )

d = v1 + v2

PFADD_VEC3

pfSubVec3( d, v1, v2 )

d = v1 - v2

PFSUB_VEC3

pfScaleVec3( d, s, v )

d = sv

PFSCALE_VEC3

pfAddScaledVec3( d, v1, s, v2 )

d = v1 + sv2

PFADD_SCALED_VEC3

pfCombineVec3( d,s1,v1,s2,v2 )

d = s1 v1 + s2 v2

PFCOMBINE_VEC3

pfNormalizeVec3( d )

d = d/|d|

none

pfCrossVec3( d, v1, v2 )

d = v1 x v2

none

pfXformPt3( d, v, m )

d = vM, where v = (vx, vy, vz,) and M is the 4x3 submatrix.

none

pfFullXformPt3( d, v, M )

d = vM/dw, where v = (vx, vy, vz, 1)

none

pfXformVec3( d, v, M )

d = vM, where v = (vx, vy, vz, 0)

none

pfDotVec3(v 1, v 2)

v1 . v2

PFDOT_VEC3

pfLengthVec3( v )

|v|

PFLENGTH_VEC3

pfSqrDistancePt3( v1, v2 )

|v2 - v1|2

PFSQR_DISTANCE_PT3

pfDistancePt3( v1, v2 )

|v2 - v1|

PFDISTANCE_PT3

pfEqualVec3( v1, v2 )

Returns TRUE if v1 = v2 and FALSE, otherwise.

PFEQUAL_VEC3

pfAlmostEqualVec3( v1,v2,tol )

Returns TRUE if each element of v1 is within tol of the corresponding element of v2 and FALSE, otherwise.

PFALMOST_EQUAL_
VEC3


Matrix Operations

A pfMatrix is a 4x4 array of floating-point numbers that is used primarily to specify a transformation in homogeneous coordinates (xyzw). Transforming a vector by a matrix means multiplying the matrix on the right by the row vector on the left.

Table 22-2 describes the OpenGL Performer mathematical operations that act on matrices.

Table 22-2. Routines for 4x4 Matrices

Routine

Effect

Macro Equivalent

pfMakeIdentMat( d )

D = I.

PFMAKE_IDENT_MAT

pfMakeVecRotVecMat( d,v1,v2 )  

D = M such that v2 = v1 M.

v1 , v2  are normalized.

none

pfMakeQuatMat( d, q ))

D = M, where M is the rotation of the quaternion q.

none

pfMakeRotMat( d, deg, x, y, z )

D = M, where M rotates by deg around (xyz).

none

pfMakeEulerMat( d, h, p, r )

D = RPH, where R, P, and H are the transforms for roll, pitch, and heading.

none

pfMakeTransMat( d, x, y, z )

D = M, where M translates by (xyz).

PFMAKE_TRANS_MAT

pfMakeScaleMat( d, x, y, z )

D = M, where M scales by (x, y, z).

PFMAKE_SCALE_MAT

pfMakeCoordMat( d, c )

D = M, where M rotates by (h, p, r) and translates by (xyz) with h, p, r, x, y, and z all specified by c.

none

pfGetOrthoMatQuat( s, q )

Returns in q a quaternion with the rotation specified by s.

none

pfGetOrthoMatCoord( s, d )

Returns in d the rotation and translation specified by s.

none

pfSetMatRow( d, r, x, y, z, w )

Set rth row of D equal to (x, y, z, w).

PFSET_MAT_ROW

pfGetMatRow( m, r, x, y, z, w )

(*x, *y, *z, *w) = rth row of M.

PFGET_MAT_ROW

pfSetMatCol( d, c, x, y, z, w )

Set cth column of D equal to (x, y, z, w).

PFSET_MAT_COL

pfGetMatCol( m, c, x, y, z, w )

(*x, *y, *z, *w) = cth column of M.

PFGET_MAT_COL

pfSetMatRowVec3( d, r, v )

Set rth row of D equal to v.

PFSET_MAT_ROWVEC3

pfGetMatRowVec3( m, r, d )

d = rth row of M.

PFGET_MAT_ROWVEC3

pfSetMatColVec3( d, c, v )

Set cth column of D equal to v.

PFSET_MAT_COLVEC3

pfGetMatColVec3( m, c, d )

d = cth column of M.

PFGET_MAT_COLVEC3

pfCopyMat( d, m )

D = M.

PFCOPY_MAT

pfAddMat( d, m1, m2 )

D = M1 + M2.

none

pfSubMat( d, m1, m2 )

D = M1 - M2.

none

pfMultMat( d, m1, m2 )

D = M1 M2.

none

pfPostMultMat( d, m )

D = DM.

none

pfPreMultMat( d, m )

D = MD.

none

pfTransposeMat( d, m )

D = MT.

none

pfPreTransMat( d, m, x, y, z )

D = TM, where T translates by (xyz).

none

pfPostTransMat( d, x, y, z, m )

D = MT, where T translates by (xyz).

none

pfPreRotMat( d, deg, x, y, z, m )

D = RM, where R rotates by deg around (x, y, z).

none

pfPostRotMat( d, m, deg, x, y, z )

D = MR, where R rotates by deg around (x, y, z).

none

pfPreScaleMat( d, x, y, z, m )

D = SM, where S scales by (x, y, z).

none

pfPostScaleMat( d, m, x, y, z )

D = MS, where S scales by (x, y, z).

none

pfInvertFullMat( d, m ))

D = M-1 for general matrices.

none

pfInvertAffMat( d, m )

D = M-1 with M affine.

none

pfInvertOrthoMat( d, m )

D = M-1 with M orthogonal.

none

pfInvertOrthoNMat( d, m )

D = M-1 with M orthonormal.

none

pfInvertIdentMat( d, m )

D = M-1 with M equal to the identity matrix.

none

pfEqualMat( d, m )

Returns TRUE if D = M and FALSE, otherwise

PFEQUAL_MAT

pfAlmostEqualMat( d, m, tol )

Returns TRUE if each element of D is within tol of the corresponding element of M and FALSE, otherwise

PFALMOST_EQUAL_MAT

Some of the math routines that take a matrix as an argument are restricted to affine, orthogonal, or orthonormal matrices; these restrictions are noted by Aff, Ortho, and OrthoN, respectively. (If such a restriction is not noted in a libpr routine name, the routine can take a general matrix.)

An affine transformation is one that leaves the homogeneous coordinate unchanged—that is, in which the last column is (0,0,0,1). An orthogonal transformation is one that preserves angles. It can include translation, rotation, and uniform scaling, but no shearing or nonuniform scaling. An orthonormal transformation is an orthogonal transformation that preserves distances; that is, one that contains no scaling.

In the visual simulation library, libpf, most routines require the matrix to be orthogonal, although this is not noted in the routine names.

The standard order of transformations for a hierarchical scene involves post-multiplying the transformation matrix for a child by the matrix for the parent. For instance, assume your scene involves a hand attached to an arm attached to a body. To get a transformation matrix H for the hand, post-multiply the arm's transformation matrix (A) by the body's (B): H = AB. To transform the hand object (at location h in hand coordinates) to body coordinates, calculate h' = hH.

Example 22-1. Matrix and Vector Math Examples

/*
 * test Rot of v1 onto v2
 */
{
    pfVec3 v1, v2, v3;
    pfMatrix m1;
    
    MakeRandomVec3(v1);
    MakeRandomVec3(v2);
    pfNormalizeVec3(v1);
    pfNormalizeVec3(v2);
    pfMakeVecRotVecMat(m1, v1, v2);
    pfXformVec3(v3, v1, m1);
    AssertEqVec3(v3, v2, “Arb Rot To”);
}
 
/*
 * test inversion of Affine Matrix
 */
{
    pfVec3 v1, v2, v3;
    pfMatrix m1, m2, m3;
    
    MakeRandomVec3(v3);
    pfMakeScaleMat(m2, v3[0], v3[1], v3[2]);
    pfPreMultMat(m1, m2);
    
    MakeRandomVec3(v1);
    pfNormalizeVec3(v1);
    MakeRandomVec3(v2);
    pfNormalizeVec3(v2);
    pfMakeVecRotVecMat(m1, v1, v2);
    s = pfLengthVec3(v2)/pfLengthVec3(v1);
    pfPreScaleMat(m1, s, s, s, m1);
    
    MakeRandomVec3(v1);
    pfNormalizeVec3(v1);
    MakeRandomVec3(v2);
    pfNormalizeVec3(v2);
    pfMakeVecRotVecMat(m2, v1, v2);
    
    MakeRandomVec3(v3);
    pfMakeTransMat(m2, v3[0], v3[1], v3[1]);
    pfPreMultMat(m1, m2);
    
    pfInvertAffMat(m3, m1);
    pfPostMultMat(m3, m1);
    AssertEqMat(m3, ident, “affine inverse”);


Quaternion Operations

A pfQuat is the OpenGL Performer data structure (a pfVec4) whose floating point values represent the components of a quaternion. Quaternions have many beneficial properties. The most relevant of these is their ability to represent 3D rotations in a manner that allows relatively simple yet meaningful interpolation between rotations. Much like multiplying two matrices, multiplying two quaternions results in the concatenation of the transformations. For more information on quaternions, see the article by Sir William Rowan Hamilton “On quaternions; or on a new system of imaginaries in algebra,” in Philosophical Magazine, xxv, pp. 10-13 (July 1844), or refer to the sources noted in the pfQuat(3pf) man page.

The properties of spherical linear interpolation makes quaternions much better suited than matrices for interpolating transformation values from keyframes in animations. The most common usage then is to use pfSlerpQuat() to interpolate between two quaternions representing two rotational transformations. The quaternion that results from the interpolation can then be passed to pfMakeQuatMat() to generate a matrix for use in a subsequent OpenGL Performer call such as pfDCSMat(). While converting a quaternion to a matrix is relatively efficient, converting a matrix to a quaternion with pfGetOrthoMatQuat() is expensive and should be avoided when possible.

Because a pfQuat is also a pfVec4, all of the pfVec4 routines and macros may be used on pfQuats as well.

Table 22-3. Routine s for Quaternions

Routine

Effect

Macro Equivalent

pfMakeRotQuat( q, a, x, y, z )

Sets q to rotation of a degrees about (x, y, z).

none

pfGetQuatRot( q, a, x, y, z )

Sets *a to angle and (*x, *y, *z) to axis of rotation represented by q.

none

pfConjQuat( d, q )

d = conjugate of q.

PFCONJ_QUAT

pfLengthQuat( q )

Returns length of q.

PFLENGTH_QUAT

pfMultQuat( d, q1, q2 )

d = q1 * q2.

PFMULT_QUAT

pfDivQuat( d, q1, d2 )

d = q1 / q1 .

PFDIV_QUAT

pfInvertQuat( d, q1 )

d = 1 / q1.

 

pfExpQuat( d, q )

d = exp(q).

none

pfLogQuat( d, q )

d = ln(q).

none

pfSlerpQuat( d, t, q1, q2 )

d = interpolation with weight t between q1 (t=0.0) and q2 (t=1.0).

none

pfSquadQuat( d, t, q1, q2, a, b )

d = quadratic interpolation between q1 and q2.

none

pfQuatMeanTangent( d, q1, q2, q3 )

d = mean tangent of q1, q2 and q3.

none


Example 22-2. Quaternion Example

/*
 * test quaternion slerp
 */
 
    pfQuat q1, q2, q3;
    pfMatrix m1, m2, m3, m3q;
    pfVec3 axis;
    float angle1, angle2, angle, t;
    
 
    MakeRandomVec3(axis);
    pfNormalizeVec3(axis);
    angle1 = -drand48()*90.0f;
    angle2 = drand48()*90.0f;
    t = drand48();
    
    pfMakeRotMat(m1, angle1, axis[0], axis[1], axis[2]);
    pfMakeRotQuat(q1, angle1, axis[0], axis[1], axis[2]);
    pfMakeQuatMat(m3q, q1);
    
    pfMakeRotMat(m2, angle2, axis[0], axis[1], axis[2]);
    pfMakeRotQuat(q2, angle2, axis[0], axis[1], axis[2]);
    pfMakeQuatMat(m3q, q2);
    AssertEqMat(m2, m3q, “make rot quat q2”);
    
    angle = (1.0f-t) * angle1 + t * angle2;
    pfMakeRotMat(m3, angle, axis[0], axis[1], axis[2]);
    
    pfMakeRotQuat(q1, angle1, axis[0], axis[1], axis[2]);
    pfMakeRotQuat(q2, angle2, axis[0], axis[1], axis[2]);
    pfSlerpQuat(q3, t, q1, q2);
    pfMakeQuatMat(m3q, q3);
    AssertEqMat(m3q, m3, “quaternion slerp”);
{


Matrix Stack Operations

OpenGL Performer allows you to create a stack of transformation matrices, which is called a pfMatStack.

Table 22-4 lists and describes the matrix stack routines. Note that none of these routines has a macro equivalent. The matrix at the top of the matrix stack is denoted “TOS,” for “Top of Stack.”

Table 22-4. Matrix Stack Routines

Routine

Operation

pfNewMStack ()

Allocate storage.

pfResetMStack ()

Reset the stack.

pfPushMStack ()

Duplicate the TOS and push it on the stack.

pfPopMStack ()

Pop the stack.

pfPreMultMStack ()

Premultiply the TOS by a matrix.

pfPostMultMStack ()

Postmultiply the TOS by a matrix.

pfLoadMStack ()

Set the TOS matrix.

pfGetMStack ()

Get the TOS matrix.

pfGetMStackTop ()

Get a pointer to the TOS matrix.

pfGetMStackDepth ()

Return the current depth of the stack.

pfPreTransMStack ()

Pre-multiply the TOS by a translation.

pfPostTransMStack ()

Post-multiply the TOS by a translation.

pfPreRotMStack ()

Pre-multiply the TOS by a rotation.

pfPostRotMStack ()

Post-multiply the TOS by a rotation.

pfPreScaleMStack ()

Pre-multiply the TOS by a scale factor.

pfPostScaleMStack ()

Post-multiply the TOS by a scale factor.


Creating and Transforming Volumes

libpr provides a number of volume primitives, including sphere, box, cylinder, half-space (plane), and frustum. libpf uses the frustum primitive for a view frustum and uses other volume primitives for bounding volumes:

  • pfNodes use bounding spheres.

  • pfGeoSets use bounding boxes.

  • Segments use bounding cylinders.

Defining a Volume

This section describes how to define geometric volumes.

Spheres

Spheres are defined by a center and a radius, as shown by the pfSphere structure's definition:

typedef struct {
    pfVec3 center;
    float radius;
} pfSphere;

A point p is in the sphere with center c and radius r if |p - c|< r.

Axially Aligned Boxes

An axially aligned box is defined by its two corners with the smallest and largest values for each coordinate. Its edges are parallel to the X, Y, and Z axes. It is represented by the pfBox data structure:

typedef struct {
    pfVec3 min;
    pfVec3 max;
} pfBox;

A point (x, y, z) is in the box if minx < x < maxx, miny < y < maxy, and minz < maxz, .

Cylinders

A cylinder is defined by its center, radius, axis, and half-length, as shown by the definition of the pfCylinder data structure:

typedef struct {
    pfVec3 center;
    float radius;
    pfVec3 axis;
    float halfLength;
} pfCylinder;

A point p is in the cylinder with center c, radius r, axis a, and half-length h, if |(p - c) . a| < h and | (p - c) - ((p - c) . a) a | < r.

Half-spaces (Planes)

A half-space is defined by a plane with a normal pointing away from the interior. It is represented by the pfPlane data structure:

typedef struct {
    pfVec3 normal;
    float offset;
} pfPlane;

A point p is in the half-space with normal n and offset d if p . n < d.

Frusta

Unlike the other volumes, a pfFrustum is not an exposed structure. You can allocate storage for a pfFrustum using pfNewFrust() and you can set the frustum using pfMakePerspFrust() or pfMakeOrthoFrust().

Creating Bounding Volumes

The easiest and most efficient way to create a volume is to use one of the bounding operations. The routines in Table 22-5 create a bounding volume that encloses other geometric objects

Table 22-5. Routines to Create Bounding Volumes

Routine

Bounding Volume

pfBoxAroundPts ()

Box enclosing a set of points

pfBoxAroundBoxes ()

Box enclosing a set of boxes

pfBoxAroundSpheres ()

Box enclosing a set of spheres

pfCylAroundSegs ()

Cylinder around a set of segments

pfSphereAroundPts ()

Sphere around a set of points

pfSphereAroundBoxes ()

Sphere around a set of boxes

pfSphereAroundSpheres ()

Sphere around a set of spheres

Bounding volumes can also be defined by extending existing volumes, but in many cases the tightness of the bounds created through a series of extend operations is substantially inferior to that of the bounds created with a single pf*Around*() operation.

Table 22-6 lists and describes the routines for extending bounding volumes.

Table 22-6. Routines to Extend Bounding Volumes

Routine

Operation

pfBoxExtendByPt ()

Extend a box to enclose a point.

pfBoxExtendByBox ()

Extend a box to enclose another box.

pfSphereExtendByPt ()

Extend a sphere to enclose a point.

pfSphereExtendBySphere ()

Extend a sphere to enclose a sphere.


Transforming Bounding Volumes

Transforming the volumes with an orthonormal transformation—that is, with no skew or nonuniform scaling, is straightforward for all of the volumes except for the axially aligned box. A straight transformation of the vertices does not suffice because the new box would no longer be axially aligned; so, an aligned box must be created that encloses the transformed vertices. Hence, a transformation of a box is not generally reversed by applying the inverse transformation to the new box.

Table 22-7 lists and describes the routines that transform bounding volumes.

Table 22-7. Routines to Transform Bounding Volumes

Routine

Operation

pfOrthoXformPlane ()

Transform a plane or half-space.

pfOrthoXformFrust ()

Transform a frustum.

pfXformBox ()

Transform and extend a bounding box.

pfOrthoXformCyl ()

Transform a cylinder.

pfOrthoXformSphere()

Transform a sphere.


Intersecting Volumes

OpenGL Performer provides a number of routines that test for intersection with volumes.

Point-Volume Intersection Tests

The point-volume intersection test returns PFIS_TRUE if the specified point is in the volume and PFIS_FALSE otherwise. Table 22-8 lists and describes the routines that test a point for inclusion within a bounding volume.

Table 22-8. Testing Points for Inclusion in a Bounding Volume

Routine

Test

pfBoxContainsPt ()

Point inside a box

pfSphereContainsPt ()

Point inside a sphere

pfCylContainsPt ()

Point inside a cylinder

pfHalfSpaceContainsPt ()

Point inside a half-space

pfFrustContainsPt ()

Point inside a frustum


Volume-Volume Intersection Tests

OpenGL Performer provides a number of volume-volume tests that are used internally for bounding-volume tests when culling to a view frustum or when testing a group of line segments against geometry in a scene (see “Intersecting with pfGeoSets”). You can intersect spheres, boxes, and cylinders against half-spaces and against frustums for culling. You can intersect cylinders against spheres for testing grouped segments against bounding volumes in a scene.

Table 22-9 lists and describes the routines that test for volume intersections.

Table 22-9. Testing Volume Intersections

Routine

Action: Test if A Inside B

pfHalfSpaceContainsSphere ()

Sphere inside a half-space

pfFrustContainsSphere ()

Sphere inside a frustum

pfSphereContainsSphere ()

Sphere inside a sphere

pfSphereContainsCyl ()

Cylinder inside a sphere

pfHalfSpaceContainsCyl ()

Cylinder inside a half-space

pfFrustContainsCyl ()

Cylinder inside a frustum

pfHalfSpaceContainsBox ()

Box inside a half-space

pfFrustContainsBox ()

Box inside a frustum

pfBoxContainsBox ()

Box inside a box

The volume-volume intersection tests are designed to quickly locate empty intersections for rejection during a cull. If the complete intersection test is too time-consuming, the result PFIS_MAYBE is returned to indicate that the two volumes might intersect.

The returned value is a bitwise OR of tokens, as shown in Table 22-10.

Table 22-10. Intersection Results

Test Result

Meaning

PFIS_FALSE

No intersection.

PFIS_MAYBE

Possible intersection.

PFIS_MAYBE | PFIS_TRUE

A contains at least part of B.

PFIS_MAYBE | PFIS_TRUE | PFIS_ALL_IN

A contains all of B.

This arrangement allows simple code such as that shown in Example 22-3.

Example 22-3. Quick Sphere Culling Against a Set of Half-Spaces

long
HSSContainsSphere(pfPlane **hs, pfSphere *sph, long numHS)
{
    long i, isect;
 
    isect = ~0;
 
    for (i = 0 ; i < numHS ; i++)
    {
        isect &= pfHalfSpaceContainsSphere(sph,hs[i]);
        if (isect == PFIS_FALSE)
            return isect;
     }
     /* if not ALL_IN all half spaces, don't know for sure */
     if (!(isect & PFIS_ALL_IN))
         isect &= ~PFIS_TRUE; 
     return isect;
}


Creating and Working with Line Segments

A pfSeg represents a line segment starting at position pos and extending for a distance length in the direction dir:

typedef struct {
    pfVec3 pos;
    pfVec3 dir;
    float length;
} pfSeg;

The routines that operate on pfSegs assume that dir is of unit length and that length is positive; otherwise, the results of operations are undefined.

You can create line segments in four ways:

  • Specify a point and a direction directly in the structure—pfSeg().

  • Specify two endpoints— pfMakePtsSeg().

  • Specify one endpoint and an orientation in polar coordinates— pfMakePolarSeg().

  • Specify starting and ending distances along an existing segment— pfClipSeg().

Intersection tests are the most important operations that use line segments. You can test the intersection of segments with volumes (half-spaces, spheres, and boxes), with 2D geometry (planes and triangles), and with geometry inside pfGeoSets.

Intersecting with Volumes

OpenGL Performer supports intersections of segments with three types of convex volumes. pfHalfSpaceIsectSeg() intersects a segment with the half-space defined by a plane with an outward facing normal. pfSphereIsectSeg() intersects with a sphere and pfBoxIsectSeg() intersects with an axially aligned box.

The intersection test of a segment and a convex volume can have one of five results:

  • The segment lies entirely outside the volume.

  • The segment lies entirely within the volume.

  • The segment lies partially inside the volume with the starting point inside.

  • The segment lies partially inside the volume with the ending point inside.

  • The segment lies partially inside the volume with both endpoints outside.

As with the volume-volume tests, the segment-volume intersection routines return a value that is the bitwise OR of some combination of the tokens PFIS_TRUE, PFIS_ALL_IN, PFIS_START_IN, and PFIS_MAYBE. (When PFIS_TRUE is set PFIS_MAYBE is also set for consistency with those routines that do quick intersection tests for culling.)

The functions take two arguments that return the distances along the segment of the starting and ending points. The return values are designed so that you can AND them together for testing for the intersection of a segment against the intersection of a number of volumes. For example, a convex polyhedron is defined as the intersection of a set of half-spaces. Example 22-4 shows how to intersect a segment with a polyhedron.

Example 22-4. Intersecting a Segment With a Convex Polyhedron

long
HSSIsectSeg(pfPlane **hs, pfSeg *seg, long nhs, float *d1,
              float *d2)
{
    long retval = 0xffff;
    for (long i = 0 ; i < nhs ; i++)
    {
        retval &= pfHalfSpaceIsectSeg(hs[i], seg, d1, d2);
        if (retval == 0)
            return 0;
        pfClipSeg(seg, *d1, *d2);
    }
    return retval;
}

Note that these routines do not actually clip the segment. If you want the segment to be clipped to the interior of the volume, you must call pfClipSeg(), as in the example above.

Intersecting with Planes and Triangles

Intersections with planes and triangles are simpler than those with volumes. pfPlaneIsectSeg() and pfTriIsectSeg() return either PFIS_TRUE or PFIS_FALSE, depending on whether an intersection has occurred. The distance of the intersection along the segment is returned in one of the arguments.

Intersecting with pfGeoSets

You can intersect line segments with the drawable geometry that is within pfGeoSets by calling pfGSetIsectSegs(). The operation is very similar to that of pfNodeIsectSegs(), except that rather than operating on an entire scene graph, only the triangles within the pfGeoSet are “traversed.”

pfGSetIsectSegs() takes a pfSegSet and tests to see whether any of the segments intersect the polygons inside the specified pfGeoSet. By default, information about the closest intersection along each segment is returned as a set of pfHit objects, one for each line segment in the request. Each pfHit object indicates the location of the intersection, the normal, and what element was hit. This element identification includes the index of the primitive within the pfGeoSet and the triangle index within the primitive (for tristrips and quads primitives), as well as the actual triangle vertices.

You can also extract information from a pfHit object using pfQueryHit() and pfMQueryHit(). (See “Intersection Requests: pfSegSets” in Chapter 4 and “Intersection Return Data: pfHit Objects” in Chapter 4 for more information about pfSegSets and pfHit objects.) The principal difference between those routines and pfGSetIsectSegs() is that with pfGSetIsectSegs() information concerning the libpf scene graph (such as transformation, geode, name, and path) is never used.

Two types of intersection testing are possible, as shown in Table 22-11.

Table 22-11. Available Intersection Tests

Test Name

Function

PFTRAV_IS_GSET

Intersect the segment with the bounding box of the pfGeoSet.

PFTRAV_IS_PRIM

Intersect the segment with the polygon-based primitives inside the pfGeoSet.

You can use PFTRAV_IS_GSET for crude collision detection and PFTRAV_IS_PRIM for fine-grained testing. You can enable both bits and dynamically choose whether to go down to the primitive level by using a discriminator callback (see “Discriminator Callbacks”). pfGSetIsectSegs() performs only primitive-level testing for pfGeoSets consisting of triangles ( PFGS_TRIS), quads ( PFGS_QUADS), and tristrips ( PFGS_TRISTRIPS), and all are decomposed into triangles.

Intersection Masks

Each pfGeoSet has an intersection mask that you set using pfGSetIsectMask(). The mask in the pfGeoSet is useful when pfGeoSets are embedded in a larger data structure; it allows you to define pfGeoSets to belong to different classes of geometry for intersection—for example, water, ground, foliage. pfGSetIsectSegs() also takes a mask, and an intersection test is performed only if the bitwise AND of the two masks is nonzero.

Discriminator Callbacks

If a callback is specified in pfGSetIsectSegs(), the callback function is invoked when a successful intersection occurs, either with the bounding box of the pfGeoSet or with a primitive. The discriminator can decide what action to take based on the information about the intersection contained in a pfHit object. The return value from the discriminator determines whether the current intersection is valid and should be copied into the return structure, whether the rest of the geometry in the pfGeoSet is examined, and whether the segment should be clipped before continuing.

Unless the return value includes the bit PFTRAV_IS_IGNORE, the intersection is considered successful and is copied into the array of pfHit structures for return.

The bits of the PFTRAV_* tokens determine whether to continue, as shown in Table 22-12.

Table 22-12. Discriminator Return Values

Result

Meaning

PFTRAV_CONT

Continue examining geometry inside the pfGeoSet.

PFTRAV_PRUNE

Terminate the traversal now.

PFTRAV_TERM

Terminate the traversal now.

The bits PFTRAV_IS_CLIP_END and PFTRAV_IS_CLIP_START cause the segment to be clipped at the end or at the start using the intersection point. By default, in the absence of a discriminator, segments are end-clipped at each successful intersection at the finest level (bounding box or primitive level) requested. Hence, the closest intersection point is always returned.

The discriminator is passed a pfHit. You can use pfQueryHit() to examine information about the intersection, including which segment number within the pfSegSet the intersection is for and the current segment as clipped by previous intersections.

General Math Routine Example Program

Example 22-5 demonstrates the use of many of the available OpenGL Performer math routines.

Example 22-5. Intersection Routines in Action

/*
 * simple test of pfCylIsectSeg 
 */
{
    pfVec3 tmpvec;
    pfSetVec3(pt1, -2.0f, 0.0f, 0.0f);
    pfSetVec3(pt2, 2.0f, 0.0f, 0.0f);
    
    pfMakePtsSeg(&seg1, pt1, pt2);
    
    pfSetVec3(cyl1.axis, 1.0f, 0.0f, 0.0f);
    pfSetVec3(cyl1.center, 0.0f, 0.0f, 0.0f);
    cyl1.radius = 0.5f;
    cyl1.halfLength = 1.0f;
    
    isect = pfCylIsectSeg(&cyl1, &seg1, &t1, &t2);
    
    pfClipSeg(&clipSeg, &seg1, t1, t2);
    AssertFloatEq(clipSeg.length, 2.0f, “clipSeg.length”);
    pfSetVec3(tmpvec, 1.0f, 0.0f, 0.0f);
    AssertVec3Eq(clipSeg.dir, tmpvec, “clipSeg.dir”);
    pfSetVec3(tmpvec, -1.0f, 0.0f, 0.0f);
    AssertVec3Eq(clipSeg.pos, tmpvec, “clipSeg.pos”);
}
/*
 * simple test of pfTriIsectSeg
 */
{
    pfVec3 tr1, tr2, tr3;
    pfSeg seg;
    float d = 0.0f;
    long i;
    
    for (i = 0 ; i < 30 ; i++)
    {
    float alpha = 2.0f * drand48() - 0.5f;
    float beta = 2.0f * drand48() - 0.5f;
    float lscale = 2.0f * drand48();
    float target;
    long shouldisect;
    
    MakeRandomVec3(tr1);
    MakeRandomVec3(tr2);
    MakeRandomVec3(tr3);
    MakeRandomVec3(pt1);
    pfCombineVec3(pt2, alpha, tr2, beta, tr3);
    pfCombineVec3(pt2, 1.0f, pt2, 1.0f - alpha - beta, tr1);
    
    pfMakePtsSeg(&seg, pt1, pt2);
    target = seg.length;
    seg.length = lscale * seg.length;
    
    isect = pfTriIsectSeg(tr1, tr2, tr3, &seg, &d);
    shouldisect = (alpha >= 0.0f && 
                   beta >= 0.0f &&
                   alpha + beta <= 1.0f &&
                   lscale >= 1.0f);
if (shouldisect)
    if (!isect)
        printf(“ERROR: missed\n”);
    else
        AssertFloatEq(d, target, “hit at wrong distance”);
 else if (isect)
    printf(“ERROR: hit\n”);
}
  
/*
 * simple test of pfCylContainsPt 
 */
{
    pfCylinder cyl;
    pfVec3 pt;
    pfVec3 perp;
    
    pfSetVec3(cyl.center, 1.0f, 10.0f, 5.0f);
    pfSetVec3(cyl.axis, 0.0f, 0.0f, 1.0f);
    pfSetVec3(perp, 1.0f, 0.0f, 0.0f);
    cyl.halfLength = 2.0f;
    cyl.radius = 0.5f;
    
    pfCopyVec3(pt, cyl.center);
    if (!pfCylContainsPt(&cyl, pt))
        printf(“center of cylinder not in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, 0.9f*cyl.halfLength,                     cyl.axis);
    if (!pfCylContainsPt(&cyl, pt))
        printf(“0.9*halfLength not in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, -0.9f*cyl.halfLength,                     cyl.axis);
    if (!pfCylContainsPt(&cyl, pt))
        printf(“-0.9*halfLength not in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, -0.9f*cyl.halfLength,                     cyl.axis);
    pfAddScaledVec3(pt, pt, 0.9f*cyl.radius, perp);
    if (!pfCylContainsPt(&cyl, pt))
        printf(printf(“-0.9*halfLength not in cylinder!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, 0.9f*cyl.halfLength,                     cyl.axis);
    pfAddScaledVec3(pt, pt, -0.9f*cyl.radius, perp);
    if (!pfCylContainsPt(&cyl, pt))
        printf(“-0.9*halfLength not in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, 1.1f*cyl.halfLength,                     cyl.axis);
    if (pfCylContainsPt(&cyl, pt))
        printf(“1.1*halfLength in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, -1.1f*cyl.halfLength,                     cyl.axis);
    if (pfCylContainsPt(&cyl, pt))
        printf(“-1.1*halfLength in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, -0.9f*cyl.halfLength,                     cyl.axis);
    pfAddScaledVec3(pt, pt, 1.1f*cyl.radius, perp);
    if (pfCylContainsPt(&cyl, pt))
          printf(“1.1*radius in cylinder!!!!\n”);
    
    pfAddScaledVec3(pt, cyl.center, 0.9f*cyl.halfLength,                     cyl.axis);
    pfAddScaledVec3(pt, pt, -1.1f*cyl.radius, perp);
    if (pfCylContainsPt(&cyl, pt))
        printf(“1.1*radius in cylinder!!!!\n”);
}