react-three-game 0.0.71 → 0.0.73

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 (27) hide show
  1. package/dist/index.d.ts +3 -4
  2. package/dist/index.js +1 -3
  3. package/dist/tools/assetviewer/page.js +1 -1
  4. package/dist/tools/prefabeditor/PrefabEditor.d.ts +0 -2
  5. package/dist/tools/prefabeditor/PrefabEditor.js +12 -22
  6. package/dist/tools/prefabeditor/PrefabRoot.d.ts +0 -21
  7. package/dist/tools/prefabeditor/PrefabRoot.js +26 -50
  8. package/dist/tools/prefabeditor/components/CameraComponent.js +3 -1
  9. package/dist/tools/prefabeditor/components/ClickComponent.js +3 -1
  10. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -6
  11. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -1
  12. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +2 -2
  13. package/dist/tools/prefabeditor/components/MaterialComponent.js +2 -2
  14. package/dist/tools/prefabeditor/components/ModelComponent.js +2 -2
  15. package/dist/tools/prefabeditor/components/PhysicsComponent.js +4 -3
  16. package/dist/tools/prefabeditor/components/PointLightComponent.js +3 -1
  17. package/dist/tools/prefabeditor/components/SoundComponent.js +4 -3
  18. package/dist/tools/prefabeditor/components/SpotLightComponent.js +4 -3
  19. package/dist/tools/prefabeditor/components/index.d.ts +1 -2
  20. package/dist/tools/prefabeditor/components/index.js +1 -1
  21. package/dist/tools/prefabeditor/runtimeContext.d.ts +40 -0
  22. package/dist/tools/prefabeditor/runtimeContext.js +45 -0
  23. package/dist/tools/prefabeditor/scene.d.ts +6 -2
  24. package/dist/tools/prefabeditor/scene.js +10 -5
  25. package/package.json +1 -1
  26. package/dist/tools/prefabeditor/RefBridge.d.ts +0 -24
  27. package/dist/tools/prefabeditor/RefBridge.js +0 -44
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 } from './tools/prefabeditor/PrefabRoot';
23
+ export type { AssetRuntime, EntityRuntime, LiveObjectRef, LiveRigidBodyRef } from './tools/prefabeditor/runtimeContext';
24
+ export { useAssetRuntime, useEntityRuntime, useEntityObjectRef, useEntityRigidBodyRef } from './tools/prefabeditor/runtimeContext';
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/runtimeContext';
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';
@@ -4,7 +4,7 @@ import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
4
4
  import { Component as ReactComponent, Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
5
5
  import { createPortal } from 'react-dom';
6
6
  import { TextureLoader } from "three";
7
- import { loadModel } from "../dragdrop";
7
+ import { loadModel } from "../dragdrop/modelLoader";
8
8
  class ErrorBoundary extends ReactComponent {
9
9
  constructor(props) {
10
10
  super(props);
@@ -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,29 +3,8 @@ 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;
11
- /** @internal Used by PhysicsComponent. */
12
- registerRigidBodyRef: (id: string, rb: any) => void;
13
- editMode?: boolean;
14
- /** Get a loaded model by asset path. */
15
- getModel: (path: string) => Object3D | null;
16
- /** Get a loaded texture by asset path. */
17
- getTexture: (path: string) => Texture | null;
18
- /** Get a loaded sound buffer by asset path. */
19
- getSound: (path: string) => AudioBuffer | null;
20
- /** Get a revision string that changes when loaded assets change (for cache-busting keys). */
21
- getAssetRevision: () => string;
22
- }
23
- /** Access the scene runtime (refBridge, getRigidBody, editMode) from within a component View. */
24
- export declare function useSceneRuntime(): SceneRuntime;
25
6
  export interface PrefabRootRef {
26
7
  root: Group | null;
27
- refBridge: RefBridge;
28
- rigidBodyRefs: Map<string, any>;
29
8
  getObject: (nodeId: string) => Object3D | null;
30
9
  getRigidBody: (nodeId: string) => any;
31
10
  addModel: (path: string, model: Object3D) => void;
@@ -11,34 +11,26 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { useHelper } from "@react-three/drei";
14
- import { forwardRef, createContext, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
15
15
  import { BoxHelper, Euler, Matrix4, } from "three";
16
16
  import { useStore } from "zustand";
17
17
  import { useClickValid } from "./useClickValid";
18
18
  import { findComponent } from "./types";
19
19
  import { getComponentDef, getComponentAssetRefs, registerComponent } from "./components/ComponentRegistry";
20
- import components from "./components";
20
+ import { builtinComponents } from "./components";
21
21
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
22
22
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
23
23
  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";
27
+ import { AssetRuntimeContext, EntityRuntimeScope } from "./runtimeContext";
28
28
  import { sound as soundManager } from "../../helpers/SoundManager";
29
- components.forEach(registerComponent);
29
+ builtinComponents.forEach(registerComponent);
30
30
  const IDENTITY = new Matrix4();
31
31
  const EMPTY_MODELS = {};
32
32
  const EMPTY_TEXTURES = {};
33
33
  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);
38
- if (!ctx)
39
- throw new Error("useSceneRuntime must be used inside <PrefabRoot>");
40
- return ctx;
41
- }
42
34
  /** Resolve a relative or absolute asset file path against a base path. */
43
35
  function resolveAssetPath(basePath, file) {
44
36
  if (file.startsWith("http://") || file.startsWith("https://"))
@@ -68,7 +60,6 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
68
60
  const objectRefs = useRef({});
69
61
  const rigidBodyRefs = useRef(new Map());
70
62
  const rootRef = useRef(null);
71
- const [refBridge] = useState(() => createRefBridge());
72
63
  const parentStore = useOptionalPrefabStoreApi();
73
64
  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
65
  const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
@@ -78,11 +69,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
78
69
  const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
79
70
  const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
80
71
  const availableSounds = useMemo(() => (Object.assign(Object.assign({}, sounds), injectedSounds)), [sounds, injectedSounds]);
72
+ const getObject = useCallback((id) => {
73
+ var _a;
74
+ return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
75
+ }, []);
81
76
  useImperativeHandle(ref, () => ({
82
77
  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; },
78
+ getObject,
86
79
  getRigidBody: (nodeId) => { var _a; return (_a = rigidBodyRefs.current.get(nodeId)) !== null && _a !== void 0 ? _a : null; },
87
80
  addModel: (path, model) => setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model }))),
88
81
  addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
@@ -90,11 +83,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
90
83
  soundManager.setBuffer(path, sound);
91
84
  setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
92
85
  },
93
- }), [refBridge]);
86
+ }), [getObject]);
94
87
  const registerRef = useCallback((id, obj) => {
95
88
  objectRefs.current[id] = obj;
96
- refBridge.register(id, obj);
97
- }, [refBridge]);
89
+ }, []);
98
90
  const registerRigidBodyRef = useCallback((id, rb) => {
99
91
  rigidBodyRefs.current.set(id, rb);
100
92
  }, []);
@@ -180,11 +172,10 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
180
172
  availableTexturesRef.current = availableTextures;
181
173
  const availableSoundsRef = useRef(availableSounds);
182
174
  availableSoundsRef.current = availableSounds;
183
- const sceneRuntime = useMemo(() => ({
184
- refBridge,
175
+ const assetRuntime = useMemo(() => ({
176
+ getObject,
185
177
  getRigidBody,
186
178
  registerRigidBodyRef,
187
- editMode,
188
179
  getModel: (path) => { var _a; return (_a = availableModelsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
189
180
  getTexture: (path) => { var _a; return (_a = availableTexturesRef.current[path]) !== null && _a !== void 0 ? _a : null; },
190
181
  getSound: (path) => { var _a; return (_a = availableSoundsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
@@ -193,12 +184,12 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
193
184
  const textureKeys = Object.keys(availableTexturesRef.current).sort().join('|');
194
185
  return `${textureKeys}::${modelKeys}`;
195
186
  },
196
- }), [refBridge, getRigidBody, registerRigidBodyRef, editMode]);
187
+ }), [getObject, getRigidBody, registerRigidBodyRef]);
197
188
  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
189
  if (!shouldProvideStoreContext) {
199
- return _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content });
190
+ return _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
200
191
  }
201
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content }) });
192
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) });
202
193
  });
203
194
  function StoreRootNode(props) {
204
195
  const rootId = usePrefabRootId();
@@ -289,14 +280,8 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
289
280
  const childNodes = getChildHostComponents(gameObject).length > 0
290
281
  ? _jsx(CompositionChildren, { childIds: childIds, selectedId: selectedId, ctx: renderCtx, parentMatrix: world })
291
282
  : _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 }));
283
+ const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, childNodes) })));
284
+ 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
285
  }
301
286
  function getChildHostComponents(gameObject) {
302
287
  var _a;
@@ -385,7 +370,7 @@ function renderCompositionSubtree(gameObject, ctx, isSelected, childIds, parentM
385
370
  const transform = getNodeTransformProps(gameObject);
386
371
  const world = parentMatrix.clone().multiply(compose(gameObject));
387
372
  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));
373
+ 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
374
  }
390
375
  function CompositionChildren({ childIds, selectedId, ctx, parentMatrix, }) {
391
376
  return childIds.map(childId => (_jsx(CompositionSubtree, { nodeId: childId, selectedId: selectedId, ctx: ctx, parentMatrix: parentMatrix }, childId)));
@@ -398,31 +383,22 @@ function CompositionSubtree({ nodeId, selectedId, ctx, parentMatrix, }) {
398
383
  return null;
399
384
  return renderCompositionSubtree(gameObject, ctx, isSelected, childIds, parentMatrix);
400
385
  }
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
- };
386
+ function renderCompositionNode(gameObject, ctx, childNodes) {
387
+ const ownContent = renderNodeOwnContent(gameObject);
388
+ return wrapWithChildHosts(gameObject, _jsxs(_Fragment, { children: [ownContent, childNodes] }));
411
389
  }
412
- function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
390
+ function renderNodeOwnContent(gameObject) {
413
391
  const geometry = findComponent(gameObject, "Geometry");
414
392
  const material = findComponent(gameObject, "Material");
415
393
  const geometryDef = geometry && getComponentDef(geometry.type);
416
394
  const materialDef = material && getComponentDef(material.type);
417
395
  if (!geometry || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
418
396
  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"))] }));
397
+ 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
398
  }
422
- function wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, subtree) {
423
- const contextProps = buildContextProps(gameObject, ctx, isSelected);
399
+ function wrapWithChildHosts(gameObject, subtree) {
424
400
  const childHosts = getChildHostComponents(gameObject);
425
- return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), subtree);
401
+ return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, { properties: properties, children: acc }, key)), subtree);
426
402
  }
427
403
  export default PrefabRoot;
428
404
  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 '../runtimeContext';
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 '../runtimeContext';
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 "../runtimeContext";
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 '../runtimeContext';
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 '../runtimeContext';
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 '../runtimeContext';
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 "../runtimeContext";
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 '../runtimeContext';
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 '../runtimeContext';
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 "../runtimeContext";
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,2 +1 @@
1
- declare const _default: import("./ComponentRegistry").Component[];
2
- export default _default;
1
+ export declare const builtinComponents: import("./ComponentRegistry").Component[];
@@ -12,7 +12,7 @@ import EnvironmentComponent from './EnvironmentComponent';
12
12
  import CameraComponent from './CameraComponent';
13
13
  import ClickComponent from './ClickComponent';
14
14
  import SoundComponent from './SoundComponent';
15
- export default [
15
+ export const builtinComponents = [
16
16
  GeometryComponent,
17
17
  TransformComponent,
18
18
  MaterialComponent,
@@ -0,0 +1,40 @@
1
+ import { type ReactNode } from "react";
2
+ import { Object3D, Texture } from "three";
3
+ type RuntimeScopeProps = {
4
+ nodeId: string;
5
+ editMode?: boolean;
6
+ isSelected?: boolean;
7
+ children: ReactNode;
8
+ };
9
+ export interface AssetRuntime {
10
+ registerRigidBodyRef: (id: string, rb: any) => void;
11
+ getModel: (path: string) => Object3D | null;
12
+ getTexture: (path: string) => Texture | null;
13
+ getSound: (path: string) => AudioBuffer | null;
14
+ getAssetRevision: () => string;
15
+ }
16
+ export interface AssetRuntimeContextValue extends AssetRuntime {
17
+ getObject: (id: string) => Object3D | null;
18
+ getRigidBody: (id: string) => any;
19
+ }
20
+ export interface EntityRuntime {
21
+ nodeId: string;
22
+ editMode?: boolean;
23
+ isSelected?: boolean;
24
+ getObject: <T extends Object3D = Object3D>() => T | null;
25
+ getRigidBody: <T = any>() => T | null;
26
+ }
27
+ export interface LiveObjectRef<T extends Object3D = Object3D> {
28
+ readonly current: T | null;
29
+ }
30
+ export interface LiveRigidBodyRef<T = any> {
31
+ readonly current: T | null;
32
+ }
33
+ export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
34
+ export declare const EntityRuntimeContext: import("react").Context<EntityRuntime | null>;
35
+ export declare function useAssetRuntime(): AssetRuntime;
36
+ export declare function useEntityRuntime(): EntityRuntime;
37
+ export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveObjectRef<T>;
38
+ export declare function useEntityRigidBodyRef<T = any>(): LiveRigidBodyRef<T>;
39
+ export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }: RuntimeScopeProps): import("react/jsx-runtime").JSX.Element;
40
+ export {};
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ function createLiveRef(getCurrent) {
4
+ return {
5
+ get current() {
6
+ return getCurrent();
7
+ },
8
+ };
9
+ }
10
+ export const AssetRuntimeContext = createContext(null);
11
+ export const EntityRuntimeContext = createContext(null);
12
+ export function useAssetRuntime() {
13
+ const ctx = useContext(AssetRuntimeContext);
14
+ if (!ctx)
15
+ throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
16
+ return ctx;
17
+ }
18
+ export function useEntityRuntime() {
19
+ const ctx = useContext(EntityRuntimeContext);
20
+ if (!ctx)
21
+ throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
22
+ return ctx;
23
+ }
24
+ export function useEntityObjectRef() {
25
+ const { getObject } = useEntityRuntime();
26
+ return useMemo(() => createLiveRef(() => getObject()), [getObject]);
27
+ }
28
+ export function useEntityRigidBodyRef() {
29
+ const { getRigidBody } = useEntityRuntime();
30
+ return useMemo(() => createLiveRef(() => getRigidBody()), [getRigidBody]);
31
+ }
32
+ export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
33
+ const assetRuntime = useContext(AssetRuntimeContext);
34
+ if (!assetRuntime)
35
+ throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
36
+ const { getObject, getRigidBody } = assetRuntime;
37
+ const value = useMemo(() => ({
38
+ nodeId,
39
+ editMode,
40
+ isSelected,
41
+ getObject: () => getObject(nodeId),
42
+ getRigidBody: () => getRigidBody(nodeId),
43
+ }), [editMode, getObject, getRigidBody, isSelected, nodeId]);
44
+ return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
45
+ }
@@ -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.73",
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
- }