192 lines
6.1 KiB
Rust

use std::fmt;
use log::info;
use crate::aabb::surrounding_box;
use crate::aabb::AABB;
use crate::hitable::Hit;
use crate::hitable::HitRecord;
use crate::ray::Ray;
pub enum KDTree {
Leaf(Box<dyn Hit>),
Branch {
left: Box<KDTree>,
right: Box<KDTree>,
bbox: Option<AABB>,
},
}
// Return the first element from the vector, which should be the only element.
// TODO(wathiede): we really want a .first_into() (.first() doesn't work because it
// returns a reference.)
fn vec_first_into<T>(v: Vec<T>) -> T {
if v.len() != 1 {
panic!(
"vec_first_into called for vector length != 1, length {}",
v.len()
);
}
v.into_iter().next().unwrap()
}
fn vec_split_into<T>(v: Vec<T>, offset: usize) -> (Vec<T>, Vec<T>) {
let mut left_half = Vec::new();
let mut right_half = Vec::new();
v.into_iter().enumerate().for_each(|(i, h)| {
if i < offset {
left_half.push(h);
} else {
right_half.push(h);
}
});
(left_half, right_half)
}
impl KDTree {
pub fn new(l: Vec<Box<dyn Hit>>, t_min: f32, t_max: f32) -> KDTree {
if l.is_empty() {
panic!("Attempt to build k-d tree with no Hit objects");
}
if l.len() == 1 {
return KDTree::Leaf(vec_first_into(l));
}
if l.len() == 2 {
let (left_vec, right_vec) = vec_split_into(l, 1);
let left = vec_first_into(left_vec);
let right = vec_first_into(right_vec);
let bbox = surrounding_box(
&left.bounding_box(t_min, t_max).unwrap(),
&right.bounding_box(t_min, t_max).unwrap(),
);
return KDTree::Branch {
left: Box::new(KDTree::Leaf(left)),
right: Box::new(KDTree::Leaf(right)),
bbox: Some(bbox),
};
}
let bbox = l[0].bounding_box(t_min, t_max).unwrap();
let mut mid_point = bbox.mid_point();
let bbox = l[1..].iter().fold(bbox, |acc, h| {
let new_bbox = &h.bounding_box(t_min, t_max).unwrap();
mid_point = mid_point + new_bbox.mid_point();
surrounding_box(&acc, new_bbox)
});
mid_point = mid_point / l.len() as f32;
let axis = bbox.longest_axis();
let mut left_half = Vec::new();
let mut right_half = Vec::new();
match axis {
0 => l.into_iter().for_each(|h| {
if h.bounding_box(t_min, t_max).unwrap().mid_point().x < mid_point.x {
left_half.push(h);
} else {
right_half.push(h);
}
}),
1 => l.into_iter().for_each(|h| {
if h.bounding_box(t_min, t_max).unwrap().mid_point().y < mid_point.y {
left_half.push(h);
} else {
right_half.push(h);
}
}),
2 => l.into_iter().for_each(|h| {
if h.bounding_box(t_min, t_max).unwrap().mid_point().z < mid_point.z {
left_half.push(h);
} else {
right_half.push(h);
}
}),
_ => panic!("Unreachable"),
};
KDTree::Branch {
left: Box::new(KDTree::new(left_half, t_min, t_max)),
right: Box::new(KDTree::new(right_half, t_min, t_max)),
bbox: Some(bbox),
}
}
fn volume(&self) -> f32 {
let bbox = self.bounding_box(0., 0.).unwrap();
(bbox.min().x - bbox.max().x).abs()
* (bbox.min().y - bbox.max().y).abs()
* (bbox.min().z - bbox.max().z).abs()
}
}
fn print_tree(f: &mut fmt::Formatter, depth: usize, kdt: &KDTree) -> fmt::Result {
let vol = kdt.volume();
write!(f, "{:8.2}{}", vol, " ".repeat(depth * 2))?;
match kdt {
KDTree::Leaf(ref hit) => writeln!(
f,
"Leaf: {}",
hit.bounding_box(0., 0.)
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
)?,
KDTree::Branch { bbox, .. } => writeln!(
f,
"Branch: {}",
// TODO(wathiede): removing this .clone() results in a complaint about moving out
// of a borrow.
bbox.clone()
.map_or("NO BBOX".to_owned(), |bb| bb.to_string())
)?,
}
if let KDTree::Branch { left, right, .. } = kdt {
print_tree(f, depth + 1, left)?;
print_tree(f, depth + 1, right)?;
}
Ok(())
}
impl fmt::Display for KDTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "kd-tree")?;
print_tree(f, 1, self)
}
}
impl Hit for KDTree {
fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
match self.bounding_box(t_min, t_max) {
Some(bbox) => {
if !bbox.hit(r, t_min, t_max) {
return None;
}
}
None => {
info!("KDTree::hit no bbox: {:?}", r);
return None;
}
}
match self {
KDTree::Leaf(ref hit) => hit.hit(r, t_min, t_max),
KDTree::Branch { left, right, bbox } => match bbox {
None => None,
Some(_bbox) => match (left.hit(r, t_min, t_max), right.hit(r, t_min, t_max)) {
(Some(hit_left), Some(hit_right)) => {
if hit_left.t < hit_right.t {
Some(hit_left)
} else {
Some(hit_right)
}
}
(Some(hit_left), None) => Some(hit_left),
(None, Some(hit_right)) => Some(hit_right),
(None, None) => None,
},
},
}
}
fn bounding_box(&self, t_min: f32, t_max: f32) -> Option<AABB> {
match self {
KDTree::Leaf(ref hit) => hit.bounding_box(t_min, t_max),
KDTree::Branch { bbox, .. } => bbox.clone(),
}
}
}