parry2d/shape/voxels/
voxels_neighborhood.rs

1use super::VoxelsChunk;
2use crate::math::{Point, 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
143        // Intersect the domains + 1 cell.
144        let [my_domain_mins, my_domain_maxs] = self.domain();
145        let [other_domain_mins, other_domain_maxs] = other.domain();
146        let d0 = [my_domain_mins - one, my_domain_maxs + one * 2];
147        let d1 = [
148            other_domain_mins - one + origin_shift,
149            other_domain_maxs + one * 2 + origin_shift,
150        ];
151
152        let d01 = [d0[0].sup(&d1[0]), d0[1].inf(&d1[1])];
153        // Iterate on the domain intersection. If the voxel exists (and is non-empty) on both shapes, we
154        // simply need to combine their bitmasks. If it doesn’t exist on both shapes, we need to
155        // actually check the neighbors.
156        //
157        // The `domain` is expressed in the grid coordinate space of `self`.
158        for i in d01[0].x..d01[1].x {
159            for j in d01[0].y..d01[1].y {
160                #[cfg(feature = "dim2")]
161                let k_range = 0..1;
162                #[cfg(feature = "dim3")]
163                let k_range = d01[0].z..d01[1].z;
164                for _k in k_range {
165                    #[cfg(feature = "dim2")]
166                    let key0 = Point::new(i, j);
167                    #[cfg(feature = "dim3")]
168                    let key0 = Point::new(i, j, _k);
169                    let key1 = key0 - origin_shift;
170                    let vox0 = self
171                        .linear_index(key0)
172                        .map(|id| &mut self.chunks[id.chunk_id].states[id.id_in_chunk])
173                        .filter(|state| !state.is_empty());
174                    let vox1 = other
175                        .linear_index(key1)
176                        .map(|id| &mut other.chunks[id.chunk_id].states[id.id_in_chunk])
177                        .filter(|state| !state.is_empty());
178
179                    match (vox0, vox1) {
180                        (Some(vox0), Some(vox1)) => {
181                            vox0.0 |= vox1.0;
182                            vox1.0 |= vox0.0;
183                        }
184                        (Some(vox0), None) => {
185                            vox0.0 |= other.compute_voxel_neighborhood_bits(key1).0;
186                        }
187                        (None, Some(vox1)) => {
188                            vox1.0 |= self.compute_voxel_neighborhood_bits(key0).0;
189                        }
190                        (None, None) => { /* Nothing to adjust. */ }
191                    }
192                }
193            }
194        }
195    }
196}