## 2.2 Vectors

`pbrt` provides both 2D and 3D vector classes. Both are
parameterized by the type of the underlying vector element, thus making it
easy to instantiate vectors of both integer and floating-point types.

In the following, we will generally only include implementations of
`Vector3` methods; all have `Vector2` parallels that have
straightforward implementation differences.

A vector is represented with a tuple of components that gives its representation in terms of the , , (in 3D) axes of the space it is defined in. The individual components of a 3D vector will be written , , and .

An alternate implementation would be to have a single template class that
is also parameterized with an integer number of dimensions and to represent
the coordinates with an array of that many `T` values. While this
approach would reduce the total amount of code, individual components of
the vector couldn’t be accessed as `v.x` and so forth. We believe
that in this case, a bit more code in the vector implementations is
worthwhile in return for more transparent access to elements.

However, some routines do find it useful to be able to easily loop over the
components of vectors; the vector classes also provide a C++ operator to
index into the components so that, given a vector `v`, `v[0] ==
v.x` and so forth.

For convenience, a number of widely used types of vectors are given a
`typedef`, so that they have more concise names in code elsewhere.

Readers who have been exposed to object-oriented design may question our decision to make the vector element data publicly accessible. Typically, data members are only accessible inside their class, and external code that wishes to access or modify the contents of a class must do so through a well-defined API of selector and mutator functions. Although we generally agree with this design principle (though see the discussion of data-oriented design in the “Further Reading” section of Chapter 1), it is not appropriate here. The purpose of selector and mutator functions is to hide the class’s internal implementation details. In the case of vectors, hiding this basic part of their design gains nothing and adds bulk to code that uses them.

By default, the values are set to zero, although the user of the
class can optionally supply values for each of the components. If the user
does supply values, we check that none of them has the floating-point
“not a number” (NaN) value using the `Assert()` macro. When compiled in
optimized mode, this macro disappears from the compiled code, saving the
expense of verifying this case. NaNs almost certainly
indicate a bug in the system; if a NaN is generated by some computation,
we’d like to catch it as soon as possible in order to make isolating
its source easier. (See Section 3.9.1 for more discussion of
NaN values.)

The code to check for NaNs just calls the `std::isnan()` function on
each of the , , and components.

Addition and subtraction of vectors are done component-wise. The usual geometric interpretation of vector addition and subtraction is shown in Figures 2.3 and 2.4.

The code for subtracting two vectors is similar and therefore not shown here.

A vector can be multiplied component-wise by a scalar, thereby
changing its length. Three functions are needed in order to cover all of
the different ways that this operation may be written in source code (i.e.,
`v*s`, `s*v`, and `v *= s`):

Similarly, a vector can be divided component-wise by a scalar. The code for scalar division is similar to scalar multiplication, although division of a scalar by a vector is not well defined and so is not permitted.

In the implementation of these methods, we use a single division to compute the scalar’s reciprocal and then perform three component-wise multiplications. This is a useful trick for avoiding division operations, which are generally much slower than multiplies on modern CPUs.

We use the `Assert()` macro to make sure that the provided divisor is not
zero; this should never happen and would indicate a bug elsewhere in the
system.

The `Vector3` class also provides a unary negation operator that returns a
new vector pointing in the opposite direction of the original one:

Finally, `Abs()` returns a vector with the absolute value operation
applied to its components.

### 2.2.1 Dot and Cross Product

Two useful operations on vectors are the dot product (also known as
the scalar or inner product) and the cross product. For two vectors
and , their *dot product*
is defined as:

The dot product has a simple relationship to the angle between the two vectors:

where is the angle between and , and
denotes the length of the vector . It follows from
this that is zero if and only if and
are perpendicular, provided that neither nor is
*degenerate*—equal to . A set of two or more
mutually perpendicular vectors is said to be *orthogonal*. An orthogonal
set of unit vectors is called *orthonormal*.

It immediately follows from Equation (2.1) that if and are unit vectors, their dot product is the cosine of the angle between them. As the cosine of the angle between two vectors often needs to be computed for rendering, we will frequently make use of this property. A few basic properties directly follow from the definition. For example, if , , and are vectors and is a scalar value, then:

We will frequently need to compute the absolute value of the dot product
as well. The `AbsDot()` function does this for us so that
a separate call to `std::abs()` isn’t necessary.

The *cross product* is another useful operation for 3D vectors. Given
two vectors in 3D, the cross product is a vector
that is perpendicular to both of them.
Given orthogonal vectors and ,
then is defined to be a vector such that
form an orthogonal coordinate system.

The cross product is defined as:

A way to remember this is to compute the determinant of the matrix:

where , , and represent the axes , , and , respectively. Note that this equation is merely a memory aid and not a rigorous mathematical construction, since the matrix entries are a mix of scalars and vectors.

In the implementation here, the vector elements are converted to double-precision (regardless
of the type of `Float`) before the subtractions in the `Cross()`
function. Using extra precision for 32-bit floating-point values here
protects against error from catastrophic cancellation, a type of
floating-point error that can happen when subtracting two values that are
very close together. This isn’t a theoretical concern: this change was
necessary to fix bugs that came up from this issue previously. See
Section 3.9 for more information on floating-point rounding
error.

From the definition of the cross product, we can derive

where is the angle between and . An important implication of this is that the cross product of two perpendicular unit vectors is itself a unit vector. Note also that the result of the cross product is a degenerate vector if and are parallel.

This definition also shows a convenient way to compute the area of a parallelogram (Figure 2.5). If the two edges of the parallelogram are given by vectors and , and it has height , the area is given by . Since , we can use Equation (2.2) to see that the area is .

### 2.2.2 Normalization

It is often necessary to *normalize* a vector—that is, to
compute a new vector pointing in the same direction but with unit length.
A normalized vector is often called a *unit vector*. The notation
used in this book for normalized vectors is that is the
normalized version of . To normalize a vector, it’s first useful
to be able to compute its length.

`Normalize()` normalizes a vector. It divides each component by the
length of the vector, . It returns a new vector; it does
*not* normalize the vector in place:

### 2.2.3 Miscellaneous Operations

A few additional operations are useful when working with vectors. The
`MinComponent()` and `MaxComponent()` methods return the
smallest and largest coordinate value,
respectively.

Related, `MaxDimension()` returns the index of the component with the
largest value.

Component-wise minimum and maximum operations are also available.

Finally, `Permute()` permutes the coordinate values according to the
index values provided.

### 2.2.4 Coordinate System from a Vector

We will frequently want to construct a local coordinate system given only a single 3D vector. Because the cross product of two vectors is orthogonal to both, we can apply the cross product two times to get a set of three orthogonal vectors for the coordinate system. Note that the two vectors generated by this technique are unique only up to a rotation about the given vector.

The implementation of this function assumes that the vector passed in,
`v1`, has already been normalized. It first constructs a
perpendicular vector by zeroing one of the components of the original
vector, swapping the remaining two, and negating one of them. Inspection of
the two cases should make clear that `v2` will be normalized and that
the dot product must be equal to zero. Given
these two perpendicular vectors, a single cross product gives the third,
which by definition will be perpendicular to the first two.