1use super::*;
2
3impl Ray {
6 pub fn new(origin: Vector3D, direction: Vector3D) -> Ray {
20 Ray {
21 origin,
22 direction,
23 t_min: RAYTRACE_DEFAULT_T_MIN,
24 t_max: RAYTRACE_DEFAULT_T_MAX,
25 depth: 0,
26 }
27 }
28
29 pub fn at(&self, t: f64) -> Vector3D {
39 self.get_origin() + self.get_direction().scaled(t)
40 }
41
42 pub fn with_depth(&self, depth: u32) -> Ray {
52 Ray {
53 origin: self.get_origin(),
54 direction: self.get_direction(),
55 t_min: self.get_t_min(),
56 t_max: self.get_t_max(),
57 depth,
58 }
59 }
60}
61
62impl Occluder {
64 pub fn sphere(center: Vector3D, radius: f64, material: Material) -> Occluder {
76 Occluder {
77 kind: OccluderKind::Sphere,
78 center,
79 extent: Vector3D::new(radius, radius, radius),
80 material,
81 }
82 }
83
84 pub fn aabb(min: Vector3D, max: Vector3D, material: Material) -> Occluder {
96 Occluder {
97 kind: OccluderKind::Aabb,
98 center: min,
99 extent: max,
100 material,
101 }
102 }
103
104 pub fn occluder_points(&self) -> Vec<(Vector3D, f64)> {
115 collect_occluder_points(std::slice::from_ref(self))
116 }
117}
118
119impl RayTraceScene {
122 pub fn new(occluders: Vec<Occluder>) -> RayTraceScene {
134 let shadow_points: Vec<(Vector3D, f64)> = collect_occluder_points(&occluders);
135 RayTraceScene {
136 occluders,
137 shadow_points,
138 }
139 }
140
141 pub fn trace(&self, ray: Ray, lights: &LightingUniforms) -> Vector3D {
159 self.trace_with_bounces(ray, lights, RAYTRACE_DEFAULT_MAX_BOUNCES)
160 }
161
162 pub fn trace_with_bounces(
182 &self,
183 ray: Ray,
184 lights: &LightingUniforms,
185 max_bounces: u32,
186 ) -> Vector3D {
187 trace_bounces(
188 ray,
189 self.get_occluders(),
190 &self.shadow_points,
191 lights,
192 max_bounces,
193 )
194 }
195
196 pub fn closest_hit(&self, ray: &Ray) -> Option<Hit> {
210 let occluders: &[Occluder] = self.get_occluders();
211 closest_hit_indexed(ray, occluders).map(
212 |(index, t, position, normal): (usize, f64, Vector3D, Vector3D)| Hit {
213 t,
214 position,
215 normal,
216 material: occluders[index].get_material().clone(),
217 },
218 )
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
228 fn trace_miss_returns_ambient() {
229 let eye: Vector3D = Vector3D::new(0.0, 0.0, 0.0);
230 let mut lights: LightingUniforms = LightingUniforms::with_eye(eye);
231 lights.set_ambient(Vector3D::new(0.2, 0.4, 0.6));
232 let ray: Ray = Ray::new(Vector3D::new(0.0, 0.0, 0.0), Vector3D::new(1.0, 0.0, 0.0));
233 let occluders: Vec<Occluder> = Vec::new();
234 let scene: RayTraceScene = RayTraceScene::new(occluders);
235 let color: Vector3D = scene.trace(ray, &lights);
236 assert!(
237 (color.get_x() - 0.2).abs() < EPSILON,
238 "expected ambient red 0.2, got {}",
239 color.get_x(),
240 );
241 assert!(
242 (color.get_y() - 0.4).abs() < EPSILON,
243 "expected ambient green 0.4, got {}",
244 color.get_y(),
245 );
246 assert!(
247 (color.get_z() - 0.6).abs() < EPSILON,
248 "expected ambient blue 0.6, got {}",
249 color.get_z(),
250 );
251 }
252
253 #[test]
256 fn trace_emissive_sphere() {
257 let eye: Vector3D = Vector3D::new(0.0, 0.0, 5.0);
258 let mut lights: LightingUniforms = LightingUniforms::with_eye(eye);
259 lights.set_ambient(Vector3D::zero());
260 let sphere_material: Material = Material::emissive(Vector3D::new(1.0, 0.0, 0.0));
261 let sphere: Occluder = Occluder::sphere(Vector3D::zero(), 1.0, sphere_material);
262 let occluders: Vec<Occluder> = vec![sphere];
263 let scene: RayTraceScene = RayTraceScene::new(occluders);
264 let ray: Ray = Ray::new(Vector3D::new(0.0, 0.0, 5.0), Vector3D::new(0.0, 0.0, -1.0));
265 let color: Vector3D = scene.trace(ray, &lights);
266 assert!(
267 (color.get_x() - 1.0).abs() < EPSILON,
268 "expected emissive red 1.0, got {}",
269 color.get_x(),
270 );
271 assert!(
272 color.get_y().abs() < EPSILON,
273 "expected emissive green 0.0, got {}",
274 color.get_y(),
275 );
276 assert!(
277 color.get_z().abs() < EPSILON,
278 "expected emissive blue 0.0, got {}",
279 color.get_z(),
280 );
281 }
282
283 #[test]
286 fn trace_reflection_single_bounce() {
287 let eye: Vector3D = Vector3D::new(0.0, 0.0, 10.0);
288 let mut lights: LightingUniforms = LightingUniforms::with_eye(eye);
289 lights.set_ambient(Vector3D::zero());
290 let mirror_material: Material = Material::phong(Vector3D::zero(), 1.0, 32.0);
291 let mirror: Occluder = Occluder::sphere(Vector3D::zero(), 1.0, mirror_material);
292 let emissive_material: Material = Material::emissive(Vector3D::new(0.0, 1.0, 0.0));
296 let emissive: Occluder =
297 Occluder::sphere(Vector3D::new(0.0, 0.0, 15.0), 1.0, emissive_material);
298 let occluders: Vec<Occluder> = vec![mirror, emissive];
299 let scene: RayTraceScene = RayTraceScene::new(occluders);
300 let ray: Ray = Ray::new(Vector3D::new(0.0, 0.0, 10.0), Vector3D::new(0.0, 0.0, -1.0));
301 let color: Vector3D = scene.trace(ray, &lights);
302 assert!(
303 color.get_y() > 0.0,
304 "expected bounce to bring back some green, got {}",
305 color.get_y(),
306 );
307 assert!(
308 color.get_x().abs() < EPSILON,
309 "expected red ~0 (no red light), got {}",
310 color.get_x(),
311 );
312 assert!(
313 color.get_z().abs() < EPSILON,
314 "expected blue ~0 (no blue light), got {}",
315 color.get_z(),
316 );
317 }
318
319 fn demo_scene() -> (Vec<Occluder>, LightingUniforms) {
328 let ground: Occluder = Occluder::aabb(
329 Vector3D::new(-5.0, -0.6, -5.0),
330 Vector3D::new(5.0, -0.5, 5.0),
331 Material::phong(Vector3D::new(0.30, 0.32, 0.36), 0.30, 24.0),
332 );
333 let mirror: Occluder = Occluder::sphere(
334 Vector3D::new(0.0, 0.4, 0.0),
335 0.9,
336 Material::phong(Vector3D::new(0.05, 0.05, 0.06), 1.0, 64.0),
337 );
338 let emissive: Occluder = Occluder::sphere(
339 Vector3D::new(1.6, 0.6, -1.4),
340 0.45,
341 Material::emissive(Vector3D::new(1.0, 0.45, 0.10)),
342 );
343 let occluders: Vec<Occluder> = vec![ground, mirror, emissive];
344 let eye: Vector3D = Vector3D::new(0.0, 0.8, 3.5);
345 let yaw: f64 = 0.7;
346 let light_dir: Vector3D = Vector3D::new(-yaw.cos(), -0.5, -yaw.sin()).normalized();
347 let sun: Light = Light::new_directional(light_dir, Vector3D::new(1.0, 0.95, 0.85));
348 let mut lights: LightingUniforms = LightingUniforms::with_eye(eye);
349 lights.set_ambient(Vector3D::new(0.10, 0.10, 0.14));
350 lights.add_light(sun);
351 (occluders, lights)
352 }
353
354 #[test]
357 fn trace_matches_trace_with_bounces_at_default_limit() {
358 let (occluders, lights): (Vec<Occluder>, LightingUniforms) = demo_scene();
359 let scene: RayTraceScene = RayTraceScene::new(occluders);
360 let ray: Ray = Ray::new(
361 Vector3D::new(0.0, 0.8, 3.5),
362 Vector3D::new(0.0, -0.4, -3.5).normalized(),
363 );
364 let default_color: Vector3D = scene.trace(ray.clone(), &lights);
365 let explicit_color: Vector3D =
366 scene.trace_with_bounces(ray, &lights, RAYTRACE_DEFAULT_MAX_BOUNCES);
367 assert_eq!(
368 default_color, explicit_color,
369 "trace must equal trace_with_bounces at the default bounce limit",
370 );
371 }
372
373 #[test]
376 fn closest_hit_returns_analytic_t() {
377 let (occluders, _lights): (Vec<Occluder>, LightingUniforms) = demo_scene();
378 let scene: RayTraceScene = RayTraceScene::new(occluders);
379 let dead_center: Ray =
380 Ray::new(Vector3D::new(0.0, 0.4, 5.0), Vector3D::new(0.0, 0.0, -1.0));
381 let expected_t: f64 = 5.0 - 0.9;
382 let hit: Option<Hit> = scene.closest_hit(&dead_center);
383 assert!(hit.is_some(), "expected dead-center ray to hit the mirror");
384 assert!(
385 (hit.expect("checked above").get_t() - expected_t).abs() < 1e-9,
386 "expected analytic t {expected_t}",
387 );
388 let away: Ray = Ray::new(Vector3D::new(0.0, 0.4, 5.0), Vector3D::new(0.0, 0.0, 1.0));
389 assert!(
390 scene.closest_hit(&away).is_none(),
391 "expected ray pointing away from the scene to miss",
392 );
393 }
394}