2025-06-13 02:53:19 +02:00

502 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::plugins::environment::systems::voxels::structure::*;
use bevy::asset::RenderAssetUsages;
use bevy::prelude::*;
use bevy::render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
/*pub(crate) fn mesh_chunk(
buffer: &[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
origin: Vec3,
step: f32,
tree: &SparseVoxelOctree,
) -> Mesh {
let mut positions = Vec::<[f32; 3]>::new();
let mut normals = Vec::<[f32; 3]>::new();
let mut uvs = Vec::<[f32; 2]>::new();
let mut indices = Vec::<u32>::new();
// helper safe test for a filled voxel
let filled = |x: i32, y: i32, z: i32| -> bool {
if (0..CHUNK_SIZE).contains(&x)
&& (0..CHUNK_SIZE).contains(&y)
&& (0..CHUNK_SIZE).contains(&z)
{
buffer[x as usize][y as usize][z as usize].is_some()
} else {
let world = origin + Vec3::new(x as f32 * step,
y as f32 * step,
z as f32 * step);
tree.get_voxel_at_world_coords(world).is_some()
}
};
// push a single quad
let mut quad = |base: Vec3,
size: Vec2,
n: Vec3, // face normal (-1|+1 on one axis)
u: Vec3,
v: Vec3|
{
let i0 = positions.len() as u32;
// 4 vertices -----------------------------------------------------------
positions.extend_from_slice(&[
(base).into(),
(base + u * size.x).into(),
(base + u * size.x + v * size.y).into(),
(base + v * size.y).into(),
]);
normals.extend_from_slice(&[[n.x, n.y, n.z]; 4]);
uvs .extend_from_slice(&[[0.0,1.0],[1.0,1.0],[1.0,0.0],[0.0,0.0]]);
// indices -- flip for the negative-side faces -------------------------
if n.x + n.y + n.z >= 0.0 {
// CCW (front-face)
indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]);
} else {
// CW → reverse two vertices so that the winding becomes CCW again
indices.extend_from_slice(&[i0, i0 + 3, i0 + 2, i0 + 2, i0 + 1, i0]);
}
};
//-----------------------------------------------------------------------
// Zfaces
//-----------------------------------------------------------------------
for z in 0..CHUNK_SIZE { // -Z faces (normal Z)
let nz = -1;
let voxel_z = z;
let neighbour_z = voxel_z as i32 + nz;
for y in 0..CHUNK_SIZE {
let mut x = 0;
while x < CHUNK_SIZE {
if filled(x, y, voxel_z) && !filled(x, y, neighbour_z) {
// greedy run along +X
let run_start = x;
let mut run = 1;
while x + run < CHUNK_SIZE
&& filled(x + run, y, voxel_z)
&& !filled(x + run, y, neighbour_z)
{
run += 1;
}
let face_z = voxel_z as f32 * step + if nz == 1 { step } else { 0.0 };
let world_base = origin + Vec3::new(run_start as f32 * step, y as f32 * step, face_z);
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(0.0, 0.0, nz as f32),
Vec3::X,
Vec3::Y);
x += run;
} else {
x += 1;
}
}
}
}
// ------ 2nd pass : +Z faces ---------------------------------------------
for z in 0..CHUNK_SIZE { // +Z faces (normal +Z)
let nz = 1;
let voxel_z = z; // this voxel
let neighbour_z = voxel_z as i32 + nz; // cell “in front of it”
for y in 0..CHUNK_SIZE {
let mut x = 0;
while x < CHUNK_SIZE {
if filled(x, y, voxel_z) && !filled(x, y, neighbour_z) {
let run_start = x;
let mut run = 1;
while x + run < CHUNK_SIZE
&& filled(x + run, y, voxel_z)
&& !filled(x + run, y, neighbour_z)
{ run += 1; }
let world_base = origin
+ Vec3::new(run_start as f32 * step,
y as f32 * step,
(voxel_z + 1) as f32 * step); // +1 !
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(0.0, 0.0, 1.0), // +Z
Vec3::X,
Vec3::Y);
x += run;
} else {
x += 1;
}
}
}
}
// ────────────────────────────────────────────────────────────────────────────
// X faces (-X pass … original code)
// ────────────────────────────────────────────────────────────────────────────
for x in 0..CHUNK_SIZE { // -X faces (normal X)
let nx = -1;
let voxel_x = x;
let neighbour_x = voxel_x as i32 + nx;
for z in 0..CHUNK_SIZE {
let mut y = 0;
while y < CHUNK_SIZE {
if filled(voxel_x, y, z) && !filled(neighbour_x, y, z) {
let run_start = y;
let mut run = 1;
while y + run < CHUNK_SIZE
&& filled(voxel_x, y + run, z)
&& !filled(neighbour_x, y + run, z)
{ run += 1; }
// **fixed x-coordinate: add step when nx == +1**
let face_x = voxel_x as f32 * step + if nx == 1 { step } else { 0.0 };
let world_base = origin
+ Vec3::new(face_x,
run_start as f32 * step,
z as f32 * step);
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(nx as f32, 0.0, 0.0),
Vec3::Y,
Vec3::Z);
y += run;
} else {
y += 1;
}
}
}
}
// ------ 2nd pass : +X faces ---------------------------------------------
for x in 0..CHUNK_SIZE { // +X faces (normal +X)
let nx = 1;
let voxel_x = x;
let neighbour_x = voxel_x as i32 + nx;
for z in 0..CHUNK_SIZE {
let mut y = 0;
while y < CHUNK_SIZE {
if filled(voxel_x, y, z) && !filled(neighbour_x, y, z) {
let run_start = y;
let mut run = 1;
while y + run < CHUNK_SIZE
&& filled(voxel_x, y + run, z)
&& !filled(neighbour_x, y + run, z)
{ run += 1; }
let world_base = origin
+ Vec3::new((voxel_x + 1) as f32 * step, // +1 !
run_start as f32 * step,
z as f32 * step);
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(1.0, 0.0, 0.0), // +X
Vec3::Y,
Vec3::Z);
y += run;
} else {
y += 1;
}
}
}
}
// ────────────────────────────────────────────────────────────────────────────
// Y faces (-Y pass … original code)
// ────────────────────────────────────────────────────────────────────────────
for y in 0..CHUNK_SIZE { // -Y faces (normal Y)
let ny = -1;
let voxel_y = y;
let neighbour_y = voxel_y as i32 + ny;
for x in 0..CHUNK_SIZE {
let mut z = 0;
while z < CHUNK_SIZE {
if filled(x, voxel_y, z) && !filled(x, neighbour_y, z) {
let run_start = z;
let mut run = 1;
while z + run < CHUNK_SIZE
&& filled(x, voxel_y, z + run)
&& !filled(x, neighbour_y, z + run)
{ run += 1; }
// **fixed y-coordinate: add step when ny == +1**
let face_y = voxel_y as f32 * step + if ny == 1 { step } else { 0.0 };
let world_base = origin
+ Vec3::new(x as f32 * step,
face_y,
run_start as f32 * step);
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(0.0, ny as f32, 0.0),
Vec3::Z,
Vec3::X);
z += run;
} else {
z += 1;
}
}
}
}
// ------ 2nd pass : +Y faces ---------------------------------------------
for y in 0..CHUNK_SIZE { // +Y faces (normal +Y)
let ny = 1;
let voxel_y = y;
let neighbour_y = voxel_y as i32 + ny;
for x in 0..CHUNK_SIZE {
let mut z = 0;
while z < CHUNK_SIZE {
if filled(x, voxel_y, z) && !filled(x, neighbour_y, z) {
let run_start = z;
let mut run = 1;
while z + run < CHUNK_SIZE
&& filled(x, voxel_y, z + run)
&& !filled(x, neighbour_y, z + run)
{ run += 1; }
let world_base = origin
+ Vec3::new(x as f32 * step,
(voxel_y + 1) as f32 * step, // +1 !
run_start as f32 * step);
quad(world_base,
Vec2::new(run as f32 * step, step),
Vec3::new(0.0, 1.0, 0.0), // +Y
Vec3::Z,
Vec3::X);
z += run;
} else {
z += 1;
}
}
}
}
//-----------------------------------------------------------------------
// build final mesh
//-----------------------------------------------------------------------
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, VertexAttributeValues::Float32x3(positions));
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, VertexAttributeValues::Float32x3(normals));
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::Float32x2(uvs));
mesh.insert_indices(Indices::U32(indices));
mesh
}*/
pub(crate) fn mesh_chunk(
buffer: &[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
origin: Vec3,
step: f32,
tree: &SparseVoxelOctree,
pool: &mut MeshBufferPool,
) -> Option<Mesh> {
// ────────────────────────────────────────────────────────────────────────────
// Helpers
// ────────────────────────────────────────────────────────────────────────────
const N: usize = CHUNK_SIZE as usize;
const MASK_LEN: usize = N * N;
// Safe voxel query that falls back to the octree for outofchunk requests.
let filled = |x: i32, y: i32, z: i32| -> bool {
if (0..CHUNK_SIZE).contains(&x)
&& (0..CHUNK_SIZE).contains(&y)
&& (0..CHUNK_SIZE).contains(&z)
{
buffer[x as usize][y as usize][z as usize].is_some()
} else {
let world = origin + Vec3::new(x as f32 * step, y as f32 * step, z as f32 * step);
tree.get_voxel_at_world_coords(world).is_some()
}
};
// Push a single quad (4 vertices, 6 indices). `base` is the lowerleft
// corner in world space; `u`/`v` are the tangent vectors (length 1); `size`
// is expressed in world units along those axes; `n` is the face normal.
// Preallocate vertex buffers for better performance, reusing the pool.
pool.clear();
let voxel_count = N * N * N;
pool.positions.reserve(voxel_count * 4);
pool.normals.reserve(voxel_count * 4);
pool.uvs.reserve(voxel_count * 4);
pool.indices.reserve(voxel_count * 6);
let positions = &mut pool.positions;
let normals = &mut pool.normals;
let uvs = &mut pool.uvs;
let indices = &mut pool.indices;
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| {
let i0 = positions.len() as u32;
positions.extend_from_slice(&[
(base).into(),
(base + u * size.x).into(),
(base + u * size.x + v * size.y).into(),
(base + v * size.y).into(),
]);
normals.extend_from_slice(&[[n.x, n.y, n.z]; 4]);
uvs.extend_from_slice(&[[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]]);
if n.x + n.y + n.z >= 0.0 {
indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]);
} else {
// Flip winding for faces with a negative normal component sum so the
// result is still counterclockwise.
indices.extend_from_slice(&[i0, i0 + 3, i0 + 2, i0 + 2, i0 + 1, i0]);
}
};
// ────────────────────────────────────────────────────────────────────────────
// Greedy meshing
// ────────────────────────────────────────────────────────────────────────────
// Axes: 0→X, 1→Y, 2→Z. For each axis we process the negative and positive
// faces (dir = 1 / +1).
for (axis, dir) in [(0, -1), (0, 1), (1, -1), (1, 1), (2, -1), (2, 1)] {
// Mapping of (u,v) axes and their unit vectors in world space.
let (u_axis, v_axis, face_normal, u_vec, v_vec) = match (axis, dir) {
(0, d) => (1, 2, Vec3::new(d as f32, 0.0, 0.0), Vec3::Y, Vec3::Z),
(1, d) => (2, 0, Vec3::new(0.0, d as f32, 0.0), Vec3::Z, Vec3::X),
(2, d) => (0, 1, Vec3::new(0.0, 0.0, d as f32), Vec3::X, Vec3::Y),
_ => unreachable!(),
};
// Iterate over every slice perpendicular to `axis`. Faces can lie on
// the 0…N grid lines (inclusive) because the positiveside faces of the
// last voxel sit at slice N.
for slice in 0..=N {
// Build the face mask for this slice using a fixed-size array to
// avoid heap allocations.
let mut mask = [false; MASK_LEN];
let mut visited = [false; MASK_LEN];
let idx = |u: usize, v: usize| -> usize { u * N + v };
for u in 0..N {
for v in 0..N {
// Translate (u,v,slice) to (x,y,z) voxel coordinates.
let mut cell = [0i32; 3];
let mut neighbor = [0i32; 3];
cell[axis] = slice as i32 + if dir == 1 { -1 } else { 0 };
neighbor[axis] = cell[axis] + dir;
cell[u_axis] = u as i32;
cell[v_axis] = v as i32;
neighbor[u_axis] = u as i32;
neighbor[v_axis] = v as i32;
if filled(cell[0], cell[1], cell[2])
&& !filled(neighbor[0], neighbor[1], neighbor[2])
{
mask[idx(u, v)] = true;
}
}
}
// Greedy merge the mask into maximal rectangles.
for u0 in 0..N {
for v0 in 0..N {
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {
continue;
}
// Determine the rectangle width.
let mut width = 1;
while u0 + width < N
&& mask[idx(u0 + width, v0)]
&& !visited[idx(u0 + width, v0)]
{
width += 1;
}
// Determine the rectangle height.
let mut height = 1;
'h: while v0 + height < N {
for du in 0..width {
if !mask[idx(u0 + du, v0 + height)]
|| visited[idx(u0 + du, v0 + height)]
{
break 'h;
}
}
height += 1;
}
// Mark the rectangle area as visited.
for du in 0..width {
for dv in 0..height {
visited[idx(u0 + du, v0 + dv)] = true;
}
}
// Compute worldspace base corner.
let mut base = origin;
match axis {
0 => {
base.x += step * slice as f32;
base.y += step * u0 as f32;
base.z += step * v0 as f32;
}
1 => {
base.x += step * v0 as f32;
base.y += step * slice as f32;
base.z += step * u0 as f32;
}
2 => {
base.x += step * u0 as f32;
base.y += step * v0 as f32;
base.z += step * slice as f32;
}
_ => unreachable!(),
}
let size = Vec2::new(width as f32 * step, height as f32 * step);
push_quad(base, size, face_normal, u_vec, v_vec);
}
}
}
}
// ────────────────────────────────────────────────────────────────────────────
// Final mesh assembly
// ────────────────────────────────────────────────────────────────────────────
if indices.is_empty() {
return None;
}
let mut mesh = Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),
);
mesh.insert_attribute(
Mesh::ATTRIBUTE_POSITION,
VertexAttributeValues::Float32x3(positions.clone()),
);
mesh.insert_attribute(
Mesh::ATTRIBUTE_NORMAL,
VertexAttributeValues::Float32x3(normals.clone()),
);
mesh.insert_attribute(
Mesh::ATTRIBUTE_UV_0,
VertexAttributeValues::Float32x2(uvs.clone()),
);
mesh.insert_indices(Indices::U32(indices.clone()));
pool.clear();
Some(mesh)
}