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}