diff --git a/Cargo.lock b/Cargo.lock index b8ec6ef..74f53a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3706,6 +3706,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", + "studio-bvh", "wasm-bindgen", "web-sys", ] diff --git a/studio/Cargo.toml b/studio/Cargo.toml index 1b8bc33..9899469 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -13,6 +13,7 @@ name = "studio-standalone" path = "src/main.rs" [dependencies] +anyhow = { workspace = true } bevy-inspector-egui = "0.21.0" bevy_mod_raycast = "0.16.0" bevy_rapier3d = "0.23.0" @@ -24,6 +25,7 @@ parking_lot = "0.12.1" serde = { version = "1.0.192", optional = true } serde-wasm-bindgen = { version = "0.6.1", optional = true } serde_json = { version = "1.0.108", optional = true } +studio-bvh = { path = "../studio-bvh" } wasm-bindgen = "0.2.84" web-sys = { version = "0.3.65", optional = true, features = [ "CustomEvent", diff --git a/studio/src/bvh/error.rs b/studio/src/bvh/error.rs new file mode 100644 index 0000000..e373e43 --- /dev/null +++ b/studio/src/bvh/error.rs @@ -0,0 +1,38 @@ +use core::fmt; + +use anyhow::anyhow; +use bevy::prelude::Deref; + +#[derive(Debug, Deref)] +pub struct Error(pub anyhow::Error); + +impl From for Error { + fn from(value: anyhow::Error) -> Self { + Self(value) + } +} + +pub trait Anyhow { + fn anyhow(self) -> anyhow::Result; +} + +impl Anyhow for Result +where + E: std::error::Error, +{ + fn anyhow(self) -> anyhow::Result { + self.map_err(|err| anyhow!("{err}")) + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.0.source() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", **self) + } +} diff --git a/studio/src/bvh/loader.rs b/studio/src/bvh/loader.rs new file mode 100644 index 0000000..82efd4e --- /dev/null +++ b/studio/src/bvh/loader.rs @@ -0,0 +1,59 @@ +use bevy::{ + asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}, + prelude::*, + utils::{BoxedFuture, HashMap}, +}; + +use super::{ + error::{Anyhow, Error}, + Bvh, BvhJoint, +}; + +#[derive(Default)] +pub struct BvhLoader; + +impl AssetLoader for BvhLoader { + type Asset = Bvh; + type Settings = (); + type Error = Error; + + fn load<'a>( + &'a self, + reader: &'a mut Reader, + _settings: &'a Self::Settings, + cx: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + let mut buf = vec![]; + reader.read_to_end(&mut buf).await.anyhow()?; + + let bvh = studio_bvh::from_bytes(&buf).anyhow()?; + + let mut joints = Vec::>::new(); + let mut named_joints = HashMap::new(); + + for (idx, joint) in bvh.joints().enumerate() { + let label = format!("Joint{idx}"); + let asset = BvhJoint { + transform: Transform::from_translation((*joint.offset()).into()), + parent: joint.parent_index().map(|idx| joints[idx].clone()), + }; + + let handle = cx.add_labeled_asset(label, asset); + joints.push(handle.clone()); + + let name = String::from_utf8_lossy(joint.name()).to_string(); + named_joints.insert(name, handle); + } + + Ok(Bvh { + joints, + named_joints, + }) + }) + } + + fn extensions(&self) -> &[&str] { + &["bvh"] + } +} diff --git a/studio/src/bvh/mod.rs b/studio/src/bvh/mod.rs new file mode 100644 index 0000000..1938d7f --- /dev/null +++ b/studio/src/bvh/mod.rs @@ -0,0 +1,29 @@ +use bevy::{prelude::*, utils::HashMap}; + +mod error; +mod loader; + +use self::loader::BvhLoader; + +pub struct BvhPlugin; + +impl Plugin for BvhPlugin { + fn build(&self, app: &mut App) { + app.init_asset::() + .init_asset::() + .register_asset_loader(BvhLoader); + } +} + +#[derive(Asset, TypePath, Debug)] +pub struct Bvh { + pub joints: Vec>, + pub named_joints: HashMap>, +} + +#[derive(Asset, TypePath, Debug, Clone)] +pub struct BvhJoint { + pub parent: Option>, + /// Local transform + pub transform: Transform, +} diff --git a/studio/src/lib.rs b/studio/src/lib.rs index 0fad6f8..0b1a7a5 100644 --- a/studio/src/lib.rs +++ b/studio/src/lib.rs @@ -6,6 +6,7 @@ use bevy_mod_picking::DefaultPickingPlugins; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; +mod bvh; mod camera_controller; mod debug; mod design_system; @@ -20,6 +21,7 @@ mod viewport; mod world_grid; use crate::{ + bvh::BvhPlugin, camera_controller::CameraControllerPlugin, debug::DebugPlugin, input::StudioInputPlugin, @@ -134,6 +136,7 @@ pub fn start_viewer( SkyboxPlugin, UiLayerPlugin, WorldGridPlugin, + BvhPlugin, // TODO: Alphabetize these plugins )) .add_systems( Startup, diff --git a/studio/src/scene.rs b/studio/src/scene.rs index 4bbe849..0965549 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -17,6 +17,7 @@ use bevy::{ use wasm_bindgen::prelude::wasm_bindgen; use crate::{ + bvh::Bvh, camera_controller::{CameraController, CameraControllerBundle}, debug::DebugPoint, math::{Combinable, Extrema}, @@ -38,6 +39,7 @@ impl Plugin for ScenePlugin { .add_systems( Update, ( + spawn_test_bvh, // TODO init_scene_loading, tick_scene_loading.run_if(in_state(SceneState::Loading)), tick_scene_spawning.run_if(in_state(SceneState::Spawning)), @@ -113,10 +115,14 @@ pub struct SkeletalJoint; #[derive(Component)] pub struct SceneRoot; -fn initialize(mut cmd: Commands) { +#[derive(Resource, Deref, DerefMut)] +struct TestBvh(Handle); // TODO + +fn initialize(mut cmd: Commands, r_assets: Res) { // Camera, graphics settings let focus = Vec3::ZERO; let camera_xform = Transform::from_xyz(-2.5, 4.5, 5.0).looking_at(focus, Vec3::Y); + cmd.spawn(( Name::new("Main Camera"), MainCamera, @@ -142,6 +148,24 @@ fn initialize(mut cmd: Commands) { }, Fxaa::default(), )); + + // TODO + cmd.insert_resource(TestBvh(r_assets.load("mocap/scott.bvh"))); +} + +// TODO +fn spawn_test_bvh( + mut _cmd: Commands, + mut er_bvh_events: EventReader>, + r_bvh: Res, + ra_bvh: Res>, +) { + for event in er_bvh_events.read() { + if matches!(event, AssetEvent::LoadedWithDependencies { .. }) { + let bvh = ra_bvh.get(&**r_bvh).unwrap(); + info!("{bvh:#?}"); + } + } } fn init_scene_loading(