use super::*;
use crate::cli::TracerCli;
use std::time::Instant;
pub type Mesh = KdTree<Triangle>;
#[cfg(test)]
mod kdtree_tests;
pub struct KdTree<T> {
objects: Vec<T>,
boundary: AaBoundingBox,
root: Box<KdNode>,
material: Material,
}
impl<T: Bounded> KdTree<T> {
pub fn new(objects: Vec<T>, material: Material) -> Self {
let start = Instant::now();
if objects.len() > 10_000 {
println!("Creating kd-tree of {} triangles", objects.len());
}
let indices = (0..objects.len()).collect();
let bounds: Vec<AaBoundingBox> = objects
.iter()
.map(|obj| obj.bounding_box()).collect();
let boundary = bounds
.iter()
.fold(AaBoundingBox::default(), |b1, b2| b1.merge(b2));
let threads = argh::from_env::<TracerCli>().threads.unwrap_or(0);
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.unwrap();
let root = pool.install(|| KdNode::construct(&bounds, &boundary, indices));
if objects.len() > 10_000 {
println!("Created kd-tree in {:#?}", start.elapsed());
}
Self {
root,
objects,
boundary,
material,
}
}
pub fn to_unit_size(self) -> Box<Instance<Self>> {
let AaBoundingBox { ax_min, ax_max } = self.bounding_box();
let bb_dim = ax_max - ax_min;
let s = 1.0 / bb_dim.max_element();
self.scale(s, s, s)
}
fn hit_subtree(
&self,
node: &KdNode,
r: &Ray,
t_min: Float,
t_max: Float,
aabb: &AaBoundingBox,
) -> Option<Hit> {
let (axis, point, mut node_first, mut node_second) = match node {
KdNode::Split(axis, point, left, right) => (*axis, *point, left, right),
KdNode::Leaf(indices) => {
let mut tt = t_max;
let mut h = None;
for idx in indices {
h = self.objects[*idx].hit(r, t_min, tt).or(h);
tt = h.as_ref().map_or(tt, |hit| hit.t);
}
return h.map(|mut h| {
h.material = &self.material;
h
});
}
};
let t_split = (point - r.o(axis)) / r.d(axis);
let (mut aabb_first, mut aabb_second) = aabb.split(axis, point);
let left_first = r.o(axis) < point || (r.o(axis) == point && r.d(axis) <= 0.0);
if !left_first {
std::mem::swap(&mut aabb_first, &mut aabb_second);
std::mem::swap(&mut node_first, &mut node_second);
}
let (t_start, t_end) = aabb.intersect(r);
let (t_start, t_end) = (t_start.max(t_min), t_end.min(t_max));
if t_split > t_end || t_split <= 0.0 {
self.hit_subtree(node_first, r, t_min, t_end, &aabb_first)
} else if t_split < t_start {
self.hit_subtree(node_second, r, t_min, t_end, &aabb_second)
} else {
match self.hit_subtree(node_first, r, t_start, t_end, &aabb_first) {
None => self.hit_subtree(node_second, r, t_min, t_end, &aabb_second),
Some(h1) => {
if h1.t < t_split {
Some(h1)
} else {
self.hit_subtree(node_second, r, t_min, h1.t, &aabb_second).or(Some(h1))
}
}
}
}
}
}
impl<T: Bounded> Bounded for KdTree<T> {
fn bounding_box(&self) -> AaBoundingBox {
self.boundary
}
}
impl<T: Bounded> Object for KdTree<T> {
fn hit(&self, r: &Ray, t_min: Float, t_max: Float) -> Option<Hit> {
let (t_start, t_end) = self.boundary.intersect(r);
let (t_start, t_end) = (t_start.max(t_min), t_end.min(t_max));
if t_start > t_end {
None
} else {
self.hit_subtree(&self.root, r, t_min, t_end, &self.boundary)
}
}
}
impl<T: Sampleable + Bounded> Sampleable for KdTree<T> {
fn area(&self) -> Float {
self.objects.iter().fold(0.0, |sum, obj| sum + obj.area())
}
fn sample_on(&self, rand_sq: Vec2) -> Hit {
let n = rand_utils::rand_float() * self.objects.len() as Float;
let mut ho = self.objects[n.floor() as usize].sample_on(rand_sq);
ho.material = &self.material;
ho
}
}
const COST_TRAVERSE: Float = 15.0;
const COST_INTERSECT: Float = 20.0;
const EMPTY_BONUS: Float = 0.2;
pub enum KdNode {
Split(Axis, Float, Box<KdNode>, Box<KdNode>),
Leaf(Vec<usize>),
}
impl KdNode {
fn cost(
boundary: &AaBoundingBox,
axis: Axis,
point: Float,
num_left: usize,
num_right: usize,
) -> Float {
if !boundary.cuts(axis, point) {
crate::INF
} else {
let (left, right) = boundary.split(axis, point);
let cost = COST_TRAVERSE + COST_INTERSECT *
(num_left as Float * left.area() / boundary.area()
+ num_right as Float * right.area() / boundary.area());
if num_left == 0 || num_right == 0 {
(1.0 - EMPTY_BONUS) * cost
} else {
cost
}
}
}
fn find_best_split(aabbs: &Vec<&AaBoundingBox>, boundary: &AaBoundingBox) -> (Axis, Float, Float) {
let mut best_cost = crate::INF;
let mut best_point = crate::INF;
let mut best_axis = Axis::X;
for axis in [Axis::X, Axis::Y, Axis::Z] {
let mut mins: Vec<Float> = Vec::with_capacity(aabbs.len());
let mut maxs: Vec<Float> = Vec::with_capacity(aabbs.len());
aabbs.iter().for_each(|aabb| {
mins.push(aabb.min(axis));
maxs.push(aabb.max(axis));
});
mins.sort_by(|a: &Float, b: &Float| a.partial_cmp(b).unwrap());
maxs.sort_by(|a: &Float, b: &Float| a.partial_cmp(b).unwrap());
let mut num_left = 0;
let mut num_right = aabbs.len();
let mut min_idx = 0;
let mut max_idx = 0;
mins.push(crate::INF);
maxs.push(crate::INF);
while mins[min_idx] < crate::INF || maxs[max_idx] < crate::INF {
let is_min = mins[min_idx] <= maxs[max_idx];
let point = mins[min_idx].min(maxs[max_idx]);
if !is_min { max_idx += 1; num_right -= 1; }
let cost = Self::cost(boundary, axis, point, num_left, num_right);
if cost < best_cost {
best_cost = cost;
best_axis = axis;
best_point = point;
}
if is_min { min_idx += 1; num_left += 1; }
}
}
(best_axis, best_point, best_cost)
}
fn partition(
aabbs: &[&AaBoundingBox],
indices: Vec<usize>,
axis: Axis,
point: Float,
) -> (Vec<usize>, Vec<usize>) {
let mut left: Vec<usize> = Vec::with_capacity(aabbs.len());
let mut right: Vec<usize> = Vec::with_capacity(aabbs.len());
aabbs.iter().zip(indices).for_each(|(aabb, idx)| {
if aabb.min(axis) < point {
left.push(idx);
}
if aabb.max(axis) > point {
right.push(idx);
}
});
(left, right)
}
pub fn construct(
bounds: &[AaBoundingBox],
boundary: &AaBoundingBox,
indices: Vec<usize>,
) -> Box<Self> {
let aabbs: Vec<&AaBoundingBox> = indices.iter()
.map(|idx| &bounds[*idx]).collect();
let (axis, point, cost) = Self::find_best_split(&aabbs, boundary);
if cost > COST_INTERSECT * indices.len() as Float {
Box::new(Self::Leaf(indices))
} else {
let (left_idx, right_idx) = Self::partition(&aabbs, indices, axis, point);
let (left_bound, right_bound) = boundary.split(axis, point);
let (left, right) = rayon::join(
|| Self::construct(bounds, &left_bound, left_idx),
|| Self::construct(bounds, &right_bound, right_idx)
);
Box::new(Self::Split(
axis,
point,
left,
right,
))
}
}
}