Skip to content

Commit

Permalink
Change definition of block visibility and VoxelOpacityMask to inclu…
Browse files Browse the repository at this point in the history
…de emission.

Part of fixing <#504>.
This doesn't test or fix the rendering behavior.
  • Loading branch information
kpreid committed Aug 8, 2024
1 parent a499c2f commit ca2b069
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 15 deletions.
42 changes: 32 additions & 10 deletions all-is-cubes/src/block/eval/derived.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -320,7 +320,7 @@ impl VoxelOpacityMask {
Self(MaskInner::Uniform(
R1,
GridAab::for_block(R1),
voxel.color.opacity_category(),
voxel.opacity_category(),
))
}

Expand All @@ -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()
{
Expand Down Expand Up @@ -365,7 +362,7 @@ impl VoxelOpacityMask {
voxels.map_container(|voxels| {
voxels
.iter()
.map(|voxel| voxel.color.opacity_category())
.map(|voxel| voxel.opacity_category())
.collect()
}),
))
Expand Down Expand Up @@ -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)
)
);
}
}
11 changes: 7 additions & 4 deletions all-is-cubes/src/block/eval/evaluated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions all-is-cubes/src/block/eval/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
20 changes: 19 additions & 1 deletion all-is-cubes/src/block/eval/voxel_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
///
Expand Down Expand Up @@ -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._
Expand Down
22 changes: 22 additions & 0 deletions all-is-cubes/src/block/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
Expand Down

0 comments on commit ca2b069

Please sign in to comment.