Skip to main content

brep_cam/index/
tree.rs

1//! Build: each node owns a contiguous slice of `order`; a node with more than
2//! `bucket_size` triangles is split on its longest axis at the median
3//! centroid (ties broken by triangle id), so the same mesh always builds the
4//! same tree. Query: descend only into nodes whose projected bounds overlap
5//! the projected query, test triangle bounds at the leaves, and return the
6//! ids ASCENDING, so a tree and the brute-force scan return identical lists.
7
8use super::{IndexStats, ProjectionMode, TriangleIndex};
9use crate::cancel::{Cancelled, Runner};
10use crate::mesh::TriangleSource;
11use crate::model::{Bounds3, Point3};
12use serde::{Deserialize, Serialize};
13
14/// Build parameters. Defaults follow the spec.
15#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17#[serde(default)]
18pub struct TreeOptions {
19    /// Largest leaf.
20    pub bucket_size: usize,
21    /// Deepest node (root is depth 0).
22    pub max_depth: usize,
23    /// A node whose longest extent is below this is not split further.
24    pub min_extent: f64,
25}
26
27impl Default for TreeOptions {
28    fn default() -> Self {
29        Self { bucket_size: 16, max_depth: 32, min_extent: 1e-6 }
30    }
31}
32
33#[derive(Clone, Debug, PartialEq)]
34struct Node {
35    bounds: Bounds3,
36    /// Leaf: start into `order`. Internal: index of the left child.
37    first: u32,
38    /// Leaf: end (exclusive) into `order`. Internal: index of the right child.
39    second: u32,
40    leaf: bool,
41}
42
43/// Static median-split tree over one mesh's triangle bounds.
44#[derive(Clone, Debug, PartialEq)]
45pub struct TreeIndex {
46    nodes: Vec<Node>,
47    order: Vec<u32>,
48    bounds: Vec<Bounds3>,
49    max_depth: usize,
50    leaf_count: usize,
51}
52
53struct Item {
54    id: u32,
55    bounds: Bounds3,
56    centroid: Point3,
57}
58
59impl TreeIndex {
60    pub fn build(mesh: &dyn TriangleSource, options: TreeOptions) -> Self {
61        let mut sink = crate::cancel::NullSink;
62        let mut runner = Runner::detached(&mut sink);
63        Self::build_with_runner(mesh, options, &mut runner).expect("a detached runner never cancels")
64    }
65
66    /// Build, publishing `index-build-start`, `index-build-partition` (at
67    /// chunk boundaries counted in triangles partitioned) and
68    /// `index-build-complete`, and stopping at a partition boundary on cancel.
69    pub fn build_with_runner(
70        mesh: &dyn TriangleSource,
71        options: TreeOptions,
72        runner: &mut Runner<'_>,
73    ) -> Result<Self, Cancelled> {
74        let n = mesh.triangle_count();
75        runner.stage("index-build-start", "building triangle index", 0, n as u64)?;
76        let mut items: Vec<Item> = (0..n)
77            .map(|i| {
78                let t = mesh.triangle(i as u32);
79                Item { id: t.id, bounds: t.bounds, centroid: t.centroid() }
80            })
81            .collect();
82        let bounds: Vec<Bounds3> = items.iter().map(|it| it.bounds).collect();
83        let mut tree = Self { nodes: Vec::new(), order: Vec::with_capacity(n), bounds, max_depth: 0, leaf_count: 0 };
84        if n > 0 {
85            let bucket = options.bucket_size.max(1);
86            tree.build_node(&mut items, 0, bucket, &options, runner)?;
87        }
88        runner.stage("index-build-complete", "triangle index built", n as u64, n as u64)?;
89        Ok(tree)
90    }
91
92    fn build_node(
93        &mut self,
94        items: &mut [Item],
95        depth: usize,
96        bucket: usize,
97        options: &TreeOptions,
98        runner: &mut Runner<'_>,
99    ) -> Result<u32, Cancelled> {
100        let bounds = items.iter().fold(Bounds3::empty(), |b, it| b.union(&it.bounds));
101        self.max_depth = self.max_depth.max(depth);
102        let index = self.nodes.len() as u32;
103        let axis = bounds.longest_axis();
104        let extent = bounds.size().axis(axis);
105        if items.len() <= bucket || depth >= options.max_depth || extent < options.min_extent {
106            let first = self.order.len() as u32;
107            // Leaves keep ascending ids so a leaf scan appends in order.
108            items.sort_by_key(|it| it.id);
109            self.order.extend(items.iter().map(|it| it.id));
110            self.nodes.push(Node { bounds, first, second: self.order.len() as u32, leaf: true });
111            self.leaf_count += 1;
112            runner.work(items.len() as u64, "index-build-partition", self.order.len() as u64, self.bounds.len() as u64)?;
113            return Ok(index);
114        }
115        // Median split on the longest axis; equal centroids order by id so the
116        // partition never depends on input order.
117        items.sort_by(|a, b| {
118            a.centroid
119                .axis(axis)
120                .partial_cmp(&b.centroid.axis(axis))
121                .unwrap_or(std::cmp::Ordering::Equal)
122                .then(a.id.cmp(&b.id))
123        });
124        let mid = items.len() / 2;
125        self.nodes.push(Node { bounds, first: 0, second: 0, leaf: false });
126        let (left_items, right_items) = items.split_at_mut(mid);
127        let left = self.build_node(left_items, depth + 1, bucket, options, runner)?;
128        let right = self.build_node(right_items, depth + 1, bucket, options, runner)?;
129        self.nodes[index as usize].first = left;
130        self.nodes[index as usize].second = right;
131        Ok(index)
132    }
133
134    pub fn node_count(&self) -> usize {
135        self.nodes.len()
136    }
137}
138
139impl TriangleIndex for TreeIndex {
140    fn query_aabb(&self, query: &Bounds3, mode: ProjectionMode, out: &mut Vec<u32>) {
141        if self.nodes.is_empty() {
142            return;
143        }
144        let start = out.len();
145        let mut stack: Vec<u32> = vec![0];
146        while let Some(index) = stack.pop() {
147            let node = &self.nodes[index as usize];
148            if !mode.overlaps(&node.bounds, query) {
149                continue;
150            }
151            if node.leaf {
152                for &id in &self.order[node.first as usize..node.second as usize] {
153                    if mode.overlaps(&self.bounds[id as usize], query) {
154                        out.push(id);
155                    }
156                }
157            } else {
158                // Right first so the left child is visited first (stable order).
159                stack.push(node.second);
160                stack.push(node.first);
161            }
162        }
163        out[start..].sort_unstable();
164    }
165
166    fn stats(&self) -> IndexStats {
167        IndexStats {
168            triangle_count: self.bounds.len(),
169            node_count: Some(self.nodes.len()),
170            leaf_count: Some(self.leaf_count),
171            max_depth: Some(self.max_depth),
172            implementation: "median-split-tree".to_string(),
173        }
174    }
175}
176
177/// Brute force for small meshes, the tree above the threshold.
178#[derive(Clone, Debug, PartialEq)]
179pub enum AnyIndex {
180    Brute(super::BruteForceIndex),
181    Tree(TreeIndex),
182}
183
184impl AnyIndex {
185    /// Meshes with fewer triangles than this scan linearly.
186    pub const TREE_THRESHOLD: usize = 256;
187
188    pub fn build(mesh: &dyn TriangleSource, options: TreeOptions) -> Self {
189        if mesh.triangle_count() < Self::TREE_THRESHOLD {
190            AnyIndex::Brute(super::BruteForceIndex::build(mesh))
191        } else {
192            AnyIndex::Tree(TreeIndex::build(mesh, options))
193        }
194    }
195}
196
197impl TriangleIndex for AnyIndex {
198    fn query_aabb(&self, query: &Bounds3, mode: ProjectionMode, out: &mut Vec<u32>) {
199        match self {
200            AnyIndex::Brute(i) => i.query_aabb(query, mode, out),
201            AnyIndex::Tree(i) => i.query_aabb(query, mode, out),
202        }
203    }
204
205    fn stats(&self) -> IndexStats {
206        match self {
207            AnyIndex::Brute(i) => i.stats(),
208            AnyIndex::Tree(i) => i.stats(),
209        }
210    }
211}