From f6ba49824b0580c939dfbef3da60f4271c29addb Mon Sep 17 00:00:00 2001 From: Patrick Owen Date: Sun, 21 Apr 2024 01:23:47 -0400 Subject: [PATCH] Fix up margins after block updates --- common/src/margins.rs | 117 +++++++++++++++++++++++++++++++++++++++++- common/src/node.rs | 69 +++---------------------- 2 files changed, 123 insertions(+), 63 deletions(-) diff --git a/common/src/margins.rs b/common/src/margins.rs index 25402743..1329d09f 100644 --- a/common/src/margins.rs +++ b/common/src/margins.rs @@ -1,8 +1,10 @@ use crate::{ dodeca::Vertex, + graph::Graph, math, - node::VoxelData, + node::{Chunk, ChunkId, VoxelData}, voxel_math::{ChunkAxisPermutation, ChunkDirection, CoordAxis, CoordSign, Coords}, + world::Material, }; /// Updates the margins of both `voxels` and `neighbor_voxels` at the side they meet at. @@ -79,6 +81,52 @@ 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 { + 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_axis_permutation = neighbor_axis_permutation(chunk.vertex, direction); + let mut neighbor_coords = coords; + neighbor_coords[direction.axis] = margin_coord; + neighbor_coords = neighbor_axis_permutation * neighbor_coords; + + neighbor_voxels.data_mut(dimension)[neighbor_coords.to_index(dimension)] = material; + *neighbor_old_surface = neighbor_surface.take().or(*neighbor_old_surface); +} + fn neighbor_axis_permutation(vertex: Vertex, direction: ChunkDirection) -> ChunkAxisPermutation { match direction.sign { CoordSign::Plus => vertex.chunk_axis_permutations()[direction.axis as usize], @@ -153,7 +201,7 @@ impl std::ops::Mul for ChunkAxisPermutation { #[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::*; @@ -221,4 +269,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 + ); + } } diff --git a/common/src/node.rs b/common/src/node.rs index 20852644..45045fec 100644 --- a/common/src/node.rs +++ b/common/src/node.rs @@ -150,9 +150,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)) @@ -162,43 +159,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 } @@ -261,27 +229,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,