Skip to content

Commit

Permalink
mesh: Make block mesh generation aware of emission-only voxels.
Browse files Browse the repository at this point in the history
Part of fixing <#504>.
We still don’t have the right rendering, but at least the geometry
exists now.
  • Loading branch information
kpreid committed Aug 12, 2024
1 parent 3139b24 commit fa9a35e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 35 deletions.
44 changes: 25 additions & 19 deletions all-is-cubes-mesh/src/block_mesh/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ use itertools::Itertools as _;
use all_is_cubes::block::{AnimationChange, EvaluatedBlock, Evoxel, Evoxels, Resolution};
use all_is_cubes::euclid::point2;
use all_is_cubes::math::{
Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, OpacityCategory, Rgb, Rgba,
Cube, Face6, FreeCoordinate, GridAab, GridCoordinate, OpacityCategory, Rgb, Vol,
};
use all_is_cubes_render::Flaws;

use crate::block_mesh::planar::{greedy_mesh, push_quad, GmRect, QuadColoring, QuadTransform};
use crate::block_mesh::planar::{
greedy_mesh, push_quad, GmRect, QuadColoring, QuadTransform, VisualVoxel,
};
use crate::block_mesh::{analyze::analyze, BlockFaceMesh};
use crate::texture::{self, Tile as _};
use crate::{BlockMesh, MeshOptions, MeshTypes, Viz};
Expand Down Expand Up @@ -188,7 +190,7 @@ pub(super) fn compute_block_mesh<M: MeshTypes>(
// That is, it excludes all obscured interior volume.
// First, we traverse the block and fill this with non-obscured voxels,
// then we erase it as we convert contiguous rectangles of it to quads.
let mut visible_image: Vec<Rgba> =
let mut visible_image: Vec<VisualVoxel> =
Vec::with_capacity(usize::try_from(occupied_rect.area()).unwrap_or(0));

let texture_plane_if_needed: Option<<M::Tile as texture::Tile>::Plane> =
Expand All @@ -214,32 +216,27 @@ pub(super) fn compute_block_mesh<M: MeshTypes>(
.cartesian_product(occupied_rect.x_range())
{
let cube: Cube = voxel_transform.transform_cube(Cube::new(s, t, layer));
let evoxel = get_voxel_with_limit(voxels_array, cube, options);

let color = options
.transparency
.limit_alpha(voxels_array.get(cube).unwrap_or(&Evoxel::AIR).color);

if layer == 0 && !color.fully_opaque() {
if layer == 0 && !evoxel.color.fully_opaque() {
// If the first layer is transparent in any cube at all, then the face is
// not fully opaque
face_mesh.fully_opaque = false;
}

let voxel_is_visible = {
use OpacityCategory::{Invisible, Opaque, Partial};
let this_cat = color.opacity_category();
let this_cat = evoxel.opacity_category();
if this_cat == Invisible {
false
} else {
// Compute whether this voxel is not hidden behind another
let obscuring_cat = voxels_array
.get(cube + face.normal_vector())
.map_or(Invisible, |ev| {
options
.transparency
.limit_alpha(ev.color)
.opacity_category()
});
let obscuring_cat = get_voxel_with_limit(
voxels_array,
cube + face.normal_vector(),
options,
)
.opacity_category();
match (this_cat, obscuring_cat) {
// Nothing to draw no matter what
(Invisible, _) => false,
Expand All @@ -264,11 +261,14 @@ pub(super) fn compute_block_mesh<M: MeshTypes>(
};
if voxel_is_visible {
layer_is_visible_somewhere = true;
visible_image.push(color);
visible_image.push(VisualVoxel {
reflectance: evoxel.color,
emission: evoxel.emission != Rgb::ZERO,
});
} else {
// All obscured voxels are treated as transparent ones, in that we don't
// generate geometry for them.
visible_image.push(Rgba::TRANSPARENT);
visible_image.push(VisualVoxel::INVISIBLE);
}
}

Expand Down Expand Up @@ -362,3 +362,9 @@ pub(super) fn compute_block_mesh<M: MeshTypes>(
};
}
}

fn get_voxel_with_limit(voxels: Vol<&[Evoxel]>, cube: Cube, options: &MeshOptions) -> Evoxel {
let mut evoxel = *voxels.get(cube).unwrap_or(&Evoxel::AIR);
evoxel.color = options.transparency.limit_alpha(evoxel.color);
evoxel
}
64 changes: 48 additions & 16 deletions all-is-cubes-mesh/src/block_mesh/planar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,48 @@ use all_is_cubes::math::{
use crate::texture::{self, TexelUnit, TextureCoordinate, TilePoint};
use crate::{BlockVertex, Coloring, IndexVec, Viz};

/// This is the subset of `Evoxel` which is processed by the [`greedy_mesh()`] planar mesh
/// generator. It does not distinguish emission other than “has some”, because we always
/// send emission to textures rather than vertex attributes.
///
/// The important property of this type is that it contains every property that might determine
/// *whether* we generate a mesh surface for a given voxel.
///
/// TODO: It would probably be better if we could just stop copying out the voxels and have all
/// phases of mesh generation consult `Evoxels` directly.
#[derive(Clone, Copy, PartialEq)]
pub(super) struct VisualVoxel {
pub reflectance: Rgba,
pub emission: bool,
}

impl VisualVoxel {
pub const INVISIBLE: Self = Self {
reflectance: Rgba::TRANSPARENT,
emission: false,
};
pub fn visible(&self) -> bool {
*self != Self::INVISIBLE
}
pub fn to_reflectance_only(self) -> Option<Rgba> {
if !self.emission {
Some(self.reflectance)
} else {
None
}
}
}

pub(super) fn greedy_mesh(
visible_image: Vec<Rgba>,
visible_image: Vec<VisualVoxel>,
image_s_range: Range<GridCoordinate>,
image_t_range: Range<GridCoordinate>,
) -> impl Iterator<Item = GmRect> {
GreedyMesher {
visible_image,
image_s_range,
image_t_range,
single_color: None,
single_reflectance: None,
rect_has_alpha: false,
}
.run()
Expand All @@ -30,12 +62,12 @@ pub(super) fn greedy_mesh(
/// Data structure for the state and components of the "greedy meshing" algorithm.
/// <https://0fps.net/2012/06/30/meshing-in-a-minecraft-game/>
struct GreedyMesher {
visible_image: Vec<Rgba>,
visible_image: Vec<VisualVoxel>,
// Logical bounding rectangle of the data in `visible_image`.
image_s_range: Range<GridCoordinate>,
image_t_range: Range<GridCoordinate>,
/// Contains a color if all voxels examined so far have that color.
single_color: Option<Rgba>,
/// Contains a color if all voxels examined so far have that reflectance and no emission.
single_reflectance: Option<Rgba>,
rect_has_alpha: bool,
}
impl GreedyMesher {
Expand Down Expand Up @@ -87,7 +119,7 @@ impl GreedyMesher {
Some(GmRect {
low_corner: Point2D::new(sl, tl),
high_corner: Point2D::new(sh, th),
single_color: self.single_color,
single_color: self.single_reflectance,
has_alpha: self.rect_has_alpha,
})
})
Expand All @@ -107,12 +139,12 @@ impl GreedyMesher {
/// returns false if not, and updates `single_color`.
#[inline]
fn add_seed(&mut self, s: GridCoordinate, t: GridCoordinate) -> bool {
let color = self.visible_image[self.index(s, t)];
if color.fully_transparent() {
let voxel = self.visible_image[self.index(s, t)];
if !voxel.visible() {
return false;
}
self.rect_has_alpha = !color.fully_opaque();
self.single_color = Some(color);
self.rect_has_alpha = !voxel.reflectance.fully_opaque();
self.single_reflectance = voxel.to_reflectance_only();
true
}

Expand All @@ -124,14 +156,14 @@ impl GreedyMesher {
if !self.image_s_range.contains(&s) || !self.image_t_range.contains(&t) {
return false;
}
let color = self.visible_image[self.index(s, t)];
if color.fully_transparent() {
let voxel = self.visible_image[self.index(s, t)];
if !voxel.visible() {
return false;
}
if Some(color) != self.single_color {
self.single_color = None; // Not a uniform color
if voxel.to_reflectance_only() != self.single_reflectance {
self.single_reflectance = None; // Not uniform
}
if !color.fully_opaque() {
if !voxel.reflectance.fully_opaque() {
self.rect_has_alpha = true;
}
true
Expand All @@ -140,7 +172,7 @@ impl GreedyMesher {
#[inline]
fn erase(&mut self, s: GridCoordinate, t: GridCoordinate) {
let index = self.index(s, t);
self.visible_image[index] = Rgba::TRANSPARENT;
self.visible_image[index] = VisualVoxel::INVISIBLE;
}
}

Expand Down

0 comments on commit fa9a35e

Please sign in to comment.