react-three-game 0.0.70 → 0.0.71

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 (36) hide show
  1. package/dist/index.d.ts +10 -5
  2. package/dist/index.js +7 -2
  3. package/dist/tools/prefabeditor/EditorTree.js +2 -12
  4. package/dist/tools/prefabeditor/EditorTreeMenus.js +1 -19
  5. package/dist/tools/prefabeditor/EditorUI.js +2 -1
  6. package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -1
  7. package/dist/tools/prefabeditor/PrefabEditor.js +12 -21
  8. package/dist/tools/prefabeditor/PrefabRoot.d.ts +25 -12
  9. package/dist/tools/prefabeditor/PrefabRoot.js +61 -28
  10. package/dist/tools/prefabeditor/RefBridge.d.ts +24 -0
  11. package/dist/tools/prefabeditor/RefBridge.js +44 -0
  12. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +10 -7
  13. package/dist/tools/prefabeditor/components/CameraComponent.js +8 -14
  14. package/dist/tools/prefabeditor/components/ClickComponent.js +2 -0
  15. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +21 -1
  16. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +124 -52
  17. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +5 -3
  18. package/dist/tools/prefabeditor/components/MaterialComponent.js +9 -6
  19. package/dist/tools/prefabeditor/components/ModelComponent.js +4 -2
  20. package/dist/tools/prefabeditor/components/PhysicsComponent.js +5 -3
  21. package/dist/tools/prefabeditor/components/PointLightComponent.d.ts +3 -0
  22. package/dist/tools/prefabeditor/components/PointLightComponent.js +55 -0
  23. package/dist/tools/prefabeditor/components/SoundComponent.js +20 -16
  24. package/dist/tools/prefabeditor/components/SpotLightComponent.js +48 -24
  25. package/dist/tools/prefabeditor/components/index.js +2 -0
  26. package/dist/tools/prefabeditor/components/lightUtils.d.ts +13 -0
  27. package/dist/tools/prefabeditor/components/lightUtils.js +64 -0
  28. package/dist/tools/prefabeditor/prefab.d.ts +37 -0
  29. package/dist/tools/prefabeditor/prefab.js +229 -0
  30. package/dist/tools/prefabeditor/prefabStore.d.ts +3 -16
  31. package/dist/tools/prefabeditor/prefabStore.js +29 -168
  32. package/dist/tools/prefabeditor/{sceneApi.js → scene.js} +3 -14
  33. package/dist/tools/prefabeditor/utils.d.ts +0 -4
  34. package/dist/tools/prefabeditor/utils.js +0 -37
  35. package/package.json +1 -1
  36. /package/dist/tools/prefabeditor/{sceneApi.d.ts → scene.d.ts} +0 -0
package/dist/index.d.ts CHANGED
@@ -10,20 +10,25 @@ export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
10
10
  export type { EditorContextType } from './tools/prefabeditor/PrefabEditor';
11
11
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
12
12
  export type { PrefabStoreApi, PrefabStoreState } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/sceneApi';
13
+ export { createScene } from './tools/prefabeditor/scene';
14
+ export { denormalizePrefab } from './tools/prefabeditor/prefab';
14
15
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
15
16
  export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
16
- export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, createModelNode, createImageNode, } from './tools/prefabeditor/utils';
17
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
17
18
  export type { ExportGLBOptions } from './tools/prefabeditor/utils';
19
+ export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
18
20
  export type { PrefabEditorProps, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
19
- export type { SpawnOptions, Scene, Entity, EntityComponent, EntityData, EntityUpdate, PropertyPath, SceneUpdates, } from './tools/prefabeditor/sceneApi';
20
- export type { PrefabRootProps, PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
21
- export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
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';
24
+ export type { Component, ComponentViewProps } from './tools/prefabeditor/components/ComponentRegistry';
22
25
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
23
26
  export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
24
27
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
25
28
  export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
26
29
  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';
27
32
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
28
33
  export type { AssetLoadOptions } from './tools/dragdrop/DragDropLoader';
29
34
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
package/dist/index.js CHANGED
@@ -10,16 +10,21 @@ export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
10
10
  export { useEditorContext } from './tools/prefabeditor/PrefabEditor';
11
11
  // Prefab Editor - Store & Scene API
12
12
  export { createPrefabStore, prefabStoreToPrefab, usePrefabStoreApi } from './tools/prefabeditor/prefabStore';
13
- export { createScene } from './tools/prefabeditor/sceneApi';
13
+ export { createScene } from './tools/prefabeditor/scene';
14
+ export { denormalizePrefab } from './tools/prefabeditor/prefab';
14
15
  // Prefab Editor - Component Registry
15
16
  export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
16
17
  // Prefab Editor - Input Components
17
18
  export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from './tools/prefabeditor/components/Input';
18
19
  // Prefab Editor - Utils
19
- export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, createModelNode, createImageNode, } from './tools/prefabeditor/utils';
20
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
21
+ export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
22
+ export { useSceneRuntime } from './tools/prefabeditor/PrefabRoot';
20
23
  export { findComponent, findComponentEntry, hasComponent } from './tools/prefabeditor/types';
21
24
  // Game Events (physics + custom events)
22
25
  export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
26
+ // RefBridge & Systems
27
+ export { createRefBridge } from './tools/prefabeditor/RefBridge';
23
28
  // Asset Loading
24
29
  export { loadFiles } from './tools/dragdrop/DragDropLoader';
25
30
  export { loadModel, loadSound, loadTexture } from './tools/dragdrop/modelLoader';
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { memo, useCallback, useState } from 'react';
3
- import { getComponentDef } from './components/ComponentRegistry';
4
3
  import { base, colors, tree } from './styles';
5
4
  import { useEditorContext } from './PrefabEditor';
6
5
  import { Dropdown } from './Dropdown';
7
6
  import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
7
+ import { createEmptyNode } from './prefab';
8
8
  import { usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStore, usePrefabStoreApi } from './prefabStore';
9
9
  export default function EditorTree({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, onUndo, onRedo, canUndo, canRedo }) {
10
10
  const { onFocusNode } = useEditorContext();
@@ -30,17 +30,7 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
30
30
  });
31
31
  };
32
32
  const handleAddChild = (parentId) => {
33
- var _a;
34
- const newNode = {
35
- id: crypto.randomUUID(),
36
- name: "New Node",
37
- components: {
38
- transform: {
39
- type: "Transform",
40
- properties: Object.assign({}, (_a = getComponentDef('Transform')) === null || _a === void 0 ? void 0 : _a.defaultProperties)
41
- }
42
- }
43
- };
33
+ const newNode = createEmptyNode();
44
34
  addChild(parentId, newNode);
45
35
  setSelectedId(newNode.id);
46
36
  };
@@ -10,28 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { useEffect, useRef, useState } from 'react';
12
12
  import { createPortal } from 'react-dom';
13
+ import { createEmptyPrefab } from './prefab';
13
14
  import { menu } from './styles';
14
15
  import { useEditorContext } from './PrefabEditor';
15
- import { getComponentDef } from './components/ComponentRegistry';
16
16
  import { loadJson, saveJson } from './utils';
17
- function createEmptyPrefab() {
18
- var _a;
19
- return {
20
- id: crypto.randomUUID(),
21
- name: 'New Prefab',
22
- root: {
23
- id: crypto.randomUUID(),
24
- name: 'Root',
25
- components: {
26
- transform: {
27
- type: 'Transform',
28
- properties: Object.assign({}, (_a = getComponentDef('Transform')) === null || _a === void 0 ? void 0 : _a.defaultProperties)
29
- }
30
- },
31
- children: []
32
- }
33
- };
34
- }
35
17
  function MenuPanel({ children, style, }) {
36
18
  return (_jsx("div", { style: Object.assign(Object.assign(Object.assign({}, menu.container), { position: 'static' }), style), onClick: (e) => e.stopPropagation(), children: children }));
37
19
  }
@@ -14,6 +14,7 @@ import { useState } from 'react';
14
14
  import { hasComponent } from "./types";
15
15
  import EditorTree from './EditorTree';
16
16
  import { getAllComponentDefs } from './components/ComponentRegistry';
17
+ import { createComponentData } from './prefab';
17
18
  import { base, colors, inspector, componentCard } from './styles';
18
19
  import { usePrefabStore } from './prefabStore';
19
20
  function EditorUI({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, basePath, onUndo, onRedo, canUndo, canRedo }) {
@@ -58,7 +59,7 @@ function NodeInspector({ node, updateNode, deleteNode, basePath }) {
58
59
  return;
59
60
  const def = ALL_COMPONENTS[addType];
60
61
  if (def) {
61
- updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [addType.toLowerCase()]: { type: def.name, properties: def.defaultProperties } }) })));
62
+ updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [addType.toLowerCase()]: createComponentData(def.name) }) })));
62
63
  }
63
64
  }, title: "Add Component", children: "+" })] }) }))] });
64
65
  }
@@ -3,7 +3,7 @@ import { Object3D, Texture } from "three";
3
3
  import { GameObject, Prefab } from "./types";
4
4
  import { PrefabRootRef } from "./PrefabRoot";
5
5
  import type { ExportGLBOptions } from "./utils";
6
- import { type Scene, type SpawnOptions } from "./sceneApi";
6
+ import { type Scene, type SpawnOptions } from "./scene";
7
7
  export interface PrefabEditorRef {
8
8
  screenshot: () => void;
9
9
  exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
@@ -16,10 +16,11 @@ import PrefabRoot from "./PrefabRoot";
16
16
  import { Physics } from "@react-three/rapier";
17
17
  import EditorUI from "./EditorUI";
18
18
  import { base, toolbar } from "./styles";
19
- import { createImageNode, createModelNode, computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, regenerateIds } from "./utils";
19
+ import { computeParentWorldMatrix, decompose, exportGLB as exportGLBFile, exportGLBData, focusCameraOnObject, regenerateIds } from "./utils";
20
20
  import { loadFiles } from "../dragdrop";
21
- import { createPrefabStore, PrefabStoreProvider, prefabStoreToPrefab } from "./prefabStore";
22
- import { createScene } from "./sceneApi";
21
+ import { denormalizePrefab, createImageNode, createModelNode, createNode } from './prefab';
22
+ import { createPrefabStore, PrefabStoreProvider } from "./prefabStore";
23
+ import { createScene } from "./scene";
23
24
  export var PrefabEditorMode;
24
25
  (function (PrefabEditorMode) {
25
26
  PrefabEditorMode["Edit"] = "edit";
@@ -38,15 +39,7 @@ const HISTORY_DEBOUNCE_MS = 500;
38
39
  const DEFAULT_PREFAB = {
39
40
  id: "prefab-default",
40
41
  name: "New Prefab",
41
- root: {
42
- id: "root",
43
- components: {
44
- transform: {
45
- type: "Transform",
46
- properties: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
47
- }
48
- }
49
- }
42
+ root: createNode('Root', {}, { id: 'root' })
50
43
  };
51
44
  const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode: initialMode = PrefabEditorMode.Edit, onChange, showUI = true, enableWindowDrop = true, canvasProps, uiPlugins, children }, ref) => {
52
45
  const [mode, setMode] = useState(initialMode);
@@ -66,10 +59,8 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
66
59
  const canvasRef = useRef(null);
67
60
  const controlsRef = useRef(null);
68
61
  const onChangeRef = useRef(onChange);
69
- const [injectedModels, setInjectedModels] = useState({});
70
- const [injectedTextures, setInjectedTextures] = useState({});
71
62
  const isEditMode = mode === PrefabEditorMode.Edit;
72
- const getPrefab = useCallback(() => prefabStoreToPrefab(prefabStore.getState()), [prefabStore]);
63
+ const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
73
64
  onChangeRef.current = onChange;
74
65
  const setSelection = useCallback((nodeId) => {
75
66
  const nextNode = nodeId ? prefabStore.getState().nodesById[nodeId] : null;
@@ -101,8 +92,6 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
101
92
  setSelectedObject(null);
102
93
  if (options === null || options === void 0 ? void 0 : options.resetHistory) {
103
94
  setSelectedId(null);
104
- setInjectedModels({});
105
- setInjectedTextures({});
106
95
  setHistory([prefab]);
107
96
  historyIndexRef.current = 0;
108
97
  setHistoryIndex(0);
@@ -124,7 +113,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
124
113
  return;
125
114
  }
126
115
  lastRevision = state.revision;
127
- const nextPrefab = prefabStoreToPrefab(state);
116
+ const nextPrefab = denormalizePrefab(state);
128
117
  const changeOrigin = changeOriginRef.current;
129
118
  if (changeOrigin !== "replace-silent") {
130
119
  (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextPrefab);
@@ -188,15 +177,17 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
188
177
  addNode(regenerateIds(prefab.root), { select: false });
189
178
  }, [addNode]);
190
179
  const addModel = useCallback((path, model, options) => {
180
+ var _a;
191
181
  const node = createModelNode(path, options === null || options === void 0 ? void 0 : options.name);
192
182
  addNode(node, options);
193
- setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model })));
183
+ (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.addModel(path, model);
194
184
  return node;
195
185
  }, [addNode]);
196
186
  const addTexture = useCallback((path, texture, options) => {
187
+ var _a;
197
188
  const node = createImageNode(path, options === null || options === void 0 ? void 0 : options.name);
198
189
  addNode(node, options);
199
- setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture })));
190
+ (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.addTexture(path, texture);
200
191
  return node;
201
192
  }, [addNode]);
202
193
  const applyHistory = (index) => {
@@ -361,7 +352,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, physics = true, mode
361
352
  addTexture,
362
353
  viewRef: prefabRootRef
363
354
  }), [addModel, addTexture, clearSelection, getPrefab, handleExportGLB, handleExportGLBData, handleScreenshot, loadPrefab, scene]);
364
- const content = (_jsxs(_Fragment, { children: [_jsx("ambientLight", { intensity: 1.5 }), _jsx("gridHelper", { args: [10, 10], position: [0, -1, 0] }), _jsx(PrefabRoot, { ref: prefabRootRef, store: prefabStore, editMode: isEditMode, selectedId: selectedId, onSelect: setSelection, onSelectedObjectChange: isEditMode ? setSelectedObject : undefined, onFocusNode: isEditMode ? handleFocusNode : undefined, basePath: basePath, injectedModels: injectedModels, injectedTextures: injectedTextures }), children] }));
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] }));
365
356
  return _jsx(PrefabStoreProvider, { store: prefabStore, children: _jsxs(EditorContext.Provider, { value: {
366
357
  mode,
367
358
  setMode: updateMode,
@@ -1,14 +1,36 @@
1
- import { Group, Matrix4, Object3D } from "three";
1
+ import { Group, Matrix4, Object3D, Texture } from "three";
2
2
  import { ThreeEvent } from "@react-three/fiber";
3
3
  import { GameObject as GameObjectType, Prefab } from "./types";
4
- import { LoadedModels, LoadedSounds, LoadedTextures } from "../dragdrop";
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;
6
25
  export interface PrefabRootRef {
7
26
  root: Group | null;
27
+ refBridge: RefBridge;
8
28
  rigidBodyRefs: Map<string, any>;
9
29
  getObject: (nodeId: string) => Object3D | null;
10
30
  getRigidBody: (nodeId: string) => any;
11
- focusNode: (nodeId: string) => void;
31
+ addModel: (path: string, model: Object3D) => void;
32
+ addTexture: (path: string, texture: Texture) => void;
33
+ addSound: (path: string, sound: AudioBuffer) => void;
12
34
  }
13
35
  export interface PrefabRootProps {
14
36
  editMode?: boolean;
@@ -17,12 +39,7 @@ export interface PrefabRootProps {
17
39
  selectedId?: string | null;
18
40
  onSelect?: (id: string | null) => void;
19
41
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
20
- onSelectedObjectChange?: (object: Object3D | null) => void;
21
- onFocusNode?: (nodeId: string) => void;
22
42
  basePath?: string;
23
- injectedModels?: LoadedModels;
24
- injectedTextures?: LoadedTextures;
25
- injectedSounds?: LoadedSounds;
26
43
  }
27
44
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<PrefabRootProps & import("react").RefAttributes<PrefabRootRef>>;
28
45
  export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
@@ -32,11 +49,7 @@ interface RendererProps {
32
49
  onSelect?: (id: string) => void;
33
50
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
34
51
  registerRef: (id: string, obj: Object3D | null) => void;
35
- registerRigidBodyRef: (id: string, rb: any) => void;
36
- getRigidBody: (id: string) => any;
37
52
  loadedModels: LoadedModels;
38
- loadedSounds: LoadedSounds;
39
- loadedTextures: LoadedTextures;
40
53
  editMode?: boolean;
41
54
  parentMatrix?: Matrix4;
42
55
  }
@@ -11,7 +11,7 @@ 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, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
+ import { forwardRef, createContext, useCallback, useContext, 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";
@@ -22,13 +22,23 @@ 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
- import { createPrefabStore, PrefabStoreProvider, prefabStoreToPrefab, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
25
+ import { denormalizePrefab } from "./prefab";
26
+ import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
27
+ import { createRefBridge } from "./RefBridge";
26
28
  import { sound as soundManager } from "../../helpers/SoundManager";
27
29
  components.forEach(registerComponent);
28
30
  const IDENTITY = new Matrix4();
29
31
  const EMPTY_MODELS = {};
30
32
  const EMPTY_TEXTURES = {};
31
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
+ }
32
42
  /** Resolve a relative or absolute asset file path against a base path. */
33
43
  function resolveAssetPath(basePath, file) {
34
44
  if (file.startsWith("http://") || file.startsWith("https://"))
@@ -43,11 +53,14 @@ function isNodeReady(node, loadedModels) {
43
53
  return true;
44
54
  return Boolean(loadedModels[model.properties.filename]);
45
55
  }
46
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onSelectedObjectChange, onFocusNode, basePath = "", injectedModels = EMPTY_MODELS, injectedTextures = EMPTY_TEXTURES, injectedSounds = EMPTY_SOUNDS }, ref) => {
56
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, basePath = "" }, ref) => {
47
57
  var _a;
48
58
  const [models, setModels] = useState({});
49
59
  const [textures, setTextures] = useState({});
50
60
  const [sounds, setSounds] = useState({});
61
+ const [injectedModels, setInjectedModels] = useState(EMPTY_MODELS);
62
+ const [injectedTextures, setInjectedTextures] = useState(EMPTY_TEXTURES);
63
+ const [injectedSounds, setInjectedSounds] = useState(EMPTY_SOUNDS);
51
64
  const loading = useRef(new Set());
52
65
  const failedModels = useRef(new Set());
53
66
  const failedTextures = useRef(new Set());
@@ -55,8 +68,9 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
55
68
  const objectRefs = useRef({});
56
69
  const rigidBodyRefs = useRef(new Map());
57
70
  const rootRef = useRef(null);
71
+ const [refBridge] = useState(() => createRefBridge());
58
72
  const parentStore = useOptionalPrefabStoreApi();
59
- const [ownedStore] = useState(() => { var _a, _b; return createPrefabStore(data !== null && data !== void 0 ? data : prefabStoreToPrefab((_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())); });
73
+ 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())); });
60
74
  const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
61
75
  const usesOwnedStore = resolvedStore === ownedStore;
62
76
  const shouldProvideStoreContext = !parentStore || parentStore !== resolvedStore;
@@ -66,17 +80,21 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
66
80
  const availableSounds = useMemo(() => (Object.assign(Object.assign({}, sounds), injectedSounds)), [sounds, injectedSounds]);
67
81
  useImperativeHandle(ref, () => ({
68
82
  root: rootRef.current,
83
+ refBridge,
69
84
  rigidBodyRefs: rigidBodyRefs.current,
70
85
  getObject: (nodeId) => { var _a; return (_a = objectRefs.current[nodeId]) !== null && _a !== void 0 ? _a : null; },
71
86
  getRigidBody: (nodeId) => { var _a; return (_a = rigidBodyRefs.current.get(nodeId)) !== null && _a !== void 0 ? _a : null; },
72
- focusNode: (nodeId) => onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(nodeId),
73
- }), [onFocusNode]);
87
+ addModel: (path, model) => setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model }))),
88
+ addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
89
+ addSound: (path, sound) => {
90
+ soundManager.setBuffer(path, sound);
91
+ setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
92
+ },
93
+ }), [refBridge]);
74
94
  const registerRef = useCallback((id, obj) => {
75
95
  objectRefs.current[id] = obj;
76
- if (id === selectedId) {
77
- onSelectedObjectChange === null || onSelectedObjectChange === void 0 ? void 0 : onSelectedObjectChange(obj);
78
- }
79
- }, [onSelectedObjectChange, selectedId]);
96
+ refBridge.register(id, obj);
97
+ }, [refBridge]);
80
98
  const registerRigidBodyRef = useCallback((id, rb) => {
81
99
  rigidBodyRefs.current.set(id, rb);
82
100
  }, []);
@@ -155,11 +173,32 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
155
173
  };
156
174
  syncAssets();
157
175
  }, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
158
- 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, registerRigidBodyRef: registerRigidBodyRef, getRigidBody: getRigidBody, loadedModels: availableModels, loadedSounds: availableSounds, loadedTextures: availableTextures, editMode: editMode, parentMatrix: IDENTITY }) }) }));
176
+ // Keep refs current so context getters are always fresh without changing context identity
177
+ const availableModelsRef = useRef(availableModels);
178
+ availableModelsRef.current = availableModels;
179
+ const availableTexturesRef = useRef(availableTextures);
180
+ availableTexturesRef.current = availableTextures;
181
+ const availableSoundsRef = useRef(availableSounds);
182
+ availableSoundsRef.current = availableSounds;
183
+ const sceneRuntime = useMemo(() => ({
184
+ refBridge,
185
+ getRigidBody,
186
+ registerRigidBodyRef,
187
+ editMode,
188
+ getModel: (path) => { var _a; return (_a = availableModelsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
189
+ getTexture: (path) => { var _a; return (_a = availableTexturesRef.current[path]) !== null && _a !== void 0 ? _a : null; },
190
+ getSound: (path) => { var _a; return (_a = availableSoundsRef.current[path]) !== null && _a !== void 0 ? _a : null; },
191
+ getAssetRevision: () => {
192
+ const modelKeys = Object.keys(availableModelsRef.current).sort().join('|');
193
+ const textureKeys = Object.keys(availableTexturesRef.current).sort().join('|');
194
+ return `${textureKeys}::${modelKeys}`;
195
+ },
196
+ }), [refBridge, getRigidBody, registerRigidBodyRef, editMode]);
197
+ 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 }) }) }));
159
198
  if (!shouldProvideStoreContext) {
160
- return content;
199
+ return _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content });
161
200
  }
162
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: content });
201
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content }) });
163
202
  });
164
203
  function StoreRootNode(props) {
165
204
  const rootId = usePrefabRootId();
@@ -218,7 +257,7 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
218
257
  }
219
258
  return (_jsx(_Fragment, { children: instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, clickEventName: clickEventName, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id))) }));
220
259
  }
221
- function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, registerRigidBodyRef, getRigidBody, loadedModels, loadedSounds, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
260
+ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, }) {
222
261
  var _a, _b;
223
262
  const gameObject = usePrefabNode(nodeId);
224
263
  const childIds = usePrefabChildIds(nodeId);
@@ -246,16 +285,16 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, regi
246
285
  const physicsDef = hasPhysics ? getComponentDef(physics.type) : null;
247
286
  const isInstanced = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.instanced;
248
287
  const physicsKey = `physics_${nodeId}_${isInstanced ? 'instanced' : 'standard'}`;
249
- const renderCtx = { loadedModels, loadedSounds, loadedTextures, editMode, registerRef, getRigidBody };
288
+ const renderCtx = { loadedModels, editMode, registerRef };
250
289
  const childNodes = getChildHostComponents(gameObject).length > 0
251
290
  ? _jsx(CompositionChildren, { childIds: childIds, selectedId: selectedId, ctx: renderCtx, parentMatrix: world })
252
- : _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, getRigidBody: getRigidBody, loadedModels: loadedModels, loadedSounds: loadedSounds, loadedTextures: loadedTextures, editMode: editMode });
291
+ : _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
253
292
  const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, isSelected, parentMatrix, childNodes) })));
254
293
  if (editMode) {
255
- 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, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey)) : null] }));
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] }));
256
295
  }
257
296
  if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
258
- return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: nodeId, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey));
297
+ return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: nodeId, children: inner }, physicsKey));
259
298
  }
260
299
  return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }));
261
300
  }
@@ -363,17 +402,11 @@ function renderCompositionNode(gameObject, ctx, isSelected, parentMatrix, childN
363
402
  const ownContent = renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix);
364
403
  return wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, _jsxs(_Fragment, { children: [ownContent, childNodes] }));
365
404
  }
366
- function buildContextProps(gameObject, ctx, isSelected, parentMatrix) {
405
+ function buildContextProps(gameObject, _ctx, isSelected) {
367
406
  return {
368
- loadedModels: ctx.loadedModels,
369
- loadedSounds: ctx.loadedSounds,
370
- loadedTextures: ctx.loadedTextures,
371
- editMode: ctx.editMode,
407
+ editMode: _ctx.editMode,
372
408
  isSelected,
373
409
  nodeId: gameObject.id,
374
- parentMatrix,
375
- registerRef: ctx.registerRef,
376
- getRigidBody: ctx.getRigidBody,
377
410
  };
378
411
  }
379
412
  function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
@@ -383,11 +416,11 @@ function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
383
416
  const materialDef = material && getComponentDef(material.type);
384
417
  if (!geometry || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
385
418
  return null;
386
- const contextProps = buildContextProps(gameObject, ctx, isSelected, parentMatrix);
419
+ const contextProps = buildContextProps(gameObject, ctx, isSelected);
387
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"))] }));
388
421
  }
389
422
  function wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, subtree) {
390
- const contextProps = buildContextProps(gameObject, ctx, isSelected, parentMatrix);
423
+ const contextProps = buildContextProps(gameObject, ctx, isSelected);
391
424
  const childHosts = getChildHostComponents(gameObject);
392
425
  return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), subtree);
393
426
  }
@@ -0,0 +1,24 @@
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;
@@ -0,0 +1,44 @@
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
+ }
@@ -1,19 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { ColorField, FieldGroup, NumberField } from "./Input";
2
+ import { ColorField, NumberField } from "./Input";
3
+ import { LightSection, mergeWithDefaults } from "./lightUtils";
4
+ const ambientLightDefaults = {
5
+ color: '#ffffff',
6
+ intensity: 1,
7
+ };
3
8
  function AmbientLightComponentEditor({ component, onUpdate, }) {
4
- return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: component.properties, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 })] }));
9
+ const values = mergeWithDefaults(ambientLightDefaults, component.properties);
10
+ return (_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 })] }));
5
11
  }
6
12
  function AmbientLightComponentView({ properties, children }) {
7
- const { color = '#ffffff', intensity = 1 } = properties;
13
+ const { color, intensity } = mergeWithDefaults(ambientLightDefaults, properties);
8
14
  return (_jsxs(_Fragment, { children: [_jsx("ambientLight", { color: color, intensity: intensity }), children] }));
9
15
  }
10
16
  const AmbientLightComponent = {
11
17
  name: 'AmbientLight',
12
18
  Editor: AmbientLightComponentEditor,
13
19
  View: AmbientLightComponentView,
14
- defaultProperties: {
15
- color: '#ffffff',
16
- intensity: 1,
17
- },
20
+ defaultProperties: {},
18
21
  };
19
22
  export default AmbientLightComponent;