react-three-game 0.0.69 → 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 (64) hide show
  1. package/dist/helpers/SoundManager.d.ts +2 -0
  2. package/dist/helpers/SoundManager.js +6 -0
  3. package/dist/index.d.ts +20 -13
  4. package/dist/index.js +14 -7
  5. package/dist/shared/GameCanvas.js +0 -2
  6. package/dist/tools/assetviewer/page.d.ts +5 -0
  7. package/dist/tools/assetviewer/page.js +3 -0
  8. package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -2
  9. package/dist/tools/dragdrop/DragDropLoader.js +18 -3
  10. package/dist/tools/dragdrop/index.d.ts +2 -2
  11. package/dist/tools/dragdrop/index.js +1 -1
  12. package/dist/tools/dragdrop/modelLoader.d.ts +10 -0
  13. package/dist/tools/dragdrop/modelLoader.js +60 -0
  14. package/dist/tools/prefabeditor/EditorTree.js +6 -40
  15. package/dist/tools/prefabeditor/EditorTreeMenus.js +2 -20
  16. package/dist/tools/prefabeditor/EditorUI.js +8 -5
  17. package/dist/tools/prefabeditor/InstanceProvider.d.ts +2 -0
  18. package/dist/tools/prefabeditor/InstanceProvider.js +54 -52
  19. package/dist/tools/prefabeditor/PrefabEditor.d.ts +23 -1
  20. package/dist/tools/prefabeditor/PrefabEditor.js +79 -47
  21. package/dist/tools/prefabeditor/PrefabRoot.d.ts +26 -9
  22. package/dist/tools/prefabeditor/PrefabRoot.js +195 -159
  23. package/dist/tools/prefabeditor/RefBridge.d.ts +24 -0
  24. package/dist/tools/prefabeditor/RefBridge.js +44 -0
  25. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +10 -7
  26. package/dist/tools/prefabeditor/components/CameraComponent.js +8 -14
  27. package/dist/tools/prefabeditor/components/ClickComponent.js +12 -7
  28. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +31 -5
  29. package/dist/tools/prefabeditor/components/ComponentRegistry.js +6 -6
  30. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +124 -52
  31. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +5 -3
  32. package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -1
  33. package/dist/tools/prefabeditor/components/Input.d.ts +16 -0
  34. package/dist/tools/prefabeditor/components/Input.js +33 -0
  35. package/dist/tools/prefabeditor/components/MaterialComponent.js +19 -8
  36. package/dist/tools/prefabeditor/components/ModelComponent.js +39 -45
  37. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -1
  38. package/dist/tools/prefabeditor/components/PhysicsComponent.js +127 -31
  39. package/dist/tools/prefabeditor/components/PointLightComponent.d.ts +3 -0
  40. package/dist/tools/prefabeditor/components/PointLightComponent.js +55 -0
  41. package/dist/tools/prefabeditor/components/SoundComponent.d.ts +3 -0
  42. package/dist/tools/prefabeditor/components/SoundComponent.js +244 -0
  43. package/dist/tools/prefabeditor/components/SpotLightComponent.js +53 -24
  44. package/dist/tools/prefabeditor/components/TransformComponent.js +2 -2
  45. package/dist/tools/prefabeditor/components/index.js +4 -0
  46. package/dist/tools/prefabeditor/components/lightUtils.d.ts +13 -0
  47. package/dist/tools/prefabeditor/components/lightUtils.js +64 -0
  48. package/dist/tools/prefabeditor/prefab.d.ts +37 -0
  49. package/dist/tools/prefabeditor/prefab.js +229 -0
  50. package/dist/tools/prefabeditor/prefabStore.d.ts +4 -16
  51. package/dist/tools/prefabeditor/prefabStore.js +32 -173
  52. package/dist/tools/prefabeditor/{sceneApi.d.ts → scene.d.ts} +15 -1
  53. package/dist/tools/prefabeditor/{sceneApi.js → scene.js} +66 -32
  54. package/dist/tools/prefabeditor/styles.d.ts +1 -0
  55. package/dist/tools/prefabeditor/styles.js +9 -0
  56. package/dist/tools/prefabeditor/types.d.ts +13 -0
  57. package/dist/tools/prefabeditor/types.js +28 -1
  58. package/dist/tools/prefabeditor/useClickValid.d.ts +13 -0
  59. package/dist/tools/prefabeditor/useClickValid.js +21 -0
  60. package/dist/tools/prefabeditor/utils.d.ts +2 -4
  61. package/dist/tools/prefabeditor/utils.js +8 -46
  62. package/package.json +1 -1
  63. package/dist/tools/prefabeditor/EditorContext.d.ts +0 -16
  64. package/dist/tools/prefabeditor/EditorContext.js +0 -9
@@ -1,12 +1,3 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
1
  var __rest = (this && this.__rest) || function (s, e) {
11
2
  var t = {};
12
3
  for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
@@ -20,47 +11,97 @@ var __rest = (this && this.__rest) || function (s, e) {
20
11
  };
21
12
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
22
13
  import { useHelper } from "@react-three/drei";
23
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
24
- import { BoxHelper, Euler, Matrix4, Quaternion, Vector3, } from "three";
14
+ import { forwardRef, createContext, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
15
+ import { BoxHelper, Euler, Matrix4, } from "three";
25
16
  import { useStore } from "zustand";
26
- import { getComponent, registerComponent } from "./components/ComponentRegistry";
17
+ import { useClickValid } from "./useClickValid";
18
+ import { findComponent } from "./types";
19
+ import { getComponentDef, getComponentAssetRefs, registerComponent } from "./components/ComponentRegistry";
27
20
  import components from "./components";
28
- import { loadModel, loadTexture } from "../dragdrop";
21
+ import { loadModel, loadSound, loadTexture } from "../dragdrop";
29
22
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
30
- import { decompose } from "./utils";
31
- import { createPrefabStore, PrefabStoreProvider, prefabStoreToPrefab, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
23
+ import { composeTransform, decompose } from "./utils";
24
+ import { isPhysicsProps } from "./components/PhysicsComponent";
25
+ import { denormalizePrefab } from "./prefab";
26
+ import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
27
+ import { createRefBridge } from "./RefBridge";
28
+ import { sound as soundManager } from "../../helpers/SoundManager";
32
29
  components.forEach(registerComponent);
33
30
  const IDENTITY = new Matrix4();
34
31
  const EMPTY_MODELS = {};
35
32
  const EMPTY_TEXTURES = {};
36
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onSelectedObjectChange, onFocusNode, basePath = "", injectedModels = EMPTY_MODELS, injectedTextures = EMPTY_TEXTURES }, ref) => {
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
+ /** Resolve a relative or absolute asset file path against a base path. */
43
+ function resolveAssetPath(basePath, file) {
44
+ if (file.startsWith("http://") || file.startsWith("https://"))
45
+ return file;
46
+ return file.startsWith("/") ? `${basePath}${file}` : `${basePath}/${file}`;
47
+ }
48
+ /** Check if all model assets required by a node are loaded. */
49
+ function isNodeReady(node, loadedModels) {
50
+ var _a;
51
+ const model = findComponent(node, "Model");
52
+ if (!((_a = model === null || model === void 0 ? void 0 : model.properties) === null || _a === void 0 ? void 0 : _a.filename))
53
+ return true;
54
+ return Boolean(loadedModels[model.properties.filename]);
55
+ }
56
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, basePath = "" }, ref) => {
57
+ var _a;
37
58
  const [models, setModels] = useState({});
38
59
  const [textures, setTextures] = useState({});
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);
39
64
  const loading = useRef(new Set());
65
+ const failedModels = useRef(new Set());
40
66
  const failedTextures = useRef(new Set());
67
+ const failedSounds = useRef(new Set());
41
68
  const objectRefs = useRef({});
42
69
  const rigidBodyRefs = useRef(new Map());
43
70
  const rootRef = useRef(null);
44
- const [internalStore] = useState(() => { var _a; return createPrefabStore(data !== null && data !== void 0 ? data : prefabStoreToPrefab((_a = store === null || store === void 0 ? void 0 : store.getState()) !== null && _a !== void 0 ? _a : missingStoreState())); });
45
- const prefabStore = store !== null && store !== void 0 ? store : internalStore;
46
- const assetManifestKey = useStore(prefabStore, state => state.assetManifestKey);
71
+ const [refBridge] = useState(() => createRefBridge());
72
+ const parentStore = useOptionalPrefabStoreApi();
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())); });
74
+ const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
75
+ const usesOwnedStore = resolvedStore === ownedStore;
76
+ const shouldProvideStoreContext = !parentStore || parentStore !== resolvedStore;
77
+ const assetManifestKey = useStore(resolvedStore, state => state.assetManifestKey);
47
78
  const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
48
79
  const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
80
+ const availableSounds = useMemo(() => (Object.assign(Object.assign({}, sounds), injectedSounds)), [sounds, injectedSounds]);
49
81
  useImperativeHandle(ref, () => ({
50
82
  root: rootRef.current,
83
+ refBridge,
51
84
  rigidBodyRefs: rigidBodyRefs.current,
52
85
  getObject: (nodeId) => { var _a; return (_a = objectRefs.current[nodeId]) !== null && _a !== void 0 ? _a : null; },
53
- focusNode: (nodeId) => onFocusNode === null || onFocusNode === void 0 ? void 0 : onFocusNode(nodeId),
54
- }), [onFocusNode]);
86
+ getRigidBody: (nodeId) => { var _a; return (_a = rigidBodyRefs.current.get(nodeId)) !== null && _a !== void 0 ? _a : null; },
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]);
55
94
  const registerRef = useCallback((id, obj) => {
56
95
  objectRefs.current[id] = obj;
57
- if (id === selectedId) {
58
- onSelectedObjectChange === null || onSelectedObjectChange === void 0 ? void 0 : onSelectedObjectChange(obj);
59
- }
60
- }, [onSelectedObjectChange, selectedId]);
96
+ refBridge.register(id, obj);
97
+ }, [refBridge]);
61
98
  const registerRigidBodyRef = useCallback((id, rb) => {
62
99
  rigidBodyRefs.current.set(id, rb);
63
100
  }, []);
101
+ const getRigidBody = useCallback((id) => {
102
+ var _a;
103
+ return (_a = rigidBodyRefs.current.get(id)) !== null && _a !== void 0 ? _a : null;
104
+ }, []);
64
105
  useEffect(() => {
65
106
  const originalError = console.error;
66
107
  console.error = (...args) => {
@@ -71,78 +112,102 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
71
112
  return () => { console.error = originalError; };
72
113
  }, []);
73
114
  useEffect(() => {
74
- if (!store && data) {
75
- prefabStore.getState().replacePrefab(data);
115
+ if (usesOwnedStore && data) {
116
+ resolvedStore.getState().replacePrefab(data);
76
117
  }
77
- }, [data, prefabStore, store]);
118
+ }, [data, resolvedStore, usesOwnedStore]);
78
119
  useEffect(() => {
79
- const syncAssets = (snapshot = prefabStore.getState()) => {
120
+ const syncAssets = (snapshot = resolvedStore.getState()) => {
80
121
  const modelsToLoad = new Set();
81
122
  const texturesToLoad = new Set();
123
+ const soundsToLoad = new Set();
82
124
  Object.values(snapshot.nodesById).forEach(node => {
83
125
  var _a;
84
126
  Object.values((_a = node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
85
- var _a, _b, _c, _d;
127
+ var _a;
86
128
  if (!(component === null || component === void 0 ? void 0 : component.type))
87
129
  return;
88
- if (component.type === 'Model' && ((_a = component.properties) === null || _a === void 0 ? void 0 : _a.filename)) {
89
- modelsToLoad.add(component.properties.filename);
90
- }
91
- if (component.type === 'Material') {
92
- ((_b = component.properties) === null || _b === void 0 ? void 0 : _b.texture) && texturesToLoad.add(component.properties.texture);
93
- ((_c = component.properties) === null || _c === void 0 ? void 0 : _c.normalMapTexture) && texturesToLoad.add(component.properties.normalMapTexture);
94
- }
95
- if (component.type === 'SpotLight' && ((_d = component.properties) === null || _d === void 0 ? void 0 : _d.map)) {
96
- texturesToLoad.add(component.properties.map);
130
+ for (const ref of getComponentAssetRefs(component.type, (_a = component.properties) !== null && _a !== void 0 ? _a : {})) {
131
+ if (ref.type === 'model')
132
+ modelsToLoad.add(ref.path);
133
+ else if (ref.type === 'texture')
134
+ texturesToLoad.add(ref.path);
135
+ else if (ref.type === 'sound')
136
+ soundsToLoad.add(ref.path);
97
137
  }
98
138
  });
99
139
  });
100
- modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
101
- if (models[file] || injectedModels[file] || loading.current.has(file))
140
+ const loadAsset = (file, loaded, injected, failed, loader) => {
141
+ if (loaded[file] || injected[file] || loading.current.has(file) || failed.has(file))
102
142
  return;
103
143
  loading.current.add(file);
104
- const path = file.startsWith("/")
105
- ? `${basePath}${file}`
106
- : `${basePath}/${file}`;
107
- const res = yield loadModel(path);
108
- const model = res.model;
109
- if (res.success && model) {
144
+ void loader(resolveAssetPath(basePath, file)).then(result => {
145
+ if (!result.success) {
146
+ console.warn(`Failed to load asset: ${file}`, result.error);
147
+ loading.current.delete(file);
148
+ failed.add(file);
149
+ }
150
+ });
151
+ };
152
+ modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, (path) => loadModel(path).then(result => {
153
+ const model = result.model;
154
+ if (result.success && model) {
110
155
  setModels(m => (Object.assign(Object.assign({}, m), { [file]: model })));
111
156
  }
112
- }));
113
- texturesToLoad.forEach(file => {
114
- if (textures[file] || injectedTextures[file] || loading.current.has(file) || failedTextures.current.has(file))
115
- return;
116
- loading.current.add(file);
117
- // Handle full URLs (http/https) or regular paths
118
- const path = file.startsWith("http://") || file.startsWith("https://")
119
- ? file
120
- : file.startsWith("/")
121
- ? `${basePath}${file}`
122
- : `${basePath}/${file}`;
123
- void loadTexture(path).then(result => {
124
- if (result.success && result.texture) {
125
- setTextures(t => (Object.assign(Object.assign({}, t), { [file]: result.texture })));
126
- return;
127
- }
128
- console.warn(`Failed to load texture: ${path}`, result.error);
157
+ return result;
158
+ })));
159
+ texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, (path) => loadTexture(path).then(result => {
160
+ if (result.success && result.texture) {
161
+ setTextures(t => (Object.assign(Object.assign({}, t), { [file]: result.texture })));
162
+ }
163
+ return result;
164
+ })));
165
+ soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, (path) => loadSound(path).then(result => {
166
+ if (result.success && result.sound) {
167
+ soundManager.setBuffer(file, result.sound);
168
+ setSounds(current => (Object.assign(Object.assign({}, current), { [file]: result.sound })));
129
169
  loading.current.delete(file);
130
- failedTextures.current.add(file);
131
- });
132
- });
170
+ }
171
+ return result;
172
+ })));
133
173
  };
134
174
  syncAssets();
135
- }, [prefabStore, assetManifestKey, basePath, injectedModels, injectedTextures]);
136
- return (_jsx(PrefabStoreProvider, { store: prefabStore, children: _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, loadedModels: availableModels, loadedTextures: availableTextures, editMode: editMode, parentMatrix: IDENTITY }) }) }) }));
175
+ }, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
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 }) }) }));
198
+ if (!shouldProvideStoreContext) {
199
+ return _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content });
200
+ }
201
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(SceneRuntimeContext.Provider, { value: sceneRuntime, children: content }) });
137
202
  });
138
203
  function StoreRootNode(props) {
139
204
  const rootId = usePrefabRootId();
140
205
  return _jsx(GameObjectRenderer, Object.assign({}, props, { nodeId: rootId }));
141
206
  }
142
207
  export function GameObjectRenderer(props) {
143
- var _a, _b, _c;
208
+ var _a, _b;
144
209
  const node = usePrefabNode(props.nodeId);
145
- const isInstanced = (_c = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced;
210
+ const isInstanced = (_b = (_a = findComponent(node, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.instanced;
146
211
  const prevInstancedRef = useRef(undefined);
147
212
  const [isTransitioning, setIsTransitioning] = useState(false);
148
213
  useEffect(() => {
@@ -160,24 +225,27 @@ export function GameObjectRenderer(props) {
160
225
  ? _jsx(InstancedNode, Object.assign({}, props), key)
161
226
  : _jsx(StandardNode, Object.assign({}, props), key);
162
227
  }
163
- function isPhysicsProps(v) {
164
- return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicPosition" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicVelocity";
165
- }
166
228
  function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onClick }) {
167
- var _a, _b, _c, _d, _e, _f, _g, _h;
229
+ var _a, _b, _c;
168
230
  const gameObject = usePrefabNode(nodeId);
169
231
  if (!gameObject)
170
232
  return null;
171
233
  const localTransform = getNodeTransformProps(gameObject);
172
234
  const isLocked = Boolean(gameObject.locked);
173
- const clickable = Object.values((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).some(component => (component === null || component === void 0 ? void 0 : component.type) === 'Click');
174
- const physicsProps = isPhysicsProps((_c = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.physics) === null || _c === void 0 ? void 0 : _c.properties)
175
- ? (_e = (_d = gameObject.components) === null || _d === void 0 ? void 0 : _d.physics) === null || _e === void 0 ? void 0 : _e.properties
235
+ const clickComponent = findComponent(gameObject, "Click");
236
+ const clickable = Boolean(clickComponent);
237
+ const clickEventName = (_a = clickComponent === null || clickComponent === void 0 ? void 0 : clickComponent.properties) === null || _a === void 0 ? void 0 : _a.eventName;
238
+ const physicsData = findComponent(gameObject, "Physics");
239
+ const physicsProps = isPhysicsProps(physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties)
240
+ ? physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties
176
241
  : undefined;
177
- const modelUrl = (_h = (_g = (_f = gameObject.components) === null || _f === void 0 ? void 0 : _f.model) === null || _g === void 0 ? void 0 : _g.properties) === null || _h === void 0 ? void 0 : _h.filename;
242
+ const modelUrl = (_c = (_b = findComponent(gameObject, "Model")) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename;
178
243
  const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physicsProps), [gameObject, modelUrl, parentMatrix, physicsProps]);
179
244
  const groupRef = useRef(null);
180
- const clickValid = useRef(false);
245
+ const editClickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
246
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
247
+ onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
248
+ });
181
249
  useEffect(() => {
182
250
  if (editMode) {
183
251
  registerRef(nodeId, groupRef.current);
@@ -185,19 +253,12 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
185
253
  }
186
254
  }, [nodeId, registerRef, editMode]);
187
255
  if (editMode) {
188
- return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale, onPointerDown: isLocked ? undefined : (e) => { e.stopPropagation(); clickValid.current = true; }, onPointerMove: isLocked ? undefined : () => { clickValid.current = false; }, onPointerUp: isLocked ? undefined : (e) => {
189
- if (clickValid.current) {
190
- e.stopPropagation();
191
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
192
- onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
193
- }
194
- clickValid.current = false;
195
- }, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id)))] }));
256
+ return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: groupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale }, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), 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)))] }));
196
257
  }
197
- return (_jsx(_Fragment, { children: instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id))) }));
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))) }));
198
259
  }
199
- function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, registerRigidBodyRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
200
- var _a, _b, _c, _d, _e;
260
+ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, }) {
261
+ var _a, _b;
201
262
  const gameObject = usePrefabNode(nodeId);
202
263
  const childIds = usePrefabChildIds(nodeId);
203
264
  const isSelected = selectedId === nodeId;
@@ -205,59 +266,49 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, regi
205
266
  return null;
206
267
  const groupRef = useRef(null);
207
268
  const helperRef = useRef(null);
208
- const clickValid = useRef(false);
209
269
  const isLocked = Boolean(gameObject.locked);
210
270
  const stillInstanced = useInstanceCheck(nodeId);
271
+ const clickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
272
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
273
+ onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
274
+ });
211
275
  useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
212
276
  useEffect(() => {
213
277
  registerRef(nodeId, groupRef.current);
214
278
  return () => registerRef(nodeId, null);
215
279
  }, [nodeId, registerRef]);
216
280
  const world = parentMatrix.clone().multiply(compose(gameObject));
217
- const onDown = (e) => {
218
- e.stopPropagation();
219
- clickValid.current = true;
220
- };
221
- const onUp = (e) => {
222
- if (clickValid.current) {
223
- e.stopPropagation();
224
- onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
225
- onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
226
- }
227
- clickValid.current = false;
228
- };
229
- const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
230
- const ready = !((_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.model) ||
231
- loadedModels[gameObject.components.model.properties.filename];
281
+ const physics = findComponent(gameObject, "Physics");
282
+ const ready = isNodeReady(gameObject, loadedModels);
232
283
  const hasPhysics = physics && ready && !stillInstanced;
233
284
  const transform = getNodeTransformProps(gameObject);
234
- const physicsDef = hasPhysics ? getComponent("Physics") : null;
235
- const isInstanced = (_e = (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.properties) === null || _e === void 0 ? void 0 : _e.instanced;
285
+ const physicsDef = hasPhysics ? getComponentDef(physics.type) : null;
286
+ const isInstanced = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.instanced;
236
287
  const physicsKey = `physics_${nodeId}_${isInstanced ? 'instanced' : 'standard'}`;
237
- const renderCtx = { loadedModels, loadedTextures, editMode, registerRef };
288
+ const renderCtx = { loadedModels, editMode, registerRef };
238
289
  const childNodes = getChildHostComponents(gameObject).length > 0
239
290
  ? _jsx(CompositionChildren, { childIds: childIds, selectedId: selectedId, ctx: renderCtx, parentMatrix: world })
240
- : _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode });
241
- const inner = (_jsx("group", { onPointerDown: editMode && !isLocked ? onDown : undefined, onPointerMove: editMode && !isLocked ? () => (clickValid.current = false) : undefined, onPointerUp: editMode && !isLocked ? onUp : undefined, children: renderCompositionNode(gameObject, renderCtx, isSelected, parentMatrix, childNodes) }));
291
+ : _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) })));
242
293
  if (editMode) {
243
- 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] }));
244
295
  }
245
296
  if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
246
- 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));
247
298
  }
248
299
  return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }));
249
300
  }
250
- const LEAF_COMPONENT_TYPES = new Set(["Geometry", "Material", "Physics"]);
251
301
  function getChildHostComponents(gameObject) {
252
302
  var _a;
253
- return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).flatMap(([key, comp]) => {
303
+ return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).reduce((result, [key, comp]) => {
254
304
  if (!(comp === null || comp === void 0 ? void 0 : comp.type))
255
- return [];
256
- const def = getComponent(comp.type);
257
- if (!(def === null || def === void 0 ? void 0 : def.View) || LEAF_COMPONENT_TYPES.has(comp.type))
258
- return [];
259
- return { key, View: def.View, properties: comp.properties };
260
- });
305
+ return result;
306
+ const def = getComponentDef(comp.type);
307
+ if (!(def === null || def === void 0 ? void 0 : def.View) || def.isWrapper)
308
+ return result;
309
+ result.push({ key, View: def.View, properties: comp.properties });
310
+ return result;
311
+ }, []);
261
312
  }
262
313
  function ChildNodes(_a) {
263
314
  var { childIds, parentMatrix } = _a, props = __rest(_a, ["childIds", "parentMatrix"]);
@@ -265,11 +316,11 @@ function ChildNodes(_a) {
265
316
  }
266
317
  function compose(node) {
267
318
  const { position, rotation, scale } = getNodeTransformProps(node);
268
- return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
319
+ return composeTransform(position, rotation, scale);
269
320
  }
270
321
  function getModelRepeatSettings(node) {
271
- var _a, _b, _c;
272
- const properties = (_c = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) !== null && _c !== void 0 ? _c : {};
322
+ var _a, _b;
323
+ const properties = (_b = (_a = findComponent(node, "Model")) === null || _a === void 0 ? void 0 : _a.properties) !== null && _b !== void 0 ? _b : {};
273
324
  return {
274
325
  repeat: Boolean(properties.repeat),
275
326
  repeatAxes: getRepeatAxesFromModelProperties(properties),
@@ -320,12 +371,12 @@ function buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physics) {
320
371
  return instances;
321
372
  }
322
373
  function getNodeTransformProps(node) {
323
- var _a, _b, _c, _d, _e;
324
- const t = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.transform) === null || _b === void 0 ? void 0 : _b.properties;
374
+ var _a, _b, _c, _d;
375
+ const t = (_a = findComponent(node, "Transform")) === null || _a === void 0 ? void 0 : _a.properties;
325
376
  return {
326
- position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
327
- rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
328
- scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
377
+ position: (_b = t === null || t === void 0 ? void 0 : t.position) !== null && _b !== void 0 ? _b : [0, 0, 0],
378
+ rotation: (_c = t === null || t === void 0 ? void 0 : t.rotation) !== null && _c !== void 0 ? _c : [0, 0, 0],
379
+ scale: (_d = t === null || t === void 0 ? void 0 : t.scale) !== null && _d !== void 0 ? _d : [1, 1, 1],
329
380
  };
330
381
  }
331
382
  function renderCompositionSubtree(gameObject, ctx, isSelected, childIds, parentMatrix = IDENTITY) {
@@ -351,40 +402,25 @@ function renderCompositionNode(gameObject, ctx, isSelected, parentMatrix, childN
351
402
  const ownContent = renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix);
352
403
  return wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, _jsxs(_Fragment, { children: [ownContent, childNodes] }));
353
404
  }
354
- function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
355
- var _a, _b;
356
- const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
357
- const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
358
- const geometryDef = geometry && getComponent("Geometry");
359
- const materialDef = material && getComponent("Material");
360
- const contextProps = {
361
- loadedModels: ctx.loadedModels,
362
- loadedTextures: ctx.loadedTextures,
363
- editMode: ctx.editMode,
405
+ function buildContextProps(gameObject, _ctx, isSelected) {
406
+ return {
407
+ editMode: _ctx.editMode,
364
408
  isSelected,
365
409
  nodeId: gameObject.id,
366
- parentMatrix,
367
- registerRef: ctx.registerRef,
368
410
  };
369
- let core;
370
- if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
371
- core = (_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"))] }));
372
- }
373
- else {
374
- core = null;
375
- }
376
- return core;
411
+ }
412
+ function renderNodeOwnContent(gameObject, ctx, isSelected, parentMatrix) {
413
+ const geometry = findComponent(gameObject, "Geometry");
414
+ const material = findComponent(gameObject, "Material");
415
+ const geometryDef = geometry && getComponentDef(geometry.type);
416
+ const materialDef = material && getComponentDef(material.type);
417
+ if (!geometry || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
418
+ 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"))] }));
377
421
  }
378
422
  function wrapWithChildHosts(gameObject, ctx, isSelected, parentMatrix, subtree) {
379
- const contextProps = {
380
- loadedModels: ctx.loadedModels,
381
- loadedTextures: ctx.loadedTextures,
382
- editMode: ctx.editMode,
383
- isSelected,
384
- nodeId: gameObject.id,
385
- parentMatrix,
386
- registerRef: ctx.registerRef,
387
- };
423
+ const contextProps = buildContextProps(gameObject, ctx, isSelected);
388
424
  const childHosts = getChildHostComponents(gameObject);
389
425
  return childHosts.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), subtree);
390
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;