Skip to main content

cumulus3d_align/
kdtree.rs

1//! 3차원 KD-트리(자체 구현). 정밀 점군 근접 마스킹용. 질의는 rayon 병렬.
2
3use rayon::prelude::*;
4
5const LEAF_SIZE: usize = 16;
6
7#[derive(Clone, Copy, Debug)]
8enum Node {
9    Leaf { start: u32, end: u32 },
10    Split { dim: u8, val: f64, left: u32, right: u32 },
11}
12
13/// 정적 KD-트리. 점은 트리 순서로 재배열해 저장하고 원래 인덱스를 함께 보관한다.
14#[derive(Clone, Debug, Default)]
15pub struct KdTree {
16    pts: Vec<[f64; 3]>,
17    orig: Vec<u32>,
18    nodes: Vec<Node>,
19}
20
21#[inline]
22fn d2(a: &[f64; 3], b: &[f64; 3]) -> f64 {
23    let (x, y, z) = (a[0] - b[0], a[1] - b[1], a[2] - b[2]);
24    x * x + y * y + z * z
25}
26
27impl KdTree {
28    /// f64 점으로 생성.
29    pub fn new(points: impl IntoIterator<Item = [f64; 3]>) -> Self {
30        let pts: Vec<[f64; 3]> = points.into_iter().collect();
31        assert!(pts.len() < u32::MAX as usize, "점이 너무 많음");
32        let mut idx: Vec<u32> = (0..pts.len() as u32).collect();
33        let mut nodes = Vec::new();
34        if !pts.is_empty() {
35            build(&pts, &mut idx, 0, &mut nodes);
36        }
37        let reordered = idx.iter().map(|&i| pts[i as usize]).collect();
38        Self { pts: reordered, orig: idx, nodes }
39    }
40    /// f32 점(PLY)으로 생성.
41    pub fn from_f32(points: &[[f32; 3]]) -> Self {
42        Self::new(points.iter().map(|p| [p[0] as f64, p[1] as f64, p[2] as f64]))
43    }
44    /// 점 개수.
45    pub fn len(&self) -> usize {
46        self.pts.len()
47    }
48    /// 점이 없으면 참.
49    pub fn is_empty(&self) -> bool {
50        self.pts.is_empty()
51    }
52
53    /// 최근접 점 (원래 인덱스, 거리²). 빈 트리면 None.
54    pub fn nearest(&self, q: &[f64; 3]) -> Option<(usize, f64)> {
55        if self.nodes.is_empty() {
56            return None;
57        }
58        let mut best = (usize::MAX, f64::INFINITY);
59        self.nearest_rec(0, q, &mut best);
60        Some((self.orig[best.0] as usize, best.1))
61    }
62
63    fn nearest_rec(&self, node: u32, q: &[f64; 3], best: &mut (usize, f64)) {
64        match self.nodes[node as usize] {
65            Node::Leaf { start, end } => {
66                for i in start as usize..end as usize {
67                    let d = d2(&self.pts[i], q);
68                    if d < best.1 {
69                        *best = (i, d);
70                    }
71                }
72            }
73            Node::Split { dim, val, left, right } => {
74                let diff = q[dim as usize] - val;
75                let (near, far) = if diff < 0.0 { (left, right) } else { (right, left) };
76                self.nearest_rec(near, q, best);
77                if diff * diff < best.1 {
78                    self.nearest_rec(far, q, best);
79                }
80            }
81        }
82    }
83
84    /// 반경 r 안(거리 ≤ r)에 점이 하나라도 있는가(조기 종료).
85    pub fn any_within(&self, q: &[f64; 3], r: f64) -> bool {
86        !self.nodes.is_empty() && self.any_rec(0, q, r * r)
87    }
88
89    fn any_rec(&self, node: u32, q: &[f64; 3], r2: f64) -> bool {
90        match self.nodes[node as usize] {
91            Node::Leaf { start, end } => self.pts[start as usize..end as usize].iter().any(|p| d2(p, q) <= r2),
92            Node::Split { dim, val, left, right } => {
93                let diff = q[dim as usize] - val;
94                let (near, far) = if diff < 0.0 { (left, right) } else { (right, left) };
95                self.any_rec(near, q, r2) || (diff * diff <= r2 && self.any_rec(far, q, r2))
96            }
97        }
98    }
99
100    /// 반경 r 안의 모든 점(원래 인덱스, 순서 미정).
101    pub fn within_radius(&self, q: &[f64; 3], r: f64) -> Vec<usize> {
102        let mut out = Vec::new();
103        if !self.nodes.is_empty() {
104            self.radius_rec(0, q, r * r, &mut out);
105        }
106        out
107    }
108
109    fn radius_rec(&self, node: u32, q: &[f64; 3], r2: f64, out: &mut Vec<usize>) {
110        match self.nodes[node as usize] {
111            Node::Leaf { start, end } => {
112                for i in start as usize..end as usize {
113                    if d2(&self.pts[i], q) <= r2 {
114                        out.push(self.orig[i] as usize);
115                    }
116                }
117            }
118            Node::Split { dim, val, left, right } => {
119                let diff = q[dim as usize] - val;
120                if diff < 0.0 || diff * diff <= r2 {
121                    self.radius_rec(left, q, r2, out);
122                }
123                if diff >= 0.0 || diff * diff <= r2 {
124                    self.radius_rec(right, q, r2, out);
125                }
126            }
127        }
128    }
129
130    /// 병렬 최근접 질의.
131    pub fn nearest_many(&self, queries: &[[f64; 3]]) -> Vec<Option<(usize, f64)>> {
132        queries.par_iter().map(|q| self.nearest(q)).collect()
133    }
134
135    /// 병렬 반경 판정(f32 질의). `결과[i]` = 질의 i 의 r 안에 점이 있음.
136    pub fn any_within_many_f32(&self, queries: &[[f32; 3]], r: f64) -> Vec<bool> {
137        queries.par_iter().map(|q| self.any_within(&[q[0] as f64, q[1] as f64, q[2] as f64], r)).collect()
138    }
139}
140
141/// idx 구간에 대해 노드를 만들고 그 노드 번호를 반환. `offset` = idx 구간의 전체 내 시작 위치.
142fn build(pts: &[[f64; 3]], idx: &mut [u32], offset: usize, nodes: &mut Vec<Node>) -> u32 {
143    let me = nodes.len() as u32;
144    let n = idx.len();
145    if n <= LEAF_SIZE {
146        nodes.push(Node::Leaf { start: offset as u32, end: (offset + n) as u32 });
147        return me;
148    }
149    // 범위가 가장 큰 축으로 분할.
150    let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
151    for &i in idx.iter() {
152        let p = &pts[i as usize];
153        for d in 0..3 {
154            lo[d] = lo[d].min(p[d]);
155            hi[d] = hi[d].max(p[d]);
156        }
157    }
158    let dim = (0..3).max_by(|&a, &b| (hi[a] - lo[a]).total_cmp(&(hi[b] - lo[b]))).unwrap_or(0);
159    if hi[dim] - lo[dim] <= 0.0 {
160        // 모든 점이 같은 위치: 더 나눌 수 없음.
161        nodes.push(Node::Leaf { start: offset as u32, end: (offset + n) as u32 });
162        return me;
163    }
164    let mid = n / 2;
165    idx.select_nth_unstable_by(mid, |a, b| pts[*a as usize][dim].total_cmp(&pts[*b as usize][dim]));
166    let val = pts[idx[mid] as usize][dim];
167    // 왼쪽 ≤ val ≤ 오른쪽. 가지치기는 |q_d − val| 하한만 쓰므로 같은 값이 양쪽에 있어도 정확하다.
168    nodes.push(Node::Leaf { start: 0, end: 0 }); // 자리 확보
169    let (l, r) = idx.split_at_mut(mid);
170    let left = build(pts, l, offset, nodes);
171    let right = build(pts, r, offset + mid, nodes);
172    nodes[me as usize] = Node::Split { dim: dim as u8, val, left, right };
173    me
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use rand::RngExt;
180
181    #[test]
182    fn nearest_matches_brute_force() {
183        let mut rng = cumulus3d_core::ransac::make_rng(Some(21));
184        let pts: Vec<[f64; 3]> = (0..10_000)
185            .map(|_| [rng.random_range(-50.0..50.0), rng.random_range(-50.0..50.0), rng.random_range(-5.0..5.0)])
186            .collect();
187        let tree = KdTree::new(pts.iter().copied());
188        let qs: Vec<[f64; 3]> = (0..2000)
189            .map(|_| [rng.random_range(-60.0..60.0), rng.random_range(-60.0..60.0), rng.random_range(-10.0..10.0)])
190            .collect();
191        let got = tree.nearest_many(&qs);
192        for (q, g) in qs.iter().zip(&got) {
193            let (bi, bd) = pts.iter().enumerate().map(|(i, p)| (i, d2(p, q))).min_by(|a, b| a.1.total_cmp(&b.1)).unwrap();
194            let (gi, gd) = g.unwrap();
195            assert_eq!(gd, bd);
196            assert!(gi == bi || d2(&pts[gi], q) == bd);
197            // 반경 질의도 무차별 대입과 일치.
198            let r = 3.0;
199            let mut within = tree.within_radius(q, r);
200            within.sort_unstable();
201            let brute: Vec<usize> = pts.iter().enumerate().filter(|(_, p)| d2(p, q) <= r * r).map(|(i, _)| i).collect();
202            assert_eq!(within, brute);
203            assert_eq!(tree.any_within(q, r), !brute.is_empty());
204        }
205    }
206
207    #[test]
208    fn duplicates_and_empty() {
209        let tree = KdTree::new(std::iter::repeat_n([1.0, 2.0, 3.0], 100));
210        assert_eq!(tree.nearest(&[0.0, 0.0, 0.0]).unwrap().1, 14.0);
211        assert!(KdTree::new(std::iter::empty()).nearest(&[0.0; 3]).is_none());
212        assert!(!KdTree::default().any_within(&[0.0; 3], 1.0));
213    }
214}