## 3.3 Vectors

`pbrt` provides both 2D and 3D vector classes that are based on the
corresponding two- and three-dimensional tuple classes. Both vector types
are themselves parameterized by the type of the underlying vector element,
thus making it easy to instantiate vectors of both integer and
floating-point types.

Two-dimensional vectors of `Float`s and integers are widely used, so
we will define aliases for those two types.

As with `Tuple2`, we will not include any further details of `Vector2`
since it is very similar to `Vector3`, which we will discuss in more
detail.

A `Vector3`’s tuple of component values gives its representation in
terms of the , , and (in 3D) axes of the space it is defined in. The
individual components of a 3D vector will be written
, , and .

We also define type aliases for two commonly used three-dimensional vector types.

`Vector3` provides a few constructors, including a default constructor
(not shown here)
and one that allows specifying each component value directly.

There is also a constructor that takes a `Vector3` with a different
element type. It is qualified with `explicit` so that it is not
unintentionally used in automatic type conversions; a cast must be used to
signify the intent of the type conversion.

Finally, constructors are provided to convert from the forthcoming
`Point3` and `Normal3` types. Their straightforward
implementations are not included here. These, too, are `explicit` to
help ensure that they are only used in situations where the conversion is
meaningful.

Addition and subtraction of vectors is performed component-wise, via methods
from `Tuple3`. The usual geometric interpretation of vector addition
and subtraction is shown in Figures 3.3
and 3.4. A vector’s length can be changed via
component-wise multiplication or division by a scalar. These capabilities,
too, are provided by `Tuple3` and so do not require any additional
implementation in the `Vector3` class.

### 3.3.1 Normalization and Vector Length

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 . Before getting to normalization, we will
start with computing vectors’ lengths.

The squared length of a vector is given by the sum of the squares of its component values.

Moving on to computing the length of a vector leads us to a quandary: what
type should the `Length()` function return? For example, if the
`Vector3` stores an integer type, that type is probably not an
appropriate return type since the vector’s length will not necessarily be
integer-valued. In that case, `Float` would be a better choice, though
we should not standardize on `Float` for everything, because given a
`Vector3` of double-precision values, we should return the length as a
`double` as well. Continuing our journey through advanced C++, we
turn to a technique known as *type traits* to solve this dilemma.

First, we define a general `TupleLength` template class that holds a
type definition, `type`. The default is set here to be `Float`.

For `Vector3`s of `double`s, we also provide a template
specialization that defines `double` as the type for length given
`double` for the element type.

Now we can implement `Length()`, using `TupleLength` to determine
which type to return. Note that the return type cannot be specified before
the function declaration is complete since the type `T` is not known
until the function parameters have been parsed. Therefore, the function is
declared as `auto` with the return type specified after its parameter
list.

There is one more C++ subtlety in these few lines of code: the reader may
wonder, why have a `using std::sqrt` declaration in the implementation
of `Length()` and then call `sqrt()`, rather than just calling
`std::sqrt()` directly? That construction is used because
we would like to be able to use component
types `T` that do not have overloaded versions of `std::sqrt()`
available to them. For example, we will later make use of `Vector3`s
that store intervals of values for each component using a forthcoming
`Interval` class. With the way the code is written here, if
`std::sqrt()` supports the type `T`, the `std` variant of
the function is called. If not, then so long as we have defined a function
named `sqrt()` that takes our custom type, that version will be used.

With all of this in hand, the implementation of `Normalize()` is
thankfully now trivial. The use of `auto` for the return type ensures
that if for example `Normalize()` is called with a vector with integer
components, then the returned vector type has `Float` components
according to type conversion from the division operator.

### 3.3.2 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 3D vectors
and , their *dot product*
is defined as

and the implementation follows directly.

A few basic properties directly follow from the definition of the dot product. For example, if , , and are vectors and is a scalar value, then:

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 follows from Equation (3.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.

If we would like to find the angle between two normalized vectors, we could use the standard library’s inverse cosine function, passing it the value of the dot product between the two vectors. However, that approach can suffer from a loss of accuracy when the two vectors are nearly parallel or facing in nearly opposite directions. The following reformulation does more of its computation with values close to the origin where there is more floating-point precision, giving a more accurate result.

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()` is not necessary in that case.

A useful operation on vectors that is based on the dot product is the
*Gram–Schmidt* process, which transforms a set of non-orthogonal
vectors that form a basis into orthogonal vectors that span the same basis.
It is based on successive application of the *orthogonal
projection* of a vector onto a normalized vector ,
which is given by (see
Figure 3.5).
The orthogonal projection can be used to compute a new vector

that is orthogonal to . An advantage of computing in this way is that and span the same subspace as and did.

The `GramSchmidt()` function implements Equation (3.2); it expects
the vector `w` to already be normalized.

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.

The cross product implementation here uses the `DifferenceOfProducts()`
function that is introduced in Section B.2.9.
Given values `a`, `b`, `c`, and `d`, it computes
`a*b-c*d` in a way that maintains more floating-point accuracy
than a direct implementation of that expression would. This concern is not
a theoretical one: previous versions of `pbrt` have resorted to using
double precision for the implementation of `Cross()` so that numerical
error would not lead to artifacts in rendered images. Using
`DifferenceOfProducts()` is a better solution since it can operate
entirely in single precision while still computing a result with low
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 3.6). 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 (3.3) to see that the area is .

### 3.3.3 Coordinate System from a Vector

We will sometimes find it useful to construct a local coordinate system given only a single normalized 3D vector. To do so, we must find two additional normalized vectors such that all three vectors are mutually perpendicular.

Given a vector , it can be shown that the two vectors

fulfill these conditions. However, computing those properties directly has high error when due to a loss of accuracy when is calculated. A reformulation of that computation, used in the following implementation, addresses that issue.