221 lines
6.3 KiB
Rust
Executable File
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)),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|