parry2d/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}