From 165078ddb4b648d92fe3d9bb1526a87606d75bbd Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Fri, 9 May 2025 17:44:56 -0700 Subject: [PATCH] Add remount logic to event listeners for 3D --- .../src/components/signaled/TopBar/TopBar.tsx | 28 +++++- .../app/src/pages/PageEnigma/Editor/editor.ts | 98 +++++++++++++------ .../src/pages/PageEnigma/Editor/free_cam.ts | 30 ++++-- .../PageEnigma/Editor/scene_manager_api.ts | 48 ++++++--- .../app/src/pages/PageEnigma/PageEditor.tsx | 5 +- .../app/src/pages/PageEnigma/PageStyling.tsx | 3 +- .../apps/editor3d/app/src/signals/appTab.ts | 5 + 7 files changed, 159 insertions(+), 58 deletions(-) diff --git a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx index bda128fe8e..34dbda4de4 100644 --- a/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx +++ b/frontend/apps/editor3d/app/src/components/signaled/TopBar/TopBar.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { faChevronLeft, faGear, @@ -18,6 +18,7 @@ import { TabSelector, TabItem } from "@storyteller/ui-tab-selector"; import { Signal } from "@preact/signals-react"; import { useSignals } from "@preact/signals-react/runtime"; import { setLogoutStates } from "~/signals/authentication/utilities"; +import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; function isEditorPath(path: string) { if (path === "/") return true; @@ -29,6 +30,7 @@ interface Props { pageName: string; appTabIdSignal: Signal; setAppTabId: (id: string) => void; + is3DInitSignal: Signal; } const appTabs: TabItem[] = [ @@ -36,7 +38,7 @@ const appTabs: TabItem[] = [ { id: "2D", label: "2D" }, ]; -export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => { +export const TopBar = ({ pageName, appTabIdSignal, setAppTabId, is3DInitSignal }: Props) => { useSignals(); const currentLocation = getCurrentLocationWithoutParams( @@ -47,6 +49,22 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => { const [activeLibraryTab, setActiveLibraryTab] = useState("my-media"); const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); + const engine3D = useContext(EngineContext); + const handleTabChange = (tabId: string) => { + if (appTabIdSignal.peek() == "3D") { + engine3D?.unmountEngine(); + } + setAppTabId(tabId); + } + + const currentAppTabId = appTabIdSignal.value; + const is3DInit = is3DInitSignal.value; + useEffect(() => { + if (currentAppTabId == "3D" && is3DInit) { + engine3D?.remountEngine(); + } + }, [currentAppTabId, engine3D, is3DInit]); + return ( <>
@@ -68,13 +86,13 @@ export const TopBar = ({ pageName, appTabIdSignal, setAppTabId }: Props) => { Back to Editor )} - {/* setAppTabId(tabId)} + onTabChange={handleTabChange} className="w-fit" - /> */} + />
diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/editor.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/editor.ts index 891a314e04..bad23acca6 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/editor.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/editor.ts @@ -57,6 +57,7 @@ import { BufferType, EngineFrameBuffers } from "./VideoProcessor/engine_buffer"; import Stats from "three/examples/jsm/libs/stats.module.js"; import { CharacterAnimationEngine } from "./Engines/CharacterAnimationEngine"; import { cameras, selectedCameraId } from "~/pages/PageEnigma/signals/camera"; +import { is3DEditorInitialized, setIs3DEditorInitialized } from "~/signals/appTab"; export type EditorInitializeConfig = { sceneToken: string; @@ -200,6 +201,7 @@ class Editor { // Allows us to cancel the queued render private renderEventToken: number; private shouldRender: boolean; + private isMounted: boolean; constructor() { this.processingHasFailed = false; @@ -261,7 +263,7 @@ class Editor { this.last_selected_sum = 0; this.selectedCanvas = false; this.renderEventToken = -1; - this.shouldRender = true; + this.shouldRender = false; // Audio Engine Test. this.render_camera_aspect_ratio = CameraAspectRatio.HORIZONTAL_3_2; @@ -601,13 +603,6 @@ class Editor { this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); - if (this.outliner_feature_flag) { - const result = this.sceneManager?.render_outliner( - this.timeline.characters, - ); - if (result) outlinerState.items.value = result.items; - } - this.mouse_controls = new MouseControls( this.camera, this.get_camera_person_mode.bind(this), @@ -648,10 +643,19 @@ class Editor { this.mouse_controls.sceneManager = this.sceneManager; } - // Creates the main update loop. - //this.renderer.setAnimationLoop(this.updateLoop.bind(this)); + if (this.outliner_feature_flag) { + const result = this.sceneManager?.render_outliner( + this.timeline.characters, + ); + if (result) outlinerState.items.value = result.items; + } - this.startRenderLoop(); + // Attach freecam state controller + this.mouseOverEventHandler = this.mouseOverEventHandler.bind(this); + + // This will enable all event and render loops + // We'll disable it here so the UI events can control is manually + // this.remountEngine(); if (!this.utils.isEmpty(sceneToken)) { this.loadScene(sceneToken); @@ -664,24 +668,6 @@ class Editor { }); } - document.addEventListener("mouseover", (event) => { - if (this.cameraViewControls) { - if ( - event.target instanceof HTMLCanvasElement || - (event.target as HTMLElement).id == "letterbox" - ) { - this.cameraViewControls.enabled = true; - this.selectedCanvas = true; - this.focused = true; - } else { - this.cameraViewControls.reset(); - this.focused = false; - this.cameraViewControls.enabled = false; - this.selectedCanvas = false; - } - } - }); - this._configurePostProcessingRaw(); loadingBarData.value = { @@ -689,6 +675,8 @@ class Editor { progress: 100, }; loadingBarIsShowing.value = false; + + setIs3DEditorInitialized(true); } public isMovable(): boolean { @@ -699,6 +687,32 @@ class Editor { document.body.appendChild(this.stats.dom); } + private mouseOverEventHandler(event: MouseEvent) { + if (this.cameraViewControls) { + if ( + event.target instanceof HTMLCanvasElement || + (event.target as HTMLElement).id == "letterbox" + ) { + this.cameraViewControls.enabled = true; + this.selectedCanvas = true; + this.focused = true; + } else { + this.cameraViewControls.reset(); + this.focused = false; + this.cameraViewControls.enabled = false; + this.selectedCanvas = false; + } + } + } + + public enableFreeCamControls() { + document.addEventListener("mouseover", this.mouseOverEventHandler); + } + + public disableFreeCamControls() { + document.removeEventListener("mouseover", this.mouseOverEventHandler); + } + // Captures the scene without the grid public snapShotOfCurrentFrame(shouldDownload: boolean = true) { if (!this.renderer?.domElement || !this.camera) { @@ -1404,7 +1418,9 @@ class Editor { startRenderLoop() { if (this.shouldRender) { console.warn("Render flag is already true"); + return; } + this.shouldRender = true; this.updateLoop(); } @@ -1418,6 +1434,30 @@ class Editor { } } + remountEngine() { + if (!is3DEditorInitialized.peek()) { + console.log("3D mount: Wait for initialization"); + return; + } + + if (this.isMounted) { + console.log("3D already mounted, skipping"); + return; + } + + this.startRenderLoop(); + this.sceneManager?.attachEventListeners(); + this.enableFreeCamControls(); + this.cameraViewControls?.attachEventListeners(); + } + + unmountEngine() { + this.stopRenderLoop(); + this.sceneManager?.detachEventListeners(); + this.disableFreeCamControls(); + this.cameraViewControls?.detachEventListeners(); + } + change_mode(type: "translate" | "rotate" | "scale") { if (this.control == undefined) { return; diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/free_cam.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/free_cam.ts index fbd7e6d375..4baab71412 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/free_cam.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/free_cam.ts @@ -83,6 +83,10 @@ class FreeCam extends EventDispatcher { this.keydown = this.keydown.bind(this); this.keyup = this.keyup.bind(this); + this.mousedown = this.mousedown.bind(this); + this.mouseup = this.mouseup.bind(this); + this.mousemove = this.mousemove.bind(this); + this.mousewheel = this.mousewheel.bind(this); this.reset = this.reset.bind(this); this.update = this.update.bind(this); this.updateMovementVector = this.updateMovementVector.bind(this); @@ -94,18 +98,28 @@ class FreeCam extends EventDispatcher { event.preventDefault(); }); - window.addEventListener("keydown", this.keydown.bind(this)); - window.addEventListener("keyup", this.keyup.bind(this)); - - window.addEventListener("mousedown", this.mousedown.bind(this)); - window.addEventListener("mouseup", this.mouseup.bind(this)); - window.addEventListener("mousemove", this.mousemove.bind(this)); - window.addEventListener("wheel", this.mousewheel.bind(this)); - this.updateMovementVector(); this.updateRotationVector(); } + attachEventListeners() { + window.addEventListener("keydown", this.keydown); + window.addEventListener("keyup", this.keyup); + window.addEventListener("mousedown", this.mousedown); + window.addEventListener("mouseup", this.mouseup); + window.addEventListener("mousemove", this.mousemove); + window.addEventListener("wheel", this.mousewheel); + } + + detachEventListeners() { + window.removeEventListener("keydown", this.keydown); + window.removeEventListener("keyup", this.keyup); + window.removeEventListener("mousedown", this.mousedown); + window.removeEventListener("mouseup", this.mouseup); + window.removeEventListener("mousemove", this.mousemove); + window.removeEventListener("wheel", this.mousewheel); + } + keydown(event: KeyboardEvent) { if (!this.enabled || isPromptBoxFocused.value) { return; diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts index a3d023b7c2..b76d53826f 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts @@ -65,6 +65,7 @@ export class SceneManager implements SceneManagerAPI { private lastSceneState: SceneState; private copiedObject: THREE.Object3D | undefined; private is_character: Function; + private devMode: boolean; constructor( version: number, @@ -80,14 +81,35 @@ export class SceneManager implements SceneManagerAPI { this.lastSceneState = this.getSceneState(); this.updateOutliner = updateOutliner; this.is_character = is_character; + this.devMode = devMode; - if (devMode) { - window.addEventListener("mousemove", this.onMouseMove.bind(this), false); - window.addEventListener("click", this.onMouseClick.bind(this), false); - window.addEventListener("keydown", this.onKeyDown.bind(this), false); - window.addEventListener("mousedown", this.onMouseDown.bind(this), false); - window.addEventListener("mouseup", this.onMouseUp.bind(this), false); + this.onMouseMove = this.onMouseMove.bind(this); + this.onMouseClick = this.onMouseClick.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onMouseDown = this.onMouseDown.bind(this); + this.onMouseUp = this.onMouseUp.bind(this); + } + + public attachEventListeners() { + if (!this.devMode) { + return; } + window.addEventListener("mousemove", this.onMouseMove, false); + window.addEventListener("click", this.onMouseClick, false); + window.addEventListener("keydown", this.onKeyDown, false); + window.addEventListener("mousedown", this.onMouseDown, false); + window.addEventListener("mouseup", this.onMouseUp, false); + } + + public detachEventListeners() { + if (!this.devMode) { + return; + } + window.removeEventListener("mousemove", this.onMouseMove, false); + window.removeEventListener("click", this.onMouseClick, false); + window.removeEventListener("keydown", this.onKeyDown, false); + window.removeEventListener("mousedown", this.onMouseDown, false); + window.removeEventListener("mouseup", this.onMouseUp, false); } public async undo() { @@ -121,7 +143,7 @@ export class SceneManager implements SceneManagerAPI { name: string, position: THREE.Vector3, ): Promise> { - if (media_token.includes("SKY::")){ + if (media_token.includes("SKY::")) { const token = media_token.replace("SKY::", ""); this.scene.updateSkybox(token); } @@ -243,16 +265,16 @@ export class SceneManager implements SceneManagerAPI { } public async copy() { - // TODO MAKE BETTER FIX: Temp disables copy and paste of characters. + // TODO MAKE BETTER FIX: Temp disables copy and paste of characters. const object = this.mouse_controls.selected?.at(0) - if(object !== undefined ){ - if(this.is_character(object.uuid) === false) - this.copiedObject = object; + if (object !== undefined) { + if (this.is_character(object.uuid) === false) + this.copiedObject = object; } } public async paste() { - if(this.copiedObject && this.copiedObject.name != "::CAM::") { + if (this.copiedObject && this.copiedObject.name != "::CAM::") { const userdata = this.copiedObject.userData; const position = this.copiedObject.position.clone(); const rotation = this.copiedObject.rotation.clone(); @@ -264,7 +286,7 @@ export class SceneManager implements SceneManagerAPI { const obj = await this.create(media_id, name, position); this.scene.setColor(obj.uuid, color); - obj.position.copy(position.add(new THREE.Vector3(0.5,0.0,0.5))); + obj.position.copy(position.add(new THREE.Vector3(0.5, 0.0, 0.5))); obj.rotation.copy(rotation); obj.scale.copy(scale); diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx index bddf096d24..e9203455d8 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEditor.tsx @@ -28,7 +28,7 @@ import { PromptBox3D } from "@storyteller/ui-promptbox"; import { PopoverItem } from "@storyteller/ui-popover"; import { OnboardingHelper } from "./comps/OnboardingHelper"; import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDisplay"; -import { appTabId, setAppTabId } from "~/signals/appTab"; +import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab"; import { KonvaCanvasContainer } from "../Page2d/KonvaCanvasContainer"; import { KonvaRootComponent } from "../Page2d/KonvaRootComponent"; import { @@ -265,6 +265,7 @@ export const PageEditor = () => { pageName="Edit Scene" appTabIdSignal={appTabId} setAppTabId={setAppTabId} + is3DInitSignal={is3DEditorInitialized} /> {appTabId.value === "3D" ? ( @@ -366,7 +367,7 @@ export const PageEditor = () => { )} {}} + onClose={() => { }} videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4" videoSrc3D="/resources/videos/artcraft-3d-demo.mp4" openAiLogo="/resources/images/openai-logo.png" diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageStyling.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageStyling.tsx index 5908006a03..abde2343ca 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageStyling.tsx +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageStyling.tsx @@ -18,7 +18,7 @@ import { StyleSelection } from "~/pages/PageEnigma/comps/StyleSelection"; import { StyleMoreOptions } from "~/pages/PageEnigma/comps/StyleMoreOptions/StyleMoreOptions"; import Queue, { QueueNames } from "~/pages/PageEnigma/Queue"; import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions"; -import { appTabId, setAppTabId } from "~/signals/appTab"; +import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab"; export const PageStyling = () => { useSignals(); @@ -64,6 +64,7 @@ export const PageStyling = () => { pageName="Stylization" appTabIdSignal={appTabId} setAppTabId={setAppTabId} + is3DInitSignal={is3DEditorInitialized} />
diff --git a/frontend/apps/editor3d/app/src/signals/appTab.ts b/frontend/apps/editor3d/app/src/signals/appTab.ts index 7891d8bbb8..aeb2f94340 100644 --- a/frontend/apps/editor3d/app/src/signals/appTab.ts +++ b/frontend/apps/editor3d/app/src/signals/appTab.ts @@ -10,3 +10,8 @@ export const setAppTabId = (newId: string) => { appTabId.value = newId; }; + +export const is3DEditorInitialized = signal(false); +export const setIs3DEditorInitialized = (isInitialized: boolean) => { + is3DEditorInitialized.value = isInitialized; +}