Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement accurate margins between adjacent chunks #379

Merged
merged 6 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/shaders/surface-extraction/extract.comp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ bool find_face(out Face info) {
// Flip face around if the neighbor is the solid one
info.inward = self_mat == 0;
info.material = self_mat | neighbor_mat;
// If self or neighbor is a void margin, then no surface should be generated, as any surface
// that would be rendered is the responsibility of the adjacent chunk.
if ((self_mat == 0 && info.voxel[info.axis] == dimension) || (neighbor_mat == 0 && neighbor[info.axis] == -1)) return false;
return (neighbor_mat == 0) != (self_mat == 0);
}

Expand Down
2 changes: 1 addition & 1 deletion client/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ impl Sim {
hit.chunk,
hit.voxel_coords,
hit.face_axis,
hit.face_direction,
hit.face_sign,
)?
} else {
(hit.chunk, hit.voxel_coords)
Expand Down
3 changes: 2 additions & 1 deletion common/src/chunk_collision.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
collision_math::Ray,
math,
node::{ChunkLayout, Coords, VoxelAABB, VoxelData},
node::{ChunkLayout, VoxelAABB, VoxelData},
voxel_math::Coords,
world::Material,
};

Expand Down
27 changes: 11 additions & 16 deletions common/src/chunk_ray_casting.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
collision_math::Ray,
math,
node::{ChunkLayout, CoordAxis, CoordDirection, Coords, VoxelAABB, VoxelData},
node::{ChunkLayout, VoxelAABB, VoxelData},
voxel_math::{CoordAxis, CoordSign, Coords},
world::Material,
};

Expand All @@ -16,7 +17,7 @@ pub struct ChunkCastHit {
pub face_axis: CoordAxis,

/// The direction along `face_axis` corresponding to the outside of the face that was hit.
pub face_direction: CoordDirection,
pub face_sign: CoordSign,
}

/// Performs ray casting against the voxels in the chunk with the given `voxel_data`
Expand Down Expand Up @@ -84,16 +85,16 @@ fn find_face_collision(
// Which side we approach the plane from affects which voxel we want to use for hit detection.
// If exiting a chunk via a chunk boundary, hit detection is handled by a different chunk.
// We also want to retain this face_direction for reporting the hit result later.
let (face_direction, voxel_t) = if math::mip(&ray.direction, &normal) < 0.0 {
let (face_sign, voxel_t) = if math::mip(&ray.direction, &normal) < 0.0 {
if t == 0 {
continue;
}
(CoordDirection::Plus, t - 1)
(CoordSign::Plus, t - 1)
} else {
if t == layout.dimension() {
continue;
}
(CoordDirection::Minus, t)
(CoordSign::Minus, t)
};

let ray_endpoint = ray.ray_point(new_tanh_distance);
Expand Down Expand Up @@ -121,7 +122,7 @@ fn find_face_collision(
tanh_distance: new_tanh_distance,
voxel_coords: Coords(math::tuv_to_xyz(t_axis, [voxel_t, voxel_u, voxel_v])),
face_axis: CoordAxis::try_from(t_axis).unwrap(),
face_direction,
face_sign,
});
}

Expand Down Expand Up @@ -221,13 +222,13 @@ mod tests {
ray: &Ray,
tanh_distance: f32,
expected_face_axis: CoordAxis,
expected_face_direction: CoordDirection,
expected_face_sign: CoordSign,
) {
let hit = chunk_ray_cast_wrapper(ctx, ray, tanh_distance);
let hit = hit.expect("collision expected");
assert_eq!(hit.voxel_coords, Coords([1, 1, 1]));
assert_eq!(hit.face_axis, expected_face_axis);
assert_eq!(hit.face_direction, expected_face_direction);
assert_eq!(hit.face_sign, expected_face_sign);
// sanity_check_normal(ray, &hit.unwrap()); TODO: Check other results
}

Expand All @@ -245,13 +246,7 @@ mod tests {
[0.0, 1.5, 1.5],
[1.5, 1.5, 1.5],
|ray, tanh_distance| {
test_face_collision(
&ctx,
ray,
tanh_distance,
CoordAxis::X,
CoordDirection::Minus,
);
test_face_collision(&ctx, ray, tanh_distance, CoordAxis::X, CoordSign::Minus);
},
);

Expand All @@ -260,7 +255,7 @@ mod tests {
[1.5, 1.5, 3.0],
[1.5, 1.5, 1.5],
|ray, tanh_distance| {
test_face_collision(&ctx, ray, tanh_distance, CoordAxis::Z, CoordDirection::Plus);
test_face_collision(&ctx, ray, tanh_distance, CoordAxis::Z, CoordSign::Plus);
},
);

Expand Down
121 changes: 118 additions & 3 deletions common/src/dodeca.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Tools for processing the geometry of a right dodecahedron

use data::*;
use serde::{Deserialize, Serialize};

use crate::dodeca::data::*;
use crate::voxel_math::ChunkAxisPermutation;

/// Sides of a right dodecahedron
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
Expand Down Expand Up @@ -112,18 +114,45 @@ impl Vertex {
sides_to_vertex()[a as usize][b as usize][c as usize]
}

/// Sides incident to this vertex, in canonical order
/// Sides incident to this vertex, in canonical order.
///
/// This canonical order determines the X, Y, and Z axes of the chunk
/// corresponding to the vertex.
#[inline]
pub fn canonical_sides(self) -> [Side; 3] {
vertex_sides()[self as usize]
}

/// Vertices adjacent to this vertex, opposite the sides in canonical order
/// Vertices adjacent to this vertex in canonical order.
///
/// The canonical order of adjacent vertices is based on the canonical order
/// of sides incident to the vertex, as each of the three adjacent vertices
/// corresponds to one of the three sides. As for which side, when two
/// vertices are adjacent, they share two out of three sides of the
/// dodecahedron. The side they do _not_ share is the side they correspond
/// to.
///
/// Put another way, anything leaving a chunk in the negative-X direction
/// will end up crossing `canonical_sides()[0]`, while anything leaving a
/// chunk in the positive-X direction will end up arriving at
/// `adjacent_vertices()[0]`.
#[inline]
pub fn adjacent_vertices(self) -> [Vertex; 3] {
adjacent_vertices()[self as usize]
}

/// Chunk axes permutations for vertices adjacent to this vertex in
/// canonical order.
///
/// The chunks of two adjacent vertices meet at a plane. When swiching
/// reference frames from one vertex to another, it is necessary to reflect
/// about this plane and then apply the permutation returned by this
/// function.
#[inline]
pub fn chunk_axis_permutations(self) -> &'static [ChunkAxisPermutation; 3] {
&chunk_axis_permutations()[self as usize]
}

/// For each vertex of the cube dual to this dodecahedral vertex, provides an iterator of at
/// most 3 steps to reach the corresponding graph node, and binary coordinates of the vertex in
/// question with respect to the origin vertex of the cube.
Expand Down Expand Up @@ -227,10 +256,12 @@ pub const BOUNDING_SPHERE_RADIUS_F64: f64 = 1.2264568712514068;
pub const BOUNDING_SPHERE_RADIUS: f32 = BOUNDING_SPHERE_RADIUS_F64 as f32;

mod data {
use std::array;
use std::sync::OnceLock;

use crate::dodeca::{Side, Vertex, SIDE_COUNT, VERTEX_COUNT};
use crate::math;
use crate::voxel_math::ChunkAxisPermutation;

/// Whether two sides share an edge
pub fn adjacent() -> &'static [[bool; SIDE_COUNT]; SIDE_COUNT] {
Expand Down Expand Up @@ -334,6 +365,39 @@ mod data {
})
}

// Which transformations have to be done after a reflection to switch reference frames from one vertex
// to one of its adjacent vertices (ordered similarly to ADJACENT_VERTICES)
Ralith marked this conversation as resolved.
Show resolved Hide resolved
pub fn chunk_axis_permutations() -> &'static [[ChunkAxisPermutation; 3]; VERTEX_COUNT] {
static LOCK: OnceLock<[[ChunkAxisPermutation; 3]; VERTEX_COUNT]> = OnceLock::new();
LOCK.get_or_init(|| {
array::from_fn(|vertex| {
array::from_fn(|result_index| {
let mut test_sides = vertex_sides()[vertex];
// Keep modifying the result_index'th element of test_sides until its three elements are all
// adjacent to a single vertex (determined using `Vertex::from_sides`).
for side in Side::iter() {
if side == vertex_sides()[vertex][result_index] {
continue;
}
test_sides[result_index] = side;
let Some(adjacent_vertex) =
Vertex::from_sides(test_sides[0], test_sides[1], test_sides[2])
else {
continue;
};
// Compare the natural permutation of sides after a reflection from `vertex` to `adjacent_vertex`
// to the canonical permutation of the sides for `adjacent_vertex`.
return ChunkAxisPermutation::from_permutation(
test_sides,
adjacent_vertex.canonical_sides(),
);
}
panic!("No suitable vertex found");
})
})
})
}

/// Transform that converts from cube-centric coordinates to dodeca-centric coordinates
pub fn dual_to_node_f64() -> &'static [na::Matrix4<f64>; VERTEX_COUNT] {
static LOCK: OnceLock<[na::Matrix4<f64>; VERTEX_COUNT]> = OnceLock::new();
Expand Down Expand Up @@ -484,6 +548,57 @@ mod tests {
}
}

#[test]
fn adjacent_chunk_axis_permutations() {
// Assumptions for this test to be valid. If any assertions in this section fail, the test itself
// needs to be modified
assert_eq!(Vertex::A.canonical_sides(), [Side::A, Side::B, Side::C]);
assert_eq!(Vertex::B.canonical_sides(), [Side::A, Side::B, Side::E]);

assert_eq!(Vertex::F.canonical_sides(), [Side::B, Side::C, Side::F]);
assert_eq!(Vertex::J.canonical_sides(), [Side::C, Side::F, Side::H]);

// Test cases

// Variables with name vertex_?_canonical_sides_reflected refer to the canonical sides
// of a particular vertex after a reflection that moves it to another vertex.
// For instance, vertex_a_canonical_sides_reflected is similar to Vertex::A.canonical_sides(),
// but one of the sides is changed to match Vertex B, but the order of the other two sides is left alone.
let vertex_a_canonical_sides_reflected = [Side::A, Side::B, Side::E];
let vertex_b_canonical_sides_reflected = [Side::A, Side::B, Side::C];
assert_eq!(
Vertex::A.chunk_axis_permutations()[2],
ChunkAxisPermutation::from_permutation(
vertex_a_canonical_sides_reflected,
Vertex::B.canonical_sides()
)
);
assert_eq!(
Vertex::B.chunk_axis_permutations()[2],
ChunkAxisPermutation::from_permutation(
vertex_b_canonical_sides_reflected,
Vertex::A.canonical_sides()
)
);

let vertex_f_canonical_sides_reflected = [Side::H, Side::C, Side::F];
let vertex_j_canonical_sides_reflected = [Side::C, Side::F, Side::B];
assert_eq!(
Vertex::F.chunk_axis_permutations()[0],
ChunkAxisPermutation::from_permutation(
vertex_f_canonical_sides_reflected,
Vertex::J.canonical_sides()
)
);
assert_eq!(
Vertex::J.chunk_axis_permutations()[2],
ChunkAxisPermutation::from_permutation(
vertex_j_canonical_sides_reflected,
Vertex::F.canonical_sides()
)
);
}

#[test]
fn side_is_facing() {
for side in Side::iter() {
Expand Down
3 changes: 2 additions & 1 deletion common/src/graph_collision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ mod tests {
collision_math::Ray,
dodeca::{self, Side, Vertex},
graph::{Graph, NodeId},
node::{populate_fresh_nodes, Coords, VoxelData},
node::{populate_fresh_nodes, VoxelData},
proto::Position,
traversal::{ensure_nearby, nearby_nodes},
voxel_math::Coords,
world::Material,
};

Expand Down
7 changes: 4 additions & 3 deletions common/src/graph_ray_casting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use crate::{
chunk_ray_casting::chunk_ray_cast,
collision_math::Ray,
graph::Graph,
node::{Chunk, ChunkId, CoordAxis, CoordDirection, Coords},
node::{Chunk, ChunkId},
proto::Position,
traversal::RayTraverser,
voxel_math::{CoordAxis, CoordSign, Coords},
};

/// Performs ray casting against the voxels in the `DualGraph`
Expand Down Expand Up @@ -54,7 +55,7 @@ pub fn ray_cast(
chunk,
voxel_coords: hit.voxel_coords,
face_axis: hit.face_axis,
face_direction: hit.face_direction,
face_sign: hit.face_sign,
})
});
}
Expand All @@ -81,5 +82,5 @@ pub struct GraphCastHit {
pub face_axis: CoordAxis,

/// The direction along `face_axis` corresponding to the outside of the face that was hit.
pub face_direction: CoordDirection,
pub face_sign: CoordSign,
}
2 changes: 2 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ pub mod graph_collision;
mod graph_entities;
pub mod graph_ray_casting;
pub mod lru_slab;
mod margins;
pub mod math;
pub mod node;
mod plane;
pub mod proto;
mod sim_config;
pub mod terraingen;
pub mod traversal;
pub mod voxel_math;
pub mod world;
pub mod worldgen;

Expand Down
Loading
Loading