all_is_cubes/block.rs
1//! Definition of blocks, which are the game objects which occupy the grid of a
2//! [`Space`]. See [`Block`] for details.
3//!
4//! The types of most interest in this module are [`Block`], [`Primitive`],
5//! [`BlockAttributes`], and [`Modifier`].
6
7use alloc::borrow::Cow;
8use alloc::boxed::Box;
9use alloc::collections::VecDeque;
10use alloc::sync::Arc;
11use alloc::vec::Vec;
12use core::fmt;
13
14use crate::inv;
15use crate::listen::{self, Listen as _, Listener};
16use crate::math::{
17 GridAab, GridCoordinate, GridPoint, GridRotation, GridVector, Rgb, Rgb01, Rgba, Vol,
18};
19use crate::space::{SetCubeError, Space, SpaceChange};
20use crate::universe::{Handle, HandleVisitor, ReadTicket, VisitHandles};
21
22// -------------------------------------------------------------------------------------------------
23
24mod attributes;
25pub use attributes::*;
26
27mod block_def;
28pub use block_def::*;
29
30pub mod builder;
31#[doc(inline)]
32pub use builder::Builder;
33
34mod eval;
35pub use eval::*;
36
37mod modifier;
38pub use modifier::*;
39
40/// Scale factor between a [`Block`] and its component voxels.
41///
42/// This resolution cubed is the number of voxels making up a block.
43///
44/// Resolutions are always powers of 2. This ensures that the arithmetic is well-behaved
45/// (no division by zero, exact floating-point representation, and the potential of
46/// fixed-point representation),
47/// and that it is always possible to subdivide a block further (up to the limit) without
48/// shifting the existing voxel boundaries.
49///
50/// Note that while quite high resolutions are permitted, this does not mean that it is
51/// practical to routinely use full blocks at that resolution. For example, 64 × 64 × 64
52/// = 262,144 voxels, occupying several megabytes just for color data.
53/// High resolutions are permitted for special purposes that do not necessarily use the
54/// full cube volume:
55///
56/// * *Thin* blocks (e.g. 128 × 128 × 1) can display high resolution text and other 2D
57/// images.
58/// * Multi-block structures can be defined using [`Modifier::Zoom`]; their total size
59/// is limited by the resolution limit.
60pub use all_is_cubes_base::resolution::Resolution;
61
62// Note: can't use a glob re-export due to <https://github.com/rust-lang/rust/issues/149895>
63pub use all_is_cubes_base::resolution::IntoResolutionError;
64
65pub mod text;
66
67#[cfg(test)]
68mod tests;
69
70// -------------------------------------------------------------------------------------------------
71// Block type declarations
72// File organization: This is a series of closely related type definitions together before
73// any of their `impl`s, so the types can be understood in context.
74
75/// A [`Block`] is something that can exist in the grid of a [`Space`]; it occupies one
76/// unit cube of simulated physical space, and has a specified appearance and behavior.
77///
78/// A [`Block`] is made up of a [`Primitive`] and zero or more [`Modifier`]s.
79///
80/// In general, when a block appears multiple times from an in-game perspective, that may
81/// or may not be the the same copy; `Block`s are "by value" and any block [`Eq`] to
82/// another will behave identically and should be treated identically. However, some
83/// blocks are defined by reference to shared mutable data, and [`Block`] containers such
84/// as [`Space`] must follow those changes.
85///
86/// To determine the concrete appearance and behavior of a block, use [`Block::evaluate()`]
87/// or [`Block::evaluate_and_listen()`], which will return an [`EvaluatedBlock`] value.
88/// Additional operations for manipulating the block are available on [`EvaluatedBlock`].
89///
90#[doc = include_str!("save/serde-warning.md")]
91#[derive(Clone)]
92pub struct Block(BlockPtr);
93
94/// Pointer to data of a [`Block`] value.
95///
96/// This is a separate type so that the enum variants are not exposed.
97/// It does not implement Eq and Hash, but Block does through it.
98#[derive(Clone, Debug)]
99enum BlockPtr {
100 Static(&'static Primitive),
101 Owned(Arc<BlockParts>),
102}
103
104#[derive(Clone, Debug, Eq, Hash, PartialEq)]
105struct BlockParts {
106 primitive: Primitive,
107 /// Modifiers are stored in innermost-first order.
108 modifiers: Vec<Modifier>,
109}
110
111/// The possible fundamental representations of a [`Block`]'s shape.
112///
113#[doc = include_str!("save/serde-warning.md")]
114#[derive(Clone, Eq, Hash, PartialEq)]
115#[non_exhaustive]
116pub enum Primitive {
117 /// A block whose definition is stored elsewhere in a
118 /// [`Universe`](crate::universe::Universe).
119 ///
120 /// Note that this is a handle to a [`Block`], not a [`Primitive`]; the referenced
121 /// [`BlockDef`] may have its own [`Modifier`]s, and thus the result of
122 /// [evaluating](Block::evaluate) a primitive with no modifiers is not necessarily
123 /// free of the effects of modifiers.
124 Indirect(Handle<BlockDef>),
125
126 /// A block of totally uniform properties.
127 Atom(Atom),
128
129 /// A block that is composed of smaller blocks, defined by the referenced [`Space`].
130 Recur {
131 /// The space from which voxels are taken.
132 space: Handle<Space>,
133
134 /// Which portion of the space will be used, specified by the most negative
135 /// corner.
136 offset: GridPoint,
137
138 /// The side length of the cubical volume of sub-blocks (voxels) used for this
139 /// block.
140 resolution: Resolution,
141 },
142
143 /// An invisible, unselectable, inert block used as “no block”; the primitive of [`AIR`].
144 ///
145 /// This is essentially a specific [`Primitive::Atom`]. There are a number of
146 /// algorithms which treat this block specially or which return it (e.g. outside the
147 /// bounds of a `Space`), so it exists here to make it an explicit element of the
148 /// data model — so that if it is, say, serialized and loaded in a future version,
149 /// it is still recognized as [`AIR`]. Additionally, it's cheaper to compare this way.
150 Air,
151
152 /// A piece of text rendered as voxels.
153 ///
154 /// To combine the text with other shapes, use [`Modifier::Composite`].
155 Text {
156 /// The text to draw, and the font and text-layout-dependent positioning.
157 text: text::Text,
158
159 /// Translation, in whole cubes, of the region of the text to draw.
160 ///
161 /// For text within a single block, this should be zero.
162 /// For multi-block text, this should be equal to the difference between
163 /// the adjacent blocks' positions.
164 offset: GridVector,
165 },
166
167 /// Arbitrary block data.
168 ///
169 /// This variant is intended for internal use in special cases where block data must
170 /// be stored without containing any dependencies; in particular, in the user interface,
171 /// displaying a block originating from a different universe.
172 ///
173 /// It cannot be serialized.
174 #[doc(hidden)]
175 Raw {
176 // Note: the `Arc` is indirection so that `Primitive` is not made very large by this
177 // special case enum, and is an `Arc` in particular so that cloning a `Primitive` does not
178 // allocate.
179 attributes: Arc<BlockAttributes>,
180 voxels: Evoxels,
181 },
182}
183
184/// Data of [`Primitive::Atom`]. The definition of a single [block](Block) that has uniform
185/// material properties rather than spatially varying ones; a single voxel.
186///
187/// All properties of an atom are [intensive properties].
188///
189/// [intensive properties]: https://en.wikipedia.org/wiki/Intensive_and_extensive_properties
190#[derive(Clone, Eq, Hash, PartialEq)]
191#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
192#[expect(clippy::exhaustive_structs)]
193pub struct Atom {
194 /// The color exhibited by diffuse reflection from this block.
195 ///
196 /// The RGB components of this color are the *[reflectance]:* the fraction of incoming light
197 /// that is reflected rather than absorbed.
198 ///
199 /// The alpha (<var>α</var>) component of this color specifies the opacity of this block,
200 /// that is, the fraction of light that is reflected or absorbed rather than transmitted.
201 ///
202 /// Whenever <var>α</var> is neither 1 nor 0 (and, trivially, in those cases too),
203 /// the reflectance and opacity
204 /// should be interpreted as being of **a unit thickness of** this material. Thus, they may be
205 /// modified by the length of material through which a light ray passes, either due to
206 /// viewing angle or due to using this block as a voxel in a [`Primitive::Recur`].
207 ///
208 /// This transformation is best understood in terms of _transmittance_ <var>T</var>,
209 /// defined as 1 − <var>α</var>.
210 /// The transmittance of a given thickness of material, <var>T</var><sub><var>d</var></sub>,
211 /// is defined in terms of the transmittance of a unit thickness,
212 /// <var>T</var><sub><var>1</var></sub>, as:
213 ///
214 /// <p style="text-align: center">
215 /// <var>T</var><sub><var>d</var></sub> = (<var>T</var><sub>1</sub>)<sup><var>d</var></sup>.
216 /// </p>
217 ///
218 /// Therefore,
219 ///
220 /// <p style="text-align: center">
221 /// <var>α</var><sub>d</sub> =
222 /// 1 − (1 − <var>α</var><sub>1</sub>)<sup><var>d</var></sup>.
223 /// </p>
224 ///
225 /// [reflectance]: https://en.wikipedia.org/wiki/Reflectance
226 pub color: Rgba,
227
228 /// Light emitted (not reflected) by the block.
229 ///
230 /// This quantity is the emitted portion of the *[luminance]* of this material, in unspecified
231 /// units where 1.0 is the display white level (except for the effects of tone mapping).
232 /// In the future this may be redefined in terms of a physical unit, but with the same
233 /// dimensions.
234 ///
235 /// Because we are describing a volume, not a surface, the physical
236 /// interpretation of this value depends on the opacity of the material.
237 /// If `self.color.alpha()` is 1.0, then this light escaping a surface must have been emitted at
238 /// the surface; if the alpha is 0.0, then it must have been emitted throughout the volume; and
239 /// in intermediate cases, then the light emitted within the volume must be greater per unit
240 /// volume to compensate for internal absorption. Still, these are not distinct cases but form
241 /// a continuum.
242 ///
243 /// The emission <var>E</var><sub><var>d</var></sub> of a particular thickness <var>d</var>
244 /// of this material is
245 ///
246 /// <p style="text-align: center">
247 /// <var>E</var><sub><var>d</var></sub> = <var>E</var><sub>1</sub> ·
248 /// ∫<sub>0</sub><sup><var>d</var></sup>
249 /// (<var>T</var><sub>1</sub>)<sup><var>x</var></sup>
250 /// <var>dx</var>
251 /// </p>
252 ///
253 /// where <var>E</var><sub>1</sub> = `self.emission` and
254 /// <var>T</var><sub>1</sub> = `1.0 - self.color.alpha()`.
255 /// When integrated, this becomes
256 ///
257 /// <p style="text-align: center">
258 /// <var>E</var><sub><var>d</var></sub> = <var>E</var><sub>1</sub> · <var>d</var>
259 /// </p>
260 ///
261 /// when <var>α</var> = 0 (<var>T</var> = 1) and
262 ///
263 /// <p style="text-align: center">
264 /// <var>E</var><sub><var>d</var></sub> = <var>E</var><sub>1</sub> ·
265 /// (<var>T</var><sub><var>d</var></sub> - 1) / (<var>T</var><sub>1</sub> - 1)
266 /// </p>
267 ///
268 /// otherwise.
269 ///
270 ///
271 /// [luminance]: https://en.wikipedia.org/wiki/Luminance
272 pub emission: Rgb,
273
274 /// The effect on a [`Body`](crate::physics::Body) of colliding with this block.
275 ///
276 /// The default value is [`BlockCollision::Hard`].
277 pub collision: BlockCollision,
278}
279
280// -------------------------------------------------------------------------------------------------
281// Impls and supporting items
282
283impl fmt::Debug for Block {
284 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285 let mut s = f.debug_struct("Block");
286 s.field("primitive", self.primitive());
287 let modifiers = self.modifiers();
288 if !modifiers.is_empty() {
289 s.field("modifiers", &self.modifiers());
290 }
291 s.finish()
292 }
293}
294
295impl Block {
296 /// Returns a new [`Builder`] which may be used to construct a [`Block`] value
297 /// from various inputs with convenient syntax.
298 pub const fn builder<'u>() -> Builder<'u, builder::NeedsPrimitive, ()> {
299 Builder::new()
300 }
301
302 /// Construct a [`Block`] from a [`Primitive`] value.
303 // TODO: Decide whether this should go away as temporary from refactoring.
304 pub fn from_primitive(p: Primitive) -> Self {
305 if let Primitive::Air = p {
306 // Avoid allocating an Arc.
307 AIR
308 } else {
309 Block(BlockPtr::Owned(Arc::new(BlockParts {
310 primitive: p,
311 modifiers: vec![],
312 })))
313 }
314 }
315
316 /// Constructs a [`Block`] which references the given static [`Primitive`].
317 ///
318 /// This performs no allocation.
319 /// It is also available as a [`From`] implementation.
320 #[doc(hidden)] // used by `block::from_color!()`, but I'm not sure whether to make it really public
321 pub const fn from_static_primitive(r: &'static Primitive) -> Self {
322 Block(BlockPtr::Static(r))
323 }
324
325 /// Returns the [`Primitive`] which defines this block before any
326 /// [`Modifier`]s are applied.
327 pub fn primitive(&self) -> &Primitive {
328 match self.0 {
329 BlockPtr::Static(primitive) => primitive,
330 BlockPtr::Owned(ref arc) => &arc.primitive,
331 }
332 }
333
334 /// Returns a mutable reference to the [`Primitive`] which defines this block before
335 /// any [`Modifier`]s are applied.
336 ///
337 /// This may cause part or all of the block's data to stop sharing storage with other
338 /// blocks.
339 pub fn primitive_mut(&mut self) -> &mut Primitive {
340 &mut self.make_parts_mut().primitive
341 }
342
343 /// Returns all the modifiers of this block.
344 ///
345 /// Modifiers are arranged in order of their application to the primitive,
346 /// or “innermost” to “outermost”.
347 ///
348 /// Note that this does not necessarily return all modifiers involved in its
349 /// definition; modifiers on the far end of a [`Primitive::Indirect`] are
350 /// not reported here, even though they take effect when evaluated.
351 pub fn modifiers(&self) -> &[Modifier] {
352 match self.0 {
353 BlockPtr::Static(_) => &[],
354 BlockPtr::Owned(ref arc_parts) => &arc_parts.modifiers,
355 }
356 }
357
358 /// Returns a mutable reference to the vector of [`Modifier`]s on this block.
359 ///
360 /// This may cause part or all of the block's data to stop sharing storage with other
361 /// blocks.
362 // TODO: This nails down our representation a bit much
363 pub fn modifiers_mut(&mut self) -> &mut Vec<Modifier> {
364 &mut self.make_parts_mut().modifiers
365 }
366
367 fn make_parts_mut(&mut self) -> &mut BlockParts {
368 match self.0 {
369 BlockPtr::Static(static_primitive) => {
370 *self = Block(BlockPtr::Owned(Arc::new(BlockParts {
371 primitive: static_primitive.clone(),
372 modifiers: vec![],
373 })));
374 match self.0 {
375 BlockPtr::Owned(ref mut arc_repr) => Arc::make_mut(arc_repr),
376 BlockPtr::Static(_) => unreachable!(),
377 }
378 }
379 BlockPtr::Owned(ref mut arc_repr) => Arc::make_mut(arc_repr),
380 }
381 }
382
383 /// Add the given modifier to this block.
384 ///
385 /// This is a convenience operation which is exactly equivalent to
386 /// doing `block.modifiers_mut().push(modifier.into())`. It does not do any of the
387 /// special case logic that, for example, [`Block::rotate()`] does.
388 #[must_use]
389 pub fn with_modifier(mut self, modifier: impl Into<Modifier>) -> Self {
390 self.modifiers_mut().push(modifier.into());
391 self
392 }
393
394 /// Returns whether this block’s evaluation would be affected at all by adding
395 /// or removing a [`Modifier::Rotate`].
396 ///
397 /// Note that this does not account for actual symmetry of the block’s current evaluation;
398 /// it only checks the primitive’s own data, and so answers whether this primitive is *always*
399 /// symmetric under all possible conditions of the rest of the universe.
400 #[cfg_attr(feature = "_special_testing", visibility::make(pub))] // TODO: unclear if good public API, but public for fuzz testing
401 pub(crate) fn rotationally_symmetric(&self) -> bool {
402 // TODO: Just checking the definitions does not reveal sufficient information.
403 // In particular, `Primitive::Indirect` is opaque. Therefore, for some applications,
404 // we want a version that operates on `EvaluatedBlock` which can consult whether the
405 // block is symmetric accounting for all parts of its definition. On the other hand,
406 // other applications might care not whether it is *currently* symmetric but whether
407 // it can ever change to be asymmetric, for which this is the actual right answer.
408 self.primitive().rotationally_symmetric()
409 && self.modifiers().iter().all(|m| m.does_not_introduce_asymmetry())
410 }
411
412 /// Add a [`Modifier::Attributes`] if there isn't one already.
413 /// Evaluates the block if needed to get existing attributes.
414 ///
415 /// TODO: bad API, because it overwrites/freezes attributes; this was added in a hurry to tidy
416 /// up the attributes-is-a-modifer refactor. The proper API is more like `with_modifier()`
417 /// for a single attribute, but we don't have single attribute override modifiers yet.
418 #[cfg(test)]
419 pub(crate) fn freezing_get_attributes_mut(
420 &mut self,
421 read_ticket: ReadTicket<'_>,
422 ) -> &mut BlockAttributes {
423 if !matches!(self.modifiers().last(), Some(Modifier::Attributes(_))) {
424 let attr_modifier = self.evaluate(read_ticket).unwrap().attributes.into();
425 self.modifiers_mut().push(attr_modifier);
426 }
427 let Some(Modifier::Attributes(a)) = self.modifiers_mut().last_mut() else {
428 unreachable!();
429 };
430 Arc::make_mut(a)
431 }
432
433 /// Rotates this block by the specified rotation.
434 ///
435 /// Compared to direct use of [`Modifier::Rotate`], this will:
436 ///
437 /// * Avoid constructing chains of redundant modifiers.
438 /// * Not rotate blocks that should never appear rotated (including atom blocks).
439 ///
440 /// (TODO: This should be replaced with `with_modifier()` or similar having a general
441 /// rule set for combining modifiers.)
442 ///
443 /// ```
444 /// use all_is_cubes::block;
445 /// use all_is_cubes::content::make_some_voxel_blocks;
446 /// use all_is_cubes::math::{Face6, Rgba};
447 /// use all_is_cubes::universe::Universe;
448 ///
449 /// let mut universe = Universe::new();
450 /// let [mut block] = make_some_voxel_blocks(&mut universe);
451 /// block.modifiers_mut().clear();
452 /// let clockwise = Face6::PY.clockwise();
453 ///
454 /// // Basic rotation
455 /// let rotated = block.clone().rotate(clockwise);
456 /// assert_eq!(rotated.modifiers(), &[block::Modifier::Rotate(clockwise)]);
457 ///
458 /// // Multiple rotations are combined
459 /// let double = rotated.clone().rotate(clockwise);
460 /// assert_eq!(double.modifiers(), &[block::Modifier::Rotate(clockwise * clockwise)]);
461 ///
462 /// // Atoms and AIR are never rotated
463 /// let atom = block::from_color!(Rgba::WHITE);
464 /// assert_eq!(atom.clone().rotate(clockwise), atom);
465 /// assert_eq!(block::AIR.rotate(clockwise), block::AIR);
466 /// ```
467 #[must_use]
468 pub fn rotate(mut self, rotation: GridRotation) -> Self {
469 if rotation == GridRotation::IDENTITY {
470 // TODO: Should we *remove* any identity rotation already present,
471 // to make a fully canonical result?
472 return self;
473 }
474
475 if self.rotationally_symmetric() {
476 return self;
477 }
478
479 let parts = self.make_parts_mut();
480 match parts.modifiers.last_mut() {
481 // TODO: If the combined rotation is the identity, discard the modifier
482 Some(Modifier::Rotate(existing_rotation)) => {
483 *existing_rotation = rotation * *existing_rotation;
484 }
485 None | Some(_) => parts.modifiers.push(Modifier::Rotate(rotation)),
486 }
487 self
488 }
489
490 /// Standardizes any characteristics of this block which may be presumed to be
491 /// specific to its usage in its current location, so that it can be used elsewhere
492 /// or compared with others. Specifically, it has the following effects:
493 ///
494 /// * Removes [`Modifier::Rotate`].
495 /// * Splits some [`Modifier::Composite`] into their parts.
496 ///
497 /// In future versions there may be additional changes or ones customizable per block.
498 ///
499 /// # Examples
500 ///
501 /// Removing rotation:
502 /// ```
503 /// use all_is_cubes::block::Block;
504 /// # use all_is_cubes::content::make_some_voxel_blocks;
505 /// use all_is_cubes::math::Face6;
506 /// use all_is_cubes::universe::Universe;
507 ///
508 /// let mut universe = Universe::new();
509 /// let [block] = make_some_voxel_blocks(&mut universe);
510 /// let rotated = block.clone().rotate(Face6::PY.clockwise());
511 ///
512 /// assert_ne!(&block, &rotated);
513 /// assert_eq!(vec![block], rotated.clone().unspecialize());
514 /// ```
515 #[must_use]
516 pub fn unspecialize(&self) -> Vec<Block> {
517 let mut queue = VecDeque::from([self.clone()]);
518 let mut output = Vec::new();
519
520 'queue: while let Some(mut block) = queue.pop_front() {
521 if block.modifiers().is_empty() {
522 // No need to reify the modifier list if it doesn't exist already.
523 output.push(block);
524 continue;
525 }
526
527 while let Some(modifier) = block.modifiers().last() {
528 match modifier.unspecialize(&block) {
529 ModifierUnspecialize::Keep => {
530 output.push(block);
531 continue 'queue;
532 }
533 ModifierUnspecialize::Pop => {
534 block.modifiers_mut().pop();
535 // and continue to possibly pop more...
536 }
537 ModifierUnspecialize::Replace(replacements) => {
538 let replacements = replacements.into_iter().inspect(|r| {
539 assert_ne!(
540 r, &block,
541 "infinite loop detected: \
542 modifier returned original block from unspecialize()"
543 );
544 });
545 queue.extend(replacements);
546 continue 'queue;
547 }
548 }
549 }
550 // If and only if we got here rather than doing something else, the block
551 // now has all its unwanted modifiers popped or replaced.
552 output.push(block);
553 }
554
555 output
556 }
557
558 /// If this block has an inventory, return it and its modifier index.
559 // TODO: not the greatest API design
560 pub fn find_inventory(&self) -> Option<(usize, &inv::Inventory)> {
561 match self
562 .modifiers()
563 .iter()
564 .enumerate()
565 .rev()
566 // TODO: we need a general theory of which modifiers we definitely should not
567 // traverse past, or maybe block evaluation should produce a derived field for
568 // "this is the modifier index of my inventory that I functionally posess"
569 .take_while(|(_, m)| !matches!(m, Modifier::Quote(_)))
570 .find(|(_, m)| matches!(m, Modifier::Inventory(_)))
571 {
572 Some((index, Modifier::Inventory(inventory))) => Some((index, inventory)),
573 Some((_index, wrong_modifier)) => unreachable!("wrong modifier {wrong_modifier:?}"),
574 None => None,
575 }
576 }
577
578 /// Converts this `Block` into a “flattened” and snapshotted form which contains all
579 /// information needed for rendering and physics, and does not require [`Handle`] access
580 /// to other objects.
581 pub fn evaluate(&self, read_ticket: ReadTicket<'_>) -> Result<EvaluatedBlock, EvalBlockError> {
582 self.evaluate2(&EvalFilter {
583 read_ticket,
584 skip_eval: false,
585 listener: None,
586 budget: Default::default(),
587 })
588 }
589
590 /// As [`Block::evaluate()`], but also installs a listener which will be notified of
591 /// changes in all data sources that might affect the evaluation result.
592 ///
593 /// Note that this does not listen for mutations of the [`Block`] value itself, in the
594 /// sense that none of the methods on [`Block`] will cause this listener to fire.
595 /// Rather, it listens for changes in by-reference-to-interior-mutable-data sources
596 /// such as the [`Space`] referred to by a [`Primitive::Recur`] or the [`BlockDef`]
597 /// referred to by a [`Primitive::Indirect`].
598 ///
599 /// # Errors
600 ///
601 /// If an evaluation error is reported, the [`Listener`] may have been installed
602 /// incompletely or not at all. It should not be relied on.
603 pub fn evaluate_and_listen(
604 &self,
605 read_ticket: ReadTicket<'_>,
606 listener: impl listen::IntoListener<listen::DynListener<BlockChange>, BlockChange>,
607 ) -> Result<EvaluatedBlock, EvalBlockError> {
608 self.evaluate2(&EvalFilter {
609 read_ticket,
610 skip_eval: false,
611 listener: Some(listener.into_listener()),
612 budget: Default::default(),
613 })
614 }
615
616 /// Internal general entry point for block evaluation.
617 ///
618 /// TODO: Placeholder name. At some point we may expose `EvalFilter` directly and make
619 /// this be just `evaluate()`.
620 pub(crate) fn evaluate2(
621 &self,
622 filter: &EvalFilter<'_>,
623 ) -> Result<EvaluatedBlock, EvalBlockError> {
624 finish_evaluation(
625 self.clone(),
626 filter.budget.get(),
627 self.evaluate_impl(filter),
628 filter,
629 )
630 }
631
632 /// Equivalent to `Evoxel::from_block(block.evaluate2(filter))` except for the error type.
633 /// For use when blocks contain other blocks as voxels.
634 fn evaluate_to_evoxel_internal(&self, filter: &EvalFilter<'_>) -> Result<Evoxel, InEvalError> {
635 // TODO: Make this more efficient by not building the full `EvaluatedBlock`
636 self.evaluate_impl(filter)
637 .map(|minev| Evoxel::from_block(&minev.finish(self.clone(), Cost::ZERO /* ignored */)))
638 }
639
640 #[inline]
641 fn evaluate_impl(&self, filter: &EvalFilter<'_>) -> Result<MinEval, InEvalError> {
642 // The block's primitive counts as 1 component.
643 Budget::decrement_components(&filter.budget)?;
644
645 let mut value: MinEval = match *self.primitive() {
646 Primitive::Indirect(ref def_handle) => {
647 def_handle.read(filter.read_ticket)?.evaluate_impl(filter)?
648 }
649
650 Primitive::Atom(Atom {
651 color,
652 emission,
653 collision,
654 }) => MinEval::new(
655 BlockAttributes::default(),
656 Evoxels::from_one(Evoxel {
657 color,
658 emission,
659 selectable: true,
660 collision,
661 }),
662 ),
663
664 Primitive::Air => AIR_EVALUATED_MIN,
665
666 Primitive::Recur {
667 offset,
668 resolution,
669 space: ref space_handle,
670 } => {
671 let block_space = space_handle.read(filter.read_ticket)?;
672
673 // The region of `space` that the parameters say to look at.
674 let full_resolution_bounds =
675 GridAab::for_block(resolution).translate(offset.to_vector());
676
677 if let Some(listener) = &filter.listener {
678 block_space.listen(listener.clone().filter(
679 move |msg: &SpaceChange| -> Option<BlockChange> {
680 match *msg {
681 SpaceChange::CubeBlock { cube, .. }
682 if full_resolution_bounds.contains_cube(cube) =>
683 {
684 Some(BlockChange::new())
685 }
686 SpaceChange::CubeBlock { .. } => None,
687 SpaceChange::EveryBlock => Some(BlockChange::new()),
688
689 // TODO: It would be nice if the space gave more precise updates
690 // such that we could conclude e.g. "this is a new/removed block
691 // in an unaffected area" without needing to store any data.
692 SpaceChange::BlockEvaluation(_) => Some(BlockChange::new()),
693
694 // Index changes by themselves cannot affect the result.
695 SpaceChange::BlockIndex(_) => None,
696
697 // Things that do not matter.
698 SpaceChange::CubeLight { .. } => None,
699 SpaceChange::Physics => None,
700 }
701 },
702 ));
703 }
704
705 // Intersect that region with the actual bounds of `space`.
706 let mut voxels_animation_hint = AnimationHint::UNCHANGING;
707 let voxels: Vol<Arc<[Evoxel]>> = match full_resolution_bounds
708 .intersection_cubes(block_space.bounds())
709 .filter(|_| !filter.skip_eval)
710 {
711 Some(occupied_bounds) => {
712 Budget::decrement_voxels(
713 &filter.budget,
714 occupied_bounds.volume().unwrap(),
715 )?;
716
717 block_space
718 .extract(
719 occupied_bounds,
720 #[inline(always)]
721 |extract| {
722 let ev = extract.block_data().evaluated();
723 voxels_animation_hint |= ev.attributes().animation_hint;
724 Evoxel::from_block(ev)
725 },
726 )
727 .translate(-offset.to_vector())
728 }
729 None => {
730 // If there is no intersection, then return an empty voxel array,
731 // with an arbitrary position.
732 // Also applies when skip_eval is true
733 Vol::from_elements(GridAab::ORIGIN_EMPTY, Box::<[Evoxel]>::default())
734 .unwrap()
735 }
736 };
737
738 MinEval::new(
739 BlockAttributes {
740 // Translate the voxels' animation hints into their effect on
741 // the outer block.
742 animation_hint: AnimationHint {
743 redefinition: voxels_animation_hint.redefinition
744 | voxels_animation_hint.replacement,
745 replacement: AnimationChange::None,
746 },
747 ..BlockAttributes::default()
748 },
749 Evoxels::from_many(resolution, voxels),
750 )
751 }
752
753 Primitive::Text { ref text, offset } => text.evaluate(offset, filter)?,
754
755 Primitive::Raw {
756 ref attributes,
757 ref voxels,
758 } => MinEval::new(BlockAttributes::clone(attributes), voxels.clone()),
759 };
760
761 #[cfg(debug_assertions)]
762 value.consistency_check();
763
764 for (index, modifier) in self.modifiers().iter().enumerate() {
765 value = modifier.evaluate(self, index, value, filter)?;
766
767 #[cfg(debug_assertions)]
768 value.consistency_check();
769 }
770
771 Ok(value)
772 }
773
774 /// Returns the single [`Rgba`] color of this block's [`Primitive::Atom`] or
775 /// [`Primitive::Air`], or panics if it has a different kind of primitive.
776 /// **Intended for use in tests only.**
777 pub fn color(&self) -> Rgba {
778 match *self.primitive() {
779 Primitive::Atom(Atom { color, .. }) => color,
780 Primitive::Air => AIR_EVALUATED.color(),
781 Primitive::Indirect(_)
782 | Primitive::Recur { .. }
783 | Primitive::Text { .. }
784 | Primitive::Raw { .. } => {
785 panic!("Block::color not defined for non-atom blocks")
786 }
787 }
788 }
789}
790
791// Manual implementations of Eq and Hash ensure that the [`BlockPtr`] storage
792// choices do not affect equality.
793impl PartialEq for Block {
794 fn eq(&self, other: &Self) -> bool {
795 self.primitive() == other.primitive() && self.modifiers() == other.modifiers()
796 }
797}
798impl Eq for Block {}
799impl core::hash::Hash for Block {
800 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
801 self.primitive().hash(state);
802 self.modifiers().hash(state);
803 }
804}
805
806impl VisitHandles for Block {
807 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
808 self.primitive().visit_handles(visitor);
809 for modifier in self.modifiers() {
810 modifier.visit_handles(visitor)
811 }
812 }
813}
814
815impl From<&'static Primitive> for Block {
816 /// Constructs a [`Block`] which references the given static [`Primitive`].
817 ///
818 /// This performs no allocation.
819 fn from(r: &'static Primitive) -> Self {
820 Block(BlockPtr::Static(r))
821 }
822}
823
824impl From<Primitive> for Block {
825 /// Constructs a [`Block`] that owns the given [`Primitive`].
826 ///
827 /// This operation creates a heap allocation for the [`Primitive`].
828 fn from(primitive: Primitive) -> Self {
829 Block::from_primitive(primitive)
830 }
831}
832
833// Implementing conversions to `Cow` allow various functions to accept either an owned
834// or borrowed `Block`. The motivation for this is to avoid unnecessary cloning
835// (in case an individual block has large data).
836// TODO: Eliminate these given the new Block-is-a-pointer world.
837impl From<Block> for Cow<'_, Block> {
838 fn from(block: Block) -> Self {
839 Cow::Owned(block)
840 }
841}
842impl<'a> From<&'a Block> for Cow<'a, Block> {
843 fn from(block: &'a Block) -> Self {
844 Cow::Borrowed(block)
845 }
846}
847
848// Converting colors to blocks.
849impl From<Rgb01> for Block {
850 /// Constructs a [`Block`] with the given reflectance color, and default attributes.
851 ///
852 /// This operation allocates a new [`Primitive`] value on the heap.
853 /// If the color is a constant, you may use [`block::from_color!`](from_color!)
854 /// instead to avoid allocation.
855 fn from(color: Rgb01) -> Self {
856 Block::from(color.with_alpha_one())
857 }
858}
859impl From<Rgba> for Block {
860 /// Construct a [`Block`] with the given reflectance color, and default attributes.
861 ///
862 /// This operation allocates a new [`Primitive`] value on the heap.
863 /// If the color is a constant, you may use [`block::from_color!`](from_color!)
864 /// instead to avoid allocation.
865 fn from(color: Rgba) -> Self {
866 Block::from_primitive(Primitive::Atom(Atom::from(color)))
867 }
868}
869
870// Scoping shenanigan: Macros can only be public from a library using `#[macro_export]`,
871// but `#[macro_export]` puts the macro at the crate root. To work around this, we use
872// `#[macro_export]` to export an undocumented name, then re-export it publicly with a normal `use`.
873#[doc(hidden)]
874#[macro_export]
875#[allow(
876 clippy::module_name_repetitions,
877 reason = "TODO: remove after <https://github.com/rust-lang/rust-clippy/pull/15319> (Rust 1.90?)"
878)]
879macro_rules! _block_from_color {
880 ($color:expr) => {
881 $crate::block::Block::from_static_primitive(const {
882 &$crate::block::Primitive::from_color($color.with_alpha_one_if_has_no_alpha())
883 })
884 };
885
886 ($r:literal, $g:literal, $b:literal $(,)?) => {
887 $crate::block::from_color!($crate::math::rgb_const!($r, $g, $b))
888 };
889
890 ($r:literal, $g:literal, $b:literal, $a:literal $(,)?) => {
891 $crate::block::from_color!($crate::math::rgba_const!($r, $g, $b, $a))
892 };
893}
894/// Construct a [`Block`] with the given reflectance color.
895///
896/// This is equivalent to calling `Block::from(some_color)`, except that:
897///
898/// * the arguments must be constant expressions,
899/// * no allocations are performed, and
900/// * the value may be used in `const` evaluation.
901///
902/// The color may be specified as an expression which returns [`Rgb`] or [`Rgba`], or as three
903/// or four [`f32`] literal color components.
904///
905/// ```
906/// use all_is_cubes::block::{self, Block};
907/// use all_is_cubes::math::{Rgb01, rgb01};
908///
909/// assert_eq!(
910/// block::from_color!(rgb01!(1.0, 0.5, 0.0)),
911/// Block::from(rgb01!(1.0, 0.5, 0.0)),
912/// );
913///
914/// assert_eq!(
915/// block::from_color!(rgb01!(1.0, 0.5, 0.0)),
916/// block::from_color!(1.0, 0.5, 0.0),
917/// );
918///
919/// // Alpha equals 1.0 if not specified.
920/// assert_eq!(
921/// block::from_color!(1.0, 0.5, 0.0),
922/// block::from_color!(1.0, 0.5, 0.0, 1.0),
923/// );
924/// ```
925#[doc(inline)] // display as item, not as reexport
926pub use _block_from_color as from_color;
927
928#[cfg(feature = "arbitrary")]
929mod arbitrary_block {
930 use super::*;
931 use arbitrary::{Arbitrary, Unstructured};
932
933 // Manual impl to skip past BlockPtr etc.
934 // This means we're not exercising the `&'static` case, but that's not possible
935 // unless we decide to leak memory.
936 impl<'a> Arbitrary<'a> for Block {
937 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
938 let mut block = Block::from_primitive(Primitive::arbitrary(u)?);
939 *block.modifiers_mut() = Vec::arbitrary(u)?;
940 Ok(block)
941 }
942
943 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
944 // Both `Primitive` and `Modifier` are arbitrarily recursive because they can
945 // contain `Block`s. Therefore, the size hint calculation will always hit the depth
946 // limit, and we should skip it for efficiency.
947 // The lower bound is 2 because `Primitive` and `Modifiers` will each require
948 // at least one byte to make a choice.
949 (2, None)
950 }
951 }
952
953 // Manual impl because `Primitive::Text` isn't properly overflow-proof yet.
954 impl<'a> Arbitrary<'a> for Primitive {
955 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
956 Ok(match u.int_in_range(0..=4)? {
957 0 => Primitive::Air,
958 1 => Primitive::Atom(Atom::arbitrary(u)?),
959 2 => Primitive::Indirect(Handle::arbitrary(u)?),
960 3 => Primitive::Recur {
961 offset: GridPoint::from(<[i32; 3]>::arbitrary(u)?),
962 resolution: Resolution::arbitrary(u)?,
963 space: Handle::arbitrary(u)?,
964 },
965 4 => Primitive::Text {
966 text: text::Text::arbitrary(u)?,
967 // TODO: fix unhandled overflows so this can be full i32 range,
968 // then replace this `Arbitrary` impl with a derived one
969 offset: GridVector::from(<[i16; 3]>::arbitrary(u)?.map(i32::from)),
970 },
971 _ => unreachable!(),
972 })
973 }
974
975 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
976 // `Primitive` is arbitrarily recursive because it can contain `Block`s
977 // (via `Indirect` and `Text`). Therefore, the size hint calculation will always hit
978 // the depth limit, and we should skip it for efficiency.
979 // The lower bound is 1 because we need at least one byte to make a choice of primitive,
980 // but if that primitive is `AIR` then we need no more bytes.
981 (1, None)
982 }
983 }
984}
985
986/// An invisible, unselectable, inert block used as “no block”.
987///
988/// It is used by [`Space`] to respond to out-of-bounds requests,
989/// as well as other algorithms treating it as replaceable or discardable.
990///
991/// When evaluated, will always produce [`AIR_EVALUATED`].
992pub const AIR: Block = Block(BlockPtr::Static(&Primitive::Air));
993
994// TODO: uncomfortable with where this impl block is located
995impl Primitive {
996 /// Construct a [`Primitive`] from a reflectance color.
997 ///
998 /// This function is equivalent to `Block::from(color)` but it can be used in const contexts.
999 pub const fn from_color(color: Rgba) -> Primitive {
1000 Primitive::Atom(Atom::from_color(color))
1001 }
1002
1003 /// Returns whether this primitive would be left unchanged by a [`Modifier::Rotate`].
1004 ///
1005 /// Note that this does not account for symmetry of the block’s evaluation; it only checks
1006 /// the primitive’s own data, and so answers whether this primitive is *always* symmetric
1007 /// under all possible conditions of the rest of the universe. That is, it does not look
1008 /// through a `Primitive::Indirect` or `Primitive::Recur` to see the indirect data.
1009 pub(in crate::block) fn rotationally_symmetric(&self) -> bool {
1010 match self {
1011 Primitive::Indirect(_) => false, // could point to anything
1012 Primitive::Atom(atom) => atom.rotationally_symmetric(),
1013 Primitive::Recur { .. } => false, // could point to anything
1014 Primitive::Air => true,
1015 Primitive::Text { .. } => false, // always asymmetric unless it's trivial
1016 Primitive::Raw { .. } => false, // not worth implementing
1017 }
1018 }
1019}
1020
1021impl Atom {
1022 fn rotationally_symmetric(&self) -> bool {
1023 let Self {
1024 color: _,
1025 emission: _,
1026 collision: _,
1027 } = self;
1028 // I'm planning to eventually have non-uniform collision behaviors
1029 // or visual effects such as normal mapping,
1030 // at which point this will be sometimes false.
1031 true
1032 }
1033}
1034
1035impl fmt::Debug for Primitive {
1036 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1037 match self {
1038 Self::Indirect(def) => f.debug_tuple("Indirect").field(def).finish(),
1039 Self::Atom(atom) => atom.fmt(f),
1040 Self::Recur {
1041 space,
1042 offset,
1043 resolution,
1044 } => f
1045 .debug_struct("Recur")
1046 .field("space", space)
1047 .field("offset", offset)
1048 .field("resolution", resolution)
1049 .finish(),
1050 Self::Air => write!(f, "Air"),
1051 Self::Text { text, offset } => {
1052 f.debug_struct("Text").field("offset", offset).field("text", text).finish()
1053 }
1054 Self::Raw {
1055 attributes,
1056 voxels: _,
1057 } => f.debug_struct("Raw").field("attributes", attributes).finish_non_exhaustive(),
1058 }
1059 }
1060}
1061
1062impl fmt::Debug for Atom {
1063 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1064 let &Self {
1065 color,
1066 emission,
1067 collision,
1068 } = self;
1069 let mut s = f.debug_struct("Atom");
1070 s.field("color", &color);
1071 if emission != Rgb::ZERO {
1072 s.field("emission", &emission);
1073 }
1074 s.field("collision", &collision);
1075 s.finish()
1076 }
1077}
1078
1079impl VisitHandles for Primitive {
1080 fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
1081 match self {
1082 Primitive::Indirect(block_handle) => visitor.visit(block_handle),
1083 Primitive::Atom(atom) => atom.visit_handles(visitor),
1084 Primitive::Air => {}
1085 Primitive::Recur {
1086 space,
1087 offset: _,
1088 resolution: _,
1089 } => {
1090 visitor.visit(space);
1091 }
1092 Primitive::Text { text, offset: _ } => text.visit_handles(visitor),
1093 Primitive::Raw { attributes, voxels } => {
1094 attributes.visit_handles(visitor);
1095 voxels.visit_handles(visitor);
1096 }
1097 }
1098 }
1099}
1100
1101impl VisitHandles for Atom {
1102 fn visit_handles(&self, _: &mut dyn HandleVisitor) {
1103 let Self {
1104 color: _,
1105 emission: _,
1106 collision: _,
1107 } = self;
1108 }
1109}
1110
1111mod conversions_for_atom {
1112 use super::*;
1113
1114 impl Atom {
1115 /// Construct an [`Atom`] with the given reflectance color.
1116 ///
1117 /// This is identical to `From<Rgba>::from()` except that it is a `const fn`.
1118 // TODO: public API?
1119 pub(crate) const fn from_color(color: Rgba) -> Self {
1120 Atom {
1121 color,
1122 emission: Rgb::ZERO,
1123 collision: BlockCollision::DEFAULT_FOR_FROM_COLOR,
1124 }
1125 }
1126 }
1127
1128 impl From<Rgb01> for Atom {
1129 /// Construct an [`Atom`] with the given reflectance color, and default attributes.
1130 fn from(color: Rgb01) -> Self {
1131 Self::from_color(color.with_alpha_one())
1132 }
1133 }
1134 impl From<Rgba> for Atom {
1135 /// Construct an [`Atom`] with the given reflectance color, and default attributes.
1136 fn from(color: Rgba) -> Self {
1137 Self::from_color(color)
1138 }
1139 }
1140
1141 impl From<Atom> for Primitive {
1142 fn from(value: Atom) -> Self {
1143 Primitive::Atom(value)
1144 }
1145 }
1146
1147 impl From<Atom> for Block {
1148 fn from(value: Atom) -> Self {
1149 Block::from_primitive(Primitive::Atom(value))
1150 }
1151 }
1152}
1153
1154mod conversions_for_indirect {
1155 use super::*;
1156
1157 impl From<Handle<BlockDef>> for Primitive {
1158 /// Convert a `Handle<BlockDef>` into a [`Primitive::Indirect`] that refers to it.
1159 fn from(block_def_handle: Handle<BlockDef>) -> Self {
1160 Primitive::Indirect(block_def_handle)
1161 }
1162 }
1163
1164 impl From<Handle<BlockDef>> for Block {
1165 /// Convert a `Handle<BlockDef>` into a block with [`Primitive::Indirect`] that refers to it.
1166 ///
1167 /// The returned block will evaluate to the same [`EvaluatedBlock`] as the block contained
1168 /// within the given [`BlockDef`] (except in case of errors).
1169 fn from(block_def_handle: Handle<BlockDef>) -> Self {
1170 Block::from_primitive(Primitive::Indirect(block_def_handle))
1171 }
1172 }
1173}
1174
1175/// Notification when an [`EvaluatedBlock`] result changes.
1176#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1177#[non_exhaustive]
1178#[expect(clippy::module_name_repetitions)] // TODO: rename?
1179pub struct BlockChange {
1180 /// I expect there _might_ be future uses for a set of flags of what changed;
1181 /// this helps preserve the option of adding them.
1182 _not_public: (),
1183}
1184
1185impl BlockChange {
1186 #[expect(clippy::new_without_default)]
1187 #[allow(missing_docs)] // TODO: why is this public, anyway?
1188 pub fn new() -> BlockChange {
1189 BlockChange { _not_public: () }
1190 }
1191}
1192
1193/// Construct a set of [`Primitive::Recur`] blocks that form a miniature of the given `space`.
1194///
1195/// The returned [`Space`] contains each of the blocks; its coordinates will correspond to
1196/// those of the input, scaled down by `resolution`.
1197///
1198/// Panics if the `Space` cannot be accessed, and returns
1199/// [`SetCubeError::TooManyBlocks`] if the space volume is too large.
1200//---
1201// TODO: This is only used once ... is it really a good public API?
1202pub fn space_to_blocks(
1203 space_handle: Handle<Space>,
1204 read_ticket: ReadTicket<'_>,
1205 resolution: Resolution,
1206 block_transform: &mut dyn FnMut(Block) -> Block,
1207) -> Result<Space, SetCubeError> {
1208 let resolution_g: GridCoordinate = resolution.into();
1209 let source_bounds = space_handle
1210 .read(read_ticket)
1211 .expect("space_to_blocks() could not read() provided space")
1212 .bounds();
1213 let destination_bounds = source_bounds.divide(resolution_g);
1214
1215 let mut destination_space = Space::empty(destination_bounds);
1216 destination_space.mutate(read_ticket, |m| {
1217 m.fill(destination_bounds, move |cube| {
1218 Some(block_transform(Block::from_primitive(Primitive::Recur {
1219 offset: (cube.lower_bounds().to_vector() * resolution_g).to_point(),
1220 resolution,
1221 space: space_handle.clone(),
1222 })))
1223 })
1224 })?;
1225 Ok(destination_space)
1226}