From c788bcf6b7642775332939669e94b7ca3e922171 Mon Sep 17 00:00:00 2001 From: Danny McGee Date: Tue, 27 Feb 2024 12:22:00 -0500 Subject: [PATCH] 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 --- Cargo.lock | 78 ++-- image-generator-demo/src/app/app.element.ts | 12 +- studio-frontend/src/app/app.element.ts | 35 +- studio-web/src/index.ts | 5 +- studio-web/src/lib/bvh-viewer.element.ts | 34 -- studio-web/src/lib/editor.element.ts | 28 -- studio-web/src/lib/mixamo-viewer.element.ts | 34 -- ...-base.element.scss => studio.element.scss} | 0 ...udio-base.element.ts => studio.element.ts} | 57 ++- studio-web/src/lib/url-params.ts | 13 +- studio-web/src/lib/viewer.element.ts | 42 -- studio/Cargo.toml | 1 + studio/src/headless.rs | 165 ++++++++ studio/src/lib.rs | 388 ++++-------------- studio/src/main-headless.rs | 2 +- studio/src/main.rs | 20 +- studio/src/scene.rs | 31 +- studio/src/viewport.rs | 3 +- studio/src/wasm/init.rs | 164 ++++++++ studio/src/wasm/mod.rs | 1 + tsconfig.base.json | 5 +- 21 files changed, 564 insertions(+), 554 deletions(-) delete mode 100644 studio-web/src/lib/bvh-viewer.element.ts delete mode 100644 studio-web/src/lib/editor.element.ts delete mode 100644 studio-web/src/lib/mixamo-viewer.element.ts rename studio-web/src/lib/{studio-base.element.scss => studio.element.scss} (100%) rename studio-web/src/lib/{studio-base.element.ts => studio.element.ts} (58%) delete mode 100644 studio-web/src/lib/viewer.element.ts create mode 100644 studio/src/headless.rs create mode 100644 studio/src/wasm/init.rs diff --git a/Cargo.lock b/Cargo.lock index c46c87c..a514c1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,7 +431,7 @@ checksum = "5ec77479f56958ed1b2802c3129ebc704d12245627b2d4dae61f11e6ed5c21b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -531,7 +531,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -606,7 +606,7 @@ checksum = "f484318350462c58ba3942a45a656c1fd6b6e484a6b6b7abc3a787ad1a51e500" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -664,7 +664,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -720,7 +720,7 @@ checksum = "c6a13f388de6f4e809c15130c3251f60921ab4d0db37cc9ccce36f2296eeb73d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -882,7 +882,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash", - "syn 2.0.37", + "syn 2.0.51", "toml_edit 0.20.7", ] @@ -1184,7 +1184,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", "uuid", ] @@ -1243,7 +1243,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -1413,7 +1413,7 @@ checksum = "7aafecc952b6b8eb1a93c12590bd867d25df2f4ae1033a01dfdfc3c35ebccfff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -1495,7 +1495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e1ef1d5e328abe1b76df974245f78e17fd17867583883d5e77444c6a8223a64" dependencies = [ "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -1515,7 +1515,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -1660,7 +1660,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -1760,7 +1760,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -2235,7 +2235,7 @@ checksum = "3fe2568f851fd6144a45fa91cfed8fe5ca8fc0b56ba6797bfc1ed2771b90e37c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -2410,7 +2410,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -2535,7 +2535,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -2699,7 +2699,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -2936,7 +2936,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.5.4", "tokio", "tower-service", "tracing", @@ -3735,7 +3735,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -3873,7 +3873,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -4013,7 +4013,7 @@ checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -4125,9 +4125,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -4149,9 +4149,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -4521,7 +4521,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -4535,6 +4535,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4820,6 +4831,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_json", + "serde_repr", "space_editor", "studio-bvh", "wasm-bindgen", @@ -4856,9 +4868,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", @@ -4983,7 +4995,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -5116,7 +5128,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] @@ -5349,7 +5361,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", "wasm-bindgen-shared", ] @@ -5383,7 +5395,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5991,7 +6003,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.51", ] [[package]] diff --git a/image-generator-demo/src/app/app.element.ts b/image-generator-demo/src/app/app.element.ts index 7f0b042..e8f2dc6 100644 --- a/image-generator-demo/src/app/app.element.ts +++ b/image-generator-demo/src/app/app.element.ts @@ -6,17 +6,15 @@ import { type ScreenshotReadyEvent, StudioMode, } from "@storyteller/studio"; -import { StudioURLParams, ViewerElement } from "@storyteller/studio-web"; import { match } from "@storyteller/utility"; import { LitElement, html, nothing } from "lit"; import { customElement, state } from "lit/decorators.js"; -import { createRef, ref } from "lit/directives/ref.js"; import { DebounceSettings, debounce } from "lodash"; import "@storyteller/studio-web/canvas-provider"; import "@storyteller/studio-web/scene-hierarchy"; +import "@storyteller/studio-web/studio"; import "@storyteller/studio-web/transform-toolbar"; -import "@storyteller/studio-web/viewer"; import "./app.element.scss" @@ -44,9 +42,6 @@ export class AppElement extends LitElement { @state() imageCaptureUrl?: string; @state() realtimeCapture = true; - params = new StudioURLParams(); - viewer = createRef(); - protected override createRenderRoot = () => this; override disconnectedCallback(): void { @@ -95,13 +90,12 @@ export class AppElement extends LitElement { protected override render = () => html` - + > ${this.imageCaptureUrl != null ? html` - ${this.#params.bvh ? html` - - ` : this.#params.mixamo ? html` - - ` : this.#params.scene ? html` - - ` : html` - - `} + diff --git a/studio-web/src/index.ts b/studio-web/src/index.ts index fc74141..e97dae9 100644 --- a/studio-web/src/index.ts +++ b/studio-web/src/index.ts @@ -1,8 +1,5 @@ -export * from "./lib/bvh-viewer.element"; export * from "./lib/canvas-provider.element"; -export * from "./lib/editor.element"; -export * from "./lib/mixamo-viewer.element"; export * from "./lib/scene-hierarchy.element"; +export * from "./lib/studio.element"; export * from "./lib/transform-toolbar.element"; export * from "./lib/url-params"; -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 deleted file mode 100644 index d81f0d3..0000000 --- a/studio-web/src/lib/bvh-viewer.element.ts +++ /dev/null @@ -1,34 +0,0 @@ -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/editor.element.ts b/studio-web/src/lib/editor.element.ts deleted file mode 100644 index 19a22a1..0000000 --- a/studio-web/src/lib/editor.element.ts +++ /dev/null @@ -1,28 +0,0 @@ -import * as studio from "@storyteller/studio"; -import { ViewportSize } from "@storyteller/studio"; -import { PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -import { StudioBaseElement } from "./studio-base.element"; - -@customElement("sts-editor") -export class EditorElement extends StudioBaseElement { - @property() scenePath?: string; - - onStart(viewportSize: ViewportSize): void { - if (!this.scenePath) return; - - studio.startEditor( - this.scenePath, - `#${this._canvasProvider!.canvasId}`, - "Storyteller Studio", - viewportSize, - ); - } - - onChanges(changes: PropertyValues): void { - if (changes.has("scenePath") && !!this.scenePath) { - // TODO - } - } -} diff --git a/studio-web/src/lib/mixamo-viewer.element.ts b/studio-web/src/lib/mixamo-viewer.element.ts deleted file mode 100644 index d3c96f8..0000000 --- a/studio-web/src/lib/mixamo-viewer.element.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as studio from "@storyteller/studio"; -import { ViewportSize } from "@storyteller/studio"; -import { PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -import { StudioBaseElement } from "./studio-base.element"; - -@customElement("sts-mixamo-viewer") -export class MixamoViewerElement extends StudioBaseElement { - @property() mixamoPath?: string; - @property() skyboxId?: string; - - onStart(viewportSize: ViewportSize): void { - if (!this.mixamoPath) return; - if (!this.skyboxId) return; - - studio.startMixamoViewer( - this.mixamoPath, - this.skyboxId, - `#${this._canvasProvider!.canvasId}`, - "Storyteller Studio", - viewportSize, - ); - } - - onChanges(changes: PropertyValues): void { - if (changes.has("mixamoPath") && !!this.mixamoPath) { - // TODO - } - - if (changes.has("skyboxId") && !!this.skyboxId) - studio.setSkybox(this.skyboxId); - } -} diff --git a/studio-web/src/lib/studio-base.element.scss b/studio-web/src/lib/studio.element.scss similarity index 100% rename from studio-web/src/lib/studio-base.element.scss rename to studio-web/src/lib/studio.element.scss diff --git a/studio-web/src/lib/studio-base.element.ts b/studio-web/src/lib/studio.element.ts similarity index 58% rename from studio-web/src/lib/studio-base.element.ts rename to studio-web/src/lib/studio.element.ts index 6d49d96..63881b5 100644 --- a/studio-web/src/lib/studio-base.element.ts +++ b/studio-web/src/lib/studio.element.ts @@ -1,16 +1,28 @@ import { inject } from "@storyteller/framework"; import * as studio from "@storyteller/studio"; -import { ViewportSize } from "@storyteller/studio"; -import { LitElement, PropertyValues, html, unsafeCSS } from "lit"; - +import { + type InitOptions, + type SkyboxId, + StudioMode, + ViewportSize, +} from "@storyteller/studio"; +import { LitElement, type PropertyValues, html, unsafeCSS } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { CANVAS_PROVIDER, type CanvasProvider } from "./canvas-provider.element"; -import styles from "./studio-base.element.scss?inline"; +import styles from "./studio.element.scss?inline"; -export abstract class StudioBaseElement extends LitElement { +@customElement("sts-studio") +export class StudioElement extends LitElement { static override styles = unsafeCSS(styles); - get canvas() { return this.#canvas; } + @property() objectId?: string; + @property() bvhPath?: string; + @property() mixamoPath?: string; + @property() sceneImport?: string; + @property() storytellerScene?: string; + @property() skyboxId?: SkyboxId; + @property({ type: Number }) mode?: StudioMode; @inject(CANVAS_PROVIDER) _canvasProvider?: CanvasProvider; @@ -19,9 +31,6 @@ export abstract class StudioBaseElement extends LitElement { #resizeObserver?: ResizeObserver; #frameRequest?: number; - abstract onStart(viewportSize: ViewportSize): void; - abstract onChanges?(changes: PropertyValues): void; - override connectedCallback(): void { super.connectedCallback(); @@ -53,7 +62,7 @@ export abstract class StudioBaseElement extends LitElement { if (!studio.isInitialized()) { this.#start(); } else { - this.onChanges?.(changes); + // TODO } } @@ -75,10 +84,36 @@ export abstract class StudioBaseElement extends LitElement { #start(): void { if (!this.#canvas) return; + if (this.mode == null) return; const { width, height } = this.getBoundingClientRect(); + const options: InitOptions = { + mode: this.mode, + canvasSelector: `#${this._canvasProvider?.canvasId}`, + canvasAlt: "Storyteller Studio", + viewportSize: new ViewportSize(width, height), + }; + try { - this.onStart(new ViewportSize(width, height)); + if ( + this.skyboxId + && (this.objectId || this.bvhPath || this.mixamoPath || this.sceneImport) + ) { + options.skyboxId = this.skyboxId; + + if (this.objectId) { + studio.initFromLibrary(this.objectId, options); + } else if (this.bvhPath) { + studio.initFromBvh(this.bvhPath, options); + } else if (this.mixamoPath) { + studio.initFromMixamo(this.mixamoPath, options); + } else if (this.sceneImport) { + studio.initFromSceneImport(this.sceneImport, options); + } + } + else if (this.storytellerScene) { + studio.initFromStorytellerScene(this.storytellerScene, options); + } } catch (err) { if (!(err as Error).message?.startsWith("Using exceptions for control flow")) throw err; diff --git a/studio-web/src/lib/url-params.ts b/studio-web/src/lib/url-params.ts index a8e449a..8135957 100644 --- a/studio-web/src/lib/url-params.ts +++ b/studio-web/src/lib/url-params.ts @@ -1,4 +1,4 @@ -import { StudioMode } from "@storyteller/studio"; +import { type SkyboxId, StudioMode } from "@storyteller/studio"; import { match } from "@storyteller/utility"; export class StudioURLParams { @@ -7,9 +7,14 @@ export class StudioURLParams { return this.#_nativeParams ??= new URLSearchParams(document.location.search); } - get bvh() { return this.#nativeParams.get("bvh"); } - get mixamo() { return this.#nativeParams.get("mixamo"); } - get scene() { return this.#nativeParams.get("scene"); } + get objectId() { return this.#nativeParams.get("objectId") ?? undefined; } + get bvh() { return this.#nativeParams.get("bvh") ?? undefined; } + get mixamo() { return this.#nativeParams.get("mixamo") ?? undefined; } + get sceneImport() { return this.#nativeParams.get("sceneImport") ?? undefined; } + get scene() { return this.#nativeParams.get("scene") ?? undefined; } + get skybox() { + return this.#nativeParams.get("skybox") as SkyboxId ?? undefined; + } get mode(): StudioMode { return match(this.#nativeParams.get("mode"), { "studio": () => StudioMode.Editor, diff --git a/studio-web/src/lib/viewer.element.ts b/studio-web/src/lib/viewer.element.ts deleted file mode 100644 index ec72727..0000000 --- a/studio-web/src/lib/viewer.element.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as studio from "@storyteller/studio"; -import { StudioMode, ViewportSize } from "@storyteller/studio"; -import { PropertyValues } from "lit"; -import { customElement, property } from "lit/decorators.js"; - -import { StudioBaseElement } from "./studio-base.element"; - -@customElement("sts-viewer") -export class ViewerElement extends StudioBaseElement { - @property() objectId?: string; - @property() skyboxId?: string; - @property({ type: Number }) mode?: StudioMode; - - onStart(viewportSize: ViewportSize): void { - if (!this.objectId) return; - if (!this.skyboxId) return; - if (this.mode == null) return; - - 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); - } -} - -declare global { - export interface HTMLElementTagNameMap { - "sts-viewer": ViewerElement; - } -} diff --git a/studio/Cargo.toml b/studio/Cargo.toml index 3892c64..8303db2 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -48,6 +48,7 @@ image = "0.24.8" bytemuck = "1.14.1" space_editor = {git = "https://github.com/rewin123/space_editor", rev = "ce83f86dc902787ab16213f3209276a784654df6"} anyhow.workspace = true +serde_repr = "0.1.18" [dependencies.reqwest] version = "0.11.24" diff --git a/studio/src/headless.rs b/studio/src/headless.rs new file mode 100644 index 0000000..3b4d1de --- /dev/null +++ b/studio/src/headless.rs @@ -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::(), + )); + + match mode { + HeadlessMode::Bvh(bvh_dir, skybox) => { + app.add_plugins(BvhPlugin); + app.add_systems( + Startup, + move |mut cmd: Commands, + asset_server: Res, + mut ew: EventWriter| { + 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| { + 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, + mut load_scene_event: EventWriter| { + 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) -> bool { + *studio_state == StudioMode::Headless +} + +pub fn is_not_headless(studio_state: Res) -> 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, + mut main_camera: Query<&mut ScreenCapture, With>, + bvh_event_reader: EventReader, +) { + 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, + mut main_camera: Query<&mut ScreenCapture, With>, + 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); + } + } +} diff --git a/studio/src/lib.rs b/studio/src/lib.rs index 48939dd..560a707 100644 --- a/studio/src/lib.rs +++ b/studio/src/lib.rs @@ -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| { - 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| { - 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) -> 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::(); } - match seed { - InitSeed::FromLibrary { - object_id, - skybox_id, - } => { - app.add_systems( - Startup, - move |mut cmd: Commands, mut ew: EventWriter| { - 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| { 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::(), - )); - - 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, - mut ew: EventWriter| { - 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| { - cmd.insert_resource(SkyboxId(skybox.clone())); - ew.send(EditorEvent::Load(EditorPrefabPath::File(scene.clone()))) + move |mut ew: EventWriter| { + 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, - mut load_scene_event: EventWriter| { - 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| { + 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| { + 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| { + ew.send(MixamoImportEvent(anim.clone())); + }); + } + _ => unreachable!(), + } } - HeadlessMode::None => {} - } - - app -} - -pub fn is_headless(studio_state: Res) -> bool { - *studio_state == StudioMode::Headless -} - -pub fn is_not_headless(studio_state: Res) -> 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, - mut main_camera: Query<&mut ScreenCapture, With>, - bvh_event_reader: EventReader, -) { - 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, - mut main_camera: Query<&mut ScreenCapture, With>, - 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| { + ew.send(ImportSceneEvent(scene.clone())); + }); } } } diff --git a/studio/src/main-headless.rs b/studio/src/main-headless.rs index da01023..f21a302 100644 --- a/studio/src/main-headless.rs +++ b/studio/src/main-headless.rs @@ -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")] diff --git a/studio/src/main.rs b/studio/src/main.rs index 8ef44f3..f15a89d 100644 --- a/studio/src/main.rs +++ b/studio/src/main.rs @@ -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 diff --git a/studio/src/scene.rs b/studio/src/scene.rs index 97575a5..875373a 100644 --- a/studio/src/scene.rs +++ b/studio/src/scene.rs @@ -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::() .add_event::() .add_event::() + .add_event::() .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( #[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) { + 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, mut ew_editor: EventWriter, diff --git a/studio/src/viewport.rs b/studio/src/viewport.rs index 1c79bd0..edda86c 100644 --- a/studio/src/viewport.rs +++ b/studio/src/viewport.rs @@ -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, diff --git a/studio/src/wasm/init.rs b/studio/src/wasm/init.rs new file mode 100644 index 0000000..0fb6a21 --- /dev/null +++ b/studio/src/wasm/init.rs @@ -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, + 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::(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::(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::(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::(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::(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(()) +} diff --git a/studio/src/wasm/mod.rs b/studio/src/wasm/mod.rs index 6ec32e2..c109cad 100644 --- a/studio/src/wasm/mod.rs +++ b/studio/src/wasm/mod.rs @@ -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; diff --git a/tsconfig.base.json b/tsconfig.base.json index 7cab5a6..1157706 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -24,11 +24,8 @@ "@storyteller/studio-ui/tree": ["studio-ui/src/lib/tree.element.ts"], "@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/studio-web/editor": ["studio-web/src/lib/editor.element.ts"], - "@storyteller/studio-web/mixamo-viewer": ["studio-web/src/lib/mixamo-viewer.element.ts"], "@storyteller/studio-web/scene-hierarchy": ["studio-web/src/lib/scene-hierarchy.element.ts"], + "@storyteller/studio-web/studio": ["studio-web/src/lib/studio.element.ts"], "@storyteller/studio-web/transform-toolbar": ["studio-web/src/lib/transform-toolbar.element.ts"], "@storyteller/utility": ["utility/src/index.ts"] }