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.
Files changed (38) hide show
  1. package/LICENSE +2 -660
  2. package/README.md +164 -91
  3. package/dist/index.d.ts +5 -3
  4. package/dist/index.js +3 -2
  5. package/dist/shared/GameCanvas.js +1 -1
  6. package/dist/tools/assetviewer/page.d.ts +13 -2
  7. package/dist/tools/assetviewer/page.js +61 -7
  8. package/dist/tools/dragdrop/index.d.ts +1 -1
  9. package/dist/tools/dragdrop/modelLoader.d.ts +2 -0
  10. package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
  11. package/dist/tools/prefabeditor/EditorTree.js +17 -3
  12. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +3 -1
  13. package/dist/tools/prefabeditor/EditorTreeMenus.js +7 -8
  14. package/dist/tools/prefabeditor/EditorUI.js +3 -7
  15. package/dist/tools/prefabeditor/GameEvents.d.ts +14 -1
  16. package/dist/tools/prefabeditor/GameEvents.js +2 -1
  17. package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -0
  18. package/dist/tools/prefabeditor/InstanceProvider.js +44 -12
  19. package/dist/tools/prefabeditor/PrefabEditor.js +77 -16
  20. package/dist/tools/prefabeditor/PrefabRoot.d.ts +9 -6
  21. package/dist/tools/prefabeditor/PrefabRoot.js +52 -126
  22. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  23. package/dist/tools/prefabeditor/components/ClickComponent.d.ts +3 -0
  24. package/dist/tools/prefabeditor/components/ClickComponent.js +45 -0
  25. package/dist/tools/prefabeditor/components/ComponentRegistry.js +0 -3
  26. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -3
  27. package/dist/tools/prefabeditor/components/Input.d.ts +5 -2
  28. package/dist/tools/prefabeditor/components/Input.js +71 -38
  29. package/dist/tools/prefabeditor/components/MaterialComponent.js +4 -69
  30. package/dist/tools/prefabeditor/components/ModelComponent.js +5 -80
  31. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +2 -0
  32. package/dist/tools/prefabeditor/components/PhysicsComponent.js +77 -10
  33. package/dist/tools/prefabeditor/components/SpotLightComponent.js +9 -7
  34. package/dist/tools/prefabeditor/components/index.js +2 -0
  35. package/dist/tools/prefabeditor/types.d.ts +1 -0
  36. package/dist/tools/prefabeditor/utils.d.ts +7 -1
  37. package/dist/tools/prefabeditor/utils.js +34 -1
  38. 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 physics events:
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 physics events:
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
- if (instanceId !== undefined && group.instances[instanceId]) {
317
- onSelect(group.instances[instanceId].sourceId);
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: editMode ? handleClick : undefined }, i));
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
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.sourceId);
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
- useEffect(() => {
52
- onPrefabChangeRef.current = onPrefabChange;
53
- }, [onPrefabChange]);
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
- setSelectedId(null);
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
- setSelectedId(node.id);
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
- setSelectedId(null);
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.focusNode(nodeId);
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: replacePrefab,
288
+ setPrefab,
238
289
  replacePrefab,
239
290
  addModel,
240
291
  addTexture,
241
292
  rootRef: prefabRootRef
242
- }), [loadedPrefab]);
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, onPrefabChange: updatePrefab, selectedId: selectedId, onSelect: setSelectedId, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
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: [_jsx(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] }, canvasRef: canvasRef }, canvasProps, { children: physics ? (_jsx(Physics, { debug: editMode, paused: editMode, children: content })) : content })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: () => setEditMode(!editMode), children: editMode ? "▶" : "⏸" }), uiPlugins] }), _jsx(EditorUI, { prefabData: loadedPrefab, setPrefabData: updatePrefab, selectedId: selectedId, setSelectedId: setSelectedId, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 })] }))] });
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, Texture } from "three";
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?: Record<string, Object3D>;
18
- injectedTextures?: Record<string, Texture>;
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: Record<string, Object3D>;
29
- loadedTextures: Record<string, Texture>;
31
+ loadedModels: LoadedModels;
32
+ loadedTextures: LoadedTextures;
30
33
  editMode?: boolean;
31
34
  parentMatrix?: Matrix4;
32
35
  }