Skip to content

Commit

Permalink
Fix up margins after block updates
Browse files Browse the repository at this point in the history
  • Loading branch information
patowen committed Apr 22, 2024
1 parent 07c02a8 commit c397286
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 63 deletions.
120 changes: 118 additions & 2 deletions common/src/margins.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{
dodeca::Vertex,
graph::Graph,
math,
node::VoxelData,
node::{Chunk, ChunkId, VoxelData},
voxel_math::{ChunkDirection, CoordAxis, CoordSign, Coords, SimpleChunkOrientation},
world::Material,
};

/// Updates the margins of both `voxels` and `neighbor_voxels` at the side they meet at.
Expand Down Expand Up @@ -94,6 +96,55 @@ pub fn initialize_margins(dimension: u8, voxels: &mut VoxelData) {
}
}

/// Assuming that the voxel at `chunk` and `coords` is set to `material`, updates the cooresponding
/// margin in the chunk at direction `direction` from `chunk` if such a margin exists. Unpopulated chunks
/// are ignored.
pub fn update_margin_voxel(
graph: &mut Graph,
chunk: ChunkId,
coords: Coords,
direction: ChunkDirection,
material: Material,
) {
let coords: CoordsWithMargins = coords.into();
let dimension = graph.layout().dimension();
let edge_coord = match direction.sign {
CoordSign::Plus => dimension,
CoordSign::Minus => 1,
};
if coords[direction.axis] != edge_coord {
// There is nothing to do if we're not on an edge voxel.
return;
}
let Some(Chunk::Populated {
modified: _neighbor_modified,
voxels: neighbor_voxels,
surface: neighbor_surface,
old_surface: neighbor_old_surface,
}) = graph
.get_chunk_neighbor(chunk, direction.axis, direction.sign)
.map(|chunk_id| &mut graph[chunk_id])
else {
// If the neighboring chunk to check is not populated, there is nothing to do.
return;
};

let margin_coord = match direction.sign {
CoordSign::Plus => dimension + 1,
CoordSign::Minus => 0,
};
let neighbor_orientation = match direction.sign {
CoordSign::Plus => chunk.vertex.adjacent_chunk_orientations()[direction.axis as usize],
CoordSign::Minus => SimpleChunkOrientation::identity(),
};
let mut neighbor_coords = coords;
neighbor_coords[direction.axis] = margin_coord;
neighbor_coords = neighbor_orientation * neighbor_coords;

neighbor_voxels.data_mut(dimension)[neighbor_coords.to_index(dimension)] = material;
*neighbor_old_surface = neighbor_surface.take().or(*neighbor_old_surface);
}

/// Coordinates for a discrete voxel within a chunk, including margins
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct CoordsWithMargins(pub [u8; 3]);
Expand Down Expand Up @@ -142,7 +193,7 @@ impl std::ops::Mul<CoordsWithMargins> for SimpleChunkOrientation {

#[cfg(test)]
mod tests {
use crate::{dodeca::Vertex, voxel_math::Coords, world::Material};
use crate::{dodeca::Vertex, graph::NodeId, node, voxel_math::Coords, world::Material};

use super::*;

Expand Down Expand Up @@ -210,4 +261,69 @@ mod tests {
Material::WoodPlanks
);
}

#[test]
fn test_update_margin_voxel() {
let mut graph = Graph::new(12);
let current_vertex = Vertex::A;
let neighbor_vertex = current_vertex.adjacent_vertices()[1];
let neighbor_node =
graph.ensure_neighbor(NodeId::ROOT, current_vertex.canonical_sides()[0]);
node::populate_fresh_nodes(&mut graph);

// These are the chunks this test will work with.
let current_chunk = ChunkId::new(NodeId::ROOT, current_vertex);
let node_neighbor_chunk = ChunkId::new(neighbor_node, current_vertex);
let vertex_neighbor_chunk = ChunkId::new(NodeId::ROOT, neighbor_vertex);

// Populate relevant chunks with void
for chunk in [current_chunk, node_neighbor_chunk, vertex_neighbor_chunk] {
*graph.get_chunk_mut(chunk).unwrap() = Chunk::Populated {
voxels: VoxelData::Solid(Material::Void),
modified: false,
surface: None,
old_surface: None,
};
}

// Update and check the margins of node_neighbor_chunk
update_margin_voxel(
&mut graph,
current_chunk,
Coords([0, 7, 9]),
ChunkDirection::MINUS_X,
Material::WoodPlanks,
);
let Chunk::Populated {
voxels: node_neighbor_voxels,
..
} = graph.get_chunk_mut(node_neighbor_chunk).unwrap()
else {
panic!("node_neighbor_chunk should have just been populated by this test");
};
assert_eq!(
node_neighbor_voxels.get(CoordsWithMargins([0, 8, 10]).to_index(12)),
Material::WoodPlanks
);

// Update and check the margins of vertex_neighbor_chunk
update_margin_voxel(
&mut graph,
current_chunk,
Coords([5, 11, 9]),
ChunkDirection::PLUS_Y,
Material::Grass,
);
let Chunk::Populated {
voxels: vertex_neighbor_voxels,
..
} = graph.get_chunk_mut(vertex_neighbor_chunk).unwrap()
else {
panic!("vertex_neighbor_chunk should have just been populated by this test");
};
assert_eq!(
vertex_neighbor_voxels.get(CoordsWithMargins([6, 10, 13]).to_index(12)),
Material::Grass
);
}
}
69 changes: 8 additions & 61 deletions common/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ impl Graph {
else {
return false;
};
if voxels.is_solid() {
voxels.clear_margin(dimension);
}
let voxel = voxels
.data_mut(dimension)
.get_mut(block_update.coords.to_index(dimension))
Expand All @@ -158,43 +155,14 @@ impl Graph {
*modified = true;
*old_surface = surface.take().or(*old_surface);

self.clear_adjacent_solid_chunk_margins(block_update.chunk_id);
true
}

/// Clears margins from any populated and solid adjacent chunks. When a chunk is modified, this function should
/// be called on that chunk to ensure that adjacent chunks are rendered, since they can no longer be assumed to be
/// hidden by world generation.
fn clear_adjacent_solid_chunk_margins(&mut self, chunk: ChunkId) {
for coord_axis in CoordAxis::iter() {
for coord_sign in CoordSign::iter() {
if let Some(chunk_id) = self.get_chunk_neighbor(chunk, coord_axis, coord_sign) {
// We only need to clear margins from populated chunks.
let _ = self.clear_solid_chunk_margin(chunk_id);
}
}
}
}

/// Tries to clear the margins of the given chunk. Fails and returns false if the
/// chunk is not populated yet. Succeeds and returns true if the chunk is not Solid, as the
/// chunk is assumed to have empty margins already.
#[must_use]
fn clear_solid_chunk_margin(&mut self, chunk: ChunkId) -> bool {
let dimension = self.layout().dimension;
let Some(Chunk::Populated {
voxels,
surface,
old_surface,
..
}) = self.get_chunk_mut(chunk)
else {
return false;
};

if voxels.is_solid() {
voxels.clear_margin(dimension);
*old_surface = surface.take().or(*old_surface);
for chunk_direction in ChunkDirection::iter() {
margins::update_margin_voxel(
self,
block_update.chunk_id,
block_update.coords,
chunk_direction,
block_update.new_material,
)
}
true
}
Expand Down Expand Up @@ -257,27 +225,6 @@ impl VoxelData {
}
}

/// Replaces all voxels in the margin of this chunk with the "Void" material. This function is a coarse
/// way to ensure that chunks are fully rendered when they need to be, avoiding a rendering bug caused
/// by a voxel's surface failing to render because of a margin being solid.
/// Until margins are fully implemented, any solid chunk produced by world generation should have its
/// margins cleared if it, or any chunk adjacent to it, is edited, since otherwise, the margins could
/// be inaccurate.
pub fn clear_margin(&mut self, dimension: u8) {
let data = self.data_mut(dimension);
let lwm = usize::from(dimension) + 2;
for z in 0..lwm {
for y in 0..lwm {
for x in 0..lwm {
if x == 0 || x == lwm - 1 || y == 0 || y == lwm - 1 || z == 0 || z == lwm - 1 {
// The current coordinates correspond to a margin point. Set it to void.
data[x + y * lwm + z * lwm.pow(2)] = Material::Void;
}
}
}
}
}

pub fn is_solid(&self) -> bool {
match *self {
VoxelData::Dense(_) => false,
Expand Down

0 comments on commit c397286

Please sign in to comment.