diff --git a/app/pages/PageEnigma/Editor/editor.ts b/app/pages/PageEnigma/Editor/editor.ts index 4bf86c922a..926adaf890 100644 --- a/app/pages/PageEnigma/Editor/editor.ts +++ b/app/pages/PageEnigma/Editor/editor.ts @@ -40,10 +40,11 @@ import { loadingBarIsShowing, signalScene, } from "~/signals"; -import { updateObjectPanel } from "../signals"; +import { outlinerState, updateObjectPanel } from "../signals"; import { GenerationOptions } from "../models/generationOptions"; import { toEngineActions } from "../Queue/toEngineActions"; import { SceneGenereationMetaData } from "../models/sceneGenerationMetadata"; +import { SceneManager } from "./scene_manager_api"; export type EditorInitializeConfig = { sceneToken: string; @@ -138,11 +139,16 @@ class Editor { utils: Utils; videoGeneration: VideoGeneration; - mouse_controls: MouseControls; + mouse_controls: MouseControls | undefined; save_manager: SaveManager; generation_options: GenerationOptions; + sceneManager: SceneManager | undefined; + + + outliner_feature_flag: boolean; + constructor() { console.log( "If you see this message twice! then it rendered twice, if you see it once it's all good.", @@ -217,9 +223,7 @@ class Editor { this.utils = new Utils(this, this.activeScene); this.videoGeneration = new VideoGeneration(this); - this.mouse_controls = new MouseControls(this); this.save_manager = new SaveManager(this); - this.current_frame = 0; // Scene State @@ -239,6 +243,10 @@ class Editor { lipSync: false, cinematic: false, }; + + + this.outliner_feature_flag = true; + } getRenderDimensions() { switch (this.render_camera_aspect_ratio) { @@ -392,11 +400,12 @@ class Editor { this.renderer.setSize(width, height); this.renderer.setPixelRatio(window.devicePixelRatio); //document.body.appendChild(this.renderer.domElement) - window.addEventListener("resize", this.onWindowResize.bind(this)); - this.renderer.domElement.addEventListener( - "resize", - this.onWindowResize.bind(this), - ); + + // window.addEventListener("resize", this.onWindowResize.bind(this)); + // this.renderer.domElement.addEventListener( + // "resize", + // this.onWindowResize.bind(this), + // ); this._configurePostProcessing(); // Controls and movement. @@ -439,22 +448,6 @@ class Editor { this.control.setRotationSnap(0.01); console.log("Control Sensitivity:", this.control.sensitivity); - // OnClick and MouseMove events. - window.addEventListener( - "mousemove", - this.mouse_controls.onMouseMove.bind(this.mouse_controls), - false, - ); - window.addEventListener( - "click", - this.mouse_controls.onMouseClick.bind(this.mouse_controls), - false, - ); - window.addEventListener( - "keydown", - this.mouse_controls.onkeydown.bind(this.mouse_controls), - false, - ); // Base control and debug stuff remove debug in prod. if (this.control == undefined) { return; @@ -486,23 +479,30 @@ class Editor { this.timeline.scene = this.activeScene; - window.addEventListener( - "mousedown", - this.mouse_controls.onMouseDown.bind(this.mouse_controls), - false, - ); - window.addEventListener( - "mouseup", - this.mouse_controls.onMouseUp.bind(this.mouse_controls), - false, - ); - // saving state of the scene this.current_scene_media_token = null; this.current_scene_glb_media_token = null; this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); + if (this.outliner_feature_flag) { + let result = this.sceneManager?.render_outliner(this.timeline.characters); + if (result) outlinerState.items.value = result.items; + } + + this.mouse_controls = new MouseControls(this.camera, this.camera_person_mode, this.lockControls, + this.camera_last_pos, this.orbitControls, + this.selectedCanvas, this.switchPreviewToggle, this.rendering, this.togglePlayback.bind(this), + this.deleteObject.bind(this), this.canvReference, this.mouse, this.timeline.mouse, + this.raycaster, this.control, this.outlinePass, this.activeScene.scene, + this.publishSelect.bind(this), this.updateSelectedUI.bind(this), + this.transform_interaction, this.last_selected, this.getAssetType.bind(this), this.setSelected.bind(this)); + + if (this.outliner_feature_flag) { + this.sceneManager = new SceneManager(this.version, this.mouse_controls, this.activeScene, true); // Enabled dev mode. + this.mouse_controls.sceneManager = this.sceneManager; + } + // Creates the main update loop. //this.renderer.setAnimationLoop(this.updateLoop.bind(this)); @@ -585,11 +585,19 @@ class Editor { action: fromEngineActions.RESET_TIMELINE, data: null, }); + + if (this.outliner_feature_flag) { + let result = this.sceneManager?.render_outliner(this.timeline.characters); + if (result) outlinerState.items.value = result.items; + } } public async loadScene(scene_media_token: string) { await this.save_manager.loadScene(scene_media_token); - + if (this.outliner_feature_flag) { + let result = this.sceneManager?.render_outliner(this.timeline.characters); + if (result) outlinerState.items.value = result.items; + } // publish to the UI the values for the prompts and artistic style and settings? Queue.publish({ queueName: QueueNames.TO_ENGINE, @@ -598,6 +606,12 @@ class Editor { }); } + setSelected(object: THREE.Object3D[] | undefined) { + if (this.sceneManager) { + this.sceneManager.selected_objects = object; + } + } + isObjectLipsync(object_uuid: string) { return this.utils.isObjectLipsync(object_uuid); } @@ -607,7 +621,11 @@ class Editor { } lockUnlockObject(object_uuid: string): boolean { - return this.utils.lockUnlockObject(object_uuid); + let res = this.utils.lockUnlockObject(object_uuid); + if(this.outliner_feature_flag) { + this.updateSelectedUI(); + } + return res; } setColor(object_uuid: string, hex_color: string) { @@ -635,29 +653,6 @@ class Editor { }); } - /** - * This cleans up the transform controls - * During saving it - * Doesn't retain those controls. - * @returns - */ - removeTransformControls(remove_outline: boolean = true) { - if (this.control == undefined) { - return; - } - if (this.outlinePass == undefined) { - return; - } - if (remove_outline) { - this.last_selected = this.selected; - this.selected = undefined; - this.publishSelect(); - } - this.control.detach(); - this.activeScene.scene.remove(this.control); - if (remove_outline) this.outlinePass.selectedObjects = []; - } - switchCameraView() { this.utils.switchCameraView(); } @@ -737,6 +732,10 @@ class Editor { deleteObject(uuid: string) { this.utils.deleteObject(uuid); + if (this.outliner_feature_flag) { + let result = this.sceneManager?.render_outliner(this.timeline.characters); + if (result) outlinerState.items.value = result.items; + } } create_parim(name: string, pos: THREE.Vector3) { @@ -763,14 +762,14 @@ class Editor { this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 ? 1024 : this.render_camera_aspect_ratio === - CameraAspectRatio.VERTICAL_9_16 + CameraAspectRatio.VERTICAL_9_16 ? 576 : 1000; const height = this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 ? 576 : this.render_camera_aspect_ratio === - CameraAspectRatio.VERTICAL_9_16 + CameraAspectRatio.VERTICAL_9_16 ? 1024 : 1000; @@ -816,6 +815,11 @@ class Editor { if (this.cam_obj == undefined) { this.cam_obj = this.activeScene.get_object_by_name(this.camera_name); + + if (this.outliner_feature_flag) { + let result = this.sceneManager?.render_outliner(this.timeline.characters); + if (result) outlinerState.items.value = result.items; + } } if (this.clock == undefined || this.renderer == undefined) { @@ -980,21 +984,41 @@ class Editor { } updateSelectedUI() { - if (this.selected === undefined || this.timeline.is_playing) { - return; + let mainSelected; + if (this.outliner_feature_flag) { + if (this.sceneManager?.selected_objects === undefined || this.timeline.is_playing) { + return; + } + if (this.sceneManager?.selected_objects.length <= 0) { + return 0; + } + + mainSelected = this.sceneManager?.selected_objects[0]; } - const pos = this.selected.position; - const rot = this.selected.rotation; - const scale = this.selected.scale; + else { + if (this.timeline.is_playing) { + return; + } + + if (this.selected == undefined) { + return 0; + } + mainSelected = this.selected; + } + + this.selected = mainSelected; + const pos = mainSelected.position; + const rot = mainSelected.rotation; + const scale = mainSelected.scale; // TODO this is a bug we need to only show when clicked on and use UPDATE when updating. updateObjectPanel({ group: - this.selected.name === this.camera_name + mainSelected.name === this.camera_name ? ClipGroup.CAMERA : ClipGroup.OBJECT, // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips - object_uuid: this.selected.uuid, - object_name: this.selected.name, + object_uuid: mainSelected.uuid, + object_name: mainSelected.name, version: String(this.version), objectVectors: { position: { @@ -1074,26 +1098,52 @@ class Editor { } publishSelect() { - if (this.selected) { - console.log("publish", this.selected); - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.SELECT_OBJECT, - data: { - type: this.getAssetType(this.selected), - object_uuid: this.selected.uuid, - version: 1, - media_id: this.selected.id.toString(), - name: "", - } as MediaItem, - }); - return; + if (this, this.outliner_feature_flag) { + if (this.sceneManager?.selected_objects && this.sceneManager?.selected_objects?.length > 0) { + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.SELECT_OBJECT, + data: { + type: this.getAssetType(this.sceneManager?.selected_objects[0]), + object_uuid: this.sceneManager?.selected_objects[0].uuid, + version: 1, + media_id: this.sceneManager?.selected_objects[0].id.toString(), + name: "", + } as MediaItem, + }); + return; + } + else { + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.DESELECT_OBJECT, + data: null, + }); + } + } + else { + if (this.selected) { + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.SELECT_OBJECT, + data: { + type: this.getAssetType(this.selected), + object_uuid: this.selected.uuid, + version: 1, + media_id: this.selected.id.toString(), + name: "", + } as MediaItem, + }); + return; + } + else { + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.DESELECT_OBJECT, + data: null, + }); + } } - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.DESELECT_OBJECT, - data: null, - }); } } diff --git a/app/pages/PageEnigma/Editor/helper.ts b/app/pages/PageEnigma/Editor/helper.ts index 6f91476aa1..13bb9372d1 100644 --- a/app/pages/PageEnigma/Editor/helper.ts +++ b/app/pages/PageEnigma/Editor/helper.ts @@ -72,7 +72,8 @@ export class Utils { this.removeTransformControls(false); } else if (this.editor.control) { this.scene.scene.add(this.editor.control); - this.editor.control.attach(this.editor.selected); + if(this.editor.sceneManager?.selected_objects) + this.editor.control.attach(this.editor.sceneManager?.selected_objects[0]); } return object.userData["locked"]; @@ -91,7 +92,7 @@ export class Utils { } if (remove_outline) { this.editor.last_selected = this.editor.selected; - this.editor.selected = undefined; + this.editor.outlinePass.selectedObjects = []; this.editor.publishSelect(); } this.editor.control.detach(); @@ -101,16 +102,19 @@ export class Utils { // TO UPDATE selected objects in the scene might want to add to the scene ... async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) { - if (this.editor.selected != undefined || this.editor.selected != null) { - this.editor.selected.position.x = position.x; - this.editor.selected.position.y = position.y; - this.editor.selected.position.z = position.z; - this.editor.selected.rotation.x = THREE.MathUtils.degToRad(rotation.x); - this.editor.selected.rotation.y = THREE.MathUtils.degToRad(rotation.y); - this.editor.selected.rotation.z = THREE.MathUtils.degToRad(rotation.z); - this.editor.selected.scale.x = scale.x; - this.editor.selected.scale.y = scale.y; - this.editor.selected.scale.z = scale.z; + if(this.editor.sceneManager?.selected_objects){ + let object = this.editor.sceneManager?.selected_objects[0] + if (object != undefined || object != null) { + object.position.x = position.x; + object.position.y = position.y; + object.position.z = position.z; + object.rotation.x = THREE.MathUtils.degToRad(rotation.x); + object.rotation.y = THREE.MathUtils.degToRad(rotation.y); + object.rotation.z = THREE.MathUtils.degToRad(rotation.z); + object.scale.x = scale.x; + object.scale.y = scale.y; + object.scale.z = scale.z; + } } } @@ -118,7 +122,7 @@ export class Utils { this.editor.camera_person_mode = !this.editor.camera_person_mode; this.editor.cameraViewControls?.reset(); if (this.editor.cam_obj) { - if (this.editor.camera_person_mode) { + if (this.editor.camera_person_mode && this.editor.camera) { this.editor.last_cam_pos.copy(this.editor.camera.position); this.editor.last_cam_rot.copy(this.editor.camera.rotation); @@ -136,7 +140,7 @@ export class Utils { } this.editor.cam_obj.scale.set(0, 0, 0); - this.editor.removeTransformControls(); + this.removeTransformControls(); this.editor.selected = this.editor.cam_obj; this.editor.publishSelect(); this.editor.updateSelectedUI(); @@ -146,7 +150,7 @@ export class Utils { element.visible = false; }); } - } else { + } else if (this.editor.camera) { this.editor.camera.position.copy(this.editor.last_cam_pos); this.editor.camera.rotation.copy(this.editor.last_cam_rot); if (this.editor.orbitControls) { @@ -173,22 +177,49 @@ export class Utils { // Returns the "check sum" of the editors selected object. getselectedSum(): number { - if (this.editor.selected === undefined) { + if (this.editor.sceneManager?.selected_objects === undefined) { + return 0; + } + if(this.editor.sceneManager?.selected_objects.length <= 0) { return 0; } const posCombo = - this.editor.selected.position.x + - this.editor.selected.position.y + - this.editor.selected.position.z; + this.editor.sceneManager?.selected_objects[0].position.x + + this.editor.sceneManager?.selected_objects[0].position.y + + this.editor.sceneManager?.selected_objects[0].position.z; const rotCombo = - this.editor.selected.rotation.x + - this.editor.selected.rotation.y + - this.editor.selected.rotation.z; + this.editor.sceneManager?.selected_objects[0].rotation.x + + this.editor.sceneManager?.selected_objects[0].rotation.y + + this.editor.sceneManager?.selected_objects[0].rotation.z; const sclCombo = - this.editor.selected.scale.x + this.editor.selected.scale.y + this.editor.selected.scale.z; + this.editor.sceneManager?.selected_objects[0].scale.x + this.editor.sceneManager?.selected_objects[0].scale.y + this.editor.sceneManager?.selected_objects[0].scale.z; return posCombo + rotCombo + sclCombo; } + /* Will add in the future + +A good practice to remove 3D objects from Three.js scenes +function removeObject3D(object3D) { + if (!(object3D instanceof THREE.Object3D)) return false; + + // for better memory management and performance + if (object3D.geometry) object3D.geometry.dispose(); + + if (object3D.material) { + if (object3D.material instanceof Array) { + // for better memory management and performance + object3D.material.forEach(material => material.dispose()); + } else { + // for better memory management and performance + object3D.material.dispose(); + } + } + object3D.removeFromParent(); // the parent might be the scene or another Object3D, but it is sure to be removed this way + return true; +} + + */ + deleteObject(uuid: string) { const obj = this.scene.get_object_by_uuid(uuid); this.removeTransformControls(); diff --git a/app/pages/PageEnigma/Editor/keybinds_controls.ts b/app/pages/PageEnigma/Editor/keybinds_controls.ts index 17689484ae..33b1a05100 100644 --- a/app/pages/PageEnigma/Editor/keybinds_controls.ts +++ b/app/pages/PageEnigma/Editor/keybinds_controls.ts @@ -1,32 +1,137 @@ -import Editor from "./editor"; import * as THREE from "three"; -import { hideObjectPanel, hotkeysStatus, showObjectPanel } from "../signals"; +import { hideObjectPanel, hotkeysStatus, outlinerState, showObjectPanel } from "../signals"; +import { OrbitControls, OutlinePass, PointerLockControls } from "three/examples/jsm/Addons.js"; +import { TransformControls } from "./TransformControls"; +import { SceneManager, SceneObject } from "./scene_manager_api"; export class MouseControls { - editor: Editor; + camera: THREE.PerspectiveCamera | null; + camera_person_mode: boolean; + lockControls: PointerLockControls | undefined; + camera_last_pos: THREE.Vector3; + selected: THREE.Object3D[] | undefined; + orbitControls: OrbitControls | undefined; + selectedCanvas: boolean; + switchPreviewToggle: boolean; + rendering: boolean; + togglePlayback: Function; + deleteObject: Function; + canvReference: HTMLCanvasElement | null = null; + mouse: THREE.Vector2 | undefined; + timeline_mouse: THREE.Vector2 | undefined; + control: TransformControls | undefined; + raycaster: THREE.Raycaster | undefined; + outlinePass: OutlinePass | undefined; + scene: THREE.Scene; + publishSelect: Function; + updateSelectedUI: Function; + transform_interaction: boolean; + last_selected: THREE.Object3D[] | undefined; + getAssetType: Function; + setSelected: Function; + sceneManager: SceneManager | undefined; - constructor(editor: Editor) { - this.editor = editor; + constructor(camera: THREE.PerspectiveCamera | null, camera_person_mode: boolean, lockControls: PointerLockControls | undefined, + camera_last_pos: THREE.Vector3, orbitControls: OrbitControls | undefined, + selectedCanvas: boolean, switchPreviewToggle: boolean, rendering: boolean, togglePlayback: Function, + deleteObject: Function, canvReference: HTMLCanvasElement | null, + mouse: THREE.Vector2 | undefined, timeline_mouse: THREE.Vector2 | undefined, + raycaster: THREE.Raycaster | undefined, control: TransformControls | undefined, + outlinePass: OutlinePass | undefined, scene: THREE.Scene, + publishSelect: Function, updateSelectedUI: Function, + transform_interaction: boolean, last_selected: THREE.Object3D | undefined, + getAssetType: Function, setSelected: Function + ) { + this.camera = camera; + this.camera_person_mode = camera_person_mode; + this.lockControls = lockControls; + this.camera_last_pos = camera_last_pos; + this.selected = []; + this.orbitControls = orbitControls; + this.selectedCanvas = selectedCanvas; + this.switchPreviewToggle = switchPreviewToggle; + this.rendering = rendering; + this.togglePlayback = togglePlayback; + this.deleteObject = deleteObject; + this.canvReference = canvReference; + this.mouse = mouse; + this.timeline_mouse = timeline_mouse; + this.raycaster = raycaster; + this.control = control; + this.outlinePass = outlinePass; + this.scene = scene; + this.publishSelect = publishSelect; + this.updateSelectedUI = updateSelectedUI; + this.transform_interaction = transform_interaction; + this.last_selected = []; + this.getAssetType = getAssetType; + this.setSelected = setSelected; + this.sceneManager = undefined; + } + + focus() { + if(this.orbitControls){ + this.orbitControls.target.copy(this.selected[0].position); + this.orbitControls.maxDistance = 4; + this.orbitControls.update(); + this.orbitControls.maxDistance = 999; + } + } + + removeTransformControls(remove_outline: boolean = true) { + if (this.control == undefined) { + return; + } + if (this.outlinePass == undefined) { + return; + } + if (remove_outline) { + this.last_selected = this.selected; + this.selected = []; + this.publishSelect(); + } + this.control.detach(); + this.scene.remove(this.control); + if (remove_outline) this.outlinePass.selectedObjects = []; + } + + selectObject(currentObject: THREE.Object3D) { + this.setSelected(this.selected); + this.publishSelect(); + + // this.editor.update_properties() + if (currentObject.userData["locked"] !== true && this.control) { + this.scene.add(this.control); + this.control.attach(currentObject); + } + + if(this.selected && this.outlinePass){ + this.outlinePass.selectedObjects = this.selected; + } + this.transform_interaction = true; + // Contact react land + showObjectPanel(); + this.updateSelectedUI(); } onMouseDown(event: any) { - if (event.button === 1 && this.editor.camera_person_mode) { - this.editor.lockControls?.lock(); + if (event.button === 1 && this.camera_person_mode) { + this.lockControls?.lock(); } } onMouseUp(event: any) { if (event.button === 1) { - this.editor.lockControls?.unlock(); + this.lockControls?.unlock(); } - if (event.button !== 0 && this.editor.camera) { + if (event.button !== 0 && this.camera) { const camera_pos = new THREE.Vector3( - parseFloat(this.editor.camera.position.x.toFixed(2)), - parseFloat(this.editor.camera.position.y.toFixed(2)), - parseFloat(this.editor.camera.position.z.toFixed(2)), + parseFloat(this.camera.position.x.toFixed(2)), + parseFloat(this.camera.position.y.toFixed(2)), + parseFloat(this.camera.position.z.toFixed(2)), ); - this.editor.camera_last_pos.copy(camera_pos); + this.camera_last_pos.copy(camera_pos); } } @@ -34,79 +139,78 @@ export class MouseControls { if (hotkeysStatus.value.disabled) { return; } - else if (event.key === "f" && this.editor.selected && this.editor.orbitControls) { - this.editor.orbitControls.target.copy(this.editor.selected.position); - this.editor.orbitControls.maxDistance = 4; - this.editor.orbitControls.update(); - this.editor.orbitControls.maxDistance = 999; + else if (event.key === "f" && this.selected && this.orbitControls) { + this.focus(); return; } else if (event.key === " ") { - if (!this.editor.rendering && !this.editor.switchPreviewToggle && this.editor.selectedCanvas) { - this.editor.togglePlayback(); + if (!this.rendering && !this.switchPreviewToggle && this.selectedCanvas) { + this.togglePlayback(); } return; } else if (event.key === "Backspace" || event.key === "Delete") { - if (this.editor.selected) { - this.editor.deleteObject(this.editor.selected.uuid); + if (this.selected) { + this.selected.forEach(selected => { + this.deleteObject(selected.uuid); + }); } return; } else if (event.key === "t") { // transform - this.editor.control?.setMode("translate"); + this.control?.setMode("translate"); return; } else if (event.key === "r") { // rotate - this.editor.control?.setMode("rotate"); + this.control?.setMode("rotate"); return; } else if (event.key === "g") { // scale - this.editor.control?.setMode("scale"); + this.control?.setMode("scale"); return; } } // Sets new mouse location usually used in raycasts. - onMouseMove(event: any) { - if(this.editor.canvReference == undefined) { return; } - const rect = this.editor.canvReference.getBoundingClientRect(); - if (this.editor.mouse == undefined) { + onMouseMove(event: MouseEvent) { + if (this.canvReference == undefined) { return; } + const rect = this.canvReference.getBoundingClientRect(); + if (this.mouse == undefined) { return; } - this.editor.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; - this.editor.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; - this.editor.timeline.mouse = this.editor.mouse; + this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1; + this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1; + this.timeline_mouse = this.mouse; } // When the mouse clicks the screen. onMouseClick() { - if(this.editor.camera == undefined) { return; } + if (this.camera == undefined) { return; } const camera_pos = new THREE.Vector3( - parseFloat(this.editor.camera.position.x.toFixed(2)), - parseFloat(this.editor.camera.position.y.toFixed(2)), - parseFloat(this.editor.camera.position.z.toFixed(2)), + parseFloat(this.camera.position.x.toFixed(2)), + parseFloat(this.camera.position.y.toFixed(2)), + parseFloat(this.camera.position.z.toFixed(2)), ); - if (this.editor.camera_last_pos.equals(new THREE.Vector3(0, 0, 0))) { - this.editor.camera_last_pos.copy(camera_pos); + if (this.camera_last_pos.equals(new THREE.Vector3(0, 0, 0))) { + this.camera_last_pos.copy(camera_pos); } if ( - this.editor.raycaster == undefined || - this.editor.mouse == undefined || - this.editor.control == undefined || - this.editor.outlinePass == undefined || - this.editor.camera_person_mode || - !this.editor.camera_last_pos.equals(camera_pos) + this.raycaster == undefined || + this.mouse == undefined || + this.control == undefined || + this.outlinePass == undefined || + this.camera_person_mode || + !this.camera_last_pos.equals(camera_pos) ) { - this.editor.camera_last_pos.copy(camera_pos); + this.camera_last_pos.copy(camera_pos); return; } - this.editor.camera_last_pos.copy(camera_pos); + this.camera_last_pos.copy(camera_pos); - this.editor.raycaster.setFromCamera(this.editor.mouse, this.editor.camera); + this.raycaster.setFromCamera(this.mouse, this.camera); const interactable: any[] = []; - this.editor.activeScene.scene.children.forEach((child: THREE.Object3D) => { + this.scene.children.forEach((child: THREE.Object3D) => { if (child.name != "") { if ( child.type == "Mesh" || @@ -118,7 +222,7 @@ export class MouseControls { } } }); - const intersects = this.editor.raycaster.intersectObjects(interactable, true); + const intersects = this.raycaster.intersectObjects(interactable, true); if (intersects.length > 0) { if (intersects[0].object.type != "GridHelper") { @@ -126,30 +230,29 @@ export class MouseControls { while (currentObject.parent && currentObject.parent.type !== "Scene") { currentObject = currentObject.parent; } - this.editor.selected = currentObject; + + this.selected = []; + // Show panel here - - if (this.editor.selected.type == "Scene") { - this.editor.selected = intersects[0].object; + if (currentObject.type == "Scene") { + this.selected?.push(intersects[0].object); } - this.editor.activeScene.selected = this.editor.selected; - this.editor.publishSelect(); - - // this.editor.update_properties() - if (this.editor.selected.userData["locked"] !== true) { - this.editor.activeScene.scene.add(this.editor.control); - this.editor.control.attach(this.editor.selected); + else { + this.selected?.push(currentObject); } - this.editor.outlinePass.selectedObjects = [this.editor.selected]; - this.editor.transform_interaction = true; - // Contact react land - showObjectPanel(); - this.editor.updateSelectedUI(); + this.selectObject(currentObject); } } else { - this.editor.removeTransformControls(); + this.selected = []; + this.setSelected(this.selected); + this.removeTransformControls(); hideObjectPanel(); } + + if (this.sceneManager) { + const selected: SceneObject | null = this.sceneManager.selected(); + outlinerState.selectedItem.value = selected; + } } } diff --git a/app/pages/PageEnigma/Editor/save_manager.ts b/app/pages/PageEnigma/Editor/save_manager.ts index b7344edd99..a88c38ff00 100644 --- a/app/pages/PageEnigma/Editor/save_manager.ts +++ b/app/pages/PageEnigma/Editor/save_manager.ts @@ -30,7 +30,7 @@ export class SaveManager { }): Promise { this.editor.generating_preview = true; // Set this to true to stop control panel from flipping out. // remove controls when saving scene. - this.editor.removeTransformControls(); + this.editor.utils.removeTransformControls(); showEditorLoader(); const proxyScene = new StoryTellerProxyScene( @@ -38,7 +38,7 @@ export class SaveManager { this.editor.activeScene, ); const scene_json = await proxyScene.saveToScene(this.editor.version); - + console.log(scene_json); const proxyTimeline = new StoryTellerProxyTimeline( this.editor.version, this.editor.timeline, diff --git a/app/pages/PageEnigma/Editor/scene.ts b/app/pages/PageEnigma/Editor/scene.ts index 6e0a6e972e..94fe22556b 100644 --- a/app/pages/PageEnigma/Editor/scene.ts +++ b/app/pages/PageEnigma/Editor/scene.ts @@ -12,7 +12,6 @@ class Scene { gridHelper: THREE.GridHelper | undefined; scene: THREE.Scene; hot_items: THREE.Object3D[] | undefined; - selected: THREE.Object3D | undefined; // Display message message_mesh: THREE.Mesh | undefined; @@ -30,7 +29,6 @@ class Scene { this.gridHelper; this.scene = new THREE.Scene(); this.hot_items = []; - this.selected = undefined; this.message_mesh = undefined; this.loading_placeholder = undefined; @@ -136,8 +134,6 @@ class Scene { "", ); if (obj_keyframe_uuid === keyframe_uuid) { - console.log("FOUNDDDD", obj_keyframe_uuid); - console.log("Found!", object); this.scene.remove(object); return; } @@ -270,6 +266,13 @@ class Scene { } } + setVisible(object_uuid: string, visible: boolean) { + let object = this.get_object_by_uuid(object_uuid); + if (object) { + object.visible = visible; + } + } + async loadMMDWithPlaceholder( media_id: string, name: string, diff --git a/app/pages/PageEnigma/Editor/scene_manager_api.ts b/app/pages/PageEnigma/Editor/scene_manager_api.ts new file mode 100644 index 0000000000..34aaac1b90 --- /dev/null +++ b/app/pages/PageEnigma/Editor/scene_manager_api.ts @@ -0,0 +1,180 @@ +import * as THREE from "three"; +import { IconDefinition, faCamera, faCube, faPerson } from "@fortawesome/pro-solid-svg-icons"; +import Scene from "./scene"; +import { MouseControls } from "./keybinds_controls"; +import { ClipGroup } from "~/enums"; + +export type SceneObject = { + id: string; + icon: IconDefinition; + name: string; + type: string; + visible: boolean; + locked: boolean; +}; + +export interface SceneManagerAPI { + create(media_token: string, name: string, position: THREE.Vector3): void; + retrieve(object_uuid: string): void; + delete(object_uuid: string): void; + update(): void; + selected(): void; + render_outliner(timeline_characters: { [key: string]: ClipGroup }): void; + onMouseMove(event: MouseEvent): void; + onMouseClick(): void; + onKeyDown(event: KeyboardEvent): void; + onMouseDown(event: MouseEvent): void; + onMouseUp(event: MouseEvent): void; + select_object(id: string): void; +} + +export class SceneManager implements SceneManagerAPI { + scene: Scene; + mouse_controls: MouseControls; + version: number; + selected_objects: THREE.Object3D[] | undefined; + + constructor(version: number, mouse_controls: MouseControls, scene: Scene, devMode: boolean = false) { + this.mouse_controls = mouse_controls; + this.scene = scene; + this.version = version + + 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, + ); + } + } + + public async create(media_token: string, name: string, position: THREE.Vector3) { + await this.scene.loadObject(media_token, name, true, position, this.version); + } + + /* NEVER CALL THIS INTERNALLY */ + public render_outliner(timeline_characters: { [key: string]: ClipGroup }) { // needs timeline_characters to render favicons. + console.log("Render Outliner.") + // Not permanent just in place until we have multi object select ability. + let selected_item = this.selected(); + let signal_items: SceneObject[] = []; + this.scene.scene.children.forEach(child => { + const converted = this.convert_object(child, timeline_characters) + if(converted.name !== ""){ + signal_items.push(converted) + } + }); + const outlinerState = { + selectedItem: selected_item, + items: signal_items + } + return outlinerState; + } + + public async retrieve(object_uuid: string) { + return this.scene.get_object_by_uuid(object_uuid); + } + + public async update() { + // We dont use parents so we will see what this will do. + } + + public async delete(object_uuid: string) { + // Deletes an object. + this.mouse_controls.deleteObject(object_uuid); + } + + public async double_click(object_uuid: string) { + this.mouse_controls.focus(); + } + + public async hideObject(object_uuid: string) { + let object = await this.retrieve(object_uuid); + if(object?.visible !== undefined){ + object.visible = !object.visible; + object.userData["visible"] = object.visible; + } + } + + public selected() { + let selected_item = null; + if(this.selected_objects && this.selected_objects.length > 0){ + selected_item = this.selected_objects[0]; + } + if(selected_item) { + return this.convert_object(selected_item, {}); + } + return null; + } + + public select_object(id: string) { + let object = this.scene.get_object_by_uuid(id); + if(object){ + this.mouse_controls.selected = [object]; + this.mouse_controls.selectObject(object); + } + } + + // Converts a 3d object to signal item format. + private convert_object(object: THREE.Object3D, timeline_characters: { [key: string]: ClipGroup }) { + let faicon = faCube; + let name = object.name; + if (object.name == "::CAM::") { + faicon = faCamera; + name = "Camera"; + } + else if (object.uuid in timeline_characters) { + faicon = faPerson; + } + let locked = object.userData["locked"]; + if(locked == undefined) { locked = false } + return { + id: object.uuid, + icon: faicon, + name: name.charAt(0).toUpperCase() + name.slice(1), + type: object.type, + visible: object.visible, + locked: object.userData["locked"], + } + } + + public onMouseMove(event: MouseEvent) { + this.mouse_controls.onMouseMove(event); + } + + public onMouseClick() { + this.mouse_controls.onMouseClick(); + } + + public onKeyDown(event: KeyboardEvent) { + this.mouse_controls.onkeydown(event); + } + + public onMouseDown(event: MouseEvent) { + this.mouse_controls.onMouseDown(event); + } + + public onMouseUp(event: MouseEvent) { + this.mouse_controls.onMouseUp(event); + } +} diff --git a/app/pages/PageEnigma/Editor/timeline.ts b/app/pages/PageEnigma/Editor/timeline.ts index 736bc04355..c0f2b36636 100644 --- a/app/pages/PageEnigma/Editor/timeline.ts +++ b/app/pages/PageEnigma/Editor/timeline.ts @@ -27,6 +27,8 @@ import EmotionEngine from "./emotion_engine"; import { GenerationOptions } from "~/pages/PageEnigma/models/generationOptions"; import { Vector3 } from "three"; +import { outlinerState } from "../signals"; + export class TimeLine { editorEngine: Editor; timeline_items: ClipUI[]; @@ -151,22 +153,27 @@ export class TimeLine { case toEngineActions.UNMUTE: await this.mute(data.data as ClipUI, true); break; - // Create operations case toEngineActions.ADD_CHARACTER: this.addCharacter(data.data as MediaItem); + let result = this.editorEngine.sceneManager?.render_outliner(this.characters); + if(result) outlinerState.items.value = result.items; break; case toEngineActions.ADD_OBJECT: { const newObject = await this.addObject(data.data as MediaItem); this.queueNewObjectMessage(newObject, data.data as MediaItem); + let result = this.editorEngine.sceneManager?.render_outliner(this.characters); + console.log(result) + if(result) outlinerState.items.value = result.items; break; } case toEngineActions.ADD_SHAPE: { const newShape = await this.addShape(data.data as MediaItem); this.queueNewObjectMessage(newShape, data.data as MediaItem); + let result = this.editorEngine.sceneManager?.render_outliner(this.characters); + if(result) outlinerState.items.value = result.items; break; } - case toEngineActions.ENTER_PREVIEW_STATE: await this.editorEngine.switchPreview(); break; @@ -217,7 +224,7 @@ export class TimeLine { obj.position.copy(pos); const object_uuid = obj.uuid; - this.characters[object_uuid] = ClipGroup.CHARACTER; + this.characters[object_uuid] = ClipGroup.CHARACTER; // TODO: Create a class to make the idea of a character. new_data["object_uuid"] = object_uuid; Queue.publish({ @@ -297,8 +304,8 @@ export class TimeLine { const raycaster = new THREE.Raycaster(); raycaster.layers.enable(0); raycaster.layers.enable(1); - if (this.mouse && this.camera) { - raycaster.setFromCamera(this.mouse, this.camera); + if (this.editorEngine.mouse && this.camera) { + raycaster.setFromCamera(this.editorEngine.mouse, this.camera); const intersects = raycaster.intersectObjects( this.scene.scene.children, false, diff --git a/app/pages/PageEnigma/Editor/video_generation.ts b/app/pages/PageEnigma/Editor/video_generation.ts index 48964a2114..739eda105f 100644 --- a/app/pages/PageEnigma/Editor/video_generation.ts +++ b/app/pages/PageEnigma/Editor/video_generation.ts @@ -21,7 +21,7 @@ export class VideoGeneration { async generateFrame() { if (!this.editor.generating_preview && this.editor.rawRenderer) { this.editor.generating_preview = true; - this.editor.removeTransformControls(); + this.editor.utils.removeTransformControls(); this.editor.activeScene.renderMode(true); if (this.editor.activeScene.hot_items) { this.editor.activeScene.hot_items.forEach((element) => { diff --git a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index f2ea6a40d6..3aa43a82f7 100644 --- a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -236,7 +236,7 @@ export const ControlPanelSceneObject = () => {

- {currentSceneObject.object_name} + {currentSceneObject.object_name.charAt(0).toUpperCase() + currentSceneObject.object_name.slice(1)}

{ /> - - { useSignals(); @@ -173,9 +173,9 @@ export const ControlsTopButtons = () => { } }); - // const handleShowOutliner = () => { - // outlinerIsShowing.value = !outlinerIsShowing.value; - // }; + const handleShowOutliner = () => { + outlinerIsShowing.value = !outlinerIsShowing.value; + }; return (
@@ -309,7 +309,7 @@ export const ControlsTopButtons = () => { ]} /> - {/* //TODO: uncomment to show outliner button + */} + { useSignals(); @@ -28,6 +29,9 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => { const isSelected = outlinerState.selectedItem.value?.id === item.id; + + const editorEngine = useContext(EngineContext) + // Delete object logic here const handleDeleteKeyPress = (event: React.KeyboardEvent) => { if (event.key === "Delete") { @@ -38,6 +42,7 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => { // Double click logic here const handleDoubleClick = () => { console.log("Item double clicked:", item.id); + editorEngine?.sceneManager?.double_click(item.id); }; return ( @@ -51,7 +56,7 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => { onMouseLeave={() => setHovered(false)} onDoubleClick={handleDoubleClick} onKeyDown={handleDeleteKeyPress} - onClick={() => selectItem(item.id)} + onClick={() => selectItem(item.id, editorEngine?.sceneManager)} tabIndex={0} > @@ -64,7 +69,7 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => {