@@ -215,17 +224,17 @@ export class AppElement extends LitElement {
.space=${this.transformSpace}
@space-change=${this.onTransformSpaceChange}
>
- ` : nothing}
-
- ${this.sceneObjects.map(obj => html`
-
- `)}
-
+
+ ${this.sceneObjects.map(obj => html`
+
+ `)}
+
+ ` : nothing}
${this.showHelp ? html`
diff --git a/studio-web/src/index.ts b/studio-web/src/index.ts
index d51e81e..7b96f52 100644
--- a/studio-web/src/index.ts
+++ b/studio-web/src/index.ts
@@ -1,2 +1,3 @@
+export * from "./lib/bvh-viewer.element";
export * from "./lib/canvas-provider.element";
export * from "./lib/viewer.element";
diff --git a/studio-web/src/lib/bvh-viewer.element.ts b/studio-web/src/lib/bvh-viewer.element.ts
new file mode 100644
index 0000000..d81f0d3
--- /dev/null
+++ b/studio-web/src/lib/bvh-viewer.element.ts
@@ -0,0 +1,34 @@
+import * as studio from "@storyteller/studio";
+import { ViewportSize } from "@storyteller/studio";
+import { customElement, property } from "lit/decorators.js";
+
+import { StudioBaseElement } from "./studio-base.element";
+import { PropertyValues } from "lit";
+
+@customElement("sts-bvh-viewer")
+export class BvhViewerElement extends StudioBaseElement {
+ @property() bvhPath?: string;
+ @property() skyboxId?: string;
+
+ onStart(viewportSize: ViewportSize): void {
+ if (!this.bvhPath) return;
+ if (!this.skyboxId) return;
+
+ studio.startBvhViewer(
+ this.bvhPath,
+ this.skyboxId,
+ `#${this._canvasProvider!.canvasId}`,
+ "Storyteller Studio",
+ viewportSize,
+ );
+ }
+
+ onChanges(changes: PropertyValues): void {
+ if (changes.has("bvhPath") && !!this.bvhPath) {
+ // TODO
+ }
+
+ if (changes.has("skyboxId") && !!this.skyboxId)
+ studio.setSkybox(this.skyboxId);
+ }
+}
diff --git a/studio-web/src/lib/viewer.element.scss b/studio-web/src/lib/studio-base.element.scss
similarity index 100%
rename from studio-web/src/lib/viewer.element.scss
rename to studio-web/src/lib/studio-base.element.scss
diff --git a/studio-web/src/lib/studio-base.element.ts b/studio-web/src/lib/studio-base.element.ts
new file mode 100644
index 0000000..65db8cd
--- /dev/null
+++ b/studio-web/src/lib/studio-base.element.ts
@@ -0,0 +1,72 @@
+import { inject } from "@storyteller/framework";
+import * as studio from "@storyteller/studio";
+import { ViewportSize } from "@storyteller/studio";
+import { LitElement, PropertyValues, html, unsafeCSS } from "lit";
+
+import { CANVAS_PROVIDER, type CanvasProvider } from "./canvas-provider.element";
+
+import styles from "./studio-base.element.scss?inline";
+
+export abstract class StudioBaseElement extends LitElement {
+ static override styles = unsafeCSS(styles);
+
+ @inject(CANVAS_PROVIDER)
+ _canvasProvider?: CanvasProvider;
+
+ #canvas?: HTMLCanvasElement;
+ #resizeObserver?: ResizeObserver;
+
+ abstract onStart(viewportSize: ViewportSize): void;
+ abstract onChanges?(changes: PropertyValues): void;
+
+ override connectedCallback(): void {
+ super.connectedCallback();
+
+ this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));
+ this.#resizeObserver.observe(this);
+
+ if (!this._canvasProvider)
+ throw new Error(`No provider found for token: ${CANVAS_PROVIDER}`);
+
+ if ((this.#canvas = this._canvasProvider.takeCanvas()))
+ this.appendChild(this.#canvas);
+ }
+
+ override disconnectedCallback(): void {
+ if (this.#canvas)
+ this._canvasProvider?.returnCanvas(this.removeChild(this.#canvas));
+
+ this.#resizeObserver?.disconnect();
+
+ super.disconnectedCallback();
+ }
+
+ protected override updated(changes: PropertyValues): void {
+ if (!studio.isInitialized()) {
+ this.#start();
+ } else {
+ this.onChanges?.(changes);
+ }
+ }
+
+ #onResize(): void {
+ const { width, height } = this.getBoundingClientRect();
+ studio.resize(new ViewportSize(width, height));
+ }
+
+ #start(): void {
+ if (!this.#canvas) return;
+
+ const { width, height } = this.getBoundingClientRect();
+ try {
+ this.onStart(new ViewportSize(width, height));
+ } catch (err) {
+ if (!(err as Error).message?.startsWith("Using exceptions for control flow"))
+ throw err;
+ }
+ }
+
+ protected override render = () => html`
+
+ `;
+}
diff --git a/studio-web/src/lib/viewer.element.ts b/studio-web/src/lib/viewer.element.ts
index 7ee3aea..ec72727 100644
--- a/studio-web/src/lib/viewer.element.ts
+++ b/studio-web/src/lib/viewer.element.ts
@@ -1,99 +1,37 @@
import * as studio from "@storyteller/studio";
import { StudioMode, ViewportSize } from "@storyteller/studio";
-import { inject } from "@storyteller/framework";
-import { LitElement, PropertyValues, html, unsafeCSS } from "lit";
+import { PropertyValues } from "lit";
import { customElement, property } from "lit/decorators.js";
-import { CANVAS_PROVIDER, type CanvasProvider } from "./canvas-provider.element";
-
-import styles from "./viewer.element.scss?inline";
+import { StudioBaseElement } from "./studio-base.element";
@customElement("sts-viewer")
-export class ViewerElement extends LitElement {
- static override styles = unsafeCSS(styles);
-
- // TODO: `` and `` should be separate element
- // types with different inputs. `` should take a BVH
- // animation and load a simple mannequin asset playing the given
- // animation in a loop, while `` should take a scene
- // asset handle and load it with full editing capabilities.
- //
- // Both elements should share most of the core functionality here,
- // which could easily be extracted to a base class for inheritance by
- // making `#startViewer` an abstract method.
+export class ViewerElement extends StudioBaseElement {
@property() objectId?: string;
@property() skyboxId?: string;
@property({ type: Number }) mode?: StudioMode;
- @inject(CANVAS_PROVIDER)
- _canvasProvider?: CanvasProvider;
-
- #canvas?: HTMLCanvasElement;
- #resizeObserver?: ResizeObserver;
-
- override connectedCallback(): void {
- super.connectedCallback();
-
- this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));
- this.#resizeObserver.observe(this);
-
- if (!this._canvasProvider)
- throw new Error(`No provider found for token: ${CANVAS_PROVIDER}`);
-
- if ((this.#canvas = this._canvasProvider.takeCanvas()))
- this.appendChild(this.#canvas);
- }
-
- override disconnectedCallback(): void {
- if (this.#canvas)
- this._canvasProvider?.returnCanvas(this.removeChild(this.#canvas));
-
- this.#resizeObserver?.disconnect();
-
- super.disconnectedCallback();
- }
-
- protected override updated(changes: PropertyValues): void {
- if (!studio.isInitialized()) {
- this.#startViewer();
- } else {
- if (changes.has("objectId") && !!this.objectId)
- studio.loadObject(this.objectId);
-
- if (changes.has("skyboxId") && !!this.skyboxId)
- studio.setSkybox(this.skyboxId);
- }
- }
-
- protected override render = () => html`
-
- `;
-
- #onResize(): void {
- const { width, height } = this.getBoundingClientRect();
- studio.resize(new ViewportSize(width, height));
- }
-
- #startViewer(): void {
- if (!this.#canvas) return;
+ onStart(viewportSize: ViewportSize): void {
if (!this.objectId) return;
if (!this.skyboxId) return;
if (this.mode == null) return;
- const { width, height } = this.getBoundingClientRect();
- try {
- studio.start(
- this.objectId,
- this.skyboxId,
- `#${this._canvasProvider!.canvasId}`,
- "Storyteller Studio",
- new ViewportSize(width, height),
- this.mode,
- );
- } catch (err) {
- if (!(err as Error).message?.startsWith("Using exceptions for control flow"))
- throw err;
- }
+ studio.start(
+ this.objectId,
+ this.skyboxId,
+ `#${this._canvasProvider!.canvasId}`,
+ "Storyteller Studio",
+ viewportSize,
+ this.mode,
+ );
+ }
+
+ onChanges(changes: PropertyValues): void {
+ if (changes.has("objectId") && !!this.objectId)
+ studio.loadObject(this.objectId);
+
+ if (changes.has("skyboxId") && !!this.skyboxId)
+ studio.setSkybox(this.skyboxId);
}
}
diff --git a/studio/Cargo.toml b/studio/Cargo.toml
index d767a44..e01809d 100644
--- a/studio/Cargo.toml
+++ b/studio/Cargo.toml
@@ -35,6 +35,7 @@ web-sys = { version = "0.3.65", optional = true, features = [
"Window",
] }
space_editor = {git = "https://github.com/rewin123/space_editor", rev = "e150ecaf0647de6532d2967928982abf2558bfbc"}
+bevy_web_asset = "0.7.0"
[features]
wasm = [
diff --git a/studio/src/lib.rs b/studio/src/lib.rs
index f77d0eb..313179d 100644
--- a/studio/src/lib.rs
+++ b/studio/src/lib.rs
@@ -2,8 +2,9 @@
use bevy::{app::PluginGroupBuilder, prelude::*};
use bevy_mod_picking::DefaultPickingPlugins;
-
+use bevy_web_asset::WebAssetPlugin;
use space_editor::prelude::space_undo::UndoPlugin;
+
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
@@ -29,7 +30,7 @@ use crate::{
inspector::InspectorPlugin,
interaction::InteractionPlugin,
math::InterpolationPlugin,
- scene::{LoadSceneEvent, ScenePlugin},
+ scene::{LoadSceneEvent, ScenePlugin, TestBvh},
skybox::{SkyboxId, SkyboxPlugin},
ui::UiLayerPlugin,
world_grid::WorldGridPlugin,
@@ -84,6 +85,35 @@ impl PluginGroup for EditorPlugins {
}
}
+#[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_systems(
+ Startup,
+ move |mut cmd: Commands, r_assets: Res| {
+ cmd.insert_resource(TestBvh(r_assets.load(bvh_path.clone())));
+ },
+ );
+
+ app.run();
+}
+
#[cfg_attr(feature = "wasm", wasm_bindgen(js_name = startViewer))]
/// Start the application as a read-only viewer for a single library object.
///
@@ -175,6 +205,7 @@ pub fn start_internal(
app.add_plugins((
#[cfg(feature = "wasm")]
viewport::wasm::ViewportPlugin,
+ WebAssetPlugin,
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: viewport_size.into(),
diff --git a/studio/src/scene.rs b/studio/src/scene.rs
index ce2d314..a5aa086 100644
--- a/studio/src/scene.rs
+++ b/studio/src/scene.rs
@@ -40,7 +40,7 @@ impl Plugin for ScenePlugin {
.add_systems(
Update,
(
- spawn_test_bvh, // TODO
+ spawn_test_bvh.run_if(resource_exists::()), // TODO
init_scene_loading,
tick_scene_loading.run_if(in_state(SceneState::Loading)),
tick_scene_spawning.run_if(in_state(SceneState::Spawning)),
@@ -117,7 +117,7 @@ pub struct SkeletalJoint(pub Color);
pub struct SceneRoot;
#[derive(Resource, Deref, DerefMut)]
-struct TestBvh(Handle); // TODO
+pub struct TestBvh(pub Handle); // TODO
#[derive(Component, Clone, Copy, Debug, Deref, DerefMut)]
pub struct BindPoseLocal(pub Transform);
@@ -158,9 +158,6 @@ fn initialize(mut cmd: Commands, r_assets: Res) {
},
Fxaa::default(),
));
-
- // TODO
- cmd.insert_resource(TestBvh(r_assets.load("mocap/shuffle.bvh")));
}
// TODO
diff --git a/tsconfig.base.json b/tsconfig.base.json
index f6337bf..ab92679 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -20,6 +20,7 @@
"@storyteller/studio-web": ["studio-web/src/index.ts"],
"@storyteller/studio-web/canvas-provider": ["studio-web/src/lib/canvas-provider.element.ts"],
"@storyteller/studio-web/viewer": ["studio-web/src/lib/viewer.element.ts"],
+ "@storyteller/studio-web/bvh-viewer": ["studio-web/src/lib/bvh-viewer.element.ts"],
"@storyteller/utility": ["utility/src/index.ts"]
}
},