diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/Commands.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/Commands.ts deleted file mode 100644 index 50bbe60715..0000000000 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/Commands.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { SceneManager } from "./scene_manager_api"; -import * as THREE from "three"; - -export interface ICommand { - undo(): Promise; - redo(): Promise; -} - -export interface TransformData { - object_uuid: string; - position: THREE.Vector3; - rotation: THREE.Euler; - scale: THREE.Vector3; -} - -export interface UserData { - object_uuid: string; - color: string; - visible: boolean; - locked: boolean; -} - -export interface CreationData { - object_uuid: string; - name: string; - position: THREE.Vector3; - userdata: Record; -} - -export interface DeletionData { - object_uuid: string; - name: string; - position: THREE.Vector3; - userdata: Record; -} - -export interface SceneState { - scene_objects: string[]; - scene_names: string[]; - scene_userdata: string[]; - scene_positions: string[]; - scene_rotations: string[]; - scene_scales: string[]; -} - -export type CommandInputTypes = TransformData | CreationData | DeletionData; - -export class CreationSceneItem implements ICommand { - private component: SceneManager; - private creationData: CreationData; - - public constructor(sceneManager: SceneManager, creationData: CreationData) { - this.component = sceneManager; - this.creationData = creationData; - } - - public async undo(): Promise { - this.component.delete(this.creationData.object_uuid); - } - - public async redo(): Promise { - const object = await this.component.create( - this.creationData.userdata["media_id"], - this.creationData.name, - this.creationData.position, - ); - object.position.copy(this.creationData.position); - object.uuid = this.creationData.object_uuid; - if (this.creationData.userdata["visible"] !== undefined) { - object.visible = this.creationData.userdata["visible"]; - } - this.component.scene.setColor( - object.uuid, - this.creationData.userdata["color"], - ); - } -} - -export class DeletionSceneItem implements ICommand { - private component: SceneManager; - private deletionData: DeletionData; - - public constructor(sceneManager: SceneManager, creationData: DeletionData) { - this.component = sceneManager; - this.deletionData = creationData; - } - - public async undo(): Promise { - const object = await this.component.create( - this.deletionData.userdata["media_id"], - this.deletionData.name, - this.deletionData.position, - ); - object.uuid = this.deletionData.object_uuid; - object.visible = this.deletionData.userdata["visible"]; - this.component.scene.setColor( - object.uuid, - this.deletionData.userdata["color"], - ); - } - - public async redo(): Promise { - this.component.delete(this.deletionData.object_uuid); - } -} - -export class TransformSceneItem implements ICommand { - private component: SceneManager; - private transformDataStart: TransformData; - private transformDataEnd: TransformData; - - public constructor( - sceneManager: SceneManager, - transformDataStart: TransformData, - transformDataEnd: TransformData, - ) { - this.component = sceneManager; - this.transformDataStart = transformDataStart; - this.transformDataEnd = transformDataEnd; - } - - public async undo(): Promise { - this.component.update( - this.transformDataStart.object_uuid, - this.transformDataStart.position, - this.transformDataStart.rotation, - this.transformDataStart.scale, - ); - } - - public async redo(): Promise { - this.component.update( - this.transformDataEnd.object_uuid, - this.transformDataEnd.position, - this.transformDataEnd.rotation, - this.transformDataEnd.scale, - ); - } -} - -export class UserDataSceneItem implements ICommand { - private component: SceneManager; - private startUserData: UserData; - private endUserData: UserData; - - public constructor( - sceneManager: SceneManager, - startUserData: UserData, - endUserData: UserData, - ) { - this.component = sceneManager; - this.startUserData = startUserData; - this.endUserData = endUserData; - } - - public async undo(): Promise { - this.component.scene.setColor( - this.startUserData.object_uuid, - this.startUserData.color, - ); - } - - public async redo(): Promise { - this.component.scene.setColor( - this.endUserData.object_uuid, - this.endUserData.color, - ); - } -} diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts index 66ba4b6228..51d7584655 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/editor.ts @@ -482,10 +482,7 @@ class Editor { this.version, this.mouse_controls, this.activeScene, - true, - this.selection.updateOutliner.bind(this.selection), - this.selection.isCharacterUuid.bind(this.selection), - ); // Enabled dev mode. + ); this.mouse_controls.sceneManager = this.sceneManager; // Add spark renderer as a child of the camera @@ -860,14 +857,12 @@ class Editor { this.isMounted = true; this.startRenderLoop(); - this.sceneManager?.attachEventListeners(); console.log("3D Editor Engine remounted"); } unmountEngine() { setIs3DSceneLoaded(false); this.stopRenderLoop(); - this.sceneManager?.detachEventListeners(); // Fix: dispose 3D contexts this.renderer?.dispose(); diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts index ba8031e3da..e6585427da 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/keymap.ts @@ -85,7 +85,9 @@ const copy = async (editor: Editor) => { const paste = async (editor: Editor) => { const obj = await editor.sceneManager?.paste(); - if (obj) editor.history.recordCreate(obj); + if (!obj) return; + editor.history.recordCreate(obj); + editor.selection.refreshOutliner(); }; export const buildKeymap = (): KeyBinding[] => [ diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts b/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts index 1324bcce96..82d9073d77 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/engine/scene_manager_api.ts @@ -11,18 +11,6 @@ import { ClipGroup } from "~/enums"; import { AssetType } from "~/enums"; import { XYZ } from "../datastructures/common"; import { usePageSceneStore } from "~/pages/PageScene/PageSceneStore"; -import { - CommandInputTypes, - CreationSceneItem, - DeletionData, - DeletionSceneItem, - ICommand, - SceneState, - TransformData, - TransformSceneItem, - UserData, - UserDataSceneItem, -} from "./Commands"; export type SceneObject = { id: string; @@ -43,105 +31,22 @@ export interface SceneManagerAPI { rotation: THREE.Euler, scale: THREE.Vector3, ): void; - undo(): void; - redo(): 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; } -type UpdateOutliner = () => void; - export class SceneManager implements SceneManagerAPI { scene: Scene; mouse_controls: MouseControls; version: number; selected_objects: THREE.Object3D[] | undefined; - private updateOutliner: UpdateOutliner; - public undoStack: ICommand[] = []; - private undoIndex: number = 0; - private lastSceneState: SceneState; private copiedObject: THREE.Object3D | undefined; - private is_character: Function; - private devMode: boolean; - constructor( - version: number, - mouse_controls: MouseControls, - scene: Scene, - devMode: boolean = false, - updateOutliner: UpdateOutliner, - is_character: Function, - ) { + constructor(version: number, mouse_controls: MouseControls, scene: Scene) { this.mouse_controls = mouse_controls; this.scene = scene; this.version = version; - this.lastSceneState = this.getSceneState(); - this.updateOutliner = updateOutliner; - this.is_character = is_character; - this.devMode = devMode; - - 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); - } - - // Pointer + keyboard listener attachment is now handled by - // hooks/useViewportPointer and hooks/useViewportKeyboard, which - // attach to the viewport canvas instead of window. The dispatch - // methods (onMouseMove etc.) below remain so MouseControls can - // continue to drive selection and FK; they're invoked by the hook. - public attachEventListeners() { - // intentionally empty — kept for back-compat with editor lifecycle - } - - public detachEventListeners() { - // intentionally empty — kept for back-compat with editor lifecycle - } - - public async undo() { - if (this.undoIndex >= this.undoStack.length) { - return; - } - this.undoIndex += 1; - const undoCommand = this.undoStack.at( - this.undoStack.length - this.undoIndex, - ); - await undoCommand?.undo(); - this.updateOutliner(); // In the future we will address this because of its relational issues with editor and the current class. - this.lastSceneState = this.getSceneState(); - } - - public async redo() { - if (this.undoIndex <= 0) { - return; - } - const undoCommand = this.undoStack.at( - this.undoStack.length - this.undoIndex, - ); - await undoCommand?.redo(); - this.undoIndex -= 1; - this.updateOutliner(); // In the future we will address this because of its relational issues with editor and the current class. - this.lastSceneState = this.getSceneState(); - } - - // Drop any redo-future before pushing a fresh command so the new action - // replaces the branch of history the user had undone past. - private dropRedoHistory() { - if (this.undoIndex > 0) { - this.undoStack = this.undoStack.slice( - 0, - this.undoStack.length - this.undoIndex, - ); - this.undoIndex = 0; - } } public async create( @@ -333,7 +238,6 @@ export class SceneManager implements SceneManagerAPI { } this.mouse_controls.selectObject(obj); - this.updateOutliner(); const store = usePageSceneStore.getState(); if (wasCharacter) { @@ -358,215 +262,4 @@ export class SceneManager implements SceneManagerAPI { return undefined; } - 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); - this.check_scene_for_updates(); - } - - private getSceneState(): SceneState { - const scene_objects: string[] = []; - const scene_names: string[] = []; - const scene_positions: string[] = []; - const scene_userdata: string[] = []; - const scene_rotations: string[] = []; - const scene_scales: string[] = []; - - this.scene.scene.children.forEach((child) => { - if (child.name != "") { - scene_objects.push(child.uuid); - scene_names.push(child.name); - scene_userdata.push(JSON.stringify(child.userData)); - scene_positions.push(JSON.stringify(child.position.clone())); - scene_rotations.push(JSON.stringify(child.rotation.clone())); - scene_scales.push(JSON.stringify(child.scale.clone())); - } - }); - return { - scene_objects: scene_objects, - scene_names: scene_names, - scene_userdata: scene_userdata, - scene_positions: scene_positions, - scene_rotations: scene_rotations, - scene_scales: scene_scales, - }; - } - - private is_creation(sceneState: SceneState): string { - let resp = ""; - sceneState.scene_objects.forEach((object) => { - if (!this.lastSceneState.scene_objects.includes(object)) { - resp = object; - return resp; - } - }); - return resp; - } - - private is_deletion(sceneState: SceneState): string { - let resp = ""; - this.lastSceneState.scene_objects.forEach((object) => { - if (sceneState.scene_objects.includes(object) == false) { - resp = object; - return resp; - } - }); - return resp; - } - - private is_userdata(sceneState: SceneState): string { - let resp = ""; - this.lastSceneState.scene_userdata.forEach((object) => { - if (sceneState.scene_userdata.includes(object) == false) { - const respIdx = this.lastSceneState.scene_userdata.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - return resp; - } - - private is_transformation(sceneState: SceneState): string { - let resp = ""; - if (resp === "") { - sceneState.scene_positions.forEach((object) => { - if (this.lastSceneState.scene_positions.includes(object) == false) { - const respIdx = sceneState.scene_positions.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - if (resp === "") { - sceneState.scene_rotations.forEach((object) => { - if (this.lastSceneState.scene_rotations.includes(object) == false) { - const respIdx = sceneState.scene_rotations.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - if (resp === "") { - sceneState.scene_scales.forEach((object) => { - if (this.lastSceneState.scene_scales.includes(object) == false) { - const respIdx = sceneState.scene_scales.indexOf(object); - resp = sceneState.scene_objects[respIdx]; - return sceneState.scene_objects[respIdx]; - } - }); - } - return resp; - } - - public async add_creation_undostack(object: THREE.Object3D) { - this.dropRedoHistory(); - this.undoStack.push( - new CreationSceneItem(this, { - object_uuid: object.uuid, - name: object.name, - position: object.position.clone(), - userdata: object.userData, - }), - ); - this.lastSceneState = this.getSceneState(); - } - - /* Can be called Internally or Externally for undo redo stack */ - // This function allows you to capture transform states and occurs on mouse up. - public async check_scene_for_updates() { - const sceneState = this.getSceneState(); - if (JSON.stringify(this.lastSceneState) !== JSON.stringify(sceneState)) { - // Add Scene state for undo redo here. - const is_deleted = this.is_deletion(sceneState); - const is_transform = this.is_transformation(sceneState); - const is_userdata = this.is_userdata(sceneState); - - this.dropRedoHistory(); - this.undoStack = this.undoStack.slice(-16); - - if (is_deleted != "") { - const objectIndex = - this.lastSceneState.scene_objects.indexOf(is_deleted); - const name = this.lastSceneState.scene_names[objectIndex]; - const position = this.lastSceneState.scene_positions[objectIndex]; - const userdata = JSON.parse( - this.lastSceneState.scene_userdata[objectIndex], - ); - - const undoPush: DeletionData = { - object_uuid: is_deleted, - name: name, - position: JSON.parse(position), - userdata: userdata, - }; - this.undoStack.push(new DeletionSceneItem(this, undoPush)); - } else if (is_userdata != "") { - const objectIndex = sceneState.scene_objects.indexOf(is_userdata); - const startUserdata = JSON.parse( - this.lastSceneState.scene_userdata[objectIndex], - ); - const userdata = JSON.parse(sceneState.scene_userdata[objectIndex]); - - const endPush: UserData = { - object_uuid: is_userdata, - color: userdata["color"], - locked: userdata["locked"], - visible: userdata["visible"], - }; - - const startPush: UserData = { - object_uuid: is_userdata, - color: startUserdata["color"], - locked: startUserdata["locked"], - visible: startUserdata["visible"], - }; - - this.undoStack.push(new UserDataSceneItem(this, startPush, endPush)); - } else if (is_transform != "") { - const objectIndexStart = - this.lastSceneState.scene_objects.indexOf(is_transform); - if ( - this.lastSceneState.scene_positions[objectIndexStart] !== undefined - ) { - const objectIndexEnd = sceneState.scene_objects.indexOf(is_transform); - - const startPush: TransformData = { - object_uuid: is_transform, - position: JSON.parse( - this.lastSceneState.scene_positions[objectIndexStart], - ), - rotation: JSON.parse( - this.lastSceneState.scene_rotations[objectIndexStart], - ), - scale: JSON.parse( - this.lastSceneState.scene_scales[objectIndexStart], - ), - }; - const endPush: TransformData = { - object_uuid: is_transform, - position: JSON.parse(sceneState.scene_positions[objectIndexEnd]), - rotation: JSON.parse(sceneState.scene_rotations[objectIndexEnd]), - scale: JSON.parse(sceneState.scene_scales[objectIndexEnd]), - }; - this.undoStack.push(new TransformSceneItem(this, startPush, endPush)); - } - } - } - this.lastSceneState = sceneState; - } } diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/hooks/useViewportPointer.ts b/frontend/apps/artcraft/app/src/pages/PageScene/hooks/useViewportPointer.ts index a03f084f3d..a0b751736b 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/hooks/useViewportPointer.ts +++ b/frontend/apps/artcraft/app/src/pages/PageScene/hooks/useViewportPointer.ts @@ -43,10 +43,7 @@ export const useViewportPointer = ( }; const onPointerUp = (e: PointerEvent) => { - // Route through sceneManager so check_scene_for_updates fires and - // transforms / deletions / userdata edits get pushed onto the undo - // stack. mouse_controls.onMouseUp is invoked from inside. - editor.sceneManager?.onMouseUp(e as unknown as MouseEvent); + editor.mouse_controls?.onMouseUp(e); }; const onClick = (_e: MouseEvent) => {