diff --git a/crates/renderer/src/meshing.rs b/crates/renderer/src/meshing.rs index 12c29c0..25f8611 100644 --- a/crates/renderer/src/meshing.rs +++ b/crates/renderer/src/meshing.rs @@ -167,9 +167,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |y, x0, z0, w, h| { - let (xmin, xmax) = (coord(x0) - 0.5, coord(x0 + w) - 0.5); - let (zmin, zmax) = (coord(z0) - 0.5, coord(z0 + h) - 0.5); - let yp = coord(y) + 0.5; + let (xmin, xmax) = (coord(x0), coord(x0 + w)); + let (zmin, zmax) = (coord(z0), coord(z0 + h)); + let yp = coord(y + 1); [ [xmin, yp, zmax], [xmax, yp, zmax], @@ -194,9 +194,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |y, x0, z0, w, h| { - let (xmin, xmax) = (coord(x0) - 0.5, coord(x0 + w) - 0.5); - let (zmin, zmax) = (coord(z0) - 0.5, coord(z0 + h) - 0.5); - let yp = coord(y) - 0.5; + let (xmin, xmax) = (coord(x0), coord(x0 + w)); + let (zmin, zmax) = (coord(z0), coord(z0 + h)); + let yp = coord(y); [ [xmin, yp, zmin], [xmax, yp, zmin], @@ -221,9 +221,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |x, z0, y0, w, h| { - let (zmin, zmax) = (coord(z0) - 0.5, coord(z0 + w) - 0.5); - let (ymin, ymax) = (coord(y0) - 0.5, coord(y0 + h) - 0.5); - let xp = coord(x) + 0.5; + let (zmin, zmax) = (coord(z0), coord(z0 + w)); + let (ymin, ymax) = (coord(y0), coord(y0 + h)); + let xp = coord(x + 1); [ [xp, ymin, zmax], [xp, ymin, zmin], @@ -248,9 +248,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |x, z0, y0, w, h| { - let (zmin, zmax) = (coord(z0) - 0.5, coord(z0 + w) - 0.5); - let (ymin, ymax) = (coord(y0) - 0.5, coord(y0 + h) - 0.5); - let xp = coord(x) - 0.5; + let (zmin, zmax) = (coord(z0), coord(z0 + w)); + let (ymin, ymax) = (coord(y0), coord(y0 + h)); + let xp = coord(x); [ [xp, ymin, zmin], [xp, ymin, zmax], @@ -275,9 +275,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |z, x0, y0, w, h| { - let (xmin, xmax) = (coord(x0) - 0.5, coord(x0 + w) - 0.5); - let (ymin, ymax) = (coord(y0) - 0.5, coord(y0 + h) - 0.5); - let zp = coord(z) + 0.5; + let (xmin, xmax) = (coord(x0), coord(x0 + w)); + let (ymin, ymax) = (coord(y0), coord(y0 + h)); + let zp = coord(z + 1); [ [xmin, ymin, zp], [xmax, ymin, zp], @@ -302,9 +302,9 @@ pub fn generate_mesh(chunk: &Chunk, neighbors: &Neighbors) -> (Vec, Vec< }) }, |z, x0, y0, w, h| { - let (xmin, xmax) = (coord(x0) - 0.5, coord(x0 + w) - 0.5); - let (ymin, ymax) = (coord(y0) - 0.5, coord(y0 + h) - 0.5); - let zp = coord(z) - 0.5; + let (xmin, xmax) = (coord(x0), coord(x0 + w)); + let (ymin, ymax) = (coord(y0), coord(y0 + h)); + let zp = coord(z); [ [xmax, ymin, zp], [xmin, ymin, zp], diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index 3f12ba6..cd870cc 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -687,7 +687,7 @@ impl Renderer { fn cull_to_frustum(&self, mvp: glam::Mat4) -> (Vec<&GpuMesh>, usize) { let frustum = Frustum::from_view_proj(mvp); - // A chunk spans CHUNK_SIZE blocks on each axis. The mesher centres block i on [i - 0.5, i + 0.5], so a chunk's box runs [offset - 0.5, offset + CHUNK_SIZE - 0.5]; the extent below is added to that shifted minimum corner. + // A chunk spans CHUNK_SIZE blocks on each axis. Block i spans [i, i+1), so a chunk's geometry runs [offset, offset + CHUNK_SIZE]. #[expect( clippy::cast_precision_loss, reason = "CHUNK_SIZE is 32, exactly representable as f32" @@ -700,7 +700,7 @@ impl Renderer { .values() .filter(|mesh| { // Reject the chunk when its world-space bounding box falls entirely outside the frustum. - let box_min = glam::Vec3::from(mesh.world_offset) - glam::Vec3::splat(0.5); + let box_min = glam::Vec3::from(mesh.world_offset); let visible = frustum.intersects_aabb(box_min, box_min + chunk_extent); if !visible { culled += 1; diff --git a/crates/renderer/src/tests/meshing.rs b/crates/renderer/src/tests/meshing.rs index ec9c60b..f799110 100644 --- a/crates/renderer/src/tests/meshing.rs +++ b/crates/renderer/src/tests/meshing.rs @@ -132,7 +132,7 @@ fn single_block_quads_carry_their_own_face_normal() { let (vertices, _) = generate_mesh(&chunk, &Neighbors::default()); // The constant axis and plane coordinate of each face of a block at (5, 5, 5), indexed by packed normal. Deducing the expected direction from the geometry rather than from the emission order keeps the assertion valid if the passes are reordered. - let expected: [(usize, f32); 6] = [(0, 5.5), (0, 4.5), (1, 5.5), (1, 4.5), (2, 5.5), (2, 4.5)]; + let expected: [(usize, f32); 6] = [(0, 6.0), (0, 5.0), (1, 6.0), (1, 5.0), (2, 6.0), (2, 5.0)]; let mut seen = [false; 6]; for quad in vertices.chunks_exact(4) { diff --git a/docs/meshing.md b/docs/meshing.md index 81b3174..7b1b336 100644 --- a/docs/meshing.md +++ b/docs/meshing.md @@ -26,9 +26,9 @@ The half-scale voxel grid ([ADR-0002](adr/0002-half-scale-voxel-grid.md)) makes ### Vertex extents: a shared convention -The mesher centres block `i` on the interval `[i - 0.5, i + 0.5]`, hence the `± 0.5` offsets throughout the quad emitters. A chunk's geometry therefore spans `[offset - 0.5, offset + CHUNK_SIZE - 0.5]`, **not** `[offset, offset + CHUNK_SIZE]`. +The mesher places block `i` on the interval `[i, i + 1)`: its near face sits at `coord(i)` and its far face at `coord(i + 1)`. This matches the `floor()`-based coordinate-to-block mapping used by the rest of the engine (`position.floor()` yields the block index), so a raycast or cursor highlight that floors a hit point resolves to the same cell the mesher drew. -That half-block shift is duplicated in the frustum cull, which builds each chunk's bounding box from the same shifted minimum corner. Nothing in the type system ties the two together: if the mesher's extents ever change, the cull's box must change with them, or chunks will be culled while still partially on screen (or drawn while fully off it). The coupling is noted at both sites; treat it as an invariant of this file pair. +A chunk's geometry therefore spans `[offset, offset + CHUNK_SIZE]`. The frustum cull builds each chunk's bounding box from the same origin; if the mesher's extents ever change, the cull's box must change with them, or chunks will be culled while still partially on screen (or drawn while fully off it). The coupling is noted at both sites; treat it as an invariant of this file pair. ## Neighbour-aware boundary culling