Skip to main content

del_msh_cpu/
kdtree2.rs

1//! methods for 2D Kd-tree
2
3use num_traits::AsPrimitive;
4
5// TODO: insert point in KD-tree for poisson disk sampling
6
7/// construct Kd-tree recursively
8/// * `nodes`
9/// * `idx_node`
10/// * `points`
11/// * `idx_point_begin`
12/// * `idx_point_end`
13/// * `i_depth`
14#[allow(clippy::identity_op)]
15pub fn construct_kdtree<Real>(
16    tree: &mut Vec<usize>,
17    idx_node: usize,
18    points: &mut Vec<([Real; 2], usize)>,
19    idx_point_begin: usize,
20    idx_point_end: usize,
21    i_depth: i32,
22) where
23    Real: num_traits::Float + Copy,
24{
25    if points.is_empty() {
26        tree.clear();
27        return;
28    }
29    if idx_node == 0 {
30        tree.resize(3, usize::MAX);
31    }
32
33    if idx_point_end - idx_point_begin == 1 {
34        // leaf node
35        tree[idx_node * 3 + 0] = points[idx_point_begin].1;
36        return;
37    }
38
39    #[allow(clippy::manual_is_multiple_of)]
40    if i_depth % 2 == 0 {
41        // sort by x-coordinate
42        points[idx_point_begin..idx_point_end].sort_by(|a, b| a.0[0].partial_cmp(&b.0[0]).unwrap());
43    } else {
44        // sort by y-coordinate
45        points[idx_point_begin..idx_point_end].sort_by(|a, b| a.0[1].partial_cmp(&b.0[1]).unwrap());
46    }
47
48    let idx_point_mid = (idx_point_end - idx_point_begin) / 2 + idx_point_begin; // median point
49    tree[idx_node * 3 + 0] = points[idx_point_mid].1;
50
51    if idx_point_begin != idx_point_mid {
52        // there is at least one point smaller than median
53        let idx_node_left = tree.len() / 3;
54        tree.resize(tree.len() + 3, usize::MAX);
55        tree[idx_node * 3 + 1] = idx_node_left;
56        construct_kdtree(
57            tree,
58            idx_node_left,
59            points,
60            idx_point_begin,
61            idx_point_mid,
62            i_depth + 1,
63        );
64    }
65    if idx_point_mid + 1 != idx_point_end {
66        // there is at least one point larger than median
67        let idx_node_right = tree.len() / 3;
68        tree.resize(tree.len() + 3, usize::MAX);
69        tree[idx_node * 3 + 2] = idx_node_right;
70        construct_kdtree(
71            tree,
72            idx_node_right,
73            points,
74            idx_point_mid + 1,
75            idx_point_end,
76            i_depth + 1,
77        );
78    }
79}
80
81#[allow(clippy::identity_op)]
82pub fn find_edges<Real>(
83    edge2xy: &mut Vec<Real>,
84    vtx2xy: &[Real],
85    nodes: &[usize],
86    idx_node: usize,
87    min: [Real; 2],
88    max: [Real; 2],
89    i_depth: i32,
90) where
91    Real: Copy,
92{
93    if idx_node >= nodes.len() {
94        return;
95    }
96    let ivtx = nodes[idx_node * 3 + 0];
97    let pos = &vtx2xy[ivtx * 2..(ivtx + 1) * 2];
98    #[allow(clippy::manual_is_multiple_of)]
99    if i_depth % 2 == 0 {
100        edge2xy.push(pos[0]);
101        edge2xy.push(min[1]);
102        edge2xy.push(pos[0]);
103        edge2xy.push(max[1]);
104        find_edges(
105            edge2xy,
106            vtx2xy,
107            nodes,
108            nodes[idx_node * 3 + 1],
109            min,
110            [pos[0], max[1]],
111            i_depth + 1,
112        );
113        find_edges(
114            edge2xy,
115            vtx2xy,
116            nodes,
117            nodes[idx_node * 3 + 2],
118            [pos[0], min[1]],
119            max,
120            i_depth + 1,
121        );
122    } else {
123        edge2xy.push(min[0]);
124        edge2xy.push(pos[1]);
125        edge2xy.push(max[0]);
126        edge2xy.push(pos[1]);
127        find_edges(
128            edge2xy,
129            vtx2xy,
130            nodes,
131            nodes[idx_node * 3 + 1],
132            min,
133            [max[0], pos[1]],
134            i_depth + 1,
135        );
136        find_edges(
137            edge2xy,
138            vtx2xy,
139            nodes,
140            nodes[idx_node * 3 + 2],
141            [min[0], pos[1]],
142            max,
143            i_depth + 1,
144        );
145    }
146}
147
148pub struct TreeBranch<'a, Real> {
149    pub vtx2xy: &'a [Real],
150    pub nodes: &'a Vec<usize>,
151    pub idx_node: usize,
152    pub min: [Real; 2],
153    pub max: [Real; 2],
154    pub i_depth: usize,
155}
156
157#[allow(clippy::identity_op)]
158pub fn nearest<Real>(pos_near: &mut ([Real; 2], usize), pos_in: [Real; 2], branch: TreeBranch<Real>)
159where
160    Real: num_traits::Float + Copy + 'static,
161    f64: AsPrimitive<Real>,
162{
163    use del_geo_core::vec2::Vec2;
164    if branch.idx_node >= branch.nodes.len() {
165        return;
166    } // this node does not exist
167
168    let cur_dist = pos_near.0.sub(&pos_in).norm();
169    if cur_dist
170        < del_geo_core::aabb2::sdf(
171            &[branch.min[0], branch.min[1], branch.max[0], branch.max[1]],
172            &pos_in,
173        )
174    {
175        return;
176    }
177
178    let ivtx = branch.nodes[branch.idx_node * 3 + 0];
179    let pos = [branch.vtx2xy[ivtx * 2], branch.vtx2xy[ivtx * 2 + 1]];
180    if pos.sub(&pos_in).norm() < cur_dist {
181        *pos_near = (pos, ivtx); // update the nearest position
182    }
183
184    #[allow(clippy::manual_is_multiple_of)]
185    if branch.i_depth % 2 == 0 {
186        // division in x direction
187        if pos_in[0] < pos[0] {
188            nearest(
189                pos_near,
190                pos_in,
191                TreeBranch {
192                    vtx2xy: branch.vtx2xy,
193                    nodes: branch.nodes,
194                    idx_node: branch.nodes[branch.idx_node * 3 + 1],
195                    min: branch.min,
196                    max: [pos[0], branch.max[1]],
197                    i_depth: branch.i_depth + 1,
198                },
199            );
200            nearest(
201                pos_near,
202                pos_in,
203                TreeBranch {
204                    vtx2xy: branch.vtx2xy,
205                    nodes: branch.nodes,
206                    idx_node: branch.nodes[branch.idx_node * 3 + 2],
207                    min: [pos[0], branch.min[1]],
208                    max: branch.max,
209                    i_depth: branch.i_depth + 1,
210                },
211            );
212        } else {
213            nearest(
214                pos_near,
215                pos_in,
216                TreeBranch {
217                    vtx2xy: branch.vtx2xy,
218                    nodes: branch.nodes,
219                    idx_node: branch.nodes[branch.idx_node * 3 + 2],
220                    min: [pos[0], branch.min[1]],
221                    max: branch.max,
222                    i_depth: branch.i_depth + 1,
223                },
224            );
225            nearest(
226                pos_near,
227                pos_in,
228                TreeBranch {
229                    vtx2xy: branch.vtx2xy,
230                    nodes: branch.nodes,
231                    idx_node: branch.nodes[branch.idx_node * 3 + 1],
232                    min: branch.min,
233                    max: [pos[0], branch.max[1]],
234                    i_depth: branch.i_depth + 1,
235                },
236            );
237        }
238    } else {
239        // division in y-direction
240        if pos_in[1] < pos[1] {
241            nearest(
242                pos_near,
243                pos_in,
244                TreeBranch {
245                    vtx2xy: branch.vtx2xy,
246                    nodes: branch.nodes,
247                    idx_node: branch.nodes[branch.idx_node * 3 + 1],
248                    min: branch.min,
249                    max: [branch.max[0], pos[1]],
250                    i_depth: branch.i_depth + 1,
251                },
252            );
253            nearest(
254                pos_near,
255                pos_in,
256                TreeBranch {
257                    vtx2xy: branch.vtx2xy,
258                    nodes: branch.nodes,
259                    idx_node: branch.nodes[branch.idx_node * 3 + 2],
260                    min: [branch.min[0], pos[1]],
261                    max: branch.max,
262                    i_depth: branch.i_depth + 1,
263                },
264            );
265        } else {
266            nearest(
267                pos_near,
268                pos_in,
269                TreeBranch {
270                    vtx2xy: branch.vtx2xy,
271                    nodes: branch.nodes,
272                    idx_node: branch.nodes[branch.idx_node * 3 + 2],
273                    min: [branch.min[0], pos[1]],
274                    max: branch.max,
275                    i_depth: branch.i_depth + 1,
276                },
277            );
278            nearest(
279                pos_near,
280                pos_in,
281                TreeBranch {
282                    vtx2xy: branch.vtx2xy,
283                    nodes: branch.nodes,
284                    idx_node: branch.nodes[branch.idx_node * 3 + 1],
285                    min: branch.min,
286                    max: [branch.max[0], pos[1]],
287                    i_depth: branch.i_depth + 1,
288                },
289            );
290        }
291    }
292}
293
294#[allow(clippy::identity_op)]
295pub fn inside_square<Real>(
296    pos_near: &mut Vec<usize>,
297    pos_in: [Real; 2],
298    rad: Real,
299    branch: TreeBranch<Real>,
300) where
301    Real: num_traits::Float + Copy + 'static,
302    f64: AsPrimitive<Real>,
303{
304    if branch.idx_node >= branch.nodes.len() {
305        return;
306    } // this node does not exist
307
308    if !del_geo_core::aabb2::is_intersect_square(
309        &[branch.min[0], branch.min[1], branch.max[0], branch.max[1]],
310        &pos_in,
311        rad,
312    ) {
313        return;
314    }
315
316    let ivtx = branch.nodes[branch.idx_node * 3 + 0];
317    let pos = [branch.vtx2xy[ivtx * 2 + 0], branch.vtx2xy[ivtx * 2 + 1]];
318    if (pos[0] - pos_in[0]).abs() < rad && (pos[1] - pos_in[1]).abs() < rad {
319        pos_near.push(ivtx); // update the nearest position
320    }
321    #[allow(clippy::manual_is_multiple_of)]
322    if branch.i_depth % 2 == 0 {
323        // division in x direction
324        inside_square(
325            pos_near,
326            pos_in,
327            rad,
328            TreeBranch {
329                vtx2xy: branch.vtx2xy,
330                nodes: branch.nodes,
331                idx_node: branch.nodes[branch.idx_node * 3 + 2],
332                min: [pos[0], branch.min[1]],
333                max: branch.max,
334                i_depth: branch.i_depth + 1,
335            },
336        );
337        inside_square(
338            pos_near,
339            pos_in,
340            rad,
341            TreeBranch {
342                vtx2xy: branch.vtx2xy,
343                nodes: branch.nodes,
344                idx_node: branch.nodes[branch.idx_node * 3 + 1],
345                min: branch.min,
346                max: [pos[0], branch.max[1]],
347                i_depth: branch.i_depth + 1,
348            },
349        );
350    } else {
351        // division in y-direction
352        inside_square(
353            pos_near,
354            pos_in,
355            rad,
356            TreeBranch {
357                vtx2xy: branch.vtx2xy,
358                nodes: branch.nodes,
359                idx_node: branch.nodes[branch.idx_node * 3 + 1],
360                min: branch.min,
361                max: [branch.max[0], pos[1]],
362                i_depth: branch.i_depth + 1,
363            },
364        );
365        inside_square(
366            pos_near,
367            pos_in,
368            rad,
369            TreeBranch {
370                vtx2xy: branch.vtx2xy,
371                nodes: branch.nodes,
372                idx_node: branch.nodes[branch.idx_node * 3 + 2],
373                min: [branch.min[0], pos[1]],
374                max: branch.max,
375                i_depth: branch.i_depth + 1,
376            },
377        );
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use crate::kdtree2::TreeBranch;
384    use num_traits::AsPrimitive;
385
386    fn test_data<Real>(num_xy: usize) -> (Vec<Real>, Vec<usize>)
387    where
388        Real: num_traits::Float + 'static + Copy,
389        f64: AsPrimitive<Real>,
390        rand::distr::StandardUniform: rand::distr::Distribution<Real>,
391    {
392        let xys = {
393            let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed([13_u8; 32]);
394            let rad: Real = 0.4_f64.as_();
395            let half: Real = 0.4_f64.as_();
396            let mut ps = Vec::<Real>::with_capacity(num_xy * 2);
397            for _i in 0..num_xy {
398                use rand::RngExt;
399                let x: Real = (rng.random::<Real>() * 2_f64.as_() - Real::one()) * rad + half;
400                let y: Real = (rng.random::<Real>() * 2_f64.as_() - Real::one()) * rad + half;
401                ps.push(x);
402                ps.push(y);
403            }
404            ps
405        };
406        let tree = {
407            let mut pairs_xy_idx = xys
408                .chunks(2)
409                .enumerate()
410                .map(|(ivtx, xy)| ([xy[0], xy[1]], ivtx))
411                .collect();
412            let mut tree = Vec::<usize>::new();
413            crate::kdtree2::construct_kdtree(&mut tree, 0, &mut pairs_xy_idx, 0, xys.len() / 2, 0);
414            tree
415        };
416        (xys, tree)
417    }
418
419    #[test]
420    fn check_nearest_raw() {
421        use crate::kdtree2::nearest;
422        use del_geo_core::vec2::Vec2;
423        // use std::time;
424        type Real = f64;
425        let (vtx2xy, nodes) = test_data::<Real>(10000);
426        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed([13_u8; 32]);
427        // let time_nearest = time::Instant::now();
428        for _ in 0..10000 {
429            use rand::RngExt;
430            let p0 = [rng.random::<Real>(), rng.random::<Real>()];
431            let mut pos_near = ([Real::MAX, Real::MAX], usize::MAX);
432            nearest(
433                &mut pos_near,
434                p0,
435                TreeBranch {
436                    vtx2xy: &vtx2xy,
437                    nodes: &nodes,
438                    idx_node: 0,
439                    min: [0., 0.],
440                    max: [1., 1.],
441                    i_depth: 0,
442                },
443            );
444        }
445        // dbg!(time_nearest.elapsed());
446        for _ in 0..10000 {
447            use rand::RngExt;
448            let p0 = [rng.random::<Real>(), rng.random::<Real>()];
449            let mut pos_near = ([Real::MAX, Real::MAX], usize::MAX);
450            nearest(
451                &mut pos_near,
452                p0,
453                TreeBranch {
454                    vtx2xy: &vtx2xy,
455                    nodes: &nodes,
456                    idx_node: 0,
457                    min: [0., 0.],
458                    max: [1., 1.],
459                    i_depth: 0,
460                },
461            );
462            let dist_min = pos_near.0.sub(&p0).norm();
463            for xy in vtx2xy.chunks(2) {
464                let xy = arrayref::array_ref![xy, 0, 2];
465                assert!(xy.sub(&p0).norm() >= dist_min);
466            }
467        }
468    }
469
470    #[test]
471    fn check_inside_square_raw() {
472        // use std::time;
473        type Real = f64;
474        let (vtx2xy, nodes) = test_data::<Real>(10000);
475        let mut rng: rand::rngs::StdRng = rand::SeedableRng::from_seed([13_u8; 32]);
476        let rad: Real = 0.03;
477        // let time_inside_square = time::Instant::now();
478        for _ in 0..10000 {
479            use rand::RngExt;
480            let p0 = [rng.random::<Real>(), rng.random::<Real>()];
481            let mut pos_near = Vec::<usize>::new();
482            crate::kdtree2::inside_square(
483                &mut pos_near,
484                p0,
485                rad,
486                TreeBranch {
487                    vtx2xy: &vtx2xy,
488                    nodes: &nodes,
489                    idx_node: 0,
490                    min: [0., 0.],
491                    max: [1., 1.],
492                    i_depth: 0,
493                },
494            );
495        }
496        // dbg!(time_inside_square.elapsed());
497        //
498        for _ in 0..10000 {
499            use rand::RngExt;
500            let p0 = [rng.random::<Real>(), rng.random::<Real>()];
501            let mut idxs0 = Vec::<usize>::new();
502            crate::kdtree2::inside_square(
503                &mut idxs0,
504                p0,
505                rad,
506                TreeBranch {
507                    vtx2xy: &vtx2xy,
508                    nodes: &nodes,
509                    idx_node: 0,
510                    min: [0., 0.],
511                    max: [1., 1.],
512                    i_depth: 0,
513                },
514            );
515            let idxs1: Vec<usize> = vtx2xy
516                .chunks(2)
517                .enumerate()
518                .filter(|(_, xy)| (xy[0] - p0[0]).abs() < rad && (xy[1] - p0[1]).abs() < rad)
519                .map(|v| v.0)
520                .collect();
521            let idxs1 = std::collections::BTreeSet::from_iter(idxs1.iter());
522            let idxs0 = std::collections::BTreeSet::from_iter(idxs0.iter());
523            assert_eq!(idxs1, idxs0);
524        }
525    }
526}