use std::collections::HashMap;
use crate::aabb::*;
use crate::candidate::{Candidates, Side};
use crate::config::BuilderConfig;
use crate::plane::{Dimension, Plane};
#[derive(Clone, Debug)]
pub enum KDTreeNode {
Leaf {
shapes: Vec<usize>,
},
Node {
l_child: usize,
l_space: AABB,
r_child: usize,
r_space: AABB,
},
}
impl KDTreeNode {
fn move_indices(&mut self, offset: usize) {
match self {
KDTreeNode::Leaf { .. } => {}
KDTreeNode::Node {
l_child, r_child, ..
} => {
*l_child += offset;
*r_child += offset;
}
}
}
}
pub fn build_tree(
config: &BuilderConfig,
space: &AABB,
candidates: Candidates,
nb_shapes: usize,
) -> (usize, Vec<KDTreeNode>) {
let (cost, best_index, n_l, n_r) = partition(config, nb_shapes, space, &candidates);
if cost > config.cost_intersection() * nb_shapes as f32 {
let shapes = candidates
.iter()
.filter(|e| e.is_left() && e.dimension() == Dimension::X)
.map(|e| e.shape)
.collect();
return (1, vec![KDTreeNode::Leaf { shapes }]);
}
let (l_space, r_space) = split_space(space, &candidates[best_index].plane);
let (left_candidates, right_candidates) = classify(candidates, best_index, nb_shapes);
let (left, right) = rayon::join(
|| build_tree(config, &l_space, left_candidates, n_l),
|| build_tree(config, &r_space, right_candidates, n_r),
);
let (depth_left, mut tree_left) = left;
let (depth_right, mut tree_right) = right;
let mut tree = vec![];
let l_child_index = 1;
let r_child_index = tree_left.len() + 1;
tree.push(KDTreeNode::Node {
l_child: l_child_index,
l_space,
r_child: r_child_index,
r_space,
});
tree_left
.iter_mut()
.for_each(|node| node.move_indices(l_child_index));
tree.extend(tree_left);
tree_right
.iter_mut()
.for_each(|node| node.move_indices(r_child_index));
tree.extend(tree_right);
(1 + depth_left.max(depth_right), tree)
}
fn partition(
config: &BuilderConfig,
n: usize,
space: &AABB,
candidates: &Candidates,
) -> (f32, usize, usize, usize) {
let mut best_cost = f32::INFINITY;
let mut best_candidate_index = 0;
let mut n_l = Dimension::get_map(0);
let mut n_r = Dimension::get_map(n);
let mut best_n_l = 0;
let mut best_n_r = n;
for (i, candidate) in candidates.iter().enumerate() {
let dim = candidate.dimension();
if candidate.is_right() {
n_r[dim] -= 1;
}
let cost = cost(config, &candidate.plane, space, n_l[dim], n_r[dim]);
if cost < best_cost {
best_cost = cost;
best_candidate_index = i;
best_n_l = n_l[dim];
best_n_r = n_r[dim];
}
if candidate.is_left() {
n_l[dim] += 1;
}
}
(best_cost, best_candidate_index, best_n_l, best_n_r)
}
fn split_space(space: &AABB, splitting_plane: &Plane) -> (AABB, AABB) {
let mut left = space.clone();
let mut right = space.clone();
let pos = splitting_plane.pos;
match splitting_plane.dimension {
Dimension::X => {
right.min.x = pos.clamp(space.min.x, space.max.x);
left.max.x = pos.clamp(space.min.x, space.max.x);
}
Dimension::Y => {
right.min.y = pos.clamp(space.min.y, space.max.y);
left.max.y = pos.clamp(space.min.y, space.max.y);
}
Dimension::Z => {
right.min.z = pos.clamp(space.min.z, space.max.z);
left.max.z = pos.clamp(space.min.z, space.max.z);
}
}
(left, right)
}
fn classify(
candidates: Candidates,
best_index: usize,
nb_shapes: usize,
) -> (Candidates, Candidates) {
let mut sides = HashMap::with_capacity(nb_shapes);
classify_items(&candidates, best_index, &mut sides);
splicing_candidates(candidates, &sides)
}
fn classify_items(candidates: &Candidates, best_index: usize, sides: &mut HashMap<usize, Side>) {
let best_dimension = candidates[best_index].dimension();
(0..(best_index + 1)).for_each(|i| {
if candidates[i].dimension() == best_dimension {
if candidates[i].is_right() {
sides.insert(candidates[i].shape, Side::Left);
} else {
sides.insert(candidates[i].shape, Side::Both);
}
}
});
(best_index..candidates.len()).for_each(|i| {
if candidates[i].dimension() == best_dimension && candidates[i].is_left() {
sides.insert(candidates[i].shape, Side::Right);
}
});
}
fn splicing_candidates(
mut candidates: Candidates,
sides: &HashMap<usize, Side>,
) -> (Candidates, Candidates) {
let mut left_candidates = Candidates::with_capacity(candidates.len() / 2);
let mut right_candidates = Candidates::with_capacity(candidates.len() / 2);
for e in candidates.drain(..) {
match sides[&e.shape] {
Side::Left => left_candidates.push(e),
Side::Right => right_candidates.push(e),
Side::Both => {
right_candidates.push(e.clone());
left_candidates.push(e);
}
}
}
(left_candidates, right_candidates)
}
fn cost(config: &BuilderConfig, plane: &Plane, space: &AABB, n_left: usize, n_right: usize) -> f32 {
if !plane.is_cutting(space) {
return f32::INFINITY;
}
let surface_space = space.surface();
let (space_left, space_right) = split_space(space, plane);
let surface_left = space_left.surface();
let surface_right = space_right.surface();
let cost = config.cost_traversal()
+ config.cost_intersection()
* (n_left as f32 * surface_left / surface_space
+ n_right as f32 * surface_right / surface_space);
if n_left == 0 || n_right == 0 {
cost * (1. - config.empty_cut_bonus())
} else {
cost
}
}