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 &minus; <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 &minus; (1 &minus; <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}