rust_tracer/
materials.rs

1use rand::{rngs::ThreadRng, Rng};
2
3use crate::{
4    hittables::HitRecord,
5    math::{Color, Ray, Vector3},
6    utils::{random_in_unit_sphere, random_unit_vector, reflect, refract},
7};
8
9pub trait Material {
10    fn scatter(&self, ray: Ray, rec: HitRecord, rng: &mut ThreadRng) -> Option<(Color, Ray)>;
11}
12
13pub struct Lambertian {
14    pub albedo: Color,
15}
16
17impl Lambertian {
18    pub fn new(albedo: Color) -> Box<Self> {
19        Box::new(Self { albedo })
20    }
21}
22
23impl Material for Lambertian {
24    fn scatter(&self, _ray: Ray, rec: HitRecord, rng: &mut ThreadRng) -> Option<(Color, Ray)> {
25        let scatter_direction = rec.normal + random_unit_vector(rng);
26
27        // Catch degenerate scatter direction
28        let scatter_direction = if scatter_direction.near_zero() {
29            rec.normal
30        } else {
31            scatter_direction
32        };
33
34        Some((self.albedo, Ray::new(rec.point, scatter_direction)))
35    }
36}
37
38pub struct Metal {
39    pub albedo: Color,
40    pub fuzz: f64,
41}
42
43impl Metal {
44    pub fn new(albedo: Color, fuzz: f64) -> Box<Self> {
45        Box::new(Self {
46            albedo,
47            fuzz: fuzz.min(1.0),
48        })
49    }
50}
51
52impl Material for Metal {
53    fn scatter(&self, ray: Ray, rec: HitRecord, rng: &mut ThreadRng) -> Option<(Color, Ray)> {
54        let reflected = reflect(ray.direction.unit_vector(), rec.normal);
55        let scattered = Ray::new(
56            rec.point,
57            reflected + self.fuzz * random_in_unit_sphere(rng),
58        );
59        if Vector3::dot(&scattered.direction, &rec.normal) > 0.0 {
60            Some((self.albedo, scattered))
61        } else {
62            None
63        }
64    }
65}
66
67#[derive(Clone, Copy)]
68pub struct Dielectric {
69    pub refraction_index: f64,
70}
71
72impl Dielectric {
73    pub fn new(refraction_index: f64) -> Box<Self> {
74        Box::new(Self { refraction_index })
75    }
76
77    fn reflectance(cosine: f64, refractive_index: f64) -> f64 {
78        let r0 = (1.0 - refractive_index) / (1.0 + refractive_index);
79        let r0 = r0 * r0;
80        r0 + (1.0 - r0) * (1.0 - cosine).powi(5)
81    }
82}
83
84impl Material for Dielectric {
85    fn scatter(&self, ray: Ray, rec: HitRecord, rng: &mut ThreadRng) -> Option<(Color, Ray)> {
86        let refraction_ratio = if rec.front_face {
87            1.0 / self.refraction_index
88        } else {
89            self.refraction_index
90        };
91
92        let unit_direction = ray.direction.unit_vector();
93
94        let cos_theta = (-unit_direction).dot(&rec.normal).min(1.0);
95        let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
96
97        let direction = if refraction_ratio * sin_theta > 1.0
98            || Dielectric::reflectance(cos_theta, refraction_ratio) > rng.gen()
99        {
100            reflect(unit_direction, rec.normal)
101        } else {
102            refract(unit_direction, rec.normal, refraction_ratio)
103        };
104
105        Some((Color::new(1.0, 1.0, 1.0), Ray::new(rec.point, direction)))
106    }
107}