Skip to content

Commit

Permalink
mesh: Compute mesh vertex positions during analysis.
Browse files Browse the repository at this point in the history
This is not currently used, but I believe it will be necessary for
eventually fixing T-junctions, and it should currently compile to
nothing (when visualization is not enabled).

We will probably need to further refine this to include the directions
of edges, so that 2D polygon connectivity can be obtained; presuming
that the solution to T-junctions looks like using a conventional
polygon triangulation algorithm.
  • Loading branch information
kpreid committed Jan 30, 2025
1 parent c89a540 commit ef1b395
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 15 deletions.
37 changes: 34 additions & 3 deletions all-is-cubes-mesh/src/block_mesh/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pub(crate) fn analyze(resolution: Resolution, voxels: Vol<&[Evoxel]>, viz: &mut

for (center, window_voxels) in windows(voxels) {
viz.window(center, voxels);
analyze_one_window(&mut analysis, center, window_voxels);
analyze_one_window(&mut analysis, center, window_voxels, viz);
viz.analysis_in_progress(&analysis);
}
viz.clear_window();
Expand All @@ -172,7 +172,12 @@ pub(crate) fn analyze(resolution: Resolution, voxels: Vol<&[Evoxel]>, viz: &mut

/// Take one of the outputs of [`windows()`] and compute its contribution to [`analysis`].
#[inline]
fn analyze_one_window(analysis: &mut Analysis, center: GridPoint, window: OctantMap<&Evoxel>) {
fn analyze_one_window(
analysis: &mut Analysis,
center: GridPoint,
window: OctantMap<&Evoxel>,
viz: &mut Viz,
) {
use Face6::*;
const ALL: OctantMask = OctantMask::ALL;
const NONE: OctantMask = OctantMask::NONE;
Expand All @@ -195,29 +200,54 @@ fn analyze_one_window(analysis: &mut Analysis, center: GridPoint, window: Octant
.map(|voxel| (voxel.color, voxel.emission))
.all_equal();

// Bitmask of which axes are going to have a visible surface
let mut axes_involved = 0;

// For each direction, check if any of the voxels in the deeper side are visible
// and not covered by a voxel of the same or stronger category in the shallower side,
// and mark that plane as occupied if so.
//
// `unflatten()` undoes the axis dropping/swapping this code does.
if uncovered(opaque, PX) || uncovered(renderable, PX) {
axes_involved |= 0b1;
analysis.expand_rect(NX, center.x, center.yz());
}
if uncovered(opaque, PY) || uncovered(renderable, PY) {
axes_involved |= 0b10;
analysis.expand_rect(NY, center.y, center.xz());
}
if uncovered(opaque, PZ) || uncovered(renderable, PZ) {
axes_involved |= 0b100;
analysis.expand_rect(NZ, center.z, center.xy());
}
if uncovered(opaque, NX) || uncovered(renderable, NX) {
axes_involved |= 0b1;
analysis.expand_rect(PX, resolution_coord - center.x, center.yz());
}
if uncovered(opaque, NY) || uncovered(renderable, NY) {
axes_involved |= 0b10;
analysis.expand_rect(PY, resolution_coord - center.y, center.xz());
}
if uncovered(opaque, NZ) || uncovered(renderable, NZ) {
axes_involved |= 0b100;
analysis.expand_rect(PZ, resolution_coord - center.z, center.xy());
}

if axes_involved == 0b111 {
// If all three axes have visible surfaces at this point, then this must become a
// vertex of the mesh. We need to record such vertices as part of the analysis,
// because not all such vertices are identifiable by being corners in a 2D slice of
// the volume — there are also vertices where an edge on one plane meets a corner on
// another plane, and we must consistently treat them as vertices to avoid
// “T-junctions”: places where an edge of one triangle meets a vertex of other
// triangles, which are subject to numerical error during rendering that causes visible
// gaps.
//
// TODO: Currently, this information is not used (except by `Viz`).
// We should store it in `analysis` or return it in a separate buffer,
// but only once it is actually going to be used.
viz.add_analysis_vertex(center);
}
}
}

Expand Down Expand Up @@ -388,7 +418,8 @@ mod tests {
let mut u = Universe::new();

let block = Block::builder()
.voxels_fn(Resolution::R4, |_| color_block!(0.0, 0.0, 0.0, 0.5)).unwrap()
.voxels_fn(Resolution::R4, |_| color_block!(0.0, 0.0, 0.0, 0.5))
.unwrap()
.build_into(&mut u);
let ev = block.evaluate().unwrap();
let analysis = analyze(
Expand Down
63 changes: 51 additions & 12 deletions all-is-cubes-mesh/src/block_mesh/viz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use {
all_is_cubes::rerun_glue as rg,
alloc::vec::Vec,
core::iter,
core::mem,
itertools::Itertools as _,
};

Expand Down Expand Up @@ -45,13 +46,18 @@ pub struct Inner {
data_bounds: Option<GridAab>,

analysis: Analysis,
analysis_vertices: Vec<rg::datatypes::Vec3D>,
/// Tracks whether we have new vertices, to reduce the amount of logged data
/// TODO: Do this for the other things too.
analysis_vertices_dirty: bool,

destination: rg::Destination,
window_voxels_path: rg::EntityPath,
occupied_path: rg::EntityPath,
layer_path: rg::EntityPath,
mesh_surface_path: rg::EntityPath,
mesh_edges_path: rg::EntityPath,
analysis_vertices_path: rg::EntityPath,

// These two together make up the mesh edge display entity's data
mesh_edge_positions: Vec<rg::components::LineStrip3D>,
Expand Down Expand Up @@ -88,9 +94,12 @@ impl Viz {
layer_path: rg::entity_path!["progress", "mesh_plane"],
mesh_surface_path: rg::entity_path!["mesh", "surface"],
mesh_edges_path: rg::entity_path!["mesh", "edges"],
analysis_vertices_path: rg::entity_path!["analysis_vertices"],
resolution: None,
data_bounds: None,
analysis: Analysis::empty(),
analysis_vertices: Vec::new(),
analysis_vertices_dirty: false,
mesh_edge_positions: Vec::new(),
mesh_edge_classes: Vec::new(),
mesh_vertex_positions: Vec::new(),
Expand Down Expand Up @@ -273,6 +282,16 @@ impl Viz {
);
}
}

pub(crate) fn add_analysis_vertex(&mut self, #[allow(unused)] point: GridPoint) {
#[cfg(feature = "rerun")]
if let Self::Enabled(state) = self {
state
.analysis_vertices
.push(rg::convert_vec(point.to_vector()));
state.analysis_vertices_dirty = true;
}
}
}

#[cfg(feature = "rerun")]
Expand All @@ -281,18 +300,38 @@ impl Inner {
self.analysis.occupied_plane_box(face, layer).unwrap()
}

fn log_analysis(&self) {
let iter = Face6::ALL.into_iter().flat_map(|face| {
self.analysis
.occupied_planes(face)
.map(move |(layer, _)| self.layer_box(face, layer))
});
self.destination.log(
&self.occupied_path,
&rg::convert_grid_aabs(iter)
.with_class_ids([rg::ClassId::MeshVizOccupiedPlane])
.with_radii([OCCUPIED_RADIUS]),
);
/// Logs the current contents of the [`Analysis`], replacing any prior data.
fn log_analysis(&mut self) {
// Log occupied_planes
{
let iter = Face6::ALL.into_iter().flat_map(|face| {
self.analysis.occupied_planes(face).map({
let this = &*self;
move |(layer, _)| this.layer_box(face, layer)
})
});
self.destination.log(
&self.occupied_path,
&rg::convert_grid_aabs(iter)
.with_class_ids([rg::ClassId::MeshVizOccupiedPlane])
.with_radii([OCCUPIED_RADIUS]),
);
}

// Log analysis_vertices (not technically part of `Analysis` *yet*)
if mem::take(&mut self.analysis_vertices_dirty) {
self.destination.log(
&self.analysis_vertices_path,
// We use `Ellipsoids3D` instead of `Points3D`, even though these are semantically
// points, to get better rendering.
&rg::archetypes::Ellipsoids3D::from_centers_and_radii(
self.analysis_vertices.iter().copied(),
[0.15],
)
.with_colors([rg::components::Color::from_rgb(80, 80, 255)])
.with_fill_mode(rg::components::FillMode::Solid),
)
}
}

fn log_voxels(
Expand Down

0 comments on commit ef1b395

Please sign in to comment.