From ca2b06942888515db0629b1dea2447e5d8bfa9c3 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Wed, 7 Aug 2024 18:02:11 -0700 Subject: [PATCH] Change definition of block visibility and `VoxelOpacityMask` to include emission. Part of fixing . This doesn't test or fix the rendering behavior. --- all-is-cubes/src/block/eval/derived.rs | 42 +++++++++++++++----- all-is-cubes/src/block/eval/evaluated.rs | 11 +++-- all-is-cubes/src/block/eval/tests.rs | 4 ++ all-is-cubes/src/block/eval/voxel_storage.rs | 20 +++++++++- all-is-cubes/src/block/tests.rs | 22 ++++++++++ 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/all-is-cubes/src/block/eval/derived.rs b/all-is-cubes/src/block/eval/derived.rs index 71141bc68..a7e7c0dea 100644 --- a/all-is-cubes/src/block/eval/derived.rs +++ b/all-is-cubes/src/block/eval/derived.rs @@ -18,7 +18,7 @@ use crate::math::{Cube, Face6, FaceMap, GridAab, Intensity, OpacityCategory, Rgb use crate::raytracer; #[cfg(doc)] -use crate::block::EvaluatedBlock; +use crate::block::{EvaluatedBlock, Evoxel}; /// Derived properties of an evaluated block. /// @@ -95,7 +95,7 @@ pub(in crate::block::eval) fn compute_derived( }, ) = voxels.single_voxel() { - let visible = !color.fully_transparent(); + let visible = !color.fully_transparent() || emission != Rgb::ZERO; return Derived { color, face_colors: FaceMap::repeat(color), @@ -281,8 +281,8 @@ impl ops::AddAssign for VoxSum { /// The visual shape of an [`EvaluatedBlock`]. /// -/// This data type stores the block's [`Resolution`], every voxel’s [`OpacityCategory`], and no -/// other information. +/// This data type stores the block's [`Resolution`], every voxel’s [`Evoxel::opacity_category()`], +/// and no other information. /// It may be used, when rendering blocks, to decide whether a change in a block /// affects the geometry of the scene, or just the colors to be drawn. /// @@ -320,7 +320,7 @@ impl VoxelOpacityMask { Self(MaskInner::Uniform( R1, GridAab::for_block(R1), - voxel.color.opacity_category(), + voxel.opacity_category(), )) } @@ -329,11 +329,8 @@ impl VoxelOpacityMask { .as_linear() .iter() .map( - // TODO: We also need to check the emission color for being nonzero. - // That isn't exactly properly “opacity” but it will align with the purposes - // this is used for. #[inline(always)] - |voxel| voxel.color.opacity_category(), + |voxel| voxel.opacity_category(), ) .all_equal_value() { @@ -365,7 +362,7 @@ impl VoxelOpacityMask { voxels.map_container(|voxels| { voxels .iter() - .map(|voxel| voxel.color.opacity_category()) + .map(|voxel| voxel.opacity_category()) .collect() }), )) @@ -514,4 +511,29 @@ mod tests { VoxelOpacityMask::new_r1(Evoxel::AIR) ); } + + #[test] + fn opacity_mask_counts_emission_as_visible() { + let bounds = GridAab::for_block(R2); + let voxel = Evoxel { + emission: Rgb::ONE, + ..Evoxel::from_color(Rgba::TRANSPARENT) + }; + assert_eq!( + VoxelOpacityMask::new( + R2, + Vol::from_elements(bounds, [voxel; 8].as_slice()).unwrap() + ), + VoxelOpacityMask::new_raw(R2, Vol::repeat(bounds, OpacityCategory::Partial)) + ); + + // test R1 case + assert_eq!( + VoxelOpacityMask::new_r1(voxel), + VoxelOpacityMask::new_raw( + R1, + Vol::repeat(GridAab::for_block(R1), OpacityCategory::Partial) + ) + ); + } } diff --git a/all-is-cubes/src/block/eval/evaluated.rs b/all-is-cubes/src/block/eval/evaluated.rs index 7ce5db5cf..726c124f8 100644 --- a/all-is-cubes/src/block/eval/evaluated.rs +++ b/all-is-cubes/src/block/eval/evaluated.rs @@ -190,8 +190,10 @@ impl EvaluatedBlock { self.derived.opaque } - /// Whether the block has any voxels/color at all that make it visible; that is, this - /// is false if the block is completely transparent. + /// Whether the block has any properties that make it visible; that is, this + /// is false only if the block is completely transparent and non-emissive. + /// + /// This value is suitable for deciding whether to skip rendering a block. #[inline] pub fn visible(&self) -> bool { self.derived.visible @@ -218,7 +220,7 @@ impl EvaluatedBlock { // --- Non-cached computed properties --- - /// Returns whether [`Self::visible`] is true (the block has some visible color/voxels) + /// Returns whether [`Self::visible()`] is true (the block has some visible color/voxels) /// or [`BlockAttributes::animation_hint`] indicates that the block might _become_ /// visible (by change of evaluation result rather than by being replaced). #[inline] @@ -244,11 +246,12 @@ impl EvaluatedBlock { } } - /// Expresses the opacity of the block as an [`OpacityCategory`]. + /// Expresses the visibility of the block as an [`OpacityCategory`]. /// /// If the return value is [`OpacityCategory::Partial`], this does not necessarily mean /// that the block contains semitransparent voxels, but only that the block as a whole /// does not fully pass light, and has not been confirmed to fully obstruct light. + /// It may also emit light but be otherwise invisible. /// /// TODO: Review uses of .opaque and .visible and see if they can be usefully replaced /// by this. diff --git a/all-is-cubes/src/block/eval/tests.rs b/all-is-cubes/src/block/eval/tests.rs index 82df95060..0ccc72fdc 100644 --- a/all-is-cubes/src/block/eval/tests.rs +++ b/all-is-cubes/src/block/eval/tests.rs @@ -135,6 +135,10 @@ fn visible_or_animated() { assert!(!va(AIR)); assert!(!va(Block::builder().color(Rgba::TRANSPARENT).build())); assert!(va(Block::builder().color(Rgba::WHITE).build())); + assert!(va(Block::builder() + .color(Rgba::TRANSPARENT) + .light_emission(Rgb::ONE) + .build())); assert!(va(Block::builder() .color(Rgba::TRANSPARENT) .animation_hint(block::AnimationHint::replacement( diff --git a/all-is-cubes/src/block/eval/voxel_storage.rs b/all-is-cubes/src/block/eval/voxel_storage.rs index b446959fe..bf7186846 100644 --- a/all-is-cubes/src/block/eval/voxel_storage.rs +++ b/all-is-cubes/src/block/eval/voxel_storage.rs @@ -10,7 +10,7 @@ use crate::block::{ BlockAttributes, BlockCollision, EvaluatedBlock, Resolution::{self, R1}, }; -use crate::math::{Cube, GridAab, Rgb, Rgba, Vol}; +use crate::math::{Cube, GridAab, OpacityCategory, Rgb, Rgba, Vol}; /// Properties of an individual voxel within [`EvaluatedBlock`]. /// @@ -83,6 +83,24 @@ impl Evoxel { collision: BlockCollision::DEFAULT_FOR_FROM_COLOR, } } + + /// Reports whether this voxel is invisible, fully opaque, or neither. + /// + /// This differs from `self.color.opacity_category()` in that it accounts for emission + /// making a voxel visible. + pub fn opacity_category(&self) -> OpacityCategory { + let &Self { + color, + emission, + selectable: _, + collision: _, + } = self; + let mut category = color.opacity_category(); + if emission != Rgb::ZERO && category == OpacityCategory::Invisible { + category = OpacityCategory::Partial; + } + category + } } /// Storage of an [`EvaluatedBlock`]'s shape — its _evaluated voxels._ diff --git a/all-is-cubes/src/block/tests.rs b/all-is-cubes/src/block/tests.rs index 113c318d7..29e01ccb2 100644 --- a/all-is-cubes/src/block/tests.rs +++ b/all-is-cubes/src/block/tests.rs @@ -142,6 +142,7 @@ mod eval { assert_eq!(e.attributes, attributes); assert_eq!(e.color(), color); assert_eq!(e.face_colors(), FaceMap::repeat(color)); + assert_eq!(e.light_emission(), Rgb::ONE); assert_eq!( e.voxels, Evoxels::from_one(Evoxel { @@ -167,6 +168,27 @@ mod eval { let e = block.evaluate().unwrap(); assert_eq!(e.color(), color); assert_eq!(e.face_colors(), FaceMap::repeat(color)); + assert_eq!(e.light_emission(), Rgb::ZERO); + assert!(e.voxels.single_voxel().is_some()); + assert_eq!(e.opaque(), FaceMap::repeat(false)); + assert_eq!(e.visible(), true); + assert_eq!( + *e.voxel_opacity_mask(), + VoxelOpacityMask::new_raw(R1, Vol::from_element(OpacityCategory::Partial)) + ) + } + + #[test] + fn emissive_only_atom() { + let emissive_color = Rgb::new(1.0, 2.0, 3.0); + let block = Block::builder() + .color(Rgba::TRANSPARENT) + .light_emission(emissive_color) + .build(); + let e = block.evaluate().unwrap(); + assert_eq!(e.color(), Rgba::TRANSPARENT); + assert_eq!(e.face_colors(), FaceMap::repeat(Rgba::TRANSPARENT)); + assert_eq!(e.light_emission(), emissive_color); assert!(e.voxels.single_voxel().is_some()); assert_eq!(e.opaque(), FaceMap::repeat(false)); assert_eq!(e.visible(), true);