Geometry and Transformations
1.5 Bounding Boxes
A bounding box describes the extent of some region of space. Bounding boxes will be useful for our renderer, because if we divide a space into bounding boxes we can make early decisions about whether calculations need to be done in a particular area (so if you imagine we have 10 complex objects in a box, we can first check if a calculation need sto be done for the box at all, and avoid it if not).
Fig 1.1: A bounding box around a set of objects in two dimensions
We can represent a bounding box in terms of it's min and max bounds, which are points in space. Here are what the structs look like:
#![allow(unused)] fn main() { pub struct Bounds2d<T> { pub min: Point2d<T>, pub max: Point2d<T>, } pub struct Bounds3d<T> { pub min: Point3d<T>, pub max: Point3d<T>, } }
The impl
blocks for the Bounds
structs provide a constructor which automatically selects the min and max points from the two arguments, as well as a default method that setting the extent to an invalid configuration, which violates the invariant that pMin.x <= pMax.x. This allows operations involving empty boxes e.g., Union() to return the correct result (otherwise they would contain everything and always be true). I've also included a new_from_point
method for returning a box from a single point.
#![allow(unused)] fn main() { impl<T: Scalar> Bounds3d<T> { pub fn new(p1: Point3d<T>, p2: Point3d<T>) -> Self { let min = Point3d::<T>::new( p1.x.min(p2.x), p1.y.min(p2.y), p1.z.min(p2.z) ); let max = Point3d::<T>::new( p1.x.max(p2.x), p1.y.max(p2.y), p1.z.max(p2.z) ); Bounds3d { min, max } } pub fn new_from_point(p: Point3d<T>) -> Self { Bounds3d { min: p, max: p } } pub fn default() -> Self { Bounds3d { min: Point3d{x: T::inf(), y: T::inf(), z: T::inf()}, max: Point3d{x: -T::inf(), y: -T::inf(), z: -T::inf()} } } } }
The c++ pbrt implementation also has an overloaded 'Union' method for adding both points and other boxes to boxes. We can achieve this in rust with traits as follows:
#![allow(unused)] fn main() { trait Union<T> { fn union(&self, other: &T) -> Self; } impl<T: Scalar> Union<Point2d<T>> for Bounds2d<T> { fn union(&self, other: &Point2d<T>) -> Self { Bounds2d { min: Point2d { x: self.min.x.min(other.x), y: self.min.y.min(other.y) }, max: Point2d { x: self.max.x.max(other.x), y: self.max.y.max(other.y) } } } } impl<T: Scalar> Union<Bounds2d<T>> for Bounds2d<T> { fn union(&self, other: &Bounds2d<T>) -> Self { Bounds2d { min: Point2d { x: self.min.x.min(other.min.x), y: self.min.y.min(other.min.y) }, max: Point2d { x: self.max.x.max(other.max.x), y: self.max.y.max(other.max.y) } } } } }
This lets us call union()
with either a Point
or another Bounds
, and have the compiler choose the correct method.