parry2d/shape/voxels/
voxels_edition.rs

1use crate::bounding_volume::Aabb;
2use crate::math::{Point, Real, Vector, DIM};
3use crate::shape::voxels::voxels_chunk::{VoxelsChunk, VoxelsChunkHeader};
4use crate::shape::{VoxelState, Voxels};
5use crate::utils::hashmap::Entry;
6use alloc::vec;
7
8impl Voxels {
9    /// Sets the size of each voxel along each local coordinate axis.
10    ///
11    /// Since the internal spatial acceleration structure needs to be updated, this
12    /// operation runs in `O(n)` time, where `n` is the number of voxels.
13    pub fn set_voxel_size(&mut self, new_size: Vector<Real>) {
14        let scale = new_size.component_div(&self.voxel_size);
15        self.chunk_bvh.scale(&scale);
16        self.voxel_size = new_size;
17    }
18
19    /// Inserts or remove a voxel from this shape.
20    ///
21    /// Return the previous `VoxelState` of this voxel.
22    pub fn set_voxel(&mut self, key: Point<i32>, is_filled: bool) -> VoxelState {
23        let (chunk_key, id_in_chunk) = Self::chunk_key_and_id_in_chunk(key);
24        let header_entry = self.chunk_headers.entry(chunk_key);
25
26        if !is_filled && matches!(header_entry, Entry::Vacant(_)) {
27            // The voxel is already empty (it doesn’t exist at all).
28            // Nothing more to do.
29            return VoxelState::EMPTY;
30        }
31
32        let chunk_header = header_entry.or_insert_with(|| {
33            let id = self.free_chunks.pop().unwrap_or_else(|| {
34                self.chunks.push(VoxelsChunk::default());
35                self.chunk_keys.push(chunk_key);
36                self.chunks.len() - 1
37            });
38
39            self.chunk_keys[id] = chunk_key;
40            self.chunk_bvh
41                .insert(VoxelsChunk::aabb(&chunk_key, &self.voxel_size), id as u32);
42            VoxelsChunkHeader { id, len: 0 }
43        });
44        let chunk_id = chunk_header.id;
45
46        let prev = self.chunks[chunk_id].states[id_in_chunk];
47        let new_is_empty = !is_filled;
48
49        if prev.is_empty() ^ new_is_empty {
50            let can_remove_chunk = if new_is_empty {
51                chunk_header.len -= 1;
52                chunk_header.len == 0
53            } else {
54                chunk_header.len += 1;
55                false
56            };
57
58            self.chunks[chunk_id].states[id_in_chunk] =
59                self.update_neighbors_state(key, new_is_empty);
60
61            if can_remove_chunk {
62                self.chunk_bvh.remove(chunk_id as u32);
63
64                #[cfg(feature = "enhanced-determinism")]
65                let _ = self.chunk_headers.swap_remove(&chunk_key);
66                #[cfg(not(feature = "enhanced-determinism"))]
67                let _ = self.chunk_headers.remove(&chunk_key);
68
69                self.free_chunks.push(chunk_id);
70                self.chunk_keys[chunk_id] = VoxelsChunk::INVALID_CHUNK_KEY;
71            }
72        }
73
74        prev
75    }
76
77    /// Crops in-place the voxel shape with a rectangular domain.
78    ///
79    /// This removes every voxels out of the `[domain_mins, domain_maxs]` bounds.
80    pub fn crop(&mut self, domain_mins: Point<i32>, domain_maxs: Point<i32>) {
81        // TODO PERF: this could be done more efficiently.
82        if let Some(new_shape) = self.cropped(domain_mins, domain_maxs) {
83            *self = new_shape;
84        }
85    }
86
87    /// Returns a cropped version of this voxel shape with a rectangular domain.
88    ///
89    /// This removes every voxels out of the `[domain_mins, domain_maxs]` bounds.
90    pub fn cropped(&self, domain_mins: Point<i32>, domain_maxs: Point<i32>) -> Option<Self> {
91        // TODO PERF: can be optimized significantly.
92        let mut in_box = vec![];
93        for vox in self.voxels() {
94            if !vox.state.is_empty()
95                && grid_aabb_contains_point(&domain_mins, &domain_maxs, &vox.grid_coords)
96            {
97                in_box.push(vox.grid_coords);
98            }
99        }
100
101        if !in_box.is_empty() {
102            Some(Voxels::new(self.voxel_size, &in_box))
103        } else {
104            None
105        }
106    }
107
108    /// Splits this voxels shape into two subshapes.
109    ///
110    /// The first subshape contains all the voxels which centers are inside the `aabb`.
111    /// The second subshape contains all the remaining voxels.
112    pub fn split_with_box(&self, aabb: &Aabb) -> (Option<Self>, Option<Self>) {
113        // TODO PERF: can be optimized significantly.
114        let mut in_box = vec![];
115        let mut rest = vec![];
116        for vox in self.voxels() {
117            if !vox.state.is_empty() {
118                if aabb.contains_local_point(&vox.center) {
119                    in_box.push(vox.grid_coords);
120                } else {
121                    rest.push(vox.grid_coords);
122                }
123            }
124        }
125
126        let in_box = if !in_box.is_empty() {
127            Some(Voxels::new(self.voxel_size, &in_box))
128        } else {
129            None
130        };
131
132        let rest = if !rest.is_empty() {
133            Some(Voxels::new(self.voxel_size, &rest))
134        } else {
135            None
136        };
137
138        (in_box, rest)
139    }
140}
141
142fn grid_aabb_contains_point(mins: &Point<i32>, maxs: &Point<i32>, point: &Point<i32>) -> bool {
143    for i in 0..DIM {
144        if point[i] < mins[i] || point[i] > maxs[i] {
145            return false;
146        }
147    }
148
149    true
150}