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.
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 |
---|---|---|
d = (x, y, z) | ||
d = v | ||
d = -v | ||
d = v1 + v2 | ||
d = v1 - v2 | ||
d = sv | ||
d = v1 + sv2 | ||
d = s1 v1 + s2 v2 | ||
d = d/|d| | none | |
d = v1 x v2 | none | |
d = vM, where v = (vx, vy, vz,) and M is the 4x3 submatrix. | none | |
d = vM/dw, where v = (vx, vy, vz, 1) | none | |
d = vM, where v = (vx, vy, vz, 0) | none | |
v1 . v2 | ||
|v| | ||
|v2 - v1|2 | ||
|v2 - v1| | ||
Returns TRUE if v1 = v2 and FALSE, otherwise. | ||
Returns TRUE if each element of v1 is within tol of the corresponding element of v2 and FALSE, otherwise. |
A pfMatrix is a 4x4 array of floating-point numbers that is used primarily to specify a transformation in homogeneous coordinates (x, y, z, w). 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. | |
D = M such that v2 = v1 M. v1 , v2 are normalized. | none | |
D = M, where M is the rotation of the quaternion q. | none | |
D = M, where M rotates by deg around (x, y, z). | none | |
D = RPH, where R, P, and H are the transforms for roll, pitch, and heading. | none | |
D = M, where M translates by (x, y, z). | ||
D = M, where M scales by (x, y, z). | ||
D = M, where M rotates by (h, p, r) and translates by (x, y, z) with h, p, r, x, y, and z all specified by c. | none | |
Returns in q a quaternion with the rotation specified by s. | none | |
Returns in d the rotation and translation specified by s. | none | |
Set rth row of D equal to (x, y, z, w). | ||
(*x, *y, *z, *w) = rth row of M. | ||
Set cth column of D equal to (x, y, z, w). | ||
(*x, *y, *z, *w) = cth column of M. | ||
Set rth row of D equal to v. | ||
d = rth row of M. | ||
Set cth column of D equal to v. | ||
d = cth column of M. | ||
D = M. | ||
D = M1 + M2. | none | |
D = M1 - M2. | none | |
D = M1 M2. | none | |
D = DM. | none | |
D = MD. | none | |
D = MT. | none | |
D = TM, where T translates by (x, y, z). | none | |
D = MT, where T translates by (x, y, z). | none | |
D = RM, where R rotates by deg around (x, y, z). | none | |
D = MR, where R rotates by deg around (x, y, z). | none | |
D = SM, where S scales by (x, y, z). | none | |
D = MS, where S scales by (x, y, z). | none | |
D = M-1 for general matrices. | none | |
D = M-1 with M affine. | none | |
D = M-1 with M orthogonal. | none | |
D = M-1 with M orthonormal. | none | |
D = M-1 with M equal to the identity matrix. | none | |
Returns TRUE if D = M and FALSE, otherwise | ||
Returns TRUE if each element of D is within tol of the corresponding element of M and FALSE, otherwise |
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”); |
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 |
---|---|---|
Sets q to rotation of a degrees about (x, y, z). | none | |
Sets *a to angle and (*x, *y, *z) to axis of rotation represented by q. | none | |
d = conjugate of q. | ||
Returns length of q. | ||
d = q1 * q2. | ||
PFDIV_QUAT | ||
d = 1 / q1. |
| |
d = exp(q). | none | |
d = ln(q). | none | |
d = interpolation with weight t between q1 (t=0.0) and q2 (t=1.0). | none | |
d = quadratic interpolation between q1 and q2. | none | |
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”); { |
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 |
---|---|
Allocate storage. | |
Reset the stack. | |
Duplicate the TOS and push it on the stack. | |
Pop the stack. | |
Set the TOS matrix. | |
Get the TOS matrix. | |
Get a pointer to the TOS matrix. | |
Return the current depth of the stack. | |
Pre-multiply the TOS by a translation. | |
Post-multiply the TOS by a translation. | |
Pre-multiply the TOS by a rotation. | |
Post-multiply the TOS by a rotation. | |
Pre-multiply the TOS by a scale factor. | |
Post-multiply the TOS by a scale factor. |
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:
This section describes how to define geometric volumes.
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.
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 < z < maxz, .
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.
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 |
---|---|
Box enclosing a set of points | |
Box enclosing a set of boxes | |
Box enclosing a set of spheres | |
Cylinder around a set of segments | |
Sphere around a set of points | |
Sphere around a set of boxes | |
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 |
---|---|
Extend a box to enclose a point. | |
Extend a box to enclose another box. | |
Extend a sphere to enclose a point. | |
Extend a sphere to enclose a sphere. |
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 |
---|---|
Transform a plane or half-space. | |
Transform a frustum. | |
Transform and extend a bounding box. | |
Transform a cylinder. | |
Transform a sphere. |
OpenGL Performer provides a number of routines that test for intersection with volumes.
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 |
---|---|
Point inside a box | |
Point inside a sphere | |
Point inside a cylinder | |
Point inside a half-space | |
Point inside a frustum |
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 |
---|---|
Sphere inside a half-space | |
Sphere inside a frustum | |
Sphere inside a sphere | |
Cylinder inside a sphere | |
Cylinder inside a half-space | |
Cylinder inside a frustum | |
Box inside a half-space | |
Box inside a frustum | |
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 |
---|---|
No intersection. | |
Possible intersection. | |
A contains at least part of B. | |
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; } |
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:
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.
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.
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.
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 |
---|---|
Intersect the segment with the bounding box of the pfGeoSet. | |
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.
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.
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 |
---|---|
Continue examining geometry inside the pfGeoSet. | |
Terminate the traversal now. | |
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.
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”); } |