psychopath/src/accel/light_tree.rs

368 lines
11 KiB
Rust

use mem_arena::MemArena;
use crate::{
algorithm::merge_slices_append,
bbox::BBox,
lerp::lerp_slice,
math::{Normal, Point, Vector},
shading::surface_closure::SurfaceClosure,
};
use super::{objects_split::sah_split, LightAccel};
const ARITY_LOG2: usize = 3; // Determines how much to collapse the binary tree,
// implicitly defining the light tree's arity. 1 = no collapsing, leave as binary
// tree.
const ARITY: usize = 1 << ARITY_LOG2; // Arity of the final tree
#[derive(Copy, Clone, Debug)]
pub struct LightTree<'a> {
root: Option<&'a Node<'a>>,
depth: usize,
}
#[derive(Copy, Clone, Debug)]
enum Node<'a> {
Inner {
children: &'a [Node<'a>],
bounds: &'a [BBox],
energy: f32,
},
Leaf {
light_index: usize,
bounds: &'a [BBox],
energy: f32,
},
}
impl<'a> Node<'a> {
fn bounds(&self) -> &'a [BBox] {
match *self {
Node::Inner { bounds, .. } | Node::Leaf { bounds, .. } => bounds,
}
}
fn energy(&self) -> f32 {
match *self {
Node::Inner { energy, .. } | Node::Leaf { energy, .. } => energy,
}
}
fn light_index(&self) -> usize {
match *self {
Node::Inner { .. } => panic!(),
Node::Leaf { light_index, .. } => light_index,
}
}
}
impl<'a> LightTree<'a> {
pub fn from_objects<'b, T, F>(
arena: &'a MemArena,
objects: &mut [T],
info_getter: F,
) -> LightTree<'a>
where
F: 'b + Fn(&T) -> (&'b [BBox], f32),
{
if objects.is_empty() {
LightTree {
root: None,
depth: 0,
}
} else {
let mut builder = LightTreeBuilder::new();
builder.recursive_build(0, 0, objects, &info_getter);
let root = unsafe { arena.alloc_uninitialized::<Node>() };
LightTree::construct_from_builder(arena, &builder, builder.root_node_index(), root);
LightTree {
root: Some(root),
depth: builder.depth,
}
}
}
fn construct_from_builder(
arena: &'a MemArena,
base: &LightTreeBuilder,
node_index: usize,
node_mem: &mut Node<'a>,
) {
if base.nodes[node_index].is_leaf {
// Leaf
let bounds_range = base.nodes[node_index].bounds_range;
let bounds = arena.copy_slice(&base.bounds[bounds_range.0..bounds_range.1]);
*node_mem = Node::Leaf {
light_index: base.nodes[node_index].child_index,
bounds: bounds,
energy: base.nodes[node_index].energy,
};
} else {
// Inner
let bounds_range = base.nodes[node_index].bounds_range;
let bounds = arena.copy_slice(&base.bounds[bounds_range.0..bounds_range.1]);
let child_count = base.node_child_count(node_index);
let children = unsafe { arena.alloc_array_uninitialized::<Node>(child_count) };
for i in 0..child_count {
LightTree::construct_from_builder(
arena,
base,
base.node_nth_child_index(node_index, i),
&mut children[i],
);
}
*node_mem = Node::Inner {
children: children,
bounds: bounds,
energy: base.nodes[node_index].energy,
};
}
}
}
impl<'a> LightAccel for LightTree<'a> {
fn select(
&self,
inc: Vector,
pos: Point,
nor: Normal,
nor_g: Normal,
sc: &SurfaceClosure,
time: f32,
n: f32,
) -> Option<(usize, f32, f32)> {
// Calculates the selection probability for a node
let node_prob = |node_ref: &Node| {
let bbox = lerp_slice(node_ref.bounds(), time);
let d = bbox.center() - pos;
let r2 = bbox.diagonal2() * 0.25;
let inv_surface_area = 1.0 / r2;
// Get the approximate amount of light contribution from the
// composite light source.
let approx_contrib = sc.estimate_eval_over_sphere_light(inc, d, r2, nor, nor_g);
node_ref.energy() * inv_surface_area * approx_contrib
};
// Traverse down the tree, keeping track of the relative probabilities
let mut node = self.root?;
let mut tot_prob = 1.0;
let mut n = n;
while let Node::Inner { children, .. } = *node {
// Calculate the relative probabilities of the children
let ps = {
let mut ps = [0.0; ARITY];
let mut total = 0.0;
for (i, child) in children.iter().enumerate() {
let p = node_prob(child);
ps[i] = p;
total += p;
}
if total <= 0.0 {
let p = 1.0 / children.len() as f32;
for prob in &mut ps[..] {
*prob = p;
}
} else {
for prob in &mut ps[..] {
*prob /= total;
}
}
ps
};
// Pick child and update probabilities
let mut base = 0.0;
for (i, &p) in ps.iter().enumerate() {
if (n <= base + p) || (i == children.len() - 1) {
tot_prob *= p;
node = &children[i];
n = (n - base) / p;
break;
} else {
base += p;
}
}
}
// Found our light!
Some((node.light_index(), tot_prob, n))
}
fn approximate_energy(&self) -> f32 {
if let Some(node) = self.root {
node.energy()
} else {
0.0
}
}
}
struct LightTreeBuilder {
nodes: Vec<BuilderNode>,
bounds: Vec<BBox>,
depth: usize,
}
#[derive(Copy, Clone, Debug)]
struct BuilderNode {
is_leaf: bool,
bounds_range: (usize, usize),
energy: f32,
child_index: usize,
}
impl LightTreeBuilder {
fn new() -> LightTreeBuilder {
LightTreeBuilder {
nodes: Vec::new(),
bounds: Vec::new(),
depth: 0,
}
}
pub fn root_node_index(&self) -> usize {
0
}
// Returns the number of children of this node, assuming a collapse
// number of `ARITY_LOG2`.
pub fn node_child_count(&self, node_index: usize) -> usize {
self.node_child_count_recurse(ARITY_LOG2, node_index)
}
// Returns the index of the nth child, assuming a collapse
// number of `ARITY_LOG2`.
pub fn node_nth_child_index(&self, node_index: usize, child_n: usize) -> usize {
self.node_nth_child_index_recurse(ARITY_LOG2, node_index, child_n)
.0
}
// Returns the number of children of this node, assuming a collapse
// number of `level_collapse`.
pub fn node_child_count_recurse(&self, level_collapse: usize, node_index: usize) -> usize {
if level_collapse > 0 {
if self.nodes[node_index].is_leaf {
1
} else {
let a = self.node_child_count_recurse(level_collapse - 1, node_index + 1);
let b = self.node_child_count_recurse(
level_collapse - 1,
self.nodes[node_index].child_index,
);
a + b
}
} else {
1
}
}
// Returns the index of the nth child, assuming a collapse
// number of `level_collapse`.
pub fn node_nth_child_index_recurse(
&self,
level_collapse: usize,
node_index: usize,
child_n: usize,
) -> (usize, usize) {
if level_collapse > 0 && !self.nodes[node_index].is_leaf {
let (index, rem) =
self.node_nth_child_index_recurse(level_collapse - 1, node_index + 1, child_n);
if rem == 0 {
return (index, 0);
}
return self.node_nth_child_index_recurse(
level_collapse - 1,
self.nodes[node_index].child_index,
rem - 1,
);
} else {
return (node_index, child_n);
}
}
fn recursive_build<'a, T, F>(
&mut self,
offset: usize,
depth: usize,
objects: &mut [T],
info_getter: &F,
) -> (usize, (usize, usize))
where
F: 'a + Fn(&T) -> (&'a [BBox], f32),
{
let me_index = self.nodes.len();
if objects.is_empty() {
return (0, (0, 0));
} else if objects.len() == 1 {
// Leaf node
let bi = self.bounds.len();
let (obj_bounds, energy) = info_getter(&objects[0]);
self.bounds.extend(obj_bounds);
self.nodes.push(BuilderNode {
is_leaf: true,
bounds_range: (bi, self.bounds.len()),
energy: energy,
child_index: offset,
});
if self.depth < depth {
self.depth = depth;
}
return (me_index, (bi, self.bounds.len()));
} else {
// Not a leaf node
self.nodes.push(BuilderNode {
is_leaf: false,
bounds_range: (0, 0),
energy: 0.0,
child_index: 0,
});
// Partition objects.
let (split_index, _) = sah_split(objects, &|obj_ref| info_getter(obj_ref).0);
// Create child nodes
let (_, c1_bounds) =
self.recursive_build(offset, depth + 1, &mut objects[..split_index], info_getter);
let (c2_index, c2_bounds) = self.recursive_build(
offset + split_index,
depth + 1,
&mut objects[split_index..],
info_getter,
);
// Determine bounds
// TODO: do merging without the temporary vec.
let bi = self.bounds.len();
let mut merged = Vec::new();
merge_slices_append(
&self.bounds[c1_bounds.0..c1_bounds.1],
&self.bounds[c2_bounds.0..c2_bounds.1],
&mut merged,
|b1, b2| *b1 | *b2,
);
self.bounds.extend(merged.drain(0..));
// Set node
let energy = self.nodes[me_index + 1].energy + self.nodes[c2_index].energy;
self.nodes[me_index] = BuilderNode {
is_leaf: false,
bounds_range: (bi, self.bounds.len()),
energy: energy,
child_index: c2_index,
};
return (me_index, (bi, self.bounds.len()));
}
}
}