Common Constants and Utility Functions
We need some math constants that we conveniently define in their own header file. For now we only need infinity, but we will also throw our own definition of pi in there, which we will need later. We'll also throw common useful constants and future utility functions in here. This new header, rtweekend.h
, will be our general main header file. 1
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::*};
Listing 24: [prelude.rs] The rtweekend.h common header
Program files will include rtweekend.h
first, so all other header files (where the bulk of our code will reside) can implicitly assume that rtweekend.h
has already been included. Header files still need to explicitly include any other necessary header files. We'll make some updates with these assumptions in mind.
// nothing changes
Listing 25: [color.rs] Assume rtweekend.h inclusion for color.h 2
diff --git a/src/hittable.rs b/src/hittable.rs
index 8ced826..a7aab5a 100644
--- a/src/hittable.rs
+++ b/src/hittable.rs
@@ -1,30 +1,27 @@
-use crate::{
- ray::Ray,
- vec3::{Point3, Vec3, dot},
-};
+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>;
}
Listing 26: [hittable.rs] Assume rtweekend.h inclusion for hittable.h
diff --git a/src/hittable_list.rs b/src/hittable_list.rs
index 95c24e4..7841161 100644
--- a/src/hittable_list.rs
+++ b/src/hittable_list.rs
@@ -1,34 +1,32 @@
-use std::rc::Rc;
-
use crate::{
hittable::{HitRecord, Hittable},
- ray::Ray,
+ 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> {
self.objects
.iter()
.filter_map(|obj| obj.hit(r, ray_tmin, ray_tmax))
.min_by(|a, b| a.t.partial_cmp(&b.t).expect("no NaN value"))
}
}
Listing 27: [hittable_list.rs] Assume rtweekend.h inclusion for hittable_list.h
diff --git a/src/sphere.rs b/src/sphere.rs
index 86d3cbb..9de9f72 100644
--- a/src/sphere.rs
+++ b/src/sphere.rs
@@ -1,57 +1,56 @@
use crate::{
hittable::{HitRecord, Hittable},
- ray::Ray,
- vec3::{Point3, dot},
+ 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> {
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 {
root = (h + sqrtd) / a;
if root <= ray_tmin || ray_tmax <= 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 28: [sphere.rs] Assume rtweekend.h inclusion for sphere.h
// nothing changes
Listing 29: [vec3.rs] Assume rtweekend.h inclusion for vec3.h
And now the new main:
diff --git a/src/main.rs b/src/main.rs
index 1f26e3d..59c000b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,82 +1,74 @@
-use code::{
- color::{Color, write_color},
- ray::Ray,
- vec3::{Point3, Vec3, dot, unit_vector},
-};
-
-fn hit_sphere(center: Point3, radius: f64, r: Ray) -> Option<f64> {
- let oc = center - r.origin();
- let a = r.direction().length_squared();
- let h = dot(r.direction(), oc);
- let c = oc.length_squared() - radius * radius;
- let discriminant = h * h - a * c;
-
- (discriminant >= 0.0).then(|| (h - f64::sqrt(discriminant)) / a)
-}
+use code::{hittable::Hittable, hittable_list::HittableList, prelude::*, sphere::Sphere};
-fn ray_color(r: Ray) -> Color {
- if let Some(t) = hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, r) {
- let n = unit_vector(r.at(t) - Vec3::new(0.0, 0.0, -1.0));
- return 0.5 * Color::new(n.x() + 1.0, n.y() + 1.0, n.z() + 1.0);
+fn ray_color(r: Ray, world: &impl Hittable) -> Color {
+ if let Some(rec) = world.hit(r, 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 {
- log::info!("Scanlines remaining: {}", IMAGE_HEIGHT - j);
+ 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);
+ let pixel_color = ray_color(r, &world);
write_color(std::io::stdout(), pixel_color)?;
}
}
- log::info!("Done.");
+ info!("Done.");
Ok(())
}
Listing 30: [main.rs] The new main with hittables
This yields a picture that is really just a visualization of where the spheres are located along with their surface normal. This is often a great way to view any flaws or specific characteristics of a geometric model.

Image 5: Resulting render of normals-colored sphere with ground
-
In Rust it is common to create a prelude for common types, which we will do here instead. Note however, that there are at the momentan no plan to include a custom prelude as a language feature, instead we need to import the prelude with
use crate::prelude::*
. ↩ -
There is no need to use the prelude in
color.rs
only for theVec3
struct. The listing is still included to match the numbering of the original book series. ↩