react-three-game 0.0.65 → 0.0.67
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 +2 -660
- package/README.md +164 -91
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/shared/GameCanvas.js +1 -1
- package/dist/tools/assetviewer/page.d.ts +13 -2
- package/dist/tools/assetviewer/page.js +61 -7
- package/dist/tools/dragdrop/index.d.ts +1 -1
- package/dist/tools/dragdrop/modelLoader.d.ts +2 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
- package/dist/tools/prefabeditor/EditorTree.js +17 -3
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +3 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +7 -8
- package/dist/tools/prefabeditor/EditorUI.js +3 -7
- package/dist/tools/prefabeditor/GameEvents.d.ts +14 -1
- package/dist/tools/prefabeditor/GameEvents.js +2 -1
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -0
- package/dist/tools/prefabeditor/InstanceProvider.js +44 -12
- package/dist/tools/prefabeditor/PrefabEditor.js +77 -16
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +9 -6
- package/dist/tools/prefabeditor/PrefabRoot.js +52 -126
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ClickComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/ClickComponent.js +45 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +0 -3
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -3
- package/dist/tools/prefabeditor/components/Input.d.ts +5 -2
- package/dist/tools/prefabeditor/components/Input.js +71 -38
- package/dist/tools/prefabeditor/components/MaterialComponent.js +4 -69
- package/dist/tools/prefabeditor/components/ModelComponent.js +5 -80
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +2 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +77 -10
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +9 -7
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/types.d.ts +1 -0
- package/dist/tools/prefabeditor/utils.d.ts +7 -1
- package/dist/tools/prefabeditor/utils.js +34 -1
- package/package.json +1 -1
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type { RapierRigidBody } from '@react-three/rapier';
|
|
2
2
|
/** Physics event types (built-in) */
|
|
3
3
|
export type PhysicsEventType = 'sensor:enter' | 'sensor:exit' | 'collision:enter' | 'collision:exit';
|
|
4
|
+
export type InteractionEventType = 'click';
|
|
4
5
|
/** Payload for physics events */
|
|
5
6
|
export interface PhysicsEventPayload {
|
|
6
7
|
sourceEntityId: string;
|
|
7
8
|
targetEntityId: string | null;
|
|
8
9
|
targetRigidBody: RapierRigidBody | null | undefined;
|
|
9
10
|
}
|
|
11
|
+
export interface ClickEventPayload {
|
|
12
|
+
sourceEntityId: string;
|
|
13
|
+
instanceEntityId?: string;
|
|
14
|
+
point: [number, number, number];
|
|
15
|
+
button: number;
|
|
16
|
+
altKey: boolean;
|
|
17
|
+
ctrlKey: boolean;
|
|
18
|
+
metaKey: boolean;
|
|
19
|
+
shiftKey: boolean;
|
|
20
|
+
}
|
|
10
21
|
/**
|
|
11
22
|
* Register your custom event types here by extending this interface:
|
|
12
23
|
*
|
|
@@ -22,6 +33,7 @@ export interface GameEventMap {
|
|
|
22
33
|
'sensor:exit': PhysicsEventPayload;
|
|
23
34
|
'collision:enter': PhysicsEventPayload;
|
|
24
35
|
'collision:exit': PhysicsEventPayload;
|
|
36
|
+
'click': ClickEventPayload;
|
|
25
37
|
}
|
|
26
38
|
/** All registered event types */
|
|
27
39
|
export type GameEventType = keyof GameEventMap | (string & {});
|
|
@@ -31,11 +43,12 @@ type EventHandler<T = unknown> = (payload: T) => void;
|
|
|
31
43
|
/**
|
|
32
44
|
* Game event system for all game interactions.
|
|
33
45
|
*
|
|
34
|
-
* Built-in
|
|
46
|
+
* Built-in events:
|
|
35
47
|
* - sensor:enter - Something entered a sensor collider
|
|
36
48
|
* - sensor:exit - Something exited a sensor collider
|
|
37
49
|
* - collision:enter - A collision started
|
|
38
50
|
* - collision:exit - A collision ended
|
|
51
|
+
* - click - A prefab entity with a Click component was clicked in play mode
|
|
39
52
|
*
|
|
40
53
|
* Custom events:
|
|
41
54
|
* - Emit any event type with any payload
|
|
@@ -4,11 +4,12 @@ const subscribers = new Map();
|
|
|
4
4
|
/**
|
|
5
5
|
* Game event system for all game interactions.
|
|
6
6
|
*
|
|
7
|
-
* Built-in
|
|
7
|
+
* Built-in events:
|
|
8
8
|
* - sensor:enter - Something entered a sensor collider
|
|
9
9
|
* - sensor:exit - Something exited a sensor collider
|
|
10
10
|
* - collision:enter - A collision started
|
|
11
11
|
* - collision:exit - A collision ended
|
|
12
|
+
* - click - A prefab entity with a Click component was clicked in play mode
|
|
12
13
|
*
|
|
13
14
|
* Custom events:
|
|
14
15
|
* - Emit any event type with any payload
|
|
@@ -12,6 +12,8 @@ export declare function getRepeatAxesFromModelProperties(properties: Record<stri
|
|
|
12
12
|
export type InstanceData = {
|
|
13
13
|
id: string;
|
|
14
14
|
sourceId: string;
|
|
15
|
+
clickable?: boolean;
|
|
16
|
+
locked?: boolean;
|
|
15
17
|
position: [number, number, number];
|
|
16
18
|
rotation: [number, number, number];
|
|
17
19
|
scale: [number, number, number];
|
|
@@ -32,7 +34,9 @@ export declare function useInstanceCheck(id: string): boolean;
|
|
|
32
34
|
export declare const GameInstance: React.ForwardRefExoticComponent<{
|
|
33
35
|
id: string;
|
|
34
36
|
sourceId?: string;
|
|
37
|
+
clickable?: boolean;
|
|
35
38
|
modelUrl: string;
|
|
39
|
+
locked?: boolean;
|
|
36
40
|
position: [number, number, number];
|
|
37
41
|
rotation: [number, number, number];
|
|
38
42
|
scale: [number, number, number];
|
|
@@ -129,9 +129,23 @@ function emitCollisionExit(sourceId, payload) {
|
|
|
129
129
|
targetRigidBody: payload.other.rigidBody,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
+
function emitClick(sourceId, instanceId, event) {
|
|
133
|
+
gameEvents.emit('click', {
|
|
134
|
+
sourceEntityId: sourceId,
|
|
135
|
+
instanceEntityId: instanceId && instanceId !== sourceId ? instanceId : undefined,
|
|
136
|
+
point: [event.point.x, event.point.y, event.point.z],
|
|
137
|
+
button: event.button,
|
|
138
|
+
altKey: event.nativeEvent.altKey,
|
|
139
|
+
ctrlKey: event.nativeEvent.ctrlKey,
|
|
140
|
+
metaKey: event.nativeEvent.metaKey,
|
|
141
|
+
shiftKey: event.nativeEvent.shiftKey,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
132
144
|
function instanceEquals(a, b) {
|
|
133
145
|
return a.id === b.id &&
|
|
134
146
|
a.sourceId === b.sourceId &&
|
|
147
|
+
a.clickable === b.clickable &&
|
|
148
|
+
a.locked === b.locked &&
|
|
135
149
|
a.meshPath === b.meshPath &&
|
|
136
150
|
arrayEquals(a.position, b.position) &&
|
|
137
151
|
arrayEquals(a.rotation, b.rotation) &&
|
|
@@ -308,22 +322,30 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
|
|
|
308
322
|
}, [group.instances]);
|
|
309
323
|
// Handle click on instanced mesh in edit mode
|
|
310
324
|
const handleClick = (e) => {
|
|
311
|
-
if (!editMode || !onSelect)
|
|
312
|
-
return;
|
|
313
|
-
e.stopPropagation();
|
|
314
|
-
// Get the instance index from the intersection
|
|
315
325
|
const instanceId = e.instanceId;
|
|
316
|
-
|
|
317
|
-
|
|
326
|
+
const instance = instanceId !== undefined ? group.instances[instanceId] : undefined;
|
|
327
|
+
if (!instance)
|
|
328
|
+
return;
|
|
329
|
+
if (editMode) {
|
|
330
|
+
if (!onSelect || instance.locked)
|
|
331
|
+
return;
|
|
332
|
+
e.stopPropagation();
|
|
333
|
+
onSelect(instance.sourceId);
|
|
334
|
+
return;
|
|
318
335
|
}
|
|
336
|
+
if (!instance.clickable)
|
|
337
|
+
return;
|
|
338
|
+
e.stopPropagation();
|
|
339
|
+
emitClick(instance.sourceId, instance.id, e);
|
|
319
340
|
};
|
|
341
|
+
const shouldHandleClick = editMode || group.instances.some(inst => inst.clickable);
|
|
320
342
|
// Add key to force remount when instance count changes significantly (helps with cleanup)
|
|
321
343
|
const rigidBodyKey = `rb_${modelKey}_${group.instances.map(inst => `${inst.id}:${getPhysicsSignature(inst.physics)}`).join('|')}`;
|
|
322
344
|
return (_jsx(InstancedRigidBodies, { ref: rigidBodiesRef, instances: instances, children: Array.from({ length: partCount }).map((_, i) => {
|
|
323
345
|
const mesh = flatMeshes[`${modelKey}__${i}`];
|
|
324
346
|
if (!mesh)
|
|
325
347
|
return null;
|
|
326
|
-
return (_jsx("instancedMesh", { ref: el => { meshRefs.current[i] = el; }, args: [mesh.geometry, mesh.material, group.instances.length], castShadow: true, receiveShadow: true, frustumCulled: false, onClick:
|
|
348
|
+
return (_jsx("instancedMesh", { ref: el => { meshRefs.current[i] = el; }, args: [mesh.geometry, mesh.material, group.instances.length], castShadow: true, receiveShadow: true, frustumCulled: false, onClick: shouldHandleClick ? handleClick : undefined }, i));
|
|
327
349
|
}) }, rigidBodyKey));
|
|
328
350
|
}
|
|
329
351
|
// Render non-physics instances using Merged (instancing without rigid bodies)
|
|
@@ -336,7 +358,10 @@ function NonPhysicsInstancedGroup({ modelKey, group, partCount, instancesMap, on
|
|
|
336
358
|
function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef, selectedId, editMode }) {
|
|
337
359
|
const clickValid = useRef(false);
|
|
338
360
|
const groupRef = useRef(null);
|
|
361
|
+
const isLocked = Boolean(instance.locked);
|
|
339
362
|
const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
|
|
363
|
+
const canSelect = editMode && !isLocked;
|
|
364
|
+
const canClick = !editMode && Boolean(instance.clickable);
|
|
340
365
|
// Use BoxHelper when object is selected in edit mode
|
|
341
366
|
useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
|
|
342
367
|
useEffect(() => {
|
|
@@ -345,13 +370,18 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef
|
|
|
345
370
|
registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
|
|
346
371
|
return () => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, null);
|
|
347
372
|
}, [editMode, instance.id, registerRef]);
|
|
348
|
-
return (_jsx("group", { ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale, onPointerDown: (e) => { e.stopPropagation(); clickValid.current = true; }, onPointerMove: () => { clickValid.current = false; }, onPointerUp: (e) => {
|
|
373
|
+
return (_jsx("group", { ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale, onPointerDown: canSelect || canClick ? (e) => { e.stopPropagation(); clickValid.current = true; } : undefined, onPointerMove: canSelect || canClick ? () => { clickValid.current = false; } : undefined, onPointerUp: canSelect || canClick ? (e) => {
|
|
349
374
|
if (clickValid.current) {
|
|
350
375
|
e.stopPropagation();
|
|
351
|
-
|
|
376
|
+
if (editMode) {
|
|
377
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.sourceId);
|
|
378
|
+
}
|
|
379
|
+
else if (instance.clickable) {
|
|
380
|
+
emitClick(instance.sourceId, instance.id, e);
|
|
381
|
+
}
|
|
352
382
|
}
|
|
353
383
|
clickValid.current = false;
|
|
354
|
-
}, children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) }));
|
|
384
|
+
} : undefined, children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) }));
|
|
355
385
|
}
|
|
356
386
|
// Hook to check if an instance exists
|
|
357
387
|
export function useInstanceCheck(id) {
|
|
@@ -360,19 +390,21 @@ export function useInstanceCheck(id) {
|
|
|
360
390
|
return (_a = ctx === null || ctx === void 0 ? void 0 : ctx.hasInstance(id)) !== null && _a !== void 0 ? _a : false;
|
|
361
391
|
}
|
|
362
392
|
// GameInstance component: registers an instance for batch rendering (renders nothing itself)
|
|
363
|
-
export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, position, rotation, scale, physics = undefined, }, ref) => {
|
|
393
|
+
export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
|
|
364
394
|
const ctx = useContext(GameInstanceContext);
|
|
365
395
|
const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
|
|
366
396
|
const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
|
|
367
397
|
const instance = useMemo(() => ({
|
|
368
398
|
id,
|
|
369
399
|
sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
|
|
400
|
+
clickable,
|
|
401
|
+
locked,
|
|
370
402
|
meshPath: modelUrl,
|
|
371
403
|
position,
|
|
372
404
|
rotation,
|
|
373
405
|
scale,
|
|
374
406
|
physics,
|
|
375
|
-
}), [id, sourceId, modelUrl, JSON.stringify(position), JSON.stringify(rotation), JSON.stringify(scale), getPhysicsSignature(physics)]);
|
|
407
|
+
}), [id, sourceId, clickable, locked, modelUrl, JSON.stringify(position), JSON.stringify(rotation), JSON.stringify(scale), getPhysicsSignature(physics)]);
|
|
376
408
|
useEffect(() => {
|
|
377
409
|
if (!addInstance || !removeInstance)
|
|
378
410
|
return;
|
|
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { MapControls, TransformControls } from "@react-three/drei";
|
|
11
12
|
import GameCanvas from "../../shared/GameCanvas";
|
|
12
13
|
import { useState, useRef, useEffect, forwardRef, useImperativeHandle } from "react";
|
|
13
14
|
import PrefabRoot from "./PrefabRoot";
|
|
@@ -15,7 +16,7 @@ import { Physics } from "@react-three/rapier";
|
|
|
15
16
|
import EditorUI from "./EditorUI";
|
|
16
17
|
import { base, toolbar } from "./styles";
|
|
17
18
|
import { EditorContext } from "./EditorContext";
|
|
18
|
-
import { createImageNode, createModelNode, exportGLB as exportSceneGLB, exportGLBData, insertNode } from "./utils";
|
|
19
|
+
import { computeParentWorldMatrix, createImageNode, createModelNode, decompose, exportGLB as exportSceneGLB, exportGLBData, findNode, focusCameraOnObject, insertNode, updateNode } from "./utils";
|
|
19
20
|
import { loadFiles } from "../dragdrop";
|
|
20
21
|
const DEFAULT_PREFAB = {
|
|
21
22
|
id: "prefab-default",
|
|
@@ -40,29 +41,57 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
40
41
|
const [rotationSnap, setRotationSnap] = useState(Math.PI / 4);
|
|
41
42
|
const [history, setHistory] = useState([loadedPrefab]);
|
|
42
43
|
const [historyIndex, setHistoryIndex] = useState(0);
|
|
44
|
+
const [selectedObject, setSelectedObject] = useState(null);
|
|
43
45
|
const throttleRef = useRef(null);
|
|
44
46
|
const lastDataRef = useRef(JSON.stringify(loadedPrefab));
|
|
45
47
|
const prefabRootRef = useRef(null);
|
|
46
48
|
const canvasRef = useRef(null);
|
|
49
|
+
const controlsRef = useRef(null);
|
|
47
50
|
const onPrefabChangeRef = useRef(onPrefabChange);
|
|
48
51
|
const pendingPrefabChangeRef = useRef(null);
|
|
49
52
|
const [injectedModels, setInjectedModels] = useState({});
|
|
50
53
|
const [injectedTextures, setInjectedTextures] = useState({});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
onPrefabChangeRef.current = onPrefabChange;
|
|
55
|
+
const setSelection = (nodeId) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const nextNode = nodeId ? findNode(loadedPrefab.root, nodeId) : null;
|
|
58
|
+
if (nextNode === null || nextNode === void 0 ? void 0 : nextNode.locked) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
setSelectedId(nodeId);
|
|
62
|
+
setSelectedObject(nodeId ? (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId)) !== null && _b !== void 0 ? _b : null : null);
|
|
63
|
+
};
|
|
64
|
+
const toggleEditMode = () => {
|
|
65
|
+
setEditMode(prev => {
|
|
66
|
+
const next = !prev;
|
|
67
|
+
if (!next) {
|
|
68
|
+
setSelectedId(null);
|
|
69
|
+
setSelectedObject(null);
|
|
70
|
+
}
|
|
71
|
+
return next;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const setSelectedIdState = (value) => {
|
|
75
|
+
setSelection(typeof value === 'function' ? value(selectedId) : value);
|
|
76
|
+
};
|
|
54
77
|
const replacePrefab = (prefab, options) => {
|
|
55
78
|
if (throttleRef.current)
|
|
56
79
|
clearTimeout(throttleRef.current);
|
|
57
80
|
lastDataRef.current = JSON.stringify(prefab);
|
|
58
81
|
pendingPrefabChangeRef.current = (options === null || options === void 0 ? void 0 : options.notifyChange) === false ? null : prefab;
|
|
59
|
-
|
|
82
|
+
setSelection(null);
|
|
60
83
|
setInjectedModels({});
|
|
61
84
|
setInjectedTextures({});
|
|
62
85
|
setHistory([prefab]);
|
|
63
86
|
setHistoryIndex(0);
|
|
64
87
|
setLoadedPrefab(prefab);
|
|
65
88
|
};
|
|
89
|
+
const setPrefab = (prefab) => {
|
|
90
|
+
if (selectedId && !findNode(prefab.root, selectedId)) {
|
|
91
|
+
setSelection(null);
|
|
92
|
+
}
|
|
93
|
+
updatePrefab(prefab);
|
|
94
|
+
};
|
|
66
95
|
useEffect(() => {
|
|
67
96
|
if (initialPrefab)
|
|
68
97
|
replacePrefab(initialPrefab, { notifyChange: false });
|
|
@@ -90,7 +119,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
90
119
|
return Object.assign(Object.assign({}, prev), { root: insertNode(prev.root, node, options === null || options === void 0 ? void 0 : options.parentId) });
|
|
91
120
|
});
|
|
92
121
|
if ((options === null || options === void 0 ? void 0 : options.select) !== false) {
|
|
93
|
-
|
|
122
|
+
setSelection(node.id);
|
|
94
123
|
}
|
|
95
124
|
return node;
|
|
96
125
|
};
|
|
@@ -115,6 +144,8 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
115
144
|
const undo = () => historyIndex > 0 && applyHistory(historyIndex - 1);
|
|
116
145
|
const redo = () => historyIndex < history.length - 1 && applyHistory(historyIndex + 1);
|
|
117
146
|
useEffect(() => {
|
|
147
|
+
if (!editMode)
|
|
148
|
+
return;
|
|
118
149
|
const handleKeyDown = (e) => {
|
|
119
150
|
if (!(e.ctrlKey || e.metaKey))
|
|
120
151
|
return;
|
|
@@ -129,7 +160,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
129
160
|
};
|
|
130
161
|
window.addEventListener('keydown', handleKeyDown);
|
|
131
162
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
132
|
-
}, [historyIndex, history]);
|
|
163
|
+
}, [editMode, historyIndex, history]);
|
|
133
164
|
useEffect(() => {
|
|
134
165
|
const currentStr = JSON.stringify(loadedPrefab);
|
|
135
166
|
if (currentStr === lastDataRef.current)
|
|
@@ -165,7 +196,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
165
196
|
const clearSelection = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
197
|
if (!selectedId)
|
|
167
198
|
return;
|
|
168
|
-
|
|
199
|
+
setSelection(null);
|
|
169
200
|
yield new Promise(resolve => {
|
|
170
201
|
requestAnimationFrame(() => {
|
|
171
202
|
requestAnimationFrame(() => resolve());
|
|
@@ -190,11 +221,31 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
190
221
|
});
|
|
191
222
|
const handleFocusNode = (nodeId) => {
|
|
192
223
|
var _a;
|
|
193
|
-
(_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.
|
|
224
|
+
const object = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId);
|
|
225
|
+
const controls = controlsRef.current;
|
|
226
|
+
const camera = controls === null || controls === void 0 ? void 0 : controls.object;
|
|
227
|
+
if (!object || !controls || !camera)
|
|
228
|
+
return;
|
|
229
|
+
focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
|
|
230
|
+
};
|
|
231
|
+
const handleTransformChange = () => {
|
|
232
|
+
var _a;
|
|
233
|
+
if (!selectedId)
|
|
234
|
+
return;
|
|
235
|
+
const object = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(selectedId);
|
|
236
|
+
if (!object)
|
|
237
|
+
return;
|
|
238
|
+
const parentWorld = computeParentWorldMatrix(loadedPrefab.root, selectedId);
|
|
239
|
+
const local = parentWorld.clone().invert().multiply(object.matrixWorld);
|
|
240
|
+
const { position, rotation, scale } = decompose(local);
|
|
241
|
+
updatePrefab(prev => (Object.assign(Object.assign({}, prev), { root: updateNode(prev.root, selectedId, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
|
|
242
|
+
type: "Transform",
|
|
243
|
+
properties: { position, rotation, scale },
|
|
244
|
+
} }) }))) })));
|
|
194
245
|
};
|
|
195
246
|
// --- Drag & drop files to add nodes ---
|
|
196
247
|
useEffect(() => {
|
|
197
|
-
if (!enableWindowDrop)
|
|
248
|
+
if (!enableWindowDrop || !editMode)
|
|
198
249
|
return;
|
|
199
250
|
function handleDragOver(e) {
|
|
200
251
|
e.preventDefault();
|
|
@@ -227,21 +278,22 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
227
278
|
window.removeEventListener('dragover', handleDragOver);
|
|
228
279
|
window.removeEventListener('drop', handleDrop);
|
|
229
280
|
};
|
|
230
|
-
}, [enableWindowDrop]);
|
|
281
|
+
}, [editMode, enableWindowDrop]);
|
|
231
282
|
useImperativeHandle(ref, () => ({
|
|
232
283
|
screenshot: handleScreenshot,
|
|
233
284
|
exportGLB: handleExportGLB,
|
|
234
285
|
exportGLBData: handleExportGLBData,
|
|
235
286
|
clearSelection,
|
|
236
287
|
prefab: loadedPrefab,
|
|
237
|
-
setPrefab
|
|
288
|
+
setPrefab,
|
|
238
289
|
replacePrefab,
|
|
239
290
|
addModel,
|
|
240
291
|
addTexture,
|
|
241
292
|
rootRef: prefabRootRef
|
|
242
|
-
})
|
|
243
|
-
const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode,
|
|
293
|
+
}));
|
|
294
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, data: loadedPrefab, editMode: editMode, selectedId: selectedId, onSelect: setSelection, onSelectedObjectChange: editMode ? setSelectedObject : undefined, onFocusNode: editMode ? handleFocusNode : undefined, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
|
|
244
295
|
return _jsxs(EditorContext.Provider, { value: {
|
|
296
|
+
editMode,
|
|
245
297
|
transformMode,
|
|
246
298
|
setTransformMode,
|
|
247
299
|
snapResolution,
|
|
@@ -250,10 +302,19 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onPr
|
|
|
250
302
|
setPositionSnap,
|
|
251
303
|
rotationSnap,
|
|
252
304
|
setRotationSnap,
|
|
253
|
-
onFocusNode: handleFocusNode,
|
|
305
|
+
onFocusNode: editMode ? handleFocusNode : undefined,
|
|
254
306
|
onScreenshot: handleScreenshot,
|
|
255
307
|
onExportGLB: handleExportGLB
|
|
256
|
-
}, children: [
|
|
308
|
+
}, children: [_jsxs(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] }, canvasRef: canvasRef }, canvasProps, { onPointerMissed: editMode
|
|
309
|
+
? (event) => {
|
|
310
|
+
var _a, _b, _c, _d;
|
|
311
|
+
const button = (_c = (_a = event.button) !== null && _a !== void 0 ? _a : (_b = event.sourceEvent) === null || _b === void 0 ? void 0 : _b.button) !== null && _c !== void 0 ? _c : 0;
|
|
312
|
+
if (button === 0 && selectedId) {
|
|
313
|
+
setSelection(null);
|
|
314
|
+
}
|
|
315
|
+
(_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
|
|
316
|
+
}
|
|
317
|
+
: canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { debug: editMode, paused: editMode, children: content })) : content, editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${transformMode}-${positionSnap}-${rotationSnap}-${snapResolution}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleEditMode, children: editMode ? "▶" : "⏸" }), uiPlugins] }), editMode && (_jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedIdState, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] });
|
|
257
318
|
});
|
|
258
319
|
PrefabEditor.displayName = "PrefabEditor";
|
|
259
320
|
export default PrefabEditor;
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { Group, Matrix4, Object3D
|
|
1
|
+
import { Group, Matrix4, Object3D } from "three";
|
|
2
2
|
import { ThreeEvent } from "@react-three/fiber";
|
|
3
3
|
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
4
|
+
import { LoadedModels, LoadedTextures } from "../dragdrop";
|
|
4
5
|
export interface PrefabRootRef {
|
|
5
6
|
root: Group | null;
|
|
6
7
|
rigidBodyRefs: Map<string, any>;
|
|
8
|
+
getObject: (nodeId: string) => Object3D | null;
|
|
7
9
|
focusNode: (nodeId: string) => void;
|
|
8
10
|
}
|
|
9
11
|
export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
10
12
|
editMode?: boolean;
|
|
11
13
|
data: Prefab;
|
|
12
|
-
onPrefabChange?: (data: Prefab) => void;
|
|
13
14
|
selectedId?: string | null;
|
|
14
15
|
onSelect?: (id: string | null) => void;
|
|
15
16
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
17
|
+
onSelectedObjectChange?: (object: Object3D | null) => void;
|
|
18
|
+
onFocusNode?: (nodeId: string) => void;
|
|
16
19
|
basePath?: string;
|
|
17
|
-
injectedModels?:
|
|
18
|
-
injectedTextures?:
|
|
20
|
+
injectedModels?: LoadedModels;
|
|
21
|
+
injectedTextures?: LoadedTextures;
|
|
19
22
|
} & import("react").RefAttributes<PrefabRootRef>>;
|
|
20
23
|
export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
24
|
interface RendererProps {
|
|
@@ -25,8 +28,8 @@ interface RendererProps {
|
|
|
25
28
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
26
29
|
registerRef: (id: string, obj: Object3D | null) => void;
|
|
27
30
|
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
28
|
-
loadedModels:
|
|
29
|
-
loadedTextures:
|
|
31
|
+
loadedModels: LoadedModels;
|
|
32
|
+
loadedTextures: LoadedTextures;
|
|
30
33
|
editMode?: boolean;
|
|
31
34
|
parentMatrix?: Matrix4;
|
|
32
35
|
}
|