mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
PageScene: strip legacy diff-based undo machinery from SceneManager
Now that every mutation site records explicitly through HistoryManager, SceneManager no longer needs to own an undo stack or reverse-engineer edits from per-mouseup scene-state diffs. Removed from SceneManager: - undoStack, undoIndex, lastSceneState, copiedObject is kept - undo, redo, dropRedoHistory methods - getSceneState, is_creation, is_deletion, is_userdata, is_transformation - add_creation_undostack, check_scene_for_updates - onMouseMove/onMouseClick/onKeyDown/onMouseDown/onMouseUp wrappers (no callers — pointer events now go canvas → useViewportPointer → mouse_controls directly) - attachEventListeners/detachEventListeners (already empty stubs) - Constructor params: devMode, updateOutliner, is_character (unused after the gut) - updateOutliner field — paste() refresh moved to keymap's paste handler Also: - engine/Commands.ts deleted — its CreationSceneItem / DeletionSceneItem / TransformSceneItem / UserDataSceneItem and supporting types are obsolete (HistoryManager owns its own entry factories). - useViewportPointer.onPointerUp reverts to mouse_controls.onMouseUp directly (the sceneManager indirection only existed to call check_scene_for_updates). - Editor.initialize drops attachEventListeners/detachEventListeners calls and the three trailing constructor args. scene_manager_api.ts: 477 → 265 LOC. SceneManagerAPI interface trims to what it actually does — object lifecycle (create/retrieve/delete/ update), selection, outliner rendering. Object Panel transform-input recording is the one remaining gap from the old diff path; tracked as a follow-up.
This commit is contained in:
1 parent
39f4c84241
commit
3f3da24ab6
5 files changed
+6
-488
No files matched your search
@@ -1,169 +0,0 @@
|
||||
import { SceneManager } from "./scene_manager_api";
|
||||
import * as THREE from "three";
|
||||
|
||||
export interface ICommand<T> {
|
||||
undo(): Promise<void>;
|
||||
redo(): Promise<void>;
|
||||
}
|
||||
|
||||
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<string, any>;
|
||||
}
|
||||
|
||||
export interface DeletionData {
|
||||
object_uuid: string;
|
||||
name: string;
|
||||
position: THREE.Vector3;
|
||||
userdata: Record<string, any>;
|
||||
}
|
||||
|
||||
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<string> {
|
||||
private component: SceneManager;
|
||||
private creationData: CreationData;
|
||||
|
||||
public constructor(sceneManager: SceneManager, creationData: CreationData) {
|
||||
this.component = sceneManager;
|
||||
this.creationData = creationData;
|
||||
}
|
||||
|
||||
public async undo(): Promise<void> {
|
||||
this.component.delete(this.creationData.object_uuid);
|
||||
}
|
||||
|
||||
public async redo(): Promise<void> {
|
||||
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<string> {
|
||||
private component: SceneManager;
|
||||
private deletionData: DeletionData;
|
||||
|
||||
public constructor(sceneManager: SceneManager, creationData: DeletionData) {
|
||||
this.component = sceneManager;
|
||||
this.deletionData = creationData;
|
||||
}
|
||||
|
||||
public async undo(): Promise<void> {
|
||||
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<void> {
|
||||
this.component.delete(this.deletionData.object_uuid);
|
||||
}
|
||||
}
|
||||
|
||||
export class TransformSceneItem implements ICommand<string> {
|
||||
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<void> {
|
||||
this.component.update(
|
||||
this.transformDataStart.object_uuid,
|
||||
this.transformDataStart.position,
|
||||
this.transformDataStart.rotation,
|
||||
this.transformDataStart.scale,
|
||||
);
|
||||
}
|
||||
|
||||
public async redo(): Promise<void> {
|
||||
this.component.update(
|
||||
this.transformDataEnd.object_uuid,
|
||||
this.transformDataEnd.position,
|
||||
this.transformDataEnd.rotation,
|
||||
this.transformDataEnd.scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSceneItem implements ICommand<string> {
|
||||
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<void> {
|
||||
this.component.scene.setColor(
|
||||
this.startUserData.object_uuid,
|
||||
this.startUserData.color,
|
||||
);
|
||||
}
|
||||
|
||||
public async redo(): Promise<void> {
|
||||
this.component.scene.setColor(
|
||||
this.endUserData.object_uuid,
|
||||
this.endUserData.color,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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[] => [
|
||||
|
||||
@@ -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<CommandInputTypes>[] = [];
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
Reference in new issue
Block a user