Many parts of the system operate on axis-aligned regions of space. For
example, multi-threading in pbrt is implemented by subdividing the image
into rectangular tiles that can be processed independently, and the
bounding volume hierarchy in Section 4.3 uses 3D boxes to bound
geometric primitives in the scene. The Bounds2 and Bounds3
template classes are used to represent the extent of these sorts of
regions. Both are parameterized by a type T that is used to
represent the coordinates of its extents.
<<Bounds Declarations>>=
template <typename T> class Bounds2 {
public:
<<Bounds2 Public Methods>>
There are a few possible representations for these sorts of bounding boxes;
pbrt uses axis-aligned bounding boxes (AABBs), where the box edges
are mutually perpendicular and aligned with the coordinate system axes.
Another possible choice is oriented bounding boxes (OBBs), where the
box edges on different sides are still perpendicular to each other but not
necessarily coordinate-system aligned. A 3D AABB can be described by one
of its vertices and three lengths, each representing the distance spanned
along the , , and coordinate axes. Alternatively, two opposite
vertices of the box can describe it. We chose the two-point representation
for pbrt’s Bounds2 and Bounds3 classes; they store the
positions of the vertex with minimum coordinate values and of the one with
maximum coordinate values. A 2D illustration of a bounding box and its
representation is shown in Figure 2.8.
Figure 2.8: An Axis-Aligned Bounding Box. The Bounds2 and
Bounds3 classes store only
the coordinates of the minimum and maximum points of the box; the other
box corners are implicit in this representation.
The default constructors create an empty
box by setting the extent to an invalid configuration, which violates the
invariant that pMin.x <= pMax.x (and similarly for the other
dimensions). By initializing two corner points with the largest and smallest
representable number, any operations involving an empty box (e.g.,
Union()) will yield the correct result.
If the caller passes two corner points (p1 and p2) to define
the box, the constructor needs to find their component-wise minimum and
maximum values since p1 and p2 are not necessarily chosen so
that p1.x <= p2.x, and so on.
In some cases, it’s also useful to use array indexing to select between the
two points at the corners of the box. The implementations of these methods
select between pMin and pMax based on the value of i.
It is similarly possible to construct a new box that bounds the space
encompassed by two other bounding boxes. The definition of this function
is similar to the earlier Union() method that takes a Point3f; the
difference is that the pMin and pMax of the second box are used
for the std::min() and std::max() tests, respectively.
The intersection of two bounding boxes can be found by computing the
maximum of their two respective minimum coordinates and the minimum of
their maximum coordinates. (See
Figure 2.9.)
Figure 2.9: Intersection of Two Bounding Boxes.
Given two bounding boxes with pMin and pMax points
denoted by open circles, the bounding box of their area of intersection
(shaded region) has a minimum point (lower left filled circle) with coordinates given by the maximum
of the coordinates of the minimum points of the two boxes in each
dimension. Similarly, its maximum point (upper right filled circle) is given by the minimums of the
boxes’ maximum coordinates.
The InsideExclusive() variant of Inside() doesn’t consider
points on the upper boundary to be inside the bounds. It is mostly useful
with integer-typed bounds.
T Volume() const {
Vector3<T> d = Diagonal();
return d.x * d.y * d.z;
}
The Bounds3::MaximumExtent() method returns the index of which of the
three axes is longest. This is useful, for example, when deciding
which axis to subdivide when building some of the ray-intersection
acceleration structures.
<<Bounds3 Public Methods>>+=
int MaximumExtent() const {
Vector3<T> d = Diagonal();
if (d.x > d.y && d.x > d.z)
return 0;
else if (d.y > d.z)
return 1;
else
return 2;
}
The Lerp() method linearly interpolates between the corners of the
box by the given amount in each dimension.
Offset() returns the continuous
position of a point relative to the corners of the box, where a point at
the minimum corner has offset , a point at the maximum corner has
offset , and so forth.
<<Bounds3 Public Methods>>+=
Vector3<T> Offset(const Point3<T> &p) const {
Vector3<T> o = p - pMin;
if (pMax.x > pMin.x) o.x /= pMax.x - pMin.x;
if (pMax.y > pMin.y) o.y /= pMax.y - pMin.y;
if (pMax.z > pMin.z) o.z /= pMax.z - pMin.z;
return o;
}
Bounds3 also provides a method that returns the center and radius of
a sphere that bounds the bounding box. In general, this may give a far looser
fit than a sphere that bounded the original contents of the Bounds3
directly, although it is a useful method to have available.
Finally, for integer bounds, there is an iterator class that fulfills the
requirements of a C++ forward iterator (i.e., it can only be advanced). The
details are slightly tedious and not particularly interesting, so the code
isn’t included in the book. Having this definition makes it possible to
write code using range-based for loops to iterate over integer
coordinates in a bounding box:
Bounds2i b = ...;
for (Point2i p : b) {
// …
}
As implemented, the iteration goes up to but doesn’t visit points equal to the
maximum extent in each dimension.