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 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}