192 lines
6.1 KiB
Rust
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(),
|
|
}
|
|
}
|
|
}
|