mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-18 07:35:19 +00:00
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:
Generated
+45
-33
@@ -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]]
|
||||
|
||||
@@ -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<ViewerElement>();
|
||||
|
||||
protected override createRenderRoot = () => this;
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
@@ -95,13 +90,12 @@ export class AppElement extends LitElement {
|
||||
|
||||
protected override render = () => html`
|
||||
<sts-canvas-provider class="app-root">
|
||||
<sts-viewer
|
||||
${ref(this.viewer)}
|
||||
<sts-studio
|
||||
class="studio-scene"
|
||||
objectId="base-human-female.gltf"
|
||||
skyboxId="test_scene"
|
||||
.mode=${StudioMode.Editor}
|
||||
></sts-viewer>
|
||||
></sts-studio>
|
||||
|
||||
${this.imageCaptureUrl != null ? html`
|
||||
<img
|
||||
|
||||
@@ -20,11 +20,8 @@ import "@storyteller/studio-ui/icon";
|
||||
import "@storyteller/studio-ui/toolbar";
|
||||
import "@storyteller/studio-ui/tree";
|
||||
import "@storyteller/studio-web/canvas-provider";
|
||||
import "@storyteller/studio-web/viewer";
|
||||
import "@storyteller/studio-web/bvh-viewer";
|
||||
import "@storyteller/studio-web/editor";
|
||||
import "@storyteller/studio-web/mixamo-viewer";
|
||||
import "@storyteller/studio-web/scene-hierarchy";
|
||||
import "@storyteller/studio-web/studio";
|
||||
import "@storyteller/studio-web/transform-toolbar";
|
||||
import "./anim-editor.element";
|
||||
import "./help.element";
|
||||
@@ -85,27 +82,15 @@ export class AppElement extends LitElement {
|
||||
${ref(this.#canvasProvider as any)}
|
||||
>
|
||||
<!-- Scene view -->
|
||||
${this.#params.bvh ? html`
|
||||
<sts-bvh-viewer
|
||||
bvhPath=${this.#params.bvh}
|
||||
skyboxId="test_scene"
|
||||
></sts-bvh-viewer>
|
||||
` : this.#params.mixamo ? html`
|
||||
<sts-mixamo-viewer
|
||||
mixamoPath=${this.#params.mixamo}
|
||||
skyboxId="test_scene"
|
||||
></sts-mixamo-viewer>
|
||||
` : this.#params.scene ? html`
|
||||
<sts-editor
|
||||
scenePath=${this.#params.scene}
|
||||
></sts-editor>
|
||||
` : html`
|
||||
<sts-viewer
|
||||
objectId="sample-room.gltf"
|
||||
skyboxId="test_scene"
|
||||
.mode=${this.#params.mode}
|
||||
></sts-viewer>
|
||||
`}
|
||||
<sts-studio
|
||||
.objectId=${this.#params.objectId}
|
||||
.bvhPath=${this.#params.bvh}
|
||||
.mixamoPath=${this.#params.mixamo}
|
||||
.sceneImport=${this.#params.sceneImport}
|
||||
.storytellerScene=${this.#params.scene}
|
||||
.skyboxId=${this.#params.skybox ?? "test_scene"}
|
||||
.mode=${this.#params.mode}
|
||||
></sts-studio>
|
||||
|
||||
<!-- Actions toolbar -->
|
||||
<sts-toolbar class="v-start h-start">
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<this>): void {
|
||||
if (changes.has("bvhPath") && !!this.bvhPath) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
if (changes.has("skyboxId") && !!this.skyboxId)
|
||||
studio.setSkybox(this.skyboxId);
|
||||
}
|
||||
}
|
||||
@@ -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<this>): void {
|
||||
if (changes.has("scenePath") && !!this.scenePath) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<this>): void {
|
||||
if (changes.has("mixamoPath") && !!this.mixamoPath) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
if (changes.has("skyboxId") && !!this.skyboxId)
|
||||
studio.setSkybox(this.skyboxId);
|
||||
}
|
||||
}
|
||||
@@ -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<this>): 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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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<this>): 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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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,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
@@ -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
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+1
-4
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user