use std::iter; use crate::{ accel::ray_code, color::{rec709_to_xyz, Color}, lerp::lerp_slice, math::Matrix4x4, ray::{RayBatch, RayStack}, scene::{Assembly, InstanceType, Object}, shading::{SimpleSurfaceShader, SurfaceShader}, surface::SurfaceIntersection, transform_stack::TransformStack, }; pub struct Tracer<'a> { ray_trace_count: u64, ray_stack: RayStack, inner: TracerInner<'a>, } impl<'a> Tracer<'a> { pub fn from_assembly(assembly: &'a Assembly) -> Tracer<'a> { Tracer { ray_trace_count: 0, ray_stack: RayStack::new(), inner: TracerInner { root: assembly, xform_stack: TransformStack::new(), isects: Vec::new(), }, } } pub fn trace<'b>(&'b mut self, rays: &mut RayBatch) -> &'b [SurfaceIntersection] { self.ray_trace_count += rays.len() as u64; self.inner.trace(rays, &mut self.ray_stack) } pub fn rays_traced(&self) -> u64 { self.ray_trace_count } } struct TracerInner<'a> { root: &'a Assembly<'a>, xform_stack: TransformStack, isects: Vec, } impl<'a> TracerInner<'a> { fn trace<'b>( &'b mut self, rays: &mut RayBatch, ray_stack: &mut RayStack, ) -> &'b [SurfaceIntersection] { ray_stack.clear(); // Ready the isects self.isects.clear(); self.isects.reserve(rays.len()); self.isects .extend(iter::repeat(SurfaceIntersection::Miss).take(rays.len())); // Prep the accel part of the rays. { let ident = Matrix4x4::new(); for i in 0..rays.len() { rays.update_local(i, &ident); } } // Divide the rays into 8 different lanes by direction. ray_stack.ensure_lane_count(8); for i in 0..rays.len() { ray_stack.push_ray_index(i, ray_code(rays.dir(i))); } ray_stack.push_lanes_to_tasks(&[0, 1, 2, 3, 4, 5, 6, 7]); // Trace each of the 8 lanes separately. while !ray_stack.is_empty() { self.trace_assembly(self.root, rays, ray_stack); } &self.isects } fn trace_assembly<'b>( &'b mut self, assembly: &Assembly, rays: &mut RayBatch, ray_stack: &mut RayStack, ) { assembly .object_accel .traverse(rays, ray_stack, |idx_range, rays, ray_stack| { let inst = &assembly.instances[idx_range.start]; // Transform rays if needed if let Some((xstart, xend)) = inst.transform_indices { // Push transforms to stack self.xform_stack.push(&assembly.xforms[xstart..xend]); // Do transforms // TODO: re-divide rays based on direction (maybe?). let xforms = self.xform_stack.top(); ray_stack.do_next_task(|ray_idx| { let t = rays.time(ray_idx); rays.update_local(ray_idx, &lerp_slice(xforms, t)); }); ray_stack.duplicate_next_task(); } // Trace rays match inst.instance_type { InstanceType::Object => { self.trace_object( &assembly.objects[inst.data_index], inst.surface_shader_index .map(|i| assembly.surface_shaders[i]), rays, ray_stack, ); } InstanceType::Assembly => { self.trace_assembly(&assembly.assemblies[inst.data_index], rays, ray_stack); } } // Un-transform rays if needed if inst.transform_indices.is_some() { // Pop transforms off stack self.xform_stack.pop(); // Undo transforms let xforms = self.xform_stack.top(); if !xforms.is_empty() { ray_stack.pop_do_next_task(|ray_idx| { let t = rays.time(ray_idx); rays.update_local(ray_idx, &lerp_slice(xforms, t)); }); } else { let ident = Matrix4x4::new(); ray_stack.pop_do_next_task(|ray_idx| { rays.update_local(ray_idx, &ident); }); } } }); } fn trace_object<'b>( &'b mut self, obj: &Object, surface_shader: Option<&dyn SurfaceShader>, rays: &mut RayBatch, ray_stack: &mut RayStack, ) { match *obj { Object::Surface(surface) => { let unassigned_shader = SimpleSurfaceShader::Emit { color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))), }; let shader = surface_shader.unwrap_or(&unassigned_shader); surface.intersect_rays( rays, ray_stack, &mut self.isects, shader, self.xform_stack.top(), ); } Object::SurfaceLight(surface) => { // Lights don't use shaders let bogus_shader = SimpleSurfaceShader::Emit { color: Color::new_xyz(rec709_to_xyz((1.0, 0.0, 1.0))), }; surface.intersect_rays( rays, ray_stack, &mut self.isects, &bogus_shader, self.xform_stack.top(), ); } } } }