Skip to content

Commit

Permalink
Refactor the way instances are created and configured
Browse files Browse the repository at this point in the history
  • Loading branch information
splashdust committed Mar 10, 2024
1 parent 6909f9b commit cb2be55
Show file tree
Hide file tree
Showing 11 changed files with 494 additions and 472 deletions.
56 changes: 24 additions & 32 deletions examples/bombs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,37 @@ use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*, utils::HashMap};
use bevy_voxel_world::prelude::*;
use noise::{HybridMulti, NoiseFn, Perlin};
use std::time::Duration;

#[derive(Clone, Default)]
#[derive(Resource, Clone, Default)]
struct MainWorld;

#[derive(Clone, Default)]
impl VoxelWorldConfig for MainWorld {
fn spawning_distance(&self) -> u32 {
15
}

fn voxel_lookup_delegate(&self) -> VoxelLookupDelegate {
Box::new(move |_chunk_pos| get_voxel_fn())
}
}

#[derive(Resource, Clone, Default)]
struct SecondWorld;

impl VoxelWorldConfig for SecondWorld {
fn spawning_distance(&self) -> u32 {
10
}

fn voxel_lookup_delegate(&self) -> VoxelLookupDelegate {
Box::new(move |_chunk_pos| get_voxel_fn_2())
}
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(VoxelWorldPlugin::<MainWorld>::default())
.add_plugins(VoxelWorldPlugin::<SecondWorld>::default())
.add_plugins(VoxelWorldPlugin::with_config(MainWorld))
.add_plugins(VoxelWorldPlugin::with_config(SecondWorld))
.add_systems(Startup, setup)
.add_systems(Update, move_camera)
.add_systems(Update, explosion)
Expand All @@ -26,28 +45,6 @@ struct ExplosionTimeout {
}

fn setup(mut commands: Commands) {
commands.insert_resource(VoxelWorldConfiguration::<MainWorld> {
// This is the spawn distance (in 32 meter chunks), centered around the camera.
spawning_distance: 15,

// Here we supply a closure that returns another closure that returns a voxel value for a given position.
// This may seem a bit convoluted, but it allows us to capture data in a sendable closure to be sent off
// to a differrent thread for the meshing process. A new closure is fetched for each chunk.
voxel_lookup_delegate: Box::new(move |_chunk_pos| get_voxel_fn()), // `get_voxel_fn` is defined below
..Default::default()
});

commands.insert_resource(VoxelWorldConfiguration::<SecondWorld> {
// This is the spawn distance (in 32 meter chunks), centered around the camera.
spawning_distance: 10,

// Here we supply a closure that returns another closure that returns a voxel value for a given position.
// This may seem a bit convoluted, but it allows us to capture data in a sendable closure to be sent off
// to a differrent thread for the meshing process. A new closure is fetched for each chunk.
voxel_lookup_delegate: Box::new(move |_chunk_pos| get_voxel_fn_2()), // `get_voxel_fn` is defined below
..Default::default()
});

commands.spawn(ExplosionTimeout {
timer: Timer::from_seconds(0.25, TimerMode::Repeating),
});
Expand Down Expand Up @@ -147,11 +144,6 @@ fn get_voxel_fn_2() -> Box<dyn FnMut(IVec3) -> WorldVoxel + Send + Sync> {
// Then we return this boxed closure that captures the noise and the cache
// This will get sent off to a separate thread for meshing by bevy_voxel_world
Box::new(move |pos: IVec3| {
// Sea level
if pos.y < 1 {
return WorldVoxel::Solid(3);
}

let [x, y, z] = pos.as_dvec3().to_array();

let sample = match cache.get(&(pos.x, pos.z)) {
Expand Down
4 changes: 2 additions & 2 deletions examples/set_voxel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ fn setup(mut commands: Commands) {
// Ambient light
commands.insert_resource(AmbientLight {
color: Color::rgb(0.98, 0.95, 0.82),
brightness: 1.0,
brightness: 1000.0,
});
}

fn set_solid_voxel(mut voxel_world: VoxelWorld) {
fn set_solid_voxel(mut voxel_world: VoxelWorld<DefaultWorld>) {
// Generate some random values
let size = 10;
let mut rng = rand::thread_rng();
Expand Down
36 changes: 20 additions & 16 deletions examples/textures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,35 @@ const SNOWY_BRICK: u8 = 0;
const FULL_BRICK: u8 = 1;
const GRASS: u8 = 2;

#[derive(Resource, Clone, Default)]
struct MyMainWorld;

impl VoxelWorldConfig for MyMainWorld {
fn texture_index_mapper(&self) -> Arc<dyn Fn(u8) -> [u32; 3] + Send + Sync> {
Arc::new(|vox_mat: u8| match vox_mat {
SNOWY_BRICK => [0, 1, 2],
FULL_BRICK => [2, 2, 2],
GRASS | _ => [3, 3, 3],
})
}
}

fn main() {
App::new()
.add_plugins(DefaultPlugins)
// We can specify a custom texture when initializing the plugin.
// This should just be a path to an image in your assets folder.
.add_plugins(VoxelWorldPlugin::default().with_voxel_texture(
"example_voxel_texture.png",
4, // number of indexes in the texture
))
.add_plugins(
VoxelWorldPlugin::with_config(MyMainWorld).with_voxel_texture(
"example_voxel_texture.png",
4, // number of indexes in the texture
),
)
.add_systems(Startup, (setup, create_voxel_scene).chain())
.run();
}

fn setup(mut commands: Commands) {
commands.insert_resource(VoxelWorldConfiguration {
// To specify how the texture map to different kind of voxels we add this mapping callback
// For each material type, we specify the texture coordinates for the top, side and bottom faces.
texture_index_mapper: Arc::new(|vox_mat: u8| match vox_mat {
SNOWY_BRICK => [0, 1, 2],
FULL_BRICK => [2, 2, 2],
GRASS | _ => [3, 3, 3],
}),
..Default::default()
});

// Camera
commands.spawn((
Camera3dBundle {
Expand All @@ -53,7 +57,7 @@ fn setup(mut commands: Commands) {
});
}

fn create_voxel_scene(mut voxel_world: VoxelWorld) {
fn create_voxel_scene(mut voxel_world: VoxelWorld<MyMainWorld>) {
// Then we can use the `u8` consts to specify the type of voxel

// 20 by 20 floor
Expand Down
26 changes: 13 additions & 13 deletions src/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ pub(crate) type VoxelArray = [WorldVoxel; PaddedChunkShape::SIZE as usize];

#[derive(Component)]
#[component(storage = "SparseSet")]
pub(crate) struct ChunkThread<I: Clone>(pub Task<ChunkTask<I>>, pub IVec3, PhantomData<I>);
pub(crate) struct ChunkThread<C>(pub Task<ChunkTask<C>>, pub IVec3, PhantomData<C>);

impl<I> ChunkThread<I>
impl<C> ChunkThread<C>
where
I: Clone + Send + Sync + 'static,
C: Send + Sync + 'static,
{
pub fn new(task: Task<ChunkTask<I>>, pos: IVec3) -> Self {
pub fn new(task: Task<ChunkTask<C>>, pos: IVec3) -> Self {
Self(task, pos, PhantomData)
}
}
Expand Down Expand Up @@ -131,13 +131,13 @@ impl Default for ChunkData {

/// A marker component for chunks, with some helpful data
#[derive(Component, Clone)]
pub struct Chunk<I> {
pub struct Chunk<C> {
pub position: IVec3,
pub entity: Entity,
_marker: PhantomData<I>,
_marker: PhantomData<C>,
}

impl<I> Chunk<I> {
impl<C> Chunk<C> {
pub fn new(position: IVec3, entity: Entity) -> Self {
Self {
position,
Expand All @@ -146,7 +146,7 @@ impl<I> Chunk<I> {
}
}

pub fn from(chunk: &Chunk<I>) -> Self {
pub fn from(chunk: &Chunk<C>) -> Self {
Self {
position: chunk.position,
entity: chunk.entity,
Expand All @@ -163,16 +163,16 @@ impl<I> Chunk<I> {

/// Holds all data needed to generate and mesh a chunk
#[derive(Component)]
pub(crate) struct ChunkTask<I: Clone> {
pub(crate) struct ChunkTask<C> {
pub position: IVec3,
pub chunk_data: ChunkData,
pub modified_voxels: ModifiedVoxels<I>,
pub modified_voxels: ModifiedVoxels<C>,
pub mesh: Option<Mesh>,
_marker: PhantomData<I>,
_marker: PhantomData<C>,
}

impl<I: Clone + Send + Sync + 'static> ChunkTask<I> {
pub fn new(entity: Entity, position: IVec3, modified_voxels: ModifiedVoxels<I>) -> Self {
impl<C: Send + Sync + 'static> ChunkTask<C> {
pub fn new(entity: Entity, position: IVec3, modified_voxels: ModifiedVoxels<C>) -> Self {
Self {
position,
chunk_data: ChunkData::with_entity(entity),
Expand Down
22 changes: 11 additions & 11 deletions src/chunk_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ use crate::{
/// The chunks also exist as entities that can be queried in the ECS,
/// but having this map in addition allows for faster spatial lookups
#[derive(Resource)]
pub struct ChunkMap<I> {
pub struct ChunkMap<C> {
map: Arc<RwLock<HashMap<IVec3, chunk::ChunkData>>>,
_marker: PhantomData<I>,
_marker: PhantomData<C>,
}

impl<I: Send + Sync + 'static> ChunkMap<I> {
impl<C: Send + Sync + 'static> ChunkMap<C> {
pub fn get(
position: &IVec3,
read_lock: &RwLockReadGuard<HashMap<IVec3, chunk::ChunkData>>,
Expand All @@ -44,9 +44,9 @@ impl<I: Send + Sync + 'static> ChunkMap<I> {

pub(crate) fn apply_buffers(
&self,
insert_buffer: &mut ChunkMapInsertBuffer<I>,
update_buffer: &mut ChunkMapUpdateBuffer<I>,
remove_buffer: &mut ChunkMapRemoveBuffer<I>,
insert_buffer: &mut ChunkMapInsertBuffer<C>,
update_buffer: &mut ChunkMapUpdateBuffer<C>,
remove_buffer: &mut ChunkMapRemoveBuffer<C>,
ev_chunk_will_spawn: &mut EventWriter<ChunkWillSpawn>,
) {
if insert_buffer.is_empty() && update_buffer.is_empty() && remove_buffer.is_empty() {
Expand Down Expand Up @@ -85,7 +85,7 @@ impl<I: Send + Sync + 'static> ChunkMap<I> {
}
}

impl<I> Default for ChunkMap<I> {
impl<C> Default for ChunkMap<C> {
fn default() -> Self {
Self {
map: Arc::new(RwLock::new(HashMap::with_capacity(1000))),
Expand All @@ -95,13 +95,13 @@ impl<I> Default for ChunkMap<I> {
}

#[derive(Resource, Deref, DerefMut, Default, Debug)]
pub(crate) struct ChunkMapInsertBuffer<I>(#[deref] Vec<(IVec3, chunk::ChunkData)>, PhantomData<I>);
pub(crate) struct ChunkMapInsertBuffer<C>(#[deref] Vec<(IVec3, chunk::ChunkData)>, PhantomData<C>);

#[derive(Resource, Deref, DerefMut, Default)]
pub(crate) struct ChunkMapUpdateBuffer<I>(
pub(crate) struct ChunkMapUpdateBuffer<C>(
#[deref] Vec<(IVec3, chunk::ChunkData, ChunkWillSpawn)>,
PhantomData<I>,
PhantomData<C>,
);

#[derive(Resource, Deref, DerefMut, Default)]
pub(crate) struct ChunkMapRemoveBuffer<I>(#[deref] Vec<IVec3>, PhantomData<I>);
pub(crate) struct ChunkMapRemoveBuffer<C>(#[deref] Vec<IVec3>, PhantomData<C>);
77 changes: 41 additions & 36 deletions src/configuration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,71 +31,76 @@ pub enum ChunkSpawnStrategy {
Close,
}

/// Configuration resource for bevy_voxel_world
#[derive(Resource)]
pub struct VoxelWorldConfiguration<I> {
/// `bevy_voxel_world` configuation structs need to implement this trait
pub trait VoxelWorldConfig: Resource + Default + Clone {
/// Distance in chunks to spawn chunks around the camera
pub spawning_distance: u32,
fn spawning_distance(&self) -> u32 {
10
}

/// Strategy for despawning chunks
pub chunk_despawn_strategy: ChunkDespawnStrategy,
fn chunk_despawn_strategy(&self) -> ChunkDespawnStrategy {
ChunkDespawnStrategy::default()
}

/// Strategy for spawning chunks
/// This is only used if the despawn strategy is `FarAway`
pub chunk_spawn_strategy: ChunkSpawnStrategy,
fn chunk_spawn_strategy(&self) -> ChunkSpawnStrategy {
ChunkSpawnStrategy::default()
}

/// Maximum number of chunks that can get queued for spawning in a given frame.
/// In some scenarios, reducing this number can help with performance, due to less
/// thread contention.
pub max_spawn_per_frame: usize,
fn max_spawn_per_frame(&self) -> usize {
10000
}

/// Number of rays to cast when spawning chunks. Higher values will result in more
/// chunks being spawned per frame, but will also increase cpu load, and can lead to
/// thread contention.
pub spawning_rays: usize,
fn spawning_rays(&self) -> usize {
100
}

/// How far outside of the viewports spawning rays should get cast. Higher values will
/// will reduce the likelyhood of chunks popping in, but will also increase cpu load.
pub spawning_ray_margin: u32,
fn spawning_ray_margin(&self) -> u32 {
25
}

/// Debugging aids
pub debug_draw_chunks: bool,
fn debug_draw_chunks(&self) -> bool {
false
}

/// A function that maps voxel materials to texture coordinates.
/// The input is the material index, and the output is a slice of three indexes into an array texture.
/// The three values correspond to the top, sides and bottom of the voxel. For example,
/// if the slice is `[1,2,2]`, the top will use texture index 1 and the sides and bottom will use texture
/// index 2.
pub texture_index_mapper: Arc<dyn Fn(u8) -> [u32; 3] + Send + Sync>,
fn texture_index_mapper(&self) -> Arc<dyn Fn(u8) -> [u32; 3] + Send + Sync> {
Arc::new(|mat| match mat {
0 => [0, 0, 0],
1 => [1, 1, 1],
2 => [2, 2, 2],
3 => [3, 3, 3],
_ => [0, 0, 0],
})
}

/// A function that returns a function that returns true if a voxel exists at the given position
/// The delegate will be called every time a new chunk needs to be computed. The delegate should
/// return a function that can be called to check if a voxel exists at a given position. This function
/// needs to be thread-safe, since chunk computation happens on a separate thread.
pub voxel_lookup_delegate: VoxelLookupDelegate,

pub _marker: std::marker::PhantomData<I>,
}

impl<I> Default for VoxelWorldConfiguration<I> {
fn default() -> Self {
Self {
spawning_distance: 10,
chunk_despawn_strategy: ChunkDespawnStrategy::default(),
chunk_spawn_strategy: ChunkSpawnStrategy::default(),
debug_draw_chunks: true,
max_spawn_per_frame: 10000,
spawning_rays: 100,
spawning_ray_margin: 25,
texture_index_mapper: Arc::new(|mat| match mat {
0 => [0, 0, 0],
1 => [1, 1, 1],
2 => [2, 2, 2],
3 => [3, 3, 3],
_ => [0, 0, 0],
}),
voxel_lookup_delegate: Box::new(|_| Box::new(|_| WorldVoxel::Unset)),
_marker: std::marker::PhantomData,
}
fn voxel_lookup_delegate(&self) -> VoxelLookupDelegate {
Box::new(|_| Box::new(|_| WorldVoxel::Unset))
}
}

#[derive(Resource, Clone, Default)]
pub struct DefaultWorld;

impl DefaultWorld {}

impl VoxelWorldConfig for DefaultWorld {}
Loading

0 comments on commit cb2be55

Please sign in to comment.