rust_tracer/
hittables.rs

1use crate::{
2    materials::Material,
3    math::{Point3, Ray, Vector3},
4};
5
6pub struct HitRecord<'a> {
7    pub point: Point3,
8    pub normal: Vector3,
9    pub t: f64,
10    pub front_face: bool,
11    pub material: &'a Box<dyn Material>,
12}
13
14impl<'a> HitRecord<'a> {
15    pub fn new(
16        point: Point3,
17        normal: Vector3,
18        t: f64,
19        front_face: bool,
20        material: &'a Box<dyn Material>,
21    ) -> Self {
22        Self {
23            point,
24            normal,
25            t,
26            front_face,
27            material,
28        }
29    }
30
31    pub fn set_face_normal(&mut self, ray: Ray, outward_normal: Vector3) {
32        self.front_face = Vector3::dot(&ray.direction, &outward_normal) < 0.0;
33        self.normal = if self.front_face {
34            outward_normal
35        } else {
36            -outward_normal
37        };
38    }
39}
40
41pub trait Hittable {
42    fn hit(&self, ray: Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
43}
44
45pub struct Sphere {
46    pub center: Point3,
47    pub radius: f64,
48    pub material: Box<dyn Material>,
49}
50
51impl Sphere {
52    pub fn new(center: Point3, radius: f64, material: Box<dyn Material>) -> Box<Self> {
53        Box::new(Self {
54            center,
55            radius,
56            material,
57        })
58    }
59}
60
61impl Hittable for Sphere {
62    fn hit(&self, ray: Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
63        let oc = ray.origin - self.center;
64        let a = ray.direction.length_squared();
65        let half_b = Vector3::dot(&oc, &ray.direction);
66        let c = oc.length_squared() - self.radius * self.radius;
67
68        let discriminant = half_b * half_b - a * c;
69
70        if discriminant < 0.0 {
71            return None;
72        }
73
74        let sqrt_discriminant = discriminant.sqrt();
75
76        // Find the nearest root that lies in the acceptable range.
77        let mut root = (-half_b - sqrt_discriminant) / a;
78        if root < t_min || t_max < root {
79            root = (-half_b + sqrt_discriminant) / a;
80            if root < t_min || t_max < root {
81                return None;
82            }
83        }
84
85        let mut hit_record = HitRecord::new(
86            ray.at(root),
87            Vector3::new(0.0, 0.0, 0.0),
88            root,
89            false,
90            &self.material,
91        );
92
93        let outward_normal = (hit_record.point - self.center) / self.radius;
94        hit_record.set_face_normal(ray, outward_normal);
95
96        Some(hit_record)
97    }
98}
99
100pub struct HittableList {
101    pub objects: Vec<Box<dyn Hittable>>,
102}
103
104impl HittableList {
105    pub fn new() -> Self {
106        Self { objects: vec![] }
107    }
108
109    pub fn add(&mut self, object: Box<dyn Hittable>) {
110        self.objects.push(object);
111    }
112
113    pub fn clear(&mut self) {
114        self.objects.clear();
115    }
116
117    pub fn hit(&self, ray: Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
118        let mut hit_anything = None;
119        let mut closest_so_far = t_max;
120
121        for object in &self.objects {
122            if let Some(hit_record) = object.hit(ray, t_min, closest_so_far) {
123                closest_so_far = hit_record.t;
124                hit_anything = Some(hit_record);
125            }
126        }
127
128        hit_anything
129    }
130}