react-three-game 0.0.92 → 0.0.94

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 (39) hide show
  1. package/README.md +81 -88
  2. package/dist/helpers/index.d.ts +0 -3
  3. package/dist/helpers/index.js +1 -8
  4. package/dist/index.d.ts +10 -10
  5. package/dist/index.js +7 -6
  6. package/dist/tools/prefabeditor/EditorTree.js +10 -14
  7. package/dist/tools/prefabeditor/EditorUI.js +4 -4
  8. package/dist/tools/prefabeditor/GameEvents.d.ts +6 -12
  9. package/dist/tools/prefabeditor/GameEvents.js +0 -8
  10. package/dist/tools/prefabeditor/InstanceProvider.d.ts +6 -4
  11. package/dist/tools/prefabeditor/InstanceProvider.js +84 -199
  12. package/dist/tools/prefabeditor/PrefabEditor.d.ts +12 -21
  13. package/dist/tools/prefabeditor/PrefabEditor.js +138 -146
  14. package/dist/tools/prefabeditor/PrefabRoot.d.ts +30 -11
  15. package/dist/tools/prefabeditor/PrefabRoot.js +182 -139
  16. package/dist/tools/prefabeditor/assetRuntime.d.ts +9 -13
  17. package/dist/tools/prefabeditor/assetRuntime.js +13 -13
  18. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +1 -1
  19. package/dist/tools/prefabeditor/components/CameraComponent.js +2 -2
  20. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +3 -3
  21. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
  22. package/dist/tools/prefabeditor/components/Input.js +5 -9
  23. package/dist/tools/prefabeditor/components/ModelComponent.js +4 -6
  24. package/dist/tools/prefabeditor/components/PointLightComponent.js +2 -2
  25. package/dist/tools/prefabeditor/components/SoundComponent.js +2 -2
  26. package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -2
  27. package/dist/tools/prefabeditor/components/index.js +0 -2
  28. package/dist/tools/prefabeditor/prefab.d.ts +1 -2
  29. package/dist/tools/prefabeditor/prefab.js +2 -3
  30. package/dist/tools/prefabeditor/prefabStore.d.ts +0 -6
  31. package/dist/tools/prefabeditor/prefabStore.js +1 -33
  32. package/dist/tools/prefabeditor/types.d.ts +1 -0
  33. package/dist/tools/prefabeditor/usePointerEvents.d.ts +3 -3
  34. package/dist/tools/prefabeditor/usePointerEvents.js +5 -5
  35. package/package.json +49 -51
  36. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +0 -26
  37. package/dist/tools/prefabeditor/components/PhysicsComponent.js +0 -302
  38. package/dist/tools/prefabeditor/scene.d.ts +0 -70
  39. package/dist/tools/prefabeditor/scene.js +0 -237
@@ -9,8 +9,8 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
13
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
+ import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
14
  import { Euler, Matrix4, } from "three";
15
15
  import { useStore } from "zustand";
16
16
  import { useClickValid } from "./useClickValid";
@@ -20,9 +20,8 @@ import { builtinComponents } from "./components";
20
20
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
21
21
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
22
22
  import { composeTransform, decompose } from "./utils";
23
- import { isPhysicsProps } from "./components/PhysicsComponent";
24
- import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
25
- import { AssetRuntimeContext, EntityRuntimeScope } from "./assetRuntime";
23
+ import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
24
+ import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
26
25
  import { gameEvents } from "./GameEvents";
27
26
  import { sound as soundManager } from "../../helpers/SoundManager";
28
27
  builtinComponents.forEach(registerComponent);
@@ -36,30 +35,13 @@ function resolveAssetPath(basePath, file) {
36
35
  return file;
37
36
  return file.startsWith("/") ? `${basePath}${file}` : `${basePath}/${file}`;
38
37
  }
39
- /** Check if all model assets required by a node are loaded. */
40
- function isNodeReady(node, loadedModels) {
38
+ /** Check if a model component's assets are loaded. */
39
+ function isNodeReady(model, loadedModels) {
41
40
  var _a;
42
- const model = findComponent(node, "Model");
43
41
  if (!((_a = model === null || model === void 0 ? void 0 : model.properties) === null || _a === void 0 ? void 0 : _a.filename))
44
42
  return true;
45
43
  return Boolean(loadedModels[model.properties.filename]);
46
44
  }
47
- function getNodeClickEventName(node) {
48
- var _a;
49
- const clickComponents = [
50
- findComponent(node, 'BufferGeometry'),
51
- findComponent(node, 'Geometry'),
52
- ];
53
- for (const component of clickComponents) {
54
- if (!((_a = component === null || component === void 0 ? void 0 : component.properties) === null || _a === void 0 ? void 0 : _a.emitClickEvent))
55
- continue;
56
- const eventName = component.properties.clickEventName;
57
- if (typeof eventName === 'string' && eventName.trim()) {
58
- return eventName.trim();
59
- }
60
- }
61
- return null;
62
- }
63
45
  function getNodeMetadataProps(node) {
64
46
  var _a, _b;
65
47
  const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
@@ -68,8 +50,20 @@ function getNodeMetadataProps(node) {
68
50
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
69
51
  };
70
52
  }
71
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, basePath = "" }, ref) => {
72
- var _a;
53
+ export var PrefabEditorMode;
54
+ (function (PrefabEditorMode) {
55
+ PrefabEditorMode["Edit"] = "edit";
56
+ PrefabEditorMode["Play"] = "play";
57
+ })(PrefabEditorMode || (PrefabEditorMode = {}));
58
+ export const SceneContext = createContext(null);
59
+ export function useScene() {
60
+ const scene = useContext(SceneContext);
61
+ if (!scene) {
62
+ throw new Error("useScene must be used within a PrefabRoot or PrefabEditor scene provider");
63
+ }
64
+ return scene;
65
+ }
66
+ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
73
67
  const [models, setModels] = useState({});
74
68
  const [textures, setTextures] = useState({});
75
69
  const [sounds, setSounds] = useState({});
@@ -81,19 +75,17 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
81
75
  const failedTextures = useRef(new Set());
82
76
  const failedSounds = useRef(new Set());
83
77
  const objectRefs = useRef({});
84
- const rigidBodyRefs = useRef(new Map());
85
- const rootRef = useRef(null);
86
- const parentStore = useOptionalPrefabStoreApi();
78
+ const nodeHandles = useRef(new Map());
87
79
  const [ownedStore] = useState(() => {
80
+ if (store)
81
+ return null;
88
82
  if (data)
89
83
  return createPrefabStore(data);
90
- if (store || parentStore)
91
- return null;
92
84
  throw new Error("PrefabRoot requires either a `data` or `store` prop");
93
85
  });
94
- const resolvedStore = (_a = store !== null && store !== void 0 ? store : parentStore) !== null && _a !== void 0 ? _a : ownedStore;
86
+ const resolvedStore = store !== null && store !== void 0 ? store : ownedStore;
95
87
  const usesOwnedStore = resolvedStore === ownedStore;
96
- const shouldProvideStoreContext = !parentStore || parentStore !== resolvedStore;
88
+ const rootId = useStore(resolvedStore, state => state.rootId);
97
89
  const assetManifestKey = useStore(resolvedStore, state => state.assetManifestKey);
98
90
  const availableModels = useMemo(() => (Object.assign(Object.assign({}, models), injectedModels)), [models, injectedModels]);
99
91
  const availableTextures = useMemo(() => (Object.assign(Object.assign({}, textures), injectedTextures)), [textures, injectedTextures]);
@@ -102,27 +94,61 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
102
94
  var _a;
103
95
  return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null;
104
96
  }, []);
105
- const getRigidBody = useCallback((id) => {
97
+ const getHandle = useCallback((id, kind) => {
98
+ var _a, _b;
99
+ return (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : null;
100
+ }, []);
101
+ const getNode = useCallback((nodeId) => {
106
102
  var _a;
107
- return (_a = rigidBodyRefs.current.get(id)) !== null && _a !== void 0 ? _a : null;
103
+ return (_a = resolvedStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null;
104
+ }, [resolvedStore]);
105
+ const registerHandle = useCallback((id, kind, handle) => {
106
+ const current = nodeHandles.current.get(id);
107
+ if (handle == null) {
108
+ if (!current)
109
+ return;
110
+ current.delete(kind);
111
+ if (current.size === 0) {
112
+ nodeHandles.current.delete(id);
113
+ }
114
+ return;
115
+ }
116
+ if (current) {
117
+ current.set(kind, handle);
118
+ return;
119
+ }
120
+ nodeHandles.current.set(id, new Map([[kind, handle]]));
108
121
  }, []);
109
- useImperativeHandle(ref, () => ({
110
- root: rootRef.current,
122
+ const sceneValue = useMemo(() => ({
123
+ get root() {
124
+ var _a;
125
+ return (_a = objectRefs.current[rootId]) !== null && _a !== void 0 ? _a : null;
126
+ },
127
+ mode: editMode ? PrefabEditorMode.Edit : PrefabEditorMode.Play,
128
+ get: getNode,
111
129
  getObject,
112
- getRigidBody: (nodeId) => { var _a; return (_a = rigidBodyRefs.current.get(nodeId)) !== null && _a !== void 0 ? _a : null; },
130
+ getHandle,
131
+ add: (node, parentId) => {
132
+ const state = resolvedStore.getState();
133
+ state.addChild(parentId !== null && parentId !== void 0 ? parentId : state.rootId, node);
134
+ return node;
135
+ },
136
+ update: (id, fn) => resolvedStore.getState().updateNode(id, fn),
137
+ remove: (id) => resolvedStore.getState().deleteNode(id),
138
+ duplicate: (id) => resolvedStore.getState().duplicateNode(id),
139
+ move: (draggedId, targetId, position) => resolvedStore.getState().moveNode(draggedId, targetId, position),
140
+ replace: (prefab) => resolvedStore.getState().replacePrefab(prefab),
113
141
  addModel: (path, model) => setInjectedModels(prev => (Object.assign(Object.assign({}, prev), { [path]: model }))),
114
142
  addTexture: (path, texture) => setInjectedTextures(prev => (Object.assign(Object.assign({}, prev), { [path]: texture }))),
115
143
  addSound: (path, sound) => {
116
144
  soundManager.setBuffer(path, sound);
117
145
  setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
118
146
  },
119
- }), [getObject]);
147
+ }), [editMode, getHandle, getNode, getObject, resolvedStore, rootId]);
148
+ useImperativeHandle(ref, () => sceneValue, [sceneValue]);
120
149
  const registerRef = useCallback((id, obj) => {
121
150
  objectRefs.current[id] = obj;
122
151
  }, []);
123
- const registerRigidBodyRef = useCallback((id, rb) => {
124
- rigidBodyRefs.current.set(id, rb);
125
- }, []);
126
152
  useEffect(() => {
127
153
  if (usesOwnedStore && data) {
128
154
  resolvedStore.getState().replacePrefab(data);
@@ -154,31 +180,27 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
154
180
  return;
155
181
  loading.current.add(file);
156
182
  void loader(resolveAssetPath(basePath, file)).then(result => {
183
+ loading.current.delete(file);
157
184
  if (!result.success) {
158
185
  console.warn(`Failed to load asset: ${file}`, result.error);
159
- loading.current.delete(file);
160
186
  failed.add(file);
161
187
  }
162
188
  });
163
189
  };
164
- modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, (path) => loadModel(path).then(result => {
165
- const model = result.model;
166
- if (result.success && model) {
167
- setModels(m => (Object.assign(Object.assign({}, m), { [file]: model })));
168
- }
190
+ modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
191
+ if (result.success && result.model)
192
+ setModels(m => (Object.assign(Object.assign({}, m), { [file]: result.model })));
169
193
  return result;
170
194
  })));
171
- texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, (path) => loadTexture(path).then(result => {
172
- if (result.success && result.texture) {
195
+ texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
196
+ if (result.success && result.texture)
173
197
  setTextures(t => (Object.assign(Object.assign({}, t), { [file]: result.texture })));
174
- }
175
198
  return result;
176
199
  })));
177
- soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, (path) => loadSound(path).then(result => {
200
+ soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
178
201
  if (result.success && result.sound) {
179
202
  soundManager.setBuffer(file, result.sound);
180
- setSounds(current => (Object.assign(Object.assign({}, current), { [file]: result.sound })));
181
- loading.current.delete(file);
203
+ setSounds(s => (Object.assign(Object.assign({}, s), { [file]: result.sound })));
182
204
  }
183
205
  return result;
184
206
  })));
@@ -186,25 +208,83 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
186
208
  syncAssets();
187
209
  }, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
188
210
  const assetRuntime = useMemo(() => ({
211
+ registerHandle,
212
+ getHandle,
189
213
  getObject,
190
- getRigidBody,
191
- registerRigidBodyRef,
192
214
  getModel: (path) => { var _a; return (_a = availableModels[path]) !== null && _a !== void 0 ? _a : null; },
193
215
  getTexture: (path) => { var _a; return (_a = availableTextures[path]) !== null && _a !== void 0 ? _a : null; },
194
216
  getSound: (path) => { var _a; return (_a = availableSounds[path]) !== null && _a !== void 0 ? _a : null; },
195
217
  getAssetRevision: () => `${Object.keys(availableTextures).sort().join('|')}::${Object.keys(availableModels).sort().join('|')}`,
196
- }), [getObject, getRigidBody, registerRigidBodyRef, availableModels, availableTextures, availableSounds]);
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
- const runtimeContent = _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
199
- if (!shouldProvideStoreContext) {
200
- return runtimeContent;
201
- }
218
+ }), [registerHandle, getHandle, getObject, availableModels, availableTextures, availableSounds]);
219
+ const handleNodeClick = useCallback((event, nodeId, fallbackObject) => {
220
+ const node = resolvedStore.getState().nodesById[nodeId];
221
+ if (!node)
222
+ return;
223
+ const { clickEventName } = analyzeNodeComponents(node);
224
+ emitNodePointerEvent(clickEventName, event, nodeId, node, fallbackObject);
225
+ onClick === null || onClick === void 0 ? void 0 : onClick(event, node);
226
+ }, [onClick, resolvedStore]);
227
+ const content = (_jsxs(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerRef, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }), children] }));
228
+ const runtimeContent = (_jsx(SceneContext.Provider, { value: sceneValue, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) }));
202
229
  return _jsx(PrefabStoreProvider, { store: resolvedStore, children: runtimeContent });
203
230
  });
204
231
  function StoreRootNode(props) {
205
232
  const rootId = usePrefabRootId();
206
233
  return _jsx(GameObjectRenderer, Object.assign({}, props, { nodeId: rootId }));
207
234
  }
235
+ function getClickEventName(component) {
236
+ var _a;
237
+ if (!((_a = component === null || component === void 0 ? void 0 : component.properties) === null || _a === void 0 ? void 0 : _a.emitClickEvent))
238
+ return null;
239
+ const eventName = component.properties.clickEventName;
240
+ return typeof eventName === 'string' && eventName.trim() ? eventName.trim() : null;
241
+ }
242
+ function analyzeNodeComponents(node) {
243
+ var _a, _b, _c;
244
+ let bufferGeometry;
245
+ let geometry;
246
+ let material;
247
+ let model;
248
+ const composition = [];
249
+ for (const [key, component] of Object.entries((_a = node.components) !== null && _a !== void 0 ? _a : {})) {
250
+ if (!(component === null || component === void 0 ? void 0 : component.type))
251
+ continue;
252
+ switch (component.type) {
253
+ case "Transform":
254
+ break;
255
+ case "BufferGeometry":
256
+ bufferGeometry = component;
257
+ break;
258
+ case "Geometry":
259
+ geometry = component;
260
+ break;
261
+ case "Material":
262
+ material = component;
263
+ break;
264
+ case "Model":
265
+ model = component;
266
+ break;
267
+ default: {
268
+ const def = getComponentDef(component.type);
269
+ if (!(def === null || def === void 0 ? void 0 : def.View))
270
+ break;
271
+ composition.push({
272
+ key,
273
+ View: def.View,
274
+ properties: component.properties,
275
+ });
276
+ break;
277
+ }
278
+ }
279
+ }
280
+ return {
281
+ geometry: bufferGeometry !== null && bufferGeometry !== void 0 ? bufferGeometry : geometry,
282
+ material,
283
+ model,
284
+ clickEventName: (_c = (_b = getClickEventName(bufferGeometry)) !== null && _b !== void 0 ? _b : getClickEventName(geometry)) !== null && _c !== void 0 ? _c : getClickEventName(model),
285
+ composition,
286
+ };
287
+ }
208
288
  function emitNodePointerEvent(eventName, event, nodeId, node, fallbackObject) {
209
289
  var _a;
210
290
  const trimmedEventName = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
@@ -247,21 +327,19 @@ export function GameObjectRenderer(props) {
247
327
  ? _jsx(InstancedNode, Object.assign({}, props), key)
248
328
  : _jsx(StandardNode, Object.assign({}, props), key);
249
329
  }
250
- function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onClick }) {
330
+ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onEditNodeClick, onClick, isVisible = true }) {
251
331
  var _a, _b;
252
332
  const gameObject = usePrefabNode(nodeId);
253
333
  if (!gameObject)
254
334
  return null;
335
+ const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
255
336
  const localTransform = getNodeTransformProps(gameObject);
256
337
  const isLocked = Boolean(gameObject.locked);
338
+ const nodeVisible = isVisible && !gameObject.hidden;
257
339
  const metadataProps = getNodeMetadataProps(gameObject);
258
- const groupProps = Object.assign(Object.assign({}, metadataProps), { position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
259
- const physicsData = findComponent(gameObject, "Physics");
260
- const physicsProps = isPhysicsProps(physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties)
261
- ? physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties
262
- : undefined;
263
- const modelUrl = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
264
- const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physicsProps), [gameObject, modelUrl, parentMatrix, physicsProps]);
340
+ const groupProps = Object.assign(Object.assign({}, metadataProps), { visible: nodeVisible, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
341
+ const modelUrl = (_b = (_a = analyzedComponents.model) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
342
+ const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl), [gameObject, modelUrl, parentMatrix]);
265
343
  const groupRef = useRef(null);
266
344
  const handleGroupRef = useCallback((object) => {
267
345
  groupRef.current = object;
@@ -271,24 +349,24 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
271
349
  }, [editMode, nodeId, registerRef]);
272
350
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
273
351
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
274
- onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
352
+ onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
275
353
  });
276
- const renderedInstances = instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id)));
354
+ const renderedInstances = instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, visible: nodeVisible, locked: isLocked, onClick: onClick }, instance.id)));
277
355
  if (editMode) {
278
356
  return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), renderedInstances] }));
279
357
  }
280
358
  return _jsx(_Fragment, { children: renderedInstances });
281
359
  }
282
- function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, }) {
283
- var _a, _b;
360
+ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, }) {
284
361
  const gameObject = usePrefabNode(nodeId);
285
362
  const childIds = usePrefabChildIds(nodeId);
286
363
  if (!gameObject)
287
364
  return null;
365
+ const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
288
366
  const isSelected = selectedId === nodeId;
289
367
  const isLocked = Boolean(gameObject.locked);
368
+ const nodeVisible = isVisible && !gameObject.hidden;
290
369
  const stillInstanced = useInstanceCheck(nodeId);
291
- const clickEventName = getNodeClickEventName(gameObject);
292
370
  const metadataProps = getNodeMetadataProps(gameObject);
293
371
  const groupRef = useRef(null);
294
372
  const handleGroupRef = useCallback((object) => {
@@ -297,21 +375,18 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
297
375
  }, [nodeId, registerRef]);
298
376
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
299
377
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
300
- onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
378
+ onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
301
379
  });
302
- const primaryClickHandlers = !editMode && (clickEventName || onClick)
380
+ const primaryClickHandlers = !editMode && onClick
303
381
  ? {
304
382
  onClick: (event) => {
305
383
  event.stopPropagation();
306
- emitNodePointerEvent(clickEventName, event, nodeId, gameObject, groupRef.current);
307
- onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
384
+ onClick(event, nodeId, groupRef.current);
308
385
  },
309
386
  }
310
387
  : undefined;
311
388
  const world = parentMatrix.clone().multiply(compose(gameObject));
312
- const physics = findComponent(gameObject, "Physics");
313
- const ready = isNodeReady(gameObject, loadedModels);
314
- const hasPhysics = physics && ready && !stillInstanced;
389
+ const ready = isNodeReady(analyzedComponents.model, loadedModels);
315
390
  const transform = getNodeTransformProps(gameObject);
316
391
  const transformProps = {
317
392
  position: transform.position,
@@ -319,40 +394,11 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
319
394
  scale: transform.scale,
320
395
  };
321
396
  const groupProps = Object.assign(Object.assign({}, metadataProps), transformProps);
322
- const physicsDef = hasPhysics ? getComponentDef(physics.type) : null;
323
- const isInstanced = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.instanced;
324
- const physicsKey = `physics_${nodeId}_${isInstanced ? 'instanced' : 'standard'}`;
325
- const renderCtx = { loadedModels, editMode, registerRef };
326
- const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
327
- const inner = renderCompositionNode(gameObject, renderCtx, primaryClickHandlers, childNodes);
397
+ const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, onEditNodeClick: onEditNodeClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode, isVisible: nodeVisible });
398
+ const inner = renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes);
328
399
  const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
329
- const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
330
- const physicsNode = hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsxs("group", Object.assign({ ref: handleGroupRef }, metadataProps, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })) }), physicsKey)) : null;
331
- return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: physicsNode !== null && physicsNode !== void 0 ? physicsNode : standardNode }));
332
- }
333
- function isRendererHandledComponent(componentType) {
334
- return componentType === "Transform"
335
- || componentType === "BufferGeometry"
336
- || componentType === "Geometry"
337
- || componentType === "Material"
338
- || componentType === "Physics"
339
- || componentType === "Model";
340
- }
341
- function getCompositionComponents(gameObject) {
342
- var _a;
343
- return Object.entries((_a = gameObject.components) !== null && _a !== void 0 ? _a : {}).reduce((result, [key, comp]) => {
344
- if (!(comp === null || comp === void 0 ? void 0 : comp.type) || isRendererHandledComponent(comp.type))
345
- return result;
346
- const def = getComponentDef(comp.type);
347
- if (!(def === null || def === void 0 ? void 0 : def.View))
348
- return result;
349
- result.push({
350
- key,
351
- View: def.View,
352
- properties: comp.properties,
353
- });
354
- return result;
355
- }, []);
400
+ const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, { visible: nodeVisible }, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
401
+ return (_jsx(NodeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: standardNode }));
356
402
  }
357
403
  function ChildNodes(_a) {
358
404
  var { childIds, parentMatrix } = _a, props = __rest(_a, ["childIds", "parentMatrix"]);
@@ -370,7 +416,7 @@ function getModelRepeatSettings(node) {
370
416
  repeatAxes: getRepeatAxesFromModelProperties(properties),
371
417
  };
372
418
  }
373
- function buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physics) {
419
+ function buildRepeatedInstances(gameObject, parentMatrix, modelUrl) {
374
420
  if (!modelUrl)
375
421
  return [];
376
422
  const transform = getNodeTransformProps(gameObject);
@@ -407,7 +453,6 @@ function buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physics) {
407
453
  position,
408
454
  rotation,
409
455
  scale,
410
- physics,
411
456
  });
412
457
  }
413
458
  }
@@ -423,32 +468,30 @@ function getNodeTransformProps(node) {
423
468
  scale: (_d = t === null || t === void 0 ? void 0 : t.scale) !== null && _d !== void 0 ? _d : [1, 1, 1],
424
469
  };
425
470
  }
426
- function renderCompositionNode(gameObject, ctx, primaryClickHandlers, childNodes) {
427
- const primaryContent = renderNodePrimaryContent(gameObject, ctx, primaryClickHandlers);
428
- return applyNodeComposition(gameObject, _jsxs(_Fragment, { children: [primaryContent, childNodes] }));
429
- }
430
- function renderNodePrimaryContent(gameObject, ctx, primaryClickHandlers) {
431
- var _a, _b, _c;
432
- const geometry = (_a = findComponent(gameObject, "BufferGeometry")) !== null && _a !== void 0 ? _a : findComponent(gameObject, "Geometry");
433
- const material = findComponent(gameObject, "Material");
434
- const model = findComponent(gameObject, "Model");
435
- const geometryDef = geometry && getComponentDef(geometry.type);
436
- const materialDef = material && getComponentDef(material.type);
437
- const modelDef = model && getComponentDef(model.type);
438
- const geometryProperties = (_b = geometry === null || geometry === void 0 ? void 0 : geometry.properties) !== null && _b !== void 0 ? _b : {};
471
+ function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes) {
472
+ var _a, _b, _c, _d;
473
+ const geometry = analyzedComponents.geometry;
474
+ const geometryDef = analyzedComponents.geometry && getComponentDef(analyzedComponents.geometry.type);
475
+ const materialDef = analyzedComponents.material && getComponentDef(analyzedComponents.material.type);
476
+ const modelDef = analyzedComponents.model && getComponentDef(analyzedComponents.model.type);
477
+ const geometryProperties = (_a = geometry === null || geometry === void 0 ? void 0 : geometry.properties) !== null && _a !== void 0 ? _a : {};
439
478
  const meshVisible = geometryProperties.visible !== false;
440
479
  const meshCastShadow = meshVisible && geometryProperties.castShadow !== false;
441
480
  const meshReceiveShadow = meshVisible && geometryProperties.receiveShadow !== false;
442
- if ((geometry === null || geometry === void 0 ? void 0 : geometry.type) && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
443
- return (_jsxs("mesh", Object.assign({ visible: meshVisible, castShadow: meshCastShadow, receiveShadow: meshReceiveShadow }, primaryClickHandlers, { children: [_jsx(geometryDef.View, { properties: geometry.properties }), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: material.properties }, "material"))] })));
481
+ let primaryContent = null;
482
+ if (((_b = analyzedComponents.geometry) === null || _b === void 0 ? void 0 : _b.type) && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
483
+ primaryContent = (_jsxs("mesh", Object.assign({ visible: meshVisible, castShadow: meshCastShadow, receiveShadow: meshReceiveShadow }, primaryClickHandlers, { children: [_jsx(geometryDef.View, { properties: analyzedComponents.geometry.properties }), analyzedComponents.material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: analyzedComponents.material.properties }, "material"))] })));
444
484
  }
445
- if ((model === null || model === void 0 ? void 0 : model.type) && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View) && !((_c = model.properties) === null || _c === void 0 ? void 0 : _c.instanced) && isNodeReady(gameObject, ctx.loadedModels)) {
446
- return _jsx(modelDef.View, { properties: model.properties });
485
+ else if (((_c = analyzedComponents.model) === null || _c === void 0 ? void 0 : _c.type)
486
+ && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)
487
+ && !((_d = analyzedComponents.model.properties) === null || _d === void 0 ? void 0 : _d.instanced)
488
+ && isNodeReady(analyzedComponents.model, loadedModels)) {
489
+ primaryContent = primaryClickHandlers ? (_jsx("group", Object.assign({}, primaryClickHandlers, { children: _jsx(modelDef.View, { properties: analyzedComponents.model.properties }) }))) : (_jsx(modelDef.View, { properties: analyzedComponents.model.properties }));
447
490
  }
448
- return null;
449
- }
450
- function applyNodeComposition(gameObject, subtree) {
451
- const components = getCompositionComponents(gameObject);
452
- return components.reduce((acc, { key, View, properties }) => (_jsx(View, { properties: properties, children: acc }, key)), subtree);
491
+ let content = _jsxs(_Fragment, { children: [primaryContent, childNodes] });
492
+ for (const { key, View, properties } of analyzedComponents.composition) {
493
+ content = (_jsx(View, { properties: properties, children: content }, key));
494
+ }
495
+ return content;
453
496
  }
454
497
  export default PrefabRoot;
@@ -1,34 +1,30 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { Object3D, Texture } from "three";
3
3
  export interface AssetRuntime {
4
- registerRigidBodyRef: (id: string, rb: any) => void;
4
+ registerHandle: (id: string, kind: string, handle: unknown) => void;
5
+ getHandle: <T = unknown>(id: string, kind: string) => T | null;
5
6
  getModel: (path: string) => Object3D | null;
6
7
  getTexture: (path: string) => Texture | null;
7
8
  getSound: (path: string) => AudioBuffer | null;
8
9
  getAssetRevision: () => string;
9
10
  getObject: (id: string) => Object3D | null;
10
- getRigidBody: (id: string) => any;
11
11
  }
12
- export interface AssetRuntimeContextValue extends AssetRuntime {
13
- }
14
- export interface EntityRuntime {
12
+ export interface NodeApi {
15
13
  nodeId: string;
16
14
  editMode?: boolean;
17
15
  isSelected?: boolean;
18
16
  getObject: <T extends Object3D = Object3D>() => T | null;
19
- getRigidBody: <T = any>() => T | null;
17
+ getHandle: <T = unknown>(kind: string) => T | null;
20
18
  }
21
19
  export interface LiveRef<T> {
22
20
  readonly current: T | null;
23
21
  }
24
- export type LiveObjectRef<T extends Object3D = Object3D> = LiveRef<T>;
25
- export type LiveRigidBodyRef<T = any> = LiveRef<T>;
26
- export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
22
+ export declare const AssetRuntimeContext: import("react").Context<AssetRuntime | null>;
27
23
  export declare function useAssetRuntime(): AssetRuntime;
28
- export declare function useEntityRuntime(): EntityRuntime;
29
- export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveRef<T>;
30
- export declare function useEntityRigidBodyRef<T = any>(): LiveRef<T>;
31
- export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }: {
24
+ export declare function useNode(): NodeApi;
25
+ export declare function useNodeObject<T extends Object3D = Object3D>(): LiveRef<T>;
26
+ export declare function useNodeHandle<T = unknown>(kind: string): LiveRef<T>;
27
+ export declare function NodeScope({ nodeId, editMode, isSelected, children, }: {
32
28
  nodeId: string;
33
29
  editMode?: boolean;
34
30
  isSelected?: boolean;
@@ -1,37 +1,37 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
3
  export const AssetRuntimeContext = createContext(null);
4
- const EntityRuntimeContext = createContext(null);
4
+ const NodeContext = createContext(null);
5
5
  export function useAssetRuntime() {
6
6
  const ctx = useContext(AssetRuntimeContext);
7
7
  if (!ctx)
8
8
  throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
9
9
  return ctx;
10
10
  }
11
- export function useEntityRuntime() {
12
- const ctx = useContext(EntityRuntimeContext);
11
+ export function useNode() {
12
+ const ctx = useContext(NodeContext);
13
13
  if (!ctx)
14
- throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
14
+ throw new Error("useNode must be used inside a component View rendered by <PrefabRoot>");
15
15
  return ctx;
16
16
  }
17
- export function useEntityObjectRef() {
18
- const { getObject } = useEntityRuntime();
17
+ export function useNodeObject() {
18
+ const { getObject } = useNode();
19
19
  return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
20
20
  }
21
- export function useEntityRigidBodyRef() {
22
- const { getRigidBody } = useEntityRuntime();
23
- return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
21
+ export function useNodeHandle(kind) {
22
+ const { getHandle } = useNode();
23
+ return useMemo(() => ({ get current() { return getHandle(kind); } }), [getHandle, kind]);
24
24
  }
25
- export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
25
+ export function NodeScope({ nodeId, editMode, isSelected, children, }) {
26
26
  const asset = useContext(AssetRuntimeContext);
27
27
  if (!asset)
28
- throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
28
+ throw new Error("NodeScope must be used inside <PrefabRoot>");
29
29
  const value = useMemo(() => ({
30
30
  nodeId,
31
31
  editMode,
32
32
  isSelected,
33
33
  getObject: () => asset.getObject(nodeId),
34
- getRigidBody: () => asset.getRigidBody(nodeId),
34
+ getHandle: (kind) => asset.getHandle(nodeId, kind),
35
35
  }), [asset, editMode, isSelected, nodeId]);
36
- return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
36
+ return _jsx(NodeContext.Provider, { value: value, children: children });
37
37
  }
@@ -52,7 +52,7 @@ function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
52
52
  function BufferGeometryComponentEditor({ component, onUpdate, }) {
53
53
  var _a;
54
54
  const properties = (_a = component.properties) !== null && _a !== void 0 ? _a : {};
55
- return (_jsxs(FieldGroup, { children: [_jsx(BufferArrayField, { label: "Positions", value: properties.positions, fallback: DEFAULT_TRIANGLE_POSITIONS, rows: 5, onChange: (positions) => onUpdate({ positions }) }), _jsx(BufferArrayField, { label: "Indices", value: properties.indices, fallback: DEFAULT_TRIANGLE_INDICES, onChange: (indices) => onUpdate({ indices }) }), _jsx(BufferArrayField, { label: "Normals", value: properties.normals, fallback: [], onChange: (normals) => onUpdate({ normals }) }), _jsx(BufferArrayField, { label: "UVs", value: properties.uvs, fallback: DEFAULT_TRIANGLE_UVS, onChange: (uvs) => onUpdate({ uvs }) }), _jsx(BooleanField, { name: "computeVertexNormals", label: "Compute Normals", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "visible", label: "Visible", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: properties, onChange: onUpdate, fallback: false }), properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: properties, onChange: onUpdate, placeholder: "entity:click" })) : null] }));
55
+ return (_jsxs(FieldGroup, { children: [_jsx(BufferArrayField, { label: "Positions", value: properties.positions, fallback: DEFAULT_TRIANGLE_POSITIONS, rows: 5, onChange: (positions) => onUpdate({ positions }) }), _jsx(BufferArrayField, { label: "Indices", value: properties.indices, fallback: DEFAULT_TRIANGLE_INDICES, onChange: (indices) => onUpdate({ indices }) }), _jsx(BufferArrayField, { label: "Normals", value: properties.normals, fallback: [], onChange: (normals) => onUpdate({ normals }) }), _jsx(BufferArrayField, { label: "UVs", value: properties.uvs, fallback: DEFAULT_TRIANGLE_UVS, onChange: (uvs) => onUpdate({ uvs }) }), _jsx(BooleanField, { name: "computeVertexNormals", label: "Compute Normals", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "visible", label: "Visible", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: properties, onChange: onUpdate, fallback: false }), properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: properties, onChange: onUpdate, placeholder: "node:click" })) : null] }));
56
56
  }
57
57
  function BufferGeometryComponentView({ properties }) {
58
58
  const positions = normalizeNumberArray(properties.positions, DEFAULT_TRIANGLE_POSITIONS);
@@ -3,7 +3,7 @@ import { OrthographicCamera, PerspectiveCamera, useHelper } from '@react-three/d
3
3
  import { useRef } from 'react';
4
4
  import { CameraHelper } from 'three';
5
5
  import { useFrame, useThree } from '@react-three/fiber';
6
- import { useEntityRuntime } from '../assetRuntime';
6
+ import { useNode } from '../assetRuntime';
7
7
  import { FieldGroup, NumberField, SelectField } from './Input';
8
8
  const CAMERA_PROJECTION_OPTIONS = [
9
9
  { value: 'perspective', label: 'Perspective' },
@@ -25,7 +25,7 @@ function CameraComponentEditor({ component, onUpdate }) {
25
25
  }
26
26
  function CameraComponentView({ properties, children }) {
27
27
  var _a;
28
- const { editMode, isSelected } = useEntityRuntime();
28
+ const { editMode, isSelected } = useNode();
29
29
  const { size } = useThree();
30
30
  const merged = Object.assign(Object.assign({}, cameraDefaults), properties);
31
31
  const projection = (_a = merged.projection) !== null && _a !== void 0 ? _a : cameraDefaults.projection;