1use std::rc::Rc;
4
5use crate::color::Color;
6use crate::light::PointLight;
7use crate::shape::Shape;
8use crate::texture::{solid_color::SolidColor, Texture};
9use crate::tuple::Tuple;
10
11#[derive(Clone)]
13pub struct Material {
14 pub texture: Rc<dyn Texture>,
15 pub ambient: f64,
16 pub diffuse: f64,
17 pub specular: f64,
18 pub shininess: f64,
19}
20
21impl Material {
22 pub fn lighting(
27 &self,
28 shape: Rc<dyn Shape>,
29 light: &PointLight,
30 position: Tuple,
31 eye: Tuple,
32 normal: Tuple,
33 in_shadow: bool,
34 ) -> Color {
35 let color = self.texture.color_at_shape(position, Rc::clone(&shape)) * light.color();
36 let lightv = (light.position() - position).normalized();
37 let ambient = color * self.ambient;
38 let light_dot_normal = lightv.dot(normal);
39
40 if in_shadow || light_dot_normal < 0.0 {
41 return ambient;
42 }
43
44 let diffuse = color * self.diffuse * light_dot_normal;
45 let reflectv = (-lightv).reflect(normal);
46 let reflect_dot_eye = reflectv.dot(eye);
47
48 if reflect_dot_eye <= 0.0 {
49 return ambient + diffuse;
50 }
51
52 let factor = reflect_dot_eye.powf(self.shininess);
53 let specular = light.color() * self.specular * factor;
54
55 return ambient + diffuse + specular;
56 }
57}
58
59impl Default for Material {
60 fn default() -> Self {
61 Self {
62 texture: Rc::new(SolidColor::new(Color::new(1.0, 1.0, 1.0))),
63 ambient: 0.1,
64 diffuse: 0.9,
65 specular: 0.9,
66 shininess: 200.0,
67 }
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::shape::sphere::Sphere;
75
76 #[test]
77 fn lighting_eye_between_light_and_surface() {
78 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
79
80 let material = Material::default();
81 let position = Tuple::point(0.0, 0.0, 0.0);
82
83 let eye = Tuple::vector(0.0, 0.0, -1.0);
84 let normal = Tuple::vector(0.0, 0.0, -1.0);
85 let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
86
87 let result = material.lighting(shape, &light, position, eye, normal, false);
88 assert_eq!(result, Color::new(1.9, 1.9, 1.9));
89 }
90
91 #[test]
92 fn lighting_eye_between_light_and_surface_light_offset_45deg() {
93 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
94
95 let material = Material::default();
96 let position = Tuple::point(0.0, 0.0, 0.0);
97
98 let eye = Tuple::vector(0.0, (2.0 as f64).sqrt() / 2.0, -(2.0 as f64).sqrt() / 2.0);
99 let normal = Tuple::vector(0.0, 0.0, -1.0);
100 let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
101
102 let result = material.lighting(shape, &light, position, eye, normal, false);
103 assert_eq!(result, Color::new(1.0, 1.0, 1.0));
104 }
105
106 #[test]
107 fn lighting_eye_opposite_surface_light_offset_45deg() {
108 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
109
110 let material = Material::default();
111 let position = Tuple::point(0.0, 0.0, 0.0);
112
113 let eye = Tuple::vector(0.0, 0.0, -1.0);
114 let normal = Tuple::vector(0.0, 0.0, -1.0);
115 let light = PointLight::new(Tuple::point(0.0, 10.0, -10.0), Color::new(1.0, 1.0, 1.0));
116
117 let result = material.lighting(shape, &light, position, eye, normal, false);
118 assert_eq!(
119 result,
120 Color::new(0.7363961030678927, 0.7363961030678927, 0.7363961030678927)
121 );
122 }
123
124 #[test]
125 fn lighting_eye_in_path_reflector() {
126 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
127
128 let material = Material::default();
129 let position = Tuple::point(0.0, 0.0, 0.0);
130
131 let eye = Tuple::vector(0.0, -(2.0 as f64).sqrt() / 2.0, -(2.0 as f64).sqrt() / 2.0);
132 let normal = Tuple::vector(0.0, 0.0, -1.0);
133 let light = PointLight::new(Tuple::point(0.0, 10.0, -10.0), Color::new(1.0, 1.0, 1.0));
134
135 let result = material.lighting(shape, &light, position, eye, normal, false);
136 assert_eq!(
137 result,
138 Color::new(1.6363961030678928, 1.6363961030678928, 1.6363961030678928)
139 );
140 }
141
142 #[test]
143 fn lighting_light_behind_surface() {
144 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
145
146 let material = Material::default();
147 let position = Tuple::point(0.0, 0.0, 0.0);
148
149 let eye = Tuple::vector(0.0, 0.0, -1.0);
150 let normal = Tuple::vector(0.0, 0.0, -1.0);
151 let light = PointLight::new(Tuple::point(0.0, 0.0, 10.0), Color::new(1.0, 1.0, 1.0));
152
153 let result = material.lighting(shape, &light, position, eye, normal, false);
154 assert_eq!(result, Color::new(0.1, 0.1, 0.1));
155 }
156
157 #[test]
158 fn lighting_surface_in_shadow() {
159 let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
160
161 let material = Material::default();
162 let position = Tuple::point(0.0, 0.0, 0.0);
163
164 let eye = Tuple::vector(0.0, 0.0, -1.0);
165 let normal = Tuple::vector(0.0, 0.0, -1.0);
166 let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
167
168 let result = material.lighting(shape, &light, position, eye, normal, true);
169 assert_eq!(result, Color::new(0.1, 0.1, 0.1));
170 }
171}