From 6ccd4e306daae68a6bd85d37f6694ffd7645d10d Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Tue, 2 Aug 2022 17:29:05 -0700 Subject: [PATCH] WIP getting rid of LightPath struct. Committing at this point because: 1. It compiles. 2. Rendering is totally wrong, but in a cool way. --- src/main.rs | 2 - src/renderer.rs | 800 +++++++++++++++++++++++++++++------------------- src/tracer.rs | 7 +- 3 files changed, 490 insertions(+), 319 deletions(-) diff --git a/src/main.rs b/src/main.rs index a8d10ed..e7f751e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,7 +52,6 @@ use crate::{ accel::BVH4Node, bbox::BBox, parse::{parse_scene, DataTree}, - renderer::LightPath, surface::SurfaceIntersection, timer::Timer, }; @@ -164,7 +163,6 @@ fn main() { "SurfaceIntersection size: {} bytes", mem::size_of::() ); - println!("LightPath size: {} bytes", mem::size_of::()); println!("BBox size: {} bytes", mem::size_of::()); // println!("BVHNode size: {} bytes", mem::size_of::()); println!("BVH4Node size: {} bytes", mem::size_of::()); diff --git a/src/renderer.rs b/src/renderer.rs index 1638006..efb7b5e 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -20,7 +20,6 @@ use crate::{ scene::{Scene, SceneLightSample}, scramble::owen4, space_fill::{hilbert, morton}, - surface, timer::Timer, tracer::Tracer, }; @@ -243,7 +242,7 @@ impl<'a> Renderer<'a> { let (d1, d2, d3, d4) = get_sample_4d(si, 0, self.seed); let (d5, _, _, _) = get_sample_4d(si, 1, self.seed); - // Calculate image plane x and y coordinates + // Calculate the values we need to generate a camera ray. let (img_x, img_y) = { let filter_x = probit(d4, 2.0 / 6.0) + 0.5; let filter_y = probit(d5, 2.0 / 6.0) + 0.5; @@ -251,31 +250,19 @@ impl<'a> Renderer<'a> { let samp_y = (filter_y + y as f32) * cmpy; ((samp_x - 0.5) * x_extent, (0.5 - samp_y) * y_extent) }; + let wavelength = map_0_1_to_wavelength(d0); + let time = d1; + let lens_uv = (d2, d3); - // Create the light path and initial ray for this sample. - let (mut path, mut ray) = LightPath::new( - &self.scene, - self.seed, - (x, y), - (img_x, img_y), - (d2, d3), - d1, - map_0_1_to_wavelength(d0), - si as u32, - ); - - // Trace light path. - let mut isect; - loop { - isect = surface::SurfaceIntersection::Miss; - tracer.trace(&mut ray, &mut isect); - if !path.next(&self.scene, &isect, &mut ray) { - break; - } - } + // Trace light path starting from a camera ray. + let ray = self + .scene + .camera + .generate_ray(img_x, img_y, time, wavelength, lens_uv.0, lens_uv.1); + let path_col = + trace_camera_light_path(&mut tracer, &self.scene, ray, si, self.seed); // Accummulate light path color to pixel. - let path_col = SpectralSample::from_parts(path.color, path.wavelength); let mut col = img_bucket.get(x, y); col += XYZ::from_spectral_sample(&path_col) / self.spp as f32; img_bucket.set(x, y, col); @@ -334,328 +321,511 @@ impl<'a> Renderer<'a> { } } -#[derive(Debug)] -enum LightPathEvent { - CameraRay, - BounceRay, - ShadowRay, -} +fn trace_camera_light_path( + tracer: &mut Tracer, + scene: &Scene, + camera_ray: Ray, + sample_index: u32, + seed: u32, +) -> SpectralSample { + use crate::shading::surface_closure::SurfaceClosure; + use crate::surface::SurfaceIntersection; -#[derive(Debug)] -pub struct LightPath { - event: LightPathEvent, - bounce_count: u32, + const BOUNCE_COUNT: usize = 3; - sampling_seed: u32, - pixel_co: (u32, u32), - sample_number: u32, // Which sample in the LDS sequence this is. - dim_offset: u32, - time: f32, - wavelength: f32, + let mut ray = camera_ray; + let mut ray_pdf = 1.0; // PDF from generating the ray. + let mut acc_color = Float4::splat(0.0); // Accumulated color. + let mut attenuation = Float4::splat(1.0); // Color attenuation along the path so far. + let mut sampling_seed = seed + 1; - next_bounce_ray: Option, - next_attenuation_fac: Float4, + for _ in 0..BOUNCE_COUNT { + let isect = tracer.trace(&mut ray); + if let SurfaceIntersection::Hit { + intersection_data: idata, + closure, + } = &isect + { + // Hit something! Do the stuff - closure_sample_pdf: f32, - light_attenuation: Float4, - pending_color_addition: Float4, - color: Float4, -} + // If it's an emission closure, handle specially: + // - Collect light from the emission. + // - Terminate the path. + if let SurfaceClosure::Emit(color) = *closure { + let mis_pdf = power_heuristic(ray_pdf, idata.sample_pdf); + acc_color += color.to_spectral_sample(ray.wavelength).e * attenuation / mis_pdf; -#[allow(clippy::new_ret_no_self)] -impl LightPath { - fn new( - scene: &Scene, - sampling_seed: u32, - pixel_co: (u32, u32), - image_plane_co: (f32, f32), - lens_uv: (f32, f32), - time: f32, - wavelength: f32, - sample_number: u32, - ) -> (LightPath, Ray) { - ( - LightPath { - event: LightPathEvent::CameraRay, - bounce_count: 0, + break; + } - sampling_seed: sampling_seed ^ 0x40d4682b, - pixel_co: pixel_co, - sample_number: sample_number, - dim_offset: 0, - time: time, - wavelength: wavelength, + // Roll the previous closure pdf into the attenauation + attenuation /= ray_pdf; - next_bounce_ray: None, - next_attenuation_fac: Float4::splat(1.0), + //------------------------------------------------- + // Sample light sources. - closure_sample_pdf: 1.0, - light_attenuation: Float4::splat(1.0), - pending_color_addition: Float4::splat(0.0), - color: Float4::splat(0.0), - }, - scene.camera.generate_ray( - image_plane_co.0, - image_plane_co.1, - time, - wavelength, - lens_uv.0, - lens_uv.1, - ), - ) - } + sampling_seed = sampling_seed.wrapping_add(1); + let (light_n, d2, d3, d4) = get_sample_4d(sample_index, 0, sampling_seed); - fn next_lds_sequence(&mut self) { - self.dim_offset = 0; - self.sampling_seed += 1; - } + let light_uvw = (d2, d3, d4); + let light_info = scene.sample_lights( + light_n, + light_uvw, + ray.wavelength, + ray.time, + &XformFull::identity(), + &isect, + ); + if !light_info.is_none() && light_info.pdf() > 0.0 && light_info.selection_pdf() > 0.0 { + let light_pdf = light_info.pdf(); + let light_sel_pdf = light_info.selection_pdf(); - fn next_lds_samp(&mut self) -> (f32, f32, f32, f32) { - let dimension = self.dim_offset; - self.dim_offset += 1; - get_sample_4d(self.sample_number, dimension, self.sampling_seed) - } + // Calculate the shadow ray and surface closure stuff. + let (light_attenuation, closure_pdf, mut shadow_ray) = match light_info { + SceneLightSample::None => unreachable!(), - fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool { - match self.event { - //-------------------------------------------------------------------- - // Result of Camera or bounce ray, prepare next bounce and light rays - LightPathEvent::CameraRay | LightPathEvent::BounceRay => { - if let surface::SurfaceIntersection::Hit { - intersection_data: ref idata, - ref closure, - } = *isect - { - // Hit something! Do the stuff - - // If it's an emission closure, handle specially: - // - Collect light from the emission. - // - Terminate the path. - use crate::shading::surface_closure::SurfaceClosure; - if let SurfaceClosure::Emit(color) = *closure { - let color = color.to_spectral_sample(self.wavelength).e; - if let LightPathEvent::CameraRay = self.event { - self.color += color; - } else { - let mis_pdf = - power_heuristic(self.closure_sample_pdf, idata.sample_pdf); - self.color += color * self.light_attenuation / mis_pdf; - }; - - return false; - } - - // Roll the previous closure pdf into the attenauation - self.light_attenuation /= self.closure_sample_pdf; - - // Prepare light ray - self.next_lds_sequence(); - let (light_n, d2, d3, d4) = self.next_lds_samp(); - let light_uvw = (d2, d3, d4); - let light_info = scene.sample_lights( - light_n, - light_uvw, - self.wavelength, - self.time, - &XformFull::identity(), - isect, - ); - let found_light = if light_info.is_none() - || light_info.pdf() <= 0.0 - || light_info.selection_pdf() <= 0.0 - { - false - } else { - let light_pdf = light_info.pdf(); - let light_sel_pdf = light_info.selection_pdf(); - - // Calculate the shadow ray and surface closure stuff - let (attenuation, closure_pdf, shadow_ray) = match light_info { - SceneLightSample::None => unreachable!(), - - // Distant light - SceneLightSample::Distant { direction, .. } => { - let (attenuation, closure_pdf) = closure.evaluate( - ray.dir, - direction, - idata.nor, - idata.nor_g, - self.wavelength, - ); - let shadow_ray = { - // Calculate the shadow ray for testing if the light is - // in shadow or not. - let offset_pos = robust_ray_origin( - idata.pos, - idata.pos_err, - idata.nor_g.normalized(), - direction, - ); - Ray::new( - offset_pos, - direction, - self.time, - self.wavelength, - std::f32::INFINITY, - true, - ) - }; - (attenuation, closure_pdf, shadow_ray) - } - - // Surface light - SceneLightSample::Surface { sample_geo, .. } => { - let dir = sample_geo.0 - idata.pos; - let (attenuation, closure_pdf) = closure.evaluate( - ray.dir, - dir, - idata.nor, - idata.nor_g, - self.wavelength, - ); - let shadow_ray = { - // Calculate the shadow ray for testing if the light is - // in shadow or not. - let offset_pos = robust_ray_origin( - idata.pos, - idata.pos_err, - idata.nor_g.normalized(), - dir, - ); - let offset_end = robust_ray_origin( - sample_geo.0, - sample_geo.2, - sample_geo.1.normalized(), - -dir, - ); - Ray::new( - offset_pos, - offset_end - offset_pos, - self.time, - self.wavelength, - 1.0, - true, - ) - }; - (attenuation, closure_pdf, shadow_ray) - } - }; - - // If there's any possible contribution, set up for a - // light ray. - if attenuation.e.max_element() <= 0.0 { - false - } else { - // Calculate and store the light that will be contributed - // to the film plane if the light is not in shadow. - let light_mis_pdf = power_heuristic(light_pdf, closure_pdf); - self.pending_color_addition = - light_info.color().e * attenuation.e * self.light_attenuation - / (light_mis_pdf * light_sel_pdf); - - *ray = shadow_ray; - - true - } - }; - - // Prepare bounce ray - let do_bounce = if self.bounce_count < 2 { - self.bounce_count += 1; - - // Sample closure - let (dir, filter, pdf) = { - self.next_lds_sequence(); - let (u, v, _, _) = self.next_lds_samp(); - closure.sample( - idata.incoming, - idata.nor, - idata.nor_g, - (u, v), - self.wavelength, + // Distant light + SceneLightSample::Distant { direction, .. } => { + let (light_attenuation, closure_pdf) = closure.evaluate( + ray.dir, + direction, + idata.nor, + idata.nor_g, + ray.wavelength, + ); + let shadow_ray = { + // Calculate the shadow ray for testing if the light is + // in shadow or not. + let offset_pos = robust_ray_origin( + idata.pos, + idata.pos_err, + idata.nor_g.normalized(), + direction, + ); + Ray::new( + offset_pos, + direction, + ray.time, + ray.wavelength, + std::f32::INFINITY, + true, ) }; + (light_attenuation, closure_pdf, shadow_ray) + } - // Check if pdf is zero, to avoid NaN's. - if (pdf > 0.0) && (filter.e.max_element() > 0.0) { - // Account for the additional light attenuation from - // this bounce - self.next_attenuation_fac = filter.e; - self.closure_sample_pdf = pdf; - - // Calculate the ray for this bounce + // Surface light + SceneLightSample::Surface { sample_geo, .. } => { + let dir = sample_geo.0 - idata.pos; + let (light_attenuation, closure_pdf) = + closure.evaluate(ray.dir, dir, idata.nor, idata.nor_g, ray.wavelength); + let shadow_ray = { + // Calculate the shadow ray for testing if the light is + // in shadow or not. let offset_pos = robust_ray_origin( idata.pos, idata.pos_err, idata.nor_g.normalized(), dir, ); - self.next_bounce_ray = Some(Ray::new( + let offset_end = robust_ray_origin( + sample_geo.0, + sample_geo.2, + sample_geo.1.normalized(), + -dir, + ); + Ray::new( offset_pos, - dir, - self.time, - self.wavelength, - std::f32::INFINITY, - false, - )); - - true - } else { - false - } - } else { - self.next_bounce_ray = None; - false - }; - - // Book keeping for next event - if found_light { - self.event = LightPathEvent::ShadowRay; - return true; - } else if do_bounce { - *ray = self.next_bounce_ray.unwrap(); - self.event = LightPathEvent::BounceRay; - self.light_attenuation *= self.next_attenuation_fac; - return true; - } else { - return false; + offset_end - offset_pos, + ray.time, + ray.wavelength, + 1.0, + true, + ) + }; + (light_attenuation, closure_pdf, shadow_ray) + } + }; + + // If there's any possible contribution, shoot a shadow + // ray to see if we can reach it. + if light_attenuation.e.max_element() > 0.0 { + if let SurfaceIntersection::Occlude = tracer.trace(&mut shadow_ray) { + // Calculate and store the light that will be contributed + // to the film plane if the light is not in shadow. + let light_mis_pdf = power_heuristic(light_pdf, closure_pdf); + acc_color += light_info.color().e * light_attenuation.e * attenuation + / (light_mis_pdf * light_sel_pdf); } - } else { - // Didn't hit anything, so background color - self.color += scene - .world - .background_color - .to_spectral_sample(self.wavelength) - .e - * self.light_attenuation - / self.closure_sample_pdf; - return false; } } - //-------------------------------------------------------------------- - // Result of shadow ray from sampling a light - LightPathEvent::ShadowRay => { - // If the light was not in shadow, add it's light to the film - // plane. - if let surface::SurfaceIntersection::Miss = *isect { - self.color += self.pending_color_addition; - } + //------------------------------------------------- + // Prepare next bounce ray. - // Set up for the next bounce, if any - if let Some(ref nbr) = self.next_bounce_ray { - *ray = *nbr; - self.light_attenuation *= self.next_attenuation_fac; - self.event = LightPathEvent::BounceRay; - return true; - } else { - return false; - } + // Sample closure + let (dir, filter, pdf) = { + sampling_seed = sampling_seed.wrapping_add(1); + let (u, v, _, _) = get_sample_4d(sample_index, 0, sampling_seed); + + closure.sample( + idata.incoming, + idata.nor, + idata.nor_g, + (u, v), + ray.wavelength, + ) + }; + + // Check if pdf is zero, to avoid NaN's. + if (pdf > 0.0) && (filter.e.max_element() > 0.0) { + // Account for the additional light attenuation from + // this bounce + attenuation *= filter.e; + ray_pdf = pdf; + + // Calculate the ray for this bounce + let offset_pos = + robust_ray_origin(idata.pos, idata.pos_err, idata.nor_g.normalized(), dir); + ray = Ray::new( + offset_pos, + dir, + ray.time, + ray.wavelength, + std::f32::INFINITY, + false, + ); + } else { + break; } } } + + SpectralSample::from_parts(acc_color, ray.wavelength) } +// #[derive(Debug)] +// enum LightPathEvent { +// CameraRay, +// BounceRay, +// ShadowRay, +// } + +// #[derive(Debug)] +// pub struct LightPath { +// event: LightPathEvent, +// bounce_count: u32, + +// sampling_seed: u32, +// pixel_co: (u32, u32), +// sample_number: u32, // Which sample in the LDS sequence this is. +// dim_offset: u32, +// time: f32, +// wavelength: f32, + +// next_bounce_ray: Option, +// next_attenuation_fac: Float4, + +// closure_sample_pdf: f32, +// light_attenuation: Float4, +// pending_color_addition: Float4, +// color: Float4, +// } + +// #[allow(clippy::new_ret_no_self)] +// impl LightPath { +// fn new( +// scene: &Scene, +// sampling_seed: u32, +// pixel_co: (u32, u32), +// image_plane_co: (f32, f32), +// lens_uv: (f32, f32), +// time: f32, +// wavelength: f32, +// sample_number: u32, +// ) -> (LightPath, Ray) { +// ( +// LightPath { +// event: LightPathEvent::CameraRay, +// bounce_count: 0, + +// sampling_seed: sampling_seed ^ 0x40d4682b, +// pixel_co: pixel_co, +// sample_number: sample_number, +// dim_offset: 0, +// time: time, +// wavelength: wavelength, + +// next_bounce_ray: None, +// next_attenuation_fac: Float4::splat(1.0), + +// closure_sample_pdf: 1.0, +// light_attenuation: Float4::splat(1.0), +// pending_color_addition: Float4::splat(0.0), +// color: Float4::splat(0.0), +// }, +// scene.camera.generate_ray( +// image_plane_co.0, +// image_plane_co.1, +// time, +// wavelength, +// lens_uv.0, +// lens_uv.1, +// ), +// ) +// } + +// fn next_lds_sequence(&mut self) { +// self.dim_offset = 0; +// self.sampling_seed += 1; +// } + +// fn next_lds_samp(&mut self) -> (f32, f32, f32, f32) { +// let dimension = self.dim_offset; +// self.dim_offset += 1; +// get_sample_4d(self.sample_number, dimension, self.sampling_seed) +// } + +// fn next(&mut self, scene: &Scene, isect: &surface::SurfaceIntersection, ray: &mut Ray) -> bool { +// match self.event { +// //-------------------------------------------------------------------- +// // Result of Camera or bounce ray, prepare next bounce and light rays +// LightPathEvent::CameraRay | LightPathEvent::BounceRay => { +// if let surface::SurfaceIntersection::Hit { +// intersection_data: ref idata, +// ref closure, +// } = *isect +// { +// // Hit something! Do the stuff + +// // If it's an emission closure, handle specially: +// // - Collect light from the emission. +// // - Terminate the path. +// use crate::shading::surface_closure::SurfaceClosure; +// if let SurfaceClosure::Emit(color) = *closure { +// let color = color.to_spectral_sample(self.wavelength).e; +// if let LightPathEvent::CameraRay = self.event { +// self.color += color; +// } else { +// let mis_pdf = +// power_heuristic(self.closure_sample_pdf, idata.sample_pdf); +// self.color += color * self.light_attenuation / mis_pdf; +// }; + +// return false; +// } + +// // Roll the previous closure pdf into the attenauation +// self.light_attenuation /= self.closure_sample_pdf; + +// // Prepare light ray +// self.next_lds_sequence(); +// let (light_n, d2, d3, d4) = self.next_lds_samp(); +// let light_uvw = (d2, d3, d4); +// let light_info = scene.sample_lights( +// light_n, +// light_uvw, +// self.wavelength, +// self.time, +// &XformFull::identity(), +// isect, +// ); +// let found_light = if light_info.is_none() +// || light_info.pdf() <= 0.0 +// || light_info.selection_pdf() <= 0.0 +// { +// false +// } else { +// let light_pdf = light_info.pdf(); +// let light_sel_pdf = light_info.selection_pdf(); + +// // Calculate the shadow ray and surface closure stuff +// let (attenuation, closure_pdf, shadow_ray) = match light_info { +// SceneLightSample::None => unreachable!(), + +// // Distant light +// SceneLightSample::Distant { direction, .. } => { +// let (attenuation, closure_pdf) = closure.evaluate( +// ray.dir, +// direction, +// idata.nor, +// idata.nor_g, +// self.wavelength, +// ); +// let shadow_ray = { +// // Calculate the shadow ray for testing if the light is +// // in shadow or not. +// let offset_pos = robust_ray_origin( +// idata.pos, +// idata.pos_err, +// idata.nor_g.normalized(), +// direction, +// ); +// Ray::new( +// offset_pos, +// direction, +// self.time, +// self.wavelength, +// std::f32::INFINITY, +// true, +// ) +// }; +// (attenuation, closure_pdf, shadow_ray) +// } + +// // Surface light +// SceneLightSample::Surface { sample_geo, .. } => { +// let dir = sample_geo.0 - idata.pos; +// let (attenuation, closure_pdf) = closure.evaluate( +// ray.dir, +// dir, +// idata.nor, +// idata.nor_g, +// self.wavelength, +// ); +// let shadow_ray = { +// // Calculate the shadow ray for testing if the light is +// // in shadow or not. +// let offset_pos = robust_ray_origin( +// idata.pos, +// idata.pos_err, +// idata.nor_g.normalized(), +// dir, +// ); +// let offset_end = robust_ray_origin( +// sample_geo.0, +// sample_geo.2, +// sample_geo.1.normalized(), +// -dir, +// ); +// Ray::new( +// offset_pos, +// offset_end - offset_pos, +// self.time, +// self.wavelength, +// 1.0, +// true, +// ) +// }; +// (attenuation, closure_pdf, shadow_ray) +// } +// }; + +// // If there's any possible contribution, set up for a +// // light ray. +// if attenuation.e.max_element() <= 0.0 { +// false +// } else { +// // Calculate and store the light that will be contributed +// // to the film plane if the light is not in shadow. +// let light_mis_pdf = power_heuristic(light_pdf, closure_pdf); +// self.pending_color_addition = +// light_info.color().e * attenuation.e * self.light_attenuation +// / (light_mis_pdf * light_sel_pdf); + +// *ray = shadow_ray; + +// true +// } +// }; + +// // Prepare bounce ray +// let do_bounce = if self.bounce_count < 2 { +// self.bounce_count += 1; + +// // Sample closure +// let (dir, filter, pdf) = { +// self.next_lds_sequence(); +// let (u, v, _, _) = self.next_lds_samp(); +// closure.sample( +// idata.incoming, +// idata.nor, +// idata.nor_g, +// (u, v), +// self.wavelength, +// ) +// }; + +// // Check if pdf is zero, to avoid NaN's. +// if (pdf > 0.0) && (filter.e.max_element() > 0.0) { +// // Account for the additional light attenuation from +// // this bounce +// self.next_attenuation_fac = filter.e; +// self.closure_sample_pdf = pdf; + +// // Calculate the ray for this bounce +// let offset_pos = robust_ray_origin( +// idata.pos, +// idata.pos_err, +// idata.nor_g.normalized(), +// dir, +// ); +// self.next_bounce_ray = Some(Ray::new( +// offset_pos, +// dir, +// self.time, +// self.wavelength, +// std::f32::INFINITY, +// false, +// )); + +// true +// } else { +// false +// } +// } else { +// self.next_bounce_ray = None; +// false +// }; + +// // Book keeping for next event +// if found_light { +// self.event = LightPathEvent::ShadowRay; +// return true; +// } else if do_bounce { +// *ray = self.next_bounce_ray.unwrap(); +// self.event = LightPathEvent::BounceRay; +// self.light_attenuation *= self.next_attenuation_fac; +// return true; +// } else { +// return false; +// } +// } else { +// // Didn't hit anything, so background color +// self.color += scene +// .world +// .background_color +// .to_spectral_sample(self.wavelength) +// .e +// * self.light_attenuation +// / self.closure_sample_pdf; +// return false; +// } +// } + +// //-------------------------------------------------------------------- +// // Result of shadow ray from sampling a light +// LightPathEvent::ShadowRay => { +// // If the light was not in shadow, add it's light to the film +// // plane. +// if let surface::SurfaceIntersection::Miss = *isect { +// self.color += self.pending_color_addition; +// } + +// // Set up for the next bounce, if any +// if let Some(ref nbr) = self.next_bounce_ray { +// *ray = *nbr; +// self.light_attenuation *= self.next_attenuation_fac; +// self.event = LightPathEvent::BounceRay; +// return true; +// } else { +// return false; +// } +// } +// } +// } +// } + /// Gets a sample, using LDS samples for lower dimensions, /// and switching to random samples at higher dimensions where /// LDS samples aren't available. diff --git a/src/tracer.rs b/src/tracer.rs index 3228d5e..123ad4d 100644 --- a/src/tracer.rs +++ b/src/tracer.rs @@ -25,13 +25,16 @@ impl<'a> Tracer<'a> { self.ray_trace_count } - pub fn trace(&mut self, ray: &mut Ray, isect: &mut SurfaceIntersection) { + pub fn trace(&mut self, ray: &mut Ray) -> SurfaceIntersection { self.ray_trace_count += 1; let local_ray = ray.to_local(); let space = XformFull::identity(); + let mut isect = SurfaceIntersection::Miss; - self.trace_assembly(self.root, ray, &local_ray, &space, isect); + self.trace_assembly(self.root, ray, &local_ray, &space, &mut isect); + + isect } fn trace_assembly(