Skip to content

Commit 94b9638

Browse files
authored
feat: take sparsity into account in Voxels::combine_voxel_states (#392)
This makes it orders of magnitude faster for very large sparse voxels with significant domains overlaps.
1 parent dac0c16 commit 94b9638

File tree

1 file changed

+58
-49
lines changed

1 file changed

+58
-49
lines changed

src/shape/voxels/voxels_neighborhood.rs

Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::VoxelsChunk;
2-
use crate::math::{Point, Vector, DIM};
2+
use crate::math::{Point, Real, Vector, DIM};
33
use crate::shape::{VoxelState, Voxels};
44

55
impl Voxels {
@@ -139,55 +139,64 @@ impl Voxels {
139139
/// will have coordinates `key + origin_shift` on `self`.
140140
pub fn combine_voxel_states(&mut self, other: &mut Self, origin_shift: Vector<i32>) {
141141
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;
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+
}
189199
}
190-
(None, None) => { /* Nothing to adjust. */ }
191200
}
192201
}
193202
}

0 commit comments

Comments
 (0)