Book Corrections for 3D Game Engine Design (1st edition)


Book Corrections Organized by Date of Change

04 Oct 2006, page 263. The last displayed equation. The summation on the left-hand side has (2m+1) in the numerator; it should be in the denominator. The summation on the right-hand side has (m+n+1) in the numerator; it should be in the denominator.

20 Feb 2006, page 118. After the three bulleted items, there is an equation for d_i. The end of that equation has the term "-r^2" but should have "-2*r^2".

28 Jan 2006, page 114. The last bulleted item has "then y_{i+1} - y_i", which should be "then y_{i+1} = y_i".

04 Jan 2006, page 83. The first displayed equation for P(s,t), second equality, is missing some terms. The x-component should be
  x0/w0 + [w1*s/(w0+(w1-w0)*s+(w2-w0)*t]*(x1/w1 - x0/w0)
        + [w2*t/(w0+(w1-w0)*s+(w2-w0)*t]*(x2/w2 - x0/w0)
and the y-component should be
  y0/w0 + [w1*s/(w0+(w1-w0)*s+(w2-w0)*t]*(y1/w1 - y0/w0)
        + [w2*t/(w0+(w1-w0)*s+(w2-w0)*t]*(y2/w2 - y0/w0)
The third equality, though, is correct.

06 May 2005, page 367. The displayed equation at the top of the page is incorrect. The construction should instead be to compute B = [(det D)/lambda]A followed by A = B/Length(B). The equation is
  B = sum_{i=0}^2 [(c_i det D)/lambda]U_i
  
    = [1 - d01 - d02 + d12 * (d01 + d02 - d12)] * U_0 +
      [1 - d01 - d12 + d02 * (d01 + d12 - d02)] * U_1 +
      [1 - d02 - d12 + d01 * (d02 + d12 - d01)] * U_2
This equation involves only the known U vectors.

21 Apr 2005, page 9. Section 2.1.4 incorrectly states that a homogeneous matrix must have h33 = 1. Perspective projection matrices (discussed in Chapter 3) are the obvious counterexamples.

22 Mar 2005, page 398. The last displayed equation is a system of equations for solving for c1 and c2. The terms Dot(P,E1) and Dot(P,E2) should be Dot(P-V0,E1) and Dot(P-V0,E2).

03 Feb 2005, page 151. The first paragraph has "U_i = D/|D|". The D vectors are missing the subscript i, so it should be "U_i = D_i/|D_i|".

06 Dec 2004, page 371. My copy of the book has the corrections in place, but apparently I failed to post the changes some time ago. At the bottom of the page, the sentence should be the following, where the notation x^y denotes "x raised to the power y".
  The primitive blocks contain totally (2^N+1)^2 vertices, 2^N*(3*(2^N+1)-1)
  edges, and 2*4^N triangles.

17 Nov 2004, pages 254-256. The body for FindIntersection has an ending brace appearing just before the comment "// provide the application...". That brace should not be there, and the pseudocode after it and through the middle of page 255 is part of FindIntersection. The closing brace should occur after "return bContinue0...".

On page 256, the return should be FindIntersection(dt,node0,node1).

17 Sep 2004, page 25. In the displayed equation
  cb*sa - 0 = (cz*sx*sy+cx*sz) - (cz*sx + cx*sy*sz) = (1-sy)*(cz*sx-cx*sz)
the middle expression is the negative of what it should be, but the right-hand side is correct. It should read
  cb*sa - 0 = (cz*sx + cx*sy*sz) - (cz*sx*sy+cx*sz) = (1-sy)*(cz*sx-cx*sz)

22 May 2004, page 129. The pseudocode for interpolating vertex attributes uses a Bresenham-like method, but the interpolation works only when the slope of the (x,a) line segment (with respect to independent variable x) is 1 or smaller in absolute value. The pseudocode should just use the full Bresenham algorithm for the line segment with end points (x0,a0) and (x1,a1).

20 May 2004, page 387. The pseudocode that sets block.delta[i] values is missing .z values on the points P. The pseudocode should be
    block.delta[0] = (P(x,y).z + P(x+2s,y).z)/2 - P(x+s,y).z;
    block.delta[1] = (P(x+2s,y).z + P(x+2s,y+2s).z)/2 - P(x+2s,y+s).z;
    block.delta[2] = (P(x,y+2s).z + P(x+2s,y+2s).z)/2 - P(x+s,y+2s).z;
    block.delta[3] = (P(x,y).z + P(x,y+2s).z)/2 - P(x,y+s).z;
    if ( block.even )
        block.delta[4] = (P(x,y+2s).z + P(x+2s,y).z)/2 - P(x+s,y+s).z;
    else
        block.delta[4] = (P(x,y).z + P(x+2s,y+2s).z)/2 - P(x+s,y+s).z;

04 Jan 2004, page 149-150. This is no a correction, but an improvement in the algorithm for computing an oriented bounding box that contains two other oriented bounding boxes. The algorithm in the book computes the "merged box" axes by averaging the quaternions that represent the orientations of the two input boxes, then converting the average to a rotation matrix. The columns of the rotation matrix are the merged box axes. The input box vertices are projected onto those axes. Rather than use the average of input box centers as the merged box center, use the center of the box implied by the intervals of projection onto the axes. The end result is a box that is usually smaller than the one constructed in the book algorithm. The MergeBoxes code in WmlContBox3.cpp has been modified accordingly.

08 Dec 2003, page 29. In the section on axis-aligned boxes, change the upper limit on the set of points to "n-1" (rather than "n"). The pseudocode loop should then have "i < n" (rather than "i <= n"). The summation at the bottom of the page should have its upper limit changed to "n-1" (rather than "n"). In the first sentence of the subsection "Fitting Points with a Gaussian Distribution", the transpose symbol T is missing on the first term of the equation. It should be "(X-C)^T * M^{-1} (X-C)".

14 Oct 2003, page 398. Immediately before the last displayed equation for vector P, there is the fragment "Using c0 = 1 - c0 - c2". This should be "c0 = 1 - c1 - c2".

24 May 2003, page 183. Fourth paragraph, "In addition to the triangle edges already defined, set E2 = V1-V0." That should be "E2 = E1-E0" or "E2 = V2-V1".

19 May 2003, page 497. A minor correction. In Euler's method, a displayed equation has x_{i+1} (lower case x) and should be X_{i+1} (upper case x).

05 May 2003, page 192. The first paragraph of section 6.2.7 has "If N*D, then the line...". This should be "If N*D is not zero, then the line...". The last paragraph has "For the case of N*D, if there...". This should be "For the case of N*D is zero, if there...".

10 Mar 2003, page 245. The paragraph starting with "Section 6.9 illustrates" should start with "Section 6.8 illustrates". The paragraph starting with "Section 6.10 presents" should start with "Section 6.9 presents".

10 Mar 2003, page 9. The next-to-last displayed equation containing H is missing a vector. The equation should be
    +   +   +         + +   +
  H | V | = |  M  | T | | V | = ...
    |---|   |-----+---| |---|
    | w |   | S^T | 1 | | w |
    +   +   +         + +   +

28 Jan 2003, page 354. In the section "Slide to Point", second paragraph. The equation is missing the U and should be: F = E + clamp{T,tmin,tmax}U

08 Jan 2003, page 301. The definition of a cylinder surface involves a curve Y(u) and a desired offset D for the curve. The definition of the surface should be X(u,v) = Y(u) + v*D for 0 <= v <= 1. (The book has X(u,v) = (1-v)*Y(u)+v*D.) The partial derivatives are then dX/du = dY/du and dX/dv = D.

31 Dec 2002, page 271. The pseudocode
  intermediate[k] += data[i]*M[j][k];
should be
  intermediate[k] += f[i]*M[j][k];
since the data array was named f earlier in the pseudocode. The pseudocode
  float dt = t - b;
should be
  float dt = t - floor(t);
The actual implementation at the interpolation pages of the Wild Magic web site has the correct calculation.

27 Oct 2002, page 44. In Figure 2.2, the arrow for the level curve Q=V0 is pointing to the wrong curve. The figure with the correct arrow shown in blue is:

segseglevel


27 Oct 2002, page 51. In Figure 2.4, the arrow for the level curve Q=V0 is pointing to the wrong curve. The level curve tagged as Q=V2 is really the curve Q=V0. A level curve for Q=V2 is shown in blue. The first point of contact was (1,t0) but should be (s0,t0).

pttrilevel


27 Oct 2002, page 174. The right image in Figure 5.3 has "p0 <= -e0" and should be "p0 >= -e0".

07 Oct 2002, page 390. The pseudocode at the bottom of the page has
  if ( vertex.IsEnabled() )
but should be
  if ( not vertex.IsEnabled() )
The terrain source code containing this is correct (file MgcTerrainBlock.cpp, function SimplifyVertices).

30 Sep 2002, page 155. The pseudocode at the top of the page is missing the time parameter. The pseudocode should be
  for each child do
      child.UpdateGS(time,false);

03 Jul 2002, page 163. The section on culling of cylinders (intersection of cylinder and plane) has an incorrect formula for the extreme values. The displayed equation at the bottom of the page (with d-Dot(N,C)...) is missing the cylinder radius in front of the square root term. Worse is that the source code is wrong since it is missing the radius term and does not do the division by Dot(N,W).

The algorithm is simpler if you just look at projecting the cylinder onto a line normal to the plane. The plane is Dot(N,X)-d = 0. The projection of cylinder points X is Dot(N,X)-d where X = C+y0*U+y1*V+y2*W for y0^2+y1^2=r^2 and |y2| <= h/2 (using the book notation). The projection values are
  Dot(N,X)-d = (Dot(N,C)-d) +
     Dot(N,U)*(r*cos(A))+Dot(N,V)*(r*sin(A)) +
     Dot(N,W)*y2
The extreme values are
  min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)|
  max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)|
as I mention in my web document for intersection of two cylinders. An intersection occurs if and only if: min <= 0 <= max. The source code (MgcIntr3DPlnCyln.cpp) has been modified to use the new algorithm. I wrote a Wild Magic application to test the new code (rotate/translate cylinder and change colors when state goes from intersect to no-intersect or from cull to no-cull). The code appears to work fine now.

05 Jun 2002, page 17. The first paragraph of the subsection "Rotation Matrix to Quaternion" has a construction that was based on the originally typeset S matrix at the bottom of page 8. An earlier book correction changed S to its transpose, so the construction mentioned above must be modified. The formulas in that paragraph should be "(r21-r12,r02-r20,r10-r01) = 2 sin(theta) S", "x = (r21-r12)/(4w)", "y = (r02-r20)/(4w)", and "z = (r10-r01)/(4w)". However, the source code in MgcQuaternion.cpp is correct.

24 May 2002, page 486. The last sentence has "f(t) = at^2 + b^t + c" (b is raised to the power t). It should be "f(t) = at^2 + bt + c" (b times t).

29 Apr 2002, page 86. The paragraph "It is also convenient..." is a bit compact. Here is some extra verbage that gives some intuition about why you choose z' = bar(s).

Section 3.2.1 describes the perspective projection of line segments. The parameter s is for the "world" line segment. As s varies uniformly in [0,1], the world point on the line segment varies uniformly between end points. The parameter bar(s) is for the projected line segment. Equation (3.2) tells you how bar(s) varies in [0,1]--it is not uniform. Let us say that it varies "perspectively". For a line segment with an end point at z0 = n and an end point at z1 = f, you can select the uniformly varying world line parameter to be s = (z-z0)/(z1-z0) = (z-n)/(f-n). For those end points, w0 = z0/n = 1 and w1 = z1/n = f/n. The right-hand side of equation (3.2) for this case reduces to bar(s) = (f/(f-n))*(1-n/z). As z varies uniformly over the world segment, z' = bar(s) varies "perspectively" according to the formula shown. When you draw a line in the frame buffer, you take (approximately) uniform steps in screen space to draw the pixels. The z' values are your depth values that you put into the depth buffer. This argument applies to any attribute that you want to interpolate in a perspective correct way. You just replace z by A where A could be color, alpha, or any scalar value of visual interest.


22 Apr 2002, page 90. The first paragraph of Section 3.3.5 mentions V0 = ... = (x0,y0,z0) and V1 = ... = (x1,y1,z1). Instead the explicit naming of the coordinates should be for T0 = (x0,y0,z0) and T1 = (x1,y1,z1), the normalized projections in screen space of V0 and V1.

07 Apr 2002, page 423. The first paragraph is badly written and incorrect. The first problem is that the dot product test is not to see if N is inside the frustum cone, but to see if N is in the "complementary cone" for the view frustum.

cones

For the case of the eye point E being on the negative side of the plane, if N is in the complementary cone, then the view frustum cannot intersect the positive side of the plane. The image below shows the case when the eye point is on the negative side of the plane.

frustums

The left of the image shows a case when N is outside the complementary cone (in 2D, angle between N and the cone edge is smaller than pi/2). The right of the image shows a case when N is inside the complementary cone (in 2D, angle between N and the cone edge is larger than pi/2). The test for N being in the complementary cone is incorrectly written in the pseudocode. The correct test is "Dot(N,-D) >= cos(pi/2-A)" where N is the plane normal, D is the camera direction vector, and A is the angle of the frustum cone. The test is equivalent to: -Dot(N,D) >= sin(A).

The second problem with the paragraph is that "negative side" should read "positive side".

The essence of the pseudocode is that before drawing the portion of a scene in a half-space, you can test if the view frustum intersects that half-space. In the "sd < 0" case, this would be
else if ( sd < 0 )
{
    if ( frustum intersects positive half-space )
    {
        draw positive tree;
        draw coincident polygons;
    }
    draw negative tree;
}
A more aggressive test would be
else if ( sd < 0 )
{
    if ( frustum intersects positive half-space )
        draw positive tree;
    if ( frustum intersects separating plane )
        draw coincident polygons;
    draw negative tree;
}

06 Apr 2002, page 57. Top of page. The partitioning of R^3 for the ray has 14 components (not 16), 7 for r > 0 and 7 for r < 0. For the segment, the partitioning has 21 components (not 24), 7 for r < 0, 7 for r in [0,1], and 7 for r > 1. The last paragraph has "P0 = = P+s*E0+t*E1". The "P" should be "B". Also, E0 and E1 are not necessarily unit length, so the text should have "s = Dot(D,E0)/Dot(E0,E0)" and "t = Dot(D,E1)/Dot(E1,E1)". (The pseudocode does compute s and t properly.)

26 Mar 2002, page 50. The first sentence of the second paragraph is "An alternate way of visualizing ... with the plane s = 1." The plane should be "s + t = 1".

25 Mar 2002, page 199. The pseudocode TestCylinderPlane has "capsule.D" and "capsule.P" in the first two lines of code. These should be "cylinder.D" and "cylinder.P".

25 Mar 2002, page 201. The pseudocode TestEllipsoidPlane has "sphere.C" in the first line of code. This should be "ellipsoid.C".

25 Mar 2002, page 90. A typographical error in section 3.3.5. The formula for T0 has "Theta transpose", but should be "R transpose".

The end of the paragraph containing this says "... for some sigma_x > 0 and sigma_y > 0. The...". The descriptions of sigma_x and sigma_y are incorrect. Twice those values are approximately the number of pixels per unit of distance. Replace that part of the paragraph "for some sigma_x ... on the view plane." by "where sigma_x = (Sx-1)/2 and sigma_y = (Sy-1)/2 for a screen with Sx pixels in width and Sy pixels in height."


25 Mar 2002, page 91. The middle of the page has "Psi = (Uz*Dx-Ux*Dz,Uz*Dy-Uy*Dz,0) = (Lx,-Ly,0)". The last term should be "(Ly,-Lx,0)". Later in that paragraph there is a term starting with "(Ly^2+Ly^2)*DeltaX^2-...". The term should start with "(Ly^2+Uy^2)".

07 Mar 2002, page 8. The matrix for S at the bottom of the page should be transposed to correspond to a counterclockwise rotation about the U axis when angle Theta > 0. Page 15 has the correct matrix for S.

28 Feb 2002, page 292. The last sentence fragment should say "corresponding to those eigenvectors in {vector(N)}^perp where N = Gradient(F)/Length(Gradient(F))". I believe the minus sign in front of the displayed equation does not belong there.

22 Feb 2002, page 85-86. Equation (3.8) is only part of the promised transformation to an orthogonal frustum. The conditions x' in [-1,1] and y' in [-1,1] only apply for the viewport on the near plane (when z = n). The sentence should say |x'| <= z/n and |y'| <= z/n. Setting w = z/n, x" = x'/w, and y" = y'/w, then it is the case that |x"| <= 1 and |y"| <= 1.

05 Feb 2002, page 134-136. The clipped triangles should retain the same vertex ordering as the parent triangle. Figure 3.10 shows the parent triangle as having clockwise ordering. The clipped triangles in the three cases are mentioned in the text in counterclockwise order. The clipper implementation does not care about the parent ordering, only that the ordering be preserved in the clipping. Thus, theoretically it does not matter if the images of the triangles in Figure 3.10 are reflected (and the book text remain unchanged) or that the book text change (and the images remain unchanged). In my copy I have left the images as is--parent triangles are clockwise ordered. The text changes are as follows.
  • Page 134, last paragraph. The new triangle is T1 = {V3,V1,V4;E3,E4,E5}.
  • Page 135, first paragraph. The new triangle is T1 = {V3,V1,V2;E3,E1,E4}.
  • Page 136, top of page. The new triangles are T1 = {V3,V1,V2;E3,E1,E5} and T2 = {V3,V2,V4;E5,E4,E6}.

01 Feb 2002, page 457. The two lines of pseudocode "MgcNodePtr spChild = new MgcNode;" should be "MgcNodePtr spChild = pkNode;".

15 Jan 2002, page 49. The equation e = -Dot(E1,B-P) should be e = +Dot(E1,B-P).

14 Jan 2002, page 8. In section 2.1, I stated that a matrix is a linear transformation. To be more precise, the definition of a linear transformation should be provided. A matrix happens to be something that represents the transformation with respect to specified bases for the domain and range of the transformation. I took the liberty not to cloud the issue with facts from linear algebra, but some mathematicians might cringe at my taking this liberty. So be it.

An error of omission: In the first sentence of section 2.1.2, the definition for rotation matrix not only requires R^{-1} = R^T, it also requires that the determinant is 1 to rule out "reflections". A matrix for which R^{-1} = R^T is said to be "orthogonal" (includes rotations and reflections).


14 Jan 2002, page 103. As mentioned, the intent of the paragraph on attenuation for a spot light is that, for unit length direction D and unit length cone axis U, the attenuation is 1 if the angle between D and U is 0 and decreases to 0 as the angle between D and U approaches the spot angle T. Some computer graphics books suggest just using A = pow(Dot(D,U),E) for the attenuation (E >= 0 is the spot exponent). Simple to compute, but it does not have the property that A = 0 when Dot(D,U) = cos(T). However, just set A = 0 for Dot(D,U) <= cos(T) (D is outside the cone). My displayed formula for d_{spot} was an attempt to attain A = 0 at the cone boundary. Unfortunately, it does not satisfy A = 1 when Dot(D,U) = 1. In the case of E = 1, if you want a linear drop-off based on angles, then use A = 1-acos(Dot(D,U)/T works. Another possibility that is nonlinear based on angles is A = (Dot(D,U)-cos(T))/(1-cos(T)). However, both functions are more expensive than just using Dot(D,U), so stick with the cheaper one to compute, even though A is not zero when Dot(D,U) = cos(T).

11 Jan 2002, page 17. The first sentence in subsection Quaternion to Rotation Matrix should say "The problem is to compute R given...".

11 Jan 2002, page 19. Section 2.4.1, first paragraph. A sentence should be added: "Also, Tan^{-1}(y,x) corresponds to the function call atan2(y,x)." I used Tan^{-1}(y,x) through the sections, but never defined it.

11 Jan 2002, page 20. The sentences at the top of the page have two occurrences of "Tan^{-1}2(r10,r11)". These should be "Tan^{-1}(r10,r11)".

11 Jan 2002, page 103. The first sentence of the paragraph starting with "For spot lights..." should say end with "...the light direction is D = (V-P)/|V-P| where P is the light position and V is an illuminated point". I had introduced P and V on page 101.

The equation for d_{spot} has three occurrences of |D|. Since D was defined to be a unit-length vector, |D| = 1 and this term can be removed from the equations. However, if you happen to implement light directions that are not unit length, the equations are correct as stated.

The first paragraph of the section Diffuse Light. I intended the surface normal N to be an outer pointing normal. In the sentence "Moreover, the intensity ... is pi/2 radians or larger", the last word should be "smaller". The equation for the color C_{diff} should have "max{-Dot(N,D),0}". (The minus sign must be included.)


11 Jan 2002, page 104. I had made an earlier change to the C_{spec} formula. The term Dot(R,D) was changed to Dot(R,U). Two other occurrences on that page must change. In the paragraph before the formula, there is Dot(R,D)^{M_{shine}}. This D should be U. In the C_{final} formula at the bottom of the page, Dot(R,D^{i}), this D should be U. Finally, the following figure illustrates the various quantities.

diagram

The reflection vector R in the book is the negative of what it should be. The correct formula is R = D - 2*Dot(N,D)*N. With the correct formula, when R = U, you get the maximum specularity. Note that in this case Dot(R,U) = 1.


22 Dec 2001, page 449. The loop body of IsDerivedFromClass in the multiple inheritance scheme is incorrect. The inheritance tree must be traversed and the query RTTI compared to each of the RTTI objects in that tree. For single inheritance, only a list is traversed and the book pseudocode shows that MgcObject takes the responsibility to do this. A better system is to make MgcRTTI be responsible for the list/tree traversal.

Single inheritance:
bool MgcRTTI::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    if ( pkQueryRTTI == this ) return true;
    if ( m_pkBaseRTTI )
        return m_pkBaseRTTI->IsDerivedFromClass(pkQueryRTTI);
    return false;
}
Multiple inheritance:
bool MgcRTTI::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    if ( pkQueryRTTI == this ) return true;
    for (unsigned int i = 0; i < m_uiNumBaseClasses; i++)
    {
        const MgcRTTI* pkBaseRTTI = m_apkBaseRTTI[i];
        if ( pkBaseRTTI->IsDerivedFromClass(pkQueryRTTI) )
            return true;
    }
    return false;
}
A base class in either inheritance system has:
bool MgcObject::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    return GetRTTI()->IsDerivedFromClass(pkQueryRTTI);
}

24 Sep 2001, page 273. The TCB spline multipliers to adjust for nonuniform spacing in time are swapped from what they should be. The multiplier for equation (7.21) should be 2*D(i-1)/(D(i-1)+D(i)) and the multiplier for equation (7.22) should be 2*D(i)/(D(i-1)+D(i)).

24 Sep 2001, page 62-63. The pseudocode for the squared distances of point-to-solid-box and point-to-hollow-box are incorrect. (The C++ source code is correct.) They should be point-to-solid-box and point-to-hollow-box.

24 Sep 2001, pages 350, 356, 523. I misspelled Jeff Lander's name as "Landers" when it should be "Lander". Sorry Jeff :) Thought I had done a search on this (probably replaced the wrong way, Lander by Landers).

24 Sep 2001, pages 176, 178. The boxes in Figures 5.4 and 5.5 were incorrectly redrawn. The original art for Figure 5.4 is

diagram

The original art for Figure 5.5 is

diagram


24 Sep 2001, page 177. I had corrected on Apr 05, 2001 the Table 5.1 by replacing the three occurrences of W by V. The same replacement must occur in the last sentence of section 5.2.1. That is, replace the |W| by |V|.

02 Aug 2001, page 232. At the bottom of the page, the additional axes are listed as Wx(NxE_i) and Wx(MxF_i). These should be WxE_i and WxF_i. The idea is to project the triangles onto a plane whose normal is W, compute the edge directions for the projection (the directions are in that plane), and take the cross product with W to get normal vectors to the edges. That said, the idea of considering additional axes for linear motion is not the best way to look at the problem. Instead you should download the document Method of Separating Axes.

10 Jul 2001, page 419. In the middle of the pseudocode there are references to positiveList and negativeList. These should be posList and negList (the original names used at the beginning of ConstructTree).

07 Jul 2001, page 175. The "else if" block of the Clip pseudocode has first line "ti = numer/denom;". This is a cut-and-paste error from the previous page. The line should be removed.

05 Apr 2001, page 176. The last displayed equation should have a vector V, not a vector M.

05 Apr 2001, page 177. In Table 5.1, the first three rows of the column for Rs should have vectors V instead of vectors W.

04 Apr 2001, page 272. To be consistent with index i-indices used on tangents on that page, the displayed equation for tangents with bias terms should have their indices changed from n to i.

19 Mar 2001, page 278. The return type of Bisect should be void, not bool.

12 Mar 2001, page 174-175. The pseudocode blocks for Clip should have "return numer <= 0" instead of "return numer > 0". The actual code in MgcIntr3DLinBox.cpp has the correct expressions.

12 Mar 2001, page 104. The formula for C_{spec} should have the dot product R*U instead of R*D.

07 Mar 2001, page 491. Equation (B.2) should have (C_1)/4, not (C_1)/2.

27 Feb 2001, page 12. The second line after equation (2.9) has the statement "hat(u) hat(u) = -1". This is a true statement. To verify it, use equation (2.2) and the fact that u0*u0+u1*u1+u2*u2 = 1.

Equation (2.12) has the terms "hat(u) theta". These are correct. "hat(u)" is a quaternion with no w-term and "theta" is a scalar. The product of quaternion*scalar is the same as scalar*quaternion.


27 Feb 2001, page 14. The first displayed equation starts with "hat(w) = inverse(M)(hat(v))". The "v" is a typographical error. It should be "hat(w) = inverse(M)(hat(w))". The equation is obtained by "diagram chasing" (yes, a real mathematical term). The following diagram illustrates the various transformations and their relationship to each other.

diagram


27 Feb 2001, page 17. The matrix in Equation (2.13) is incorrect. The transpose of the matrix is what should be displayed. The source code on the CD-ROM does have the correct conversions between quaternions and rotation matrices.

27 Feb 2001, page 21-24. For Rx*Rz*Ry, case thetaZ = -PI/2 should have thetaX = atan2(-r20,r22) and case thetaZ = PI/2 should have thetaX = atan2(r20,r22). For Ry*Rx*Rz, case thetaX = -PI/2 should have thetaY = atan2(-r01,r00) and case thetaX = PI/2 should have thetaY = atan2(r01,r00). For Rz*Ry*Rx, case thetaY = -PI/2 should have thetaZ = atan2(-r01,-r02) and case thetaY = PI/2 should have thetaZ = -atan2(r01,r02). The corresponding code in MgcMatrix3.cpp has been corrected. It has also been set up to compare the relevant matrix terms to +1 and -1 rather than PI/2 and -PI/2 (to avoid one asin call in the non-unique factorization cases).

27 Feb 2001, page 25. The first sentence after the matrix equation at the top of the page should have: ThetaY = Sin^{-1}(c_a s_b). In the middle of the page, the block of equations for ThetaX, ThetaY, and ThetaZ, the denominators of the arguments of the four Sin^{-1} terms should have c_a*s_b instead of c_b*s_a. At the end of the page, the block of equations for ThetaX, ThetaY, and ThetaZ, the four Sin^{-1} terms should have Cos^{-1} instead.

27 Feb 2001, page 34. "We need to compute the largest b0 so that all points lie above the hemicircle w^2+(v-b0)^2 = r^2 with v <= a0." The last inequality should be "v <= b0". "Similarly, there is a smallest value b1 so that all points lie below the hemicircle w^2+(v-b0)^2 = r^2 with v >= b1." The circle equation should be w^2+(v-b1)^2 = r^2. The references to "above" and "below" assume that the V-axis is horizontal and the W-axis is vertical, even though the point pairs are (v_i,w_i).

27 Feb 2001, page 35. "The corner must be adjusted to K1 = A + a1 U + b1 V so that...". The a1 and b1 terms were defined earlier. The intent for K1 is that the a1 and b1 values are increased to meet the constraint mentioned in the paragraph. This may lead to confusion, so replace a1 and b1 in the K1 equation and in later occurrences in the paragraph to new symbols, something like a1' and b1' (or some such).

27 Feb 2001, page 50. The last sentence should read: "Because the global minimum occurs in region 2, the negative of the gradient at the corner (0,1) cannot point inside D."

27 Feb 2001, page 114. The equation in the sixth line from the top should be s_i = (y_0 - y_i) + (dy/dx)(x_i + 1 - x_0).

27 Feb 2001, page 150. The projections in the last two loops of the OBB merge pseudocode should be onto the interpolated box axes. The expression Dot(box0.axis[j],delta) should be Dot(box.axis[j],delta) and the expression Dot(box1.axis[j],delta) should be Dot(box.axis[j],delta). The source code has the same error (correction has been uploaded).

27 Feb 2001, page 188. The formula for Q(t) is missing a couple of vector D terms. The formula should be
  Q(t) = |(W-(Dot(D,W)/Dot(D,D))D)t +
           ((C-P)-(Dot(D,C-P)/Dot(D,D))D)|^2
The boldface D terms are the ones missing in the text.

27 Feb 2001, page 227. The formula for D at the bottom of the page should read: D = (U0 + T*V1) - (C + T*V0).

27 Feb 2001, page 233. Tables 6.10, 6.11, and 6.12 have three equations each for one of the x_i as a fraction with denominator of the form Dot(N,Cross(A_i,E_j)). The formula is valid as long as the denominator is not zero, but the denominator can be zero (in which case the OBB has a face parallel to the triangle). Each of the nine equations of this type was constructed by a cross of Equation (6.6) with an E_j vector followed by a dot by N. Instead of the dot by N, a dot with the separating axis direction should be used. That is, if L = Cross(A_i,E_j), the corresponding formula for x_i should have the N replaced by Cross(A_i,E_j) and the |N|^2 term in the numerator replaced by Dot(N,Cross(A_i,E_j)).

27 Feb 2001, page 279. The term \vec{x}_{t_i} is used in equation (7.23) before it is defined a few lines later.

27 Feb 2001, page 281. The first displayed equation for sigma_i has two cases for i. The second case is n+1 <= i <= 2*n. The summation in that case should be sum_{ell=i-n}^{n} S_{ell,i-ell} (the book has upper limit of 2*n which is incorrect).

27 Feb 2001, page 308. The displayed equation before equation (8.3) is incorrect. A bicubic rectangle patch can have nonzero fourth, fifth, or sixth derivative terms. There are four identities, two for s and two for t, just like the ones for cubic curves:
    x(s,t) = (x(s+d,t)+x(s-d,t)-d^2*x_{ss}(s,t))/2,
    x_{ss}(s,t) = (x_{ss}(s+d,t)+x_{ss}(s-d,t))/2,
    x(s,t) = (x(s,t+d)+x(s,t+d)-d^2*x_{tt}(s,t))/2,
    x_{tt}(s,t) = (x_{tt}(s,t+d)+x_{tt}(s,t-d))/2.
The other identities metioned on page 309 are obtained by taking partial derivatives of these identities.

27 Feb 2001, page 345. The derivation of 'squad' in equation (9.6) is the right idea, but notationally has problems. The paragraph after the equation uses the same c0 and c1 for u(t), v(t), and S(t). The quantities should be u(t) = c0(t)p+c1(t)q, v(t) = d0(t)a+d1(t)b, S(t) = e0(2t(1-t))u(t) + e1(2t(1-t))v(t). The derivative calculations proceed as in the proof and the evaluations of the c0, c1, d0, d1, e0, and e1 coefficients at t=0 and t=1 will lead to the same equations for S'(0) and S'(1).

27 Feb 2001, page 415. The pseudocode for 'void Render (Region region)' should be
    planeSet.Add(portal.planes);
    Render(portal.adjacentRegion);
    planeSet.Remove(portal.planes);

27 Feb 2001, page 445. The single line of code in the constructor for MgcRTTI should be
    m_pkBaseRTTI = pkBaseRTTI;

27 Feb 2001, page 477. In the equations for dE/da and dE/db, the -2 coefficient should be a 2 and the 2 coefficient should be a -2.

27 Feb 2001, page 479. In the equations for dE/da, dE/db, and dE/dc, the -2 coefficient should be a 2 and the 2 coefficient should be a -2.

27 Feb 2001, page 480. The equation at the bottom of the page should be E(C) = sum_{i=0}^{n} (C*V_i)^2 = C^T M C.

27 Feb 2001, page 489. Table B.1 header should have t^3+3t^2-1.

Book Corrections Organized by Page Number

page 8. The matrix for S at the bottom of the page should be transposed to correspond to a counterclockwise rotation about the U axis when angle Theta > 0. Page 15 has the correct matrix for S.

page 8. In section 2.1, I stated that a matrix is a linear transformation. To be more precise, the definition of a linear transformation should be provided. A matrix happens to be something that represents the transformation with respect to specified bases for the domain and range of the transformation. I took the liberty not to cloud the issue with facts from linear algebra, but some mathematicians might cringe at my taking this liberty. So be it.

An error of omission: In the first sentence of section 2.1.2, the definition for rotation matrix not only requires R^{-1} = R^T, it also requires that the determinant is 1 to rule out "reflections". A matrix for which R^{-1} = R^T is said to be "orthogonal" (includes rotations and reflections).


page 9. Section 2.1.4 incorrectly states that a homogeneous matrix must have h33 = 1. Perspective projection matrices (discussed in Chapter 3) are the obvious counterexamples.

page 9. The next-to-last displayed equation containing H is missing a vector. The equation should be
    +   +   +         + +   +
  H | V | = |  M  | T | | V | = ...
    |---|   |-----+---| |---|
    | w |   | S^T | 1 | | w |
    +   +   +         + +   +

page 12. The second line after equation (2.9) has the statement "hat(u) hat(u) = -1". This is a true statement. To verify it, use equation (2.2) and the fact that u0*u0+u1*u1+u2*u2 = 1.

Equation (2.12) has the terms "hat(u) theta". These are correct. "hat(u)" is a quaternion with no w-term and "theta" is a scalar. The product of quaternion*scalar is the same as scalar*quaternion.


page 14. The first displayed equation starts with "hat(w) = inverse(M)(hat(v))". The "v" is a typographical error. It should be "hat(w) = inverse(M)(hat(w))". The equation is obtained by "diagram chasing" (yes, a real mathematical term). The following diagram illustrates the various transformations and their relationship to each other.

diagram


page 17. The first paragraph of the subsection "Rotation Matrix to Quaternion" has a construction that was based on the originally typeset S matrix at the bottom of page 8. An earlier book correction changed S to its transpose, so the construction mentioned above must be modified. The formulas in that paragraph should be "(r21-r12,r02-r20,r10-r01) = 2 sin(theta) S", "x = (r21-r12)/(4w)", "y = (r02-r20)/(4w)", and "z = (r10-r01)/(4w)". However, the source code in MgcQuaternion.cpp is correct.

page 17. The first sentence in subsection Quaternion to Rotation Matrix should say "The problem is to compute R given...".

page 17. The matrix in Equation (2.13) is incorrect. The transpose of the matrix is what should be displayed. The source code on the CD-ROM does have the correct conversions between quaternions and rotation matrices.

page 19. Section 2.4.1, first paragraph. A sentence should be added: "Also, Tan^{-1}(y,x) corresponds to the function call atan2(y,x)." I used Tan^{-1}(y,x) through the sections, but never defined it.

page 20. The sentences at the top of the page have two occurrences of "Tan^{-1}2(r10,r11)". These should be "Tan^{-1}(r10,r11)".

page 21-24. For Rx*Rz*Ry, case thetaZ = -PI/2 should have thetaX = atan2(-r20,r22) and case thetaZ = PI/2 should have thetaX = atan2(r20,r22). For Ry*Rx*Rz, case thetaX = -PI/2 should have thetaY = atan2(-r01,r00) and case thetaX = PI/2 should have thetaY = atan2(r01,r00). For Rz*Ry*Rx, case thetaY = -PI/2 should have thetaZ = atan2(-r01,-r02) and case thetaY = PI/2 should have thetaZ = -atan2(r01,r02). The corresponding code in MgcMatrix3.cpp has been corrected. It has also been set up to compare the relevant matrix terms to +1 and -1 rather than PI/2 and -PI/2 (to avoid one asin call in the non-unique factorization cases).

page 25. The first sentence after the matrix equation at the top of the page should have: ThetaY = Sin^{-1}(c_a s_b). In the middle of the page, the block of equations for ThetaX, ThetaY, and ThetaZ, the denominators of the arguments of the four Sin^{-1} terms should have c_a*s_b instead of c_b*s_a. At the end of the page, the block of equations for ThetaX, ThetaY, and ThetaZ, the four Sin^{-1} terms should have Cos^{-1} instead.

page 25. In the displayed equation
  cb*sa - 0 = (cz*sx*sy+cx*sz) - (cz*sx + cx*sy*sz) = (1-sy)*(cz*sx-cx*sz)
the middle expression is the negative of what it should be, but the right-hand side is correct. It should read
  cb*sa - 0 = (cz*sx + cx*sy*sz) - (cz*sx*sy+cx*sz) = (1-sy)*(cz*sx-cx*sz)

page 29. In the section on axis-aligned boxes, change the upper limit on the set of points to "n-1" (rather than "n"). The pseudocode loop should then have "i < n" (rather than "i <= n"). The summation at the bottom of the page should have its upper limit changed to "n-1" (rather than "n"). In the first sentence of the subsection "Fitting Points with a Gaussian Distribution", the transpose symbol T is missing on the first term of the equation. It should be "(X-C)^T * M^{-1} (X-C)".

page 34. "We need to compute the largest b0 so that all points lie above the hemicircle w^2+(v-b0)^2 = r^2 with v <= a0." The last inequality should be "v <= b0". "Similarly, there is a smallest value b1 so that all points lie below the hemicircle w^2+(v-b0)^2 = r^2 with v >= b1." The circle equation should be w^2+(v-b1)^2 = r^2. The references to "above" and "below" assume that the V-axis is horizontal and the W-axis is vertical, even though the point pairs are (v_i,w_i).

page 35. "The corner must be adjusted to K1 = A + a1 U + b1 V so that...". The a1 and b1 terms were defined earlier. The intent for K1 is that the a1 and b1 values are increased to meet the constraint mentioned in the paragraph. This may lead to confusion, so replace a1 and b1 in the K1 equation and in later occurrences in the paragraph to new symbols, something like a1' and b1' (or some such).

page 44. In Figure 2.2, the arrow for the level curve Q=V0 is pointing to the wrong curve. The figure with the correct arrow shown in blue is:

segseglevel


page 51. In Figure 2.4, the arrow for the level curve Q=V0 is pointing to the wrong curve. The level curve tagged as Q=V2 is really the curve Q=V0. A level curve for Q=V2 is shown in blue. The first point of contact was (1,t0) but should be (s0,t0).

pttrilevel


page 49. The equation e = -Dot(E1,B-P) should be e = +Dot(E1,B-P).

page 50. The first sentence of the second paragraph is "An alternate way of visualizing ... with the plane s = 1." The plane should be "s + t = 1".

page 50. The last sentence should read: "Because the global minimum occurs in region 2, the negative of the gradient at the corner (0,1) cannot point inside D."

page 57. Top of page. The partitioning of R^3 for the ray has 14 components (not 16), 7 for r > 0 and 7 for r < 0. For the segment, the partitioning has 21 components (not 24), 7 for r < 0, 7 for r in [0,1], and 7 for r > 1. The last paragraph has "P0 = = P+s*E0+t*E1". The "P" should be "B". Also, E0 and E1 are not necessarily unit length, so the text should have "s = Dot(D,E0)/Dot(E0,E0)" and "t = Dot(D,E1)/Dot(E1,E1)". (The pseudocode does compute s and t properly.)

page 62-63. The pseudocode for the squared distances of point-to-solid-box and point-to-hollow-box are incorrect. (The C++ source code is correct.) They should be point-to-solid-box and point-to-hollow-box.

page 83. The first displayed equation for P(s,t), second equality, is missing some terms. The x-component should be
  x0/w0 + [w1*s/(w0+(w1-w0)*s+(w2-w0)*t]*(x1/w1 - x0/w0)
        + [w2*t/(w0+(w1-w0)*s+(w2-w0)*t]*(x2/w2 - x0/w0)
and the y-component should be
  y0/w0 + [w1*s/(w0+(w1-w0)*s+(w2-w0)*t]*(y1/w1 - y0/w0)
        + [w2*t/(w0+(w1-w0)*s+(w2-w0)*t]*(y2/w2 - y0/w0)
The third equality, though, is correct.

page 85-86. Equation (3.8) is only part of the promised transformation to an orthogonal frustum. The conditions x' in [-1,1] and y' in [-1,1] only apply for the viewport on the near plane (when z = n). The sentence should say |x'| <= z/n and |y'| <= z/n. Setting w = z/n, x" = x'/w, and y" = y'/w, then it is the case that |x"| <= 1 and |y"| <= 1.

page 86. The paragraph "It is also convenient..." is a bit compact. Here is some extra verbage that gives some intuition about why you choose z' = bar(s).

Section 3.2.1 describes the perspective projection of line segments. The parameter s is for the "world" line segment. As s varies uniformly in [0,1], the world point on the line segment varies uniformly between end points. The parameter bar(s) is for the projected line segment. Equation (3.2) tells you how bar(s) varies in [0,1]--it is not uniform. Let us say that it varies "perspectively". For a line segment with an end point at z0 = n and an end point at z1 = f, you can select the uniformly varying world line parameter to be s = (z-z0)/(z1-z0) = (z-n)/(f-n). For those end points, w0 = z0/n = 1 and w1 = z1/n = f/n. The right-hand side of equation (3.2) for this case reduces to bar(s) = (f/(f-n))*(1-n/z). As z varies uniformly over the world segment, z' = bar(s) varies "perspectively" according to the formula shown. When you draw a line in the frame buffer, you take (approximately) uniform steps in screen space to draw the pixels. The z' values are your depth values that you put into the depth buffer. This argument applies to any attribute that you want to interpolate in a perspective correct way. You just replace z by A where A could be color, alpha, or any scalar value of visual interest.


page 90. The first paragraph of Section 3.3.5 mentions V0 = ... = (x0,y0,z0) and V1 = ... = (x1,y1,z1). Instead the explicit naming of the coordinates should be for T0 = (x0,y0,z0) and T1 = (x1,y1,z1), the normalized projections in screen space of V0 and V1.

page 90. A typographical error in section 3.3.5. The formula for T0 has "Theta transpose", but should be "R transpose".

The end of the paragraph containing this says "... for some sigma_x > 0 and sigma_y > 0. The...". The descriptions of sigma_x and sigma_y are incorrect. Twice those values are approximately the number of pixels per unit of distance. Replace that part of the paragraph "for some sigma_x ... on the view plane." by "where sigma_x = (Sx-1)/2 and sigma_y = (Sy-1)/2 for a screen with Sx pixels in width and Sy pixels in height."


page 91. The middle of the page has "Psi = (Uz*Dx-Ux*Dz,Uz*Dy-Uy*Dz,0) = (Lx,-Ly,0)". The last term should be "(Ly,-Lx,0)". Later in that paragraph there is a term starting with "(Ly^2+Ly^2)*DeltaX^2-...". The term should start with "(Ly^2+Uy^2)".

page 103. As mentioned, the intent of the paragraph on attenuation for a spot light is that, for unit length direction D and unit length cone axis U, the attenuation is 1 if the angle between D and U is 0 and decreases to 0 as the angle between D and U approaches the spot angle T. Some computer graphics books suggest just using A = pow(Dot(D,U),E) for the attenuation (E >= 0 is the spot exponent). Simple to compute, but it does not have the property that A = 0 when Dot(D,U) = cos(T). However, just set A = 0 for Dot(D,U) <= cos(T) (D is outside the cone). My displayed formula for d_{spot} was an attempt to attain A = 0 at the cone boundary. Unfortunately, it does not satisfy A = 1 when Dot(D,U) = 1. In the case of E = 1, if you want a linear drop-off based on angles, then use A = 1-acos(Dot(D,U)/T works. Another possibility that is nonlinear based on angles is A = (Dot(D,U)-cos(T))/(1-cos(T)). However, both functions are more expensive than just using Dot(D,U), so stick with the cheaper one to compute, even though A is not zero when Dot(D,U) = cos(T).

page 103. The first sentence of the paragraph starting with "For spot lights..." should say end with "...the light direction is D = (V-P)/|V-P| where P is the light position and V is an illuminated point". I had introduced P and V on page 101.

The equation for d_{spot} has three occurrences of |D|. Since D was defined to be a unit-length vector, |D| = 1 and this term can be removed from the equations. However, if you happen to implement light directions that are not unit length, the equations are correct as stated.

The first paragraph of the section Diffuse Light. I intended the surface normal N to be an outer pointing normal. In the sentence "Moreover, the intensity ... is pi/2 radians or larger", the last word should be "smaller". The equation for the color C_{diff} should have "max{-Dot(N,D),0}". (The minus sign must be included.)


page 104. I had made an earlier change to the C_{spec} formula. The term Dot(R,D) was changed to Dot(R,U). Two other occurrences on that page must change. In the paragraph before the formula, there is Dot(R,D)^{M_{shine}}. This D should be U. In the C_{final} formula at the bottom of the page, Dot(R,D^{i}), this D should be U. Finally, the following figure illustrates the various quantities.

diagram

The reflection vector R in the book is the negative of what it should be. The correct formula is R = D - 2*Dot(N,D)*N. With the correct formula, when R = U, you get the maximum specularity. Note that in this case Dot(R,U) = 1.


page 104. The formula for C_{spec} should have the dot product R*U instead of R*D.

page 114. The equation in the sixth line from the top should be s_i = (y_0 - y_i) + (dy/dx)(x_i + 1 - x_0).

page 114. The last bulleted item has "then y_{i+1} - y_i", which should be "then y_{i+1} = y_i".

page 118. After the three bulleted items, there is an equation for d_i. The end of that equation has the term "-r^2" but should have "-2*r^2".

page 129. The pseudocode for interpolating vertex attributes uses a Bresenham-like method, but the interpolation works only when the slope of the (x,a) line segment (with respect to independent variable x) is 1 or smaller in absolute value. The pseudocode should just use the full Bresenham algorithm for the line segment with end points (x0,a0) and (x1,a1).

page 134-136. The clipped triangles should retain the same vertex ordering as the parent triangle. Figure 3.10 shows the parent triangle as having clockwise ordering. The clipped triangles in the three cases are mentioned in the text in counterclockwise order. The clipper implementation does not care about the parent ordering, only that the ordering be preserved in the clipping. Thus, theoretically it does not matter if the images of the triangles in Figure 3.10 are reflected (and the book text remain unchanged) or that the book text change (and the images remain unchanged). In my copy I have left the images as is--parent triangles are clockwise ordered. The text changes are as follows.
  • Page 134, last paragraph. The new triangle is T1 = {V3,V1,V4;E3,E4,E5}.
  • Page 135, first paragraph. The new triangle is T1 = {V3,V1,V2;E3,E1,E4}.
  • Page 136, top of page. The new triangles are T1 = {V3,V1,V2;E3,E1,E5} and T2 = {V3,V2,V4;E5,E4,E6}.

page 149-150. This is no a correction, but an improvement in the algorithm for computing an oriented bounding box that contains two other oriented bounding boxes. The algorithm in the book computes the "merged box" axes by averaging the quaternions that represent the orientations of the two input boxes, then converting the average to a rotation matrix. The columns of the rotation matrix are the merged box axes. The input box vertices are projected onto those axes. Rather than use the average of input box centers as the merged box center, use the center of the box implied by the intervals of projection onto the axes. The end result is a box that is usually smaller than the one constructed in the book algorithm. The MergeBoxes code in WmlContBox3.cpp has been modified accordingly.

page 150. The projections in the last two loops of the OBB merge pseudocode should be onto the interpolated box axes. The expression Dot(box0.axis[j],delta) should be Dot(box.axis[j],delta) and the expression Dot(box1.axis[j],delta) should be Dot(box.axis[j],delta). The source code has the same error (correction has been uploaded).

page 151. The first paragraph has "U_i = D/|D|". The D vectors are missing the subscript i, so it should be "U_i = D_i/|D_i|".

page 155. The pseudocode at the top of the page is missing the time parameter. The pseudocode should be
  for each child do
      child.UpdateGS(time,false);

page 163. The section on culling of cylinders (intersection of cylinder and plane) has an incorrect formula for the extreme values. The displayed equation at the bottom of the page (with d-Dot(N,C)...) is missing the cylinder radius in front of the square root term. Worse is that the source code is wrong since it is missing the radius term and does not do the division by Dot(N,W).

The algorithm is simpler if you just look at projecting the cylinder onto a line normal to the plane. The plane is Dot(N,X)-d = 0. The projection of cylinder points X is Dot(N,X)-d where X = C+y0*U+y1*V+y2*W for y0^2+y1^2=r^2 and |y2| <= h/2 (using the book notation). The projection values are
  Dot(N,X)-d = (Dot(N,C)-d) +
     Dot(N,U)*(r*cos(A))+Dot(N,V)*(r*sin(A)) +
     Dot(N,W)*y2
The extreme values are
  min = (Dot(N,C)-d) - r*sqrt(1-Dot(N,W)^2) - (h/2)*|Dot(N,W)|
  max = (Dot(N,C)-d) + r*sqrt(1-Dot(N,W)^2) + (h/2)*|Dot(N,W)|
as I mention in my web document for intersection of two cylinders. An intersection occurs if and only if: min <= 0 <= max. The source code (MgcIntr3DPlnCyln.cpp) has been modified to use the new algorithm. I wrote a Wild Magic application to test the new code (rotate/translate cylinder and change colors when state goes from intersect to no-intersect or from cull to no-cull). The code appears to work fine now.

page 174. The right image in Figure 5.3 has "p0 <= -e0" and should be "p0 >= -e0".

page 174-175. The pseudocode blocks for Clip should have "return numer <= 0" instead of "return numer > 0". The actual code in MgcIntr3DLinBox.cpp has the correct expressions.

page 175. The "else if" block of the Clip pseudocode has first line "ti = numer/denom;". This is a cut-and-paste error from the previous page. The line should be removed.

page 176. The last displayed equation should have a vector V, not a vector M.

pages 176, 178. The boxes in Figures 5.4 and 5.5 were incorrectly redrawn. The original art for Figure 5.4 is

diagram

The original art for Figure 5.5 is

diagram


page 177. I had corrected on Apr 05, 2001 the Table 5.1 by replacing the three occurrences of W by V. The same replacement must occur in the last sentence of section 5.2.1. That is, replace the |W| by |V|.

page 177. In Table 5.1, the first three rows of the column for Rs should have vectors V instead of vectors W.

page 183. Fourth paragraph, "In addition to the triangle edges already defined, set E2 = V1-V0." That should be "E2 = E1-E0" or "E2 = V2-V1".

page 188. The formula for Q(t) is missing a couple of vector D terms. The formula should be
  Q(t) = |(W-(Dot(D,W)/Dot(D,D))D)t +
           ((C-P)-(Dot(D,C-P)/Dot(D,D))D)|^2
The boldface D terms are the ones missing in the text.

page 192. The first paragraph of section 6.2.7 has "If N*D, then the line...". This should be "If N*D is not zero, then the line...". The last paragraph has "For the case of N*D, if there...". This should be "For the case of N*D is zero, if there...".

page 199. The pseudocode TestCylinderPlane has "capsule.D" and "capsule.P" in the first two lines of code. These should be "cylinder.D" and "cylinder.P".

page 201. The pseudocode TestEllipsoidPlane has "sphere.C" in the first line of code. This should be "ellipsoid.C".

page 227. The formula for D at the bottom of the page should read: D = (U0 + T*V1) - (C + T*V0).

page 232. At the bottom of the page, the additional axes are listed as Wx(NxE_i) and Wx(MxF_i). These should be WxE_i and WxF_i. The idea is to project the triangles onto a plane whose normal is W, compute the edge directions for the projection (the directions are in that plane), and take the cross product with W to get normal vectors to the edges. That said, the idea of considering additional axes for linear motion is not the best way to look at the problem. Instead you should download the document Method of Separating Axes.

page 233. Tables 6.10, 6.11, and 6.12 have three equations each for one of the x_i as a fraction with denominator of the form Dot(N,Cross(A_i,E_j)). The formula is valid as long as the denominator is not zero, but the denominator can be zero (in which case the OBB has a face parallel to the triangle). Each of the nine equations of this type was constructed by a cross of Equation (6.6) with an E_j vector followed by a dot by N. Instead of the dot by N, a dot with the separating axis direction should be used. That is, if L = Cross(A_i,E_j), the corresponding formula for x_i should have the N replaced by Cross(A_i,E_j) and the |N|^2 term in the numerator replaced by Dot(N,Cross(A_i,E_j)).

page 245. The paragraph starting with "Section 6.9 illustrates" should start with "Section 6.8 illustrates". The paragraph starting with "Section 6.10 presents" should start with "Section 6.9 presents".

pages 254-256. The body for FindIntersection has an ending brace appearing just before the comment "// provide the application...". That brace should not be there, and the pseudocode after it and through the middle of page 255 is part of FindIntersection. The closing brace should occur after "return bContinue0...".

On page 256, the return should be FindIntersection(dt,node0,node1).

page 263. The last displayed equation. The summation on the left-hand side has (2m+1) in the numerator; it should be in the denominator. The summation on the right-hand side has (m+n+1) in the numerator; it should be in the denominator.

page 271. The pseudocode
  intermediate[k] += data[i]*M[j][k];
should be
  intermediate[k] += f[i]*M[j][k];
since the data array was named f earlier in the pseudocode. The pseudocode
  float dt = t - b;
should be
  float dt = t - floor(t);
The actual implementation at the interpolation pages of the Wild Magic web site has the correct calculation.

page 272. To be consistent with index i-indices used on tangents on that page, the displayed equation for tangents with bias terms should have their indices changed from n to i.

page 273. The TCB spline multipliers to adjust for nonuniform spacing in time are swapped from what they should be. The multiplier for equation (7.21) should be 2*D(i-1)/(D(i-1)+D(i)) and the multiplier for equation (7.22) should be 2*D(i)/(D(i-1)+D(i)).

page 278. The return type of Bisect should be void, not bool.

page 279. The term \vec{x}_{t_i} is used in equation (7.23) before it is defined a few lines later.

page 281. The first displayed equation for sigma_i has two cases for i. The second case is n+1 <= i <= 2*n. The summation in that case should be sum_{ell=i-n}^{n} S_{ell,i-ell} (the book has upper limit of 2*n which is incorrect).

page 292. The last sentence fragment should say "corresponding to those eigenvectors in {vector(N)}^perp where N = Gradient(F)/Length(Gradient(F))". I believe the minus sign in front of the displayed equation does not belong there.

page 301. The definition of a cylinder surface involves a curve Y(u) and a desired offset D for the curve. The definition of the surface should be X(u,v) = Y(u) + v*D for 0 <= v <= 1. (The book has X(u,v) = (1-v)*Y(u)+v*D.) The partial derivatives are then dX/du = dY/du and dX/dv = D.

page 308. The displayed equation before equation (8.3) is incorrect. A bicubic rectangle patch can have nonzero fourth, fifth, or sixth derivative terms. There are four identities, two for s and two for t, just like the ones for cubic curves:
    x(s,t) = (x(s+d,t)+x(s-d,t)-d^2*x_{ss}(s,t))/2,
    x_{ss}(s,t) = (x_{ss}(s+d,t)+x_{ss}(s-d,t))/2,
    x(s,t) = (x(s,t+d)+x(s,t+d)-d^2*x_{tt}(s,t))/2,
    x_{tt}(s,t) = (x_{tt}(s,t+d)+x_{tt}(s,t-d))/2.
The other identities metioned on page 309 are obtained by taking partial derivatives of these identities.

page 345. The derivation of 'squad' in equation (9.6) is the right idea, but notationally has problems. The paragraph after the equation uses the same c0 and c1 for u(t), v(t), and S(t). The quantities should be u(t) = c0(t)p+c1(t)q, v(t) = d0(t)a+d1(t)b, S(t) = e0(2t(1-t))u(t) + e1(2t(1-t))v(t). The derivative calculations proceed as in the proof and the evaluations of the c0, c1, d0, d1, e0, and e1 coefficients at t=0 and t=1 will lead to the same equations for S'(0) and S'(1).

page 354. In the section "Slide to Point", second paragraph. The equation is missing the U and should be: F = E + clamp{T,tmin,tmax}U

pages 350, 356, 523. I misspelled Jeff Lander's name as "Landers" when it should be "Lander". Sorry Jeff :) Thought I had done a search on this (probably replaced the wrong way, Lander by Landers).

page 367. The displayed equation at the top of the page is incorrect. The construction should instead be to compute B = [(det D)/lambda]A followed by A = B/Length(B). The equation is
  B = sum_{i=0}^2 [(c_i det D)/lambda]U_i
  
    = [1 - d01 - d02 + d12 * (d01 + d02 - d12)] * U_0 +
      [1 - d01 - d12 + d02 * (d01 + d12 - d02)] * U_1 +
      [1 - d02 - d12 + d01 * (d02 + d12 - d01)] * U_2
This equation involves only the known U vectors.

page 371. My copy of the book has the corrections in place, but apparently I failed to post the changes some time ago. At the bottom of the page, the sentence should be the following, where the notation x^y denotes "x raised to the power y".
  The primitive blocks contain totally (2^N+1)^2 vertices, 2^N*(3*(2^N+1)-1)
  edges, and 2*4^N triangles.

page 387. The pseudocode that sets block.delta[i] values is missing .z values on the points P. The pseudocode should be
    block.delta[0] = (P(x,y).z + P(x+2s,y).z)/2 - P(x+s,y).z;
    block.delta[1] = (P(x+2s,y).z + P(x+2s,y+2s).z)/2 - P(x+2s,y+s).z;
    block.delta[2] = (P(x,y+2s).z + P(x+2s,y+2s).z)/2 - P(x+s,y+2s).z;
    block.delta[3] = (P(x,y).z + P(x,y+2s).z)/2 - P(x,y+s).z;
    if ( block.even )
        block.delta[4] = (P(x,y+2s).z + P(x+2s,y).z)/2 - P(x+s,y+s).z;
    else
        block.delta[4] = (P(x,y).z + P(x+2s,y+2s).z)/2 - P(x+s,y+s).z;

page 390. The pseudocode at the bottom of the page has
  if ( vertex.IsEnabled() )
but should be
  if ( not vertex.IsEnabled() )
The terrain source code containing this is correct (file MgcTerrainBlock.cpp, function SimplifyVertices).

page 398. Immediately before the last displayed equation for vector P, there is the fragment "Using c0 = 1 - c0 - c2". This should be "c0 = 1 - c1 - c2".

page 398. The last displayed equation is a system of equations for solving for c1 and c2. The terms Dot(P,E1) and Dot(P,E2) should be Dot(P-V0,E1) and Dot(P-V0,E2).

page 415. The pseudocode for 'void Render (Region region)' should be
    planeSet.Add(portal.planes);
    Render(portal.adjacentRegion);
    planeSet.Remove(portal.planes);

page 419. In the middle of the pseudocode there are references to positiveList and negativeList. These should be posList and negList (the original names used at the beginning of ConstructTree).

page 423. The first paragraph is badly written and incorrect. The first problem is that the dot product test is not to see if N is inside the frustum cone, but to see if N is in the "complementary cone" for the view frustum.

cones

For the case of the eye point E being on the negative side of the plane, if N is in the complementary cone, then the view frustum cannot intersect the positive side of the plane. The image below shows the case when the eye point is on the negative side of the plane.

frustums

The left of the image shows a case when N is outside the complementary cone (in 2D, angle between N and the cone edge is smaller than pi/2). The right of the image shows a case when N is inside the complementary cone (in 2D, angle between N and the cone edge is larger than pi/2). The test for N being in the complementary cone is incorrectly written in the pseudocode. The correct test is "Dot(N,-D) >= cos(pi/2-A)" where N is the plane normal, D is the camera direction vector, and A is the angle of the frustum cone. The test is equivalent to: -Dot(N,D) >= sin(A).

The second problem with the paragraph is that "negative side" should read "positive side".

The essence of the pseudocode is that before drawing the portion of a scene in a half-space, you can test if the view frustum intersects that half-space. In the "sd < 0" case, this would be
else if ( sd < 0 )
{
    if ( frustum intersects positive half-space )
    {
        draw positive tree;
        draw coincident polygons;
    }
    draw negative tree;
}
A more aggressive test would be
else if ( sd < 0 )
{
    if ( frustum intersects positive half-space )
        draw positive tree;
    if ( frustum intersects separating plane )
        draw coincident polygons;
    draw negative tree;
}

page 445. The single line of code in the constructor for MgcRTTI should be
    m_pkBaseRTTI = pkBaseRTTI;

page 449. The loop body of IsDerivedFromClass in the multiple inheritance scheme is incorrect. The inheritance tree must be traversed and the query RTTI compared to each of the RTTI objects in that tree. For single inheritance, only a list is traversed and the book pseudocode shows that MgcObject takes the responsibility to do this. A better system is to make MgcRTTI be responsible for the list/tree traversal.

Single inheritance:
bool MgcRTTI::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    if ( pkQueryRTTI == this ) return true;
    if ( m_pkBaseRTTI )
        return m_pkBaseRTTI->IsDerivedFromClass(pkQueryRTTI);
    return false;
}
Multiple inheritance:
bool MgcRTTI::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    if ( pkQueryRTTI == this ) return true;
    for (unsigned int i = 0; i < m_uiNumBaseClasses; i++)
    {
        const MgcRTTI* pkBaseRTTI = m_apkBaseRTTI[i];
        if ( pkBaseRTTI->IsDerivedFromClass(pkQueryRTTI) )
            return true;
    }
    return false;
}
A base class in either inheritance system has:
bool MgcObject::IsDerivedFromClass (const MgcRTTI* pkQueryRTTI) const
{
    return GetRTTI()->IsDerivedFromClass(pkQueryRTTI);
}

page 457. The two lines of pseudocode "MgcNodePtr spChild = new MgcNode;" should be "MgcNodePtr spChild = pkNode;".

page 477. In the equations for dE/da and dE/db, the -2 coefficient should be a 2 and the 2 coefficient should be a -2.

page 479. In the equations for dE/da, dE/db, and dE/dc, the -2 coefficient should be a 2 and the 2 coefficient should be a -2.

page 480. The equation at the bottom of the page should be E(C) = sum_{i=0}^{n} (C*V_i)^2 = C^T M C.

page 486. The last sentence has "f(t) = at^2 + b^t + c" (b is raised to the power t). It should be "f(t) = at^2 + bt + c" (b times t).

page 489. Table B.1 header should have t^3+3t^2-1.

page 491. Equation (B.2) should have (C_1)/4, not (C_1)/2.

page 497. A minor correction. In Euler's method, a displayed equation has x_{i+1} (lower case x) and should be X_{i+1} (upper case x).