use std::marker::PhantomData;
use std::num::NonZero;
use bevy::platform::collections::HashMap;
use bevy::prelude::*;
#[cfg(feature = "cpu")]
pub mod cpu;
#[cfg(feature = "gpu")]
pub mod gpu;
#[derive(SystemSet, Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct ChunkGenSystems;
#[derive(Default, Debug, Clone, Copy, Reflect)]
#[cfg_attr(feature = "gpu", derive(encase::ShaderType))]
struct Vertex {
position: Vec3,
_padding1: f32,
normal: Vec3,
_padding2: f32,
}
#[derive(Default, Debug, Clone, Copy, Reflect)]
#[cfg_attr(feature = "gpu", derive(encase::ShaderType))]
struct Triangle {
vertex_a: u32,
vertex_b: u32,
vertex_c: u32,
}
#[derive(Component, Debug)]
pub struct Chunk<Sampler> {
pub position: IVec3,
_marker: std::marker::PhantomData<Sampler>,
}
#[derive(Component, Debug)]
pub struct ChunkLoader<T> {
pub position: IVec3,
pub loading_radius: u32,
_marker: std::marker::PhantomData<T>,
}
impl<T> ChunkLoader<T> {
pub fn new(loading_radius: u32) -> Self {
Self {
position: IVec3::ZERO,
loading_radius,
_marker: std::marker::PhantomData,
}
}
}
impl<T> Default for ChunkLoader<T> {
fn default() -> Self {
Self {
position: default(),
loading_radius: default(),
_marker: default(),
}
}
}
impl<T> Clone for ChunkLoader<T> {
fn clone(&self) -> Self {
Self {
position: self.position,
loading_radius: self.loading_radius,
_marker: self._marker,
}
}
}
#[derive(Resource, Debug)]
pub struct ChunkMaterial<Sampler, Material: Asset> {
pub material: Handle<Material>,
_marker: std::marker::PhantomData<Sampler>,
}
impl<Sampler, Material: Asset> ChunkMaterial<Sampler, Material> {
pub fn new(material: Handle<Material>) -> Self {
Self {
material,
_marker: std::marker::PhantomData,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ChunkGeneratorRunning {
Run,
Pause,
Stop,
Reset,
}
#[derive(Resource, Debug)]
#[cfg_attr(
feature = "gpu",
derive(bevy::render::extract_resource::ExtractResource)
)]
pub struct ChunkGeneratorSettings<Sampler: Send + Sync + 'static> {
pub running: ChunkGeneratorRunning,
surface_threshold: f32, num_voxels_per_axis: u32,
chunk_size: f32,
max_chunks_per_frame: usize,
num_buffers: usize,
bounds: Option<GenBounds>,
_marker: std::marker::PhantomData<Sampler>,
}
impl<Sampler: Send + Sync + 'static> Clone for ChunkGeneratorSettings<Sampler> {
fn clone(&self) -> Self {
Self {
running: ChunkGeneratorRunning::Run,
surface_threshold: self.surface_threshold,
num_voxels_per_axis: self.num_voxels_per_axis,
chunk_size: self.chunk_size,
max_chunks_per_frame: self.max_chunks_per_frame,
num_buffers: self.num_buffers,
bounds: self.bounds.clone(),
_marker: self._marker,
}
}
}
#[derive(Debug, Clone)]
struct GenBounds {
min: Vec3,
max: Vec3,
}
impl<Sampler: Send + Sync + 'static> ChunkGeneratorSettings<Sampler> {
pub fn new(num_voxels_per_axis: u32, chunk_size: f32) -> Self {
Self {
running: ChunkGeneratorRunning::Run,
surface_threshold: 0.0,
num_voxels_per_axis,
chunk_size,
max_chunks_per_frame: 1,
num_buffers: 3,
bounds: None,
_marker: PhantomData,
}
}
pub fn with_surface_threshold(mut self, surface_threshold: f32) -> Self {
self.surface_threshold = surface_threshold;
self
}
pub fn with_bounds(mut self, min: Vec3, max: Vec3) -> Self {
self.bounds = Some(GenBounds { min, max });
self
}
pub fn with_max_chunks_per_frame(mut self, max_chunks_per_frame: usize) -> Self {
self.max_chunks_per_frame = max_chunks_per_frame;
self
}
pub fn with_num_buffers(mut self, num_buffers: usize) -> Self {
self.num_buffers = num_buffers;
self
}
pub fn stopped(mut self) -> Self {
self.running = ChunkGeneratorRunning::Stop;
self
}
pub fn num_voxels_per_axis(&self) -> u32 {
self.num_voxels_per_axis
}
pub fn num_samples_per_axis(&self) -> u32 {
self.num_voxels_per_axis + 3 }
pub fn max_num_vertices(&self) -> u64 {
self.max_num_triangles() * 3
}
pub fn chunk_size(&self) -> f32 {
self.chunk_size
}
pub fn vertices_buffer_size(&self) -> NonZero<u64> {
(size_of::<Vertex>() as u64 * self.max_num_vertices())
.try_into()
.expect("zero vertices")
}
pub fn max_num_triangles(&self) -> u64 {
(self.num_voxels_per_axis as u64).pow(3) * 5
}
pub fn triangles_buffer_size(&self) -> NonZero<u64> {
(size_of::<Triangle>() as u64 * self.max_num_triangles())
.try_into()
.expect("zero triangles")
}
pub fn voxel_size(&self) -> f32 {
self.chunk_size / self.num_voxels_per_axis as f32
}
pub fn position_to_chunk(&self, position: Vec3) -> IVec3 {
(position / self.chunk_size).floor().as_ivec3()
}
pub fn chunk_to_position(&self, chunk: IVec3) -> Vec3 {
chunk.as_vec3() * self.chunk_size
}
pub fn sample_to_local_position(&self, sample: UVec3) -> Vec3 {
(sample.as_vec3() - Vec3::ONE) / self.num_voxels_per_axis as f32 * self.chunk_size
}
pub fn sample_to_position(&self, sample: UVec3, chunk: IVec3) -> Vec3 {
self.chunk_to_position(chunk) + self.sample_to_local_position(sample)
}
pub fn voxel_to_local_position(&self, voxel: UVec3) -> Vec3 {
voxel.as_vec3() / self.num_voxels_per_axis as f32 * self.chunk_size
}
pub fn voxel_to_position(&self, voxel: UVec3, chunk: IVec3) -> Vec3 {
self.chunk_to_position(chunk) + self.voxel_to_local_position(voxel)
}
fn is_chunk_in_bounds(&self, chunk_position: IVec3) -> bool {
if let Some(bounds) = &self.bounds {
let position = self.chunk_to_position(chunk_position);
position.x >= bounds.min.x
&& position.x <= bounds.max.x
&& position.y >= bounds.min.y
&& position.y <= bounds.max.y
&& position.z >= bounds.min.z
&& position.z <= bounds.max.z
} else {
true
}
}
}
pub fn is_generator_running<Sampler: Send + Sync + 'static>(
settings: Res<ChunkGeneratorSettings<Sampler>>,
) -> bool {
matches!(settings.running, ChunkGeneratorRunning::Run)
}
#[derive(Resource, Debug, Clone)]
pub struct ChunkGeneratorCache<Sampler> {
loaded_chunks: HashMap<IVec3, LoadState>,
chunks_to_load: Vec<IVec3>,
_marker: std::marker::PhantomData<Sampler>,
}
impl<Sampler: Send + Sync + 'static> ChunkGeneratorCache<Sampler> {
pub fn is_chunk_marked(
&self,
settings: &ChunkGeneratorSettings<Sampler>,
chunk_position: IVec3,
) -> bool {
!settings.is_chunk_in_bounds(chunk_position)
|| self.loaded_chunks.contains_key(&chunk_position)
}
pub fn is_chunk_generated(
&self,
settings: &ChunkGeneratorSettings<Sampler>,
chunk_position: IVec3,
) -> bool {
!settings.is_chunk_in_bounds(chunk_position)
|| matches!(
self.loaded_chunks.get(&chunk_position),
Some(LoadState::Finished)
)
}
pub fn is_chunk_with_position_marked(
&self,
settings: &ChunkGeneratorSettings<Sampler>,
position: Vec3,
) -> bool {
self.is_chunk_marked(settings, settings.position_to_chunk(position))
}
pub fn is_chunk_with_position_generated(
&self,
settings: &ChunkGeneratorSettings<Sampler>,
position: Vec3,
) -> bool {
self.is_chunk_generated(settings, settings.position_to_chunk(position))
}
}
impl<Sampler: Send + Sync + 'static> Default for ChunkGeneratorCache<Sampler> {
fn default() -> Self {
Self {
loaded_chunks: default(),
chunks_to_load: default(),
_marker: default(),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum LoadState {
Loading,
Finished,
}
fn update_chunk_loaders<Sampler: Send + Sync + 'static>(
settings: Res<ChunkGeneratorSettings<Sampler>>,
mut chunk_loaders: Query<
(&mut ChunkLoader<Sampler>, &GlobalTransform),
Changed<GlobalTransform>,
>,
) {
for (mut chunk_loader, transform) in chunk_loaders.iter_mut() {
let chunk_position = (transform.translation() / settings.chunk_size)
.floor()
.as_ivec3();
if chunk_loader.position != chunk_position {
chunk_loader.position = chunk_position;
}
}
}
fn queue_chunks<Sampler: Send + Sync + 'static>(
settings: Res<ChunkGeneratorSettings<Sampler>>,
mut cache: ResMut<ChunkGeneratorCache<Sampler>>,
chunk_loaders: Query<&ChunkLoader<Sampler>, Changed<ChunkLoader<Sampler>>>,
) {
for chunk_loader in chunk_loaders.iter() {
let mut load_order = Vec::new();
let loading_radius = chunk_loader.loading_radius as i32;
for x in -loading_radius..=loading_radius {
for y in -loading_radius..=loading_radius {
for z in -loading_radius..=loading_radius {
load_order.push(Vec3::new(x as f32, y as f32, z as f32));
}
}
}
load_order.sort_by(|a, b| {
a.length_squared()
.partial_cmp(&b.length_squared())
.unwrap_or(std::cmp::Ordering::Equal)
});
for offset in load_order {
let chunk_position = chunk_loader.position + offset.as_ivec3();
if !cache.is_chunk_marked(&settings, chunk_position) {
cache
.loaded_chunks
.insert(chunk_position, LoadState::Loading);
cache.chunks_to_load.push(chunk_position);
trace!("Queued chunk for loading: {chunk_position:?}");
}
}
}
}
#[derive(EntityEvent)]
pub struct ChunkGenerated(Entity);