bevy_bones3 0.4.0

A voxel world storage and mesh generation plugin for Bevy.
Documentation
use std::ops::Mul;
use std::time::Duration;

use bevy::prelude::*;
use bevy::time::common_conditions::on_timer;
use bevy_bones3::prelude::*;
use bones3_remesh::ecs::components::RemeshChunk;
use bones3_remesh::ecs::resources::ChunkMaterialList;
use bones3_remesh::mesh::block_model::{BlockOcclusion, BlockShape};
use bones3_remesh::vertex_data::{CubeModelBuilder, ShapeBuilder};
use bones3_remesh::Bones3RemeshPlugin;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            Bones3CorePlugin::<BlockState>::default(),
            Bones3RemeshPlugin::<BlockState>::default(),
        ))
        .add_systems(Startup, init)
        .add_systems(
            Update,
            update_wave.run_if(on_timer(Duration::from_secs_f32(0.25))),
        )
        .run();
}

#[derive(Debug, Default, Reflect, Clone, Copy)]
enum BlockState {
    #[default]
    Empty,
    Solid,
}

impl BlockShape for BlockState {
    fn write_shape(&self, shape_builder: &mut ShapeBuilder) {
        match self {
            BlockState::Empty => {},
            BlockState::Solid => {
                shape_builder.add_shape(
                    CubeModelBuilder::new().set_occlusion(shape_builder.get_occlusion()),
                    0,
                );
            },
        }
    }

    fn check_occlude(&self, _: BlockOcclusion, _other: Self) -> bool {
        match self {
            BlockState::Empty => false,
            BlockState::Solid => true,
        }
    }
}

fn init(
    mut materials: ResMut<Assets<StandardMaterial>>,
    mut chunk_materials: ResMut<ChunkMaterialList>,
    mut commands: VoxelCommands,
) {
    commands
        .commands()
        .spawn(DirectionalLightBundle {
            transform: Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.5, 0.25, 0.0)),
            directional_light: DirectionalLight {
                illuminance: 50000.0,
                ..default()
            },
            ..default()
        })
        .commands()
        .spawn(Camera3dBundle {
            transform: Transform::from_xyz(75.0, 45.0, 75.0).looking_at(Vec3::ZERO, Vec3::Y),
            ..default()
        })
        .commands()
        .insert_resource(AmbientLight {
            color:      Color::WHITE,
            brightness: 2.5,
        });

    let stone_handle = materials.add(Color::GRAY.into());
    chunk_materials.add_material(stone_handle, None);

    let mut world = commands.spawn_world(SpatialBundle::default());

    let chunk_radius = IVec3::new(3, 0, 3);
    for chunk_coords in Region::from_points(-chunk_radius, chunk_radius).iter() {
        world
            .spawn_chunk(
                chunk_coords,
                (
                    SpatialBundle {
                        transform: Transform::from_translation(chunk_coords.as_vec3() * 16.0),
                        ..default()
                    },
                    VoxelStorage::<BlockState>::default(),
                ),
            )
            .unwrap();
    }
}

fn update_wave(
    time: Res<Time>,
    mut chunks: Query<(&mut VoxelStorage<BlockState>, &VoxelChunk, Entity)>,
    mut commands: Commands,
) {
    for (_, _, chunk_id) in chunks.iter() {
        commands.entity(chunk_id).insert(RemeshChunk);
    }

    let secs = time.elapsed_seconds();

    chunks
        .par_iter_mut()
        .for_each_mut(|(mut storage, chunk_meta, _)| {
            let chunk_coords = chunk_meta.chunk_coords();
            for pos in Region::CHUNK.shift(chunk_coords * 16).iter() {
                let distance = pos
                    .as_vec3()
                    .mul(Vec3::new(1.0, 0.0, 1.0))
                    .distance(Vec3::new(7.5, 0.0, 7.5));

                let shape = ((distance * distance) / 128.0 - secs).sin() * 4.0 + 7.0;
                let vert = pos.y as f32;

                if vert < shape.floor() {
                    storage.set_block(pos, BlockState::Solid);
                } else {
                    storage.set_block(pos, BlockState::Empty);
                }
            }
        });
}