Refactor Studio entry-points (#54)

This PR heavily refactors the entry-point functions in
`studio/src/lib.rs` to simplify and consolidate. Additionally, the
frontend application should now be configurable as expected with any
valid combination of the following URL parameters:

* `objectId` : A pre-bundled library object to load (e.g., "couch.gltf")
* `bvh` : Any resolvable path to a BVH animation with a MocapNET
skeleton (e.g., "mocap/shuffle.bvh")
* `mixamo` : Any resolvable path to a glTF animation with the Mixamo
skeleton (e.g., "mocap/hip-hop-dancing.gltf")
* `sceneImport` : Any resolvable path to an arbitrary glTF scene
(untested, but should theoretically work)
* `scene` : Any resolvable path to a previously saved Storyteller Studio
scene in `*.scn.ron` format
* `skybox` : The ID of a pre-bundled skybox asset. Valid options are:
  - "gum_trees_4k"
  - "kloofenfal_28d_misty_4k"
  - "meadow_4k"
  - "promenade_de_vidy_4k"
  - "scythian_tombs_4k"
  - "test_scene" (this is the default if not specified)
* `mode` : One of "studio" or "viewer" (the default if not specified)

"Validity" rules are as follows:

* `objectId`, `bvh`, `mixamo`, `sceneImport` and `scene` are mutually
exclusive
* `skybox` is not supported for `scene` (the skybox should be saved and
loaded as part of the scene description, though this is not currently
working)
* Both `mode` options are valid for all other parameters
This commit is contained in:
Danny McGee
2024-02-27 12:22:00 -05:00
committed by GitHub
parent d51eec52c3
commit c788bcf6b7
21 changed files with 564 additions and 554 deletions
+165
View File
@@ -0,0 +1,165 @@
use std::time::Duration;
use bevy::{
app::{AppExit, ScheduleRunnerPlugin},
prelude::*,
};
use space_editor::prelude::{EditorEvent, EditorPrefabPath};
use wgpu::Extent3d;
use crate::{
bvh::{BvhAnimCompleteEvent, TestBvh},
screenshot::{ScreenCapture, ScreenShotPlugin},
BvhPlugin, CommonPlugins, LoadLibraryObjectEvent, LoadSceneEvent, MainCamera,
MixamoImportEvent, MixamoPlugin, SkyboxId, StudioMode, WorldGridPlugin,
};
#[derive(Debug, Resource, Clone)]
pub enum HeadlessMode {
Bvh(String, String),
Scene(String, String),
Mixamo(String, String),
None,
}
pub fn start_headless(
mode: HeadlessMode,
fps: u32,
outout_dir: &str,
frames_to_capture: u32,
image_res: Extent3d,
) -> App {
let mut app = App::new();
app.insert_resource(mode.clone());
app.insert_resource(StudioMode::Headless);
app.insert_resource(HeadlessSettings {
max_frames: frames_to_capture,
output_dims: image_res,
output_dir: outout_dir.to_string(),
});
app.add_plugins((
ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / fps as f64)),
DefaultPlugins.set(WindowPlugin {
primary_window: None,
exit_condition: bevy::window::ExitCondition::DontExit,
close_when_requested: true,
}),
ScreenShotPlugin,
CommonPlugins.build().disable::<WorldGridPlugin>(),
));
match mode {
HeadlessMode::Bvh(bvh_dir, skybox) => {
app.add_plugins(BvhPlugin);
app.add_systems(
Startup,
move |mut cmd: Commands,
asset_server: Res<AssetServer>,
mut ew: EventWriter<LoadLibraryObjectEvent>| {
cmd.insert_resource(SkyboxId(skybox.clone()));
cmd.insert_resource(TestBvh(asset_server.load(bvh_dir.clone())));
ew.send(LoadLibraryObjectEvent("Mannequin.gltf".to_string()));
},
);
app.add_systems(PostUpdate, check_for_bvh_capture_complete);
}
HeadlessMode::Scene(scene, skybox) => {
app.add_systems(
Startup,
move |mut cmd: Commands, mut ew: EventWriter<EditorEvent>| {
cmd.insert_resource(SkyboxId(skybox.clone()));
ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone())))
},
);
}
HeadlessMode::Mixamo(mixamo_path, skybox) => {
app.add_plugins(MixamoPlugin);
app.add_systems(
Startup,
move |mut commands: Commands,
mut ew: EventWriter<MixamoImportEvent>,
mut load_scene_event: EventWriter<LoadSceneEvent>| {
ew.send(MixamoImportEvent(mixamo_path.clone()));
load_scene_event.send(LoadSceneEvent("Mannequin.gltf".to_string()));
commands.insert_resource(SkyboxId(skybox.clone()));
},
);
app.add_systems(PostUpdate, check_for_mixamo_capture_complete);
app.run();
}
HeadlessMode::None => {}
}
app
}
pub fn is_headless(studio_state: Res<StudioMode>) -> bool {
*studio_state == StudioMode::Headless
}
pub fn is_not_headless(studio_state: Res<StudioMode>) -> bool {
*studio_state != StudioMode::Headless
}
#[derive(Resource)]
pub struct HeadlessSettings {
pub output_dims: Extent3d,
pub output_dir: String,
pub max_frames: u32,
}
impl Default for HeadlessSettings {
fn default() -> Self {
HeadlessSettings {
output_dims: Extent3d {
width: 1280,
height: 720,
depth_or_array_layers: 1,
},
output_dir: "./screen_shots/".to_string(),
max_frames: u32::MAX,
}
}
}
fn check_for_bvh_capture_complete(
mut close_app: EventWriter<AppExit>,
mut main_camera: Query<&mut ScreenCapture, With<MainCamera>>,
bvh_event_reader: EventReader<BvhAnimCompleteEvent>,
) {
let Ok(mut screen_capture) = main_camera.get_single_mut() else {
return;
};
println!("Frames captured {}", screen_capture.frames_captured());
if !screen_capture.should_capture() || !bvh_event_reader.is_empty() {
screen_capture.stop_capturing();
close_app.send(AppExit);
}
}
fn check_for_mixamo_capture_complete(
mut close_app: EventWriter<AppExit>,
mut main_camera: Query<&mut ScreenCapture, With<MainCamera>>,
mixamo_anims: Query<&AnimationPlayer>,
) {
let Ok(mut capture) = main_camera.get_single_mut() else {
return;
};
for anim in mixamo_anims.iter() {
println!(
"Time elapsed {} | Frames Captured : {}",
anim.elapsed(),
capture.frames_captured()
);
if anim.completions() != 0 {
capture.stop_capturing();
close_app.send(AppExit);
}
}
}
+77 -311
View File
@@ -1,20 +1,14 @@
#![allow(clippy::type_complexity, clippy::too_many_arguments)]
use std::time::Duration;
use bevy::{
app::{AppExit, PluginGroupBuilder, ScheduleRunnerPlugin},
prelude::*,
};
use bevy::{app::PluginGroupBuilder, prelude::*};
use bevy_mod_picking::DefaultPickingPlugins;
use bevy_web_asset::WebAssetPlugin;
use bvh::{BvhAnimCompleteEvent, TestBvh};
use screenshot::{ScreenCapture, ScreenShotPlugin};
use space_editor::prelude::{EditorEvent, EditorPrefabPath};
use scene::ImportSceneEvent;
#[cfg(feature = "wasm")]
use serde_repr::{Deserialize_repr, Serialize_repr};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
use wgpu::Extent3d;
mod anim;
mod bvh;
@@ -22,6 +16,7 @@ mod camera_controller;
mod change_tracking;
mod debug;
mod design_system;
pub mod headless;
mod hierarchy;
mod input;
mod inspector;
@@ -63,7 +58,12 @@ mod wasm;
#[derive(Component)]
pub(crate) struct MainCamera;
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[cfg_attr(
feature = "wasm",
wasm_bindgen,
repr(u8),
derive(Deserialize_repr, Serialize_repr)
)]
#[derive(Resource, Clone, Copy, PartialEq, Eq, Debug)]
pub enum StudioMode {
Editor,
@@ -104,166 +104,47 @@ impl PluginGroup for EditorPlugins {
}
}
#[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(
InitSeed::FromLibrary {
object_id: object_id.into(),
skybox_id: skybox_id.into(),
},
canvas_selector,
canvas_alt,
viewport_size,
StudioMode::Viewer,
)
.run();
}
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startEditor))]
pub fn start_editor(
scene_id: &str,
canvas_selector: &str,
canvas_alt: &str,
viewport_size: ViewportSize,
) {
start_internal(
InitSeed::FromScene(scene_id.into()),
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(
InitSeed::FromLibrary {
object_id: "Mannequin.gltf".into(),
skybox_id: skybox_id.into(),
},
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(
InitSeed::FromLibrary {
object_id: "Mannequin.gltf".into(),
skybox_id: skybox_id.into(),
},
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(
InitSeed::FromLibrary {
object_id: object_id.into(),
skybox_id: skybox_id.into(),
},
canvas_selector,
canvas_alt,
viewport_size,
studio_mode,
)
.run();
}
pub enum InitSeed {
/// Initialize the application from assets in the "bundled" asset library.
FromLibrary {
/// A filename, including the extension, that can be found under
/// `studio/assets/gltf` (e.g., "Mannequin.gltf")
object_id: String,
#[derive(Debug)]
pub enum InitParams {
/// Initialize the application by importing an asset and processing it to
/// work with Storyteller Studio's feature-set.
FromAsset {
asset: InitAsset,
/// A skybox name that corresponds to a matching set of assets under
/// `studio/assets/skyboxes`, _not_ including file extensions or
/// "diffuse"/"specular" qualifiers (e.g., "gum_trees_4k")
skybox_id: String,
},
/// Initialize the application by loading a previously saved `*.scn.ron`
/// scene file.
/// Initialize the application by loading a previously saved Storyteller
/// Studio scene.
FromScene(String),
}
pub fn start_internal(
seed: InitSeed,
canvas_selector: &str,
canvas_alt: &str,
viewport_size: ViewportSize,
studio_mode: StudioMode,
) -> App {
#[derive(Debug)]
pub enum InitAsset {
/// Initialize the application from an asset in the "bundled" asset library.
/// Takes a filename, including the extension, that can be found under
/// `studio/assets/gltf` (e.g., "base-human-female.gltf").
LibraryObject(String),
/// Initialize the application by loading an arbitrary glTF scene from any
/// source (e.g., the Storyteller.ai backend)
ImportedScene(String),
/// Initialize the application by retargeting a BVH animation (currently
/// only supports the MocapNET skeleton) onto the bundled `Mannequin.gltf`
/// model.
BvhAnim(String),
/// Initialize the application by retargeting a Mixamo animation onto the
/// bundled `Mannequin.gltf` model.
MixamoAnim(String),
}
pub fn start(init: InitParams, mode: StudioMode, window_config: Option<Window>) -> App {
#[cfg(feature = "wasm")]
console_error_panic_hook::set_once();
let mut app = App::new();
app.insert_resource(HeadlessMode::None);
app.insert_resource(studio_mode);
app.insert_resource(mode);
app.add_plugins((
#[cfg(feature = "wasm")]
@@ -271,12 +152,7 @@ pub fn start_internal(
WebAssetPlugin,
RemoteAssetPlugin,
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: viewport_size.into(),
title: canvas_alt.into(),
canvas: Some(canvas_selector.into()),
..default()
}),
primary_window: Some(window_config.unwrap_or_default()),
..default()
}),
CommonPlugins,
@@ -284,7 +160,7 @@ pub fn start_internal(
wasm::ScreenshotPlugin,
));
if matches!(studio_mode, StudioMode::Editor) {
if matches!(mode, StudioMode::Editor) {
app.add_plugins((DefaultPickingPlugins, EditorPlugins));
// Only for registering components for undo
@@ -292,20 +168,11 @@ pub fn start_internal(
app.auto_reflected_undo::<Transform>();
}
match seed {
InitSeed::FromLibrary {
object_id,
skybox_id,
} => {
app.add_systems(
Startup,
move |mut cmd: Commands, mut ew: EventWriter<LoadLibraryObjectEvent>| {
cmd.insert_resource(SkyboxId(skybox_id.clone()));
ew.send(LoadLibraryObjectEvent(object_id.clone()));
},
);
match init {
InitParams::FromAsset { asset, skybox_id } => {
init_from_asset(&mut app, asset, skybox_id);
}
InitSeed::FromScene(scene) => {
InitParams::FromScene(scene) => {
app.add_systems(Startup, move |mut ew: EventWriter<LoadSceneEvent>| {
ew.send(LoadSceneEvent(scene.clone()));
});
@@ -318,152 +185,51 @@ pub fn start_internal(
app
}
#[derive(Debug, Resource, Clone)]
pub enum HeadlessMode {
Bvh(String, String),
Scene(String, String),
Mixamo(String, String),
None,
}
pub fn start_headless(
mode: HeadlessMode,
fps: u32,
outout_dir: &str,
frames_to_capture: u32,
image_res: Extent3d,
) -> App {
let mut app = App::new();
app.insert_resource(mode.clone());
app.insert_resource(StudioMode::Headless);
app.insert_resource(HeadlessSettings {
max_frames: frames_to_capture,
output_dims: image_res,
output_dir: outout_dir.to_string(),
fn init_from_asset(app: &mut App, asset: InitAsset, skybox_id: String) {
app.add_systems(Startup, move |mut cmd: Commands| {
cmd.insert_resource(SkyboxId(skybox_id.clone()));
});
app.add_plugins((
ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1.0 / fps as f64)),
DefaultPlugins.set(WindowPlugin {
primary_window: None,
exit_condition: bevy::window::ExitCondition::DontExit,
close_when_requested: true,
}),
ScreenShotPlugin,
CommonPlugins.build().disable::<WorldGridPlugin>(),
));
match mode {
HeadlessMode::Bvh(bvh_dir, skybox) => {
app.add_plugins(BvhPlugin);
match asset {
InitAsset::LibraryObject(id) => {
app.add_systems(
Startup,
move |mut cmd: Commands,
asset_server: Res<AssetServer>,
mut ew: EventWriter<LoadLibraryObjectEvent>| {
cmd.insert_resource(SkyboxId(skybox.clone()));
cmd.insert_resource(TestBvh(asset_server.load(bvh_dir.clone())));
ew.send(LoadLibraryObjectEvent("Mannequin.gltf".to_string()));
},
);
app.add_systems(PostUpdate, check_for_bvh_capture_complete);
}
HeadlessMode::Scene(scene, skybox) => {
app.add_systems(
Startup,
move |mut cmd: Commands, mut ew: EventWriter<EditorEvent>| {
cmd.insert_resource(SkyboxId(skybox.clone()));
ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone())))
move |mut ew: EventWriter<LoadLibraryObjectEvent>| {
ew.send(LoadLibraryObjectEvent(id.clone()));
},
);
}
HeadlessMode::Mixamo(mixamo_path, skybox) => {
app.add_plugins(MixamoPlugin);
anim @ (InitAsset::BvhAnim(_) | InitAsset::MixamoAnim(_)) => {
app.add_systems(
Startup,
move |mut commands: Commands,
mut ew: EventWriter<MixamoImportEvent>,
mut load_scene_event: EventWriter<LoadSceneEvent>| {
ew.send(MixamoImportEvent(mixamo_path.clone()));
load_scene_event.send(LoadSceneEvent("Mannequin.gltf".to_string()));
commands.insert_resource(SkyboxId(skybox.clone()));
move |mut ew: EventWriter<LoadLibraryObjectEvent>| {
ew.send(LoadLibraryObjectEvent("Mannequin.gltf".into()));
},
);
app.add_systems(PostUpdate, check_for_mixamo_capture_complete);
app.run();
match anim {
InitAsset::BvhAnim(anim) => {
app.add_plugins(BvhPlugin);
app.add_systems(
Startup,
move |mut cmd: Commands, r_assets: Res<AssetServer>| {
cmd.insert_resource(bvh::TestBvh(r_assets.load(anim.clone())));
},
);
}
InitAsset::MixamoAnim(anim) => {
app.add_plugins(MixamoPlugin);
app.add_systems(Startup, move |mut ew: EventWriter<MixamoImportEvent>| {
ew.send(MixamoImportEvent(anim.clone()));
});
}
_ => unreachable!(),
}
}
HeadlessMode::None => {}
}
app
}
pub fn is_headless(studio_state: Res<StudioMode>) -> bool {
*studio_state == StudioMode::Headless
}
pub fn is_not_headless(studio_state: Res<StudioMode>) -> bool {
*studio_state != StudioMode::Headless
}
#[derive(Resource)]
pub struct HeadlessSettings {
pub output_dims: Extent3d,
pub output_dir: String,
pub max_frames: u32,
}
impl Default for HeadlessSettings {
fn default() -> Self {
HeadlessSettings {
output_dims: Extent3d {
width: 1280,
height: 720,
depth_or_array_layers: 1,
},
output_dir: "./screen_shots/".to_string(),
max_frames: u32::MAX,
}
}
}
fn check_for_bvh_capture_complete(
mut close_app: EventWriter<AppExit>,
mut main_camera: Query<&mut ScreenCapture, With<MainCamera>>,
bvh_event_reader: EventReader<BvhAnimCompleteEvent>,
) {
let Ok(mut screen_capture) = main_camera.get_single_mut() else {
return;
};
println!("Frames captured {}", screen_capture.frames_captured());
if !screen_capture.should_capture() || !bvh_event_reader.is_empty() {
screen_capture.stop_capturing();
close_app.send(AppExit);
}
}
fn check_for_mixamo_capture_complete(
mut close_app: EventWriter<AppExit>,
mut main_camera: Query<&mut ScreenCapture, With<MainCamera>>,
mixamo_anims: Query<&AnimationPlayer>,
) {
let Ok(mut capture) = main_camera.get_single_mut() else {
return;
};
for anim in mixamo_anims.iter() {
println!(
"Time elapsed {} | Frames Captured : {}",
anim.elapsed(),
capture.frames_captured()
);
if anim.completions() != 0 {
capture.stop_capturing();
close_app.send(AppExit);
InitAsset::ImportedScene(scene) => {
app.add_systems(Startup, move |mut ew: EventWriter<ImportSceneEvent>| {
ew.send(ImportSceneEvent(scene.clone()));
});
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
use bevy::render::render_resource::Extent3d;
use clap::{Args, Parser, Subcommand};
use studio::{start_headless, HeadlessMode};
use studio::headless::{start_headless, HeadlessMode};
#[derive(Parser)]
#[command(name = "StorytellerStudioHeadless")]
+12 -8
View File
@@ -1,6 +1,9 @@
use bevy::{app::Update, window::close_on_esc};
use bevy::{
app::Update,
window::{close_on_esc, Window},
};
use clap::Parser;
use studio::{screenshot::ScreenShotPlugin, InitSeed, StudioMode, ViewportSize};
use studio::{screenshot::ScreenShotPlugin, InitAsset, InitParams, StudioMode};
#[derive(Parser)]
#[command(name = "StorytellerStudio")]
@@ -20,15 +23,16 @@ fn main() {
let skybox_id = args.skybox.unwrap_or("gum_trees_4k".to_string());
// Edit the arguments here to change the default startup scene or skybox
let mut app = studio::start_internal(
InitSeed::FromLibrary {
object_id,
let mut app = studio::start(
InitParams::FromAsset {
asset: InitAsset::LibraryObject(object_id),
skybox_id,
},
"", // ignored for non-WASM
"Storyteller Studio",
ViewportSize::default(),
StudioMode::Editor,
Some(Window {
title: "Storyteller Studio".into(),
..Default::default()
}),
);
// Add any additional systems, resources, etc. for local debugging
+26 -5
View File
@@ -26,10 +26,10 @@ use crate::{
camera_controller::{CameraController, CameraControllerBundle},
change_tracking::{ChangeTracker, ChangeTrackingPlugin},
debug::DebugPoint,
is_headless, is_not_headless,
headless::{is_headless, is_not_headless, HeadlessSettings},
math::{Combinable, Extrema},
screenshot::ScreenCapture,
HeadlessSettings, MainCamera,
MainCamera,
};
pub struct ScenePlugin;
@@ -46,6 +46,7 @@ impl Plugin for ScenePlugin {
.add_state::<SceneState>()
.add_event::<LoadLibraryObjectEvent>()
.add_event::<LoadSceneEvent>()
.add_event::<ImportSceneEvent>()
.add_systems(
Startup,
(
@@ -55,7 +56,8 @@ impl Plugin for ScenePlugin {
)
.add_systems(
Update,
(init_object_loading, init_scene_loading).in_set(SceneSpawnerSystem),
(init_object_loading, init_scene_import, init_scene_loading)
.in_set(SceneSpawnerSystem),
)
.add_systems(
OnEnter(SceneState::Active),
@@ -208,8 +210,7 @@ fn auto_add_handle<T: Asset>(
#[derive(Event, Deref)]
pub struct LoadLibraryObjectEvent(pub String);
/// Request the application to load a previously saved `*.scn.ron` dynamic scene
/// file.
/// Request the application to load a previously saved Storyteller Studio scene.
///
/// Expects a full, resolvable path to the file (e.g. `scenes/my-scene.scn.ron`
/// for a local file saved under `studio/assets/scenes`, or
@@ -218,6 +219,11 @@ pub struct LoadLibraryObjectEvent(pub String);
#[derive(Event, Deref)]
pub struct LoadSceneEvent(pub String);
/// Request the application to import and process a glTF scene from an arbitrary
/// asset source (e.g., an http path or a Storyteller.ai media token).
#[derive(Event, Deref)]
pub struct ImportSceneEvent(pub String);
// TODOL This could be converted into an enum that indicates what "kind" of
// element this is (e.g. StaticMesh, SkinnedMesh, Bone, Light, Camera).
// We don't have an immediate need for that just yet, but likely will
@@ -329,6 +335,21 @@ fn init_object_loading(mut cmd: Commands, mut er: EventReader<LoadLibraryObjectE
}
}
fn init_scene_import(mut cmd: Commands, mut er: EventReader<ImportSceneEvent>) {
if let Some(path) = er.read().last() {
info!("Importing asset: {}", path.0);
cmd.spawn((
SpatialBundle::default(),
space_editor::prelude::GltfPrefab {
path: path.0.clone(),
scene: "Scene0".to_string(),
},
PrefabMarker,
SceneRoot,
));
}
}
fn init_scene_loading(
mut er_load_scene: EventReader<LoadSceneEvent>,
mut ew_editor: EventWriter<EditorEvent>,
+2 -1
View File
@@ -1,10 +1,11 @@
use core::fmt;
use bevy::window::WindowResolution;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ViewportSize {
pub width: f32,
pub height: f32,
+164
View File
@@ -0,0 +1,164 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use crate::{InitAsset, InitParams, StudioMode, ViewportSize};
#[wasm_bindgen(typescript_custom_section)]
const WASM_ENTRY_POINTS: &str = r#"
export type SkyboxId
= "gum_trees_4k"
| "kloofenfal_28d_misty_4k"
| "meadow_4k"
| "promenade_de_vidy_4k"
| "scythian_tombs_4k"
| "test_scene"
;
export interface InitOptions {
mode: StudioMode;
/** Required for all entry-points except `initFromStorytellerScene()` */
skyboxId?: SkyboxId;
canvasSelector: string;
canvasAlt: string;
viewportSize: ViewportSize;
}
export function initFromLibrary(objectId: string, options: InitOptions): void;
export function initFromBvh(animPath: string, options: InitOptions): void;
export function initFromMixamo(animPath: string, options: InitOptions): void;
export function initFromSceneImport(scenePath: string, options: InitOptions): void;
export function initFromStorytellerScene(scenePath: string, options: InitOptions): void;
"#;
#[derive(Serialize, Deserialize)]
struct WasmInitOptions {
#[serde(rename = "skyboxId")]
skybox_id: Option<String>,
mode: StudioMode,
#[serde(rename = "canvasSelector")]
canvas_selector: String,
#[serde(rename = "canvasAlt")]
canvas_alt: String,
#[serde(rename = "viewportSize")]
viewport_size: ViewportSize,
}
#[wasm_bindgen(skip_typescript, js_name = initFromLibrary)]
pub fn init_from_library(object_id: String, options: JsValue) -> Result<(), JsValue> {
let options = serde_wasm_bindgen::from_value::<WasmInitOptions>(options)?;
let skybox_id = options
.skybox_id
.expect("`skyboxId` is required for `initFromLibrary`");
crate::start(
InitParams::FromAsset {
asset: InitAsset::LibraryObject(object_id),
skybox_id,
},
options.mode,
Some(Window {
resolution: options.viewport_size.into(),
title: options.canvas_alt,
canvas: Some(options.canvas_selector),
..default()
}),
)
.run();
Ok(())
}
#[wasm_bindgen(skip_typescript, js_name = initFromBvh)]
pub fn init_from_bvh(anim_path: String, options: JsValue) -> Result<(), JsValue> {
let options = serde_wasm_bindgen::from_value::<WasmInitOptions>(options)?;
let skybox_id = options
.skybox_id
.expect("`skyboxId` is required for `initFromBvh`");
crate::start(
InitParams::FromAsset {
asset: InitAsset::BvhAnim(anim_path),
skybox_id,
},
options.mode,
Some(Window {
resolution: options.viewport_size.into(),
title: options.canvas_alt,
canvas: Some(options.canvas_selector),
..default()
}),
)
.run();
Ok(())
}
#[wasm_bindgen(skip_typescript, js_name = initFromMixamo)]
pub fn init_from_mixamo(anim_path: String, options: JsValue) -> Result<(), JsValue> {
let options = serde_wasm_bindgen::from_value::<WasmInitOptions>(options)?;
let skybox_id = options
.skybox_id
.expect("`skyboxId` is required for `initFromMixamo`");
crate::start(
InitParams::FromAsset {
asset: InitAsset::MixamoAnim(anim_path),
skybox_id,
},
options.mode,
Some(Window {
resolution: options.viewport_size.into(),
title: options.canvas_alt,
canvas: Some(options.canvas_selector),
..default()
}),
)
.run();
Ok(())
}
#[wasm_bindgen(skip_typescript, js_name = initFromSceneImport)]
pub fn init_from_scene_import(scene_path: String, options: JsValue) -> Result<(), JsValue> {
let options = serde_wasm_bindgen::from_value::<WasmInitOptions>(options)?;
let skybox_id = options
.skybox_id
.expect("`skyboxId` is required for `initFromSceneImport`");
crate::start(
InitParams::FromAsset {
asset: InitAsset::ImportedScene(scene_path),
skybox_id,
},
options.mode,
Some(Window {
resolution: options.viewport_size.into(),
title: options.canvas_alt,
canvas: Some(options.canvas_selector),
..default()
}),
)
.run();
Ok(())
}
#[wasm_bindgen(skip_typescript, js_name = initFromStorytellerScene)]
pub fn init_from_storyteller_scene(scene_path: String, options: JsValue) -> Result<(), JsValue> {
let options = serde_wasm_bindgen::from_value::<WasmInitOptions>(options)?;
crate::start(
InitParams::FromScene(scene_path),
options.mode,
Some(Window {
resolution: options.viewport_size.into(),
title: options.canvas_alt,
canvas: Some(options.canvas_selector),
..default()
}),
)
.run();
Ok(())
}
+1
View File
@@ -4,6 +4,7 @@ use parking_lot::ReentrantMutex;
use wasm_bindgen::prelude::wasm_bindgen;
mod conv;
mod init;
#[cfg(feature = "wasm_screenshot")]
mod screenshot;
mod util;