1use alloc::sync::Arc;
2use core::hash::Hash as _;
3use core::ops;
4
5#[cfg(doc)]
7use crate::block::{AIR, Atom, Primitive};
8
9use crate::block::{
10 BlockAttributes, BlockCollision, EvaluatedBlock,
11 Resolution::{self, R1},
12};
13use crate::math::{Cube, GridAab, OpacityCategory, Rgb, Rgba, Vol};
14
15#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
20#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
21#[non_exhaustive]
22pub struct Evoxel {
23 pub color: Rgba,
30
31 pub emission: Rgb,
35
36 pub selectable: bool,
39
40 pub collision: BlockCollision,
42}
43
44impl Evoxel {
45 pub const AIR: Self = Self {
50 color: Rgba::TRANSPARENT,
51 emission: Rgb::ZERO,
52 selectable: false,
53 collision: BlockCollision::None,
54 };
55
56 pub fn from_block(block: &EvaluatedBlock) -> Self {
60 Self {
61 color: block.color(),
62 emission: block.light_emission(),
63 selectable: block.attributes().selectable,
64 collision: block.uniform_collision().unwrap_or(BlockCollision::Hard),
67 }
68 }
69
70 pub const fn from_color(color: Rgba) -> Self {
73 Self {
74 color,
75 emission: Rgb::ZERO,
76 selectable: BlockAttributes::DEFAULT_REF.selectable,
79 collision: BlockCollision::DEFAULT_FOR_FROM_COLOR,
80 }
81 }
82
83 #[inline]
91 pub fn opacity_category(&self) -> OpacityCategory {
92 let &Self {
93 color,
94 emission,
95 selectable: _,
96 collision: _,
97 } = self;
98 let mut category = color.opacity_category();
99 if emission != Rgb::ZERO && category == OpacityCategory::Invisible {
100 category = OpacityCategory::Partial;
101 }
102 category
103 }
104}
105
106#[derive(Clone, Debug, Eq, Hash, PartialEq)]
117#[non_exhaustive]
118pub struct Evoxels(EvoxelsInner);
119
120#[derive(Clone, Debug, Eq, Hash, PartialEq)]
124enum EvoxelsInner {
125 One(Evoxel),
127 Many(Resolution, Vol<Arc<[Evoxel]>>),
130}
131
132impl Evoxels {
133 #[inline]
135 pub const fn from_one(voxel: Evoxel) -> Self {
136 Evoxels(EvoxelsInner::One(voxel))
137 }
138
139 pub fn from_many(resolution: Resolution, voxels: Vol<Arc<[Evoxel]>>) -> Self {
145 let bounds = voxels.bounds();
146 assert!(
147 GridAab::for_block(resolution).contains_box(bounds),
148 "Evoxels data bounds {bounds:?} exceeds specified resolution {resolution}"
149 );
150
151 Evoxels(EvoxelsInner::Many(resolution, voxels))
154 }
155
156 #[inline]
159 pub fn resolution(&self) -> Resolution {
160 match self.0 {
161 EvoxelsInner::One(_) => R1,
162 EvoxelsInner::Many(resolution, _) => resolution,
163 }
164 }
165
166 pub fn count(&self) -> usize {
168 match self.0 {
169 EvoxelsInner::One(_) => 1,
170 EvoxelsInner::Many(_, ref voxels) => voxels.volume(),
171 }
172 }
173
174 #[inline]
176 pub fn single_voxel(&self) -> Option<Evoxel> {
177 match self.0 {
178 EvoxelsInner::One(v) => Some(v),
179 EvoxelsInner::Many(R1, ref voxels) => {
180 Some(voxels.get([0, 0, 0]).copied().unwrap_or(Evoxel::AIR))
181 }
182 EvoxelsInner::Many(_, _) => None,
183 }
184 }
185
186 pub fn as_vol_ref(&self) -> Vol<&[Evoxel]> {
188 match self.0 {
189 EvoxelsInner::One(ref voxel) => Vol::from_element_ref(voxel),
190 EvoxelsInner::Many(_, ref voxels) => voxels.as_ref(),
191 }
192 }
193
194 pub fn as_vol_mut(&mut self) -> Vol<&mut [Evoxel]> {
196 match self.0 {
197 EvoxelsInner::One(ref mut voxel) => Vol::from_element_mut(voxel),
198 EvoxelsInner::Many(_, ref mut voxels) => {
199 Vol::from_elements(voxels.bounds(), voxels.make_linear_mut()).unwrap()
200 }
201 }
202 }
203
204 #[inline]
212 pub fn get(&self, position: Cube) -> Option<Evoxel> {
213 match (&self.0, position) {
214 (&EvoxelsInner::One(voxel), Cube::ORIGIN) => Some(voxel),
215 (EvoxelsInner::One(_), _) => None,
216 (EvoxelsInner::Many(_, voxels), position) => voxels.get(position).copied(),
217 }
218 }
219
220 #[inline]
222 pub fn bounds(&self) -> GridAab {
223 match self.0 {
224 EvoxelsInner::One(_) => GridAab::ORIGIN_CUBE,
225 EvoxelsInner::Many(_, ref voxels) => voxels.bounds(),
226 }
227 }
228
229 pub(crate) fn cheap_or_ptr_eq(&self, other: &Self) -> bool {
234 match (self.single_voxel(), other.single_voxel()) {
237 (Some(v1), Some(v2)) => v1 == v2,
238 (None, Some(_)) | (Some(_), None) => false, (None, None) => {
240 self.resolution() == other.resolution()
241 && self.bounds() == other.bounds()
242 && core::ptr::eq(
243 self.as_vol_ref().as_linear(),
244 other.as_vol_ref().as_linear(),
245 )
246 }
247 }
248 }
249
250 pub(crate) fn cheap_or_ptr_hash<H: core::hash::Hasher>(&self, state: &mut H) {
252 match self.single_voxel() {
253 Some(v) => {
254 0u8.hash(state);
255 v.hash(state);
256 }
257 None => {
258 1u8.hash(state);
259 self.resolution().hash(state);
260 self.as_vol_ref().as_linear().as_ptr().hash(state);
261 }
262 }
263 }
264
265 #[cfg(debug_assertions)]
266 pub(crate) fn consistency_check(&self) {
267 let allowed_bounds = GridAab::for_block(self.resolution());
268 let actual_bounds = self.bounds();
269 assert!(
270 allowed_bounds.contains_box(actual_bounds),
271 "Evoxels contains out of bounds voxels {actual_bounds:?} \
272 within allowed {allowed_bounds:?}"
273 );
274 }
275}
276
277impl ops::Index<Cube> for Evoxels {
278 type Output = Evoxel;
279
280 #[inline]
281 #[track_caller]
282 fn index(&self, position: Cube) -> &Self::Output {
283 match (&self.0, position) {
284 (EvoxelsInner::One(voxel), Cube::ORIGIN) => voxel,
285 (EvoxelsInner::One(_), _) => panic!("out of bounds of Evoxels::One"),
286 (EvoxelsInner::Many(_, voxels), position) => &voxels[position],
287 }
288 }
289}
290
291#[cfg(feature = "arbitrary")]
292#[mutants::skip]
293impl<'a> arbitrary::Arbitrary<'a> for Evoxels {
294 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
295 use crate::math::GridCoordinate;
296 use euclid::point3;
297
298 let resolution = Resolution::arbitrary(u)?;
299 Ok(if resolution == R1 {
300 Evoxels::from_one(u.arbitrary()?)
301 } else {
302 let limit = GridCoordinate::from(resolution) - 1;
303 let lower_bounds = point3(
304 u.int_in_range(0..=limit)?,
305 u.int_in_range(0..=limit)?,
306 u.int_in_range(0..=limit)?,
307 );
308 let upper_bounds = point3(
309 u.int_in_range(lower_bounds.x..=limit)?,
310 u.int_in_range(lower_bounds.y..=limit)?,
311 u.int_in_range(lower_bounds.z..=limit)?,
312 );
313 let bounds = GridAab::from_lower_upper(lower_bounds, upper_bounds).to_vol().unwrap();
314 let contents = u
315 .arbitrary_iter()?
316 .take(bounds.volume())
317 .collect::<Result<Arc<[Evoxel]>, _>>()?;
318 Evoxels::from_many(
319 resolution,
320 bounds
321 .with_elements(contents)
322 .map_err(|_wrong_length| arbitrary::Error::NotEnoughData)?,
323 )
324 })
325 }
326
327 fn size_hint(depth: usize) -> (usize, Option<usize>) {
328 use crate::math::GridCoordinate;
329
330 let max_data_size =
331 GridAab::for_block(Resolution::MAX).volume().unwrap() * Evoxel::size_hint(0).1.unwrap();
332
333 arbitrary::size_hint::and(
334 Resolution::size_hint(depth),
335 arbitrary::size_hint::or(
336 Evoxel::size_hint(depth), arbitrary::size_hint::and(
338 (3, Some(size_of::<GridCoordinate>() * 6)), (0, Some(max_data_size)), ),
341 ),
342 )
343 }
344}
345
346impl crate::universe::VisitHandles for Evoxels {
347 fn visit_handles(&self, visitor: &mut dyn crate::universe::HandleVisitor) {
348 let _ = visitor;
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use crate::block::Resolution::*;
358 use core::hash::{BuildHasher as _, Hasher};
359
360 #[test]
361 fn cheap_eq_hash() {
362 let vox1 = Evoxel::AIR;
364 let vox2 = Evoxel {
365 emission: Rgb::new(1.0, 2.0, 3.0),
366 ..Evoxel::AIR
367 };
368
369 let vol_of_vox1 = Vol::from_element(vox1);
370 let samples = [
371 (0, Evoxels::from_one(vox1)),
374 (0, Evoxels::from_one(vox1)),
375 (1, Evoxels::from_one(vox2)),
376 (2, Evoxels::from_many(R2, vol_of_vox1.clone())),
378 (2, Evoxels::from_many(R2, vol_of_vox1.clone())),
379 (3, Evoxels::from_many(R2, Vol::from_element(vox2))),
381 (4, Evoxels::from_many(R2, Vol::from_element(vox2))),
382 (5, Evoxels::from_many(R4, vol_of_vox1.clone())),
384 (6, Evoxels::from_many(R2, vol_of_vox1.translate([1, 0, 0]))),
386 ];
387
388 let hasher = std::hash::BuildHasherDefault::<std::hash::DefaultHasher>::default();
389 let cheap_hash_one = |value: &Evoxels| {
390 let mut state = hasher.build_hasher();
391 value.cheap_or_ptr_hash(&mut state);
392 state.finish()
393 };
394
395 let mut equals = 0;
396 let mut collisions = 0;
397 let mut checks = 0;
398 for all1 @ (_index1, (class1, value1)) in samples.iter().enumerate() {
399 for all2 @ (_index2, (class2, value2)) in samples.iter().enumerate() {
400 eprintln!("checking {value1:?} == {value2:?}");
401
402 let equal12 = value1.cheap_or_ptr_eq(value2);
403 let hash1 = cheap_hash_one(value1);
404 let hash2 = cheap_hash_one(value2);
405
406 assert_eq!(
407 equal12,
408 class1 == class2,
409 "equality not as expected for:\nleft: {all1:?}\nright: {all2:?}"
410 );
411
412 if equal12 {
413 assert_eq!(hash1, hash2);
414 equals += 1;
415 } else if hash1 == hash2 {
416 collisions += 1;
417 }
418 checks += 1;
419 }
420 }
421
422 dbg!(equals, collisions, checks);
423
424 assert!(collisions < checks / 10);
427 }
428
429 #[cfg(feature = "arbitrary")]
430 #[test]
431 fn arbitrary_size_hints() {
432 use arbitrary::Arbitrary as _;
433
434 Evoxel::size_hint(0).1.unwrap();
436 Evoxels::size_hint(0).1.unwrap();
437 }
438}