bvh2d/bvh2d/
bvh2d_impl.rs

1use crate::aabb::{Bounded, AABB};
2use crate::bvh2d::iter::BVH2dTraverseIterator;
3use crate::utils::{concatenate_vectors, joint_aabb_of_shapes, Bucket};
4use crate::Point2;
5use crate::EPSILON;
6use std::f32;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[derive(Copy, Clone, Debug)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[allow(clippy::upper_case_acronyms)]
14pub(crate) enum BVH2dNode {
15    Leaf {
16        shape_index: usize,
17    },
18
19    Node {
20        child_l_index: usize,
21        child_l_aabb: AABB,
22        child_r_index: usize,
23        child_r_aabb: AABB,
24    },
25}
26
27impl BVH2dNode {
28    const DUMMY: BVH2dNode = { BVH2dNode::Leaf { shape_index: 0 } };
29
30    fn build<T: Bounded>(shapes: &[T], indices: &[usize], nodes: &mut Vec<BVH2dNode>) -> usize {
31        // Helper function to accumulate the AABB joint and the centroids AABB
32        #[inline]
33        fn grow_convex_hull(convex_hull: (AABB, AABB), shape_aabb: &AABB) -> (AABB, AABB) {
34            let center = &shape_aabb.center();
35            let convex_hull_aabbs = &convex_hull.0;
36            let convex_hull_centroids = &convex_hull.1;
37            (
38                convex_hull_aabbs.join(shape_aabb),
39                convex_hull_centroids.grow(center),
40            )
41        }
42
43        // If there is only one element left, don't split anymore
44        if indices.len() == 1 {
45            let shape_index = indices[0];
46            let node_index = nodes.len();
47            nodes.push(BVH2dNode::Leaf { shape_index });
48            // Let the shape know the index of the node that represents it.
49            return node_index;
50        }
51
52        let mut convex_hull = (AABB::EMPTY, AABB::EMPTY);
53        for index in indices {
54            convex_hull = grow_convex_hull(convex_hull, &shapes[*index].aabb());
55        }
56        let (aabb_bounds, centroid_bounds) = convex_hull;
57
58        // From here on we handle the recursive case. This dummy is required, because the children
59        // must know their parent, and it's easier to update one parent node than the child nodes.
60        let node_index = nodes.len();
61        nodes.push(BVH2dNode::DUMMY);
62
63        // Find the axis along which the shapes are spread the most.
64        let split_axis = centroid_bounds.largest_axis();
65        let split_axis_size = centroid_bounds.max[split_axis] - centroid_bounds.min[split_axis];
66
67        // The following `if` partitions `indices` for recursively calling `BVH2d::build`.
68        let (child_l_index, child_l_aabb, child_r_index, child_r_aabb) = if split_axis_size
69            < EPSILON
70        {
71            // In this branch the shapes lie too close together so that splitting them in a
72            // sensible way is not possible. Instead we just split the list of shapes in half.
73            let (child_l_indices, child_r_indices) = indices.split_at(indices.len() / 2);
74            let child_l_aabb = joint_aabb_of_shapes(child_l_indices, shapes);
75            let child_r_aabb = joint_aabb_of_shapes(child_r_indices, shapes);
76
77            // Proceed recursively.
78            let child_l_index = BVH2dNode::build(shapes, child_l_indices, nodes);
79            let child_r_index = BVH2dNode::build(shapes, child_r_indices, nodes);
80            (child_l_index, child_l_aabb, child_r_index, child_r_aabb)
81        } else {
82            const NUM_BUCKETS: usize = 4;
83            const BUCKET_START_CAPACITY: usize = 20;
84            let mut buckets = [Bucket::EMPTY; NUM_BUCKETS];
85            let mut bucket_assignments: [Vec<usize>; NUM_BUCKETS] = [
86                Vec::with_capacity(BUCKET_START_CAPACITY),
87                Vec::with_capacity(BUCKET_START_CAPACITY),
88                Vec::with_capacity(BUCKET_START_CAPACITY),
89                Vec::with_capacity(BUCKET_START_CAPACITY),
90            ];
91
92            // In this branch the `split_axis_size` is large enough to perform meaningful splits.
93            // We start by assigning the shapes to `Bucket`s.
94            for idx in indices {
95                let shape = &shapes[*idx];
96                let shape_aabb = shape.aabb();
97                let shape_center = shape_aabb.center();
98
99                // Get the relative position of the shape centroid `[0.0..1.0]`.
100                let bucket_num_relative =
101                    (shape_center[split_axis] - centroid_bounds.min[split_axis]) / split_axis_size;
102
103                // Convert that to the actual `Bucket` number.
104                let bucket_num = (bucket_num_relative * (NUM_BUCKETS as f32 - 0.01)) as usize;
105
106                // Extend the selected `Bucket` and add the index to the actual bucket.
107                buckets[bucket_num].add_aabb(&shape_aabb);
108                bucket_assignments[bucket_num].push(*idx);
109            }
110
111            // Compute the costs for each configuration and select the best configuration.
112            let mut min_bucket = 0;
113            let mut min_cost = f32::INFINITY;
114            let mut child_l_aabb = AABB::EMPTY;
115            let mut child_r_aabb = AABB::EMPTY;
116            for i in 0..(NUM_BUCKETS - 1) {
117                let (l_buckets, r_buckets) = buckets.split_at(i + 1);
118                let child_l = l_buckets.iter().fold(Bucket::EMPTY, Bucket::join_bucket);
119                let child_r = r_buckets.iter().fold(Bucket::EMPTY, Bucket::join_bucket);
120
121                let cost = (child_l.size as f32 * child_l.aabb.surface_area()
122                    + child_r.size as f32 * child_r.aabb.surface_area())
123                    / aabb_bounds.surface_area();
124                if cost < min_cost {
125                    min_bucket = i;
126                    min_cost = cost;
127                    child_l_aabb = child_l.aabb;
128                    child_r_aabb = child_r.aabb;
129                }
130            }
131
132            // Join together all index buckets.
133            let (l_assignments, r_assignments) = bucket_assignments.split_at_mut(min_bucket + 1);
134            let child_l_indices = concatenate_vectors(l_assignments);
135            let child_r_indices = concatenate_vectors(r_assignments);
136
137            // Proceed recursively.
138            let child_l_index = BVH2dNode::build(shapes, &child_l_indices, nodes);
139            let child_r_index = BVH2dNode::build(shapes, &child_r_indices, nodes);
140            (child_l_index, child_l_aabb, child_r_index, child_r_aabb)
141        };
142
143        // Construct the actual data structure and replace the dummy node.
144        debug_assert!(!child_l_aabb.is_empty());
145        debug_assert!(!child_r_aabb.is_empty());
146        nodes[node_index] = BVH2dNode::Node {
147            child_l_aabb,
148            child_l_index,
149            child_r_aabb,
150            child_r_index,
151        };
152
153        node_index
154    }
155}
156
157#[allow(clippy::upper_case_acronyms)]
158#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
159#[derive(Clone, Debug)]
160pub struct BVH2d {
161    pub(crate) nodes: Vec<BVH2dNode>,
162}
163
164impl BVH2d {
165    pub fn build<Shape: Bounded>(shapes: &[Shape]) -> BVH2d {
166        let indices = (0..shapes.len()).collect::<Vec<usize>>();
167        let expected_node_count = shapes.len() * 2;
168        let mut nodes = Vec::with_capacity(expected_node_count);
169        BVH2dNode::build(shapes, &indices, &mut nodes);
170        BVH2d { nodes }
171    }
172
173    pub fn contains_iterator<'a>(&'a self, point: &'a Point2) -> BVH2dTraverseIterator<'a> {
174        BVH2dTraverseIterator::new(self, point)
175    }
176}