A Data Structure to Describe Ray-Object Intersections
The hit_record
is to avoid a bunch of arguments so we can stuff whatever info we want in there. You can use arguments instead of an encapsulated type, it’s just a matter of taste. Hittables and materials need to be able to reference the other's type in code so there is some circularity of the references. In C++ we add the line class material; to tell the compiler that material is a class that will be defined later. Since we're just specifying a pointer to the class, the compiler doesn't need to know the details of the class, solving the circular reference issue. 1
diff --git a/src/hittable.rs b/src/hittable.rs
index 1b65b92..000dc1d 100644
--- a/src/hittable.rs
+++ b/src/hittable.rs
@@ -1,27 +1,43 @@
-use crate::prelude::*;
+use crate::{
+ material::{Lambertian, Material},
+ prelude::*,
+};
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(Clone)]
pub struct HitRecord {
pub p: Point3,
pub normal: Vec3,
+ pub mat: Rc<dyn Material>,
pub t: f64,
pub front_face: bool,
}
+impl Default for HitRecord {
+ fn default() -> Self {
+ Self {
+ p: Default::default(),
+ normal: Default::default(),
+ mat: Rc::new(Lambertian::default()),
+ t: Default::default(),
+ front_face: Default::default(),
+ }
+ }
+}
+
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_t: Interval) -> Option<HitRecord>;
}
Listing 59: [hittable.rs] Hit record with added material pointer 2
hit_record
is just a way to stuff a bunch of arguments into a class so we can send them as a group. When a ray hits a surface (a particular sphere for example), the material pointer in the hit_record
will be set to point at the material pointer the sphere was given when it was set up in main()
when we start. When the ray_color()
routine gets the hit_record
it can call member functions of the material pointer to find out what ray, if any, is scattered.
To achieve this, hit_record
needs to be told the material that is assigned to the sphere.
diff --git a/src/sphere.rs b/src/sphere.rs
index a2710b4..2b0026a 100644
--- a/src/sphere.rs
+++ b/src/sphere.rs
@@ -1,56 +1,61 @@
use crate::{
hittable::{HitRecord, Hittable},
+ material::{Lambertian, Material},
prelude::*,
};
-#[derive(Debug, Clone, Copy)]
+#[derive(Clone)]
pub struct Sphere {
center: Point3,
radius: f64,
+ mat: Rc<dyn Material>,
}
impl Sphere {
pub fn new(center: Point3, radius: f64) -> Self {
Self {
center,
radius: f64::max(0.0, radius),
+ // TODO: Initialize the material pointer `mat`.
+ mat: Rc::new(Lambertian::default()),
}
}
}
impl Hittable for Sphere {
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 !ray_t.surrounds(root) {
root = (h + sqrtd) / a;
if !ray_t.surrounds(root) {
return None;
}
}
let t = root;
let p = r.at(t);
let mut rec = HitRecord {
t,
p,
+ mat: self.mat.clone(),
..Default::default()
};
let outward_normal = (p - self.center) / self.radius;
rec.set_face_normal(r, outward_normal);
Some(rec)
}
}
Listing 60: [sphere.rs] Ray-sphere intersection with added material information
-
In Rust traits needs to be imported using the
use
keyword. Circular references are not a problem in Rust, since theuse
keyword does not simply copy the header file in comparison to the C++include
macro. ↩ -
HitRecord
can no longer deriveDefault
sinceRc<dyn Material>
can not implementDefault
. Instead, the default material is set to a Lambertian. ↩