use nalgebra::Point3;
use crate::{Classification, Cuttable, Polygon};
use super::node::{faces_same_direction, BspNode};
use super::selector::PlaneSelector;
use super::visitor::BspVisitor;
#[derive(Debug, Clone, Default)]
pub struct BspTree {
root: Option<BspNode>,
}
impl BspTree {
pub fn new() -> Self {
Self { root: None }
}
pub fn build<S: PlaneSelector>(polygons: Vec<Polygon>, selector: &S) -> Self {
Self {
root: build_node(polygons, selector),
}
}
pub fn from_polygons(polygons: Vec<Polygon>) -> Self {
use super::selector::FirstPolygon;
Self::build(polygons, &FirstPolygon)
}
#[inline]
pub fn is_empty(&self) -> bool {
self.root.is_none()
}
#[inline]
pub fn root(&self) -> Option<&BspNode> {
self.root.as_ref()
}
#[inline]
pub fn root_mut(&mut self) -> Option<&mut BspNode> {
self.root.as_mut()
}
pub fn polygon_count(&self) -> usize {
self.root.as_ref().map_or(0, |n| n.polygon_count())
}
pub fn depth(&self) -> usize {
self.root.as_ref().map_or(0, |n| n.depth())
}
pub fn traverse_front_to_back<V: BspVisitor>(&self, eye: Point3<f32>, visitor: &mut V) {
if let Some(ref root) = self.root {
traverse_front_to_back_node(root, eye, visitor);
}
}
pub fn traverse_back_to_front<V: BspVisitor>(&self, eye: Point3<f32>, visitor: &mut V) {
if let Some(ref root) = self.root {
traverse_back_to_front_node(root, eye, visitor);
}
}
pub fn collect_polygons(&self) -> Vec<Polygon> {
let mut result = Vec::with_capacity(self.polygon_count());
collect_polygons_recursive(self.root.as_ref(), &mut result);
result
}
}
fn build_node<S: PlaneSelector>(mut polygons: Vec<Polygon>, selector: &S) -> Option<BspNode> {
if polygons.is_empty() {
return None;
}
let splitter_idx = polygons
.iter()
.position(|p| Some(p) == selector.select(&polygons))?;
let splitter = polygons.swap_remove(splitter_idx);
let plane = splitter.plane();
let mut coplanar_front = Vec::new();
let mut coplanar_back = Vec::new();
let mut front_list = Vec::new();
let mut back_list = Vec::new();
if faces_same_direction(&splitter, &plane) {
coplanar_front.push(splitter);
} else {
coplanar_back.push(splitter);
}
for polygon in polygons {
match polygon.classify(&plane) {
Classification::Front => {
front_list.push(polygon);
}
Classification::Back => {
back_list.push(polygon);
}
Classification::Coplanar => {
if faces_same_direction(&polygon, &plane) {
coplanar_front.push(polygon);
} else {
coplanar_back.push(polygon);
}
}
Classification::Spanning => {
let (front_part, back_part) = polygon.cut(&plane);
if let Some(f) = front_part {
front_list.push(f);
}
if let Some(b) = back_part {
back_list.push(b);
}
}
}
}
let mut node = BspNode::with_coplanar(plane, coplanar_front, coplanar_back);
node.set_front(build_node(front_list, selector));
node.set_back(build_node(back_list, selector));
Some(node)
}
fn traverse_front_to_back_node<V: BspVisitor>(node: &BspNode, eye: Point3<f32>, visitor: &mut V) {
let side = node.plane().classify_point(eye);
let coplanar: Vec<Polygon> = node.all_coplanar().cloned().collect();
match side {
crate::PlaneSide::Front | crate::PlaneSide::OnPlane => {
if let Some(front) = node.front() {
traverse_front_to_back_node(front, eye, visitor);
}
if !coplanar.is_empty() {
visitor.visit(&coplanar);
}
if let Some(back) = node.back() {
traverse_front_to_back_node(back, eye, visitor);
}
}
crate::PlaneSide::Back => {
if let Some(back) = node.back() {
traverse_front_to_back_node(back, eye, visitor);
}
if !coplanar.is_empty() {
visitor.visit(&coplanar);
}
if let Some(front) = node.front() {
traverse_front_to_back_node(front, eye, visitor);
}
}
}
}
fn traverse_back_to_front_node<V: BspVisitor>(node: &BspNode, eye: Point3<f32>, visitor: &mut V) {
let side = node.plane().classify_point(eye);
let coplanar: Vec<Polygon> = node.all_coplanar().cloned().collect();
match side {
crate::PlaneSide::Front | crate::PlaneSide::OnPlane => {
if let Some(back) = node.back() {
traverse_back_to_front_node(back, eye, visitor);
}
if !coplanar.is_empty() {
visitor.visit(&coplanar);
}
if let Some(front) = node.front() {
traverse_back_to_front_node(front, eye, visitor);
}
}
crate::PlaneSide::Back => {
if let Some(front) = node.front() {
traverse_back_to_front_node(front, eye, visitor);
}
if !coplanar.is_empty() {
visitor.visit(&coplanar);
}
if let Some(back) = node.back() {
traverse_back_to_front_node(back, eye, visitor);
}
}
}
}
fn collect_polygons_recursive(node: Option<&BspNode>, result: &mut Vec<Polygon>) {
if let Some(n) = node {
result.extend(n.all_coplanar().cloned());
collect_polygons_recursive(n.front(), result);
collect_polygons_recursive(n.back(), result);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bsp::visitor::CollectingVisitor;
use nalgebra::Point3;
fn make_triangle(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> Polygon {
Polygon::new(vec![
Point3::new(a[0], a[1], a[2]),
Point3::new(b[0], b[1], b[2]),
Point3::new(c[0], c[1], c[2]),
])
}
#[test]
fn empty_tree() {
let tree = BspTree::new();
assert!(tree.is_empty());
assert_eq!(tree.polygon_count(), 0);
assert_eq!(tree.depth(), 0);
}
#[test]
fn build_empty() {
let tree = BspTree::from_polygons(vec![]);
assert!(tree.is_empty());
}
#[test]
fn build_single_polygon() {
let poly = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let tree = BspTree::from_polygons(vec![poly]);
assert!(!tree.is_empty());
assert_eq!(tree.polygon_count(), 1);
assert_eq!(tree.depth(), 1);
}
#[test]
fn build_two_parallel_polygons() {
let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let poly2 = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
let tree = BspTree::from_polygons(vec![poly1, poly2]);
assert_eq!(tree.polygon_count(), 2);
assert!(tree.depth() >= 1);
}
#[test]
fn build_coplanar_same_facing() {
let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let poly2 = make_triangle([1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 1.0, 0.0]);
let tree = BspTree::from_polygons(vec![poly1, poly2]);
assert_eq!(tree.polygon_count(), 2);
assert_eq!(tree.depth(), 1);
let root = tree.root().unwrap();
assert_eq!(root.coplanar_count(), 2);
}
#[test]
fn build_spanning_polygon_gets_split() {
let splitter = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]);
let spanning = make_triangle([-0.5, -1.0, 0.5], [0.5, 1.0, 0.5], [0.5, -1.0, 0.5]);
let tree = BspTree::from_polygons(vec![splitter, spanning]);
assert_eq!(tree.polygon_count(), 3);
}
#[test]
fn traverse_front_to_back_single() {
let poly = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let tree = BspTree::from_polygons(vec![poly.clone()]);
let mut visitor = CollectingVisitor::new();
tree.traverse_front_to_back(Point3::new(0.0, 0.0, 10.0), &mut visitor);
assert_eq!(visitor.polygons().len(), 1);
}
#[test]
fn traverse_front_to_back_ordering() {
let poly_near = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
let poly_far = make_triangle([0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.0, 1.0, -1.0]);
let tree = BspTree::from_polygons(vec![poly_far.clone(), poly_near.clone()]);
let mut visitor = CollectingVisitor::new();
tree.traverse_front_to_back(Point3::new(0.5, 0.5, 10.0), &mut visitor);
let collected = visitor.into_polygons();
assert_eq!(collected.len(), 2);
let first_z = collected[0].centroid().z;
let second_z = collected[1].centroid().z;
assert!(
first_z > second_z,
"Expected front-to-back order: first_z={} should be > second_z={}",
first_z,
second_z
);
}
#[test]
fn traverse_back_to_front_ordering() {
let poly_near = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
let poly_far = make_triangle([0.0, 0.0, -1.0], [1.0, 0.0, -1.0], [0.0, 1.0, -1.0]);
let tree = BspTree::from_polygons(vec![poly_far.clone(), poly_near.clone()]);
let mut visitor = CollectingVisitor::new();
tree.traverse_back_to_front(Point3::new(0.5, 0.5, 10.0), &mut visitor);
let collected = visitor.into_polygons();
assert_eq!(collected.len(), 2);
let first_z = collected[0].centroid().z;
let second_z = collected[1].centroid().z;
assert!(
first_z < second_z,
"Expected back-to-front order: first_z={} should be < second_z={}",
first_z,
second_z
);
}
#[test]
fn collect_polygons() {
let poly1 = make_triangle([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]);
let poly2 = make_triangle([0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [0.0, 1.0, 1.0]);
let poly3 = make_triangle([0.0, 0.0, 2.0], [1.0, 0.0, 2.0], [0.0, 1.0, 2.0]);
let tree = BspTree::from_polygons(vec![poly1, poly2, poly3]);
let collected = tree.collect_polygons();
assert_eq!(collected.len(), 3);
}
}