1use crate::hittable::HitRecord;
2use crate::ray::Ray;
3use crate::utils::random_double;
4use crate::vec3::{Color, dot, random_in_unit_sphere, random_unit_vector, reflect, refract, unit_vector};
5
6pub trait Material: Send + Sync {
8 fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)>;
10}
11
12pub struct Lambertian {
14 pub albedo: Color,
16}
17
18impl Lambertian {
19 pub fn new(albedo: Color) -> Self {
21 Lambertian { albedo }
22 }
23}
24
25impl Material for Lambertian {
26 fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
27 let mut scatter_direction = rec.normal + random_unit_vector();
28 if scatter_direction.near_zero() {
30 scatter_direction = rec.normal;
31 }
32 let scattered = Ray::new(rec.p, scatter_direction);
33 Some((self.albedo, scattered))
34 }
35}
36
37pub struct Metal {
39 pub albedo: Color,
41 pub fuzz: f64,
43}
44
45impl Metal {
46 pub fn new(albedo: Color, fuzz: f64) -> Self {
48 Metal {
49 albedo,
50 fuzz: fuzz.min(1.0),
51 }
52 }
53}
54
55impl Material for Metal {
56 fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
57 let reflected = reflect(unit_vector(r_in.direction()), rec.normal);
58 let scattered = Ray::new(
59 rec.p,
60 reflected + self.fuzz * random_in_unit_sphere(),
61 );
62 if dot(scattered.direction(), rec.normal) > 0.0 {
64 Some((self.albedo, scattered))
65 } else {
66 None
67 }
68 }
69}
70
71fn schlick(cosine: f64, ref_idx: f64) -> f64 {
75 let r0 = ((1.0 - ref_idx) / (1.0 + ref_idx)).powi(2);
76 r0 + (1.0 - r0) * (1.0 - cosine).powi(5)
77}
78
79pub struct Dielectric {
81 pub ref_idx: f64,
83}
84
85impl Dielectric {
86 pub fn new(ref_idx: f64) -> Self {
88 Dielectric { ref_idx }
89 }
90}
91
92impl Material for Dielectric {
93 fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<(Color, Ray)> {
94 let attenuation = Color::new(1.0, 1.0, 1.0);
96 let etai_over_etat = if rec.front_face {
98 1.0 / self.ref_idx
99 } else {
100 self.ref_idx
101 };
102
103 let unit_direction = unit_vector(r_in.direction());
104 let cos_theta = dot(-unit_direction, rec.normal).min(1.0);
105 let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
106
107 let direction = if etai_over_etat * sin_theta > 1.0 {
108 reflect(unit_direction, rec.normal)
110 } else {
111 let reflect_prob = schlick(cos_theta, etai_over_etat);
113 if random_double() < reflect_prob {
114 reflect(unit_direction, rec.normal)
115 } else {
116 refract(unit_direction, rec.normal, etai_over_etat)
117 }
118 };
119
120 Some((attenuation, Ray::new(rec.p, direction)))
121 }
122}