voxelis/core/batch.rs
1//! Module `core::batch`
2//!
3//! This module provides a buffer for batching set, clear, and fill operations on an octree node interner.
4//! It is designed to optimize voxel modifications by accumulating changes before applying them to the octree.
5//!
6//! # Examples
7//!
8//! ```
9//! use voxelis::{Batch, MaxDepth, VoxInterner, spatial::VoxOpsWrite};
10//! use glam::IVec3;
11//!
12//! // Create interner for 8-bit voxels
13//! let mut interner = VoxInterner::<u8>::with_memory_budget(1024);
14//!
15//! let mut batch = Batch::<u8>::new(MaxDepth::new(4));
16//! // Fill the octree with a uniform voxel value
17//! batch.fill(&mut interner, 2);
18//! // Set a voxel at position (1, 2, 3)
19//! batch.set(&mut interner, IVec3::new(1, 2, 3), 1);
20//! // Clear a voxel at position (4, 5, 6)
21//! batch.set(&mut interner, IVec3::new(4, 5, 6), 0);
22//! ```
23
24use glam::IVec3;
25
26use crate::{
27 MaxDepth, VoxInterner, VoxelTrait, interner::MAX_CHILDREN, spatial::VoxOpsWrite,
28 utils::common::encode_child_index_path,
29};
30
31/// Accumulates per-node voxel modifications, enabling efficient bulk updates for an octree.
32///
33/// # Type parameters
34///
35/// * `T` - The voxel type implementing [`VoxelTrait`].
36#[derive(Debug)]
37pub struct Batch<T: VoxelTrait> {
38 masks: Vec<(u8, u8)>,
39 values: Vec<[T; MAX_CHILDREN]>,
40 to_fill: Option<T>,
41 max_depth: MaxDepth,
42 has_patches: bool,
43}
44
45impl<T: VoxelTrait> Batch<T> {
46 /// Creates a new [`Batch`] for a tree of the given maximum depth.
47 /// Returns a new, empty [`Batch`] ready to record set, clear, or fill operations.
48 ///
49 /// # Arguments
50 ///
51 /// * `max_depth` - Maximum depth (levels) of the target octree.
52 ///
53 /// # Example
54 ///
55 /// ```rust
56 /// use voxelis::{Batch, MaxDepth};
57 ///
58 /// let batch = Batch::<u8>::new(MaxDepth::new(4));
59 /// ```
60 #[must_use]
61 pub fn new(max_depth: MaxDepth) -> Self {
62 let lower_depth = if max_depth.max() > 0 {
63 max_depth.max() - 1
64 } else {
65 0
66 };
67 let size = 1 << (3 * lower_depth);
68
69 Self {
70 masks: vec![(0, 0); size],
71 values: vec![[T::default(); MAX_CHILDREN]; size],
72 to_fill: None,
73 max_depth,
74 has_patches: false,
75 }
76 }
77
78 #[must_use]
79 #[inline(always)]
80 /// Returns the internal vector of (`set_mask`, `clear_mask`) pairs per node.
81 pub fn masks(&self) -> &Vec<(u8, u8)> {
82 &self.masks
83 }
84
85 #[must_use]
86 #[inline(always)]
87 /// Returns the buffered voxel values array for each child of every node.
88 pub fn values(&self) -> &Vec<[T; MAX_CHILDREN]> {
89 &self.values
90 }
91
92 #[must_use]
93 #[inline(always)]
94 /// Returns the uniform fill value if `fill` was invoked; otherwise `None`.
95 pub fn to_fill(&self) -> Option<T> {
96 self.to_fill
97 }
98
99 /// Counts and returns the number of recorded set or clear operations.
100 #[must_use]
101 pub fn size(&self) -> usize {
102 self.masks
103 .iter()
104 .filter(|(set_mask, clear_mask)| *set_mask != 0 || *clear_mask != 0)
105 .count()
106 }
107
108 /// Indicates whether any operations have been recorded in this batch.
109 #[must_use]
110 pub fn has_patches(&self) -> bool {
111 self.has_patches
112 }
113
114 /// Records a voxel set or clear operation at the specified 3D position.
115 /// Returns `true` indicating that the state has changed.
116 ///
117 /// # Arguments
118 ///
119 /// * `position` - 3D coordinates of the voxel to modify.
120 /// * `voxel` - The voxel value to set; `T::default()` clears the voxel.
121 ///
122 /// # Panics
123 ///
124 /// Panics if `position` is out of bounds for the configured `max_depth`.
125 pub fn just_set(&mut self, position: IVec3, voxel: T) -> bool {
126 assert!(position.x >= 0 && position.x < (1 << self.max_depth.max()));
127 assert!(position.y >= 0 && position.y < (1 << self.max_depth.max()));
128 assert!(position.z >= 0 && position.z < (1 << self.max_depth.max()));
129
130 let full_path = encode_child_index_path(&position);
131
132 let path = full_path & !0b111;
133 let path_index = (path >> 3) as usize;
134 let index = (full_path & 0b111) as usize;
135 let bit = 1 << index;
136
137 let (set_mask, clear_mask) = &mut self.masks[path_index];
138
139 if voxel != T::default() {
140 *set_mask |= bit;
141 *clear_mask &= !bit;
142 } else {
143 *set_mask &= !bit;
144 *clear_mask |= bit;
145 }
146
147 self.values[path_index][index] = voxel;
148
149 self.has_patches = true;
150
151 true
152 }
153}
154
155impl<T: VoxelTrait> VoxOpsWrite<T> for Batch<T> {
156 /// Records a set or clear operation for the given `position`, delegating to `just_set`.
157 /// Records a voxel set or clear operation at the specified 3D position.
158 /// Returns `true` indicating that the state has changed.
159 ///
160 /// # Arguments
161 ///
162 /// * `position` - 3D coordinates of the voxel to modify.
163 /// * `voxel` - The voxel value to set; `T::default()` clears the voxel.
164 ///
165 /// # Panics
166 ///
167 /// Panics if `position` is out of bounds for the configured `max_depth`.
168 fn set(&mut self, _interner: &mut VoxInterner<T>, position: IVec3, voxel: T) -> bool {
169 assert!(position.x >= 0 && position.x < (1 << self.max_depth.max()));
170 assert!(position.y >= 0 && position.y < (1 << self.max_depth.max()));
171 assert!(position.z >= 0 && position.z < (1 << self.max_depth.max()));
172
173 let full_path = encode_child_index_path(&position);
174
175 let path = full_path & !0b111;
176 let path_index = (path >> 3) as usize;
177 let index = (full_path & 0b111) as usize;
178 let bit = 1 << index;
179
180 let (set_mask, clear_mask) = &mut self.masks[path_index];
181
182 if voxel != T::default() {
183 *set_mask |= bit;
184 *clear_mask &= !bit;
185 } else {
186 *set_mask &= !bit;
187 *clear_mask |= bit;
188 }
189
190 self.values[path_index][index] = voxel;
191
192 self.has_patches = true;
193
194 true
195 }
196
197 /// Clears existing operations and sets a uniform fill value for the batch.
198 fn fill(&mut self, interner: &mut VoxInterner<T>, value: T) {
199 self.clear(interner);
200 self.to_fill = Some(value);
201 }
202
203 /// Resets all recorded operations, clearing masks, values, and fill state.
204 fn clear(&mut self, _interner: &mut VoxInterner<T>) {
205 self.masks.fill((0, 0));
206 self.values.fill([T::default(); MAX_CHILDREN]);
207 self.to_fill = None;
208 self.has_patches = false;
209 }
210}