react-three-game 0.0.84 → 0.0.86

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 (40) hide show
  1. package/README.md +110 -36
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.js +1 -4
  4. package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
  5. package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
  6. package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
  7. package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
  8. package/dist/tools/prefabeditor/PrefabRoot.js +99 -48
  9. package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
  10. package/dist/tools/prefabeditor/assetRuntime.js +37 -0
  11. package/dist/tools/prefabeditor/components/BufferGeometryComponent.d.ts +3 -0
  12. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +98 -0
  13. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  14. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  15. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  16. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  17. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  18. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  19. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  20. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  21. package/dist/tools/prefabeditor/components/Input.js +0 -55
  22. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
  23. package/dist/tools/prefabeditor/components/MaterialComponent.js +49 -18
  24. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
  25. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
  26. package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
  27. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
  29. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  30. package/dist/tools/prefabeditor/components/index.js +10 -8
  31. package/dist/tools/prefabeditor/types.d.ts +1 -0
  32. package/dist/tools/prefabeditor/types.js +18 -0
  33. package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
  34. package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
  35. package/package.json +1 -1
  36. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
  37. package/dist/tools/prefabeditor/GameEvents.js +0 -118
  38. package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
  39. package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
  40. package/dist/tools/prefabeditor/runtime.js +0 -184
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ![Scene Editor](assets/editor.gif)
4
4
 
5
- JSON-first 3D game engine for React Three Fiber.
5
+ JSON-first scene mounting and authoring for React Three Fiber.
6
6
 
7
7
  Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fiber](https://github.com/pmndrs/react-three-fiber), and [@react-three/rapier](https://github.com/pmndrs/react-three-rapier).
8
8
 
@@ -10,7 +10,7 @@ Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fi
10
10
  * **🎬 Scene Editor** - Edit prefabs visually with hierarchy, inspector, transform gizmos, and play mode.
11
11
  * **⚛️ Physics** - Author rigid bodies directly in prefab data and run them through Rapier.
12
12
  * **🧩 Components** - Build scenes from reusable `GameObject` + component composition.
13
- * **🔧 Runtime Scene API** - Mutate the live world through `Scene`, `Entity`, and `EntityComponent` handles.
13
+ * **🔧 Direct Runtime Access** - Get native `Object3D`, Rapier rigid body, and prefab store access without a parallel engine API.
14
14
  * **⚡ R3F Native** - Use normal React Three Fiber components whenever runtime behavior is clearer in code.
15
15
 
16
16
  ## Documentation
@@ -98,12 +98,10 @@ Open the hosted editor here:
98
98
 
99
99
  * https://prnth.com/react-three-game/editor
100
100
 
101
- ## Prefabs And Scenes
101
+ ## Prefabs And Mounted Scenes
102
102
 
103
103
  `Prefab` is the serializable pure data format.
104
104
 
105
- `Scene` is the live runtime/editor world handle.
106
-
107
105
  That means a saved scene is just a prefab, and the same prefab can be:
108
106
 
109
107
  * edited directly in `PrefabEditor`
@@ -113,13 +111,35 @@ That means a saved scene is just a prefab, and the same prefab can be:
113
111
  `PrefabRoot` keeps the rendering model narrow and compositional:
114
112
 
115
113
  * `Transform` is the renderer-owned outer transform
116
- * `Geometry` + `Material` become the primary mesh content
114
+ * `Geometry` or `BufferGeometry` + `Material` become the primary mesh content
117
115
  * non-instanced `Model` becomes the node's primary content
118
116
  * `Physics` is a renderer-owned outer wrapper
119
117
  * every other component `View` wraps the current subtree
120
118
 
121
119
  Custom component `View`s use normal React Three Fiber composition with `children`.
122
120
 
121
+ For agent-authored custom meshes, use `BufferGeometry` with flat numeric arrays:
122
+
123
+ ```json
124
+ {
125
+ "id": "triangle",
126
+ "components": {
127
+ "bufferGeometry": {
128
+ "type": "BufferGeometry",
129
+ "properties": {
130
+ "positions": [0, 0, 0, 1, 0, 0, 0, 1, 0],
131
+ "indices": [0, 1, 2],
132
+ "computeVertexNormals": true
133
+ }
134
+ },
135
+ "material": {
136
+ "type": "Material",
137
+ "properties": { "color": "#ff8844" }
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
123
143
  ## Prefab Format
124
144
 
125
145
  ```ts
@@ -141,7 +161,7 @@ interface GameObject {
141
161
 
142
162
  ## Runtime Mutation
143
163
 
144
- Use the `Scene` API from `PrefabEditorRef` for authored state changes that should stay in prefab data.
164
+ Use native object access for Three.js behavior and the prefab store for authored data changes.
145
165
 
146
166
  ```tsx
147
167
  import { useEffect, useRef } from "react";
@@ -151,48 +171,105 @@ function RaiseBall() {
151
171
  const editorRef = useRef<PrefabEditorRef>(null);
152
172
 
153
173
  useEffect(() => {
154
- const transform = editorRef.current
155
- ?.scene
156
- .find("ball")
157
- ?.getComponent<{ position: [number, number, number] }>("Transform");
158
-
159
- transform?.set("position", [0, 8, 0]);
174
+ editorRef.current?.store.getState().updateNode("ball", (node) => ({
175
+ ...node,
176
+ components: {
177
+ ...node.components,
178
+ transform: {
179
+ type: "Transform",
180
+ properties: {
181
+ ...node.components?.transform?.properties,
182
+ position: [0, 8, 0],
183
+ },
184
+ },
185
+ },
186
+ }));
160
187
  }, []);
161
188
 
162
189
  return <PrefabEditor ref={editorRef} initialPrefab={prefab} />;
163
190
  }
164
191
  ```
165
192
 
166
- Batch related entity changes so they flush as one store revision:
193
+ For live Three.js access, use mounted objects directly:
167
194
 
168
195
  ```tsx
169
- scene.batch(() => {
170
- scene.find("orb1")?.getComponent("Transform")?.set("position", [1, 0, 0]);
171
- scene.find("orb2")?.getComponent("Transform")?.set("position", [-1, 0, 0]);
172
- });
196
+ const ball = editorRef.current?.getObject("ball");
197
+ ball?.rotateY(0.5);
173
198
  ```
174
199
 
175
- Define a component runtime factory with `create(ctx)` for hot runtime behavior that mutates the live Three.js object directly:
200
+ Mounted node metadata is mirrored onto the canonical Three.js wrapper object:
201
+
202
+ * `GameObject.id` -> `object.userData.prefabNodeId`
203
+ * `GameObject.name` -> `object.name` and `object.userData.prefabNodeName`
204
+ * `Data.properties.data` -> merged into `object.userData`
205
+
206
+ That gives you a stable authored id for traversal-based integrations, while still making Three.js name lookup convenient:
176
207
 
177
208
  ```tsx
178
- import type { Component } from "react-three-game";
179
-
180
- const Rotator: Component = {
181
- name: "Rotator",
182
- Editor: () => null,
183
- defaultProperties: { speed: 1 },
184
- create(ctx) {
185
- return {
186
- update(dt) {
187
- const speed = ctx.component.get<number>("speed") ?? 1;
188
- ctx.object.rotation.y += dt * speed;
209
+ const playerByName = editorRef.current?.root?.getObjectByName("Player");
210
+ const playerById = editorRef.current?.root?.getObjectByProperty("userData.prefabNodeId", "player");
211
+ ```
212
+
213
+ Treat names as a convenience surface, not the primary lookup key:
214
+
215
+ * names are not guaranteed unique
216
+ * `getObject(id)` is still the stable authored-node lookup
217
+ * traversal metadata is applied to the prefab node transform object, not necessarily the inner mesh or model child
218
+
219
+ You can author extra `userData` from the editor with a `Data` component:
220
+
221
+ ```json
222
+ {
223
+ "data": {
224
+ "faction": "enemy",
225
+ "health": 100,
226
+ "loot": { "table": "crate" }
227
+ }
228
+ }
229
+ ```
230
+
231
+ For batched authored updates, write through the store once:
232
+
233
+ ```tsx
234
+ editorRef.current?.store.getState().updateNodes([
235
+ {
236
+ id: "orb1",
237
+ update: (node) => ({
238
+ ...node,
239
+ components: {
240
+ ...node.components,
241
+ transform: {
242
+ ...node.components?.transform,
243
+ type: "Transform",
244
+ properties: {
245
+ ...node.components?.transform?.properties,
246
+ position: [1, 0, 0],
247
+ },
248
+ },
189
249
  },
190
- };
250
+ }),
191
251
  },
192
- };
252
+ {
253
+ id: "orb2",
254
+ update: (node) => ({
255
+ ...node,
256
+ components: {
257
+ ...node.components,
258
+ transform: {
259
+ ...node.components?.transform,
260
+ type: "Transform",
261
+ properties: {
262
+ ...node.components?.transform?.properties,
263
+ position: [-1, 0, 0],
264
+ },
265
+ },
266
+ },
267
+ }),
268
+ },
269
+ ]);
193
270
  ```
194
271
 
195
- `View` handles composition and rendering. `create(ctx)` handles imperative runtime behavior.
272
+ Custom component `View`s should use normal React and R3F behavior, such as `useFrame`, refs, and native Three.js APIs.
196
273
 
197
274
  ## Useful Exports
198
275
 
@@ -202,9 +279,6 @@ const Rotator: Component = {
202
279
  * `PrefabEditorMode`
203
280
  * `Prefab`
204
281
  * `GameObject`
205
- * `Scene`
206
- * `Entity`
207
- * `EntityComponent`
208
282
  * `registerComponent`
209
283
  * `createPrefabStore`
210
284
  * `usePrefabStoreApi`
package/dist/index.d.ts CHANGED
@@ -10,7 +10,6 @@ export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
10
10
  export type { EditorContextType } from './tools/prefabeditor/PrefabEditor';
11
11
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
12
12
  export type { PrefabStoreApi, PrefabStoreState } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/scene';
14
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
15
14
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
16
15
  export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
@@ -18,11 +17,10 @@ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computePar
18
17
  export type { ExportGLBOptions } from './tools/prefabeditor/utils';
19
18
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
20
19
  export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
21
- export type { SpawnOptions, Scene, Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, SceneUpdates, } from './tools/prefabeditor/scene';
20
+ export type { SpawnOptions, } from './tools/prefabeditor/scene';
22
21
  export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
23
- export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/runtime';
24
- export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
25
- export type { ComponentInstance, ComponentRuntimeContext } from './tools/prefabeditor/runtime';
22
+ export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/assetRuntime';
23
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
26
24
  export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
27
25
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
28
26
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
@@ -30,8 +28,6 @@ export type { MaterialOverrides } from './tools/prefabeditor/components/Material
30
28
  export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
31
29
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
32
30
  export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
33
- export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
34
- export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, InteractionEventType, PhysicsEventPayload, ClickEventPayload } from './tools/prefabeditor/GameEvents';
35
31
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
36
32
  export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
37
33
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
package/dist/index.js CHANGED
@@ -10,7 +10,6 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
10
10
  export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
11
11
  // Prefab Editor - Store & Scene API
12
12
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/scene';
14
13
  export { denormalizePrefab } from './tools/prefabeditor/prefab';
15
14
  // Prefab Editor - Component Registry
16
15
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
@@ -19,12 +18,10 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
19
18
  // Prefab Editor - Utils
20
19
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
21
20
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
22
- export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtime';
21
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/assetRuntime';
23
22
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
24
23
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
25
24
  export { float, positionLocal, sin, time, uniform, vec3, } from 'three/tsl';
26
- // Game Events (physics + custom events)
27
- export { gameEvents, useGameEvent, usePhysicsEvent, useClickEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
28
25
  // Asset Loading
29
26
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
30
27
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
@@ -12,8 +12,6 @@ export declare function getRepeatAxesFromModelProperties(properties: Record<stri
12
12
  export type InstanceData = {
13
13
  id: string;
14
14
  sourceId: string;
15
- clickable?: boolean;
16
- clickEventName?: string;
17
15
  locked?: boolean;
18
16
  position: [number, number, number];
19
17
  rotation: [number, number, number];
@@ -35,8 +33,6 @@ export declare function useInstanceCheck(id: string): boolean;
35
33
  export declare const GameInstance: React.ForwardRefExoticComponent<{
36
34
  id: string;
37
35
  sourceId?: string;
38
- clickable?: boolean;
39
- clickEventName?: string;
40
36
  modelUrl: string;
41
37
  locked?: boolean;
42
38
  position: [number, number, number];
@@ -15,8 +15,7 @@ import { Merged, useHelper } from '@react-three/drei';
15
15
  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
- import { gameEvents, getEntityIdFromRigidBody } from "./GameEvents";
19
- import { useClickValid } from "./useClickValid";
18
+ import { usePointerEvents } from "./usePointerEvents";
20
19
  export const DEFAULT_REPEAT_AXES = [{ axis: 'x', count: 1, offset: 1 }];
21
20
  export function normalizeRepeatAxes(value) {
22
21
  if (!Array.isArray(value)) {
@@ -103,32 +102,9 @@ function hasPhysics(instance) {
103
102
  function getColliderType(physics) {
104
103
  return physics.colliders || (physics.type === 'fixed' ? 'trimesh' : 'hull');
105
104
  }
106
- function emitPhysicsEvent(sourceId, eventName, payload) {
107
- if (!eventName)
108
- return;
109
- gameEvents.emit(eventName, {
110
- sourceEntityId: sourceId,
111
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
112
- targetRigidBody: payload.other.rigidBody,
113
- });
114
- }
115
- function emitClick(sourceId, instanceId, eventName, event) {
116
- gameEvents.emit(eventName, {
117
- sourceEntityId: sourceId,
118
- instanceEntityId: instanceId && instanceId !== sourceId ? instanceId : undefined,
119
- point: [event.point.x, event.point.y, event.point.z],
120
- button: event.button,
121
- altKey: event.nativeEvent.altKey,
122
- ctrlKey: event.nativeEvent.ctrlKey,
123
- metaKey: event.nativeEvent.metaKey,
124
- shiftKey: event.nativeEvent.shiftKey,
125
- });
126
- }
127
105
  function instanceEquals(a, b) {
128
106
  return a.id === b.id &&
129
107
  a.sourceId === b.sourceId &&
130
- a.clickable === b.clickable &&
131
- a.clickEventName === b.clickEventName &&
132
108
  a.locked === b.locked &&
133
109
  a.meshPath === b.meshPath &&
134
110
  arrayEquals(a.position, b.position) &&
@@ -245,7 +221,7 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
245
221
  const rigidBodiesRef = useRef(null);
246
222
  const instances = useMemo(() => group.instances.filter(hasPhysics).map(inst => {
247
223
  const _a = inst.physics, { activeCollisionTypes: _activeCollisionTypes, colliders: _colliders, userData } = _a, rigidBodyProps = __rest(_a, ["activeCollisionTypes", "colliders", "userData"]);
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) });
224
+ 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 }) });
249
225
  }), [group.instances]);
250
226
  // Apply scale to visual meshes (InstancedRigidBodies only scales colliders, not visuals)
251
227
  useEffect(() => {
@@ -317,12 +293,8 @@ function InstancedRigidGroup({ group, modelKey, partCount, flatMeshes, onSelect,
317
293
  onSelect(instance.sourceId);
318
294
  return;
319
295
  }
320
- if (!instance.clickable)
321
- return;
322
- e.stopPropagation();
323
- emitClick(instance.sourceId, instance.id, instance.clickEventName || 'click', e);
324
296
  };
325
- const shouldHandleClick = editMode || group.instances.some(inst => inst.clickable);
297
+ const shouldHandleClick = editMode;
326
298
  // Add key to force remount when instance count changes significantly (helps with cleanup)
327
299
  const rigidBodyKey = `rb_${modelKey}_${group.instances.map(inst => `${inst.id}:${getPhysicsSignature(inst.physics)}`).join('|')}`;
328
300
  return (_jsx(InstancedRigidBodies, { ref: rigidBodiesRef, instances: instances, children: Array.from({ length: partCount }).map((_, i) => {
@@ -344,14 +316,15 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, registerRef
344
316
  const isLocked = Boolean(instance.locked);
345
317
  const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
346
318
  const canSelect = editMode && !isLocked;
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
- }
319
+ const canClick = false;
320
+ const pointerHandlers = usePointerEvents({
321
+ enabled: canSelect || canClick,
322
+ entity: instance,
323
+ onClick: () => {
324
+ if (editMode) {
325
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.sourceId);
326
+ }
327
+ },
355
328
  });
356
329
  // Use BoxHelper when object is selected in edit mode
357
330
  useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
@@ -370,7 +343,7 @@ export function useInstanceCheck(id) {
370
343
  return (_a = ctx === null || ctx === void 0 ? void 0 : ctx.hasInstance(id)) !== null && _a !== void 0 ? _a : false;
371
344
  }
372
345
  // GameInstance component: registers an instance for batch rendering (renders nothing itself)
373
- export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false, clickEventName, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
346
+ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked = false, position, rotation, scale, physics = undefined, }, ref) => {
374
347
  const ctx = useContext(GameInstanceContext);
375
348
  const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
376
349
  const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
@@ -381,8 +354,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false,
381
354
  const instance = useMemo(() => ({
382
355
  id,
383
356
  sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
384
- clickable,
385
- clickEventName,
386
357
  locked,
387
358
  meshPath: modelUrl,
388
359
  position,
@@ -392,8 +363,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, clickable = false,
392
363
  }), [
393
364
  id,
394
365
  sourceId,
395
- clickable,
396
- clickEventName,
397
366
  locked,
398
367
  modelUrl,
399
368
  positionX,
@@ -2,18 +2,23 @@ import GameCanvas from "../../shared/GameCanvas";
2
2
  import { Object3D, Texture } from "three";
3
3
  import { GameObject, Prefab } from "./types";
4
4
  import type { ExportGLBOptions } from "./utils";
5
- import { type Scene, type SpawnOptions } from "./scene";
5
+ import { type PrefabStoreApi } from "./prefabStore";
6
+ import type { SpawnOptions } from "./scene";
6
7
  export interface PrefabEditorRef {
8
+ root: Object3D | null;
9
+ store: PrefabStoreApi;
10
+ getObject: (nodeId: string) => Object3D | null;
11
+ getRigidBody: (nodeId: string) => any;
7
12
  screenshot: () => void;
8
13
  exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
9
14
  exportGLBData: () => Promise<ArrayBuffer | undefined>;
10
15
  clearSelection: () => Promise<void>;
11
16
  save: () => Prefab;
12
- scene: Scene;
13
17
  load: (prefab: Prefab, options?: {
14
18
  resetHistory?: boolean;
15
19
  notifyChange?: boolean;
16
20
  }) => void;
21
+ addNode: (node: GameObject, options?: SpawnOptions) => GameObject;
17
22
  addModel: (path: string, model: Object3D, options?: SpawnOptions) => GameObject;
18
23
  addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
19
24
  }
@@ -10,7 +10,7 @@ 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, useReducer, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
13
+ import { useCallback, useEffect, useReducer, useRef, useState, forwardRef, useImperativeHandle, createContext, useContext } from "react";
14
14
  import { findComponentEntry } from "./types";
15
15
  import PrefabRoot from "./PrefabRoot";
16
16
  import { Physics } from "@react-three/rapier";
@@ -20,7 +20,6 @@ import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, export
20
20
  import { loadFiles } from "../dragdrop";
21
21
  import { denormalizePrefab, createImageNode, createModelNode, createNode } from './prefab';
22
22
  import { createPrefabStore, PrefabStoreProvider } from "./prefabStore";
23
- import { createScene } from "./scene";
24
23
  function isObjectAttachedToRoot(root, object) {
25
24
  if (!root || !object)
26
25
  return false;
@@ -53,7 +52,6 @@ const DEFAULT_PREFAB = {
53
52
  root: createNode('Root', {}, { id: 'root' })
54
53
  };
55
54
  const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
56
- var _a, _b;
57
55
  const [mode, setMode] = useState(initialMode);
58
56
  const [selectedId, setSelectedId] = useState(null);
59
57
  const [transformMode, setTransformMode] = useState("translate");
@@ -73,6 +71,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
73
71
  const onChangeRef = useRef(onChange);
74
72
  const isEditMode = mode === PrefabEditorMode.Edit;
75
73
  const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
74
+ const getRootObject = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
76
75
  const getObject = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
77
76
  const getRigidBody = useCallback((nodeId) => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getRigidBody(nodeId)) !== null && _b !== void 0 ? _b : null; }, []);
78
77
  const handleObjectRefChange = useCallback((nodeId) => {
@@ -173,7 +172,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
173
172
  return () => unsubscribe();
174
173
  }, [prefabStore, selectedId]);
175
174
  const selectedObject = selectedId ? getObject(selectedId) : null;
176
- const transformObject = isObjectAttachedToRoot((_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null, selectedObject)
175
+ const transformObject = isObjectAttachedToRoot(getRootObject(), selectedObject)
177
176
  ? selectedObject
178
177
  : null;
179
178
  const addNode = useCallback((node, options) => {
@@ -251,21 +250,19 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
251
250
  });
252
251
  }), [setSelection]);
253
252
  const handleExportGLB = useCallback((...args_1) => __awaiter(void 0, [...args_1], void 0, function* (options = {}) {
254
- var _a;
255
253
  yield clearSelection();
256
- const rootObject = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
254
+ const rootObject = getRootObject();
257
255
  if (!rootObject)
258
256
  return;
259
257
  return exportGLBFile(rootObject, Object.assign({ filename: `${prefabStore.getState().prefabName || 'prefab'}.glb` }, options));
260
- }), [clearSelection, prefabStore]);
258
+ }), [clearSelection, getRootObject, prefabStore]);
261
259
  const handleExportGLBData = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
262
- var _a;
263
260
  yield clearSelection();
264
- const rootObject = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root;
261
+ const rootObject = getRootObject();
265
262
  if (!rootObject)
266
263
  return;
267
264
  return exportGLBData(rootObject);
268
- }), [clearSelection]);
265
+ }), [clearSelection, getRootObject]);
269
266
  const handleFocusNode = useCallback((nodeId) => {
270
267
  const object = getObject(nodeId);
271
268
  const controls = controlsRef.current;
@@ -274,18 +271,6 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
274
271
  return;
275
272
  focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
276
273
  }, [getObject]);
277
- const scene = useMemo(() => createScene({
278
- getRootId: () => prefabStore.getState().rootId,
279
- getNode: (id) => { var _a; return (_a = prefabStore.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
280
- getChildIds: (id) => { var _a; return (_a = prefabStore.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
281
- getParentId: (id) => { var _a; return (_a = prefabStore.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
282
- updateNode: (id, update) => prefabStore.getState().updateNode(id, update),
283
- updateNodes: (updates) => prefabStore.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update }))),
284
- addNode: (node, options) => addNode(node, options).id,
285
- removeNode: (id) => prefabStore.getState().deleteNode(id),
286
- getObject,
287
- getRigidBody,
288
- }), [addNode, getObject, getRigidBody, prefabStore]);
289
274
  const handleTransformChange = () => {
290
275
  if (!selectedId)
291
276
  return;
@@ -342,16 +327,20 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
342
327
  };
343
328
  }, [addModel, addTexture, isEditMode, enableWindowDrop]);
344
329
  useImperativeHandle(ref, () => ({
330
+ root: getRootObject(),
331
+ store: prefabStore,
332
+ getObject,
333
+ getRigidBody,
345
334
  screenshot: handleScreenshot,
346
335
  exportGLB: handleExportGLB,
347
336
  exportGLBData: handleExportGLBData,
348
337
  clearSelection,
349
338
  save: getPrefab,
350
- scene,
351
339
  load: loadPrefab,
340
+ addNode,
352
341
  addModel,
353
342
  addTexture
354
- }), [addModel, addTexture, clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, scene]);
343
+ }), [addModel, addNode, addTexture, clearSelection, getObject, getPrefab, getRigidBody, getRootObject, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, prefabStore]);
355
344
  const content = (_jsxs(_Fragment, { children: [isEditMode ? _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }) : null, _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onObjectRefChange: handleObjectRefChange, basePath: basePath }), children] }));
356
345
  const handleCanvasCreated = useCallback((state) => {
357
346
  var _a;