Miles RyanandArEnSc authored and GitHub committed 2024-06-27 17:46:32 -04:00
1 parent 211db3fae9
commit cc0350fe89
9 files changed
+976 -462

No files matched your search

+169
View File
@@ -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,
);
}
}
+8 -3
View File
@@ -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) {
+286 -245
View File
@@ -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;
}
}
}
+28 -1
View File
@@ -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;
}
}
+419 -156
View File
@@ -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;
}
}
+55 -39
View File
@@ -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,