react-three-game 0.0.69 → 0.0.70

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 (52) hide show
  1. package/dist/helpers/SoundManager.d.ts +2 -0
  2. package/dist/helpers/SoundManager.js +6 -0
  3. package/dist/index.d.ts +10 -8
  4. package/dist/index.js +7 -5
  5. package/dist/shared/GameCanvas.js +0 -2
  6. package/dist/tools/assetviewer/page.d.ts +5 -0
  7. package/dist/tools/assetviewer/page.js +3 -0
  8. package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -2
  9. package/dist/tools/dragdrop/DragDropLoader.js +18 -3
  10. package/dist/tools/dragdrop/index.d.ts +2 -2
  11. package/dist/tools/dragdrop/index.js +1 -1
  12. package/dist/tools/dragdrop/modelLoader.d.ts +10 -0
  13. package/dist/tools/dragdrop/modelLoader.js +60 -0
  14. package/dist/tools/prefabeditor/EditorTree.js +6 -30
  15. package/dist/tools/prefabeditor/EditorTreeMenus.js +3 -3
  16. package/dist/tools/prefabeditor/EditorUI.js +6 -4
  17. package/dist/tools/prefabeditor/InstanceProvider.d.ts +2 -0
  18. package/dist/tools/prefabeditor/InstanceProvider.js +54 -52
  19. package/dist/tools/prefabeditor/PrefabEditor.d.ts +22 -0
  20. package/dist/tools/prefabeditor/PrefabEditor.js +68 -27
  21. package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -1
  22. package/dist/tools/prefabeditor/PrefabRoot.js +148 -145
  23. package/dist/tools/prefabeditor/components/ClickComponent.js +10 -7
  24. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +10 -4
  25. package/dist/tools/prefabeditor/components/ComponentRegistry.js +6 -6
  26. package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -1
  27. package/dist/tools/prefabeditor/components/Input.d.ts +16 -0
  28. package/dist/tools/prefabeditor/components/Input.js +33 -0
  29. package/dist/tools/prefabeditor/components/MaterialComponent.js +10 -2
  30. package/dist/tools/prefabeditor/components/ModelComponent.js +35 -43
  31. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -1
  32. package/dist/tools/prefabeditor/components/PhysicsComponent.js +122 -28
  33. package/dist/tools/prefabeditor/components/SoundComponent.d.ts +3 -0
  34. package/dist/tools/prefabeditor/components/SoundComponent.js +240 -0
  35. package/dist/tools/prefabeditor/components/SpotLightComponent.js +6 -1
  36. package/dist/tools/prefabeditor/components/TransformComponent.js +2 -2
  37. package/dist/tools/prefabeditor/components/index.js +2 -0
  38. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
  39. package/dist/tools/prefabeditor/prefabStore.js +11 -13
  40. package/dist/tools/prefabeditor/sceneApi.d.ts +15 -1
  41. package/dist/tools/prefabeditor/sceneApi.js +77 -32
  42. package/dist/tools/prefabeditor/styles.d.ts +1 -0
  43. package/dist/tools/prefabeditor/styles.js +9 -0
  44. package/dist/tools/prefabeditor/types.d.ts +13 -0
  45. package/dist/tools/prefabeditor/types.js +28 -1
  46. package/dist/tools/prefabeditor/useClickValid.d.ts +13 -0
  47. package/dist/tools/prefabeditor/useClickValid.js +21 -0
  48. package/dist/tools/prefabeditor/utils.d.ts +2 -0
  49. package/dist/tools/prefabeditor/utils.js +34 -35
  50. package/package.json +1 -1
  51. package/dist/tools/prefabeditor/EditorContext.d.ts +0 -16
  52. package/dist/tools/prefabeditor/EditorContext.js +0 -9
@@ -16,29 +16,31 @@ import { InstancedRigidBodies } from "@react-three/rapier";
16
16
  import { ActiveCollisionTypes } from "@dimforge/rapier3d-compat";
17
17
  import { Mesh, Matrix4, Vector3, Quaternion, Euler, BoxHelper } from "three";
18
18
  import { gameEvents, getEntityIdFromRigidBody } from "./GameEvents";
19
+ import { useClickValid } from "./useClickValid";
19
20
  export const DEFAULT_REPEAT_AXES = [{ axis: 'x', count: 1, offset: 1 }];
20
21
  export function normalizeRepeatAxes(value) {
21
22
  if (!Array.isArray(value)) {
22
23
  return DEFAULT_REPEAT_AXES;
23
24
  }
24
25
  const seen = new Set();
25
- const normalized = value.flatMap((entry) => {
26
+ const normalized = value.reduce((result, entry) => {
26
27
  if (!entry || typeof entry !== 'object')
27
- return [];
28
+ return result;
28
29
  const axisValue = entry.axis;
29
30
  if (axisValue !== 'x' && axisValue !== 'y' && axisValue !== 'z')
30
- return [];
31
+ return result;
31
32
  if (seen.has(axisValue))
32
- return [];
33
+ return result;
33
34
  seen.add(axisValue);
34
35
  const countValue = Number(entry.count);
35
36
  const offsetValue = Number(entry.offset);
36
- return [{
37
- axis: axisValue,
38
- count: Number.isFinite(countValue) ? Math.max(1, Math.floor(countValue)) : 1,
39
- offset: Number.isFinite(offsetValue) ? offsetValue : 1,
40
- }];
41
- });
37
+ result.push({
38
+ axis: axisValue,
39
+ count: Number.isFinite(countValue) ? Math.max(1, Math.floor(countValue)) : 1,
40
+ offset: Number.isFinite(offsetValue) ? offsetValue : 1,
41
+ });
42
+ return result;
43
+ }, []);
42
44
  return normalized.length > 0 ? normalized : DEFAULT_REPEAT_AXES;
43
45
  }
44
46
  function toVector3Tuple(value, fallback) {
@@ -101,36 +103,17 @@ function hasPhysics(instance) {
101
103
  function getColliderType(physics) {
102
104
  return physics.colliders || (physics.type === 'fixed' ? 'trimesh' : 'hull');
103
105
  }
104
- function emitSensorEnter(sourceId, payload) {
105
- gameEvents.emit('sensor:enter', {
106
- sourceEntityId: sourceId,
107
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
108
- targetRigidBody: payload.other.rigidBody,
109
- });
110
- }
111
- function emitSensorExit(sourceId, payload) {
112
- gameEvents.emit('sensor:exit', {
113
- sourceEntityId: sourceId,
114
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
115
- targetRigidBody: payload.other.rigidBody,
116
- });
117
- }
118
- function emitCollisionEnter(sourceId, payload) {
119
- gameEvents.emit('collision:enter', {
120
- sourceEntityId: sourceId,
121
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
122
- targetRigidBody: payload.other.rigidBody,
123
- });
124
- }
125
- function emitCollisionExit(sourceId, payload) {
126
- gameEvents.emit('collision:exit', {
106
+ function emitPhysicsEvent(sourceId, eventName, payload) {
107
+ if (!eventName)
108
+ return;
109
+ gameEvents.emit(eventName, {
127
110
  sourceEntityId: sourceId,
128
111
  targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
129
112
  targetRigidBody: payload.other.rigidBody,
130
113
  });
131
114
  }
132
- function emitClick(sourceId, instanceId, event) {
133
- gameEvents.emit('click', {
115
+ function emitClick(sourceId, instanceId, eventName, event) {
116
+ gameEvents.emit(eventName, {
134
117
  sourceEntityId: sourceId,
135
118
  instanceEntityId: instanceId && instanceId !== sourceId ? instanceId : undefined,
136
119
  point: [event.point.x, event.point.y, event.point.z],
@@ -145,6 +128,7 @@ function instanceEquals(a, b) {
145
128
  return a.id === b.id &&
146
129
  a.sourceId === b.sourceId &&
147
130
  a.clickable === b.clickable &&
131
+ a.clickEventName === b.clickEventName &&
148
132
  a.locked === b.locked &&
149
133
  a.meshPath === b.meshPath &&
150
134
  arrayEquals(a.position, b.position) &&
@@ -261,7 +245,7 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
261
245
  const rigidBodiesRef = useRef(null);
262
246
  const instances = useMemo(() => group.instances.filter(hasPhysics).map(inst => {
263
247
  const _a = inst.physics, { activeCollisionTypes: _activeCollisionTypes, colliders: _colliders, userData } = _a, rigidBodyProps = __rest(_a, ["activeCollisionTypes", "colliders", "userData"]);
264
- return Object.assign(Object.assign({ key: inst.id, position: inst.position, rotation: inst.rotation, scale: inst.scale }, rigidBodyProps), { colliders: getColliderType(inst.physics), userData: Object.assign(Object.assign({}, userData), { entityId: inst.sourceId }), onIntersectionEnter: (payload) => emitSensorEnter(inst.sourceId, payload), onIntersectionExit: (payload) => emitSensorExit(inst.sourceId, payload), onCollisionEnter: (payload) => emitCollisionEnter(inst.sourceId, payload), onCollisionExit: (payload) => emitCollisionExit(inst.sourceId, payload) });
248
+ return Object.assign(Object.assign({ key: inst.id, position: inst.position, rotation: inst.rotation, scale: inst.scale }, rigidBodyProps), { colliders: getColliderType(inst.physics), userData: Object.assign(Object.assign({}, userData), { entityId: inst.sourceId }), onIntersectionEnter: (payload) => emitPhysicsEvent(inst.sourceId, inst.physics.sensorEnterEventName, payload), onIntersectionExit: (payload) => emitPhysicsEvent(inst.sourceId, inst.physics.sensorExitEventName, payload), onCollisionEnter: (payload) => emitPhysicsEvent(inst.sourceId, inst.physics.collisionEnterEventName, payload), onCollisionExit: (payload) => emitPhysicsEvent(inst.sourceId, inst.physics.collisionExitEventName, payload) });
265
249
  }), [group.instances]);
266
250
  // Apply scale to visual meshes (InstancedRigidBodies only scales colliders, not visuals)
267
251
  useEffect(() => {
@@ -336,7 +320,7 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
336
320
  if (!instance.clickable)
337
321
  return;
338
322
  e.stopPropagation();
339
- emitClick(instance.sourceId, instance.id, e);
323
+ emitClick(instance.sourceId, instance.id, instance.clickEventName || 'click', e);
340
324
  };
341
325
  const shouldHandleClick = editMode || group.instances.some(inst => inst.clickable);
342
326
  // Add key to force remount when instance count changes significantly (helps with cleanup)
@@ -356,12 +340,19 @@ function NonPhysicsInstancedGroup({ modelKey, group, partCount, instancesMap, on
356
340
  }
357
341
  // Individual instance item with its own click state
358
342
  function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef, selectedId, editMode }) {
359
- const clickValid = useRef(false);
360
343
  const groupRef = useRef(null);
361
344
  const isLocked = Boolean(instance.locked);
362
345
  const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
363
346
  const canSelect = editMode && !isLocked;
364
347
  const canClick = !editMode && Boolean(instance.clickable);
348
+ const pointerHandlers = useClickValid(canSelect || canClick, (e) => {
349
+ if (editMode) {
350
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.sourceId);
351
+ }
352
+ else if (instance.clickable) {
353
+ emitClick(instance.sourceId, instance.id, instance.clickEventName || 'click', e);
354
+ }
355
+ });
365
356
  // Use BoxHelper when object is selected in edit mode
366
357
  useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
367
358
  useEffect(() => {
@@ -370,18 +361,7 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef
370
361
  registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
371
362
  return () => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, null);
372
363
  }, [editMode, instance.id, registerRef]);
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) => {
374
- if (clickValid.current) {
375
- e.stopPropagation();
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
- }
382
- }
383
- clickValid.current = false;
384
- } : undefined, children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) }));
364
+ return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) })));
385
365
  }
386
366
  // Hook to check if an instance exists
387
367
  export function useInstanceCheck(id) {
@@ -390,21 +370,43 @@ export function useInstanceCheck(id) {
390
370
  return (_a = ctx === null || ctx === void 0 ? void 0 : ctx.hasInstance(id)) !== null && _a !== void 0 ? _a : false;
391
371
  }
392
372
  // GameInstance component: registers an instance for batch rendering (renders nothing itself)
393
- export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
373
+ export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false, clickEventName, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
394
374
  const ctx = useContext(GameInstanceContext);
395
375
  const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
396
376
  const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
377
+ const [positionX, positionY, positionZ] = position;
378
+ const [rotationX, rotationY, rotationZ] = rotation;
379
+ const [scaleX, scaleY, scaleZ] = scale;
380
+ const physicsSignature = getPhysicsSignature(physics);
397
381
  const instance = useMemo(() => ({
398
382
  id,
399
383
  sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
400
384
  clickable,
385
+ clickEventName,
401
386
  locked,
402
387
  meshPath: modelUrl,
403
388
  position,
404
389
  rotation,
405
390
  scale,
406
391
  physics,
407
- }), [id, sourceId, clickable, locked, modelUrl, JSON.stringify(position), JSON.stringify(rotation), JSON.stringify(scale), getPhysicsSignature(physics)]);
392
+ }), [
393
+ id,
394
+ sourceId,
395
+ clickable,
396
+ clickEventName,
397
+ locked,
398
+ modelUrl,
399
+ positionX,
400
+ positionY,
401
+ positionZ,
402
+ rotationX,
403
+ rotationY,
404
+ rotationZ,
405
+ scaleX,
406
+ scaleY,
407
+ scaleZ,
408
+ physicsSignature,
409
+ ]);
408
410
  useEffect(() => {
409
411
  if (!addInstance || !removeInstance)
410
412
  return;
@@ -19,10 +19,32 @@ export interface PrefabEditorRef {
19
19
  addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
20
20
  viewRef: React.RefObject<PrefabRootRef | null>;
21
21
  }
22
+ export declare enum PrefabEditorMode {
23
+ Edit = "edit",
24
+ Play = "play"
25
+ }
26
+ export interface EditorContextType {
27
+ mode: PrefabEditorMode;
28
+ setMode: (mode: PrefabEditorMode) => void;
29
+ transformMode: "translate" | "rotate" | "scale";
30
+ setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
31
+ scaleSnap: number;
32
+ setScaleSnap: (resolution: number) => void;
33
+ positionSnap: number;
34
+ setPositionSnap: (resolution: number) => void;
35
+ rotationSnap: number;
36
+ setRotationSnap: (resolution: number) => void;
37
+ onFocusNode?: (nodeId: string) => void;
38
+ onScreenshot?: () => void;
39
+ onExportGLB?: () => void;
40
+ }
41
+ export declare const EditorContext: import("react").Context<EditorContextType | null>;
42
+ export declare function useEditorContext(): EditorContextType;
22
43
  export interface PrefabEditorProps {
23
44
  basePath?: string;
24
45
  initialPrefab?: Prefab;
25
46
  physics?: boolean;
47
+ mode?: PrefabEditorMode;
26
48
  onChange?: (prefab: Prefab) => void;
27
49
  showUI?: boolean;
28
50
  enableWindowDrop?: boolean;
@@ -10,16 +10,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { MapControls, TransformControls } from "@react-three/drei";
12
12
  import GameCanvas from "../../shared/GameCanvas";
13
- import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "react";
13
+ import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
14
+ import { findComponentEntry } from "./types";
14
15
  import PrefabRoot from "./PrefabRoot";
15
16
  import { Physics } from "@react-three/rapier";
16
17
  import EditorUI from "./EditorUI";
17
18
  import { base, toolbar } from "./styles";
18
- import { EditorContext } from "./EditorContext";
19
19
  import { createImageNode, createModelNode, computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, regenerateIds } from "./utils";
20
20
  import { loadFiles } from "../dragdrop";
21
21
  import { createPrefabStore, PrefabStoreProvider, prefabStoreToPrefab } from "./prefabStore";
22
22
  import { createScene } from "./sceneApi";
23
+ export var PrefabEditorMode;
24
+ (function (PrefabEditorMode) {
25
+ PrefabEditorMode["Edit"] = "edit";
26
+ PrefabEditorMode["Play"] = "play";
27
+ })(PrefabEditorMode || (PrefabEditorMode = {}));
28
+ export const EditorContext = createContext(null);
29
+ export function useEditorContext() {
30
+ const context = useContext(EditorContext);
31
+ if (!context) {
32
+ throw new Error("useEditorContext must be used within EditorContext.Provider");
33
+ }
34
+ return context;
35
+ }
36
+ const MAX_HISTORY_LENGTH = 50;
37
+ const HISTORY_DEBOUNCE_MS = 500;
23
38
  const DEFAULT_PREFAB = {
24
39
  id: "prefab-default",
25
40
  name: "New Prefab",
@@ -33,8 +48,8 @@ const DEFAULT_PREFAB = {
33
48
  }
34
49
  }
35
50
  };
36
- const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
37
- const [editMode, setEditMode] = useState(true);
51
+ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
52
+ const [mode, setMode] = useState(initialMode);
38
53
  const [selectedId, setSelectedId] = useState(null);
39
54
  const [transformMode, setTransformMode] = useState("translate");
40
55
  const [scaleSnap, setScaleSnap] = useState(0);
@@ -53,6 +68,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
53
68
  const onChangeRef = useRef(onChange);
54
69
  const [injectedModels, setInjectedModels] = useState({});
55
70
  const [injectedTextures, setInjectedTextures] = useState({});
71
+ const isEditMode = mode === PrefabEditorMode.Edit;
56
72
  const getPrefab = useCallback(() => prefabStoreToPrefab(prefabStore.getState()), [prefabStore]);
57
73
  onChangeRef.current = onChange;
58
74
  const setSelection = useCallback((nodeId) => {
@@ -62,16 +78,23 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
62
78
  }
63
79
  setSelectedId(nodeId);
64
80
  }, [prefabStore]);
65
- const toggleEditMode = () => {
66
- setEditMode(prev => {
67
- const next = !prev;
68
- if (!next) {
81
+ const updateMode = useCallback((nextMode) => {
82
+ setMode(prev => {
83
+ if (prev === nextMode)
84
+ return prev;
85
+ if (nextMode === PrefabEditorMode.Play) {
69
86
  setSelectedId(null);
70
87
  setSelectedObject(null);
71
88
  }
72
- return next;
89
+ return nextMode;
73
90
  });
91
+ }, []);
92
+ const toggleMode = () => {
93
+ updateMode(isEditMode ? PrefabEditorMode.Play : PrefabEditorMode.Edit);
74
94
  };
95
+ useEffect(() => {
96
+ updateMode(initialMode);
97
+ }, [initialMode, updateMode]);
75
98
  const loadPrefab = useCallback((prefab, options) => {
76
99
  changeOriginRef.current = (options === null || options === void 0 ? void 0 : options.notifyChange) === false ? "replace-silent" : "replace";
77
100
  prefabStore.getState().replacePrefab(prefab);
@@ -110,7 +133,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
110
133
  clearTimeout(historyTimeout);
111
134
  historyTimeout = null;
112
135
  }
113
- if (changeOrigin || !editMode) {
136
+ if (changeOrigin || !isEditMode) {
114
137
  changeOriginRef.current = null;
115
138
  return;
116
139
  }
@@ -118,13 +141,13 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
118
141
  const currentHistoryIndex = historyIndexRef.current;
119
142
  setHistory(prev => {
120
143
  const nextHistory = [...prev.slice(0, currentHistoryIndex + 1), nextPrefab];
121
- return nextHistory.length > 50 ? nextHistory.slice(1) : nextHistory;
144
+ return nextHistory.length > MAX_HISTORY_LENGTH ? nextHistory.slice(1) : nextHistory;
122
145
  });
123
- const nextHistoryIndex = Math.min(currentHistoryIndex + 1, 49);
146
+ const nextHistoryIndex = Math.min(currentHistoryIndex + 1, MAX_HISTORY_LENGTH - 1);
124
147
  historyIndexRef.current = nextHistoryIndex;
125
148
  setHistoryIndex(nextHistoryIndex);
126
149
  historyTimeout = null;
127
- }, 500);
150
+ }, HISTORY_DEBOUNCE_MS);
128
151
  });
129
152
  return () => {
130
153
  if (historyTimeout) {
@@ -132,7 +155,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
132
155
  }
133
156
  unsubscribe();
134
157
  };
135
- }, [editMode, prefabStore]);
158
+ }, [isEditMode, prefabStore]);
136
159
  useEffect(() => {
137
160
  if (!selectedId)
138
161
  return;
@@ -187,7 +210,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
187
210
  const undo = () => historyIndex > 0 && applyHistory(historyIndex - 1);
188
211
  const redo = () => historyIndex < history.length - 1 && applyHistory(historyIndex + 1);
189
212
  useEffect(() => {
190
- if (!editMode)
213
+ if (!isEditMode)
191
214
  return;
192
215
  const handleKeyDown = (e) => {
193
216
  if (!(e.ctrlKey || e.metaKey))
@@ -203,7 +226,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
203
226
  };
204
227
  window.addEventListener('keydown', handleKeyDown);
205
228
  return () => window.removeEventListener('keydown', handleKeyDown);
206
- }, [editMode, historyIndex, history]);
229
+ }, [isEditMode, historyIndex, history]);
207
230
  const handleScreenshot = useCallback(() => {
208
231
  const canvas = canvasRef.current;
209
232
  if (!canvas)
@@ -253,6 +276,18 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
253
276
  const scene = useMemo(() => createScene({
254
277
  getRootId: () => prefabStore.getState().rootId,
255
278
  getNode: (id) => { var _a; return (_a = prefabStore.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
279
+ getChildIds: (id) => { var _a; return (_a = prefabStore.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
280
+ getParentId: (id) => { var _a; return (_a = prefabStore.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
281
+ findByName: (name) => {
282
+ var _a;
283
+ const state = prefabStore.getState();
284
+ const normalized = name.toLowerCase();
285
+ for (const [id, node] of Object.entries(state.nodesById)) {
286
+ if (((_a = node.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === normalized)
287
+ return id;
288
+ }
289
+ return null;
290
+ },
256
291
  updateNode: (id, update) => prefabStore.getState().updateNode(id, update),
257
292
  updateNodes: (updates) => prefabStore.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update }))),
258
293
  addNode: (node, options) => addNode(node, options).id,
@@ -268,14 +303,19 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
268
303
  const parentWorld = computeParentWorldMatrix(prefabStore.getState(), selectedId);
269
304
  const local = parentWorld.clone().invert().multiply(object.matrixWorld);
270
305
  const { position, rotation, scale } = decompose(local);
271
- prefabStore.getState().updateNode(selectedId, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
272
- type: "Transform",
273
- properties: { position, rotation, scale },
274
- } }) })));
306
+ prefabStore.getState().updateNode(selectedId, node => {
307
+ var _a;
308
+ const entry = findComponentEntry(node, "Transform");
309
+ const key = (_a = entry === null || entry === void 0 ? void 0 : entry[0]) !== null && _a !== void 0 ? _a : "transform";
310
+ return Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [key]: {
311
+ type: "Transform",
312
+ properties: { position, rotation, scale },
313
+ } }) });
314
+ });
275
315
  };
276
316
  // --- Drag & drop files to add nodes ---
277
317
  useEffect(() => {
278
- if (!enableWindowDrop || !editMode)
318
+ if (!enableWindowDrop || !isEditMode)
279
319
  return;
280
320
  function handleDragOver(e) {
281
321
  e.preventDefault();
@@ -308,7 +348,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
308
348
  window.removeEventListener('dragover', handleDragOver);
309
349
  window.removeEventListener('drop', handleDrop);
310
350
  };
311
- }, [addModel, addTexture, editMode, enableWindowDrop]);
351
+ }, [addModel, addTexture, isEditMode, enableWindowDrop]);
312
352
  useImperativeHandle(ref, () => ({
313
353
  screenshot: handleScreenshot,
314
354
  exportGLB: handleExportGLB,
@@ -321,9 +361,10 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
321
361
  addTexture,
322
362
  viewRef: prefabRootRef
323
363
  }), [addModel, addTexture, clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, scene]);
324
- const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: editMode, selectedId: selectedId, onSelect: setSelection, onSelectedObjectChange: editMode ? setSelectedObject : undefined, onFocusNode: editMode ? handleFocusNode : undefined, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
364
+ const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onSelectedObjectChange: isEditMode ? setSelectedObject : undefined, onFocusNode: isEditMode ? handleFocusNode : undefined, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
325
365
  return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsxs(EditorContext.Provider, { value: {
326
- editMode,
366
+ mode,
367
+ setMode: updateMode,
327
368
  transformMode,
328
369
  setTransformMode,
329
370
  scaleSnap,
@@ -332,10 +373,10 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
332
373
  setPositionSnap,
333
374
  rotationSnap,
334
375
  setRotationSnap,
335
- onFocusNode: editMode ? handleFocusNode : undefined,
376
+ onFocusNode: isEditMode ? handleFocusNode : undefined,
336
377
  onScreenshot: handleScreenshot,
337
378
  onExportGLB: handleExportGLB
338
- }, children: [_jsxs(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] }, canvasRef: canvasRef }, canvasProps, { onPointerMissed: editMode
379
+ }, children: [_jsxs(GameCanvas, Object.assign({ camera: { position: [0, 5, 15] }, canvasRef: canvasRef }, canvasProps, { onPointerMissed: isEditMode
339
380
  ? (event) => {
340
381
  var _a, _b, _c, _d;
341
382
  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;
@@ -344,7 +385,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, onCh
344
385
  }
345
386
  (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
346
387
  }
347
- : 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: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleEditMode, children: editMode ? "▶" : "⏸" }), uiPlugins] }), editMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
388
+ : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [physics ? (_jsx(Physics, { debug: isEditMode, paused: isEditMode, children: content })) : content, isEditMode && (_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: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) });
348
389
  });
349
390
  PrefabEditor.displayName = "PrefabEditor";
350
391
  export default PrefabEditor;
@@ -1,12 +1,13 @@
1
1
  import { Group, Matrix4, Object3D } from "three";
2
2
  import { ThreeEvent } from "@react-three/fiber";
3
3
  import { GameObject as GameObjectType, Prefab } from "./types";
4
- import { LoadedModels, LoadedTextures } from "../dragdrop";
4
+ import { LoadedModels, LoadedSounds, LoadedTextures } from "../dragdrop";
5
5
  import { PrefabStoreApi } from "./prefabStore";
6
6
  export interface PrefabRootRef {
7
7
  root: Group | null;
8
8
  rigidBodyRefs: Map<string, any>;
9
9
  getObject: (nodeId: string) => Object3D | null;
10
+ getRigidBody: (nodeId: string) => any;
10
11
  focusNode: (nodeId: string) => void;
11
12
  }
12
13
  export interface PrefabRootProps {
@@ -21,6 +22,7 @@ export interface PrefabRootProps {
21
22
  basePath?: string;
22
23
  injectedModels?: LoadedModels;
23
24
  injectedTextures?: LoadedTextures;
25
+ injectedSounds?: LoadedSounds;
24
26
  }
25
27
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<PrefabRootProps & import("react").RefAttributes<PrefabRootRef>>;
26
28
  export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
@@ -31,7 +33,9 @@ interface RendererProps {
31
33
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
32
34
  registerRef: (id: string, obj: Object3D | null) => void;
33
35
  registerRigidBodyRef: (id: string, rb: any) => void;
36
+ getRigidBody: (id: string) => any;
34
37
  loadedModels: LoadedModels;
38
+ loadedSounds: LoadedSounds;
35
39
  loadedTextures: LoadedTextures;
36
40
  editMode?: boolean;
37
41
  parentMatrix?: Matrix4;