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