mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-13 23:52:55 +00:00
a5aeff7a3a
This PR adds support for Mixamo FBX imports (via backend conversion to glTF/glB) and some miscellaneous improvements to the animation systems. ### User-facing improvements * Retargeting math has been fixed to eliminate jittering issues * Retargeter can now handle animated bone translations when present (generally this is only used for root motion) ### Housekeeping / Code improvements * Animation-related components and system logic have been moved to a dedicated `anim` module and `AnimPlugin` * Retargeting stuff has been moved from the `bvh` module to `anim::retargeting` * Component-based solution for marking an entity as an animation retargeting source makes it easy to extend the retargeting functionality to new types of source animations ### Limitations Currently the code assumes that there will be at most one animation source at a time. That single animation (if present) will be retargeted to all other skeletons in the scene. A component-based solution for directing a specific target skeleton to use a specific source animation is possible, but will require some additional consideration around the implementation design for the read-only "viewer" modes. ### Test animations: * Hip-Hop Dancing (Mixamo): ?mode=viewer&mixamo=mocap/hip-hop-dancing.gltf * Shuffle (MocapNET): ?mode=viewer&bvh=mocap/shuffle.bvh * Scott (MocapNET): ?mode=viewer&bvh=mocap/scott.bvh * Scott 1 (MocapNET): ?mode=viewer&bvh=mocap/scott1.bvh
275 lines
6.9 KiB
Rust
275 lines
6.9 KiB
Rust
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
|
|
|
|
use bevy::{app::PluginGroupBuilder, prelude::*};
|
|
use bevy_mod_picking::DefaultPickingPlugins;
|
|
use bevy_web_asset::WebAssetPlugin;
|
|
|
|
#[cfg(feature = "wasm")]
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
mod anim;
|
|
mod bvh;
|
|
mod camera_controller;
|
|
mod debug;
|
|
mod design_system;
|
|
mod hierarchy;
|
|
mod input;
|
|
mod inspector;
|
|
mod interaction;
|
|
mod math;
|
|
mod mixamo;
|
|
mod persistance;
|
|
mod scene;
|
|
mod skybox;
|
|
mod ui;
|
|
pub mod viewport;
|
|
mod world_grid;
|
|
|
|
pub use crate::{
|
|
anim::AnimPlugin,
|
|
bvh::BvhPlugin,
|
|
camera_controller::CameraControllerPlugin,
|
|
debug::DebugPlugin,
|
|
input::StudioInputPlugin,
|
|
inspector::InspectorPlugin,
|
|
interaction::InteractionPlugin,
|
|
math::InterpolationPlugin,
|
|
mixamo::{MixamoImportEvent, MixamoPlugin},
|
|
persistance::PersistancePlugin,
|
|
scene::{LoadSceneEvent, ScenePlugin},
|
|
skybox::{SkyboxId, SkyboxPlugin},
|
|
ui::UiLayerPlugin,
|
|
viewport::ViewportSize,
|
|
world_grid::WorldGridPlugin,
|
|
};
|
|
|
|
#[cfg(feature = "wasm")]
|
|
mod wasm;
|
|
|
|
#[derive(Component)]
|
|
pub(crate) struct MainCamera;
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
|
#[derive(Resource, Clone, Copy, PartialEq, Eq, Debug)]
|
|
pub enum StudioMode {
|
|
Editor,
|
|
Viewer,
|
|
}
|
|
|
|
pub(crate) fn is_editor(r_studio_mode: Res<StudioMode>) -> bool {
|
|
matches!(*r_studio_mode, StudioMode::Editor)
|
|
}
|
|
|
|
/// The collection of plugins that should be loaded for both viewer-mode and
|
|
/// editor-mode instances of the app.
|
|
struct CommonPlugins;
|
|
impl PluginGroup for CommonPlugins {
|
|
fn build(self) -> PluginGroupBuilder {
|
|
PluginGroupBuilder::start::<Self>()
|
|
.add(CameraControllerPlugin)
|
|
.add(DebugPlugin)
|
|
.add(InspectorPlugin)
|
|
.add(InterpolationPlugin)
|
|
.add(ScenePlugin)
|
|
.add(SkyboxPlugin)
|
|
.add(StudioInputPlugin)
|
|
.add(UiLayerPlugin)
|
|
.add(WorldGridPlugin)
|
|
.add(PersistancePlugin)
|
|
.add(AnimPlugin)
|
|
}
|
|
}
|
|
|
|
/// The collection of plugins that should be loaded only for editor-mode
|
|
/// instances of the app.
|
|
struct EditorPlugins;
|
|
impl PluginGroup for EditorPlugins {
|
|
fn build(self) -> PluginGroupBuilder {
|
|
PluginGroupBuilder::start::<Self>().add(InteractionPlugin)
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startViewer))]
|
|
/// Start the application as a read-only viewer for a single library object.
|
|
///
|
|
/// @param object_id ID of the library object to load.
|
|
/// @param skybox_id ID of the Skybox to use
|
|
/// @param canvas_selector A CSS selector for the canvas that will host the Bevy
|
|
/// application. This should be unique (i.e. `querySelectorAll` should not
|
|
/// return more than one element), so using an ID selector is a good idea.
|
|
/// @param canvas_alt `alt` text for the HTML canvas element.
|
|
/// @param viewport_size The size of the canvas in CSS `px` units.
|
|
pub fn start_viewer(
|
|
object_id: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
) {
|
|
start_internal(
|
|
object_id,
|
|
skybox_id,
|
|
canvas_selector,
|
|
canvas_alt,
|
|
viewport_size,
|
|
StudioMode::Viewer,
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startEditor))]
|
|
pub fn start_editor(
|
|
object_id: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
) {
|
|
start_internal(
|
|
object_id,
|
|
skybox_id,
|
|
canvas_selector,
|
|
canvas_alt,
|
|
viewport_size,
|
|
StudioMode::Editor,
|
|
)
|
|
.run();
|
|
}
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startBvhViewer))]
|
|
pub fn start_bvh_viewer(
|
|
bvh_path: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
) {
|
|
let mut app = start_internal(
|
|
"Mannequin.gltf",
|
|
skybox_id,
|
|
canvas_selector,
|
|
canvas_alt,
|
|
viewport_size,
|
|
StudioMode::Viewer,
|
|
);
|
|
|
|
let bvh_path = bvh_path.to_string();
|
|
|
|
app.add_plugins(BvhPlugin);
|
|
app.add_systems(
|
|
Startup,
|
|
move |mut cmd: Commands, r_assets: Res<AssetServer>| {
|
|
cmd.insert_resource(bvh::TestBvh(r_assets.load(bvh_path.clone())));
|
|
},
|
|
);
|
|
|
|
app.run();
|
|
}
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startMixamoViewer))]
|
|
pub fn start_mixamo_viewer(
|
|
mixamo_path: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
) {
|
|
let mut app = start_internal(
|
|
"Mannequin.gltf",
|
|
skybox_id,
|
|
canvas_selector,
|
|
canvas_alt,
|
|
viewport_size,
|
|
StudioMode::Viewer,
|
|
);
|
|
|
|
let mixamo_path = mixamo_path.to_string();
|
|
|
|
app.add_plugins(MixamoPlugin);
|
|
app.add_systems(Startup, move |mut ew: EventWriter<MixamoImportEvent>| {
|
|
ew.send(MixamoImportEvent(mixamo_path.clone()));
|
|
});
|
|
|
|
app.run();
|
|
}
|
|
|
|
#[cfg_attr(feature = "wasm", wasm_bindgen)]
|
|
pub fn start(
|
|
object_id: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
studio_mode: StudioMode,
|
|
) {
|
|
start_internal(
|
|
object_id,
|
|
skybox_id,
|
|
canvas_selector,
|
|
canvas_alt,
|
|
viewport_size,
|
|
studio_mode,
|
|
)
|
|
.run();
|
|
}
|
|
|
|
// TODO: These `start_` functiions should be refactored to better support the
|
|
// needs of the "reaodnly viewer" vs "full studio editor" use cases. See
|
|
// notes in `studio-web/src/lib/viewer.element.ts` for more info.
|
|
|
|
pub fn start_internal(
|
|
object_id: &str,
|
|
skybox_id: &str,
|
|
canvas_selector: &str,
|
|
canvas_alt: &str,
|
|
viewport_size: ViewportSize,
|
|
studio_mode: StudioMode,
|
|
) -> App {
|
|
#[cfg(feature = "wasm")]
|
|
console_error_panic_hook::set_once();
|
|
|
|
let object_id = object_id.to_string();
|
|
let skybox_id = skybox_id.to_string();
|
|
|
|
let mut app = App::new();
|
|
|
|
app.insert_resource(studio_mode);
|
|
|
|
app.add_plugins((
|
|
#[cfg(feature = "wasm")]
|
|
viewport::wasm::ViewportPlugin,
|
|
WebAssetPlugin,
|
|
DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
resolution: viewport_size.into(),
|
|
title: canvas_alt.into(),
|
|
canvas: Some(canvas_selector.into()),
|
|
..default()
|
|
}),
|
|
..default()
|
|
}),
|
|
CommonPlugins,
|
|
));
|
|
|
|
if matches!(studio_mode, StudioMode::Editor) {
|
|
app.add_plugins((DefaultPickingPlugins, EditorPlugins));
|
|
|
|
// Only for registering components for undo
|
|
use space_editor::prelude::space_undo::AppAutoUndo;
|
|
app.auto_reflected_undo::<Transform>();
|
|
}
|
|
|
|
app.add_systems(
|
|
Startup,
|
|
move |mut cmd: Commands, mut ew: EventWriter<LoadSceneEvent>| {
|
|
cmd.insert_resource(SkyboxId(skybox_id.clone()));
|
|
ew.send(LoadSceneEvent(object_id.clone()));
|
|
},
|
|
);
|
|
|
|
#[cfg(feature = "wasm")]
|
|
wasm::initialize();
|
|
|
|
app
|
|
}
|