diff --git a/src/scene/hook.rs b/src/hook.rs similarity index 100% rename from src/scene/hook.rs rename to src/hook.rs diff --git a/src/lib.rs b/src/lib.rs index 13e97dd..d7cdbe6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,13 +53,20 @@ use bevy::{ mod loader; mod scene; +mod hook; +mod systems; +mod parse; +mod mesh; +mod voxel; + +#[cfg(test)] +mod tests; + +pub use scene::{VoxelLayer, VoxelScene, VoxelSceneBundle, VoxelSceneHookBundle, VoxelModelInstance, VoxelModel}; +pub use hook::VoxelSceneHook; pub use loader::VoxLoaderSettings; #[doc(inline)] use loader::VoxSceneLoader; -use scene::VoxelModel; -pub use scene::{VoxelLayer, VoxelScene, VoxelSceneBundle, VoxelSceneHook, VoxelSceneHookBundle}; -mod mesh; -mod voxel; /// The core plugin adding functionality for loading `.vox` files. /// @@ -73,7 +80,7 @@ impl Plugin for VoxScenePlugin { .register_asset_loader(VoxSceneLoader) .add_systems( SpawnScene, - (scene::systems::spawn_vox_scenes, scene::systems::run_hooks).chain(), + (systems::spawn_vox_scenes, systems::run_hooks).chain(), ); } } diff --git a/src/loader.rs b/src/loader.rs index 1039bd4..77a5c5e 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -1,4 +1,4 @@ -use crate::voxel::VoxelData; +use crate::{voxel::VoxelData, parse::{parse_xform_node, find_subasset_names}}; use anyhow::anyhow; use bevy::{ asset::{io::Reader, AssetLoader, AsyncReadExt, Handle, LoadContext}, @@ -13,7 +13,7 @@ use bevy::{ use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::scene::{self, LayerInfo, VoxelModel, VoxelNode, VoxelScene}; +use crate::scene::{LayerInfo, VoxelModel, VoxelNode, VoxelScene}; /// An asset loader capable of loading models in `.vox` files as usable [`bevy::render::mesh::Mesh`]es. /// @@ -392,7 +392,7 @@ impl VoxSceneLoader { // Scene graph let root = - scene::parse::parse_xform_node(&file.scenes, &file.scenes[0], None, load_context); + parse_xform_node(&file.scenes, &file.scenes[0], None, load_context); let layers: Vec = file .layers .iter() @@ -402,7 +402,7 @@ impl VoxSceneLoader { }) .collect(); let mut subasset_by_name: HashMap = HashMap::new(); - scene::parse::find_subasset_names(&mut subasset_by_name, &root); + find_subasset_names(&mut subasset_by_name, &root); for (subscene_name, node) in subasset_by_name { load_context.labeled_asset_scope(subscene_name.clone(), |_| VoxelScene { diff --git a/src/scene/parse.rs b/src/parse.rs similarity index 99% rename from src/scene/parse.rs rename to src/parse.rs index 2209628..24571d0 100644 --- a/src/scene/parse.rs +++ b/src/parse.rs @@ -6,7 +6,7 @@ use bevy::{ }; use dot_vox::{Frame, SceneNode}; -use super::VoxelNode; +use crate::scene::VoxelNode; pub(crate) fn parse_xform_node( graph: &Vec, diff --git a/src/scene/mod.rs b/src/scene.rs similarity index 64% rename from src/scene/mod.rs rename to src/scene.rs index a006df1..7d6ad13 100644 --- a/src/scene/mod.rs +++ b/src/scene.rs @@ -1,8 +1,3 @@ -mod hook; -pub(crate) mod parse; -pub(super) mod systems; -#[cfg(test)] -mod tests; use bevy::{ asset::{Asset, Handle}, ecs::{bundle::Bundle, component::Component}, @@ -12,13 +7,14 @@ use bevy::{ render::{mesh::Mesh, view::Visibility}, transform::components::Transform, }; -pub use hook::VoxelSceneHook; -use crate::voxel::VoxelData; +use crate::{voxel::VoxelData, hook::VoxelSceneHook}; /// A component bundle for spawning Voxel Scenes. /// /// The root of the spawned scene will be the entity that has this bundle. +/// In addition to the standard components bevy uses to organise and render pbr meshes, +/// spawned entities will also have [`VoxelLayer`] and [`VoxelModelInstance`] components added. /// ```no_run /// # use bevy::prelude::*; /// # use bevy_vox_scene::VoxelSceneBundle; @@ -55,7 +51,37 @@ pub struct VoxelSceneBundle { /// A component bundle for spawning Voxel Scenes, with a [`VoxelSceneHook`]. /// /// The root of the spawned scene will be the entity that has this bundle. -/// The [`VoxelSceneHook`] allows you to easily modify Entities deep within the scene hierarchy. +/// In addition to the standard components bevy uses to organise and render pbr meshes, +/// spawned entities will also have [`VoxelLayer`] and [`VoxelModelInstance`] components added. +/// The [`VoxelSceneHook`] allows you to modify entities spawned within the hierarchy. +/// A typical use-case would be adding additional components based on an entity's [`bevy::core::Name`] +/// or [`VoxelLayer`]. +/// ``` +/// # use bevy::{prelude::*, app::AppExit, utils::HashSet}; +/// # use bevy_vox_scene::{VoxelSceneHook, VoxelSceneHookBundle}; +/// # +/// # #[derive(Component)] +/// # struct Fish; +/// # +/// # fn setup( +/// # mut commands: Commands, +/// # assets: Res, +/// # ) { +/// VoxelSceneHookBundle { +/// scene: assets.load("study.vox#tank"), +/// hook: VoxelSceneHook::new(move |entity, commands| { +/// let Some(name) = entity.get::() else { return }; +/// match name.as_str() { +/// "tank/goldfish" | "tank/tetra" => { +/// commands.insert(Fish); +/// } +/// _ => {}, +/// } +/// }), +/// ..default() +/// }; +/// # } +/// ``` #[derive(Bundle, Default)] pub struct VoxelSceneHookBundle { /// A handle to a [`VoxelScene`], typically loaded from a ".vox" file via the [`bevy::asset::AssetServer`]. @@ -81,18 +107,22 @@ pub struct VoxelScene { #[derive(Debug, Clone, Default)] pub(crate) struct VoxelNode { - name: Option, - transform: Mat4, - children: Vec, - model: Option>, - is_hidden: bool, - layer_id: u32, + pub name: Option, + pub transform: Mat4, + pub children: Vec, + pub model: Option>, + pub is_hidden: bool, + pub layer_id: u32, } +/// Asset containing the voxel data for a model, as well as handles to the mesh derived from that data and the material #[derive(Asset, TypePath)] -pub(crate) struct VoxelModel { +pub struct VoxelModel { + /// The voxel data used to generate the mesh pub data: VoxelData, + /// Handle to the model's mesh pub mesh: Handle, + /// Handle to the model's material pub material: Handle, } @@ -102,8 +132,11 @@ pub(crate) struct LayerInfo { pub is_hidden: bool, } +/// Component wrapping the handle to the [`VoxelModel`] +/// +/// When the scene is spawned this component gets added to entities with a voxel mesh. #[derive(Component)] -pub struct VoxelModelInstance(Handle); +pub struct VoxelModelInstance(pub Handle); /// A component specifying which layer the Entity belongs to, with an optional name. /// diff --git a/src/scene/systems.rs b/src/systems.rs similarity index 97% rename from src/scene/systems.rs rename to src/systems.rs index 70a3307..ab87449 100644 --- a/src/scene/systems.rs +++ b/src/systems.rs @@ -15,7 +15,7 @@ use bevy::{ transform::components::Transform, }; -use super::{VoxelLayer, VoxelModel, VoxelModelInstance, VoxelNode, VoxelScene}; +use crate::scene::{VoxelLayer, VoxelModel, VoxelModelInstance, VoxelNode, VoxelScene}; pub(crate) fn spawn_vox_scenes( mut commands: Commands, diff --git a/src/scene/tests.rs b/src/tests.rs similarity index 92% rename from src/scene/tests.rs rename to src/tests.rs index 2065ad2..4c2a9ee 100644 --- a/src/scene/tests.rs +++ b/src/tests.rs @@ -1,12 +1,12 @@ use super::*; -use crate::VoxScenePlugin; +use crate::{VoxScenePlugin, scene::VoxelModelInstance}; use bevy::{ app::App, - asset::{AssetApp, AssetPlugin, AssetServer, Assets, LoadState}, + asset::{AssetApp, AssetPlugin, AssetServer, Assets, LoadState, Handle}, core::Name, hierarchy::Children, - render::texture::ImagePlugin, - MinimalPlugins, + render::{texture::ImagePlugin, mesh::Mesh}, + MinimalPlugins, pbr::StandardMaterial, utils::hashbrown::HashSet, }; #[async_std::test] @@ -191,6 +191,14 @@ async fn test_spawn_system() { 3, "But only 3 of the voxel nodes are named" ); + let mut instance_query = app.world.query::<&VoxelModelInstance>(); + assert_eq!( + instance_query.iter(&app.world).len(), + 4, + "4 model instances spawned in this scene slice" + ); + let models: HashSet> = instance_query.iter(&app.world).map(|c| c.0.clone()).collect(); + assert_eq!(models.len(), 2, "Instances point to 2 unique models"); assert_eq!( app.world .get::(entity) diff --git a/src/voxel.rs b/src/voxel.rs index 5780b7e..2673329 100644 --- a/src/voxel.rs +++ b/src/voxel.rs @@ -6,12 +6,12 @@ use ndshape::Shape; // trait implementation rules requires the use of a newtype to allow meshing. #[derive(Clone, Copy, PartialEq, Eq)] -pub(crate) struct Voxel { - pub(crate) index: u8, - pub(crate) is_translucent: bool, +pub struct Voxel { + pub index: u8, + pub is_translucent: bool, } -pub(crate) const EMPTY_VOXEL: Voxel = Voxel { +pub const EMPTY_VOXEL: Voxel = Voxel { index: 255, is_translucent: false, }; @@ -34,7 +34,7 @@ impl MergeVoxel for Voxel { } } -pub(crate) struct VoxelData { +pub struct VoxelData { pub shape: RuntimeShape, pub voxels: Vec, }