Geometry and Transformations

1.4 Rays

A ray is a semi-infinite line specified by a point \(o\) representing its origin and a vector \(d\) representing its direction.

The parametric form of a ray expresses it as a function of a scalar value \(t\), giving the set of points that the ray passes through:

\[ r(t) = o + td \quad 0 \le t < \infty \]

The Ray class implements an at(t) method, allowing a caller to retrieve a point along the ray:


#![allow(unused)]

fn main() {
pub struct Ray<T> {
    pub origin: Point3d<T>,
    pub direction: Vector3d<T>,
    pub t_max: Cell<T>,
    pub time: T
}
impl<T: Scalar> Ray<T> { 
    pub fn new(origin: Point3d<T>, direction: Vector3d<T>, tmax: T, time: T) -> Self {
        Ray {
            origin,
            direction,
            t_max: Cell::new(tmax),
            time
        }
    }
    pub fn default() -> Self {
        Ray {
            origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            t_max: Cell::new(T::inf()),
            time: T::zero()
        }
    }
    pub fn at(&self, t: T) -> Point3d<T> {
        self.origin + self.direction * t
    }
}
}

As with the c++ implementation, the Ray struct provides an all-argument constructor as well as a default() which sets the origin, direction and time values to 0, and t_max to infinity. The Ray also includes a member variable that limits the ray to a segment along its infinite extent. This field, tMax, allows us to restrict the ray to a segment of points. Following the c++ pbrt implementation, this fields needs to be mutable to allow a caller to modify it (this will be useful when recording the points where the ray intersects with an object). In order to make a single field mutable in rust, we need to use the Cell<T> struct. This will allow a caller to set and get the value using the set and get methods on Cell<T>.

1.4.2 Ray Differentials

The c++ pbrt implementation includes a subclass of Ray with additional information about two auxiliary rays. These extra rays represent camera rays offset by one sample in the \(x\) and \(y\) direction from the main ray on the film plane. By moving the Ray methods out to a trait and implementing them for both Ray and RayDifferential, we can enable geometry methods which act on both Rays and RayDifferentials without caring which underlying type they are dealing with. Here's what the RayMethods trait looks like:


#![allow(unused)]
fn main() {
trait RayMethods<T> {
    fn origin(&self) -> Point3d<T>;
    fn direction(&self) -> Vector3d<T>;
    fn at(&self, t: T) -> Point3d<T>;
    fn t_max(&self) -> &Cell<T>;
    fn time(&self) -> T;
}
}

We can then add impl blocks returning the appropriate fields for both Ray and RayDifferential. RayDifferential has a few extra fields and methods:


#![allow(unused)]
fn main() {
pub struct RayDifferential<T> {
    pub origin: Point3d<T>,
    pub direction: Vector3d<T>,
    pub t_max: Cell<T>,
    pub time: T,
    pub rx_origin: Point3d<T>,
    pub ry_origin: Point3d<T>,
    pub rx_direction: Vector3d<T>,
    pub ry_direction: Vector3d<T>,
    pub has_differentials: bool
}
impl<T: Scalar> RayDifferential<T> {
    pub fn new(origin: Point3d<T>, direction: Vector3d<T>, tmax: T, time: T) -> Self {
        RayDifferential {
            origin: origin,
            direction: direction,
            rx_origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            rx_direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            ry_origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            ry_direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            t_max: Cell::new(tmax),
            time: time,
            has_differentials: false
        }
    }
    pub fn default() -> Self {
        RayDifferential {
            origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            rx_origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            rx_direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            ry_origin: Point3d::<T>::new(T::zero(), T::zero(), T::zero()),
            ry_direction: Vector3d::<T>::new(T::zero(), T::zero(), T::zero()),
            t_max: Cell::new(T::inf()),
            time: T::zero(),
            has_differentials: false
        }
    }
    pub fn from_ray(ray: &Ray<T>) -> Self {
        RayDifferential::new(ray.origin, ray.direction, ray.t_max.get(), ray.time)
    }
    pub fn scale_differentials(&mut self, s: T) {
        self.rx_origin = self.origin + (self.rx_origin - self.origin) * s;
        self.ry_origin = self.origin + (self.ry_origin - self.origin) * s;
        self.rx_direction = self.direction + (self.rx_direction - self.direction) * s;
        self.ry_direction = self.direction + (self.ry_direction - self.direction) * s;
    }
}
}

The RayDifferential the rx_ fields describe origin and direction information about these rays, and the 'ScaleDifferentials' method updates the differential rays for an estimated sample spacing of s. Finally, a from_ray_ constructor allows us to create a RayDifferential from a Ray.