Files
voxtech/rendering/src/chunk/mesh_gen.rs
T
2026-06-04 18:22:57 +02:00

221 lines
6.3 KiB
Rust
Executable File

use bevy_app::{App, Plugin, PostUpdate};
use bevy_ecs::{
component::Component,
entity::Entity,
query::{With, Without},
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Commands, Query, Res, ResMut},
};
use crossbeam::channel::{Receiver, Sender};
use std::{ops::Range, sync::Arc, thread::JoinHandle, time::Instant};
use tracing::debug;
use voxtech_terrain::chunk::{ChunkData, SIZE, UnsafeChunkData};
use crate::{
chunk::renderer::{ChunkRenderer, Face, FaceColour, FaceDirection},
plugin::RenderPluginState,
};
#[derive(Debug, Component)]
pub(crate) struct ChunkMesh {
range: Range<usize>,
last_update: Instant,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Component)]
pub(crate) struct ChunkMeshDirty;
pub(crate) struct ChunkMesher {
pub thread_count: usize,
}
impl Plugin for ChunkMesher {
fn build(&self, app: &mut App) {
app.insert_resource(ChunkMesherState::new(self.thread_count));
app.add_systems(PostUpdate, tag_not_meshed);
app.add_systems(PostUpdate, schedule_dirty.after(tag_not_meshed));
app.add_systems(PostUpdate, submit_meshed.after(schedule_dirty));
}
fn cleanup(&self, app: &mut App) {
app.world_mut().remove_resource::<ChunkMesherState>();
}
}
struct MeshRequest {
time: Instant,
entity: Entity,
voxels: Box<UnsafeChunkData>,
}
struct MeshResult {
time: Instant,
entity: Entity,
faces: Vec<Face>,
}
#[derive(Resource)]
struct ChunkMesherState {
sender: Sender<MeshRequest>,
receiver: Receiver<MeshResult>,
threads: Vec<JoinHandle<()>>,
}
impl ChunkMesherState {
pub fn new(thread_count: usize) -> Self {
let (request_sender, request_receiver) = crossbeam::channel::unbounded();
let request_receiver = Arc::new(request_receiver);
let (result_sender, result_receiver) = crossbeam::channel::unbounded();
let result_sender = Arc::new(result_sender);
let mut threads = Vec::with_capacity(thread_count);
for _ in 0..thread_count {
let sender = result_sender.clone();
let receiver = request_receiver.clone();
threads.push(std::thread::spawn(move || {
loop {
let Ok(MeshRequest {
time,
entity,
voxels,
}) = receiver.recv()
else {
return;
};
debug!("Meshing {entity:?}...");
let now = Instant::now();
let mut faces = vec![];
generate_chunk_mesh::<{ FaceDirection::Front as usize }>(&voxels, &mut faces);
let elapsed = now.elapsed();
debug!("Meshed {entity:?} in {:?} ({} faces)", elapsed, faces.len());
if !sender
.send(MeshResult {
time,
entity,
faces,
})
.is_ok()
{
return;
}
}
}));
}
Self {
sender: request_sender,
receiver: result_receiver,
threads,
}
}
}
fn tag_not_meshed(
mut commands: Commands,
query: Query<Entity, (With<ChunkData>, Without<ChunkMesh>)>,
) {
for entity in query.iter() {
commands.entity(entity).insert((
ChunkMeshDirty,
ChunkMesh {
range: 0..0,
last_update: Instant::now(),
},
));
}
}
fn schedule_dirty(
mut commands: Commands,
state: Res<ChunkMesherState>,
query: Query<(Entity, &mut ChunkData), With<ChunkMeshDirty>>,
) {
for (entity, data) in query.iter() {
let now = Instant::now();
unsafe {
let data = data.as_unsafe();
let mut copy = Box::<UnsafeChunkData>::new_uninit();
std::ptr::copy(data, copy.as_mut_ptr(), 1);
state
.sender
.send(MeshRequest {
time: Instant::now(),
entity,
voxels: copy.assume_init(),
})
.unwrap();
commands.entity(entity).remove::<ChunkMeshDirty>();
}
let elapsed = now.elapsed();
debug!("Created snapshot of {entity:?} in {:?}", elapsed);
}
}
fn submit_meshed(mut commands: Commands, state: Res<ChunkMesherState>) {
if state.receiver.is_empty() {
return;
}
// let mut world = plugin_state.world.lock().unwrap();
// let Some(mut renderer) = world.get_resource_mut::<ChunkRenderer>() else {
// return;
// };
// for MeshResult {
// time,
// entity,
// faces,
// } in state.receiver.try_iter()
// {
// commands.entity(entity).insert(ChunkMesh {
// last_update: time,
// range: renderer.set_mesh(entity, faces),
// });
// }
}
#[inline]
const fn rotate_indices<const FACE: usize>([x, y, z]: [usize; 3]) -> [usize; 3] {
const TOP: usize = FaceDirection::Top as usize;
const BOTTOM: usize = FaceDirection::Bottom as usize;
const LEFT: usize = FaceDirection::Left as usize;
const RIGHT: usize = FaceDirection::Right as usize;
const FRONT: usize = FaceDirection::Front as usize;
const BACK: usize = FaceDirection::Back as usize;
match FACE {
TOP => todo!(),
BOTTOM => todo!(),
LEFT => todo!(),
RIGHT => todo!(),
FRONT => [x, y, z],
BACK => todo!(),
_ => unreachable!(),
}
}
fn generate_chunk_mesh<const FACE: usize>(voxels: &UnsafeChunkData, faces: &mut Vec<Face>) {
for z in 0..SIZE {
for y in 0..SIZE {
for x in 0..SIZE {
if voxels[rotate_indices::<FACE>([x, y, z])] == 0 {
continue;
}
if z != 0 && voxels[rotate_indices::<FACE>([x, y, z - 1])] != 0 {
continue;
}
faces.push(
Face::new()
.with_x(x as _)
.with_y(y as _)
.with_colour(FaceColour::new().with_r(255)),
);
}
}
}
}