Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

An Interval Class

Before we continue, we'll implement an interval class to manage real-valued intervals with a minimum and a maximum. We'll end up using this class quite often as we proceed.

#[derive(Debug, Clone, Copy)]
pub struct Interval {
    pub min: f64,
    pub max: f64,
}

impl Default for Interval {
    fn default() -> Self {
        Self::EMPTY
    }
}

impl Interval {
    pub const EMPTY: Self = Self {
        min: f64::INFINITY,
        max: f64::NEG_INFINITY,
    };

    pub const UNIVERSE: Self = Self {
        min: f64::NEG_INFINITY,
        max: f64::INFINITY,
    };

    pub fn new(min: f64, max: f64) -> Self {
        Self { min, max }
    }

    pub fn size(&self) -> f64 {
        self.max - self.min
    }

    pub fn contains(&self, x: f64) -> bool {
        self.min <= x && x <= self.max
    }

    pub fn surrounds(&self, x: f64) -> bool {
        self.min < x && x < self.max
    }
}

Listing 31: [interval.rs] Introducing the new interval class


diff --git a/src/prelude.rs b/src/prelude.rs
index fcd4621..2ec9487 100644
--- a/src/prelude.rs
+++ b/src/prelude.rs
@@ -1,14 +1,14 @@
 pub use log::*;
 
 // Rust Std usings
 
 pub use std::rc::Rc;
 
 // Constants
 
 pub const INFINITY: f64 = f64::INFINITY;
 pub const PI: f64 = std::f64::consts::PI;
 
 // Common Headers
 
-pub use crate::{color::*, ray::*, vec3::*};
+pub use crate::{color::*, interval::Interval, ray::*, vec3::*};

Listing 32: [prelude.rs] Including the new interval class


diff --git a/src/hittable.rs b/src/hittable.rs
index a7aab5a..1b65b92 100644
--- a/src/hittable.rs
+++ b/src/hittable.rs
@@ -1,27 +1,27 @@
 use crate::prelude::*;
 
 #[derive(Debug, Default, Clone, Copy)]
 pub struct HitRecord {
     pub p: Point3,
     pub normal: Vec3,
     pub t: f64,
     pub front_face: bool,
 }
 
 impl HitRecord {
     pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) {
         // Sets the hit record normal vector.
         // NOTE: the parameter `outward_normal` is assumed to have unit length.
 
         self.front_face = dot(r.direction(), outward_normal) < 0.0;
         self.normal = if self.front_face {
             outward_normal
         } else {
             -outward_normal
         };
     }
 }
 
 pub trait Hittable {
-    fn hit(&self, r: Ray, ray_tmin: f64, ray_tmax: f64) -> Option<HitRecord>;
+    fn hit(&self, r: Ray, ray_t: Interval) -> Option<HitRecord>;
 }

Listing 33: [hittable.rs] hittable::hit() using interval


diff --git a/src/hittable_list.rs b/src/hittable_list.rs
index 7841161..4647aa5 100644
--- a/src/hittable_list.rs
+++ b/src/hittable_list.rs
@@ -1,32 +1,32 @@
 use crate::{
     hittable::{HitRecord, Hittable},
     prelude::*,
 };
 
 #[derive(Default)]
 pub struct HittableList {
     pub objects: Vec<Rc<dyn Hittable>>,
 }
 
 impl HittableList {
     pub fn new() -> Self {
         Self::default()
     }
 
     pub fn clear(&mut self) {
         self.objects.clear();
     }
 
     pub fn add(&mut self, object: Rc<dyn Hittable>) {
         self.objects.push(object);
     }
 }
 
 impl Hittable for HittableList {
-    fn hit(&self, r: Ray, ray_tmin: f64, ray_tmax: f64) -> Option<HitRecord> {
+    fn hit(&self, r: Ray, ray_t: Interval) -> Option<HitRecord> {
         self.objects
             .iter()
-            .filter_map(|obj| obj.hit(r, ray_tmin, ray_tmax))
+            .filter_map(|obj| obj.hit(r, ray_t))
             .min_by(|a, b| a.t.partial_cmp(&b.t).expect("no NaN value"))
     }
 }

Listing 34: [hittable_list.rs] hittable_list::hit() using interval


diff --git a/src/sphere.rs b/src/sphere.rs
index 9de9f72..a2710b4 100644
--- a/src/sphere.rs
+++ b/src/sphere.rs
@@ -1,56 +1,56 @@
 use crate::{
     hittable::{HitRecord, Hittable},
     prelude::*,
 };
 
 #[derive(Debug, Clone, Copy)]
 pub struct Sphere {
     center: Point3,
     radius: f64,
 }
 
 impl Sphere {
     pub fn new(center: Point3, radius: f64) -> Self {
         Self {
             center,
             radius: f64::max(0.0, radius),
         }
     }
 }
 
 impl Hittable for Sphere {
-    fn hit(&self, r: Ray, ray_tmin: f64, ray_tmax: f64) -> Option<HitRecord> {
+    fn hit(&self, r: Ray, ray_t: Interval) -> Option<HitRecord> {
         let oc = self.center - r.origin();
         let a = r.direction().length_squared();
         let h = dot(r.direction(), oc);
         let c = oc.length_squared() - self.radius * self.radius;
 
         let discriminant = h * h - a * c;
         if discriminant < 0.0 {
             return None;
         }
 
         let sqrtd = f64::sqrt(discriminant);
 
         // Find the nearest root that lies in the acceptable range.
         let mut root = (h - sqrtd) / a;
-        if root <= ray_tmin || ray_tmax <= root {
+        if !ray_t.surrounds(root) {
             root = (h + sqrtd) / a;
-            if root <= ray_tmin || ray_tmax <= root {
+            if !ray_t.surrounds(root) {
                 return None;
             }
         }
 
         let t = root;
         let p = r.at(t);
         let mut rec = HitRecord {
             t,
             p,
             ..Default::default()
         };
         let outward_normal = (p - self.center) / self.radius;
         rec.set_face_normal(r, outward_normal);
 
         Some(rec)
     }
 }

Listing 35: [sphere.rs] sphere using interval


diff --git a/src/main.rs b/src/main.rs
index 59c000b..a8d3932 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,74 +1,74 @@
 use code::{hittable::Hittable, hittable_list::HittableList, prelude::*, sphere::Sphere};
 
 fn ray_color(r: Ray, world: &impl Hittable) -> Color {
-    if let Some(rec) = world.hit(r, 0.0, INFINITY) {
+    if let Some(rec) = world.hit(r, Interval::new(0.0, INFINITY)) {
         return 0.5 * (rec.normal + Color::new(1.0, 1.0, 1.0));
     }
 
     let unit_direction = unit_vector(r.direction());
     let a = 0.5 * (unit_direction.y() + 1.0);
     (1.0 - a) * Color::new(1.0, 1.0, 1.0) + a * Color::new(0.5, 0.7, 1.0)
 }
 
 fn main() -> Result<(), Box<dyn std::error::Error>> {
     // Image
 
     const ASPECT_RATIO: f64 = 16.0 / 9.0;
     const IMAGE_WIDTH: i32 = 400;
 
     // Calculate the image height, and ensure that it's at least 1.
     const IMAGE_HEIGHT: i32 = {
         let image_height = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as i32;
         if image_height < 1 { 1 } else { image_height }
     };
 
     // World
 
     let mut world = HittableList::new();
 
     world.add(Rc::new(Sphere::new(Point3::new(0.0, 0.0, -1.0), 0.5)));
     world.add(Rc::new(Sphere::new(Point3::new(0.0, -100.5, -1.0), 100.0)));
 
     // Camera
 
     let focal_length = 1.0;
     let viewport_height = 2.0;
     let viewport_width = viewport_height * (IMAGE_WIDTH as f64) / (IMAGE_HEIGHT as f64);
     let camera_center = Point3::new(0.0, 0.0, 0.0);
 
     // Calculate the vectors across the horizontal and down the vertical viewport edges.
     let viewport_u = Vec3::new(viewport_width, 0.0, 0.0);
     let viewport_v = Vec3::new(0.0, -viewport_height, 0.0);
 
     // Calculate the horizontal and vertical delta vectors from pixel to pixel.
     let pixel_delta_u = viewport_u / IMAGE_WIDTH as f64;
     let pixel_delta_v = viewport_v / IMAGE_HEIGHT as f64;
 
     // Calculate the location of the upper left pixel.
     let viewport_upper_left =
         camera_center - Vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0;
     let pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
 
     // Render
 
     env_logger::init();
     println!("P3");
     println!("{IMAGE_WIDTH} {IMAGE_HEIGHT}");
     println!("255");
 
     for j in 0..IMAGE_HEIGHT {
         info!("Scanlines remaining: {}", IMAGE_HEIGHT - j);
         for i in 0..IMAGE_WIDTH {
             let pixel_center =
                 pixel00_loc + (i as f64) * pixel_delta_u + (j as f64) * pixel_delta_v;
             let ray_direction = pixel_center - camera_center;
             let r = Ray::new(camera_center, ray_direction);
 
             let pixel_color = ray_color(r, &world);
             write_color(std::io::stdout(), pixel_color)?;
         }
     }
     info!("Done.");
 
     Ok(())
 }

Listing 36: [main.rs] The new main using interval