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