use std::iter; use std::cell::UnsafeCell; use algorithm::partition; use math::{Matrix4x4, multiply_matrix_slices}; use lerp::lerp_slice; use assembly::{Assembly, Object, InstanceType}; use ray::{Ray, AccelRay}; use surface::SurfaceIntersection; pub struct Tracer<'a> { root: &'a Assembly, rays: UnsafeCell>, // Should only be used from trace(), not any other methods xform_stack: TransformStack, isects: Vec, } impl<'a> Tracer<'a> { pub fn from_assembly(assembly: &'a Assembly) -> Tracer<'a> { Tracer { root: assembly, rays: UnsafeCell::new(Vec::new()), xform_stack: TransformStack::new(), isects: Vec::new(), } } pub fn trace<'b>(&'b mut self, wrays: &[Ray]) -> &'b [SurfaceIntersection] { // Ready the rays let rays_ptr = self.rays.get(); unsafe { (*rays_ptr).clear(); (*rays_ptr).reserve(wrays.len()); let mut ids = 0..(wrays.len() as u32); (*rays_ptr).extend(wrays.iter().map(|wr| AccelRay::new(wr, ids.next().unwrap()))); } // Ready the isects self.isects.clear(); self.isects.reserve(wrays.len()); self.isects.extend(iter::repeat(SurfaceIntersection::Miss).take(wrays.len())); // Start tracing let ray_refs = unsafe { // IMPORTANT NOTE: // We're creating an unsafe non-lifetime-bound slice of self.rays // here so that we can pass it to trace_assembly() without // conflicting with self. // Because of this, it is absolutely CRITICAL that self.rays // NOT be used in any other methods. The rays should only be // accessed in other methods via the mutable slice passed directly // to them in their function parameters. &mut (*rays_ptr)[..] }; self.trace_assembly(self.root, wrays, ray_refs); return &self.isects; } fn trace_assembly<'b>(&'b mut self, assembly: &Assembly, wrays: &[Ray], accel_rays: &mut [AccelRay]) { assembly.object_accel.traverse(&mut accel_rays[..], &assembly.instances[..], |inst, rs| { // 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 let xforms = self.xform_stack.top(); for ray in &mut rs[..] { let id = ray.id; let t = ray.time; ray.update_from_xformed_world_ray(&wrays[id as usize], &lerp_slice(xforms, t)); } } // Trace rays { // This is kind of weird looking, but what we're doing here is // splitting the rays up based on direction if they were // transformed, and not splitting them up if they weren't // transformed. // But to keep the actual tracing code in one place (DRY), // we map both cases to an array slice that contains slices of // ray arrays. Gah... that's confusing even when explained. // TODO: do this in a way that's less confusing. Probably split // the tracing code out into a trace_instance() method or // something. let mut tmp = if let Some(_) = inst.transform_indices { split_rays_by_direction(rs) } else { [&mut rs[..], &mut [], &mut [], &mut [], &mut [], &mut [], &mut [], &mut []] }; let mut ray_sets = if let Some(_) = inst.transform_indices { &mut tmp[..] } else { &mut tmp[..1] }; // Loop through the split ray slices and trace them for ray_set in ray_sets.iter_mut().filter(|ray_set| ray_set.len() > 0) { match inst.instance_type { InstanceType::Object => { self.trace_object(&assembly.objects[inst.data_index], wrays, ray_set); } InstanceType::Assembly => { self.trace_assembly(&assembly.assemblies[inst.data_index], wrays, ray_set); } } } } // Un-transform rays if needed if let Some(_) = inst.transform_indices { // Pop transforms off stack self.xform_stack.pop(); // Undo transforms let xforms = self.xform_stack.top(); if xforms.len() > 0 { for ray in &mut rs[..] { let id = ray.id; let t = ray.time; ray.update_from_xformed_world_ray(&wrays[id as usize], &lerp_slice(xforms, t)); } } else { for ray in &mut rs[..] { let id = ray.id; ray.update_from_world_ray(&wrays[id as usize]); } } } }); } fn trace_object<'b>(&'b mut self, obj: &Object, wrays: &[Ray], rays: &mut [AccelRay]) { match obj { &Object::Surface(ref surface) => { surface.intersect_rays(rays, wrays, &mut self.isects, self.xform_stack.top()); } &Object::Light(_) => { // TODO } } } } fn split_rays_by_direction(rays: &mut [AccelRay]) -> [&mut [AccelRay]; 8] { // | | | | | | | | | // s1 s2 s3 s4 s5 s6 s7 let s4 = partition(&mut rays[..], |r| r.dir_inv[0].is_sign_positive()); let s2 = partition(&mut rays[..s4], |r| r.dir_inv[1].is_sign_positive()); let s6 = s4 + partition(&mut rays[s4..], |r| r.dir_inv[1].is_sign_positive()); let s1 = partition(&mut rays[..s2], |r| r.dir_inv[2].is_sign_positive()); let s3 = s2 + partition(&mut rays[s2..s4], |r| r.dir_inv[2].is_sign_positive()); let s5 = s4 + partition(&mut rays[s4..s6], |r| r.dir_inv[2].is_sign_positive()); let s7 = s6 + partition(&mut rays[s6..], |r| r.dir_inv[2].is_sign_positive()); let (rest, rs7) = rays.split_at_mut(s7); let (rest, rs6) = rest.split_at_mut(s6); let (rest, rs5) = rest.split_at_mut(s5); let (rest, rs4) = rest.split_at_mut(s4); let (rest, rs3) = rest.split_at_mut(s3); let (rest, rs2) = rest.split_at_mut(s2); let (rs0, rs1) = rest.split_at_mut(s1); [rs0, rs1, rs2, rs3, rs4, rs5, rs6, rs7] } struct TransformStack { stack: Vec, stack_indices: Vec, scratch_space: Vec, } impl TransformStack { fn new() -> TransformStack { let mut ts = TransformStack { stack: Vec::new(), stack_indices: Vec::new(), scratch_space: Vec::new(), }; ts.stack_indices.push(0); ts.stack_indices.push(0); ts } fn push(&mut self, xforms: &[Matrix4x4]) { assert!(xforms.len() > 0); if self.stack.len() == 0 { self.stack.extend(xforms); } else { let sil = self.stack_indices.len(); let i1 = self.stack_indices[sil - 2]; let i2 = self.stack_indices[sil - 1]; self.scratch_space.clear(); multiply_matrix_slices(&self.stack[i1..i2], xforms, &mut self.scratch_space); self.stack.extend(&self.scratch_space); } self.stack_indices.push(self.stack.len()); } fn pop(&mut self) { assert!(self.stack_indices.len() > 1); let sl = self.stack.len(); let sil = self.stack_indices.len(); let i1 = self.stack_indices[sil - 2]; let i2 = self.stack_indices[sil - 1]; self.stack.truncate(sl - (i2 - i1)); self.stack_indices.pop(); } fn top<'a>(&'a self) -> &'a [Matrix4x4] { let sil = self.stack_indices.len(); let i1 = self.stack_indices[sil - 2]; let i2 = self.stack_indices[sil - 1]; &self.stack[i1..i2] } }