blockymodel-web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/blockymodel-web.js +2717 -0
- package/dist/blockymodel-web.js.map +1 -0
- package/dist/blockymodel-web.umd.cjs +122 -0
- package/dist/blockymodel-web.umd.cjs.map +1 -0
- package/dist/editor/Editor.d.ts +94 -0
- package/dist/editor/Editor.d.ts.map +1 -0
- package/dist/editor/History.d.ts +52 -0
- package/dist/editor/History.d.ts.map +1 -0
- package/dist/editor/SelectionManager.d.ts +57 -0
- package/dist/editor/SelectionManager.d.ts.map +1 -0
- package/dist/editor/Serializer.d.ts +44 -0
- package/dist/editor/Serializer.d.ts.map +1 -0
- package/dist/editor/TransformManager.d.ts +73 -0
- package/dist/editor/TransformManager.d.ts.map +1 -0
- package/dist/editor/commands/AddNodeCommand.d.ts +24 -0
- package/dist/editor/commands/AddNodeCommand.d.ts.map +1 -0
- package/dist/editor/commands/Command.d.ts +50 -0
- package/dist/editor/commands/Command.d.ts.map +1 -0
- package/dist/editor/commands/RemoveNodeCommand.d.ts +28 -0
- package/dist/editor/commands/RemoveNodeCommand.d.ts.map +1 -0
- package/dist/editor/commands/SetPositionCommand.d.ts +24 -0
- package/dist/editor/commands/SetPositionCommand.d.ts.map +1 -0
- package/dist/editor/commands/SetPropertyCommand.d.ts +41 -0
- package/dist/editor/commands/SetPropertyCommand.d.ts.map +1 -0
- package/dist/editor/commands/SetRotationCommand.d.ts +24 -0
- package/dist/editor/commands/SetRotationCommand.d.ts.map +1 -0
- package/dist/editor/commands/SetScaleCommand.d.ts +24 -0
- package/dist/editor/commands/SetScaleCommand.d.ts.map +1 -0
- package/dist/editor/index.d.ts +15 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/loaders/BlockyModelLoader.d.ts +48 -0
- package/dist/loaders/BlockyModelLoader.d.ts.map +1 -0
- package/dist/types/blockymodel.d.ts +72 -0
- package/dist/types/blockymodel.d.ts.map +1 -0
- package/dist/ui/HierarchyPanel.d.ts +73 -0
- package/dist/ui/HierarchyPanel.d.ts.map +1 -0
- package/dist/ui/PropertyPanel.d.ts +59 -0
- package/dist/ui/PropertyPanel.d.ts.map +1 -0
- package/dist/ui/UVEditor.d.ts +56 -0
- package/dist/ui/UVEditor.d.ts.map +1 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/viewer/ViewerController.d.ts +71 -0
- package/dist/viewer/ViewerController.d.ts.map +1 -0
- package/package.json +63 -0
- package/src/editor/Editor.ts +196 -0
- package/src/editor/History.ts +123 -0
- package/src/editor/SelectionManager.ts +183 -0
- package/src/editor/Serializer.ts +212 -0
- package/src/editor/TransformManager.ts +270 -0
- package/src/editor/commands/AddNodeCommand.ts +53 -0
- package/src/editor/commands/Command.ts +63 -0
- package/src/editor/commands/RemoveNodeCommand.ts +59 -0
- package/src/editor/commands/SetPositionCommand.ts +51 -0
- package/src/editor/commands/SetPropertyCommand.ts +100 -0
- package/src/editor/commands/SetRotationCommand.ts +51 -0
- package/src/editor/commands/SetScaleCommand.ts +51 -0
- package/src/editor/index.ts +19 -0
- package/src/index.ts +49 -0
- package/src/loaders/BlockyModelLoader.ts +281 -0
- package/src/main.ts +290 -0
- package/src/styles.css +597 -0
- package/src/types/blockymodel.ts +82 -0
- package/src/ui/HierarchyPanel.ts +343 -0
- package/src/ui/PropertyPanel.ts +434 -0
- package/src/ui/UVEditor.ts +336 -0
- package/src/ui/index.ts +4 -0
- package/src/viewer/ViewerController.ts +295 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { TransformControls } from "three/examples/jsm/controls/TransformControls.js";
|
|
3
|
+
import type { Editor } from "./Editor";
|
|
4
|
+
import { SetPositionCommand } from "./commands/SetPositionCommand";
|
|
5
|
+
import { SetRotationCommand } from "./commands/SetRotationCommand";
|
|
6
|
+
import { SetScaleCommand } from "./commands/SetScaleCommand";
|
|
7
|
+
import type { Command } from "./commands/Command";
|
|
8
|
+
|
|
9
|
+
type TransformMode = "translate" | "rotate" | "scale";
|
|
10
|
+
type TransformCallback = (...args: unknown[]) => void;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Manages transform controls for selected objects
|
|
14
|
+
*/
|
|
15
|
+
export class TransformManager {
|
|
16
|
+
private editor: Editor;
|
|
17
|
+
private controls: TransformControls;
|
|
18
|
+
private mode: TransformMode = "translate";
|
|
19
|
+
private eventListeners: Map<string, Set<TransformCallback>> = new Map();
|
|
20
|
+
|
|
21
|
+
// Track transform start state for undo
|
|
22
|
+
private transformStartPosition: THREE.Vector3 | null = null;
|
|
23
|
+
private transformStartRotation: THREE.Euler | null = null;
|
|
24
|
+
private transformStartScale: THREE.Vector3 | null = null;
|
|
25
|
+
private isTransforming: boolean = false;
|
|
26
|
+
|
|
27
|
+
constructor(editor: Editor) {
|
|
28
|
+
this.editor = editor;
|
|
29
|
+
|
|
30
|
+
// Create TransformControls
|
|
31
|
+
this.controls = new TransformControls(
|
|
32
|
+
this.editor.camera,
|
|
33
|
+
this.editor.renderer.domElement
|
|
34
|
+
);
|
|
35
|
+
this.controls.setSize(0.75);
|
|
36
|
+
this.editor.scene.add(this.controls.getHelper());
|
|
37
|
+
|
|
38
|
+
// Bind event handlers
|
|
39
|
+
this.handleDraggingChanged = this.handleDraggingChanged.bind(this);
|
|
40
|
+
this.handleObjectChange = this.handleObjectChange.bind(this);
|
|
41
|
+
|
|
42
|
+
this.controls.addEventListener("dragging-changed", this.handleDraggingChanged);
|
|
43
|
+
this.controls.addEventListener("objectChange", this.handleObjectChange);
|
|
44
|
+
|
|
45
|
+
// Setup keyboard shortcuts
|
|
46
|
+
this.handleKeyDown = this.handleKeyDown.bind(this);
|
|
47
|
+
document.addEventListener("keydown", this.handleKeyDown);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Handle dragging state changes
|
|
52
|
+
*/
|
|
53
|
+
private handleDraggingChanged(event: { value: unknown }): void {
|
|
54
|
+
const isDragging = event.value as boolean;
|
|
55
|
+
// Disable orbit controls while transforming
|
|
56
|
+
this.editor.controls.enabled = !isDragging;
|
|
57
|
+
|
|
58
|
+
if (isDragging) {
|
|
59
|
+
// Starting transform - capture initial state
|
|
60
|
+
this.captureStartState();
|
|
61
|
+
this.isTransforming = true;
|
|
62
|
+
} else if (this.isTransforming) {
|
|
63
|
+
// Ending transform - create command
|
|
64
|
+
this.createCommand();
|
|
65
|
+
this.isTransforming = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Capture the start state before transform
|
|
71
|
+
*/
|
|
72
|
+
private captureStartState(): void {
|
|
73
|
+
const object = this.controls.object;
|
|
74
|
+
if (!object) return;
|
|
75
|
+
|
|
76
|
+
this.transformStartPosition = object.position.clone();
|
|
77
|
+
this.transformStartRotation = object.rotation.clone();
|
|
78
|
+
this.transformStartScale = object.scale.clone();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a command after transform completes
|
|
83
|
+
*/
|
|
84
|
+
private createCommand(): void {
|
|
85
|
+
const object = this.controls.object;
|
|
86
|
+
if (!object) return;
|
|
87
|
+
|
|
88
|
+
let command: Command | null = null;
|
|
89
|
+
|
|
90
|
+
switch (this.mode) {
|
|
91
|
+
case "translate":
|
|
92
|
+
if (this.transformStartPosition && !object.position.equals(this.transformStartPosition)) {
|
|
93
|
+
command = new SetPositionCommand(
|
|
94
|
+
object,
|
|
95
|
+
object.position.clone(),
|
|
96
|
+
this.transformStartPosition.clone()
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case "rotate":
|
|
102
|
+
if (this.transformStartRotation) {
|
|
103
|
+
const changed =
|
|
104
|
+
object.rotation.x !== this.transformStartRotation.x ||
|
|
105
|
+
object.rotation.y !== this.transformStartRotation.y ||
|
|
106
|
+
object.rotation.z !== this.transformStartRotation.z;
|
|
107
|
+
if (changed) {
|
|
108
|
+
command = new SetRotationCommand(
|
|
109
|
+
object,
|
|
110
|
+
object.rotation.clone(),
|
|
111
|
+
this.transformStartRotation.clone()
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case "scale":
|
|
118
|
+
if (this.transformStartScale && !object.scale.equals(this.transformStartScale)) {
|
|
119
|
+
command = new SetScaleCommand(
|
|
120
|
+
object,
|
|
121
|
+
object.scale.clone(),
|
|
122
|
+
this.transformStartScale.clone()
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (command) {
|
|
129
|
+
this.emit("objectChanged", object, command);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Clear start state
|
|
133
|
+
this.transformStartPosition = null;
|
|
134
|
+
this.transformStartRotation = null;
|
|
135
|
+
this.transformStartScale = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Handle object change during transform (live updates)
|
|
140
|
+
*/
|
|
141
|
+
private handleObjectChange(): void {
|
|
142
|
+
// This fires continuously during drag - can be used for live UI updates
|
|
143
|
+
const object = this.controls.object;
|
|
144
|
+
if (object) {
|
|
145
|
+
// Emit a lightweight change event for UI updates without creating commands
|
|
146
|
+
this.emit("objectChanging", object);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handle keyboard shortcuts
|
|
152
|
+
*/
|
|
153
|
+
private handleKeyDown(event: KeyboardEvent): void {
|
|
154
|
+
// Ignore if in input field
|
|
155
|
+
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
switch (event.key.toLowerCase()) {
|
|
160
|
+
case "g":
|
|
161
|
+
this.setMode("translate");
|
|
162
|
+
break;
|
|
163
|
+
case "r":
|
|
164
|
+
if (!event.ctrlKey && !event.metaKey) {
|
|
165
|
+
this.setMode("rotate");
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case "s":
|
|
169
|
+
if (!event.ctrlKey && !event.metaKey) {
|
|
170
|
+
this.setMode("scale");
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
case "escape":
|
|
174
|
+
if (this.isTransforming) {
|
|
175
|
+
// Cancel transform - restore start state
|
|
176
|
+
this.cancelTransform();
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Cancel current transform and restore original state
|
|
184
|
+
*/
|
|
185
|
+
private cancelTransform(): void {
|
|
186
|
+
const object = this.controls.object;
|
|
187
|
+
if (!object) return;
|
|
188
|
+
|
|
189
|
+
if (this.transformStartPosition) {
|
|
190
|
+
object.position.copy(this.transformStartPosition);
|
|
191
|
+
}
|
|
192
|
+
if (this.transformStartRotation) {
|
|
193
|
+
object.rotation.copy(this.transformStartRotation);
|
|
194
|
+
}
|
|
195
|
+
if (this.transformStartScale) {
|
|
196
|
+
object.scale.copy(this.transformStartScale);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.isTransforming = false;
|
|
200
|
+
this.transformStartPosition = null;
|
|
201
|
+
this.transformStartRotation = null;
|
|
202
|
+
this.transformStartScale = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Attach controls to an object
|
|
207
|
+
*/
|
|
208
|
+
attach(object: THREE.Object3D | null): void {
|
|
209
|
+
const helper = this.controls.getHelper();
|
|
210
|
+
if (object) {
|
|
211
|
+
this.controls.attach(object);
|
|
212
|
+
helper.visible = true;
|
|
213
|
+
} else {
|
|
214
|
+
this.controls.detach();
|
|
215
|
+
helper.visible = false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Set transform mode
|
|
221
|
+
*/
|
|
222
|
+
setMode(mode: TransformMode): void {
|
|
223
|
+
this.mode = mode;
|
|
224
|
+
this.controls.setMode(mode);
|
|
225
|
+
this.emit("modeChanged", mode);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get current transform mode
|
|
230
|
+
*/
|
|
231
|
+
getMode(): TransformMode {
|
|
232
|
+
return this.mode;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Get TransformControls instance
|
|
237
|
+
*/
|
|
238
|
+
getControls(): TransformControls {
|
|
239
|
+
return this.controls;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Add event listener
|
|
244
|
+
*/
|
|
245
|
+
on(event: string, callback: TransformCallback): void {
|
|
246
|
+
if (!this.eventListeners.has(event)) {
|
|
247
|
+
this.eventListeners.set(event, new Set());
|
|
248
|
+
}
|
|
249
|
+
this.eventListeners.get(event)!.add(callback);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Emit event
|
|
254
|
+
*/
|
|
255
|
+
private emit(event: string, ...args: unknown[]): void {
|
|
256
|
+
this.eventListeners.get(event)?.forEach((callback) => callback(...args));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Clean up
|
|
261
|
+
*/
|
|
262
|
+
dispose(): void {
|
|
263
|
+
document.removeEventListener("keydown", this.handleKeyDown);
|
|
264
|
+
this.controls.removeEventListener("dragging-changed", this.handleDraggingChanged);
|
|
265
|
+
this.controls.removeEventListener("objectChange", this.handleObjectChange);
|
|
266
|
+
this.controls.dispose();
|
|
267
|
+
this.editor.scene.remove(this.controls.getHelper());
|
|
268
|
+
this.eventListeners.clear();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to add a child node to a parent
|
|
6
|
+
*/
|
|
7
|
+
export class AddNodeCommand extends BaseCommand {
|
|
8
|
+
readonly type = "AddNode";
|
|
9
|
+
readonly updatable = false;
|
|
10
|
+
|
|
11
|
+
private parent: THREE.Object3D;
|
|
12
|
+
private child: THREE.Object3D;
|
|
13
|
+
private index: number;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
parent: THREE.Object3D,
|
|
17
|
+
child: THREE.Object3D,
|
|
18
|
+
index?: number
|
|
19
|
+
) {
|
|
20
|
+
super(parent, false);
|
|
21
|
+
this.parent = parent;
|
|
22
|
+
this.child = child;
|
|
23
|
+
this.index = index ?? parent.children.length;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
execute(): void {
|
|
27
|
+
this.parent.add(this.child);
|
|
28
|
+
// Reorder to correct index if needed
|
|
29
|
+
if (this.index < this.parent.children.length - 1) {
|
|
30
|
+
const children = this.parent.children;
|
|
31
|
+
children.splice(children.indexOf(this.child), 1);
|
|
32
|
+
children.splice(this.index, 0, this.child);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
undo(): void {
|
|
37
|
+
this.parent.remove(this.child);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the added child
|
|
42
|
+
*/
|
|
43
|
+
getChild(): THREE.Object3D {
|
|
44
|
+
return this.child;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the parent
|
|
49
|
+
*/
|
|
50
|
+
getParent(): THREE.Object3D {
|
|
51
|
+
return this.parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base interface for all editor commands
|
|
5
|
+
* Commands implement the command pattern for undo/redo support
|
|
6
|
+
*/
|
|
7
|
+
export interface Command {
|
|
8
|
+
/**
|
|
9
|
+
* Unique identifier for this command type
|
|
10
|
+
*/
|
|
11
|
+
readonly type: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The object this command operates on
|
|
15
|
+
*/
|
|
16
|
+
readonly object: THREE.Object3D;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Execute the command (apply the new state)
|
|
20
|
+
*/
|
|
21
|
+
execute(): void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Undo the command (restore the previous state)
|
|
25
|
+
*/
|
|
26
|
+
undo(): void;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether this command can be merged with subsequent commands of the same type
|
|
30
|
+
* Used for rapid updates (e.g., continuous dragging)
|
|
31
|
+
*/
|
|
32
|
+
readonly updatable: boolean;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Update this command with a new end state
|
|
36
|
+
* Only called if updatable is true and commands are of same type on same object
|
|
37
|
+
*/
|
|
38
|
+
update?(command: Command): void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Timestamp when this command was created
|
|
42
|
+
*/
|
|
43
|
+
readonly timestamp: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Base class providing common functionality for commands
|
|
48
|
+
*/
|
|
49
|
+
export abstract class BaseCommand implements Command {
|
|
50
|
+
abstract readonly type: string;
|
|
51
|
+
readonly object: THREE.Object3D;
|
|
52
|
+
readonly updatable: boolean;
|
|
53
|
+
readonly timestamp: number;
|
|
54
|
+
|
|
55
|
+
constructor(object: THREE.Object3D, updatable: boolean = false) {
|
|
56
|
+
this.object = object;
|
|
57
|
+
this.updatable = updatable;
|
|
58
|
+
this.timestamp = Date.now();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
abstract execute(): void;
|
|
62
|
+
abstract undo(): void;
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to remove a node from its parent
|
|
6
|
+
*/
|
|
7
|
+
export class RemoveNodeCommand extends BaseCommand {
|
|
8
|
+
readonly type = "RemoveNode";
|
|
9
|
+
readonly updatable = false;
|
|
10
|
+
|
|
11
|
+
private parent: THREE.Object3D;
|
|
12
|
+
private child: THREE.Object3D;
|
|
13
|
+
private index: number;
|
|
14
|
+
|
|
15
|
+
constructor(child: THREE.Object3D) {
|
|
16
|
+
super(child, false);
|
|
17
|
+
if (!child.parent) {
|
|
18
|
+
throw new Error("Cannot remove node without parent");
|
|
19
|
+
}
|
|
20
|
+
this.parent = child.parent;
|
|
21
|
+
this.child = child;
|
|
22
|
+
this.index = this.parent.children.indexOf(child);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
execute(): void {
|
|
26
|
+
this.parent.remove(this.child);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
undo(): void {
|
|
30
|
+
this.parent.add(this.child);
|
|
31
|
+
// Restore to original index
|
|
32
|
+
if (this.index < this.parent.children.length) {
|
|
33
|
+
const children = this.parent.children;
|
|
34
|
+
children.splice(children.indexOf(this.child), 1);
|
|
35
|
+
children.splice(this.index, 0, this.child);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the removed child
|
|
41
|
+
*/
|
|
42
|
+
getChild(): THREE.Object3D {
|
|
43
|
+
return this.child;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the parent
|
|
48
|
+
*/
|
|
49
|
+
getParent(): THREE.Object3D {
|
|
50
|
+
return this.parent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the original index
|
|
55
|
+
*/
|
|
56
|
+
getIndex(): number {
|
|
57
|
+
return this.index;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand, type Command } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to set an object's position
|
|
6
|
+
*/
|
|
7
|
+
export class SetPositionCommand extends BaseCommand {
|
|
8
|
+
readonly type = "SetPosition";
|
|
9
|
+
readonly updatable = true;
|
|
10
|
+
|
|
11
|
+
private newPosition: THREE.Vector3;
|
|
12
|
+
private oldPosition: THREE.Vector3;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
object: THREE.Object3D,
|
|
16
|
+
newPosition: THREE.Vector3,
|
|
17
|
+
oldPosition: THREE.Vector3
|
|
18
|
+
) {
|
|
19
|
+
super(object, true);
|
|
20
|
+
this.newPosition = newPosition.clone();
|
|
21
|
+
this.oldPosition = oldPosition.clone();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute(): void {
|
|
25
|
+
this.object.position.copy(this.newPosition);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
undo(): void {
|
|
29
|
+
this.object.position.copy(this.oldPosition);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(command: Command): void {
|
|
33
|
+
if (command instanceof SetPositionCommand) {
|
|
34
|
+
this.newPosition.copy(command.newPosition);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the new position
|
|
40
|
+
*/
|
|
41
|
+
getNewPosition(): THREE.Vector3 {
|
|
42
|
+
return this.newPosition.clone();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the old position
|
|
47
|
+
*/
|
|
48
|
+
getOldPosition(): THREE.Vector3 {
|
|
49
|
+
return this.oldPosition.clone();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand, type Command } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to set a property on an object
|
|
6
|
+
*/
|
|
7
|
+
export class SetPropertyCommand extends BaseCommand {
|
|
8
|
+
readonly type = "SetProperty";
|
|
9
|
+
readonly updatable = true;
|
|
10
|
+
|
|
11
|
+
private propertyPath: string;
|
|
12
|
+
private newValue: unknown;
|
|
13
|
+
private oldValue: unknown;
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
object: THREE.Object3D,
|
|
17
|
+
propertyPath: string,
|
|
18
|
+
newValue: unknown,
|
|
19
|
+
oldValue: unknown
|
|
20
|
+
) {
|
|
21
|
+
super(object, true);
|
|
22
|
+
this.propertyPath = propertyPath;
|
|
23
|
+
this.newValue = this.cloneValue(newValue);
|
|
24
|
+
this.oldValue = this.cloneValue(oldValue);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clone a value if it's an object/array
|
|
29
|
+
*/
|
|
30
|
+
private cloneValue(value: unknown): unknown {
|
|
31
|
+
if (value === null || value === undefined) return value;
|
|
32
|
+
if (typeof value === "object") {
|
|
33
|
+
return JSON.parse(JSON.stringify(value));
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a nested property by path (e.g., "userData.size.x")
|
|
40
|
+
*/
|
|
41
|
+
private getProperty(obj: unknown, path: string): unknown {
|
|
42
|
+
const parts = path.split(".");
|
|
43
|
+
let current = obj as Record<string, unknown>;
|
|
44
|
+
for (const part of parts) {
|
|
45
|
+
if (current === null || current === undefined) return undefined;
|
|
46
|
+
current = current[part] as Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Set a nested property by path
|
|
53
|
+
*/
|
|
54
|
+
private setProperty(obj: unknown, path: string, value: unknown): void {
|
|
55
|
+
const parts = path.split(".");
|
|
56
|
+
let current = obj as Record<string, unknown>;
|
|
57
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
58
|
+
if (current[parts[i]] === undefined) {
|
|
59
|
+
current[parts[i]] = {};
|
|
60
|
+
}
|
|
61
|
+
current = current[parts[i]] as Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
current[parts[parts.length - 1]] = value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
execute(): void {
|
|
67
|
+
this.setProperty(this.object, this.propertyPath, this.cloneValue(this.newValue));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
undo(): void {
|
|
71
|
+
this.setProperty(this.object, this.propertyPath, this.cloneValue(this.oldValue));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
update(command: Command): void {
|
|
75
|
+
if (command instanceof SetPropertyCommand && command.propertyPath === this.propertyPath) {
|
|
76
|
+
this.newValue = command.newValue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the property path
|
|
82
|
+
*/
|
|
83
|
+
getPropertyPath(): string {
|
|
84
|
+
return this.propertyPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get the new value
|
|
89
|
+
*/
|
|
90
|
+
getNewValue(): unknown {
|
|
91
|
+
return this.cloneValue(this.newValue);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the old value
|
|
96
|
+
*/
|
|
97
|
+
getOldValue(): unknown {
|
|
98
|
+
return this.cloneValue(this.oldValue);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand, type Command } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to set an object's rotation
|
|
6
|
+
*/
|
|
7
|
+
export class SetRotationCommand extends BaseCommand {
|
|
8
|
+
readonly type = "SetRotation";
|
|
9
|
+
readonly updatable = true;
|
|
10
|
+
|
|
11
|
+
private newRotation: THREE.Euler;
|
|
12
|
+
private oldRotation: THREE.Euler;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
object: THREE.Object3D,
|
|
16
|
+
newRotation: THREE.Euler,
|
|
17
|
+
oldRotation: THREE.Euler
|
|
18
|
+
) {
|
|
19
|
+
super(object, true);
|
|
20
|
+
this.newRotation = newRotation.clone();
|
|
21
|
+
this.oldRotation = oldRotation.clone();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute(): void {
|
|
25
|
+
this.object.rotation.copy(this.newRotation);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
undo(): void {
|
|
29
|
+
this.object.rotation.copy(this.oldRotation);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(command: Command): void {
|
|
33
|
+
if (command instanceof SetRotationCommand) {
|
|
34
|
+
this.newRotation.copy(command.newRotation);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the new rotation
|
|
40
|
+
*/
|
|
41
|
+
getNewRotation(): THREE.Euler {
|
|
42
|
+
return this.newRotation.clone();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the old rotation
|
|
47
|
+
*/
|
|
48
|
+
getOldRotation(): THREE.Euler {
|
|
49
|
+
return this.oldRotation.clone();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { BaseCommand, type Command } from "./Command";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Command to set an object's scale
|
|
6
|
+
*/
|
|
7
|
+
export class SetScaleCommand extends BaseCommand {
|
|
8
|
+
readonly type = "SetScale";
|
|
9
|
+
readonly updatable = true;
|
|
10
|
+
|
|
11
|
+
private newScale: THREE.Vector3;
|
|
12
|
+
private oldScale: THREE.Vector3;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
object: THREE.Object3D,
|
|
16
|
+
newScale: THREE.Vector3,
|
|
17
|
+
oldScale: THREE.Vector3
|
|
18
|
+
) {
|
|
19
|
+
super(object, true);
|
|
20
|
+
this.newScale = newScale.clone();
|
|
21
|
+
this.oldScale = oldScale.clone();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
execute(): void {
|
|
25
|
+
this.object.scale.copy(this.newScale);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
undo(): void {
|
|
29
|
+
this.object.scale.copy(this.oldScale);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
update(command: Command): void {
|
|
33
|
+
if (command instanceof SetScaleCommand) {
|
|
34
|
+
this.newScale.copy(command.newScale);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the new scale
|
|
40
|
+
*/
|
|
41
|
+
getNewScale(): THREE.Vector3 {
|
|
42
|
+
return this.newScale.clone();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the old scale
|
|
47
|
+
*/
|
|
48
|
+
getOldScale(): THREE.Vector3 {
|
|
49
|
+
return this.oldScale.clone();
|
|
50
|
+
}
|
|
51
|
+
}
|