mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 08:57:54 +00:00
Redo Undo in Editor. (#539)
   --------- Co-authored-by: ArEnSc <6252325+ArEnSc@users.noreply.github.com>
This commit is contained in:
1 parent
211db3fae9
commit
cc0350fe89
9 files changed
+976
-462
No files matched your search
@@ -0,0 +1,169 @@
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -493,9 +493,7 @@ 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,
|
||||
);
|
||||
const result = this.sceneManager?.render_outliner(this.timeline.characters);
|
||||
if (result) outlinerState.items.value = result.items;
|
||||
}
|
||||
|
||||
@@ -531,6 +529,7 @@ class Editor {
|
||||
this.mouse_controls,
|
||||
this.activeScene,
|
||||
true,
|
||||
this.updateOutliner.bind(this),
|
||||
); // Enabled dev mode.
|
||||
this.mouse_controls.sceneManager = this.sceneManager;
|
||||
}
|
||||
@@ -1029,6 +1028,12 @@ class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
updateOutliner() {
|
||||
const result = this.sceneManager?.render_outliner(this.timeline.characters);
|
||||
if (result) outlinerState.items.value = result.items;
|
||||
this.updateSelectedUI();
|
||||
}
|
||||
|
||||
updateSelectedUI() {
|
||||
let mainSelected;
|
||||
if (this.outliner_feature_flag) {
|
||||
|
||||
@@ -1,261 +1,302 @@
|
||||
import * as THREE from "three";
|
||||
import { hideObjectPanel, hotkeysStatus, outlinerState, showObjectPanel } from "../signals";
|
||||
import { OrbitControls, OutlinePass, PointerLockControls } from "three/examples/jsm/Addons.js";
|
||||
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 {
|
||||
camera: THREE.PerspectiveCamera | null;
|
||||
get_camera_person_mode: Function;
|
||||
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;
|
||||
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;
|
||||
private isProcessing: boolean = false;
|
||||
|
||||
constructor(camera: THREE.PerspectiveCamera | null, get_camera_person_mode: Function, 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.get_camera_person_mode = get_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;
|
||||
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.selected) {
|
||||
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);
|
||||
}
|
||||
|
||||
focus() {
|
||||
if(this.orbitControls){
|
||||
this.orbitControls.target.copy(this.selected[0].position);
|
||||
this.orbitControls.maxDistance = 4;
|
||||
this.orbitControls.update();
|
||||
this.orbitControls.maxDistance = 999;
|
||||
}
|
||||
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.camera_person_mode) {
|
||||
this.lockControls?.lock();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp(event: any) {
|
||||
if (event.button === 1) {
|
||||
this.lockControls?.unlock();
|
||||
}
|
||||
|
||||
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 = [];
|
||||
if (event.button !== 0 && this.camera) {
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
);
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
}
|
||||
}
|
||||
|
||||
selectObject(currentObject: THREE.Object3D) {
|
||||
this.setSelected(this.selected);
|
||||
this.publishSelect();
|
||||
sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 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.get_camera_person_mode()) {
|
||||
this.lockControls?.lock();
|
||||
console.log("LOCK")
|
||||
}
|
||||
}
|
||||
|
||||
onMouseUp(event: any) {
|
||||
if (event.button === 1) {
|
||||
this.lockControls?.unlock();
|
||||
console.log("UNLOCK")
|
||||
|
||||
}
|
||||
|
||||
if (event.button !== 0 && this.camera) {
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
);
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
}
|
||||
}
|
||||
|
||||
onkeydown(event: KeyboardEvent) {
|
||||
if (hotkeysStatus.value.disabled) {
|
||||
return;
|
||||
}
|
||||
else if (event.key === "f" && this.selected && this.orbitControls) {
|
||||
this.focus();
|
||||
return;
|
||||
}
|
||||
else if (event.key === " ") {
|
||||
if (!this.rendering && !this.switchPreviewToggle && this.selectedCanvas) {
|
||||
this.togglePlayback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (event.key === "Backspace" || event.key === "Delete") {
|
||||
if (this.selected) {
|
||||
this.selected.forEach(selected => {
|
||||
this.deleteObject(selected.uuid);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (event.key === "t") { // transform
|
||||
this.control?.setMode("translate");
|
||||
return;
|
||||
}
|
||||
else if (event.key === "r") { // rotate
|
||||
this.control?.setMode("rotate");
|
||||
return;
|
||||
}
|
||||
else if (event.key === "g") { // scale
|
||||
this.control?.setMode("scale");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets new mouse location usually used in raycasts.
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (this.canvReference == undefined) { return; }
|
||||
const rect = this.canvReference.getBoundingClientRect();
|
||||
if (this.mouse == undefined) {
|
||||
return;
|
||||
}
|
||||
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.camera == undefined) { return; }
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
);
|
||||
if (this.camera_last_pos.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
}
|
||||
|
||||
if (
|
||||
this.raycaster == undefined ||
|
||||
this.mouse == undefined ||
|
||||
this.control == undefined ||
|
||||
this.outlinePass == undefined ||
|
||||
this.camera_person_mode ||
|
||||
!this.camera_last_pos.equals(camera_pos)
|
||||
) {
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
return;
|
||||
}
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
const interactable: any[] = [];
|
||||
this.scene.children.forEach((child: THREE.Object3D) => {
|
||||
if (child.name != "") {
|
||||
if (
|
||||
child.type == "Mesh" ||
|
||||
child.type == "Object3D" ||
|
||||
child.type == "Group" ||
|
||||
child.type == "SkinnedMesh"
|
||||
) {
|
||||
interactable.push(child);
|
||||
}
|
||||
}
|
||||
async onkeydown(event: KeyboardEvent) {
|
||||
if (hotkeysStatus.value.disabled) {
|
||||
return;
|
||||
} else if (event.key === "f" && this.selected && this.orbitControls) {
|
||||
this.focus();
|
||||
return;
|
||||
} else if (event.key === " ") {
|
||||
if (!this.rendering && !this.switchPreviewToggle && this.selectedCanvas) {
|
||||
this.togglePlayback();
|
||||
}
|
||||
return;
|
||||
} else if (event.key === "Backspace" || event.key === "Delete") {
|
||||
if (this.selected) {
|
||||
this.selected.forEach((selected) => {
|
||||
this.deleteObject(selected.uuid);
|
||||
});
|
||||
const intersects = this.raycaster.intersectObjects(interactable, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.type != "GridHelper") {
|
||||
let currentObject = intersects[0].object;
|
||||
while (currentObject.parent && currentObject.parent.type !== "Scene") {
|
||||
currentObject = currentObject.parent;
|
||||
}
|
||||
|
||||
this.selected = [];
|
||||
|
||||
// Show panel here
|
||||
if (currentObject.type == "Scene") {
|
||||
this.selected?.push(intersects[0].object);
|
||||
}
|
||||
else {
|
||||
this.selected?.push(currentObject);
|
||||
}
|
||||
|
||||
this.selectObject(currentObject);
|
||||
}
|
||||
} else {
|
||||
this.selected = [];
|
||||
this.setSelected(this.selected);
|
||||
this.removeTransformControls();
|
||||
hideObjectPanel();
|
||||
}
|
||||
|
||||
if (this.sceneManager) {
|
||||
const selected: SceneObject | null = this.sceneManager.selected();
|
||||
outlinerState.selectedItem.value = selected;
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (event.key === "t") {
|
||||
// transform
|
||||
this.control?.setMode("translate");
|
||||
return;
|
||||
} else if (event.key === "r") {
|
||||
// rotate
|
||||
this.control?.setMode("rotate");
|
||||
return;
|
||||
} else if (event.key === "g") {
|
||||
// scale
|
||||
this.control?.setMode("scale");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.ctrlKey && !this.isProcessing) {
|
||||
if (event.key === "z") {
|
||||
// undo
|
||||
this.isProcessing = true;
|
||||
await this.sceneManager?.undo();
|
||||
this.isProcessing = false;
|
||||
return;
|
||||
} else if (event.key === "y") {
|
||||
// redo
|
||||
this.isProcessing = true;
|
||||
await this.sceneManager?.redo();
|
||||
this.isProcessing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets new mouse location usually used in raycasts.
|
||||
onMouseMove(event: MouseEvent) {
|
||||
if (this.canvReference == undefined) {
|
||||
return;
|
||||
}
|
||||
const rect = this.canvReference.getBoundingClientRect();
|
||||
if (this.mouse == undefined) {
|
||||
return;
|
||||
}
|
||||
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.camera == undefined) {
|
||||
return;
|
||||
}
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
);
|
||||
if (this.camera_last_pos.equals(new THREE.Vector3(0, 0, 0))) {
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
}
|
||||
|
||||
if (
|
||||
this.raycaster == undefined ||
|
||||
this.mouse == undefined ||
|
||||
this.control == undefined ||
|
||||
this.outlinePass == undefined ||
|
||||
this.camera_person_mode ||
|
||||
!this.camera_last_pos.equals(camera_pos)
|
||||
) {
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
return;
|
||||
}
|
||||
this.camera_last_pos.copy(camera_pos);
|
||||
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
const interactable: any[] = [];
|
||||
this.scene.children.forEach((child: THREE.Object3D) => {
|
||||
if (child.name != "") {
|
||||
if (
|
||||
child.type == "Mesh" ||
|
||||
child.type == "Object3D" ||
|
||||
child.type == "Group" ||
|
||||
child.type == "SkinnedMesh"
|
||||
) {
|
||||
interactable.push(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
const intersects = this.raycaster.intersectObjects(interactable, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.type != "GridHelper") {
|
||||
let currentObject = intersects[0].object;
|
||||
while (currentObject.parent && currentObject.parent.type !== "Scene") {
|
||||
currentObject = currentObject.parent;
|
||||
}
|
||||
|
||||
this.selected = [];
|
||||
|
||||
// Show panel here
|
||||
if (currentObject.type == "Scene") {
|
||||
this.selected?.push(intersects[0].object);
|
||||
} else {
|
||||
this.selected?.push(currentObject);
|
||||
}
|
||||
|
||||
this.selectObject(currentObject);
|
||||
}
|
||||
} else {
|
||||
this.selected = [];
|
||||
this.setSelected(this.selected);
|
||||
this.removeTransformControls();
|
||||
hideObjectPanel();
|
||||
}
|
||||
|
||||
if (this.sceneManager) {
|
||||
const selected: SceneObject | null = this.sceneManager.selected();
|
||||
outlinerState.selectedItem.value = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,32 @@ class Scene {
|
||||
return obj;
|
||||
}
|
||||
|
||||
get_objects_name(uuid: string): string {
|
||||
const object = this.get_object_by_uuid(uuid);
|
||||
if (object) {
|
||||
return object.name;
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
get_objects_position(uuid: string): THREE.Vector3 {
|
||||
const object = this.get_object_by_uuid(uuid);
|
||||
let pos = new THREE.Vector3(0, 0, 0);
|
||||
if (object) {
|
||||
pos = pos.copy(object.position);
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
get_objects_userdata(uuid: string): Record<string, any> {
|
||||
const object = this.get_object_by_uuid(uuid);
|
||||
let userdata = {};
|
||||
if (object) {
|
||||
userdata = object.userData;
|
||||
}
|
||||
return userdata;
|
||||
}
|
||||
|
||||
get_object_by_uuid(uuid: string) {
|
||||
return this.scene.getObjectByProperty("uuid", uuid);
|
||||
}
|
||||
@@ -272,9 +298,10 @@ class Scene {
|
||||
}
|
||||
|
||||
setVisible(object_uuid: string, visible: boolean) {
|
||||
const object = this.get_object_by_uuid(object_uuid);
|
||||
let object = this.get_object_by_uuid(object_uuid);
|
||||
if (object) {
|
||||
object.visible = visible;
|
||||
object.userData["visible"] = object.visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,180 +1,443 @@
|
||||
import * as THREE from "three";
|
||||
import { IconDefinition, faCamera, faCube, faPerson } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
IconDefinition,
|
||||
faCamera,
|
||||
faCube,
|
||||
faPerson,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import Scene from "./scene";
|
||||
import { MouseControls } from "./keybinds_controls";
|
||||
import { ClipGroup } from "~/enums";
|
||||
import {
|
||||
CommandInputTypes,
|
||||
CreationSceneItem,
|
||||
DeletionData,
|
||||
DeletionSceneItem,
|
||||
ICommand,
|
||||
SceneState,
|
||||
TransformData,
|
||||
TransformSceneItem,
|
||||
UserData,
|
||||
UserDataSceneItem,
|
||||
} from "./Commands";
|
||||
|
||||
export type SceneObject = {
|
||||
id: string;
|
||||
icon: IconDefinition;
|
||||
name: string;
|
||||
type: string;
|
||||
visible: boolean;
|
||||
locked: boolean;
|
||||
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;
|
||||
create(media_token: string, name: string, position: THREE.Vector3): void;
|
||||
retrieve(object_uuid: string): void;
|
||||
delete(object_uuid: string): void;
|
||||
update(
|
||||
object_uuid: string,
|
||||
position: THREE.Vector3,
|
||||
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;
|
||||
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;
|
||||
|
||||
constructor(version: number, mouse_controls: MouseControls, scene: Scene, devMode: boolean = false) {
|
||||
this.mouse_controls = mouse_controls;
|
||||
this.scene = scene;
|
||||
this.version = version
|
||||
constructor(
|
||||
version: number,
|
||||
mouse_controls: MouseControls,
|
||||
scene: Scene,
|
||||
devMode: boolean = false,
|
||||
updateOutliner: UpdateOutliner,
|
||||
) {
|
||||
this.mouse_controls = mouse_controls;
|
||||
this.scene = scene;
|
||||
this.version = version;
|
||||
this.lastSceneState = this.getSceneState();
|
||||
this.updateOutliner = updateOutliner;
|
||||
|
||||
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,
|
||||
);
|
||||
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 undo() {
|
||||
this.undoIndex += 1;
|
||||
if (this.undoIndex >= this.undoStack.length) {
|
||||
this.undoIndex = this.undoStack.length;
|
||||
}
|
||||
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() {
|
||||
const undoCommand = this.undoStack.at(
|
||||
this.undoStack.length - this.undoIndex,
|
||||
);
|
||||
await undoCommand?.redo();
|
||||
this.undoIndex -= 1;
|
||||
if (this.undoIndex <= -1) {
|
||||
this.undoIndex = 0;
|
||||
}
|
||||
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 create(
|
||||
media_token: string,
|
||||
name: string,
|
||||
position: THREE.Vector3,
|
||||
): Promise<THREE.Object3D<THREE.Object3DEventMap>> {
|
||||
if (media_token !== "Parim") {
|
||||
return await this.scene.loadObject(
|
||||
media_token,
|
||||
name,
|
||||
true,
|
||||
position,
|
||||
this.version,
|
||||
);
|
||||
} else {
|
||||
return await this.scene.instantiate(name, position);
|
||||
}
|
||||
}
|
||||
|
||||
/* NEVER CALL THIS INTERNALLY */
|
||||
public render_outliner(timeline_characters: { [key: string]: ClipGroup }) {
|
||||
// needs timeline_characters to render favicons.
|
||||
// Not permanent just in place until we have multi object select ability.
|
||||
const selected_item = this.selected();
|
||||
const 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(
|
||||
object_uuid: string,
|
||||
position: THREE.Vector3,
|
||||
rotation: THREE.Euler,
|
||||
scale: THREE.Vector3,
|
||||
) {
|
||||
const object = await this.retrieve(object_uuid);
|
||||
if (object) {
|
||||
object.position.copy(position);
|
||||
object.rotation.copy(rotation);
|
||||
object.scale.copy(scale);
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(object_uuid: string) {
|
||||
// Deletes an object.
|
||||
this.mouse_controls.deleteObject(object_uuid);
|
||||
}
|
||||
|
||||
public async double_click() {
|
||||
this.mouse_controls.focus();
|
||||
}
|
||||
|
||||
public async hideObject(object_uuid: string) {
|
||||
const 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) {
|
||||
const 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);
|
||||
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];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
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];
|
||||
}
|
||||
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;
|
||||
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 selected() {
|
||||
let selected_item = null;
|
||||
if(this.selected_objects && this.selected_objects.length > 0){
|
||||
selected_item = this.selected_objects[0];
|
||||
public async add_creation_undostack(object: THREE.Object3D) {
|
||||
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.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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
this.undoIndex = 0;
|
||||
this.lastSceneState = sceneState;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { GenerationOptions } from "~/pages/PageEnigma/models/generationOptions";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
import { outlinerState } from "../signals";
|
||||
import { CreationSceneItem } from "./Commands";
|
||||
|
||||
export class TimeLine {
|
||||
editorEngine: Editor;
|
||||
@@ -155,7 +156,8 @@ export class TimeLine {
|
||||
// Create operations
|
||||
case toEngineActions.ADD_CHARACTER: {
|
||||
const newObject = await this.addCharacter(data.data as MediaItem);
|
||||
this.queueNewObjectMessage(newObject, data.data as MediaItem);
|
||||
if (newObject)
|
||||
this.queueNewObjectMessage(newObject, data.data as MediaItem);
|
||||
const result = this.editorEngine.sceneManager?.render_outliner(
|
||||
this.characters,
|
||||
);
|
||||
@@ -164,7 +166,8 @@ export class TimeLine {
|
||||
}
|
||||
case toEngineActions.ADD_OBJECT: {
|
||||
const newObject = await this.addObject(data.data as MediaItem);
|
||||
this.queueNewObjectMessage(newObject, data.data as MediaItem);
|
||||
if (newObject)
|
||||
this.queueNewObjectMessage(newObject, data.data as MediaItem);
|
||||
const result = this.editorEngine.sceneManager?.render_outliner(
|
||||
this.characters,
|
||||
);
|
||||
@@ -175,7 +178,7 @@ export class TimeLine {
|
||||
case toEngineActions.ADD_SHAPE: {
|
||||
const newShape = await this.addShape(data.data as MediaItem);
|
||||
this.queueNewObjectMessage(newShape, data.data as MediaItem);
|
||||
const result = this.editorEngine.sceneManager?.render_outliner(
|
||||
let result = this.editorEngine.sceneManager?.render_outliner(
|
||||
this.characters,
|
||||
);
|
||||
if (result) outlinerState.items.value = result.items;
|
||||
@@ -219,44 +222,45 @@ export class TimeLine {
|
||||
const pos = this.getPos();
|
||||
const new_data = { ...data };
|
||||
|
||||
const obj = await this.scene.loadObject(
|
||||
const obj = await this.editorEngine.sceneManager?.create(
|
||||
media_id,
|
||||
name,
|
||||
true,
|
||||
pos,
|
||||
this.editorEngine.version,
|
||||
);
|
||||
|
||||
obj.userData["name"] = name;
|
||||
obj.name = name;
|
||||
obj.position.copy(pos);
|
||||
const object_uuid = obj.uuid;
|
||||
if (obj) {
|
||||
obj.userData["name"] = name;
|
||||
obj.name = name;
|
||||
obj.position.copy(pos);
|
||||
const object_uuid = obj.uuid;
|
||||
|
||||
this.characters[object_uuid] = ClipGroup.CHARACTER; // TODO: Create a class to make the idea of a character.
|
||||
new_data["object_uuid"] = object_uuid;
|
||||
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({
|
||||
queueName: QueueNames.FROM_ENGINE,
|
||||
action: fromEngineActions.UPDATE_CHARACTER_ID,
|
||||
data: new_data,
|
||||
});
|
||||
Queue.publish({
|
||||
queueName: QueueNames.FROM_ENGINE,
|
||||
action: fromEngineActions.UPDATE_CHARACTER_ID,
|
||||
data: new_data,
|
||||
});
|
||||
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
data.version,
|
||||
ClipType.FAKE,
|
||||
ClipGroup.CHARACTER,
|
||||
"Default",
|
||||
media_id,
|
||||
obj.uuid,
|
||||
obj.uuid,
|
||||
name,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
this.addPlayableClip(
|
||||
new ClipUI(
|
||||
data.version,
|
||||
ClipType.FAKE,
|
||||
ClipGroup.CHARACTER,
|
||||
"Default",
|
||||
media_id,
|
||||
obj.uuid,
|
||||
obj.uuid,
|
||||
name,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
);
|
||||
|
||||
await this.editorEngine.sceneManager?.add_creation_undostack(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -315,22 +319,34 @@ export class TimeLine {
|
||||
const pos = this.getPos();
|
||||
const media_id = data.media_id;
|
||||
const name = data.name;
|
||||
const obj = await this.scene.loadGlbWithPlaceholder(
|
||||
// const obj = await this.scene.loadGlbWithPlaceholder(
|
||||
// media_id,
|
||||
// name,
|
||||
// true,
|
||||
// pos,
|
||||
// this.editorEngine.version,
|
||||
// );
|
||||
|
||||
const obj = await this.editorEngine.sceneManager?.create(
|
||||
media_id,
|
||||
name,
|
||||
true,
|
||||
pos,
|
||||
this.editorEngine.version,
|
||||
);
|
||||
obj.userData["name"] = name;
|
||||
obj.name = name;
|
||||
obj.position.copy(pos);
|
||||
if (obj) {
|
||||
obj.userData["name"] = name;
|
||||
obj.name = name;
|
||||
obj.position.copy(pos);
|
||||
|
||||
await this.editorEngine.sceneManager?.add_creation_undostack(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public async addShape(data: MediaItem) {
|
||||
const pos = this.getPos();
|
||||
return this.editorEngine.create_parim(data.media_id, pos);
|
||||
const parim = this.editorEngine.create_parim(data.media_id, pos);
|
||||
await this.editorEngine.sceneManager?.add_creation_undostack(parim);
|
||||
return parim;
|
||||
}
|
||||
|
||||
public async addKeyFrame(data: Keyframe) {
|
||||
|
||||
@@ -236,8 +236,7 @@ export const ControlPanelSceneObject = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
<p className="max-w-36 truncate font-semibold">
|
||||
{currentSceneObject.object_name.charAt(0).toUpperCase() +
|
||||
currentSceneObject.object_name.slice(1)}
|
||||
{currentSceneObject.object_name.charAt(0).toUpperCase() + currentSceneObject.object_name.slice(1)}
|
||||
</p>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
|
||||
@@ -177,9 +177,9 @@ export const ControlsTopButtons = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const handleShowOutliner = () => {
|
||||
outlinerIsShowing.value = !outlinerIsShowing.value;
|
||||
};
|
||||
const handleShowOutliner = () => {
|
||||
outlinerIsShowing.value = !outlinerIsShowing.value;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-2 pt-2">
|
||||
@@ -324,6 +324,7 @@ export const ControlsTopButtons = () => {
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
<Button
|
||||
icon={outlinerIsShowing.value ? faCheckSquare : faSquare}
|
||||
className="shadow-xl"
|
||||
@@ -337,7 +338,7 @@ export const ControlsTopButtons = () => {
|
||||
onClick={handleShowOutliner}
|
||||
>
|
||||
Outliner
|
||||
</Button>
|
||||
</Button>
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
|
||||
@@ -33,7 +33,8 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => {
|
||||
|
||||
const isSelected = outlinerState.selectedItem.value?.id === item.id;
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const editorEngine = useContext(EngineContext)
|
||||
|
||||
// Delete object logic here
|
||||
const handleDeleteKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
@@ -45,7 +46,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);
|
||||
editorEngine?.sceneManager?.double_click();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -72,10 +73,7 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => {
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleLock(
|
||||
item.id,
|
||||
editorEngine?.lockUnlockObject.bind(editorEngine),
|
||||
);
|
||||
toggleLock(item.id, editorEngine?.lockUnlockObject.bind(editorEngine));
|
||||
}}
|
||||
style={{
|
||||
opacity: hovered || item.locked ? 1 : 0,
|
||||
@@ -91,12 +89,7 @@ const OutlinerItem = ({ item }: { item: SceneObject }) => {
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleVisibility(
|
||||
item.id,
|
||||
editorEngine?.sceneManager?.hideObject.bind(
|
||||
editorEngine.sceneManager,
|
||||
),
|
||||
);
|
||||
toggleVisibility(item.id, editorEngine?.sceneManager?.hideObject.bind(editorEngine.sceneManager));
|
||||
}}
|
||||
style={{
|
||||
opacity: hovered || !item.visible ? 1 : 0,
|
||||
|
||||
Reference in new issue
Block a user