psychopath/src/tracer.rs

192 lines
5.9 KiB
Rust

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<SurfaceIntersection>,
}
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(),
);
}
}
}
}