diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e3f7e58 Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 9538435..9b694a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ bevy = { version = "0.11.0", default-features = false, features = [ "bevy_render", "bevy_asset", ] } -dot_vox = "4.1.0" +dot_vox = "5.1.1" ndshape = "0.3.0" block-mesh = "0.2.0" ndcopy = "0.3.0" @@ -23,7 +23,14 @@ anyhow = "1.0.38" [dev-dependencies] bevy = { version = "0.11.0" } +bevy-inspector-egui = "0.19.0" +bevy_egui = "0.21.0" [[example]] name = "basic" path = "examples/basic.rs" + + +[[example]] +name = "boy" +path = "examples/boy.rs" diff --git a/README.md b/README.md index 70f78bb..d5f90cd 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,9 @@ Take a look in the `examples/` directory for a complete working example. ## Acknowledgements This asset loader is powered by the awesome [`block-mesh-rs`](https://github.com/bonsairobo/block-mesh-rs) crate. + + +# ChangeLog +- Add more information read from vox. such as relationship between vox models in scene. see `examples/boy.rs` +- in example boy use Tab can toggle the faces. +- boy.vox copy from [teravit](https://teravit.app/en/contents/index.html?category=content-en&id=5854&platform=null%23#) \ No newline at end of file diff --git a/assets/boy.vox b/assets/boy.vox new file mode 100644 index 0000000..695c5d7 Binary files /dev/null and b/assets/boy.vox differ diff --git a/examples/boy.rs b/examples/boy.rs new file mode 100644 index 0000000..17d1f76 --- /dev/null +++ b/examples/boy.rs @@ -0,0 +1,174 @@ +use bevy::prelude::*; +use bevy_egui::EguiPlugin; +use bevy_inspector_egui::quick::WorldInspectorPlugin; +use bevy_vox_mesh::{vox_scene_info::VoxSceneInfo, VoxMeshPlugin}; +use std::f32::consts::PI; + +fn main() { + App::default() + .add_plugins(DefaultPlugins) + .add_plugins(EguiPlugin) + .add_plugins(WorldInspectorPlugin::new()) + .add_plugins(VoxMeshPlugin::default()) + .register_type::() + .insert_resource(BoyMate { + handle: None, + mate: None, + }) + .insert_resource(BoyEntity { boy_entity: None }) + .insert_resource(FaceNow::default()) + .add_systems(Startup, setup) + .add_systems(Update, (load_mate, load_boy, toggle_faces)) + .run(); +} + +#[derive(Debug, Resource)] +pub struct BoyEntity { + pub boy_entity: Option, +} + +#[derive(Debug, Resource, Clone)] +pub struct BoyMate { + pub handle: Option>, + pub mate: Option, +} + +#[derive(Debug, Resource, Clone)] +pub struct FaceNow { + pub now_face: &'static str, +} + +impl Default for FaceNow { + fn default() -> Self { + Self { now_face: "face0" } + } +} + +fn toggle_faces( + keyboard_input: Res>, + mut query: Query<(Entity, &Name, &mut Visibility)>, + mut face_now: ResMut, +) { + let faces = vec!["face0", "face1", "face2", "face3"]; + if keyboard_input.just_pressed(KeyCode::Tab) { + if let Some(index) = faces.iter().position(|&x| x == face_now.now_face) { + let next_index = if index == faces.len() - 1 { + 0 + } else { + index + 1 + }; + let next_face = faces[next_index]; + for (_, name, mut visibility) in query.iter_mut() { + if faces.contains(&name.as_str()) { + if name.as_str() == next_face { + *visibility.as_mut() = Visibility::Inherited; + } else { + *visibility.as_mut() = Visibility::Hidden; + } + } + } + face_now.now_face = next_face; + } + } +} + +fn load_boy( + mut commands: Commands, + boy_mate: Res, + mut boy_entity: ResMut, + assets: Res, + mut stdmats: ResMut>, + mut mesh_assets: ResMut>, +) { + if let Some(_entity) = boy_entity.boy_entity { + // 这里可以进行其他的处理? + } else { + if let Some(mate_data) = boy_mate.mate.clone() { + if mate_data.all_loaded("boy.vox", mesh_assets.as_ref(), assets.as_ref()) { + // println!("这里生成模型的详情"); + let boy = mate_data.to_entity( + "boy.vox", + &mut commands, + assets.as_ref(), + stdmats.add(Color::rgb(1., 1., 1.).into()), + &mut mesh_assets, + ); + commands.entity(boy).insert(( + Visibility::Inherited, + ComputedVisibility::HIDDEN, + GlobalTransform::IDENTITY, + Transform { + translation: Vec3 { + x: 0.0, + y: 1.0 / 40. * 40., // height is 80 so the button is scale*80/2 + z: 0.0, + }, + scale: Vec3 { + x: 1.0 / 40., + y: 1.0 / 40., + z: 1.0 / 40., + }, + ..Default::default() + } * Transform::from_rotation(Quat::from_axis_angle(Vec3::Y, PI)), + )); + boy_entity.boy_entity = Some(boy); + } + } + } +} + +fn load_mate(mate_assets: Res>, mut boy_mate: ResMut) { + if let Some(handle) = boy_mate.handle.clone() { + match boy_mate.mate { + Some(_) => {} + None => { + if let Some(mate) = mate_assets.get(&handle) { + boy_mate.mate = Some(mate.clone()); + } + } + } + } +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut stdmats: ResMut>, + mut boy_mate: ResMut, + assets: Res, +) { + let mate_data_handle: Handle = assets.load("boy.vox#scene"); + boy_mate.handle = Some(mate_data_handle); + + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..Default::default() + }); + + commands.spawn(PointLightBundle { + point_light: PointLight { + intensity: 1500.0, + shadows_enabled: true, + ..default() + }, + transform: Transform::from_xyz(4.0, 8.0, 4.0), + ..default() + }); + + commands.spawn(PbrBundle { + mesh: meshes.add(Mesh::from(shape::Plane { + subdivisions: 2, + size: 5.0, + })), + material: stdmats.add(Color::rgb(0.3, 0.5, 0.3).into()), + ..Default::default() + }); + + // commands.spawn(PbrBundle { + // transform: Transform::from_scale((0.01, 0.01, 0.01).into()) + // * Transform::from_rotation(Quat::from_axis_angle(Vec3::Y, PI)), + // mesh: assets.load("boy.vox#model5"), + // material: stdmats.add(Color::rgb(1., 1., 1.).into()), + // ..Default::default() + // }); +} diff --git a/src/lib.rs b/src/lib.rs index f9fb4d6..faf8e94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,9 @@ use block_mesh::{QuadCoordinateConfig, RIGHT_HANDED_Y_UP_CONFIG}; mod loader; #[doc(inline)] use loader::VoxLoader; +use vox_scene_info::VoxSceneInfo; +pub mod vox_scene_info; mod mesh; mod voxel; @@ -61,6 +63,7 @@ impl Default for VoxMeshPlugin { impl Plugin for VoxMeshPlugin { fn build(&self, app: &mut App) { + app.add_asset::(); app.add_asset_loader(VoxLoader { config: self.config.clone(), v_flip_face: self.v_flip_faces, diff --git a/src/loader.rs b/src/loader.rs index 0756f03..24eec2f 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -2,6 +2,9 @@ use anyhow::{anyhow, Error}; use bevy::asset::{AssetLoader, LoadContext, LoadedAsset}; use block_mesh::QuadCoordinateConfig; +use crate::vox_scene_info::VoxSceneInfo; + + /// An asset loader capable of loading models in `.vox` files as usable [`bevy::render::mesh::Mesh`]es. /// /// The meshes generated by this asset loader only use standard [`bevy::render::mesh::Mesh`] attributes for easier compatibility with shaders. @@ -20,7 +23,7 @@ impl AssetLoader for VoxLoader { load_context: &'a mut LoadContext, ) -> bevy::utils::BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { - self.process_vox_file(bytes, load_context)?; + self.process_vox_file(bytes, load_context).await?; Ok(()) }) } @@ -31,10 +34,10 @@ impl AssetLoader for VoxLoader { } impl VoxLoader { - fn process_vox_file<'a>( + async fn process_vox_file<'a, 'b>( &self, bytes: &'a [u8], - load_context: &'a mut LoadContext, + load_context: &'a mut LoadContext<'b>, ) -> Result<(), Error> { let file = match dot_vox::load_bytes(bytes) { Ok(data) => data, @@ -44,14 +47,16 @@ impl VoxLoader { let palette: Vec<[f32; 4]> = file .palette .iter() - .map(|color| color.to_le_bytes().map(|byte| byte as f32 / u8::MAX as f32)) + .map(|color| { + let color_rgba: [u8; 4] = color.into(); + color_rgba.map(|byte| byte as f32 / u8::MAX as f32) + }) .collect(); for (index, model) in file.models.iter().enumerate() { let (shape, buffer) = crate::voxel::load_from_model(model); let mesh = crate::mesh::mesh_model(shape, &buffer, &palette, &self.config, self.v_flip_face); - match index { 0 => { load_context.set_default_asset(LoadedAsset::new(mesh.clone())); @@ -64,6 +69,10 @@ impl VoxLoader { } } } + load_context.set_labeled_asset( + &format!("scene"), + LoadedAsset::new(VoxSceneInfo::new(file.scenes, file.layers)), + ); Ok(()) } diff --git a/src/vox_scene_info.rs b/src/vox_scene_info.rs new file mode 100644 index 0000000..ba636d5 --- /dev/null +++ b/src/vox_scene_info.rs @@ -0,0 +1,213 @@ +use bevy::{ + prelude::{ + AssetServer, Assets, BuildChildren, Commands, Component, ComputedVisibility, Entity, + GlobalTransform, Handle, Mesh, Name, PbrBundle, StandardMaterial, Transform, Visibility, + }, + reflect::{TypePath, TypeUuid}, + utils::HashMap, +}; +use dot_vox::{Layer, SceneNode}; + +#[derive(Debug, TypeUuid, TypePath, Clone)] +#[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"] + +pub struct VoxSceneInfo { + pub scenes: Vec, + pub layers: Vec, + // layers hidden status + pub layer_map: HashMap, +} + +#[derive(Debug, Clone, Component)] +pub struct LayerData(pub u32); + +impl VoxSceneInfo { + pub fn new(scenes: Vec, layers: Vec) -> Self { + Self { + scenes: scenes, + layers: layers.clone(), + layer_map: layers + .clone() + .into_iter() + .map(|x| { + let number = x + .attributes + .get("_name") + .map(|s| s.parse().unwrap()) + .unwrap_or(u32::MAX); + let hidden = x + .attributes + .get("_hidden") + .map(|s| s == "1") + .unwrap_or(false); + (number, hidden) + }) + .collect(), + } + } + + pub fn assert_all_loaded( + &self, + base_id: &'static str, + mesh_assets: &Assets, + asset_server: &AssetServer, + ) -> bool { + for node in self.scenes.iter() { + match node { + SceneNode::Shape { + attributes: _, + models, + } => { + for shape in models { + let key = if shape.model_id == 0 { + format!("{}", base_id,) + } else { + format!("{}#model{}", base_id, shape.model_id) + }; + let handle: Handle = asset_server.get_handle(key); + if mesh_assets.get(&handle).is_none() { + return false; + } + } + } + _ => {} + } + } + return true; + } + + pub fn to_entity( + &self, + base_id: &'static str, + commands: &mut Commands, + asset_server: &AssetServer, + material_handle: Handle, + mesh_assets: &mut Assets, + ) -> Entity { + // 这里要根据 sence的定义生成一系列的entity + let root_scene = &self.scenes[0]; + let ret = deal_scene_node( + base_id, + commands, + asset_server, + root_scene, + &self.scenes, + material_handle.clone(), + mesh_assets, + &self.layer_map, + ); + ret[0] + } +} + +fn deal_scene_node( + base_id: &'static str, + commands: &mut Commands, + asset_server: &AssetServer, + scene_node: &SceneNode, + scenes_tree: &Vec, + material_handle: Handle, + mesh_assets: &mut Assets, + layer_map: &HashMap, +) -> Vec { + let mut result: Vec = Vec::new(); + match scene_node { + SceneNode::Transform { + attributes, + frames, + child, + layer_id, + } => { + // 标记一下当前数据? + let mut node = commands.spawn(LayerData(layer_id.clone())); + if let Some(name) = attributes.get("_name") { + node.insert(Name::new(name.to_owned())); + } + for frame in frames.iter() { + // TODO: Support Other Types + if let Some(pos) = frame.position() { + node.insert(Transform::from_xyz( + pos.x as f32, + pos.y as f32, + pos.z as f32, + )); + } + } + + let children = deal_scene_node( + base_id, + node.commands(), + asset_server, + &scenes_tree[child.clone() as usize], + scenes_tree, + material_handle.clone(), + mesh_assets, + layer_map, + ); + node.push_children(&children); + + let visibilty = if let Some(hidden) = layer_map.get(layer_id) { + if *hidden { + Visibility::Hidden + } else { + Visibility::Inherited + } + } else { + Visibility::Inherited + }; + node.insert(( + visibilty, + ComputedVisibility::HIDDEN, + GlobalTransform::IDENTITY, + )); + result.push(node.id()); + } + SceneNode::Group { + attributes: _, + children, + } => { + // 获取一组数据 + for ch_key in children { + let children = deal_scene_node( + base_id, + commands, + asset_server, + &scenes_tree[ch_key.clone() as usize], + scenes_tree, + material_handle.clone(), + mesh_assets, + layer_map, + ); + result.extend(children); + } + } + SceneNode::Shape { + attributes: _, + models, + } => { + // 这里生成单个的entity + for shape in models { + let key = if shape.model_id == 0 { + format!("{}", base_id,) + } else { + format!("{}#model{}", base_id, shape.model_id) + }; + let handle: Handle = asset_server.get_handle(key.clone()); + println!("{}-{:?}", key, asset_server.get_load_state(handle.clone())); + if let Some(mesh) = mesh_assets.get(&handle) { + result.push( + commands + .spawn(PbrBundle { + transform: Transform::IDENTITY, + mesh: mesh_assets.add(mesh.clone()), + material: material_handle.clone(), + ..Default::default() + }) + .id(), + ); + } + } + } + } + result +}