parry3d/shape/voxels/
voxels_neighborhood.rs

1use super::VoxelsChunk;
2use crate::math::{Point, Real, Vector, DIM};
3use crate::shape::{VoxelState, Voxels};
4
5impl Voxels {
6    /// Updates the state of the neighbors of the voxel `key`.
7    ///
8    /// Modifies the state of the neighbors of `key` to account for it being empty or full.
9    /// Returns (but doesn’t modify) the new state of the voxel specified by `key`.
10    #[must_use]
11    pub(super) fn update_neighbors_state(
12        &mut self,
13        key: Point<i32>,
14        center_is_empty: bool,
15    ) -> VoxelState {
16        let mut key_data = 0;
17
18        for k in 0..DIM {
19            let mut left = key;
20            let mut right = key;
21            left[k] -= 1;
22            right[k] += 1;
23
24            // TODO PERF: all the calls to `linear_index` result in a hashmap lookup each time.
25            //            We should instead be smarter and detect if left/right are in the same chunk
26            //            to only look it up once.
27            if let Some(left_id) = self.linear_index(left) {
28                let left_state = &mut self.chunks[left_id.chunk_id].states[left_id.id_in_chunk];
29                if !left_state.is_empty() {
30                    if center_is_empty {
31                        left_state.0 &= !(1 << (k * 2));
32                    } else {
33                        left_state.0 |= 1 << (k * 2);
34                        key_data |= 1 << (k * 2 + 1);
35                    }
36                }
37            }
38
39            if let Some(right_id) = self.linear_index(right) {
40                let right_state = &mut self.chunks[right_id.chunk_id].states[right_id.id_in_chunk];
41                if !right_state.is_empty() {
42                    if center_is_empty {
43                        right_state.0 &= !(1 << (k * 2 + 1));
44                    } else {
45                        right_state.0 |= 1 << (k * 2 + 1);
46                        key_data |= 1 << (k * 2);
47                    }
48                }
49            }
50        }
51
52        if center_is_empty {
53            VoxelState::EMPTY
54        } else {
55            VoxelState::new(key_data)
56        }
57    }
58
59    pub(super) fn recompute_all_voxels_states(&mut self) {
60        for (chunk_key, chunk_header) in self.chunk_headers.iter() {
61            for id_in_chunk in 0..VoxelsChunk::VOXELS_PER_CHUNK {
62                let voxel_key = VoxelsChunk::voxel_key_at_id(*chunk_key, id_in_chunk as u32);
63                self.chunks[chunk_header.id].states[id_in_chunk] =
64                    self.compute_voxel_state(voxel_key);
65            }
66        }
67    }
68
69    fn compute_voxel_state(&self, key: Point<i32>) -> VoxelState {
70        let Some(id) = self.linear_index(key) else {
71            return VoxelState::EMPTY;
72        };
73
74        if self.chunks[id.chunk_id].states[id.id_in_chunk].is_empty() {
75            return VoxelState::EMPTY;
76        }
77
78        self.compute_voxel_neighborhood_bits(key)
79    }
80
81    pub(super) fn compute_voxel_neighborhood_bits(&self, key: Point<i32>) -> VoxelState {
82        let mut occupied_faces = 0;
83
84        for k in 0..DIM {
85            let (mut prev, mut next) = (key, key);
86            prev[k] -= 1;
87            next[k] += 1;
88
89            if let Some(next_id) = self.linear_index(next) {
90                if !self.chunks[next_id.chunk_id].states[next_id.id_in_chunk].is_empty() {
91                    occupied_faces |= 1 << (k * 2);
92                }
93            }
94            if let Some(prev_id) = self.linear_index(prev) {
95                if !self.chunks[prev_id.chunk_id].states[prev_id.id_in_chunk].is_empty() {
96                    occupied_faces |= 1 << (k * 2 + 1);
97                }
98            }
99        }
100
101        VoxelState::new(occupied_faces)
102    }
103
104    /// Merges voxel state (neighborhood) information of a given voxel (and all its neighbors)
105    /// from `self` and `other`, to account for a recent change to the given `voxel` in `self`.
106    ///
107    /// This is designed to be called after `self` was modified with [`Voxels::set_voxel`].
108    ///
109    /// This is the same as [`Voxels::combine_voxel_states`] but localized to a single voxel and its
110    /// neighbors.
111    pub fn propagate_voxel_change(
112        &mut self,
113        other: &mut Self,
114        voxel: Point<i32>,
115        origin_shift: Vector<i32>,
116    ) {
117        let center_is_empty = self
118            .voxel_state(voxel)
119            .map(|vox| vox.is_empty())
120            .unwrap_or(true);
121        let center_state_delta =
122            other.update_neighbors_state(voxel - origin_shift, center_is_empty);
123
124        if let Some(vid) = self.linear_index(voxel) {
125            self.chunks[vid.chunk_id].states[vid.id_in_chunk].0 |= center_state_delta.0;
126        }
127    }
128
129    /// Merges voxel state (neighborhood) information of each voxel from `self` and `other`.
130    ///
131    /// This allows each voxel from one shape to be aware of the presence of neighbors belonging to
132    /// the other so that collision detection is capable of transitioning between the boundaries of
133    /// one shape to the other without hitting an internal edge.
134    ///
135    /// Both voxels shapes are assumed to have the same [`Self::voxel_size`].
136    /// If `other` lives in a coordinate space with a different origin than `self`, then
137    /// `origin_shift` represents the distance (as a multiple of the `voxel_size`) from the origin
138    /// of `self` to the origin of `other`. Therefore, a voxel with coordinates `key` on `other`
139    /// will have coordinates `key + origin_shift` on `self`.
140    pub fn combine_voxel_states(&mut self, other: &mut Self, origin_shift: Vector<i32>) {
141        let one = Vector::repeat(1);
142        let origin_shift_worldspace = origin_shift.cast::<Real>().component_mul(&self.voxel_size);
143
144        for chunk_key in &self.chunk_keys {
145            let mut aabb = VoxelsChunk::aabb(chunk_key, &self.voxel_size);
146            // Enlarge by one-half voxel so we detect cases where we also detect neighbor chunks from `other`.
147            aabb.mins -= self.voxel_size / 2.0;
148            aabb.maxs += self.voxel_size / 2.0;
149            // Shift to the local coordinate system of `other`.
150            let shifted_aabb = aabb.translated(&-origin_shift_worldspace);
151
152            if other.chunk_bvh.intersect_aabb(&shifted_aabb).any(|_| true) {
153                // Check the voxels from this chunk against the other voxels shape.
154
155                // Iterate on the domain intersection. If the voxel exists (and is non-empty) on both shapes, we
156                // simply need to combine their bitmasks. If it doesn’t exist on both shapes, we need to
157                // actually check the neighbors.
158                //
159                // The `domain` is expressed in the grid coordinate space of `self`.
160                let mut domain = VoxelsChunk::keys_bounds(chunk_key);
161                // Enlarge the domain by one voxel so that voxels from `other` but not existing in `self` are updated too.
162                domain[0] -= one;
163                domain[1] += one;
164
165                for i in domain[0].x..domain[1].x {
166                    for j in domain[0].y..domain[1].y {
167                        #[cfg(feature = "dim2")]
168                        let k_range = 0..1;
169                        #[cfg(feature = "dim3")]
170                        let k_range = domain[0].z..domain[1].z;
171                        for _k in k_range {
172                            #[cfg(feature = "dim2")]
173                            let key0 = Point::new(i, j);
174                            #[cfg(feature = "dim3")]
175                            let key0 = Point::new(i, j, _k);
176                            let key1 = key0 - origin_shift;
177                            let vox0 = self
178                                .linear_index(key0)
179                                .map(|id| &mut self.chunks[id.chunk_id].states[id.id_in_chunk])
180                                .filter(|state| !state.is_empty());
181                            let vox1 = other
182                                .linear_index(key1)
183                                .map(|id| &mut other.chunks[id.chunk_id].states[id.id_in_chunk])
184                                .filter(|state| !state.is_empty());
185
186                            match (vox0, vox1) {
187                                (Some(vox0), Some(vox1)) => {
188                                    vox0.0 |= vox1.0;
189                                    vox1.0 |= vox0.0;
190                                }
191                                (Some(vox0), None) => {
192                                    vox0.0 |= other.compute_voxel_neighborhood_bits(key1).0;
193                                }
194                                (None, Some(vox1)) => {
195                                    vox1.0 |= self.compute_voxel_neighborhood_bits(key0).0;
196                                }
197                                (None, None) => { /* Nothing to adjust. */ }
198                            }
199                        }
200                    }
201                }
202            }
203        }
204    }
205}