react-three-game 0.0.109 → 0.0.110

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/README.md +4 -11
  2. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +46 -8
  3. package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +1 -1
  4. package/dist/plugins/crashcat/CrashcatRuntime.d.ts +1 -1
  5. package/dist/shared/ContactShadow.d.ts +1 -1
  6. package/dist/shared/GameCanvas.d.ts +1 -1
  7. package/dist/tools/assetviewer/page.d.ts +10 -10
  8. package/dist/tools/dragdrop/DragDropLoader.d.ts +2 -2
  9. package/dist/tools/prefabeditor/Dropdown.d.ts +1 -1
  10. package/dist/tools/prefabeditor/EditorTree.d.ts +1 -1
  11. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -2
  12. package/dist/tools/prefabeditor/EditorUI.d.ts +1 -1
  13. package/dist/tools/prefabeditor/InstanceProvider.d.ts +1 -1
  14. package/dist/tools/prefabeditor/PrefabEditor.js +67 -51
  15. package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -2
  16. package/dist/tools/prefabeditor/PrefabRoot.js +73 -208
  17. package/dist/tools/prefabeditor/SceneProvider.d.ts +14 -0
  18. package/dist/tools/prefabeditor/SceneProvider.js +68 -0
  19. package/dist/tools/prefabeditor/assetRuntime.d.ts +25 -2
  20. package/dist/tools/prefabeditor/assetRuntime.js +115 -1
  21. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +3 -3
  22. package/dist/tools/prefabeditor/components/Input.d.ts +19 -19
  23. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +1 -1
  24. package/dist/tools/prefabeditor/components/MaterialComponent.js +13 -14
  25. package/dist/tools/prefabeditor/components/ModelComponent.js +2 -3
  26. package/dist/tools/prefabeditor/components/PrefabRefComponent.js +39 -15
  27. package/dist/tools/prefabeditor/components/SoundComponent.js +6 -2
  28. package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -5
  29. package/dist/tools/prefabeditor/components/lightUtils.d.ts +2 -2
  30. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
  31. package/dist/tools/prefabeditor/prefabStore.js +11 -0
  32. package/dist/viewer.d.ts +11 -12
  33. package/dist/viewer.js +7 -9
  34. package/package.json +9 -9
  35. package/dist/tools/prefabeditor/components/runtime.d.ts +0 -4
  36. package/dist/tools/prefabeditor/components/runtime.js +0 -372
@@ -19,22 +19,36 @@ var __rest = (this && this.__rest) || function (s, e) {
19
19
  return t;
20
20
  };
21
21
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
22
- import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
22
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
23
23
  import { Euler, Matrix4 } from "three";
24
- import { useThree } from "@react-three/fiber";
25
24
  import { useStore } from "zustand";
26
25
  import { useClickValid } from "./useClickValid";
27
26
  import { findComponent, getNodeUserData } from "./types";
28
- import { getComponentDef } from "./components/ComponentRegistry";
27
+ import { getComponentDef, registerComponent } from "./components/ComponentRegistry";
28
+ import { builtinComponents } from "./components";
29
29
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
30
30
  import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties } from "./InstanceProvider";
31
31
  import { composeTransform, decompose, withBasePath } from "./runtimeUtils";
32
- import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
33
- import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
32
+ import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStoreApi } from "./prefabStore";
33
+ import { AssetRuntimeProvider, NodeScope, useAllModels, useAssetRuntime } from "./assetRuntime";
34
34
  import { gameEvents } from "./GameEvents";
35
- import { sound as soundManager } from "../../helpers/SoundManager";
36
- import { PrefabEditorMode, SceneContext } from "./SceneContext";
35
+ import { useScene } from "./SceneContext";
36
+ import { SceneProvider } from "./SceneProvider";
37
37
  const IDENTITY = new Matrix4();
38
+ // Reusable scratch objects for buildRepeatedInstances. The matrices are pure
39
+ // intermediates (their results are read out into plain arrays per instance),
40
+ // so they can be shared across calls instead of allocating fresh Matrix4s in
41
+ // nested loops for every repeated instance.
42
+ const _scratchTranslation = new Matrix4();
43
+ const _scratchRotation = new Matrix4();
44
+ const _scratchScale = new Matrix4();
45
+ const _scratchOffset = new Matrix4();
46
+ const _scratchWorld = new Matrix4();
47
+ const _scratchEuler = new Euler();
48
+ builtinComponents.forEach((component) => {
49
+ if (!getComponentDef(component.name))
50
+ registerComponent(component);
51
+ });
38
52
  const EMPTY_NODE_COMPONENTS = {
39
53
  geometry: undefined,
40
54
  materials: [],
@@ -43,20 +57,6 @@ const EMPTY_NODE_COMPONENTS = {
43
57
  clickEventName: null,
44
58
  composition: [],
45
59
  };
46
- function precompileModel(model, renderer, camera) {
47
- return __awaiter(this, void 0, void 0, function* () {
48
- try {
49
- if (typeof renderer.compileAsync === "function") {
50
- yield renderer.compileAsync(model, camera);
51
- return;
52
- }
53
- renderer.compile(model, camera);
54
- }
55
- catch (error) {
56
- console.warn("Failed to precompile model before adding it to the scene", error);
57
- }
58
- });
59
- }
60
60
  /** Check if a model component's assets are loaded. */
61
61
  function isNodeReady(model, loadedModels) {
62
62
  var _a;
@@ -72,21 +72,8 @@ function getNodeMetadataProps(node) {
72
72
  userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
73
73
  };
74
74
  }
75
- export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
76
- const parentScene = useContext(SceneContext);
77
- const parentAssetRuntime = useContext(AssetRuntimeContext);
78
- const renderer = useThree(state => state.gl);
79
- const camera = useThree(state => state.camera);
80
- const [models, setModels] = useState({});
81
- const [textures, setTextures] = useState({});
82
- const [sounds, setSounds] = useState({});
83
- const loading = useRef(new Set());
84
- const failedModels = useRef(new Set());
85
- const failedTextures = useRef(new Set());
86
- const failedSounds = useRef(new Set());
87
- const injectedModelVersions = useRef({});
88
- const objectRefs = useRef({});
89
- const nodeHandles = useRef(new Map());
75
+ export const PrefabRoot = forwardRef((props, ref) => {
76
+ const { data, store } = props;
90
77
  const [ownedStore] = useState(() => {
91
78
  if (store)
92
79
  return null;
@@ -95,198 +82,76 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
95
82
  throw new Error("PrefabRoot requires either a `data` or `store` prop");
96
83
  });
97
84
  const resolvedStore = store !== null && store !== void 0 ? store : ownedStore;
98
- if (!resolvedStore) {
85
+ if (!resolvedStore)
99
86
  throw new Error("PrefabRoot requires either a `data` or `store` prop");
100
- }
101
- const usesOwnedStore = resolvedStore === ownedStore;
102
- const rootId = useStore(resolvedStore, state => state.rootId);
103
- const assetRefCounts = useStore(resolvedStore, state => state.assetRefCounts);
104
- const getModel = useCallback((path) => { var _a, _b; return (_b = (_a = models[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getModel(path)) !== null && _b !== void 0 ? _b : null; }, [models, parentAssetRuntime]);
105
- const getTexture = useCallback((path) => { var _a, _b; return (_b = (_a = textures[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getTexture(path)) !== null && _b !== void 0 ? _b : null; }, [parentAssetRuntime, textures]);
106
- const getSound = useCallback((path) => { var _a, _b; return (_b = (_a = sounds[path]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getSound(path)) !== null && _b !== void 0 ? _b : null; }, [parentAssetRuntime, sounds]);
107
- const assetRevision = useMemo(() => { var _a; return `${(_a = parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getAssetRevision()) !== null && _a !== void 0 ? _a : ""}::${Object.keys(textures).sort().join('|')}::${Object.keys(models).sort().join('|')}`; }, [models, parentAssetRuntime, textures]);
108
- const getObject = useCallback((id) => {
109
- var _a, _b;
110
- return (_b = (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getObject(id)) !== null && _b !== void 0 ? _b : null;
111
- }, [parentAssetRuntime]);
112
- const getHandle = useCallback((id, kind) => {
113
- var _a, _b, _c;
114
- return (_c = (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getHandle(id, kind)) !== null && _c !== void 0 ? _c : null;
115
- }, [parentAssetRuntime]);
116
- const getNode = useCallback((nodeId) => {
117
- var _a;
118
- return (_a = resolvedStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null;
119
- }, [resolvedStore]);
120
- const registerHandle = useCallback((id, kind, handle) => {
121
- const current = nodeHandles.current.get(id);
122
- if (handle == null) {
123
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, null);
124
- if (!current)
125
- return;
126
- current.delete(kind);
127
- if (current.size === 0) {
128
- nodeHandles.current.delete(id);
129
- }
130
- return;
131
- }
132
- if (current) {
133
- current.set(kind, handle);
134
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, handle);
135
- return;
136
- }
137
- nodeHandles.current.set(id, new Map([[kind, handle]]));
138
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerHandle(id, kind, handle);
139
- }, [parentAssetRuntime]);
140
- const registerObject = useCallback((id, obj) => {
141
- if (obj) {
142
- objectRefs.current[id] = obj;
143
- }
144
- else {
145
- delete objectRefs.current[id];
146
- }
147
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerObject(id, obj);
148
- }, [parentAssetRuntime]);
149
- const registerModel = useCallback((path, model) => {
150
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerModel(path, model);
151
- setModels(prev => prev[path] === model ? prev : Object.assign(Object.assign({}, prev), { [path]: model }));
152
- }, [parentAssetRuntime]);
153
- const registerTexture = useCallback((path, texture) => {
154
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerTexture(path, texture);
155
- setTextures(prev => prev[path] === texture ? prev : Object.assign(Object.assign({}, prev), { [path]: texture }));
156
- }, [parentAssetRuntime]);
157
- const registerSound = useCallback((path, sound) => {
158
- parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.registerSound(path, sound);
159
- soundManager.setBuffer(path, sound);
160
- setSounds(prev => prev[path] === sound ? prev : Object.assign(Object.assign({}, prev), { [path]: sound }));
161
- }, [parentAssetRuntime]);
162
- const sceneValue = useMemo(() => ({
163
- get root() {
164
- var _a;
165
- return (_a = objectRefs.current[rootId]) !== null && _a !== void 0 ? _a : null;
166
- },
167
- mode: editMode ? PrefabEditorMode.Edit : PrefabEditorMode.Play,
168
- basePath,
169
- get: getNode,
170
- getObject,
171
- getHandle,
172
- getModel,
173
- add: (node, parentId) => {
174
- const state = resolvedStore.getState();
175
- state.addChild(parentId !== null && parentId !== void 0 ? parentId : state.rootId, node);
176
- return node;
177
- },
178
- update: (id, fn) => resolvedStore.getState().updateNode(id, fn),
179
- replaceNode: (id, node) => resolvedStore.getState().replaceNode(id, node),
180
- remove: (id) => resolvedStore.getState().deleteNode(id),
181
- duplicate: (id) => resolvedStore.getState().duplicateNode(id),
182
- move: (draggedId, targetId, position) => resolvedStore.getState().moveNode(draggedId, targetId, position),
183
- replace: (prefab) => resolvedStore.getState().replacePrefab(prefab),
184
- addModel: (path, model) => {
185
- var _a;
186
- const version = ((_a = injectedModelVersions.current[path]) !== null && _a !== void 0 ? _a : 0) + 1;
187
- injectedModelVersions.current[path] = version;
188
- void precompileModel(model, renderer, camera).then(() => {
189
- if (injectedModelVersions.current[path] !== version)
190
- return;
191
- registerModel(path, model);
192
- });
193
- },
194
- addTexture: registerTexture,
195
- addSound: registerSound,
196
- }), [basePath, camera, editMode, getHandle, getModel, getNode, getObject, registerModel, registerSound, registerTexture, renderer, resolvedStore, rootId]);
197
- useImperativeHandle(ref, () => sceneValue, [sceneValue]);
198
87
  useEffect(() => {
199
- if (usesOwnedStore && data) {
88
+ if (!store && data)
200
89
  resolvedStore.getState().replacePrefab(data);
201
- }
202
- }, [data, resolvedStore, usesOwnedStore]);
90
+ }, [data, resolvedStore, store]);
91
+ return (_jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(AssetRuntimeProvider, { children: _jsx(SceneProvider, { store: resolvedStore, editMode: props.editMode, basePath: props.basePath, children: _jsx(PrefabRootBody, Object.assign({ ref: ref }, props)) }) }) }));
92
+ });
93
+ const PrefabRootBody = forwardRef(({ editMode, selectedId, onSelect, onClick, onEditNodeClick, basePath = "", children }, ref) => {
94
+ const scene = useScene();
95
+ const runtime = useAssetRuntime();
96
+ const models = useAllModels();
97
+ const storeApi = usePrefabStoreApi();
98
+ const assetRefCounts = useStore(storeApi, state => state.assetRefCounts);
99
+ useImperativeHandle(ref, () => scene, [scene]);
100
+ const loading = useRef(new Set());
101
+ const failed = useRef(new Set());
203
102
  useEffect(() => {
204
- const loadAsset = (file, loaded, failed, loader) => {
205
- if (loaded[file] || loading.current.has(file) || failed.has(file))
103
+ const tryLoad = (key, hasLoaded, run) => {
104
+ if (hasLoaded || loading.current.has(key) || failed.current.has(key))
206
105
  return;
207
- loading.current.add(file);
208
- void loader(withBasePath(basePath, file)).then(result => {
209
- loading.current.delete(file);
106
+ loading.current.add(key);
107
+ void run().then(result => {
108
+ loading.current.delete(key);
210
109
  if (!result.success) {
211
- console.warn(`Failed to load asset: ${file}`, result.error);
212
- failed.add(file);
110
+ console.warn(`Failed to load asset: ${key}`, result.error);
111
+ failed.current.add(key);
213
112
  }
214
113
  });
215
114
  };
216
115
  Object.keys(assetRefCounts).forEach(entry => {
217
- const separator = entry.indexOf(':');
218
- const type = entry.slice(0, separator);
219
- const file = entry.slice(separator + 1);
116
+ const sep = entry.indexOf(':');
117
+ const type = entry.slice(0, sep);
118
+ const file = entry.slice(sep + 1);
119
+ const path = withBasePath(basePath, file);
220
120
  if (type === 'model') {
221
- const inheritedModel = models[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getModel(file);
222
- if (inheritedModel) {
223
- registerModel(file, inheritedModel);
224
- return;
225
- }
226
- loadAsset(file, models, failedModels.current, path => loadModel(path).then((result) => __awaiter(void 0, void 0, void 0, function* () {
227
- const loadedModel = result.model;
228
- if (result.success && loadedModel) {
229
- yield precompileModel(loadedModel, renderer, camera);
230
- registerModel(file, loadedModel);
231
- }
232
- return result;
233
- })));
121
+ tryLoad(entry, !!runtime.getModel(file), () => __awaiter(void 0, void 0, void 0, function* () {
122
+ const r = yield loadModel(path);
123
+ if (r.success && r.model)
124
+ runtime.registerModel(file, r.model);
125
+ return r;
126
+ }));
234
127
  }
235
128
  else if (type === 'texture') {
236
- const inheritedTexture = textures[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getTexture(file);
237
- if (inheritedTexture) {
238
- registerTexture(file, inheritedTexture);
239
- return;
240
- }
241
- loadAsset(file, textures, failedTextures.current, path => loadTexture(path).then(result => {
242
- const loadedTexture = result.texture;
243
- if (result.success && loadedTexture) {
244
- registerTexture(file, loadedTexture);
245
- }
246
- return result;
129
+ tryLoad(entry, !!runtime.getTexture(file), () => __awaiter(void 0, void 0, void 0, function* () {
130
+ const r = yield loadTexture(path);
131
+ if (r.success && r.texture)
132
+ runtime.registerTexture(file, r.texture);
133
+ return r;
247
134
  }));
248
135
  }
249
136
  else if (type === 'sound') {
250
- const inheritedSound = sounds[file] ? null : parentAssetRuntime === null || parentAssetRuntime === void 0 ? void 0 : parentAssetRuntime.getSound(file);
251
- if (inheritedSound) {
252
- registerSound(file, inheritedSound);
253
- return;
254
- }
255
- loadAsset(file, sounds, failedSounds.current, path => loadSound(path).then(result => {
256
- const loadedSound = result.sound;
257
- if (result.success && loadedSound) {
258
- registerSound(file, loadedSound);
259
- }
260
- return result;
137
+ tryLoad(entry, !!runtime.getSound(file), () => __awaiter(void 0, void 0, void 0, function* () {
138
+ const r = yield loadSound(path);
139
+ if (r.success && r.sound)
140
+ runtime.registerSound(file, r.sound);
141
+ return r;
261
142
  }));
262
143
  }
263
144
  });
264
- }, [assetRefCounts, basePath, camera, models, parentAssetRuntime, registerModel, registerSound, registerTexture, renderer, sounds, textures]);
265
- const assetRuntime = useMemo(() => ({
266
- registerObject,
267
- registerHandle,
268
- registerModel,
269
- registerTexture,
270
- registerSound,
271
- getHandle,
272
- getObject,
273
- getModel,
274
- getTexture,
275
- getSound,
276
- getAssetRevision: () => assetRevision,
277
- }), [registerObject, registerHandle, registerModel, registerTexture, registerSound, getHandle, getObject, getModel, getTexture, getSound, assetRevision]);
145
+ }, [assetRefCounts, basePath, runtime]);
278
146
  const handleNodeClick = useCallback((event, nodeId, fallbackObject) => {
279
- const node = resolvedStore.getState().nodesById[nodeId];
147
+ const node = storeApi.getState().nodesById[nodeId];
280
148
  if (!node)
281
149
  return;
282
150
  const { clickEventName } = analyzeNodeComponents(node);
283
151
  emitNodePointerEvent(clickEventName, event, nodeId, node, fallbackObject);
284
152
  onClick === null || onClick === void 0 ? void 0 : onClick(event, node);
285
- }, [onClick, resolvedStore]);
286
- const content = (_jsxs(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: registerObject, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: registerObject, loadedModels: models, editMode: editMode, parentMatrix: IDENTITY, basePath: basePath }), children] }));
287
- const runtimeContent = parentAssetRuntime ? content : (_jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }));
288
- const sceneContent = parentScene ? runtimeContent : (_jsx(SceneContext.Provider, { value: sceneValue, children: runtimeContent }));
289
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: sceneContent });
153
+ }, [onClick, storeApi]);
154
+ return (_jsxs(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, registerRef: runtime.registerObject, children: [_jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: editMode ? undefined : handleNodeClick, onEditNodeClick: editMode ? onEditNodeClick : undefined, registerRef: runtime.registerObject, loadedModels: models, editMode: editMode, parentMatrix: IDENTITY, basePath: basePath }), children] }));
290
155
  });
291
156
  function StoreRootNode(props) {
292
157
  const rootId = usePrefabRootId();
@@ -502,11 +367,11 @@ function buildRepeatedInstances(gameObject, parentMatrix, modelUrl) {
502
367
  offsets[axisIndex] = entry.offset;
503
368
  }
504
369
  }
505
- const baseTranslation = new Matrix4().makeTranslation(transform.position[0], transform.position[1], transform.position[2]);
506
- const baseRotation = new Matrix4().makeRotationFromEuler(new Euler(...transform.rotation));
507
- const baseScale = new Matrix4().makeScale(transform.scale[0], transform.scale[1], transform.scale[2]);
508
- const offsetMatrix = new Matrix4();
509
- const worldMatrix = new Matrix4();
370
+ const baseTranslation = _scratchTranslation.makeTranslation(transform.position[0], transform.position[1], transform.position[2]);
371
+ const baseRotation = _scratchRotation.makeRotationFromEuler(_scratchEuler.set(transform.rotation[0], transform.rotation[1], transform.rotation[2]));
372
+ const baseScale = _scratchScale.makeScale(transform.scale[0], transform.scale[1], transform.scale[2]);
373
+ const offsetMatrix = _scratchOffset;
374
+ const worldMatrix = _scratchWorld;
510
375
  const instances = [];
511
376
  for (let x = 0; x < counts[0]; x++) {
512
377
  for (let y = 0; y < counts[1]; y++) {
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ import type { PrefabStoreApi } from "./prefabStore";
3
+ export interface SceneProviderProps {
4
+ store: PrefabStoreApi;
5
+ editMode?: boolean;
6
+ basePath?: string;
7
+ children: ReactNode;
8
+ }
9
+ /**
10
+ * Recursive provider: if a Scene is already present above, this is a
11
+ * pass-through. Otherwise this layer becomes the owner and builds a default
12
+ * Scene bound to the given store + asset runtime.
13
+ */
14
+ export declare function SceneProvider({ store, editMode, basePath, children }: SceneProviderProps): import("react").JSX.Element;
@@ -0,0 +1,68 @@
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
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
11
+ import { useContext, useMemo } from "react";
12
+ import { useThree } from "@react-three/fiber";
13
+ import { useStore } from "zustand";
14
+ import { PrefabEditorMode, SceneContext } from "./SceneContext";
15
+ import { useAssetRuntime } from "./assetRuntime";
16
+ /**
17
+ * Recursive provider: if a Scene is already present above, this is a
18
+ * pass-through. Otherwise this layer becomes the owner and builds a default
19
+ * Scene bound to the given store + asset runtime.
20
+ */
21
+ export function SceneProvider({ store, editMode, basePath = "", children }) {
22
+ const inherited = useContext(SceneContext);
23
+ if (inherited !== null)
24
+ return _jsx(_Fragment, { children: children });
25
+ return (_jsx(SceneOwner, { store: store, editMode: editMode, basePath: basePath, children: children }));
26
+ }
27
+ function SceneOwner({ store, editMode, basePath, children }) {
28
+ const runtime = useAssetRuntime();
29
+ const renderer = useThree(s => s.gl);
30
+ const camera = useThree(s => s.camera);
31
+ const rootId = useStore(store, s => s.rootId);
32
+ const scene = useMemo(() => ({
33
+ get root() { return runtime.getObject(rootId); },
34
+ mode: editMode ? PrefabEditorMode.Edit : PrefabEditorMode.Play,
35
+ basePath,
36
+ get: (id) => { var _a; return (_a = store.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
37
+ getObject: runtime.getObject,
38
+ getHandle: runtime.getHandle,
39
+ getModel: runtime.getModel,
40
+ add: (node, parentId) => {
41
+ const s = store.getState();
42
+ s.addChild(parentId !== null && parentId !== void 0 ? parentId : s.rootId, node);
43
+ return node;
44
+ },
45
+ update: (id, fn) => store.getState().updateNode(id, fn),
46
+ replaceNode: (id, node) => store.getState().replaceNode(id, node),
47
+ remove: (id) => store.getState().deleteNode(id),
48
+ duplicate: (id) => store.getState().duplicateNode(id),
49
+ move: (a, b, p) => store.getState().moveNode(a, b, p),
50
+ replace: (p) => store.getState().replacePrefab(p),
51
+ addModel: (path, model) => {
52
+ void precompile(model, renderer, camera).then(() => runtime.registerModel(path, model));
53
+ },
54
+ addTexture: runtime.registerTexture,
55
+ addSound: runtime.registerSound,
56
+ }), [store, editMode, basePath, runtime, rootId, renderer, camera]);
57
+ return _jsx(SceneContext.Provider, { value: scene, children: children });
58
+ }
59
+ function precompile(model, renderer, camera) {
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ try {
62
+ yield renderer.compileAsync(model, camera);
63
+ }
64
+ catch (error) {
65
+ console.warn("Failed to precompile model before adding it to the scene", error);
66
+ }
67
+ });
68
+ }
@@ -1,6 +1,10 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { Object3D, Texture } from "three";
3
+ import type { LoadedModels, LoadedSounds, LoadedTextures } from "../dragdrop";
3
4
  export interface AssetRuntime {
5
+ models: LoadedModels;
6
+ textures: LoadedTextures;
7
+ sounds: LoadedSounds;
4
8
  registerObject: (id: string, object: Object3D | null) => void;
5
9
  registerHandle: (id: string, kind: string, handle: unknown) => void;
6
10
  registerModel: (path: string, model: Object3D) => void;
@@ -10,7 +14,7 @@ export interface AssetRuntime {
10
14
  getModel: (path: string) => Object3D | null;
11
15
  getTexture: (path: string) => Texture | null;
12
16
  getSound: (path: string) => AudioBuffer | null;
13
- getAssetRevision: () => string;
17
+ getAssetRevision: () => number;
14
18
  getObject: (id: string) => Object3D | null;
15
19
  }
16
20
  export interface NodeApi {
@@ -23,7 +27,21 @@ export interface NodeApi {
23
27
  export interface LiveRef<T> {
24
28
  readonly current: T | null;
25
29
  }
30
+ export interface AssetRuntimeProviderProps {
31
+ children: ReactNode;
32
+ runtimeRef?: React.MutableRefObject<AssetRuntime | null>;
33
+ }
26
34
  export declare const AssetRuntimeContext: import("react").Context<AssetRuntime | null>;
35
+ /** Subscribe to a single loaded model; re-renders only when that model changes. */
36
+ export declare function useModelAsset(path?: string | null): Object3D | null;
37
+ /** Subscribe to a single loaded texture; re-renders only when that texture changes. */
38
+ export declare function useTextureAsset(path?: string | null): Texture | null;
39
+ /** Subscribe to a single loaded sound; re-renders only when that sound changes. */
40
+ export declare function useSoundAsset(path?: string | null): AudioBuffer | null;
41
+ /** Subscribe to the full model map (only needed by the instancing root). */
42
+ export declare function useAllModels(): LoadedModels;
43
+ /** Coarse "an asset was (un)registered" signal. */
44
+ export declare function useAssetRevision(): number;
27
45
  export declare function useAssetRuntime(): AssetRuntime;
28
46
  export declare function useNode(): NodeApi;
29
47
  export declare function useNodeObject<T extends Object3D = Object3D>(): LiveRef<T>;
@@ -33,4 +51,9 @@ export declare function NodeScope({ nodeId, editMode, isSelected, children, }: {
33
51
  editMode?: boolean;
34
52
  isSelected?: boolean;
35
53
  children: ReactNode;
36
- }): import("react/jsx-runtime").JSX.Element;
54
+ }): import("react").JSX.Element;
55
+ /**
56
+ * Recursive provider: if an AssetRuntime is already present above, this is a
57
+ * pass-through. Otherwise this layer becomes the owner and allocates state.
58
+ */
59
+ export declare function AssetRuntimeProvider({ children, runtimeRef }: AssetRuntimeProviderProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
@@ -1,7 +1,40 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useMemo } from "react";
2
+ import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
3
+ import { useStore } from "zustand";
4
+ import { createStore } from "zustand/vanilla";
5
+ import { sound as soundManager } from "../../helpers/SoundManager";
3
6
  export const AssetRuntimeContext = createContext(null);
4
7
  const NodeContext = createContext(null);
8
+ function createAssetStore() {
9
+ return createStore(() => ({ models: {}, textures: {}, sounds: {}, version: 0 }));
10
+ }
11
+ const AssetStoreContext = createContext(null);
12
+ function useAssetStore() {
13
+ const store = useContext(AssetStoreContext);
14
+ if (!store)
15
+ throw new Error("Asset hooks must be used inside <PrefabRoot>");
16
+ return store;
17
+ }
18
+ /** Subscribe to a single loaded model; re-renders only when that model changes. */
19
+ export function useModelAsset(path) {
20
+ return useStore(useAssetStore(), s => { var _a; return (path ? (_a = s.models[path]) !== null && _a !== void 0 ? _a : null : null); });
21
+ }
22
+ /** Subscribe to a single loaded texture; re-renders only when that texture changes. */
23
+ export function useTextureAsset(path) {
24
+ return useStore(useAssetStore(), s => { var _a; return (path ? (_a = s.textures[path]) !== null && _a !== void 0 ? _a : null : null); });
25
+ }
26
+ /** Subscribe to a single loaded sound; re-renders only when that sound changes. */
27
+ export function useSoundAsset(path) {
28
+ return useStore(useAssetStore(), s => { var _a; return (path ? (_a = s.sounds[path]) !== null && _a !== void 0 ? _a : null : null); });
29
+ }
30
+ /** Subscribe to the full model map (only needed by the instancing root). */
31
+ export function useAllModels() {
32
+ return useStore(useAssetStore(), s => s.models);
33
+ }
34
+ /** Coarse "an asset was (un)registered" signal. */
35
+ export function useAssetRevision() {
36
+ return useStore(useAssetStore(), s => s.version);
37
+ }
5
38
  export function useAssetRuntime() {
6
39
  const ctx = useContext(AssetRuntimeContext);
7
40
  if (!ctx)
@@ -35,3 +68,84 @@ export function NodeScope({ nodeId, editMode, isSelected, children, }) {
35
68
  }), [asset, editMode, isSelected, nodeId]);
36
69
  return _jsx(NodeContext.Provider, { value: value, children: children });
37
70
  }
71
+ /**
72
+ * Recursive provider: if an AssetRuntime is already present above, this is a
73
+ * pass-through. Otherwise this layer becomes the owner and allocates state.
74
+ */
75
+ export function AssetRuntimeProvider({ children, runtimeRef }) {
76
+ const inherited = useContext(AssetRuntimeContext);
77
+ if (inherited !== null) {
78
+ if (runtimeRef)
79
+ runtimeRef.current = inherited;
80
+ return children;
81
+ }
82
+ return _jsx(AssetRuntimeOwner, { runtimeRef: runtimeRef, children: children });
83
+ }
84
+ function AssetRuntimeOwner({ children, runtimeRef }) {
85
+ const [assetStore] = useState(createAssetStore);
86
+ const objectRefs = useRef({});
87
+ const nodeHandles = useRef(new Map());
88
+ const registerObject = useCallback((id, obj) => {
89
+ if (obj)
90
+ objectRefs.current[id] = obj;
91
+ else
92
+ delete objectRefs.current[id];
93
+ }, []);
94
+ const registerHandle = useCallback((id, kind, handle) => {
95
+ const current = nodeHandles.current.get(id);
96
+ if (handle == null) {
97
+ if (!current)
98
+ return;
99
+ current.delete(kind);
100
+ if (current.size === 0)
101
+ nodeHandles.current.delete(id);
102
+ return;
103
+ }
104
+ if (current) {
105
+ current.set(kind, handle);
106
+ return;
107
+ }
108
+ nodeHandles.current.set(id, new Map([[kind, handle]]));
109
+ }, []);
110
+ const registerModel = useCallback((path, model) => {
111
+ if (assetStore.getState().models[path] === model)
112
+ return;
113
+ assetStore.setState(s => ({ models: Object.assign(Object.assign({}, s.models), { [path]: model }), version: s.version + 1 }));
114
+ }, [assetStore]);
115
+ const registerTexture = useCallback((path, texture) => {
116
+ if (assetStore.getState().textures[path] === texture)
117
+ return;
118
+ assetStore.setState(s => ({ textures: Object.assign(Object.assign({}, s.textures), { [path]: texture }), version: s.version + 1 }));
119
+ }, [assetStore]);
120
+ const registerSound = useCallback((path, sound) => {
121
+ soundManager.setBuffer(path, sound);
122
+ if (assetStore.getState().sounds[path] === sound)
123
+ return;
124
+ assetStore.setState(s => ({ sounds: Object.assign(Object.assign({}, s.sounds), { [path]: sound }), version: s.version + 1 }));
125
+ }, [assetStore]);
126
+ const getObject = useCallback((id) => { var _a; return (_a = objectRefs.current[id]) !== null && _a !== void 0 ? _a : null; }, []);
127
+ const getHandle = useCallback((id, kind) => {
128
+ var _a, _b;
129
+ return (_b = (_a = nodeHandles.current.get(id)) === null || _a === void 0 ? void 0 : _a.get(kind)) !== null && _b !== void 0 ? _b : null;
130
+ }, []);
131
+ const getModel = useCallback((path) => { var _a; return (_a = assetStore.getState().models[path]) !== null && _a !== void 0 ? _a : null; }, [assetStore]);
132
+ const getTexture = useCallback((path) => { var _a; return (_a = assetStore.getState().textures[path]) !== null && _a !== void 0 ? _a : null; }, [assetStore]);
133
+ const getSound = useCallback((path) => { var _a; return (_a = assetStore.getState().sounds[path]) !== null && _a !== void 0 ? _a : null; }, [assetStore]);
134
+ const getAssetRevision = useCallback(() => assetStore.getState().version, [assetStore]);
135
+ // Stable runtime: all members have stable identity, so consumers that only
136
+ // use imperative getters/registrars never re-render on asset loads. The
137
+ // live maps are exposed as snapshot getters for imperative readers; reactive
138
+ // consumers use the selector hooks (useModelAsset, useAllModels, ...).
139
+ const runtime = useMemo(() => ({
140
+ get models() { return assetStore.getState().models; },
141
+ get textures() { return assetStore.getState().textures; },
142
+ get sounds() { return assetStore.getState().sounds; },
143
+ registerObject, registerHandle,
144
+ registerModel, registerTexture, registerSound,
145
+ getObject, getHandle, getModel, getTexture, getSound,
146
+ getAssetRevision,
147
+ }), [assetStore, registerObject, registerHandle, registerModel, registerTexture, registerSound, getObject, getHandle, getModel, getTexture, getSound, getAssetRevision]);
148
+ if (runtimeRef)
149
+ runtimeRef.current = runtime;
150
+ return (_jsx(AssetStoreContext.Provider, { value: assetStore, children: _jsx(AssetRuntimeContext.Provider, { value: runtime, children: children }) }));
151
+ }
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Environment } from '@react-three/drei';
3
3
  import { FieldGroup, NumberField } from './Input';
4
- import { useAssetRuntime } from '../assetRuntime';
4
+ import { useAssetRevision } from '../assetRuntime';
5
5
  function EnvironmentView({ properties, children, }) {
6
- const { getAssetRevision } = useAssetRuntime();
6
+ const assetRevision = useAssetRevision();
7
7
  const { intensity = 1, resolution = 256 } = properties;
8
- const environmentRevision = `${getAssetRevision()}::${intensity}::${resolution}`;
8
+ const environmentRevision = `${assetRevision}::${intensity}::${resolution}`;
9
9
  return (_jsx(Environment, { background: true, environmentIntensity: intensity, resolution: resolution, frames: 1, children: children }, environmentRevision));
10
10
  }
11
11
  const EnvironmentComponent = {