react-three-game 0.0.71 → 0.0.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -19,16 +19,15 @@ export type { ExportGLBOptions } from './tools/prefabeditor/utils';
19
19
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
20
20
  export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
21
21
  export type { SpawnOptions, Scene, Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, SceneUpdates, } from './tools/prefabeditor/scene';
22
- export type { PrefabRootProps, PrefabRootRef, SceneRuntime } from './tools/prefabeditor/PrefabRoot';
23
- export { useSceneRuntime } from './tools/prefabeditor/PrefabRoot';
22
+ export type { PrefabRootProps, AssetRuntime } from './tools/prefabeditor/PrefabRoot';
23
+ export type { EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/PrefabRoot';
24
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/PrefabRoot';
24
25
  export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
25
26
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
26
27
  export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
27
28
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
28
29
  export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
29
30
  export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, InteractionEventType, PhysicsEventPayload, ClickEventPayload } from './tools/prefabeditor/GameEvents';
30
- export { createRefBridge } from './tools/prefabeditor/RefBridge';
31
- export type { RefBridge } from './tools/prefabeditor/RefBridge';
32
31
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
33
32
  export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
34
33
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
package/dist/index.js CHANGED
@@ -19,12 +19,10 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
19
19
  // Prefab Editor - Utils
20
20
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
21
21
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
22
- export { useSceneRuntime } from './tools/prefabeditor/PrefabRoot';
22
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/PrefabRoot';
23
23
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
24
24
  // Game Events (physics + custom events)
25
25
  export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
26
- // RefBridge & Systems
27
- export { createRefBridge } from './tools/prefabeditor/RefBridge';
28
26
  // Asset Loading
29
27
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
30
28
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
@@ -1,7 +1,6 @@
1
1
  import GameCanvas from "../../shared/GameCanvas";
2
2
  import { Object3D, Texture } from "three";
3
3
  import { GameObject, Prefab } from "./types";
4
- import { PrefabRootRef } from "./PrefabRoot";
5
4
  import type { ExportGLBOptions } from "./utils";
6
5
  import { type Scene, type SpawnOptions } from "./scene";
7
6
  export interface PrefabEditorRef {
@@ -17,7 +16,6 @@ export interface PrefabEditorRef {
17
16
  }) => void;
18
17
  addModel: (path: string, model: Object3D, options?: SpawnOptions) => GameObject;
19
18
  addTexture: (path: string, texture: Texture, options?: SpawnOptions) => GameObject;
20
- viewRef: React.RefObject<PrefabRootRef | null>;
21
19
  }
22
20
  export declare enum PrefabEditorMode {
23
21
  Edit = "edit",
@@ -61,6 +61,8 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
61
61
  const onChangeRef = useRef(onChange);
62
62
  const isEditMode = mode === PrefabEditorMode.Edit;
63
63
  const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
64
+ 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; }, []);
65
+ 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; }, []);
64
66
  onChangeRef.current = onChange;
65
67
  const setSelection = useCallback((nodeId) => {
66
68
  const nextNode = nodeId ? prefabStore.getState().nodesById[nodeId] : null;
@@ -157,13 +159,12 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
157
159
  return () => unsubscribe();
158
160
  }, [prefabStore, selectedId]);
159
161
  useEffect(() => {
160
- var _a, _b;
161
162
  if (!selectedId) {
162
163
  setSelectedObject(null);
163
164
  return;
164
165
  }
165
- setSelectedObject((_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(selectedId)) !== null && _b !== void 0 ? _b : null);
166
- }, [selectedId]);
166
+ setSelectedObject(getObject(selectedId));
167
+ }, [getObject, selectedId]);
167
168
  const addNode = useCallback((node, options) => {
168
169
  var _a;
169
170
  const { addChild, rootId } = prefabStore.getState();
@@ -256,39 +257,29 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
256
257
  return exportGLBData(rootObject);
257
258
  }), [clearSelection]);
258
259
  const handleFocusNode = useCallback((nodeId) => {
259
- var _a;
260
- const object = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(nodeId);
260
+ const object = getObject(nodeId);
261
261
  const controls = controlsRef.current;
262
262
  const camera = controls === null || controls === void 0 ? void 0 : controls.object;
263
263
  if (!object || !controls || !camera)
264
264
  return;
265
265
  focusCameraOnObject(object, camera, controls.target, () => { var _a; return (_a = controls.update) === null || _a === void 0 ? void 0 : _a.call(controls); });
266
- }, []);
266
+ }, [getObject]);
267
267
  const scene = useMemo(() => createScene({
268
268
  getRootId: () => prefabStore.getState().rootId,
269
269
  getNode: (id) => { var _a; return (_a = prefabStore.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
270
270
  getChildIds: (id) => { var _a; return (_a = prefabStore.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
271
271
  getParentId: (id) => { var _a; return (_a = prefabStore.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
272
- findByName: (name) => {
273
- var _a;
274
- const state = prefabStore.getState();
275
- const normalized = name.toLowerCase();
276
- for (const [id, node] of Object.entries(state.nodesById)) {
277
- if (((_a = node.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === normalized)
278
- return id;
279
- }
280
- return null;
281
- },
282
272
  updateNode: (id, update) => prefabStore.getState().updateNode(id, update),
283
273
  updateNodes: (updates) => prefabStore.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update }))),
284
274
  addNode: (node, options) => addNode(node, options).id,
285
275
  removeNode: (id) => prefabStore.getState().deleteNode(id),
286
- }), [addNode, prefabStore]);
276
+ getObject,
277
+ getRigidBody,
278
+ }), [addNode, getObject, getRigidBody, prefabStore]);
287
279
  const handleTransformChange = () => {
288
- var _a;
289
280
  if (!selectedId)
290
281
  return;
291
- const object = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.getObject(selectedId);
282
+ const object = getObject(selectedId);
292
283
  if (!object)
293
284
  return;
294
285
  const parentWorld = computeParentWorldMatrix(prefabStore.getState(), selectedId);
@@ -349,10 +340,9 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
349
340
  scene,
350
341
  load: loadPrefab,
351
342
  addModel,
352
- addTexture,
353
- viewRef: prefabRootRef
343
+ addTexture
354
344
  }), [addModel, addTexture, clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, scene]);
355
- const content = (_jsxs(_Fragment, { children: [_jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, basePath: basePath }), children] }));
345
+ 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, basePath: basePath }), children] }));
356
346
  return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsxs(EditorContext.Provider, { value: {
357
347
  mode,
358
348
  setMode: updateMode,
@@ -3,14 +3,10 @@ import { ThreeEvent } from "@react-three/fiber";
3
3
  import { GameObject as GameObjectType, Prefab } from "./types";
4
4
  import { LoadedModels } from "../dragdrop";
5
5
  import { PrefabStoreApi } from "./prefabStore";
6
- import { type RefBridge } from "./RefBridge";
7
- /** Runtime context available to all component Views inside a PrefabRoot. */
8
- export interface SceneRuntime {
9
- refBridge: RefBridge;
10
- getRigidBody: (id: string) => any;
6
+ /** Scene-wide shared services available to component Views inside a PrefabRoot. */
7
+ export interface AssetRuntime {
11
8
  /** @internal Used by PhysicsComponent. */
12
9
  registerRigidBodyRef: (id: string, rb: any) => void;
13
- editMode?: boolean;
14
10
  /** Get a loaded model by asset path. */
15
11
  getModel: (path: string) => Object3D | null;
16
12
  /** Get a loaded texture by asset path. */
@@ -20,12 +16,29 @@ export interface SceneRuntime {
20
16
  /** Get a revision string that changes when loaded assets change (for cache-busting keys). */
21
17
  getAssetRevision: () => string;
22
18
  }
23
- /** Access the scene runtime (refBridge, getRigidBody, editMode) from within a component View. */
24
- export declare function useSceneRuntime(): SceneRuntime;
19
+ export interface EntityRuntime {
20
+ nodeId: string;
21
+ editMode?: boolean;
22
+ isSelected?: boolean;
23
+ getObject: <T extends Object3D = Object3D>() => T | null;
24
+ getRigidBody: <T = any>() => T | null;
25
+ }
26
+ export interface LiveObjectRef<T extends Object3D = Object3D> {
27
+ readonly current: T | null;
28
+ }
29
+ export interface LiveRigidBodyRef<T = any> {
30
+ readonly current: T | null;
31
+ }
32
+ /** Access scene-wide shared services from within a component View. */
33
+ export declare function useAssetRuntime(): AssetRuntime;
34
+ /** Access the current node's runtime from within a component View. */
35
+ export declare function useEntityRuntime(): EntityRuntime;
36
+ /** Read the current component's Object3D through a live ref-like accessor. */
37
+ export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveObjectRef<T>;
38
+ /** Read the current component's rigid body through a live ref-like accessor. */
39
+ export declare function useEntityRigidBodyRef<T = any>(): LiveRigidBodyRef<T>;
25
40
  export interface PrefabRootRef {
26
41
  root: Group | null;
27
- refBridge: RefBridge;
28
- rigidBodyRefs: Map<string, any>;
29
42
  getObject: (nodeId: string) => Object3D | null;
30
43
  getRigidBody: (nodeId: string) => any;
31
44
  addModel: (path: string, model: Object3D) => void;
@@ -24,21 +24,60 @@ import { composeTransform, decompose } from "./utils";
24
24
  import { isPhysicsProps } from "./components/PhysicsComponent";
25
25
  import { denormalizePrefab } from "./prefab";
26
26
  import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
27
- import { createRefBridge } from "./RefBridge";
28
27
  import { sound as soundManager } from "../../helpers/SoundManager";
29
28
  components.forEach(registerComponent);
30
29
  const IDENTITY = new Matrix4();
31
30
  const EMPTY_MODELS = {};
32
31
  const EMPTY_TEXTURES = {};
33
32
  const EMPTY_SOUNDS = {};
34
- const SceneRuntimeContext = createContext(null);
35
- /** Access the scene runtime (refBridge, getRigidBody, editMode) from within a component View. */
36
- export function useSceneRuntime() {
37
- const ctx = useContext(SceneRuntimeContext);
33
+ const AssetRuntimeContext = createContext(null);
34
+ const EntityRuntimeContext = createContext(null);
35
+ /** Access scene-wide shared services from within a component View. */
36
+ export function useAssetRuntime() {
37
+ const ctx = useContext(AssetRuntimeContext);
38
38
  if (!ctx)
39
- throw new Error("useSceneRuntime must be used inside <PrefabRoot>");
39
+ throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
40
40
  return ctx;
41
41
  }
42
+ /** Access the current node's runtime from within a component View. */
43
+ export function useEntityRuntime() {
44
+ const ctx = useContext(EntityRuntimeContext);
45
+ if (!ctx)
46
+ throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
47
+ return ctx;
48
+ }
49
+ /** Read the current component's Object3D through a live ref-like accessor. */
50
+ export function useEntityObjectRef() {
51
+ const { getObject } = useEntityRuntime();
52
+ return useMemo(() => ({
53
+ get current() {
54
+ return getObject();
55
+ },
56
+ }), [getObject]);
57
+ }
58
+ /** Read the current component's rigid body through a live ref-like accessor. */
59
+ export function useEntityRigidBodyRef() {
60
+ const { getRigidBody } = useEntityRuntime();
61
+ return useMemo(() => ({
62
+ get current() {
63
+ return getRigidBody();
64
+ },
65
+ }), [getRigidBody]);
66
+ }
67
+ function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
68
+ const assetRuntime = useContext(AssetRuntimeContext);
69
+ if (!assetRuntime)
70
+ throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
71
+ const { getObject, getRigidBody } = assetRuntime;
72
+ const value = useMemo(() => ({
73
+ nodeId,
74
+ editMode,
75
+ isSelected,
76
+ getObject: () => getObject(nodeId),
77
+ getRigidBody: () => getRigidBody(nodeId),
78
+ }), [editMode, getObject, getRigidBody, isSelected, nodeId]);
79
+ return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
80
+ }
42
81
  /** Resolve a relative or absolute asset file path against a base path. */
43
82
  function resolveAssetPath(basePath, file) {
44
83
  if (file.startsWith("http://") || file.startsWith("https://"))
@@ -68,7 +107,6 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
68
107
  const objectRefs = useRef({});
69
108
  const rigidBodyRefs = useRef(new Map());
70
109
  const rootRef = useRef(null);
71
- const [refBridge] = useState(() => createRefBridge());
72
110
  const parentStore = useOptionalPrefabStoreApi();
73
111
  const [ownedStore] = useState(() => { var _a, _b; return createPrefabStore(data !== null && data !== void 0 ? data : denormalizePrefab((_b = (_a = store === null || store === void 0 ? void 0 : store.getState()) !== null && _a !== void 0 ? _a : parentStore === null || parentStore === void 0 ? void 0 : parentStore.getState()) !== null && _b !== void 0 ? _b : missingStoreState())); });
74
112
  const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
@@ -78,11 +116,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
78
116
  const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
79
117
  const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
80
118
  const availableSounds = useMemo(() => (Object.assign(Object.assign({}, sounds), injectedSounds)), [sounds, injectedSounds]);
119
+ const getObject = useCallback((id) => {
120
+ var _a;
121
+ return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
122
+ }, []);
81
123
  useImperativeHandle(ref, () => ({
82
124
  root: rootRef.current,
83
- refBridge,
84
- rigidBodyRefs: rigidBodyRefs.current,
85
- getObject: (nodeId) => { var _a; return (_a = objectRefs.current[nodeId]) !== null && _a !== void 0 ? _a : null; },
125
+ getObject,
86
126
  getRigidBody: (nodeId) => { var _a; return (_a = rigidBodyRefs.current.get(nodeId)) !== null && _a !== void 0 ? _a : null; },
87
127
  addModel: (path, model) => setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model }))),
88
128
  addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
@@ -90,11 +130,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
90
130
  soundManager.setBuffer(path, sound);
91
131
  setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
92
132
  },
93
- }), [refBridge]);
133
+ }), [getObject]);
94
134
  const registerRef = useCallback((id, obj) => {
95
135
  objectRefs.current[id] = obj;
96
- refBridge.register(id, obj);
97
- }, [refBridge]);
136
+ }, []);
98
137
  const registerRigidBodyRef = useCallback((id, rb) => {
99
138
  rigidBodyRefs.current.set(id, rb);
100
139
  }, []);
@@ -180,11 +219,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
180
219
  availableTexturesRef.current = availableTextures;
181
220
  const availableSoundsRef = useRef(availableSounds);
182
221
  availableSoundsRef.current = availableSounds;
183
- const sceneRuntime = useMemo(() => ({
184
- refBridge,
222
+ const assetRuntime = useMemo(() => ({
223
+ getObject,
185
224
  getRigidBody,
186
225
  registerRigidBodyRef,
187
- editMode,
188
226
  getModel: (path) => { var _a; return (_a = availableModelsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
189
227
  getTexture: (path) => { var _a; return (_a = availableTexturesRef.current[path]) !== null && _a !== void 0 ? _a : null; },
190
228
  getSound: (path) => { var _a; return (_a = availableSoundsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
@@ -193,12 +231,12 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
193
231
  const textureKeys = Object.keys(availableTexturesRef.current).sort().join('|');
194
232
  return `${textureKeys}::${modelKeys}`;
195
233
  },
196
- }), [refBridge, getRigidBody, registerRigidBodyRef, editMode]);
234
+ }), [getObject, getRigidBody, registerRigidBodyRef]);
197
235
  const content = (_jsx("group", { ref: rootRef, children: _jsx(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }) }) }));
198
236
  if (!shouldProvideStoreContext) {
199
- return _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content });
237
+ return _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
200
238
  }
201
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content }) });
239
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) });
202
240
  });
203
241
  function StoreRootNode(props) {
204
242
  const rootId = usePrefabRootId();
@@ -289,14 +327,8 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
289
327
  const childNodes = getChildHostComponents(gameObject).length > 0
290
328
  ? _jsx(CompositionChildren, { childIds: childIds, selectedId: selectedId, ctx: renderCtx, parentMatrix: world })
291
329
  : _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
292
- const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, isSelected, parentMatrix, childNodes) })));
293
- if (editMode) {
294
- return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: nodeId, children: inner }, physicsKey)) : null] }));
295
- }
296
- if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
297
- return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: nodeId, children: inner }, physicsKey));
298
- }
299
- return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }));
330
+ const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, childNodes) })));
331
+ return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }, physicsKey)) : null] })) : hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }, physicsKey)) : (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner })) }));
300
332
  }
301
333
  function getChildHostComponents(gameObject) {
302
334
  var _a;
@@ -385,7 +417,7 @@ function renderCompositionSubtree(gameObject, ctx, isSelected, childIds, parentM
385
417
  const transform = getNodeTransformProps(gameObject);
386
418
  const world = parentMatrix.clone().multiply(compose(gameObject));
387
419
  const childNodes = _jsx(CompositionChildren, { childIds: childIds, ctx: ctx, parentMatrix: world });
388
- return (_jsx("group", { position: transform.position, rotation: transform.rotation, scale: transform.scale, children: renderCompositionNode(gameObject, ctx, isSelected, parentMatrix, childNodes) }, gameObject.id));
420
+ return (_jsx(EntityRuntimeScope, { nodeId: gameObject.id, editMode: ctx.editMode, isSelected: isSelected, children: _jsx("group", { position: transform.position, rotation: transform.rotation, scale: transform.scale, children: renderCompositionNode(gameObject, ctx, childNodes) }, gameObject.id) }));
389
421
  }
390
422
  function CompositionChildren({ childIds, selectedId, ctx, parentMatrix, }) {
391
423
  return childIds.map(childId => (_jsx(CompositionSubtree, { nodeId: childId, selectedId: selectedId, ctx: ctx, parentMatrix: parentMatrix }, childId)));
@@ -398,31 +430,22 @@ function CompositionSubtree({ nodeId, selectedId, ctx, parentMatrix, }) {
398
430
  return null;
399
431
  return renderCompositionSubtree(gameObject, ctx, isSelected, childIds, parentMatrix);
400
432
  }
401
- function renderCompositionNode(gameObject, ctx, isSelected, parentMatrix, childNodes) {
402
- const ownContent = renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix);
403
- return wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, _jsxs(_Fragment, { children: [ownContent, childNodes] }));
404
- }
405
- function buildContextProps(gameObject, _ctx, isSelected) {
406
- return {
407
- editMode: _ctx.editMode,
408
- isSelected,
409
- nodeId: gameObject.id,
410
- };
433
+ function renderCompositionNode(gameObject, ctx, childNodes) {
434
+ const ownContent = renderNodeOwnContent(gameObject);
435
+ return wrapWithChildHosts(gameObject, _jsxs(_Fragment, { children: [ownContent, childNodes] }));
411
436
  }
412
- function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
437
+ function renderNodeOwnContent(gameObject) {
413
438
  const geometry = findComponent(gameObject, "Geometry");
414
439
  const material = findComponent(gameObject, "Material");
415
440
  const geometryDef = geometry && getComponentDef(geometry.type);
416
441
  const materialDef = material && getComponentDef(material.type);
417
442
  if (!geometry || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
418
443
  return null;
419
- const contextProps = buildContextProps(gameObject, ctx, isSelected);
420
- return (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material"))] }));
444
+ return (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, { properties: geometry.properties }), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: material.properties }, "material"))] }));
421
445
  }
422
- function wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, subtree) {
423
- const contextProps = buildContextProps(gameObject, ctx, isSelected);
446
+ function wrapWithChildHosts(gameObject, subtree) {
424
447
  const childHosts = getChildHostComponents(gameObject);
425
- return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), subtree);
448
+ return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, { properties: properties, children: acc }, key)), subtree);
426
449
  }
427
450
  export default PrefabRoot;
428
451
  function missingStoreState() {
@@ -3,6 +3,7 @@ import { PerspectiveCamera, useHelper } from '@react-three/drei';
3
3
  import { useRef } from 'react';
4
4
  import { CameraHelper } from 'three';
5
5
  import { useFrame } from '@react-three/fiber';
6
+ import { useEntityRuntime } from '../PrefabRoot';
6
7
  import { FieldGroup, NumberField } from './Input';
7
8
  const cameraDefaults = {
8
9
  fov: 50,
@@ -14,7 +15,8 @@ function CameraComponentEditor({ component, onUpdate }) {
14
15
  const values = Object.assign(Object.assign({}, cameraDefaults), component.properties);
15
16
  return (_jsxs(FieldGroup, { children: [_jsx(NumberField, { name: "fov", label: "FOV", values: values, onChange: onUpdate, fallback: 50, min: 1, max: 179, step: 1 }), _jsx(NumberField, { name: "near", label: "Near", values: values, onChange: onUpdate, fallback: 0.1, min: 0.001, step: 0.1 }), _jsx(NumberField, { name: "zoom", label: "Zoom", values: values, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "far", label: "Far", values: values, onChange: onUpdate, fallback: 1000, min: 0.1, step: 1 })] }));
16
17
  }
17
- function CameraComponentView({ properties, children, editMode, isSelected }) {
18
+ function CameraComponentView({ properties, children }) {
19
+ const { editMode, isSelected } = useEntityRuntime();
18
20
  const merged = Object.assign(Object.assign({}, cameraDefaults), properties);
19
21
  const fov = merged.fov;
20
22
  const near = merged.near;
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useRef } from 'react';
3
3
  import { gameEvents } from '../GameEvents';
4
+ import { useEntityRuntime } from '../PrefabRoot';
4
5
  import { FieldGroup, StringField } from './Input';
5
6
  function ClickComponentEditor({ component, onUpdate }) {
6
7
  return (_jsxs(FieldGroup, { children: [_jsx("div", { style: { fontSize: 12, opacity: 0.8 }, children: "Emits a game event in play mode when this entity is clicked." }), _jsx(StringField, { name: "eventName", label: "Emit Event", values: component.properties, onChange: onUpdate, placeholder: "click" })] }));
7
8
  }
8
- function ClickComponentView({ children, editMode, nodeId, properties }) {
9
+ function ClickComponentView({ children, properties }) {
9
10
  const clickValid = useRef(false);
11
+ const { editMode, nodeId } = useEntityRuntime();
10
12
  const eventName = (properties === null || properties === void 0 ? void 0 : properties.eventName) || 'click';
11
13
  const emitClick = (event) => {
12
14
  if (!nodeId)
@@ -10,12 +10,6 @@ export interface ComponentViewProps<P = Record<string, any>> {
10
10
  properties: P;
11
11
  /** Children to render (for wrapper / child-host components). */
12
12
  children?: React.ReactNode;
13
- /** The entity ID this component belongs to. */
14
- nodeId?: string;
15
- /** True when the editor is in edit mode. */
16
- editMode?: boolean;
17
- /** True when this entity is selected in the editor. */
18
- isSelected?: boolean;
19
13
  /** Entity local position (passed to wrapper components like Physics). */
20
14
  position?: [number, number, number];
21
15
  /** Entity local rotation in radians (passed to wrapper components like Physics). */
@@ -3,6 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect, useState } from "react";
4
4
  import { useFrame } from "@react-three/fiber";
5
5
  import { CameraHelper, Vector3 } from "three";
6
+ import { useEntityRuntime } from "../PrefabRoot";
6
7
  import { BooleanField, ColorField, NumberField, NumberInput, Vector3Input } from "./Input";
7
8
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
8
9
  import { colors } from "../styles";
@@ -100,7 +101,8 @@ function DirectionalLightComponentEditor({ component, onUpdate }) {
100
101
  const values = mergeWithDefaults(directionalLightDefaults, component.properties);
101
102
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(Vector3Input, { label: "Target Offset", value: values.targetOffset, onChange: targetOffset => onUpdate({ targetOffset }), snap: 0.5 })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 }), _jsx(ShadowFrustumField, { values: values, onChange: onUpdate })] })) : null] })] }));
102
103
  }
103
- function DirectionalLightView({ properties, children, editMode, isSelected }) {
104
+ function DirectionalLightView({ properties, children }) {
105
+ const { editMode, isSelected } = useEntityRuntime();
104
106
  const merged = mergeWithDefaults(directionalLightDefaults, properties);
105
107
  const color = merged.color;
106
108
  const intensity = merged.intensity;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Environment } from '@react-three/drei';
3
3
  import { FieldGroup, NumberField } from './Input';
4
- import { useSceneRuntime } from '../PrefabRoot';
4
+ import { useAssetRuntime } from '../PrefabRoot';
5
5
  function EnvironmentView({ properties, children, }) {
6
- const { getAssetRevision } = useSceneRuntime();
6
+ const { getAssetRevision } = useAssetRuntime();
7
7
  const { intensity = 1, resolution = 256 } = properties;
8
8
  const environmentRevision = `${getAssetRevision()}::${intensity}::${resolution}`;
9
9
  return (_jsx(Environment, { background: true, environmentIntensity: intensity, resolution: resolution, frames: 1, children: children }, environmentRevision));
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { extend } from '@react-three/fiber';
14
14
  import { FieldRenderer, Label, NumberInput } from './Input';
15
- import { useSceneRuntime } from '../PrefabRoot';
15
+ import { useAssetRuntime } from '../PrefabRoot';
16
16
  import { useMemo } from 'react';
17
17
  import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
18
18
  import { TexturePicker } from '../../assetviewer/page';
@@ -122,7 +122,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
122
122
  // View for Material component
123
123
  function MaterialComponentView({ properties: rawProps }) {
124
124
  var _a, _b, _c, _d, _e;
125
- const { getTexture } = useSceneRuntime();
125
+ const { getTexture } = useAssetRuntime();
126
126
  const properties = rawProps;
127
127
  const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
128
128
  const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { ModelPicker } from '../../assetviewer/page';
3
3
  import { useContext, useMemo } from 'react';
4
4
  import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput } from './Input';
5
- import { useSceneRuntime } from '../PrefabRoot';
5
+ import { useAssetRuntime } from '../PrefabRoot';
6
6
  import { EditorContext } from '../PrefabEditor';
7
7
  import { DEFAULT_REPEAT_AXES, getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
8
8
  import { colors } from '../styles';
@@ -65,7 +65,7 @@ function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
65
65
  }
66
66
  // View for Model component
67
67
  function ModelComponentView({ properties, children }) {
68
- const { getModel } = useSceneRuntime();
68
+ const { getModel } = useAssetRuntime();
69
69
  // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
70
70
  if (!properties.filename || properties.instanced)
71
71
  return _jsx(_Fragment, { children: children });
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
14
14
  import { useRef, useEffect, useCallback } from 'react';
15
- import { useSceneRuntime } from "../PrefabRoot";
15
+ import { useAssetRuntime, useEntityRuntime } from "../PrefabRoot";
16
16
  import { BooleanField, FieldGroup, ListEditor, NumberField, SelectField, SelectInput, StringInput, Vector3Field } from "./Input";
17
17
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
18
18
  import { colors } from "../styles";
@@ -163,8 +163,9 @@ function PhysicsComponentEditor({ component, onUpdate }) {
163
163
  { value: 'all', label: 'All (includes kinematic & fixed)' },
164
164
  ] })] }));
165
165
  }
166
- function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId }) {
167
- const { registerRigidBodyRef } = useSceneRuntime();
166
+ function PhysicsComponentView({ properties, children, position, rotation, scale }) {
167
+ const { registerRigidBodyRef } = useAssetRuntime();
168
+ const { editMode, nodeId } = useEntityRuntime();
168
169
  const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, sensorEnterEventName, sensorExitEventName, collisionEnterEventName, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "capsuleRadius", "capsuleHalfHeight", "sensorEnterEventName", "sensorExitEventName", "collisionEnterEventName", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
169
170
  const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
170
171
  const usesManualCapsuleCollider = colliderType === 'capsule';
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { useHelper } from '@react-three/drei';
4
4
  import { PointLightHelper } from 'three';
5
+ import { useEntityRuntime } from '../PrefabRoot';
5
6
  import { BooleanField, ColorField, NumberField } from './Input';
6
7
  import { LightSection, ShadowBiasField, mergeWithDefaults } from './lightUtils';
7
8
  const pointLightDefaults = {
@@ -21,7 +22,8 @@ function PointLightComponentEditor({ component, onUpdate }) {
21
22
  const values = mergeWithDefaults(pointLightDefaults, component.properties);
22
23
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 0 }), _jsx(NumberField, { name: "decay", label: "Decay", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 2 })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 })] })) : null] })] }));
23
24
  }
24
- function PointLightView({ properties, children, editMode, isSelected }) {
25
+ function PointLightView({ properties, children }) {
26
+ const { editMode, isSelected } = useEntityRuntime();
25
27
  const merged = mergeWithDefaults(pointLightDefaults, properties);
26
28
  const color = merged.color;
27
29
  const intensity = merged.intensity;
@@ -4,7 +4,7 @@ import { useThree } from '@react-three/fiber';
4
4
  import { SoundPicker } from '../../assetviewer/page';
5
5
  import { sound as soundManager } from '../../../helpers/SoundManager';
6
6
  import { gameEvents } from '../GameEvents';
7
- import { useSceneRuntime } from '../PrefabRoot';
7
+ import { useAssetRuntime, useEntityRuntime } from '../PrefabRoot';
8
8
  import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
9
9
  import { colors } from '../styles';
10
10
  import { AudioListener } from 'three';
@@ -123,8 +123,9 @@ function payloadMatchesNode(nodeId, payload) {
123
123
  const hasEntityIds = ids.some(id => typeof id === 'string');
124
124
  return hasEntityIds ? ids.includes(nodeId) : true;
125
125
  }
126
- function SoundComponentView({ properties, editMode, nodeId, children }) {
127
- const { getSound } = useSceneRuntime();
126
+ function SoundComponentView({ properties, children }) {
127
+ const { getSound } = useAssetRuntime();
128
+ const { editMode, nodeId } = useEntityRuntime();
128
129
  const { camera } = useThree();
129
130
  const { eventName, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = 'inverse' } = properties;
130
131
  const sequenceIndexRef = useRef(0);
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect } from "react";
4
4
  import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
5
5
  import { SpotLightHelper } from "three";
6
- import { useSceneRuntime } from "../PrefabRoot";
6
+ import { useAssetRuntime, useEntityRuntime } from "../PrefabRoot";
7
7
  import { useFrame } from "@react-three/fiber";
8
8
  import { TexturePicker } from "../../assetviewer/page";
9
9
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
@@ -28,9 +28,10 @@ function SpotLightComponentEditor({ component, onUpdate, basePath = "" }) {
28
28
  const values = mergeWithDefaults(spotLightDefaults, component.properties);
29
29
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "angle", label: "Angle", values: values, onChange: onUpdate, min: 0, max: Math.PI / 2, step: 0.05, fallback: Math.PI / 3 }), _jsx(NumberField, { name: "penumbra", label: "Penumbra", values: values, onChange: onUpdate, min: 0, max: 1, step: 0.05, fallback: 0 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 0 }), _jsx(NumberField, { name: "decay", label: "Decay", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 2 }), _jsx(Vector3Input, { label: "Target Offset", value: values.targetOffset, onChange: targetOffset => onUpdate({ targetOffset }), snap: 0.5 }), _jsxs("div", { children: [_jsx(Label, { children: "Texture Map" }), _jsx(TexturePicker, { value: values.map, onChange: (map) => onUpdate({ map }), basePath: basePath })] })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 })] })) : null] })] }));
30
30
  }
31
- function SpotLightView({ properties, children, editMode, isSelected }) {
31
+ function SpotLightView({ properties, children }) {
32
32
  var _a;
33
- const { getTexture } = useSceneRuntime();
33
+ const { getTexture } = useAssetRuntime();
34
+ const { editMode, isSelected } = useEntityRuntime();
34
35
  const merged = mergeWithDefaults(spotLightDefaults, properties);
35
36
  const color = merged.color;
36
37
  const intensity = merged.intensity;
@@ -1,3 +1,4 @@
1
+ import type { Object3D } from "three";
1
2
  import type { GameObject } from "./types";
2
3
  export interface SpawnOptions {
3
4
  name?: string;
@@ -19,6 +20,8 @@ export interface Entity {
19
20
  readonly enabled: boolean;
20
21
  readonly parent: Entity | null;
21
22
  readonly children: Entity[];
23
+ readonly object: Object3D | null;
24
+ readonly rigidBody: any;
22
25
  set: (data: EntityData) => void;
23
26
  update: (update: (node: EntityData) => EntityData) => void;
24
27
  getComponent: <TProperties = Record<string, any>>(name: string) => EntityComponent<TProperties> | null;
@@ -30,7 +33,7 @@ export type EntityUpdate = (node: EntityData) => EntityData;
30
33
  export type SceneUpdates = Record<string, EntityUpdate>;
31
34
  export interface Scene {
32
35
  readonly rootId: string;
33
- find: (nameOrId: string) => Entity | null;
36
+ find: (id: string) => Entity | null;
34
37
  get: (id: string) => Entity;
35
38
  create: (name: string, components?: Record<string, {
36
39
  type: string;
@@ -48,11 +51,12 @@ interface SceneAdapter {
48
51
  getNode: (id: string) => EntityData | null;
49
52
  getChildIds: (id: string) => string[];
50
53
  getParentId: (id: string) => string | null;
51
- findByName: (name: string) => string | null;
52
54
  updateNode: (id: string, update: (node: EntityData) => EntityData) => void;
53
55
  updateNodes: (updates: Record<string, (node: EntityData) => EntityData>) => void;
54
56
  addNode: (node: GameObject, options?: SpawnOptions) => string;
55
57
  removeNode: (id: string) => void;
58
+ getObject?: (id: string) => Object3D | null;
59
+ getRigidBody?: (id: string) => any;
56
60
  }
57
61
  export declare function createScene(adapter: SceneAdapter): Scene;
58
62
  export {};
@@ -119,6 +119,14 @@ export function createScene(adapter) {
119
119
  get children() {
120
120
  return adapter.getChildIds(id).map(createNode);
121
121
  },
122
+ get object() {
123
+ var _a, _b;
124
+ return (_b = (_a = adapter.getObject) === null || _a === void 0 ? void 0 : _a.call(adapter, id)) !== null && _b !== void 0 ? _b : null;
125
+ },
126
+ get rigidBody() {
127
+ var _a, _b;
128
+ return (_b = (_a = adapter.getRigidBody) === null || _a === void 0 ? void 0 : _a.call(adapter, id)) !== null && _b !== void 0 ? _b : null;
129
+ },
122
130
  set(data) {
123
131
  adapter.updateNode(id, () => data);
124
132
  },
@@ -173,11 +181,8 @@ export function createScene(adapter) {
173
181
  get rootId() {
174
182
  return adapter.getRootId();
175
183
  },
176
- find(nameOrId) {
177
- if (adapter.getNode(nameOrId))
178
- return createNode(nameOrId);
179
- const foundId = adapter.findByName(nameOrId);
180
- return foundId ? createNode(foundId) : null;
184
+ find(id) {
185
+ return adapter.getNode(id) ? createNode(id) : null;
181
186
  },
182
187
  get: getNode,
183
188
  create(name, components, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.71",
3
+ "version": "0.0.72",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -1,24 +0,0 @@
1
- import { Object3D } from "three";
2
- /**
3
- * RefBridge — O(1) lookup from entity ID to live Three.js Object3D.
4
- *
5
- * In play mode the Scene API writes directly to these refs, bypassing
6
- * React reconciliation. In edit mode the store path is used instead.
7
- *
8
- * Component Views register/unregister refs on mount/unmount via
9
- * `bridge.register(id, obj)` / `bridge.unregister(id)`.
10
- */
11
- export interface RefBridge {
12
- register: (id: string, obj: Object3D | null) => void;
13
- unregister: (id: string) => void;
14
- get: (id: string) => Object3D | null;
15
- /** Apply a transform component's properties directly to the Object3D. */
16
- setTransform: (id: string, position?: number[], rotation?: number[], scale?: number[]) => void;
17
- /** Read current transform from the Object3D back into a properties bag. */
18
- readTransform: (id: string) => {
19
- position: [number, number, number];
20
- rotation: [number, number, number];
21
- scale: [number, number, number];
22
- } | null;
23
- }
24
- export declare function createRefBridge(): RefBridge;
@@ -1,44 +0,0 @@
1
- export function createRefBridge() {
2
- const refs = new Map();
3
- return {
4
- register(id, obj) {
5
- if (obj) {
6
- refs.set(id, obj);
7
- }
8
- else {
9
- refs.delete(id);
10
- }
11
- },
12
- unregister(id) {
13
- refs.delete(id);
14
- },
15
- get(id) {
16
- var _a;
17
- return (_a = refs.get(id)) !== null && _a !== void 0 ? _a : null;
18
- },
19
- setTransform(id, position, rotation, scale) {
20
- const obj = refs.get(id);
21
- if (!obj)
22
- return;
23
- if (position) {
24
- obj.position.set(position[0], position[1], position[2]);
25
- }
26
- if (rotation) {
27
- obj.rotation.set(rotation[0], rotation[1], rotation[2]);
28
- }
29
- if (scale) {
30
- obj.scale.set(scale[0], scale[1], scale[2]);
31
- }
32
- },
33
- readTransform(id) {
34
- const obj = refs.get(id);
35
- if (!obj)
36
- return null;
37
- return {
38
- position: [obj.position.x, obj.position.y, obj.position.z],
39
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
40
- scale: [obj.scale.x, obj.scale.y, obj.scale.z],
41
- };
42
- },
43
- };
44
- }