Chapter 13: The Torus class

13.1 Mathematics of the torus

We now explore how to use the ray tracer code to draw a torus, which is more commonly known as a donut shape. This shape is implemented by class Torus, which is declared in imager.h and implemented in torus.cpp. Two constants suffice to define the shape of the torus: $A$, the distance from the center of the hole inside the torus and the center of its tube, and $B$, the radius of the tube, as shown in Figure 13.1. In order to ensure that there is a hole inside the torus, we will assume that $A > B$.

Figure 13.1: The torus dimensions $A$ and $B$.

Note that Torus is a reorientable solid (it derives from SolidObject_Reorientable), so it technically resides in object space coordinates $(r,s,t)$. However, I will use $(x, y, z)$ throughout the derivation here to more closely match the x, y, and z member variables of the C++ type Vector. Hopefully this will be more helpful than confusing when looking back and forth between the C++ code and the math in this chapter.

Our torus is centered at the origin $(0,0,0)$ and oriented such that it is cut in half (like a bagel) by the $xy$ plane. The following equation describes every point on the surface of a torus aligned this way. See the Wikipedia article Torus for more information about the derivation of this formula.

\[ (x^2 + y^2 + z^2 + A^2 - B^2)^2 = 4 A^2(x^2 + y^2) \]

13.2 Intersection of a ray and a torus

As with every other kind of solid object we want to depict with the ray tracer code, we will need to solve equations for intersections with a ray, and figure out how to calculate a surface normal vector for an intersection point once we find it. This will be the most challenging of the algebra problems we have faced so far. As usual, we will substitute occurrences of $x$, $y$, and $z$ in the torus equation with linear expressions of the parameter $u$ based on the parametric ray equation $\mathbf{P} = \mathbf{D} + u \mathbf{E}$:

\[ \begin{cases} & x = E_x u + D_x \\ & y = E_y u + D_y \\ & z = E_z u + D_z \end{cases} \]

The substitution yields a single equation in $u$ that expresses where along the ray (if anywhere) that ray passes through the surface of the torus:

\begin{align*} & \left[ (E_x u + D_x)^2 + (E_y u + D_y)^2 + (E_z u + D_z)^2 + (A^2 - B^2) \right] ^2 \\ = \quad & 4 A^2 \left[ (E_x u + D_x)^2 + (E_y u + D_y)^2 \right] \end{align*}

Expanding the innermost squared terms that involve $u$, we find

\begin{align*} \big[ & (E_x^2 u^2 + 2 D_x E_x u + D_x^2) \\ + & (E_y^2 u^2 + 2 D_y E_y u + D_y^2) \\ + & (E_z^2 u^2 + 2 D_z E_z u + D_z^2) \big] ^2 \\ = \\ 4 A^2 \big[ & (E_x^2 u^2 + 2 D_x E_x u + D_x^2) \\ + & (E_y^2 u^2 + 2 D_y E_y u + D_y^2) \big] \end{align*}

Collecting like powers of $u$ on both sides of the equation, we have

\begin{align*} & \big[ (E_x^2 + E_y^2 + E_z^2)u^2 + 2(D_x E_x + D_y E_y + D_z E_z)u + (D_x^2 + D_y^2 + D_z^2 + A^2 - B^2) \big] ^2 \\ = \quad & 4 A^2 \big[ (E_x^2 + E_y^2)u^2 + 2(D_x E_x + D_y E_y)u + (D_x^2 + D_y^2) \big] \end{align*}

Let's define some constant symbols to simplify the algebra. Note that all of these definitions contain nothing but known constant terms, so the C++ algorithm will have no difficulty calculating their values.

\[ \text{Let } \begin{cases} \quad G&= 4 A^2 (E_x^2 + E_y^2) \\ \quad H&= 8 A^2 (D_x E_x + D_y E_y) \\ \quad I&= 4 A^2 (D_x^2 + D_y^2) \\ \quad J&= E_x^2 + E_y^2 + E_z^2 = \lvert \mathbf{E} \rvert ^ 2 \\ \quad K&= 2(D_x E_x + D_y E_y + D_z E_z) = 2(\mathbf{D} \cdot \mathbf{E}) \\ \quad L&= D_x^2 + D_y^2 + D_z^2 + A^2 - B^2 = \lvert \mathbf{D} \rvert ^2 + (A^2 - B^2) \end{cases} \]

The equation becomes much more concise:

\[ (J u^2 + K u + L)^2 = G u^2 + H u + I \]

Expanding the squared term on the left is aided by a 3-by-3 grid showing every term multiplied by every term, as shown in Figure 13.2.

Figure 13.2: Grid that aids in expanding $(J u^2 + K u + L)^2$.

Grouping the grid in upward sloping diagonals, as shown by the five curves in the figure, reveals collections of like powers of $u$. Summing the terms along these diagonals and factoring out the common powers of $u$, we have:

\begin{align*} & J^2 u^4 + 2 J K u^3 + (2 J L + K^2)u^2 + 2 K L u + L^2 = G u^2 + H u + I \end{align*}

Subtracting the right side of the equation from both sides and collecting like terms, the equation becomes:

\begin{align*} & J^2u^4 + 2JKu^3 + (2JL+K^2-G)u^2 + (2KL-H)u + (L^2-I) = 0 \end{align*}

13.3 Solving the quartic intersection equation

An equation like this, where the unknown variable $u$ appears as $u^4$, $u^3$, $u^2$, $u^1$, and $u^0$ (the constant term $L^2-I^2$) is known as a quartic equation. It is solvable analytically, but the procedure is very complicated. I will not detail the solution here because there are many resources on the Internet that do a fine job. The source file algebra.cpp and its header file algebra.h contain a pair of overloaded functions called SolveQuarticEquation, one geared for quartic equations with all real coefficients (as we have here), and another more general version for those with complex coefficients. The complex version of SolveQuarticEquation is based on techniques from the Wikipedia article called Quartic equation. The real version calls the complex version and filters out any solutions that are not real numbers. Only real values of $u$ have meaning in the parametric ray equation, so we use the real version of SolveQuadraticEquation to solve the torus equation above.

Take a look in torus.cpp at the method Torus::SolveIntersections. Given a vantage point and a direction vector, it finds all positive real solutions for $u$. Because the intersection equation is quartic in $u$, there can be up to four real solutions. This makes sense intuitively because a ray can pierce a torus in up to four places, marked $u_0$ through $u_3$ in Figure 13.3.

Figure 13.3: A ray can pierce a torus in up to four locations.

Torus::SolveIntersections fills in the parameter uArray with 0 to 4 positive real solutions for $u$, and returns the number of solutions. The caller must use the return value to know how many of the elements of uArray are valid.

The Torus class derives from SolidObject_Reorientable so that the math can assume a fixed position and orientation in space, while still allowing us to create images of a torus translated and rotated any way we want. Torus::ObjectSpace_AppendAllIntersections receives a vantage point and direction vector that have already been translated into object space coordinates. It calls SolveIntersections to find 0 to 4 positive real values of $u$. Each valid value $u$ value gives us an intersection point when $u$ is plugged into the parametric ray equation $\mathbf{P} = \mathbf{D} + u \mathbf{E}$.

13.4 Surface normal vector for a point on a torus

We are left with the problem of finding the surface normal unit vector for a point on the surface of a torus. It is possible to solve this problem using 3D calculus and partial derivatives, but with a little geometric intuition and a pair of diagrams, there is a much easier way. Figures 13.4 and 13.5 show two views of a point $\mathbf{P}$ on the surface of a torus, one edge-on, the other top-down.

Figure 13.4: Surface normal vector $\mathbf{\hat{n}}$ for a point on a torus (edge view).

Figure 13.5: Surface normal vector $\mathbf{\hat{n}}$ for a point on a torus (top view).

In both views, $\mathbf{O}$ is the origin $(0,0,0)$, which is also the center of the torus hole. We let $\mathbf{Q}$ be the point at the center of the torus tube that is closest to the intersection point $\mathbf{P}$. The edge-on view on the left shows a cross-section of the torus passing through the plane of $\mathbf{O}$, $\mathbf{P}$, and $\mathbf{Q}$. The top-down view on the right depicts that same cross-section plane as a diagonal dashed line. Looking at these diagrams, it becomes clear that the surface normal vector points in the exact opposite direction from $\mathbf{P}$ as the point $\mathbf{Q}$ does.

Assuming we have already calculated $\mathbf{P}$ from $u$ using $\mathbf{P} = \mathbf{D} + u \mathbf{E}$, how do we determine where $\mathbf{Q}$ is? The answer comes from combining three facts we know about $\mathbf{Q}$:

  1. $\mathbf{Q}$ lies on the $xy$ plane, so its $z$ component must be 0. This means $\mathbf{Q} = (Q_x, Q_y, 0)$, where $Q_x$ and $Q_y$ are unknowns to be found.
  2. The distance from $\mathbf{Q}$ to the origin $\mathbf{O}$ is the constant $A$ (by definition— $A$ is the distance from the center of the torus hole to any point on the center of the solid tube). So $\lvert \mathbf{Q} \rvert = A$.
  3. The "shadow" of $\mathbf{P}$ on the $xy$ plane (marked $\mathbf{P}'$ in Figure 13.4) lies in the same direction from the origin as $\mathbf{Q}$. If $\mathbf{P}=(P_x,P_y,P_z)$, then $\mathbf{P}'=(P_x,P_y,0)$.

We find $\mathbf{Q}$ by converting $\mathbf{P}'$ into a unit vector, then multiplying that unit vector by $A$ (which we know to be the same as $\lvert \mathbf{Q} \rvert$), giving us $\mathbf{Q}$ itself:

\begin{align*} \mathbf{Q} &= A \frac{\mathbf{P}'}{\lvert \mathbf{P}' \rvert} = \left( \frac{A}{\lvert \mathbf{P}' \rvert} \right) \mathbf{P}' \\ \mathbf{Q} &= \frac{A}{\sqrt{P_x^2 + P_y^2}} (P_x, P_y, 0) \end{align*}

Let $\mathbf{N}$ be the vector from $\mathbf{Q}$ to $\mathbf{P}$:

\begin{align*} \mathbf{N} &= \mathbf{P} - \mathbf{Q} \\ \mathbf{N} &= (P_x, P_y, P_z) - \frac{R}{\sqrt{P_x^2 + P_y^2}} (P_x, P_y, 0) \end{align*}

To make the formula simpler, we can let

\[\alpha = \frac{R}{\sqrt{P_x^2 + P_y^2}}\] which gives us

\begin{align*} \mathbf{N} &= (P_x, P_y, P_z) - (\alpha P_x, \alpha P_y, 0) \\ \mathbf{N} &= ((1 - \alpha)P_x, (1 - \alpha)P_y, P_z) \end{align*}

Converting $\mathbf{N}$ to a unit vector is simply a matter of dividing $\mathbf{N}$ by its own magnitude, resulting in the surface normal unit vector $\mathbf{\hat{n}}$:

\[ \mathbf{\hat{n}} = \frac{\mathbf{N}}{\lvert \mathbf{N} \rvert} \]

The member function Torus::SurfaceNormal in torus.cpp performs this calculation for any point $\mathbf{P}$ on the surface of a torus.

Using the algorithms explained above, the member function ObjectSpace_AppendAllIntersections in the class Torus finds intersections with a ray and the torus, and for each intersection point, determines its surface normal unit vector. As with every other solid object, these two vectors and the color of the surface are all the ray tracer needs to draw an image.

Copyright © 2013 by Don Cross. All Rights Reserved.