@react-three/fiber 10.0.0-alpha.1 → 10.0.0-canary.164c7be
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.
- package/LICENSE +21 -21
- package/dist/index.cjs +1256 -1419
- package/dist/index.d.cts +1887 -1699
- package/dist/index.d.mts +1887 -1699
- package/dist/index.d.ts +1887 -1699
- package/dist/index.mjs +1216 -1398
- package/dist/legacy.cjs +1222 -1410
- package/dist/legacy.d.cts +1888 -1700
- package/dist/legacy.d.mts +1888 -1700
- package/dist/legacy.d.ts +1888 -1700
- package/dist/legacy.mjs +1182 -1389
- package/dist/webgpu/index.cjs +1937 -1470
- package/dist/webgpu/index.d.cts +2145 -1722
- package/dist/webgpu/index.d.mts +2145 -1722
- package/dist/webgpu/index.d.ts +2145 -1722
- package/dist/webgpu/index.mjs +1890 -1449
- package/package.json +8 -5
- package/react-reconciler/constants.js +1 -9
- package/react-reconciler/index.js +4 -20
- package/readme.md +244 -318
package/dist/webgpu/index.mjs
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as webgpu from 'three/webgpu';
|
|
2
|
-
import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1,
|
|
2
|
+
import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, Vector4, PostProcessing } from 'three/webgpu';
|
|
3
3
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
4
|
-
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
import * as React from 'react';
|
|
6
|
-
import React__default, {
|
|
6
|
+
import React__default, { useLayoutEffect, useRef, useMemo, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
|
|
7
7
|
import useMeasure from 'react-use-measure';
|
|
8
8
|
import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
|
|
9
|
+
import { useThree as useThree$1, useLoader as useLoader$1, useFrame as useFrame$1, createPortal as createPortal$1, applyProps as applyProps$1, extend as extend$1 } from '@react-three/fiber';
|
|
10
|
+
import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
|
|
11
|
+
import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
|
|
12
|
+
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
|
|
13
|
+
import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
|
|
14
|
+
import { GainMapLoader } from '@monogrid/gainmap-js';
|
|
9
15
|
import Tb, { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
|
|
10
16
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
17
|
+
import { getScheduler } from '@pmndrs/scheduler';
|
|
18
|
+
export { Scheduler, getScheduler } from '@pmndrs/scheduler';
|
|
11
19
|
import { suspend, preload, clear } from 'suspend-react';
|
|
12
20
|
import { dequal } from 'dequal/lite';
|
|
13
21
|
import 'zustand/shallow';
|
|
@@ -46,9 +54,392 @@ const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
|
46
54
|
WebGLRenderer: WebGLRenderer
|
|
47
55
|
}, [webgpu]);
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
const primaryRegistry = /* @__PURE__ */ new Map();
|
|
58
|
+
const pendingSubscribers = /* @__PURE__ */ new Map();
|
|
59
|
+
function registerPrimary(id, renderer, store) {
|
|
60
|
+
if (primaryRegistry.has(id)) {
|
|
61
|
+
console.warn(`Canvas with id="${id}" already registered. Overwriting.`);
|
|
62
|
+
}
|
|
63
|
+
const entry = { renderer, store };
|
|
64
|
+
primaryRegistry.set(id, entry);
|
|
65
|
+
const subscribers = pendingSubscribers.get(id);
|
|
66
|
+
if (subscribers) {
|
|
67
|
+
subscribers.forEach((callback) => callback(entry));
|
|
68
|
+
pendingSubscribers.delete(id);
|
|
69
|
+
}
|
|
70
|
+
return () => {
|
|
71
|
+
const currentEntry = primaryRegistry.get(id);
|
|
72
|
+
if (currentEntry?.renderer === renderer) {
|
|
73
|
+
primaryRegistry.delete(id);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function getPrimary(id) {
|
|
78
|
+
return primaryRegistry.get(id);
|
|
79
|
+
}
|
|
80
|
+
function waitForPrimary(id, timeout = 5e3) {
|
|
81
|
+
const existing = primaryRegistry.get(id);
|
|
82
|
+
if (existing) {
|
|
83
|
+
return Promise.resolve(existing);
|
|
84
|
+
}
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timeoutId = setTimeout(() => {
|
|
87
|
+
const subscribers = pendingSubscribers.get(id);
|
|
88
|
+
if (subscribers) {
|
|
89
|
+
const index = subscribers.indexOf(callback);
|
|
90
|
+
if (index !== -1) subscribers.splice(index, 1);
|
|
91
|
+
if (subscribers.length === 0) pendingSubscribers.delete(id);
|
|
92
|
+
}
|
|
93
|
+
reject(new Error(`Timeout waiting for canvas with id="${id}". Make sure a <Canvas id="${id}"> is mounted.`));
|
|
94
|
+
}, timeout);
|
|
95
|
+
const callback = (entry) => {
|
|
96
|
+
clearTimeout(timeoutId);
|
|
97
|
+
resolve(entry);
|
|
98
|
+
};
|
|
99
|
+
if (!pendingSubscribers.has(id)) {
|
|
100
|
+
pendingSubscribers.set(id, []);
|
|
101
|
+
}
|
|
102
|
+
pendingSubscribers.get(id).push(callback);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function hasPrimary(id) {
|
|
106
|
+
return primaryRegistry.has(id);
|
|
107
|
+
}
|
|
108
|
+
function unregisterPrimary(id) {
|
|
109
|
+
primaryRegistry.delete(id);
|
|
110
|
+
}
|
|
111
|
+
function getPrimaryIds() {
|
|
112
|
+
return Array.from(primaryRegistry.keys());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const presetsObj = {
|
|
116
|
+
apartment: "lebombo_1k.hdr",
|
|
117
|
+
city: "potsdamer_platz_1k.hdr",
|
|
118
|
+
dawn: "kiara_1_dawn_1k.hdr",
|
|
119
|
+
forest: "forest_slope_1k.hdr",
|
|
120
|
+
lobby: "st_fagans_interior_1k.hdr",
|
|
121
|
+
night: "dikhololo_night_1k.hdr",
|
|
122
|
+
park: "rooitou_park_1k.hdr",
|
|
123
|
+
studio: "studio_small_03_1k.hdr",
|
|
124
|
+
sunset: "venice_sunset_1k.hdr",
|
|
125
|
+
warehouse: "empty_warehouse_01_1k.hdr"
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const CUBEMAP_ROOT = "https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/";
|
|
129
|
+
const isArray = (arr) => Array.isArray(arr);
|
|
130
|
+
const defaultFiles = ["/px.png", "/nx.png", "/py.png", "/ny.png", "/pz.png", "/nz.png"];
|
|
131
|
+
function useEnvironment({
|
|
132
|
+
files = defaultFiles,
|
|
133
|
+
path = "",
|
|
134
|
+
preset = void 0,
|
|
135
|
+
colorSpace = void 0,
|
|
136
|
+
extensions
|
|
137
|
+
} = {}) {
|
|
138
|
+
if (preset) {
|
|
139
|
+
validatePreset(preset);
|
|
140
|
+
files = presetsObj[preset];
|
|
141
|
+
path = CUBEMAP_ROOT;
|
|
142
|
+
}
|
|
143
|
+
const multiFile = isArray(files);
|
|
144
|
+
const { extension, isCubemap } = getExtension(files);
|
|
145
|
+
const loader = getLoader$1(extension);
|
|
146
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
147
|
+
const renderer = useThree$1((state) => state.renderer);
|
|
148
|
+
useLayoutEffect(() => {
|
|
149
|
+
if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
|
|
150
|
+
function clearGainmapTexture() {
|
|
151
|
+
useLoader$1.clear(loader, multiFile ? [files] : files);
|
|
152
|
+
}
|
|
153
|
+
renderer.domElement.addEventListener("webglcontextlost", clearGainmapTexture, { once: true });
|
|
154
|
+
}, [extension, files, loader, multiFile, renderer.domElement]);
|
|
155
|
+
const loaderResult = useLoader$1(
|
|
156
|
+
loader,
|
|
157
|
+
multiFile ? [files] : files,
|
|
158
|
+
(loader2) => {
|
|
159
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
160
|
+
loader2.setRenderer?.(renderer);
|
|
161
|
+
}
|
|
162
|
+
loader2.setPath?.(path);
|
|
163
|
+
if (extensions) extensions(loader2);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
let texture = multiFile ? (
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
loaderResult[0]
|
|
169
|
+
) : loaderResult;
|
|
170
|
+
if (extension === "jpg" || extension === "jpeg" || extension === "webp") {
|
|
171
|
+
texture = texture.renderTarget?.texture;
|
|
172
|
+
}
|
|
173
|
+
texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
|
|
174
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
|
|
175
|
+
return texture;
|
|
176
|
+
}
|
|
177
|
+
const preloadDefaultOptions = {
|
|
178
|
+
files: defaultFiles,
|
|
179
|
+
path: "",
|
|
180
|
+
preset: void 0,
|
|
181
|
+
extensions: void 0
|
|
182
|
+
};
|
|
183
|
+
useEnvironment.preload = (preloadOptions) => {
|
|
184
|
+
const options = { ...preloadDefaultOptions, ...preloadOptions };
|
|
185
|
+
let { files, path = "" } = options;
|
|
186
|
+
const { preset, extensions } = options;
|
|
187
|
+
if (preset) {
|
|
188
|
+
validatePreset(preset);
|
|
189
|
+
files = presetsObj[preset];
|
|
190
|
+
path = CUBEMAP_ROOT;
|
|
191
|
+
}
|
|
192
|
+
const { extension } = getExtension(files);
|
|
193
|
+
if (extension === "webp" || extension === "jpg" || extension === "jpeg") {
|
|
194
|
+
throw new Error("useEnvironment: Preloading gainmaps is not supported");
|
|
195
|
+
}
|
|
196
|
+
const loader = getLoader$1(extension);
|
|
197
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
198
|
+
useLoader$1.preload(loader, isArray(files) ? [files] : files, (loader2) => {
|
|
199
|
+
loader2.setPath?.(path);
|
|
200
|
+
if (extensions) extensions(loader2);
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
const clearDefaultOptins = {
|
|
204
|
+
files: defaultFiles,
|
|
205
|
+
preset: void 0
|
|
206
|
+
};
|
|
207
|
+
useEnvironment.clear = (clearOptions) => {
|
|
208
|
+
const options = { ...clearDefaultOptins, ...clearOptions };
|
|
209
|
+
let { files } = options;
|
|
210
|
+
const { preset } = options;
|
|
211
|
+
if (preset) {
|
|
212
|
+
validatePreset(preset);
|
|
213
|
+
files = presetsObj[preset];
|
|
214
|
+
}
|
|
215
|
+
const { extension } = getExtension(files);
|
|
216
|
+
const loader = getLoader$1(extension);
|
|
217
|
+
if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
|
|
218
|
+
useLoader$1.clear(loader, isArray(files) ? [files] : files);
|
|
219
|
+
};
|
|
220
|
+
function validatePreset(preset) {
|
|
221
|
+
if (!(preset in presetsObj)) throw new Error("Preset must be one of: " + Object.keys(presetsObj).join(", "));
|
|
222
|
+
}
|
|
223
|
+
function getExtension(files) {
|
|
224
|
+
const isCubemap = isArray(files) && files.length === 6;
|
|
225
|
+
const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
|
|
226
|
+
const firstEntry = isArray(files) ? files[0] : files;
|
|
227
|
+
const extension = isCubemap ? "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
|
|
228
|
+
return { extension, isCubemap, isGainmap };
|
|
229
|
+
}
|
|
230
|
+
function getLoader$1(extension) {
|
|
231
|
+
const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
|
|
232
|
+
return loader;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const isRef$1 = (obj) => obj.current && obj.current.isScene;
|
|
236
|
+
const resolveScene = (scene) => isRef$1(scene) ? scene.current : scene;
|
|
237
|
+
function setEnvProps(background, scene, defaultScene, texture, sceneProps = {}) {
|
|
238
|
+
sceneProps = {
|
|
239
|
+
backgroundBlurriness: 0,
|
|
240
|
+
backgroundIntensity: 1,
|
|
241
|
+
backgroundRotation: [0, 0, 0],
|
|
242
|
+
environmentIntensity: 1,
|
|
243
|
+
environmentRotation: [0, 0, 0],
|
|
244
|
+
...sceneProps
|
|
245
|
+
};
|
|
246
|
+
const target = resolveScene(scene || defaultScene);
|
|
247
|
+
const oldbg = target.background;
|
|
248
|
+
const oldenv = target.environment;
|
|
249
|
+
const oldSceneProps = {
|
|
250
|
+
// @ts-ignore
|
|
251
|
+
backgroundBlurriness: target.backgroundBlurriness,
|
|
252
|
+
// @ts-ignore
|
|
253
|
+
backgroundIntensity: target.backgroundIntensity,
|
|
254
|
+
// @ts-ignore
|
|
255
|
+
backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
|
|
256
|
+
// @ts-ignore
|
|
257
|
+
environmentIntensity: target.environmentIntensity,
|
|
258
|
+
// @ts-ignore
|
|
259
|
+
environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
|
|
260
|
+
};
|
|
261
|
+
if (background !== "only") target.environment = texture;
|
|
262
|
+
if (background) target.background = texture;
|
|
263
|
+
applyProps$1(target, sceneProps);
|
|
264
|
+
return () => {
|
|
265
|
+
if (background !== "only") target.environment = oldenv;
|
|
266
|
+
if (background) target.background = oldbg;
|
|
267
|
+
applyProps$1(target, oldSceneProps);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function EnvironmentMap({ scene, background = false, map, ...config }) {
|
|
271
|
+
const defaultScene = useThree$1((state) => state.scene);
|
|
272
|
+
React.useLayoutEffect(() => {
|
|
273
|
+
if (map) return setEnvProps(background, scene, defaultScene, map, config);
|
|
274
|
+
});
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
function EnvironmentCube({
|
|
278
|
+
background = false,
|
|
279
|
+
scene,
|
|
280
|
+
blur,
|
|
281
|
+
backgroundBlurriness,
|
|
282
|
+
backgroundIntensity,
|
|
283
|
+
backgroundRotation,
|
|
284
|
+
environmentIntensity,
|
|
285
|
+
environmentRotation,
|
|
286
|
+
...rest
|
|
287
|
+
}) {
|
|
288
|
+
const texture = useEnvironment(rest);
|
|
289
|
+
const defaultScene = useThree$1((state) => state.scene);
|
|
290
|
+
React.useLayoutEffect(() => {
|
|
291
|
+
return setEnvProps(background, scene, defaultScene, texture, {
|
|
292
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
293
|
+
backgroundIntensity,
|
|
294
|
+
backgroundRotation,
|
|
295
|
+
environmentIntensity,
|
|
296
|
+
environmentRotation
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
React.useEffect(() => {
|
|
300
|
+
return () => {
|
|
301
|
+
texture.dispose();
|
|
302
|
+
};
|
|
303
|
+
}, [texture]);
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
function EnvironmentPortal({
|
|
307
|
+
children,
|
|
308
|
+
near = 0.1,
|
|
309
|
+
far = 1e3,
|
|
310
|
+
resolution = 256,
|
|
311
|
+
frames = 1,
|
|
312
|
+
map,
|
|
313
|
+
background = false,
|
|
314
|
+
blur,
|
|
315
|
+
backgroundBlurriness,
|
|
316
|
+
backgroundIntensity,
|
|
317
|
+
backgroundRotation,
|
|
318
|
+
environmentIntensity,
|
|
319
|
+
environmentRotation,
|
|
320
|
+
scene,
|
|
321
|
+
files,
|
|
322
|
+
path,
|
|
323
|
+
preset = void 0,
|
|
324
|
+
extensions
|
|
325
|
+
}) {
|
|
326
|
+
const gl = useThree$1((state) => state.gl);
|
|
327
|
+
const defaultScene = useThree$1((state) => state.scene);
|
|
328
|
+
const camera = React.useRef(null);
|
|
329
|
+
const [virtualScene] = React.useState(() => new Scene());
|
|
330
|
+
const fbo = React.useMemo(() => {
|
|
331
|
+
const fbo2 = new WebGLCubeRenderTarget(resolution);
|
|
332
|
+
fbo2.texture.type = HalfFloatType;
|
|
333
|
+
return fbo2;
|
|
334
|
+
}, [resolution]);
|
|
335
|
+
React.useEffect(() => {
|
|
336
|
+
return () => {
|
|
337
|
+
fbo.dispose();
|
|
338
|
+
};
|
|
339
|
+
}, [fbo]);
|
|
340
|
+
React.useLayoutEffect(() => {
|
|
341
|
+
if (frames === 1) {
|
|
342
|
+
const autoClear = gl.autoClear;
|
|
343
|
+
gl.autoClear = true;
|
|
344
|
+
camera.current.update(gl, virtualScene);
|
|
345
|
+
gl.autoClear = autoClear;
|
|
346
|
+
}
|
|
347
|
+
return setEnvProps(background, scene, defaultScene, fbo.texture, {
|
|
348
|
+
backgroundBlurriness: blur ?? backgroundBlurriness,
|
|
349
|
+
backgroundIntensity,
|
|
350
|
+
backgroundRotation,
|
|
351
|
+
environmentIntensity,
|
|
352
|
+
environmentRotation
|
|
353
|
+
});
|
|
354
|
+
}, [
|
|
355
|
+
children,
|
|
356
|
+
virtualScene,
|
|
357
|
+
fbo.texture,
|
|
358
|
+
scene,
|
|
359
|
+
defaultScene,
|
|
360
|
+
background,
|
|
361
|
+
frames,
|
|
362
|
+
gl,
|
|
363
|
+
blur,
|
|
364
|
+
backgroundBlurriness,
|
|
365
|
+
backgroundIntensity,
|
|
366
|
+
backgroundRotation,
|
|
367
|
+
environmentIntensity,
|
|
368
|
+
environmentRotation
|
|
369
|
+
]);
|
|
370
|
+
let count = 1;
|
|
371
|
+
useFrame$1(() => {
|
|
372
|
+
if (frames === Infinity || count < frames) {
|
|
373
|
+
const autoClear = gl.autoClear;
|
|
374
|
+
gl.autoClear = true;
|
|
375
|
+
camera.current.update(gl, virtualScene);
|
|
376
|
+
gl.autoClear = autoClear;
|
|
377
|
+
count++;
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
return /* @__PURE__ */ jsx(Fragment, { children: createPortal$1(
|
|
381
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
382
|
+
children,
|
|
383
|
+
/* @__PURE__ */ jsx("cubeCamera", { ref: camera, args: [near, far, fbo] }),
|
|
384
|
+
files || preset ? /* @__PURE__ */ jsx(EnvironmentCube, { background: true, files, preset, path, extensions }) : map ? /* @__PURE__ */ jsx(EnvironmentMap, { background: true, map, extensions }) : null
|
|
385
|
+
] }),
|
|
386
|
+
virtualScene
|
|
387
|
+
) });
|
|
388
|
+
}
|
|
389
|
+
function EnvironmentGround(props) {
|
|
390
|
+
const textureDefault = useEnvironment(props);
|
|
391
|
+
const texture = props.map || textureDefault;
|
|
392
|
+
React.useMemo(() => extend$1({ GroundProjectedEnvImpl: GroundedSkybox }), []);
|
|
393
|
+
React.useEffect(() => {
|
|
394
|
+
return () => {
|
|
395
|
+
textureDefault.dispose();
|
|
396
|
+
};
|
|
397
|
+
}, [textureDefault]);
|
|
398
|
+
const height = props.ground?.height ?? 15;
|
|
399
|
+
const radius = props.ground?.radius ?? 60;
|
|
400
|
+
const scale = props.ground?.scale ?? 1e3;
|
|
401
|
+
const args = React.useMemo(
|
|
402
|
+
() => [texture, height, radius],
|
|
403
|
+
[texture, height, radius]
|
|
404
|
+
);
|
|
405
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
406
|
+
/* @__PURE__ */ jsx(EnvironmentMap, { ...props, map: texture }),
|
|
407
|
+
/* @__PURE__ */ jsx("groundProjectedEnvImpl", { args, scale })
|
|
408
|
+
] });
|
|
409
|
+
}
|
|
410
|
+
function EnvironmentColor({ color, scene }) {
|
|
411
|
+
const defaultScene = useThree$1((state) => state.scene);
|
|
412
|
+
React.useLayoutEffect(() => {
|
|
413
|
+
if (color === void 0) return;
|
|
414
|
+
const target = resolveScene(scene || defaultScene);
|
|
415
|
+
const oldBg = target.background;
|
|
416
|
+
target.background = new Color(color);
|
|
417
|
+
return () => {
|
|
418
|
+
target.background = oldBg;
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
function EnvironmentDualSource(props) {
|
|
424
|
+
const { backgroundFiles, ...envProps } = props;
|
|
425
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
426
|
+
/* @__PURE__ */ jsx(EnvironmentCube, { ...envProps, background: false }),
|
|
427
|
+
/* @__PURE__ */ jsx(EnvironmentCube, { ...props, files: backgroundFiles, background: "only" })
|
|
428
|
+
] });
|
|
429
|
+
}
|
|
430
|
+
function Environment(props) {
|
|
431
|
+
if (props.color && !props.files && !props.preset && !props.map) {
|
|
432
|
+
return /* @__PURE__ */ jsx(EnvironmentColor, { ...props });
|
|
433
|
+
}
|
|
434
|
+
if (props.backgroundFiles && props.backgroundFiles !== props.files) {
|
|
435
|
+
return /* @__PURE__ */ jsx(EnvironmentDualSource, { ...props });
|
|
436
|
+
}
|
|
437
|
+
return props.ground ? /* @__PURE__ */ jsx(EnvironmentGround, { ...props }) : props.map ? /* @__PURE__ */ jsx(EnvironmentMap, { ...props }) : props.children ? /* @__PURE__ */ jsx(EnvironmentPortal, { ...props }) : /* @__PURE__ */ jsx(EnvironmentCube, { ...props });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
var __defProp$1 = Object.defineProperty;
|
|
441
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
442
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
52
443
|
const act = React["act"];
|
|
53
444
|
const useIsomorphicLayoutEffect = /* @__PURE__ */ (() => typeof window !== "undefined" && (window.document?.createElement || window.navigator?.product === "ReactNative"))() ? React.useLayoutEffect : React.useEffect;
|
|
54
445
|
function useMutableCallback(fn) {
|
|
@@ -80,7 +471,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
80
471
|
return _a = class extends React.Component {
|
|
81
472
|
constructor() {
|
|
82
473
|
super(...arguments);
|
|
83
|
-
__publicField$
|
|
474
|
+
__publicField$1(this, "state", { error: false });
|
|
84
475
|
}
|
|
85
476
|
componentDidCatch(err) {
|
|
86
477
|
this.props.set(err);
|
|
@@ -88,7 +479,7 @@ const ErrorBoundary = /* @__PURE__ */ (() => {
|
|
|
88
479
|
render() {
|
|
89
480
|
return this.state.error ? null : this.props.children;
|
|
90
481
|
}
|
|
91
|
-
}, __publicField$
|
|
482
|
+
}, __publicField$1(_a, "getDerivedStateFromError", () => ({ error: true })), _a;
|
|
92
483
|
})();
|
|
93
484
|
|
|
94
485
|
const is = {
|
|
@@ -225,7 +616,8 @@ function prepare(target, root, type, props) {
|
|
|
225
616
|
object,
|
|
226
617
|
eventCount: 0,
|
|
227
618
|
handlers: {},
|
|
228
|
-
isHidden: false
|
|
619
|
+
isHidden: false,
|
|
620
|
+
deferredRefs: []
|
|
229
621
|
};
|
|
230
622
|
if (object) object.__r3f = instance;
|
|
231
623
|
}
|
|
@@ -274,7 +666,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
274
666
|
let occlusionSetupPromise = null;
|
|
275
667
|
function enableOcclusion(store) {
|
|
276
668
|
const state = store.getState();
|
|
277
|
-
const { internal, renderer
|
|
669
|
+
const { internal, renderer } = state;
|
|
278
670
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
279
671
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
280
672
|
if (!hasOcclusionSupport) {
|
|
@@ -437,6 +829,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
437
829
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
438
830
|
}
|
|
439
831
|
|
|
832
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
833
|
+
function fromRef(ref) {
|
|
834
|
+
return { [FROM_REF]: ref };
|
|
835
|
+
}
|
|
836
|
+
function isFromRef(value) {
|
|
837
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
841
|
+
function once(...args) {
|
|
842
|
+
return { [ONCE]: args.length ? args : true };
|
|
843
|
+
}
|
|
844
|
+
function isOnce(value) {
|
|
845
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
846
|
+
}
|
|
847
|
+
|
|
440
848
|
const RESERVED_PROPS = [
|
|
441
849
|
"children",
|
|
442
850
|
"key",
|
|
@@ -507,7 +915,7 @@ function getMemoizedPrototype(root) {
|
|
|
507
915
|
ctor = new root.constructor();
|
|
508
916
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
509
917
|
}
|
|
510
|
-
} catch
|
|
918
|
+
} catch {
|
|
511
919
|
}
|
|
512
920
|
return ctor;
|
|
513
921
|
}
|
|
@@ -538,7 +946,7 @@ function applyProps(object, props) {
|
|
|
538
946
|
const rootState = instance && findInitialRoot(instance).getState();
|
|
539
947
|
const prevHandlers = instance?.eventCount;
|
|
540
948
|
for (const prop in props) {
|
|
541
|
-
|
|
949
|
+
const value = props[prop];
|
|
542
950
|
if (RESERVED_PROPS.includes(prop)) continue;
|
|
543
951
|
if (instance && EVENT_REGEX.test(prop)) {
|
|
544
952
|
if (typeof value === "function") instance.handlers[prop] = value;
|
|
@@ -553,6 +961,25 @@ function applyProps(object, props) {
|
|
|
553
961
|
continue;
|
|
554
962
|
}
|
|
555
963
|
if (value === void 0) continue;
|
|
964
|
+
if (isFromRef(value)) {
|
|
965
|
+
instance?.deferredRefs?.push({ prop, ref: value[FROM_REF] });
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (isOnce(value)) {
|
|
969
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
970
|
+
if (instance) {
|
|
971
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
972
|
+
instance.appliedOnce.add(prop);
|
|
973
|
+
}
|
|
974
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
975
|
+
const args = value[ONCE];
|
|
976
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
977
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
978
|
+
} else if (args !== true && args.length > 0) {
|
|
979
|
+
targetRoot[targetKey] = args[0];
|
|
980
|
+
}
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
556
983
|
let { root, key, target } = resolve(object, prop);
|
|
557
984
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
558
985
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -575,7 +1002,10 @@ function applyProps(object, props) {
|
|
|
575
1002
|
else target.set(value);
|
|
576
1003
|
} else {
|
|
577
1004
|
root[key] = value;
|
|
578
|
-
if (
|
|
1005
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1006
|
+
root.needsUpdate = true;
|
|
1007
|
+
}
|
|
1008
|
+
if (rootState && rootState.renderer?.outputColorSpace === SRGBColorSpace && colorMaps.includes(key) && isTexture(value) && root[key]?.isTexture && // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
|
|
579
1009
|
root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
|
|
580
1010
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
581
1011
|
}
|
|
@@ -608,38 +1038,60 @@ function applyProps(object, props) {
|
|
|
608
1038
|
return object;
|
|
609
1039
|
}
|
|
610
1040
|
|
|
1041
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1042
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1043
|
+
function getPointerState(internal, pointerId) {
|
|
1044
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1045
|
+
if (!state) {
|
|
1046
|
+
state = {
|
|
1047
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1048
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1049
|
+
initialClick: [0, 0],
|
|
1050
|
+
initialHits: []
|
|
1051
|
+
};
|
|
1052
|
+
internal.pointerMap.set(pointerId, state);
|
|
1053
|
+
}
|
|
1054
|
+
return state;
|
|
1055
|
+
}
|
|
1056
|
+
function getPointerId(event) {
|
|
1057
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1058
|
+
}
|
|
611
1059
|
function makeId(event) {
|
|
612
1060
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
613
1061
|
}
|
|
614
|
-
function releaseInternalPointerCapture(
|
|
615
|
-
const
|
|
1062
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1063
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1064
|
+
if (!pointerState) return;
|
|
1065
|
+
const captureData = pointerState.captured.get(obj);
|
|
616
1066
|
if (captureData) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
capturedMap.delete(pointerId);
|
|
620
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
621
|
-
}
|
|
1067
|
+
pointerState.captured.delete(obj);
|
|
1068
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
622
1069
|
}
|
|
623
1070
|
}
|
|
624
1071
|
function removeInteractivity(store, object) {
|
|
625
1072
|
const { internal } = store.getState();
|
|
626
1073
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1074
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1075
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1076
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1077
|
+
if (value.eventObject === object || value.object === object) {
|
|
1078
|
+
pointerState.hovered.delete(key);
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
if (pointerState.captured.has(object)) {
|
|
1082
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
631
1083
|
}
|
|
632
|
-
}
|
|
633
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
634
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
635
|
-
});
|
|
1084
|
+
}
|
|
636
1085
|
unregisterVisibility(store, object);
|
|
637
1086
|
}
|
|
638
1087
|
function createEvents(store) {
|
|
639
|
-
function calculateDistance(event) {
|
|
1088
|
+
function calculateDistance(event, pointerId) {
|
|
640
1089
|
const { internal } = store.getState();
|
|
641
|
-
const
|
|
642
|
-
|
|
1090
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1091
|
+
if (!pointerState) return 0;
|
|
1092
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1093
|
+
const dx = event.offsetX - initialX;
|
|
1094
|
+
const dy = event.offsetY - initialY;
|
|
643
1095
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
644
1096
|
}
|
|
645
1097
|
function filterPointerEvents(objects) {
|
|
@@ -675,6 +1127,15 @@ function createEvents(store) {
|
|
|
675
1127
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
676
1128
|
}
|
|
677
1129
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1130
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1131
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1132
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1133
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1134
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1135
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1136
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
678
1139
|
const aState = getRootState(a.object);
|
|
679
1140
|
const bState = getRootState(b.object);
|
|
680
1141
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -690,14 +1151,19 @@ function createEvents(store) {
|
|
|
690
1151
|
for (const hit of hits) {
|
|
691
1152
|
let eventObject = hit.object;
|
|
692
1153
|
while (eventObject) {
|
|
693
|
-
if (eventObject.__r3f?.eventCount)
|
|
1154
|
+
if (eventObject.__r3f?.eventCount) {
|
|
694
1155
|
intersections.push({ ...hit, eventObject });
|
|
1156
|
+
}
|
|
695
1157
|
eventObject = eventObject.parent;
|
|
696
1158
|
}
|
|
697
1159
|
}
|
|
698
|
-
if ("pointerId" in event
|
|
699
|
-
|
|
700
|
-
|
|
1160
|
+
if ("pointerId" in event) {
|
|
1161
|
+
const pointerId = event.pointerId;
|
|
1162
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1163
|
+
if (pointerState?.captured.size) {
|
|
1164
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1165
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1166
|
+
}
|
|
701
1167
|
}
|
|
702
1168
|
}
|
|
703
1169
|
return intersections;
|
|
@@ -710,28 +1176,26 @@ function createEvents(store) {
|
|
|
710
1176
|
if (state) {
|
|
711
1177
|
const { raycaster, pointer, camera, internal } = state;
|
|
712
1178
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
713
|
-
const hasPointerCapture = (id) =>
|
|
1179
|
+
const hasPointerCapture = (id) => {
|
|
1180
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1181
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1182
|
+
};
|
|
714
1183
|
const setPointerCapture = (id) => {
|
|
715
1184
|
const captureData = { intersection: hit, target: event.target };
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
} else {
|
|
719
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
720
|
-
}
|
|
1185
|
+
const pointerState = getPointerState(internal, id);
|
|
1186
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
721
1187
|
event.target.setPointerCapture(id);
|
|
722
1188
|
};
|
|
723
1189
|
const releasePointerCapture = (id) => {
|
|
724
|
-
|
|
725
|
-
if (captures) {
|
|
726
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
727
|
-
}
|
|
1190
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
728
1191
|
};
|
|
729
|
-
|
|
730
|
-
for (
|
|
731
|
-
|
|
1192
|
+
const extractEventProps = {};
|
|
1193
|
+
for (const prop in event) {
|
|
1194
|
+
const property = event[prop];
|
|
732
1195
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
733
1196
|
}
|
|
734
|
-
|
|
1197
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1198
|
+
const raycastEvent = {
|
|
735
1199
|
...hit,
|
|
736
1200
|
...extractEventProps,
|
|
737
1201
|
pointer,
|
|
@@ -741,18 +1205,19 @@ function createEvents(store) {
|
|
|
741
1205
|
unprojectedPoint,
|
|
742
1206
|
ray: raycaster.ray,
|
|
743
1207
|
camera,
|
|
1208
|
+
pointerId: eventPointerId,
|
|
744
1209
|
// Hijack stopPropagation, which just sets a flag
|
|
745
1210
|
stopPropagation() {
|
|
746
|
-
const
|
|
1211
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
747
1212
|
if (
|
|
748
1213
|
// ...if this pointer hasn't been captured
|
|
749
|
-
!
|
|
750
|
-
|
|
1214
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1215
|
+
pointerState.captured.has(hit.eventObject)
|
|
751
1216
|
) {
|
|
752
1217
|
raycastEvent.stopped = localState.stopped = true;
|
|
753
|
-
if (
|
|
1218
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
754
1219
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
755
|
-
cancelPointer([...higher, hit]);
|
|
1220
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
756
1221
|
}
|
|
757
1222
|
}
|
|
758
1223
|
},
|
|
@@ -768,15 +1233,18 @@ function createEvents(store) {
|
|
|
768
1233
|
}
|
|
769
1234
|
return intersections;
|
|
770
1235
|
}
|
|
771
|
-
function cancelPointer(intersections) {
|
|
1236
|
+
function cancelPointer(intersections, pointerId) {
|
|
772
1237
|
const { internal } = store.getState();
|
|
773
|
-
|
|
1238
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1239
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1240
|
+
if (!pointerState) return;
|
|
1241
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
774
1242
|
if (!intersections.length || !intersections.find(
|
|
775
1243
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
776
1244
|
)) {
|
|
777
1245
|
const eventObject = hoveredObj.eventObject;
|
|
778
1246
|
const instance = eventObject.__r3f;
|
|
779
|
-
|
|
1247
|
+
pointerState.hovered.delete(hoveredId);
|
|
780
1248
|
if (instance?.eventCount) {
|
|
781
1249
|
const handlers = instance.handlers;
|
|
782
1250
|
const data = { ...hoveredObj, intersections };
|
|
@@ -805,41 +1273,118 @@ function createEvents(store) {
|
|
|
805
1273
|
instance?.handlers.onDropMissed?.(event);
|
|
806
1274
|
}
|
|
807
1275
|
}
|
|
1276
|
+
function cleanupPointer(pointerId) {
|
|
1277
|
+
const { internal } = store.getState();
|
|
1278
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1279
|
+
if (pointerState) {
|
|
1280
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1281
|
+
const eventObject = hoveredObj.eventObject;
|
|
1282
|
+
const instance = eventObject.__r3f;
|
|
1283
|
+
if (instance?.eventCount) {
|
|
1284
|
+
const handlers = instance.handlers;
|
|
1285
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1286
|
+
handlers.onPointerOut?.(data);
|
|
1287
|
+
handlers.onPointerLeave?.(data);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
internal.pointerMap.delete(pointerId);
|
|
1291
|
+
}
|
|
1292
|
+
internal.pointerDirty.delete(pointerId);
|
|
1293
|
+
}
|
|
1294
|
+
function processDeferredPointer(event, pointerId) {
|
|
1295
|
+
const state = store.getState();
|
|
1296
|
+
const { internal } = state;
|
|
1297
|
+
if (!state.events.enabled) return;
|
|
1298
|
+
const filter = filterPointerEvents;
|
|
1299
|
+
const hits = intersect(event, filter);
|
|
1300
|
+
cancelPointer(hits, pointerId);
|
|
1301
|
+
function onIntersect(data) {
|
|
1302
|
+
const eventObject = data.eventObject;
|
|
1303
|
+
const instance = eventObject.__r3f;
|
|
1304
|
+
if (!instance?.eventCount) return;
|
|
1305
|
+
const handlers = instance.handlers;
|
|
1306
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1307
|
+
const id = makeId(data);
|
|
1308
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1309
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1310
|
+
if (!hoveredItem) {
|
|
1311
|
+
pointerState.hovered.set(id, data);
|
|
1312
|
+
handlers.onPointerOver?.(data);
|
|
1313
|
+
handlers.onPointerEnter?.(data);
|
|
1314
|
+
} else if (hoveredItem.stopped) {
|
|
1315
|
+
data.stopPropagation();
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
handlers.onPointerMove?.(data);
|
|
1319
|
+
}
|
|
1320
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1321
|
+
}
|
|
808
1322
|
function handlePointer(name) {
|
|
809
1323
|
switch (name) {
|
|
810
1324
|
case "onPointerLeave":
|
|
811
|
-
case "onPointerCancel":
|
|
812
1325
|
case "onDragLeave":
|
|
813
1326
|
return () => cancelPointer([]);
|
|
1327
|
+
// Global cancel of these events
|
|
1328
|
+
case "onPointerCancel":
|
|
1329
|
+
return (event) => {
|
|
1330
|
+
const pointerId = getPointerId(event);
|
|
1331
|
+
cleanupPointer(pointerId);
|
|
1332
|
+
};
|
|
814
1333
|
case "onLostPointerCapture":
|
|
815
1334
|
return (event) => {
|
|
816
1335
|
const { internal } = store.getState();
|
|
817
|
-
|
|
1336
|
+
const pointerId = getPointerId(event);
|
|
1337
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1338
|
+
if (pointerState?.captured.size) {
|
|
818
1339
|
requestAnimationFrame(() => {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1340
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1341
|
+
if (pointerState2?.captured.size) {
|
|
1342
|
+
pointerState2.captured.clear();
|
|
822
1343
|
}
|
|
1344
|
+
cancelPointer([], pointerId);
|
|
823
1345
|
});
|
|
824
1346
|
}
|
|
825
1347
|
};
|
|
826
1348
|
}
|
|
827
1349
|
return function handleEvent(event) {
|
|
828
1350
|
const state = store.getState();
|
|
829
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1351
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1352
|
+
const pointerId = getPointerId(event);
|
|
830
1353
|
internal.lastEvent.current = event;
|
|
831
|
-
if (!
|
|
1354
|
+
if (!events.enabled) return;
|
|
832
1355
|
const isPointerMove = name === "onPointerMove";
|
|
833
1356
|
const isDragOver = name === "onDragOver";
|
|
834
1357
|
const isDrop = name === "onDrop";
|
|
835
1358
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1359
|
+
const isPointerDown = name === "onPointerDown";
|
|
1360
|
+
const isPointerUp = name === "onPointerUp";
|
|
1361
|
+
const isWheel = name === "onWheel";
|
|
1362
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1363
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1364
|
+
events.compute?.(event, state);
|
|
1365
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1369
|
+
events.compute?.(event, state);
|
|
1370
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1374
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1375
|
+
internal.pointerDirty.delete(pointerId);
|
|
1376
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1377
|
+
}
|
|
836
1378
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
837
1379
|
const hits = intersect(event, filter);
|
|
838
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1380
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1381
|
+
if (isPointerDown) {
|
|
1382
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1383
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1384
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1385
|
+
}
|
|
1386
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1387
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
843
1388
|
if (isClickEvent && !hits.length) {
|
|
844
1389
|
if (delta <= 2) {
|
|
845
1390
|
pointerMissed(event, internal.interaction);
|
|
@@ -854,7 +1399,9 @@ function createEvents(store) {
|
|
|
854
1399
|
dropMissed(event, internal.interaction);
|
|
855
1400
|
if (onDropMissed) onDropMissed(event);
|
|
856
1401
|
}
|
|
857
|
-
if (isPointerMove || isDragOver)
|
|
1402
|
+
if (isPointerMove || isDragOver) {
|
|
1403
|
+
cancelPointer(hits, pointerId);
|
|
1404
|
+
}
|
|
858
1405
|
function onIntersect(data) {
|
|
859
1406
|
const eventObject = data.eventObject;
|
|
860
1407
|
const instance = eventObject.__r3f;
|
|
@@ -863,9 +1410,10 @@ function createEvents(store) {
|
|
|
863
1410
|
if (isPointerMove) {
|
|
864
1411
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
865
1412
|
const id = makeId(data);
|
|
866
|
-
const
|
|
1413
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1414
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
867
1415
|
if (!hoveredItem) {
|
|
868
|
-
|
|
1416
|
+
pointerState2.hovered.set(id, data);
|
|
869
1417
|
handlers.onPointerOver?.(data);
|
|
870
1418
|
handlers.onPointerEnter?.(data);
|
|
871
1419
|
} else if (hoveredItem.stopped) {
|
|
@@ -875,9 +1423,10 @@ function createEvents(store) {
|
|
|
875
1423
|
handlers.onPointerMove?.(data);
|
|
876
1424
|
} else if (isDragOver) {
|
|
877
1425
|
const id = makeId(data);
|
|
878
|
-
const
|
|
1426
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1427
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
879
1428
|
if (!hoveredItem) {
|
|
880
|
-
|
|
1429
|
+
pointerState2.hovered.set(id, data);
|
|
881
1430
|
handlers.onDragOverEnter?.(data);
|
|
882
1431
|
} else if (hoveredItem.stopped) {
|
|
883
1432
|
data.stopPropagation();
|
|
@@ -888,18 +1437,18 @@ function createEvents(store) {
|
|
|
888
1437
|
} else {
|
|
889
1438
|
const handler = handlers[name];
|
|
890
1439
|
if (handler) {
|
|
891
|
-
if (!isClickEvent ||
|
|
1440
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
892
1441
|
pointerMissed(
|
|
893
1442
|
event,
|
|
894
|
-
internal.interaction.filter((object) => !
|
|
1443
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
895
1444
|
);
|
|
896
1445
|
handler(data);
|
|
897
1446
|
}
|
|
898
1447
|
} else {
|
|
899
|
-
if (isClickEvent &&
|
|
1448
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
900
1449
|
pointerMissed(
|
|
901
1450
|
event,
|
|
902
|
-
internal.interaction.filter((object) => !
|
|
1451
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
903
1452
|
);
|
|
904
1453
|
}
|
|
905
1454
|
}
|
|
@@ -908,7 +1457,15 @@ function createEvents(store) {
|
|
|
908
1457
|
handleIntersects(hits, event, delta, onIntersect);
|
|
909
1458
|
};
|
|
910
1459
|
}
|
|
911
|
-
|
|
1460
|
+
function flushDeferredPointers() {
|
|
1461
|
+
const { internal, events } = store.getState();
|
|
1462
|
+
if (!events.frameTimedRaycasts) return;
|
|
1463
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1464
|
+
processDeferredPointer(event, pointerId);
|
|
1465
|
+
}
|
|
1466
|
+
internal.pointerDirty.clear();
|
|
1467
|
+
}
|
|
1468
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
912
1469
|
}
|
|
913
1470
|
const DOM_EVENTS = {
|
|
914
1471
|
onClick: ["click", false],
|
|
@@ -927,11 +1484,16 @@ const DOM_EVENTS = {
|
|
|
927
1484
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
928
1485
|
};
|
|
929
1486
|
function createPointerEvents(store) {
|
|
930
|
-
const { handlePointer } = createEvents(store);
|
|
1487
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1488
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1489
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
931
1490
|
return {
|
|
932
1491
|
priority: 1,
|
|
933
1492
|
enabled: true,
|
|
934
|
-
|
|
1493
|
+
frameTimedRaycasts: true,
|
|
1494
|
+
alwaysFireOnScroll: true,
|
|
1495
|
+
updateOnFrame: false,
|
|
1496
|
+
compute(event, state) {
|
|
935
1497
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
936
1498
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
937
1499
|
},
|
|
@@ -940,11 +1502,33 @@ function createPointerEvents(store) {
|
|
|
940
1502
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
941
1503
|
{}
|
|
942
1504
|
),
|
|
943
|
-
update: () => {
|
|
1505
|
+
update: (pointerId) => {
|
|
1506
|
+
const { events, internal } = store.getState();
|
|
1507
|
+
if (!events.handlers) return;
|
|
1508
|
+
if (pointerId !== void 0) {
|
|
1509
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1510
|
+
if (event) {
|
|
1511
|
+
internal.pointerDirty.delete(pointerId);
|
|
1512
|
+
processDeferredPointer(event, pointerId);
|
|
1513
|
+
} else if (internal.lastEvent?.current) {
|
|
1514
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1515
|
+
}
|
|
1516
|
+
} else {
|
|
1517
|
+
flushDeferredPointers();
|
|
1518
|
+
if (internal.lastEvent?.current) {
|
|
1519
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
flush: () => {
|
|
944
1524
|
const { events, internal } = store.getState();
|
|
945
|
-
|
|
1525
|
+
flushDeferredPointers();
|
|
1526
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1527
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1528
|
+
}
|
|
946
1529
|
},
|
|
947
1530
|
connect: (target) => {
|
|
1531
|
+
if (!target) return;
|
|
948
1532
|
const { set, events } = store.getState();
|
|
949
1533
|
events.disconnect?.();
|
|
950
1534
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -968,6 +1552,32 @@ function createPointerEvents(store) {
|
|
|
968
1552
|
}
|
|
969
1553
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
970
1554
|
}
|
|
1555
|
+
},
|
|
1556
|
+
registerPointer: (config) => {
|
|
1557
|
+
const pointerId = nextXRPointerId++;
|
|
1558
|
+
xrPointers.set(pointerId, config);
|
|
1559
|
+
const { internal } = store.getState();
|
|
1560
|
+
getPointerState(internal, pointerId);
|
|
1561
|
+
return pointerId;
|
|
1562
|
+
},
|
|
1563
|
+
unregisterPointer: (pointerId) => {
|
|
1564
|
+
xrPointers.delete(pointerId);
|
|
1565
|
+
const { internal } = store.getState();
|
|
1566
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1567
|
+
if (pointerState) {
|
|
1568
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1569
|
+
const eventObject = hoveredObj.eventObject;
|
|
1570
|
+
const instance = eventObject.__r3f;
|
|
1571
|
+
if (instance?.eventCount) {
|
|
1572
|
+
const handlers = instance.handlers;
|
|
1573
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1574
|
+
handlers.onPointerOut?.(data);
|
|
1575
|
+
handlers.onPointerLeave?.(data);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
internal.pointerMap.delete(pointerId);
|
|
1579
|
+
}
|
|
1580
|
+
internal.pointerDirty.delete(pointerId);
|
|
971
1581
|
}
|
|
972
1582
|
};
|
|
973
1583
|
}
|
|
@@ -1058,23 +1668,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1058
1668
|
set,
|
|
1059
1669
|
get,
|
|
1060
1670
|
// Mock objects that have to be configured
|
|
1671
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1672
|
+
primaryStore: null,
|
|
1061
1673
|
gl: null,
|
|
1062
1674
|
renderer: null,
|
|
1063
1675
|
camera: null,
|
|
1064
1676
|
frustum: new Frustum(),
|
|
1065
1677
|
autoUpdateFrustum: true,
|
|
1066
1678
|
raycaster: null,
|
|
1067
|
-
events: {
|
|
1679
|
+
events: {
|
|
1680
|
+
priority: 1,
|
|
1681
|
+
enabled: true,
|
|
1682
|
+
connected: false,
|
|
1683
|
+
frameTimedRaycasts: true,
|
|
1684
|
+
alwaysFireOnScroll: true,
|
|
1685
|
+
updateOnFrame: false
|
|
1686
|
+
},
|
|
1068
1687
|
scene: null,
|
|
1069
1688
|
rootScene: null,
|
|
1070
1689
|
xr: null,
|
|
1071
1690
|
inspector: null,
|
|
1072
1691
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1073
1692
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1074
|
-
|
|
1075
|
-
linear: false,
|
|
1076
|
-
flat: false,
|
|
1077
|
-
textureColorSpace: "srgb",
|
|
1693
|
+
textureColorSpace: SRGBColorSpace,
|
|
1078
1694
|
isLegacy: false,
|
|
1079
1695
|
webGPUSupported: false,
|
|
1080
1696
|
isNative: false,
|
|
@@ -1114,10 +1730,40 @@ const createStore = (invalidate, advance) => {
|
|
|
1114
1730
|
getCurrentViewport
|
|
1115
1731
|
},
|
|
1116
1732
|
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
1117
|
-
setSize: (width, height, top
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1733
|
+
setSize: (width, height, top, left) => {
|
|
1734
|
+
const state2 = get();
|
|
1735
|
+
if (width === void 0) {
|
|
1736
|
+
set({ _sizeImperative: false });
|
|
1737
|
+
if (state2._sizeProps) {
|
|
1738
|
+
const { width: propW, height: propH } = state2._sizeProps;
|
|
1739
|
+
if (propW !== void 0 || propH !== void 0) {
|
|
1740
|
+
const currentSize = state2.size;
|
|
1741
|
+
const newSize = {
|
|
1742
|
+
width: propW ?? currentSize.width,
|
|
1743
|
+
height: propH ?? currentSize.height,
|
|
1744
|
+
top: currentSize.top,
|
|
1745
|
+
left: currentSize.left
|
|
1746
|
+
};
|
|
1747
|
+
set((s) => ({
|
|
1748
|
+
size: newSize,
|
|
1749
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1750
|
+
}));
|
|
1751
|
+
getScheduler().invalidate();
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const w = width;
|
|
1757
|
+
const h = height ?? width;
|
|
1758
|
+
const t = top ?? state2.size.top;
|
|
1759
|
+
const l = left ?? state2.size.left;
|
|
1760
|
+
const size = { width: w, height: h, top: t, left: l };
|
|
1761
|
+
set((s) => ({
|
|
1762
|
+
size,
|
|
1763
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1764
|
+
_sizeImperative: true
|
|
1765
|
+
}));
|
|
1766
|
+
getScheduler().invalidate();
|
|
1121
1767
|
},
|
|
1122
1768
|
setDpr: (dpr) => set((state2) => {
|
|
1123
1769
|
const resolved = calculateDpr(dpr);
|
|
@@ -1128,22 +1774,32 @@ const createStore = (invalidate, advance) => {
|
|
|
1128
1774
|
},
|
|
1129
1775
|
setError: (error) => set(() => ({ error })),
|
|
1130
1776
|
error: null,
|
|
1131
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1777
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1132
1778
|
uniforms: {},
|
|
1133
1779
|
nodes: {},
|
|
1780
|
+
buffers: {},
|
|
1781
|
+
gpuStorage: {},
|
|
1134
1782
|
textures: /* @__PURE__ */ new Map(),
|
|
1135
|
-
|
|
1783
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1784
|
+
renderPipeline: null,
|
|
1136
1785
|
passes: {},
|
|
1786
|
+
_hmrVersion: 0,
|
|
1787
|
+
_sizeImperative: false,
|
|
1788
|
+
_sizeProps: null,
|
|
1137
1789
|
previousRoot: void 0,
|
|
1138
1790
|
internal: {
|
|
1139
1791
|
// Events
|
|
1140
1792
|
interaction: [],
|
|
1141
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1142
1793
|
subscribers: [],
|
|
1794
|
+
// Per-pointer state (new unified structure)
|
|
1795
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1796
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1797
|
+
lastEvent: React.createRef(),
|
|
1798
|
+
// Deprecated but kept for backwards compatibility
|
|
1799
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1143
1800
|
initialClick: [0, 0],
|
|
1144
1801
|
initialHits: [],
|
|
1145
1802
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1146
|
-
lastEvent: React.createRef(),
|
|
1147
1803
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1148
1804
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1149
1805
|
// Occlusion system (WebGPU only)
|
|
@@ -1226,13 +1882,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1226
1882
|
rootStore.subscribe(() => {
|
|
1227
1883
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1228
1884
|
const actualRenderer = internal.actualRenderer;
|
|
1885
|
+
const canvasTarget = internal.canvasTarget;
|
|
1229
1886
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1230
1887
|
oldSize = size;
|
|
1231
1888
|
oldDpr = viewport.dpr;
|
|
1232
1889
|
updateCamera(camera, size);
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1235
|
-
|
|
1890
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1891
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1892
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1893
|
+
} else {
|
|
1894
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1895
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1896
|
+
if (canvasTarget) {
|
|
1897
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1898
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1236
1901
|
}
|
|
1237
1902
|
if (camera !== oldCamera) {
|
|
1238
1903
|
oldCamera = camera;
|
|
@@ -1303,1076 +1968,45 @@ useLoader.clear = function(loader, input) {
|
|
|
1303
1968
|
};
|
|
1304
1969
|
useLoader.loader = getLoader;
|
|
1305
1970
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
else {
|
|
1346
|
-
const constraintType = before ? "before" : "after";
|
|
1347
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1348
|
-
}
|
|
1349
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1350
|
-
this.phaseMap.set(name, node);
|
|
1351
|
-
this.invalidateCache();
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Get ordered list of phase names
|
|
1355
|
-
*/
|
|
1356
|
-
getOrderedPhases() {
|
|
1357
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1358
|
-
return this.orderedNamesCache;
|
|
1359
|
-
}
|
|
1360
|
-
/**
|
|
1361
|
-
* Check if a phase exists
|
|
1362
|
-
*/
|
|
1363
|
-
hasPhase(name) {
|
|
1364
|
-
return this.phaseMap.has(name);
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Get the index of a phase (-1 if not found)
|
|
1368
|
-
*/
|
|
1369
|
-
getPhaseIndex(name) {
|
|
1370
|
-
if (!name) return -1;
|
|
1371
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1375
|
-
* Used for resolving before/after constraints.
|
|
1376
|
-
*
|
|
1377
|
-
* @param name - The phase name to ensure exists
|
|
1378
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1379
|
-
*/
|
|
1380
|
-
ensurePhase(name) {
|
|
1381
|
-
if (this.phaseMap.has(name)) return name;
|
|
1382
|
-
const node = { name, isAutoGenerated: true };
|
|
1383
|
-
this.phases.push(node);
|
|
1384
|
-
this.phaseMap.set(name, node);
|
|
1385
|
-
this.invalidateCache();
|
|
1386
|
-
return name;
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Resolve where a job with before/after constraints should go.
|
|
1390
|
-
* Creates auto-generated phases if needed.
|
|
1391
|
-
*
|
|
1392
|
-
* @param before - Phase(s) to run before
|
|
1393
|
-
* @param after - Phase(s) to run after
|
|
1394
|
-
* @returns The resolved phase name
|
|
1395
|
-
*/
|
|
1396
|
-
resolveConstraintPhase(before, after) {
|
|
1397
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1398
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1399
|
-
if (beforeArr.length > 0) {
|
|
1400
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1401
|
-
}
|
|
1402
|
-
if (afterArr.length > 0) {
|
|
1403
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1404
|
-
}
|
|
1405
|
-
return "update";
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1409
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1410
|
-
*
|
|
1411
|
-
* @param target - The target phase name to position relative to
|
|
1412
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1413
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1414
|
-
* @returns The auto-generated phase name
|
|
1415
|
-
*/
|
|
1416
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1417
|
-
const autoName = `${prefix}:${target}`;
|
|
1418
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1419
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1420
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1421
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1422
|
-
else this.phases.push(node);
|
|
1423
|
-
this.phaseMap.set(autoName, node);
|
|
1424
|
-
this.invalidateCache();
|
|
1425
|
-
return autoName;
|
|
1426
|
-
}
|
|
1427
|
-
// Internal --------------------------------
|
|
1428
|
-
invalidateCache() {
|
|
1429
|
-
this.orderedNamesCache = null;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1434
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1435
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1436
|
-
for (const phase of orderedPhases) {
|
|
1437
|
-
buckets.set(phase, []);
|
|
1438
|
-
}
|
|
1439
|
-
for (const job of jobs.values()) {
|
|
1440
|
-
if (!job.enabled) continue;
|
|
1441
|
-
let bucket = buckets.get(job.phase);
|
|
1442
|
-
if (!bucket) {
|
|
1443
|
-
bucket = [];
|
|
1444
|
-
buckets.set(job.phase, bucket);
|
|
1445
|
-
}
|
|
1446
|
-
bucket.push(job);
|
|
1447
|
-
}
|
|
1448
|
-
const sortedBuckets = [];
|
|
1449
|
-
for (const phase of orderedPhases) {
|
|
1450
|
-
const bucket = buckets.get(phase);
|
|
1451
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1452
|
-
bucket.sort((a, b) => {
|
|
1453
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1454
|
-
return a.index - b.index;
|
|
1455
|
-
});
|
|
1456
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1457
|
-
}
|
|
1458
|
-
for (const [phase, bucket] of buckets) {
|
|
1459
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1460
|
-
bucket.sort((a, b) => {
|
|
1461
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1462
|
-
return a.index - b.index;
|
|
1463
|
-
});
|
|
1464
|
-
sortedBuckets.push(bucket);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
return sortedBuckets.flat();
|
|
1468
|
-
}
|
|
1469
|
-
function hasCrossJobConstraints(bucket) {
|
|
1470
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1471
|
-
for (const job of bucket) {
|
|
1472
|
-
for (const ref of job.before) {
|
|
1473
|
-
if (jobIds.has(ref)) return true;
|
|
1474
|
-
}
|
|
1475
|
-
for (const ref of job.after) {
|
|
1476
|
-
if (jobIds.has(ref)) return true;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
return false;
|
|
1480
|
-
}
|
|
1481
|
-
function topologicalSort(jobs) {
|
|
1482
|
-
const n = jobs.length;
|
|
1483
|
-
if (n <= 1) return jobs;
|
|
1484
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1485
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1486
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1487
|
-
for (const job of jobs) {
|
|
1488
|
-
jobMap.set(job.id, job);
|
|
1489
|
-
inDegree.set(job.id, 0);
|
|
1490
|
-
adjacency.set(job.id, []);
|
|
1491
|
-
}
|
|
1492
|
-
for (const job of jobs) {
|
|
1493
|
-
for (const ref of job.before) {
|
|
1494
|
-
if (jobMap.has(ref)) {
|
|
1495
|
-
adjacency.get(job.id).push(ref);
|
|
1496
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
for (const ref of job.after) {
|
|
1500
|
-
if (jobMap.has(ref)) {
|
|
1501
|
-
adjacency.get(ref).push(job.id);
|
|
1502
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
const queue = [];
|
|
1507
|
-
for (const job of jobs) {
|
|
1508
|
-
if (inDegree.get(job.id) === 0) {
|
|
1509
|
-
queue.push(job);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
queue.sort((a, b) => {
|
|
1513
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1514
|
-
return a.index - b.index;
|
|
1515
|
-
});
|
|
1516
|
-
const result = [];
|
|
1517
|
-
while (queue.length > 0) {
|
|
1518
|
-
const job = queue.shift();
|
|
1519
|
-
result.push(job);
|
|
1520
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1521
|
-
for (const neighborId of neighbors) {
|
|
1522
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1523
|
-
inDegree.set(neighborId, newDegree);
|
|
1524
|
-
if (newDegree === 0) {
|
|
1525
|
-
const neighbor = jobMap.get(neighborId);
|
|
1526
|
-
insertSorted(queue, neighbor);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
if (result.length !== n) {
|
|
1531
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1532
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1533
|
-
for (const job of jobs) {
|
|
1534
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return result;
|
|
1538
|
-
}
|
|
1539
|
-
function insertSorted(arr, job) {
|
|
1540
|
-
let i = 0;
|
|
1541
|
-
while (i < arr.length) {
|
|
1542
|
-
const cmp = arr[i];
|
|
1543
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1544
|
-
break;
|
|
1545
|
-
}
|
|
1546
|
-
i++;
|
|
1547
|
-
}
|
|
1548
|
-
arr.splice(i, 0, job);
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
function shouldRun(job, now) {
|
|
1552
|
-
if (!job.enabled) return false;
|
|
1553
|
-
if (!job.fps) return true;
|
|
1554
|
-
const minInterval = 1e3 / job.fps;
|
|
1555
|
-
const lastRun = job.lastRun ?? 0;
|
|
1556
|
-
const elapsed = now - lastRun;
|
|
1557
|
-
if (elapsed < minInterval) return false;
|
|
1558
|
-
if (job.drop) {
|
|
1559
|
-
job.lastRun = now;
|
|
1560
|
-
} else {
|
|
1561
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1562
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1563
|
-
if (job.lastRun < now - minInterval) {
|
|
1564
|
-
job.lastRun = now;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
return true;
|
|
1568
|
-
}
|
|
1569
|
-
function resetJobTiming(job) {
|
|
1570
|
-
job.lastRun = void 0;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
var __defProp = Object.defineProperty;
|
|
1574
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1575
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1576
|
-
const hmrData = (() => {
|
|
1577
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1578
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1579
|
-
try {
|
|
1580
|
-
return (0, eval)("import.meta.hot");
|
|
1581
|
-
} catch {
|
|
1582
|
-
return void 0;
|
|
1583
|
-
}
|
|
1584
|
-
})();
|
|
1585
|
-
const _Scheduler = class _Scheduler {
|
|
1586
|
-
//* Constructor ================================
|
|
1587
|
-
constructor() {
|
|
1588
|
-
//* Critical State ================================
|
|
1589
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1590
|
-
__publicField(this, "phaseGraph");
|
|
1591
|
-
__publicField(this, "loopState", {
|
|
1592
|
-
running: false,
|
|
1593
|
-
rafHandle: null,
|
|
1594
|
-
lastTime: null,
|
|
1595
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1596
|
-
frameCount: 0,
|
|
1597
|
-
elapsedTime: 0,
|
|
1598
|
-
createdAt: performance.now()
|
|
1599
|
-
});
|
|
1600
|
-
__publicField(this, "stoppedTime", 0);
|
|
1601
|
-
//* Private State ================================
|
|
1602
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1603
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1604
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1605
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1606
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1607
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1608
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1609
|
-
__publicField(this, "pendingFrames", 0);
|
|
1610
|
-
__publicField(this, "_frameloop", "always");
|
|
1611
|
-
//* Independent Mode & Error Handling State ================================
|
|
1612
|
-
__publicField(this, "_independent", false);
|
|
1613
|
-
__publicField(this, "errorHandler", null);
|
|
1614
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1615
|
-
//* Core Loop Execution Methods ================================
|
|
1616
|
-
/**
|
|
1617
|
-
* Main RAF loop callback.
|
|
1618
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1619
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1620
|
-
* @returns {void}
|
|
1621
|
-
* @private
|
|
1622
|
-
*/
|
|
1623
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1624
|
-
if (!this.loopState.running) return;
|
|
1625
|
-
this.executeFrame(timestamp);
|
|
1626
|
-
if (this._frameloop === "demand") {
|
|
1627
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1628
|
-
if (this.pendingFrames === 0) {
|
|
1629
|
-
this.notifyIdle(timestamp);
|
|
1630
|
-
return this.stop();
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1634
|
-
});
|
|
1635
|
-
this.phaseGraph = new PhaseGraph();
|
|
1636
|
-
}
|
|
1637
|
-
static get instance() {
|
|
1638
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1639
|
-
}
|
|
1640
|
-
static set instance(value) {
|
|
1641
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1645
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1646
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1647
|
-
*/
|
|
1648
|
-
static get() {
|
|
1649
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1650
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1651
|
-
}
|
|
1652
|
-
if (!_Scheduler.instance) {
|
|
1653
|
-
_Scheduler.instance = new _Scheduler();
|
|
1654
|
-
if (hmrData?.data) {
|
|
1655
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
return _Scheduler.instance;
|
|
1659
|
-
}
|
|
1660
|
-
/**
|
|
1661
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1662
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1663
|
-
* @returns {void}
|
|
1664
|
-
*/
|
|
1665
|
-
static reset() {
|
|
1666
|
-
if (_Scheduler.instance) {
|
|
1667
|
-
_Scheduler.instance.stop();
|
|
1668
|
-
_Scheduler.instance = null;
|
|
1669
|
-
}
|
|
1670
|
-
if (hmrData?.data) {
|
|
1671
|
-
hmrData.data.scheduler = null;
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
//* Getters & Setters ================================
|
|
1675
|
-
get phases() {
|
|
1676
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1677
|
-
}
|
|
1678
|
-
get frameloop() {
|
|
1679
|
-
return this._frameloop;
|
|
1680
|
-
}
|
|
1681
|
-
set frameloop(mode) {
|
|
1682
|
-
if (this._frameloop === mode) return;
|
|
1683
|
-
const wasAlways = this._frameloop === "always";
|
|
1684
|
-
this._frameloop = mode;
|
|
1685
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1686
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1687
|
-
}
|
|
1688
|
-
get isRunning() {
|
|
1689
|
-
return this.loopState.running;
|
|
1690
|
-
}
|
|
1691
|
-
get isReady() {
|
|
1692
|
-
return this.roots.size > 0;
|
|
1693
|
-
}
|
|
1694
|
-
get independent() {
|
|
1695
|
-
return this._independent;
|
|
1696
|
-
}
|
|
1697
|
-
set independent(value) {
|
|
1698
|
-
this._independent = value;
|
|
1699
|
-
if (value) this.ensureDefaultRoot();
|
|
1700
|
-
}
|
|
1701
|
-
//* Root Management Methods ================================
|
|
1702
|
-
/**
|
|
1703
|
-
* Register a root (Canvas) with the scheduler.
|
|
1704
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1705
|
-
* @param {string} id - Unique identifier for this root
|
|
1706
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1707
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1708
|
-
*/
|
|
1709
|
-
registerRoot(id, options = {}) {
|
|
1710
|
-
if (this.roots.has(id)) {
|
|
1711
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1712
|
-
return () => this.unregisterRoot(id);
|
|
1713
|
-
}
|
|
1714
|
-
const entry = {
|
|
1715
|
-
id,
|
|
1716
|
-
getState: options.getState ?? (() => ({})),
|
|
1717
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1718
|
-
sortedJobs: [],
|
|
1719
|
-
needsRebuild: false
|
|
1720
|
-
};
|
|
1721
|
-
if (options.onError) {
|
|
1722
|
-
this.errorHandler = options.onError;
|
|
1723
|
-
}
|
|
1724
|
-
this.roots.set(id, entry);
|
|
1725
|
-
if (this.roots.size === 1) {
|
|
1726
|
-
this.notifyRootReady();
|
|
1727
|
-
if (this._frameloop === "always") this.start();
|
|
1728
|
-
}
|
|
1729
|
-
return () => this.unregisterRoot(id);
|
|
1730
|
-
}
|
|
1731
|
-
/**
|
|
1732
|
-
* Unregister a root from the scheduler.
|
|
1733
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1734
|
-
* The last root to unregister stops the RAF loop.
|
|
1735
|
-
* @param {string} id - The root ID to unregister
|
|
1736
|
-
* @returns {void}
|
|
1737
|
-
*/
|
|
1738
|
-
unregisterRoot(id) {
|
|
1739
|
-
const root = this.roots.get(id);
|
|
1740
|
-
if (!root) return;
|
|
1741
|
-
for (const jobId of root.jobs.keys()) {
|
|
1742
|
-
this.jobStateListeners.delete(jobId);
|
|
1743
|
-
}
|
|
1744
|
-
this.roots.delete(id);
|
|
1745
|
-
if (this.roots.size === 0) {
|
|
1746
|
-
this.stop();
|
|
1747
|
-
this.errorHandler = null;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
/**
|
|
1751
|
-
* Subscribe to be notified when a root becomes available.
|
|
1752
|
-
* Fires immediately if a root already exists.
|
|
1753
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1754
|
-
* @returns {() => void} Unsubscribe function
|
|
1755
|
-
*/
|
|
1756
|
-
onRootReady(callback) {
|
|
1757
|
-
if (this.roots.size > 0) {
|
|
1758
|
-
callback();
|
|
1759
|
-
return () => {
|
|
1760
|
-
};
|
|
1761
|
-
}
|
|
1762
|
-
this.rootReadyCallbacks.add(callback);
|
|
1763
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1764
|
-
}
|
|
1765
|
-
/**
|
|
1766
|
-
* Notify all registered root-ready callbacks.
|
|
1767
|
-
* Called when the first root registers.
|
|
1768
|
-
* @returns {void}
|
|
1769
|
-
* @private
|
|
1770
|
-
*/
|
|
1771
|
-
notifyRootReady() {
|
|
1772
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1773
|
-
try {
|
|
1774
|
-
cb();
|
|
1775
|
-
} catch (error) {
|
|
1776
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
this.rootReadyCallbacks.clear();
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Ensure a default root exists for independent mode.
|
|
1783
|
-
* Creates a minimal root with no state provider.
|
|
1784
|
-
* @returns {void}
|
|
1785
|
-
* @private
|
|
1786
|
-
*/
|
|
1787
|
-
ensureDefaultRoot() {
|
|
1788
|
-
if (!this.roots.has("__default__")) {
|
|
1789
|
-
this.registerRoot("__default__");
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
/**
|
|
1793
|
-
* Trigger error handling for job errors.
|
|
1794
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1795
|
-
* @param {Error} error - The error to handle
|
|
1796
|
-
* @returns {void}
|
|
1797
|
-
*/
|
|
1798
|
-
triggerError(error) {
|
|
1799
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1800
|
-
else console.error("[Scheduler]", error);
|
|
1801
|
-
}
|
|
1802
|
-
//* Phase Management Methods ================================
|
|
1803
|
-
/**
|
|
1804
|
-
* Add a named phase to the scheduler's execution order.
|
|
1805
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1806
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1807
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1808
|
-
* @returns {void}
|
|
1809
|
-
* @example
|
|
1810
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1811
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1812
|
-
*/
|
|
1813
|
-
addPhase(name, options) {
|
|
1814
|
-
this.phaseGraph.addPhase(name, options);
|
|
1815
|
-
for (const root of this.roots.values()) {
|
|
1816
|
-
root.needsRebuild = true;
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
/**
|
|
1820
|
-
* Check if a phase exists in the scheduler.
|
|
1821
|
-
* @param {string} name - The phase name to check
|
|
1822
|
-
* @returns {boolean} True if the phase exists
|
|
1823
|
-
*/
|
|
1824
|
-
hasPhase(name) {
|
|
1825
|
-
return this.phaseGraph.hasPhase(name);
|
|
1826
|
-
}
|
|
1827
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1828
|
-
/**
|
|
1829
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1830
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1831
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1832
|
-
* @param {string} id - Unique identifier for this global job
|
|
1833
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1834
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1835
|
-
* @deprecated Use useFrame with phases instead
|
|
1836
|
-
*/
|
|
1837
|
-
registerGlobal(phase, id, callback) {
|
|
1838
|
-
const job = { id, callback };
|
|
1839
|
-
if (phase === "before") {
|
|
1840
|
-
this.globalBeforeJobs.set(id, job);
|
|
1841
|
-
} else {
|
|
1842
|
-
this.globalAfterJobs.set(id, job);
|
|
1843
|
-
}
|
|
1844
|
-
return () => {
|
|
1845
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1846
|
-
else this.globalAfterJobs.delete(id);
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1850
|
-
/**
|
|
1851
|
-
* Register an idle callback that fires when the loop stops.
|
|
1852
|
-
* Used internally by deprecated addTail API.
|
|
1853
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1854
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1855
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1856
|
-
*/
|
|
1857
|
-
onIdle(callback) {
|
|
1858
|
-
this.idleCallbacks.add(callback);
|
|
1859
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* Notify all registered idle callbacks.
|
|
1863
|
-
* Called when the loop stops in demand mode.
|
|
1864
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1865
|
-
* @returns {void}
|
|
1866
|
-
* @private
|
|
1867
|
-
*/
|
|
1868
|
-
notifyIdle(timestamp) {
|
|
1869
|
-
for (const cb of this.idleCallbacks) {
|
|
1870
|
-
try {
|
|
1871
|
-
cb(timestamp);
|
|
1872
|
-
} catch (error) {
|
|
1873
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
//* Job Registration & Management Methods ================================
|
|
1878
|
-
/**
|
|
1879
|
-
* Register a job (frame callback) with a specific root.
|
|
1880
|
-
* This is the core registration method used by useFrame internally.
|
|
1881
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1882
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1883
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1884
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1885
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1886
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1887
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1888
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1889
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1890
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1891
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1892
|
-
*/
|
|
1893
|
-
register(callback, options = {}) {
|
|
1894
|
-
const rootId = options.rootId;
|
|
1895
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1896
|
-
if (!root) {
|
|
1897
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1898
|
-
return () => {
|
|
1899
|
-
};
|
|
1900
|
-
}
|
|
1901
|
-
const id = options.id ?? this.generateJobId();
|
|
1902
|
-
let phase = options.phase ?? "update";
|
|
1903
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1904
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1905
|
-
}
|
|
1906
|
-
const before = this.normalizeConstraints(options.before);
|
|
1907
|
-
const after = this.normalizeConstraints(options.after);
|
|
1908
|
-
const job = {
|
|
1909
|
-
id,
|
|
1910
|
-
callback,
|
|
1911
|
-
phase,
|
|
1912
|
-
before,
|
|
1913
|
-
after,
|
|
1914
|
-
priority: options.priority ?? 0,
|
|
1915
|
-
index: this.nextJobIndex++,
|
|
1916
|
-
fps: options.fps,
|
|
1917
|
-
drop: options.drop ?? true,
|
|
1918
|
-
enabled: options.enabled ?? true,
|
|
1919
|
-
system: options.system ?? false
|
|
1920
|
-
};
|
|
1921
|
-
if (root.jobs.has(id)) {
|
|
1922
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1923
|
-
}
|
|
1924
|
-
root.jobs.set(id, job);
|
|
1925
|
-
root.needsRebuild = true;
|
|
1926
|
-
return () => this.unregister(id, root.id);
|
|
1927
|
-
}
|
|
1928
|
-
/**
|
|
1929
|
-
* Unregister a job by its ID.
|
|
1930
|
-
* Searches all roots if rootId is not provided.
|
|
1931
|
-
* @param {string} id - The job ID to unregister
|
|
1932
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1933
|
-
* @returns {void}
|
|
1934
|
-
*/
|
|
1935
|
-
unregister(id, rootId) {
|
|
1936
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1937
|
-
if (root?.jobs.delete(id)) {
|
|
1938
|
-
root.needsRebuild = true;
|
|
1939
|
-
this.jobStateListeners.delete(id);
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
/**
|
|
1943
|
-
* Update a job's options dynamically.
|
|
1944
|
-
* Searches all roots to find the job by ID.
|
|
1945
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1946
|
-
* @param {string} id - The job ID to update
|
|
1947
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1948
|
-
* @returns {void}
|
|
1949
|
-
*/
|
|
1950
|
-
updateJob(id, options) {
|
|
1951
|
-
let job;
|
|
1952
|
-
let root;
|
|
1953
|
-
for (const r of this.roots.values()) {
|
|
1954
|
-
job = r.jobs.get(id);
|
|
1955
|
-
if (job) {
|
|
1956
|
-
root = r;
|
|
1957
|
-
break;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
if (!job || !root) return;
|
|
1961
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
1962
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
1963
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
1964
|
-
if (options.enabled !== void 0) {
|
|
1965
|
-
const wasEnabled = job.enabled;
|
|
1966
|
-
job.enabled = options.enabled;
|
|
1967
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
1968
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
1969
|
-
}
|
|
1970
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
1971
|
-
if (options.phase) job.phase = options.phase;
|
|
1972
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
1973
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
1974
|
-
root.needsRebuild = true;
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
//* Job State Management Methods ================================
|
|
1978
|
-
/**
|
|
1979
|
-
* Check if a job is currently paused (disabled).
|
|
1980
|
-
* @param {string} id - The job ID to check
|
|
1981
|
-
* @returns {boolean} True if the job exists and is paused
|
|
1982
|
-
*/
|
|
1983
|
-
isJobPaused(id) {
|
|
1984
|
-
for (const root of this.roots.values()) {
|
|
1985
|
-
const job = root.jobs.get(id);
|
|
1986
|
-
if (job) return !job.enabled;
|
|
1987
|
-
}
|
|
1988
|
-
return false;
|
|
1989
|
-
}
|
|
1990
|
-
/**
|
|
1991
|
-
* Subscribe to state changes for a specific job.
|
|
1992
|
-
* Listener is called when job is paused or resumed.
|
|
1993
|
-
* @param {string} id - The job ID to subscribe to
|
|
1994
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
1995
|
-
* @returns {() => void} Unsubscribe function
|
|
1996
|
-
*/
|
|
1997
|
-
subscribeJobState(id, listener) {
|
|
1998
|
-
if (!this.jobStateListeners.has(id)) {
|
|
1999
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2000
|
-
}
|
|
2001
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2002
|
-
return () => {
|
|
2003
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2004
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2005
|
-
this.jobStateListeners.delete(id);
|
|
2006
|
-
}
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Notify all listeners that a job's state has changed.
|
|
2011
|
-
* @param {string} id - The job ID that changed
|
|
2012
|
-
* @returns {void}
|
|
2013
|
-
* @private
|
|
2014
|
-
*/
|
|
2015
|
-
notifyJobStateChange(id) {
|
|
2016
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2017
|
-
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Pause a job by ID (sets enabled=false).
|
|
2020
|
-
* Notifies any subscribed state listeners.
|
|
2021
|
-
* @param {string} id - The job ID to pause
|
|
2022
|
-
* @returns {void}
|
|
2023
|
-
*/
|
|
2024
|
-
pauseJob(id) {
|
|
2025
|
-
this.updateJob(id, { enabled: false });
|
|
2026
|
-
this.notifyJobStateChange(id);
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2030
|
-
* Resets job timing to prevent frame accumulation.
|
|
2031
|
-
* Notifies any subscribed state listeners.
|
|
2032
|
-
* @param {string} id - The job ID to resume
|
|
2033
|
-
* @returns {void}
|
|
2034
|
-
*/
|
|
2035
|
-
resumeJob(id) {
|
|
2036
|
-
this.updateJob(id, { enabled: true });
|
|
2037
|
-
this.notifyJobStateChange(id);
|
|
2038
|
-
}
|
|
2039
|
-
//* Frame Loop Control Methods ================================
|
|
2040
|
-
/**
|
|
2041
|
-
* Start the requestAnimationFrame loop.
|
|
2042
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2043
|
-
* No-op if already running.
|
|
2044
|
-
* @returns {void}
|
|
2045
|
-
*/
|
|
2046
|
-
start() {
|
|
2047
|
-
if (this.loopState.running) return;
|
|
2048
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2049
|
-
let adjustedCreated = 0;
|
|
2050
|
-
if (this.stoppedTime > 0) {
|
|
2051
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2052
|
-
this.stoppedTime = 0;
|
|
2053
|
-
}
|
|
2054
|
-
Object.assign(this.loopState, {
|
|
2055
|
-
running: true,
|
|
2056
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2057
|
-
lastTime: performance.now(),
|
|
2058
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2059
|
-
frameCount: 0,
|
|
2060
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Stop the requestAnimationFrame loop.
|
|
2065
|
-
* Cancels any pending RAF callback.
|
|
2066
|
-
* No-op if not running.
|
|
2067
|
-
* @returns {void}
|
|
2068
|
-
*/
|
|
2069
|
-
stop() {
|
|
2070
|
-
if (!this.loopState.running) return;
|
|
2071
|
-
this.loopState.running = false;
|
|
2072
|
-
if (this.loopState.rafHandle !== null) {
|
|
2073
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2074
|
-
this.loopState.rafHandle = null;
|
|
2075
|
-
}
|
|
2076
|
-
this.stoppedTime = performance.now();
|
|
2077
|
-
}
|
|
2078
|
-
/**
|
|
2079
|
-
* Request frames to be rendered in demand mode.
|
|
2080
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2081
|
-
* No-op if frameloop is not 'demand'.
|
|
2082
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2083
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2084
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2085
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2086
|
-
* @returns {void}
|
|
2087
|
-
* @example
|
|
2088
|
-
* // Request a single frame render
|
|
2089
|
-
* scheduler.invalidate();
|
|
2090
|
-
*
|
|
2091
|
-
* @example
|
|
2092
|
-
* // Request 5 frames (e.g., for animations)
|
|
2093
|
-
* scheduler.invalidate(5);
|
|
2094
|
-
*
|
|
2095
|
-
* @example
|
|
2096
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2097
|
-
* scheduler.invalidate(3, false);
|
|
2098
|
-
*
|
|
2099
|
-
* @example
|
|
2100
|
-
* // Add 2 more frames to existing pending count
|
|
2101
|
-
* scheduler.invalidate(2, true);
|
|
2102
|
-
*/
|
|
2103
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2104
|
-
if (this._frameloop !== "demand") return;
|
|
2105
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2106
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2107
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2108
|
-
}
|
|
2109
|
-
/**
|
|
2110
|
-
* Reset timing state for deterministic testing.
|
|
2111
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2112
|
-
* @returns {void}
|
|
2113
|
-
*/
|
|
2114
|
-
resetTiming() {
|
|
2115
|
-
this.loopState.lastTime = null;
|
|
2116
|
-
this.loopState.frameCount = 0;
|
|
2117
|
-
this.loopState.elapsedTime = 0;
|
|
2118
|
-
this.loopState.createdAt = performance.now();
|
|
2119
|
-
}
|
|
2120
|
-
//* Manual Stepping Methods ================================
|
|
2121
|
-
/**
|
|
2122
|
-
* Manually execute a single frame for all roots.
|
|
2123
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2124
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2125
|
-
* @returns {void}
|
|
2126
|
-
* @example
|
|
2127
|
-
* // Manual control mode
|
|
2128
|
-
* scheduler.frameloop = 'never';
|
|
2129
|
-
* scheduler.step(); // Execute one frame
|
|
2130
|
-
*/
|
|
2131
|
-
step(timestamp) {
|
|
2132
|
-
const now = timestamp ?? performance.now();
|
|
2133
|
-
this.executeFrame(now);
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Manually execute a single job by its ID.
|
|
2137
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2138
|
-
* @param {string} id - The job ID to step
|
|
2139
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2140
|
-
* @returns {void}
|
|
2141
|
-
*/
|
|
2142
|
-
stepJob(id, timestamp) {
|
|
2143
|
-
let job;
|
|
2144
|
-
let root;
|
|
2145
|
-
for (const r of this.roots.values()) {
|
|
2146
|
-
job = r.jobs.get(id);
|
|
2147
|
-
if (job) {
|
|
2148
|
-
root = r;
|
|
2149
|
-
break;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
if (!job || !root) {
|
|
2153
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
const now = timestamp ?? performance.now();
|
|
2157
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2158
|
-
const delta = deltaMs / 1e3;
|
|
2159
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2160
|
-
const providedState = root.getState?.() ?? {};
|
|
2161
|
-
const frameState = {
|
|
2162
|
-
...providedState,
|
|
2163
|
-
time: now,
|
|
2164
|
-
delta,
|
|
2165
|
-
elapsed,
|
|
2166
|
-
frame: this.loopState.frameCount
|
|
2167
|
-
};
|
|
2168
|
-
try {
|
|
2169
|
-
job.callback(frameState, delta);
|
|
2170
|
-
} catch (error) {
|
|
2171
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2172
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
/**
|
|
2176
|
-
* Execute a single frame across all roots.
|
|
2177
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2178
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2179
|
-
* @returns {void}
|
|
2180
|
-
* @private
|
|
2181
|
-
*/
|
|
2182
|
-
executeFrame(timestamp) {
|
|
2183
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2184
|
-
const delta = deltaMs / 1e3;
|
|
2185
|
-
this.loopState.lastTime = timestamp;
|
|
2186
|
-
this.loopState.frameCount++;
|
|
2187
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2188
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2189
|
-
for (const root of this.roots.values()) {
|
|
2190
|
-
this.tickRoot(root, timestamp, delta);
|
|
2191
|
-
}
|
|
2192
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2193
|
-
}
|
|
2194
|
-
/**
|
|
2195
|
-
* Run all global jobs from a job map.
|
|
2196
|
-
* Catches and logs errors without stopping execution.
|
|
2197
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2198
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2199
|
-
* @returns {void}
|
|
2200
|
-
* @private
|
|
2201
|
-
*/
|
|
2202
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2203
|
-
for (const job of jobs.values()) {
|
|
2204
|
-
try {
|
|
2205
|
-
job.callback(timestamp);
|
|
2206
|
-
} catch (error) {
|
|
2207
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Execute all jobs for a single root in sorted order.
|
|
2213
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2214
|
-
* Errors are caught and propagated via triggerError.
|
|
2215
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2216
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2217
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2218
|
-
* @returns {void}
|
|
2219
|
-
* @private
|
|
2220
|
-
*/
|
|
2221
|
-
tickRoot(root, timestamp, delta) {
|
|
2222
|
-
if (root.needsRebuild) {
|
|
2223
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2224
|
-
root.needsRebuild = false;
|
|
2225
|
-
}
|
|
2226
|
-
const providedState = root.getState?.() ?? {};
|
|
2227
|
-
const frameState = {
|
|
2228
|
-
...providedState,
|
|
2229
|
-
time: timestamp,
|
|
2230
|
-
delta,
|
|
2231
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2232
|
-
// Convert ms to seconds
|
|
2233
|
-
frame: this.loopState.frameCount
|
|
2234
|
-
};
|
|
2235
|
-
for (const job of root.sortedJobs) {
|
|
2236
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2237
|
-
try {
|
|
2238
|
-
job.callback(frameState, delta);
|
|
2239
|
-
} catch (error) {
|
|
2240
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2241
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
//* Debug & Inspection Methods ================================
|
|
2246
|
-
/**
|
|
2247
|
-
* Get the total number of registered jobs across all roots.
|
|
2248
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2249
|
-
* @returns {number} Total job count
|
|
2250
|
-
*/
|
|
2251
|
-
getJobCount() {
|
|
2252
|
-
let count = 0;
|
|
2253
|
-
for (const root of this.roots.values()) {
|
|
2254
|
-
count += root.jobs.size;
|
|
2255
|
-
}
|
|
2256
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2257
|
-
}
|
|
2258
|
-
/**
|
|
2259
|
-
* Get all registered job IDs across all roots.
|
|
2260
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2261
|
-
* @returns {string[]} Array of all job IDs
|
|
2262
|
-
*/
|
|
2263
|
-
getJobIds() {
|
|
2264
|
-
const ids = [];
|
|
2265
|
-
for (const root of this.roots.values()) {
|
|
2266
|
-
ids.push(...root.jobs.keys());
|
|
2267
|
-
}
|
|
2268
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2269
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2270
|
-
return ids;
|
|
2271
|
-
}
|
|
2272
|
-
/**
|
|
2273
|
-
* Get the number of registered roots (Canvas instances).
|
|
2274
|
-
* @returns {number} Number of registered roots
|
|
2275
|
-
*/
|
|
2276
|
-
getRootCount() {
|
|
2277
|
-
return this.roots.size;
|
|
2278
|
-
}
|
|
2279
|
-
/**
|
|
2280
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2281
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2282
|
-
*
|
|
2283
|
-
* @param phase The phase to check
|
|
2284
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2285
|
-
* @returns true if any user jobs exist in the phase
|
|
2286
|
-
*/
|
|
2287
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2288
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2289
|
-
return rootsToCheck.some((root) => {
|
|
2290
|
-
if (!root) return false;
|
|
2291
|
-
for (const job of root.jobs.values()) {
|
|
2292
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2293
|
-
}
|
|
2294
|
-
return false;
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
|
-
//* Utility Methods ================================
|
|
2298
|
-
/**
|
|
2299
|
-
* Generate a unique root ID for automatic root registration.
|
|
2300
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2301
|
-
*/
|
|
2302
|
-
generateRootId() {
|
|
2303
|
-
return `root_${this.nextRootIndex++}`;
|
|
2304
|
-
}
|
|
2305
|
-
/**
|
|
2306
|
-
* Generate a unique job ID.
|
|
2307
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2308
|
-
* @private
|
|
2309
|
-
*/
|
|
2310
|
-
generateJobId() {
|
|
2311
|
-
return `job_${this.nextJobIndex}`;
|
|
2312
|
-
}
|
|
2313
|
-
/**
|
|
2314
|
-
* Normalize before/after constraints to a Set.
|
|
2315
|
-
* Handles undefined, single string, or array inputs.
|
|
2316
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2317
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2318
|
-
* @private
|
|
2319
|
-
*/
|
|
2320
|
-
normalizeConstraints(value) {
|
|
2321
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2322
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2323
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2327
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2328
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2329
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2330
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2331
|
-
let Scheduler = _Scheduler;
|
|
2332
|
-
const getScheduler = () => Scheduler.get();
|
|
2333
|
-
if (hmrData) {
|
|
2334
|
-
hmrData.accept?.();
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function useFrame(callback, priorityOrOptions) {
|
|
2338
|
-
const store = React.useContext(context);
|
|
2339
|
-
const isInsideCanvas = store !== null;
|
|
2340
|
-
const scheduler = getScheduler();
|
|
2341
|
-
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2342
|
-
id: priorityOrOptions.id,
|
|
2343
|
-
phase: priorityOrOptions.phase,
|
|
2344
|
-
priority: priorityOrOptions.priority,
|
|
2345
|
-
fps: priorityOrOptions.fps,
|
|
2346
|
-
drop: priorityOrOptions.drop,
|
|
2347
|
-
enabled: priorityOrOptions.enabled,
|
|
2348
|
-
before: priorityOrOptions.before,
|
|
2349
|
-
after: priorityOrOptions.after
|
|
2350
|
-
}) : "";
|
|
2351
|
-
const options = React.useMemo(() => {
|
|
2352
|
-
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2353
|
-
}, [optionsKey]);
|
|
2354
|
-
const reactId = React.useId();
|
|
2355
|
-
const id = options.id ?? reactId;
|
|
2356
|
-
const callbackRef = useMutableCallback(callback);
|
|
2357
|
-
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2358
|
-
useIsomorphicLayoutEffect(() => {
|
|
2359
|
-
if (!callback) return;
|
|
2360
|
-
if (isInsideCanvas) {
|
|
2361
|
-
const state = store.getState();
|
|
2362
|
-
const rootId = state.internal.rootId;
|
|
2363
|
-
if (isLegacyPriority) {
|
|
2364
|
-
state.internal.priority++;
|
|
2365
|
-
let parentRoot = state.previousRoot;
|
|
2366
|
-
while (parentRoot) {
|
|
2367
|
-
const parentState = parentRoot.getState();
|
|
2368
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2369
|
-
parentRoot = parentState?.previousRoot;
|
|
2370
|
-
}
|
|
2371
|
-
notifyDepreciated({
|
|
2372
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2373
|
-
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2374
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2375
|
-
});
|
|
1971
|
+
function useFrame(callback, priorityOrOptions) {
|
|
1972
|
+
const store = React.useContext(context);
|
|
1973
|
+
const isInsideCanvas = store !== null;
|
|
1974
|
+
const scheduler = getScheduler();
|
|
1975
|
+
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
1976
|
+
id: priorityOrOptions.id,
|
|
1977
|
+
phase: priorityOrOptions.phase,
|
|
1978
|
+
priority: priorityOrOptions.priority,
|
|
1979
|
+
fps: priorityOrOptions.fps,
|
|
1980
|
+
drop: priorityOrOptions.drop,
|
|
1981
|
+
enabled: priorityOrOptions.enabled,
|
|
1982
|
+
before: priorityOrOptions.before,
|
|
1983
|
+
after: priorityOrOptions.after
|
|
1984
|
+
}) : "";
|
|
1985
|
+
const options = React.useMemo(() => {
|
|
1986
|
+
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
1987
|
+
}, [optionsKey]);
|
|
1988
|
+
const reactId = React.useId();
|
|
1989
|
+
const id = options.id ?? reactId;
|
|
1990
|
+
const callbackRef = useMutableCallback(callback);
|
|
1991
|
+
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
1992
|
+
useIsomorphicLayoutEffect(() => {
|
|
1993
|
+
if (!callback) return;
|
|
1994
|
+
if (isInsideCanvas) {
|
|
1995
|
+
const state = store.getState();
|
|
1996
|
+
const rootId = state.internal.rootId;
|
|
1997
|
+
if (isLegacyPriority) {
|
|
1998
|
+
state.internal.priority++;
|
|
1999
|
+
let parentRoot = state.previousRoot;
|
|
2000
|
+
while (parentRoot) {
|
|
2001
|
+
const parentState = parentRoot.getState();
|
|
2002
|
+
if (parentState?.internal) parentState.internal.priority++;
|
|
2003
|
+
parentRoot = parentState?.previousRoot;
|
|
2004
|
+
}
|
|
2005
|
+
notifyDepreciated({
|
|
2006
|
+
heading: "useFrame with numeric priority is deprecated",
|
|
2007
|
+
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2008
|
+
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2009
|
+
});
|
|
2376
2010
|
}
|
|
2377
2011
|
const wrappedCallback = (frameState, delta) => {
|
|
2378
2012
|
const localState = store.getState();
|
|
@@ -2406,20 +2040,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2406
2040
|
}
|
|
2407
2041
|
};
|
|
2408
2042
|
} else {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
unregisterJob = registerOutside();
|
|
2418
|
-
});
|
|
2419
|
-
return () => {
|
|
2420
|
-
unsubReady();
|
|
2421
|
-
unregisterJob?.();
|
|
2422
|
-
};
|
|
2043
|
+
return scheduler.register(
|
|
2044
|
+
(state, delta) => {
|
|
2045
|
+
const frameState = state;
|
|
2046
|
+
if (!frameState.renderer) return;
|
|
2047
|
+
callbackRef.current?.(frameState, delta);
|
|
2048
|
+
},
|
|
2049
|
+
{ id, ...options }
|
|
2050
|
+
);
|
|
2423
2051
|
}
|
|
2424
2052
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2425
2053
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2510,22 +2138,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2510
2138
|
function useTexture(input, optionsOrOnLoad) {
|
|
2511
2139
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2512
2140
|
const store = useStore();
|
|
2513
|
-
const textureCache = useThree((state) => state.textures);
|
|
2514
2141
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2515
|
-
const { onLoad, cache =
|
|
2142
|
+
const { onLoad, cache = true } = options;
|
|
2143
|
+
const onLoadRef = useRef(onLoad);
|
|
2144
|
+
onLoadRef.current = onLoad;
|
|
2145
|
+
const onLoadCalledForRef = useRef(null);
|
|
2516
2146
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2517
2147
|
const cachedResult = useMemo(() => {
|
|
2518
2148
|
if (!cache) return null;
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2149
|
+
const textures = store.getState().textures;
|
|
2150
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2151
|
+
return buildFromCache(input, textures);
|
|
2152
|
+
}, [cache, urls, input, store]);
|
|
2522
2153
|
const loadedTextures = useLoader(
|
|
2523
2154
|
TextureLoader,
|
|
2524
2155
|
IsObject(input) ? Object.values(input) : input
|
|
2525
2156
|
);
|
|
2157
|
+
const inputKey = urls.join("\0");
|
|
2526
2158
|
useLayoutEffect(() => {
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2159
|
+
if (cachedResult) return;
|
|
2160
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2161
|
+
onLoadCalledForRef.current = inputKey;
|
|
2162
|
+
onLoadRef.current?.(loadedTextures);
|
|
2163
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2529
2164
|
useEffect(() => {
|
|
2530
2165
|
if (cachedResult) return;
|
|
2531
2166
|
if ("initTexture" in renderer) {
|
|
@@ -2558,8 +2193,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2558
2193
|
}, [input, loadedTextures, cachedResult]);
|
|
2559
2194
|
useEffect(() => {
|
|
2560
2195
|
if (!cache) return;
|
|
2561
|
-
if (cachedResult) return;
|
|
2562
|
-
const set = store.setState;
|
|
2563
2196
|
const urlTextureMap = [];
|
|
2564
2197
|
if (typeof input === "string") {
|
|
2565
2198
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2573,18 +2206,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2573
2206
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2574
2207
|
}
|
|
2575
2208
|
}
|
|
2576
|
-
|
|
2577
|
-
const
|
|
2578
|
-
let
|
|
2209
|
+
store.setState((state) => {
|
|
2210
|
+
const refs = new Map(state._textureRefs);
|
|
2211
|
+
let textures = state.textures;
|
|
2212
|
+
let added = false;
|
|
2579
2213
|
for (const [url, texture] of urlTextureMap) {
|
|
2580
|
-
if (!
|
|
2581
|
-
|
|
2582
|
-
|
|
2214
|
+
if (!textures.has(url)) {
|
|
2215
|
+
if (!added) {
|
|
2216
|
+
textures = new Map(textures);
|
|
2217
|
+
added = true;
|
|
2218
|
+
}
|
|
2219
|
+
textures.set(url, texture);
|
|
2583
2220
|
}
|
|
2221
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2584
2222
|
}
|
|
2585
|
-
return
|
|
2223
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2224
|
+
});
|
|
2225
|
+
return () => store.setState((state) => {
|
|
2226
|
+
const refs = new Map(state._textureRefs);
|
|
2227
|
+
for (const [url] of urlTextureMap) {
|
|
2228
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2229
|
+
if (next <= 0) refs.delete(url);
|
|
2230
|
+
else refs.set(url, next);
|
|
2231
|
+
}
|
|
2232
|
+
return { _textureRefs: refs };
|
|
2586
2233
|
});
|
|
2587
|
-
}, [cache, input, mappedTextures, store
|
|
2234
|
+
}, [cache, input, mappedTextures, store]);
|
|
2588
2235
|
return mappedTextures;
|
|
2589
2236
|
}
|
|
2590
2237
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -2600,106 +2247,90 @@ const Texture = ({
|
|
|
2600
2247
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
2601
2248
|
};
|
|
2602
2249
|
|
|
2603
|
-
function
|
|
2604
|
-
if (entry instanceof Texture$1) return entry;
|
|
2605
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
|
|
2606
|
-
return entry.value;
|
|
2607
|
-
}
|
|
2608
|
-
return null;
|
|
2609
|
-
}
|
|
2610
|
-
function useTextures() {
|
|
2250
|
+
function useTextures(selector) {
|
|
2611
2251
|
const store = useStore();
|
|
2612
|
-
|
|
2613
|
-
const set = store.setState;
|
|
2252
|
+
const registry = useMemo(() => {
|
|
2614
2253
|
const getState = store.getState;
|
|
2615
|
-
const
|
|
2616
|
-
|
|
2617
|
-
const newMap = new Map(state.textures);
|
|
2618
|
-
newMap.set(key, value);
|
|
2619
|
-
return { textures: newMap };
|
|
2620
|
-
});
|
|
2621
|
-
};
|
|
2622
|
-
const addMultiple = (items) => {
|
|
2623
|
-
set((state) => {
|
|
2624
|
-
const newMap = new Map(state.textures);
|
|
2625
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2626
|
-
for (const [key, value] of entries) {
|
|
2627
|
-
newMap.set(key, value);
|
|
2628
|
-
}
|
|
2629
|
-
return { textures: newMap };
|
|
2630
|
-
});
|
|
2631
|
-
};
|
|
2632
|
-
const remove = (key) => {
|
|
2633
|
-
set((state) => {
|
|
2634
|
-
const newMap = new Map(state.textures);
|
|
2635
|
-
newMap.delete(key);
|
|
2636
|
-
return { textures: newMap };
|
|
2637
|
-
});
|
|
2638
|
-
};
|
|
2639
|
-
const removeMultiple = (keys) => {
|
|
2640
|
-
set((state) => {
|
|
2641
|
-
const newMap = new Map(state.textures);
|
|
2642
|
-
for (const key of keys) newMap.delete(key);
|
|
2643
|
-
return { textures: newMap };
|
|
2644
|
-
});
|
|
2645
|
-
};
|
|
2646
|
-
const dispose = (key) => {
|
|
2647
|
-
const entry = getState().textures.get(key);
|
|
2648
|
-
if (entry) {
|
|
2649
|
-
const tex = getTextureValue(entry);
|
|
2650
|
-
tex?.dispose();
|
|
2651
|
-
}
|
|
2652
|
-
remove(key);
|
|
2653
|
-
};
|
|
2654
|
-
const disposeMultiple = (keys) => {
|
|
2655
|
-
const textures = getState().textures;
|
|
2656
|
-
for (const key of keys) {
|
|
2657
|
-
const entry = textures.get(key);
|
|
2658
|
-
if (entry) {
|
|
2659
|
-
const tex = getTextureValue(entry);
|
|
2660
|
-
tex?.dispose();
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
removeMultiple(keys);
|
|
2664
|
-
};
|
|
2665
|
-
const disposeAll = () => {
|
|
2666
|
-
const textures = getState().textures;
|
|
2667
|
-
for (const entry of textures.values()) {
|
|
2668
|
-
const tex = getTextureValue(entry);
|
|
2669
|
-
tex?.dispose();
|
|
2670
|
-
}
|
|
2671
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2672
|
-
};
|
|
2254
|
+
const setState = store.setState;
|
|
2255
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2673
2256
|
return {
|
|
2674
|
-
|
|
2675
|
-
get textures() {
|
|
2257
|
+
get all() {
|
|
2676
2258
|
return getState().textures;
|
|
2677
2259
|
},
|
|
2678
|
-
|
|
2679
|
-
|
|
2260
|
+
get(input) {
|
|
2261
|
+
if (typeof input === "string") return getOne(input);
|
|
2262
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2263
|
+
const out = {};
|
|
2264
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2265
|
+
return out;
|
|
2266
|
+
},
|
|
2680
2267
|
has: (key) => getState().textures.has(key),
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2268
|
+
add(keyOrRecord, texture) {
|
|
2269
|
+
setState((state) => {
|
|
2270
|
+
const textures = new Map(state.textures);
|
|
2271
|
+
if (typeof keyOrRecord === "string") {
|
|
2272
|
+
textures.set(keyOrRecord, texture);
|
|
2273
|
+
} else {
|
|
2274
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2275
|
+
}
|
|
2276
|
+
return { textures };
|
|
2277
|
+
});
|
|
2278
|
+
},
|
|
2279
|
+
dispose(key, options) {
|
|
2280
|
+
const state = getState();
|
|
2281
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2282
|
+
if (refs > 0 && !options?.force) {
|
|
2283
|
+
console.warn(
|
|
2284
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2285
|
+
);
|
|
2286
|
+
return false;
|
|
2287
|
+
}
|
|
2288
|
+
state.textures.get(key)?.dispose();
|
|
2289
|
+
setState((s) => {
|
|
2290
|
+
const textures = new Map(s.textures);
|
|
2291
|
+
textures.delete(key);
|
|
2292
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2293
|
+
nextRefs.delete(key);
|
|
2294
|
+
return { textures, _textureRefs: nextRefs };
|
|
2295
|
+
});
|
|
2296
|
+
return true;
|
|
2297
|
+
},
|
|
2298
|
+
disposeAll() {
|
|
2299
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2300
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2301
|
+
}
|
|
2691
2302
|
};
|
|
2692
2303
|
}, [store]);
|
|
2304
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2305
|
+
const selected = useThree(subscribe);
|
|
2306
|
+
return selector ? selected : registry;
|
|
2693
2307
|
}
|
|
2694
2308
|
|
|
2695
|
-
function useRenderTarget(
|
|
2309
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2696
2310
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2697
2311
|
const size = useThree((s) => s.size);
|
|
2312
|
+
let width;
|
|
2313
|
+
let height;
|
|
2314
|
+
let opts;
|
|
2315
|
+
if (typeof widthOrOptions === "object") {
|
|
2316
|
+
opts = widthOrOptions;
|
|
2317
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2318
|
+
width = widthOrOptions;
|
|
2319
|
+
if (typeof heightOrOptions === "object") {
|
|
2320
|
+
height = widthOrOptions;
|
|
2321
|
+
opts = heightOrOptions;
|
|
2322
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2323
|
+
height = heightOrOptions;
|
|
2324
|
+
opts = options;
|
|
2325
|
+
} else {
|
|
2326
|
+
height = widthOrOptions;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2698
2329
|
return useMemo(() => {
|
|
2699
2330
|
const w = width ?? size.width;
|
|
2700
2331
|
const h = height ?? size.height;
|
|
2701
|
-
return new RenderTarget(w, h,
|
|
2702
|
-
}, [width, height, size.width, size.height,
|
|
2332
|
+
return new RenderTarget(w, h, opts);
|
|
2333
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2703
2334
|
}
|
|
2704
2335
|
|
|
2705
2336
|
function useStore() {
|
|
@@ -2749,28 +2380,18 @@ function addTail(callback) {
|
|
|
2749
2380
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2750
2381
|
getScheduler().invalidate(frames, stackFrames);
|
|
2751
2382
|
}
|
|
2752
|
-
function advance(timestamp
|
|
2383
|
+
function advance(timestamp) {
|
|
2753
2384
|
getScheduler().step(timestamp);
|
|
2754
2385
|
}
|
|
2755
2386
|
|
|
2756
|
-
const version = "10.0.0-alpha.
|
|
2387
|
+
const version = "10.0.0-alpha.2";
|
|
2757
2388
|
const packageData = {
|
|
2758
2389
|
version: version};
|
|
2759
2390
|
|
|
2760
2391
|
function Xb(Tt) {
|
|
2761
2392
|
return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
|
|
2762
2393
|
}
|
|
2763
|
-
var Rm = { exports: {} }, Og = { exports: {} };
|
|
2764
|
-
/**
|
|
2765
|
-
* @license React
|
|
2766
|
-
* react-reconciler.production.js
|
|
2767
|
-
*
|
|
2768
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2769
|
-
*
|
|
2770
|
-
* This source code is licensed under the MIT license found in the
|
|
2771
|
-
* LICENSE file in the root directory of this source tree.
|
|
2772
|
-
*/
|
|
2773
|
-
var _b;
|
|
2394
|
+
var Rm = { exports: {} }, Og = { exports: {} }, _b;
|
|
2774
2395
|
function Kb() {
|
|
2775
2396
|
return _b || (_b = 1, (function(Tt) {
|
|
2776
2397
|
Tt.exports = function(m) {
|
|
@@ -3842,7 +3463,6 @@ Error generating stack: ` + l.message + `
|
|
|
3842
3463
|
if (J === cl || J === jc) throw J;
|
|
3843
3464
|
var Ge = Yn(29, J, null, P.mode);
|
|
3844
3465
|
return Ge.lanes = H, Ge.return = P, Ge;
|
|
3845
|
-
} finally {
|
|
3846
3466
|
}
|
|
3847
3467
|
};
|
|
3848
3468
|
}
|
|
@@ -4496,7 +4116,6 @@ Error generating stack: ` + l.message + `
|
|
|
4496
4116
|
var h = r.lastRenderedState, y = d(h, a);
|
|
4497
4117
|
if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
|
|
4498
4118
|
} catch {
|
|
4499
|
-
} finally {
|
|
4500
4119
|
}
|
|
4501
4120
|
if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
|
|
4502
4121
|
}
|
|
@@ -6917,10 +6536,7 @@ Error generating stack: ` + l.message + `
|
|
|
6917
6536
|
function vr(t, r) {
|
|
6918
6537
|
Sf(t, r), (t = t.alternate) && Sf(t, r);
|
|
6919
6538
|
}
|
|
6920
|
-
var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
|
|
6921
|
-
var gc = Symbol.for("react.activity");
|
|
6922
|
-
var $r = Symbol.for("react.memo_cache_sentinel");
|
|
6923
|
-
var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6539
|
+
var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6924
6540
|
m.cloneMutableInstance;
|
|
6925
6541
|
var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
|
|
6926
6542
|
m.cloneMutableTextInstance;
|
|
@@ -7289,17 +6905,7 @@ No matching component was found for:
|
|
|
7289
6905
|
}, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
|
|
7290
6906
|
})(Og)), Og.exports;
|
|
7291
6907
|
}
|
|
7292
|
-
var Mg = { exports: {} };
|
|
7293
|
-
/**
|
|
7294
|
-
* @license React
|
|
7295
|
-
* react-reconciler.development.js
|
|
7296
|
-
*
|
|
7297
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
7298
|
-
*
|
|
7299
|
-
* This source code is licensed under the MIT license found in the
|
|
7300
|
-
* LICENSE file in the root directory of this source tree.
|
|
7301
|
-
*/
|
|
7302
|
-
var Rb;
|
|
6908
|
+
var Mg = { exports: {} }, Rb;
|
|
7303
6909
|
function e0() {
|
|
7304
6910
|
return Rb || (Rb = 1, (function(Tt) {
|
|
7305
6911
|
process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
|
|
@@ -13066,10 +12672,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
|
|
|
13066
12672
|
function Ic() {
|
|
13067
12673
|
return di;
|
|
13068
12674
|
}
|
|
13069
|
-
var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
|
|
13070
|
-
var Ds = Symbol.for("react.activity");
|
|
13071
|
-
var Bh = Symbol.for("react.memo_cache_sentinel");
|
|
13072
|
-
var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
12675
|
+
var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
13073
12676
|
m.cloneMutableInstance;
|
|
13074
12677
|
var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
|
|
13075
12678
|
m.cloneMutableTextInstance;
|
|
@@ -14037,15 +13640,6 @@ function n0() {
|
|
|
14037
13640
|
var t0 = n0();
|
|
14038
13641
|
const r0 = Xb(t0);
|
|
14039
13642
|
|
|
14040
|
-
/**
|
|
14041
|
-
* @license React
|
|
14042
|
-
* react-reconciler-constants.production.js
|
|
14043
|
-
*
|
|
14044
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14045
|
-
*
|
|
14046
|
-
* This source code is licensed under the MIT license found in the
|
|
14047
|
-
* LICENSE file in the root directory of this source tree.
|
|
14048
|
-
*/
|
|
14049
13643
|
const t = 1, o = 8, r = 32, e = 2;
|
|
14050
13644
|
|
|
14051
13645
|
function createReconciler(config) {
|
|
@@ -14072,10 +13666,11 @@ function extend(objects) {
|
|
|
14072
13666
|
function validateInstance(type, props) {
|
|
14073
13667
|
const name = toPascalCase(type);
|
|
14074
13668
|
const target = catalogue[name];
|
|
14075
|
-
if (type !== "primitive" && !target)
|
|
13669
|
+
if (type !== "primitive" && !target) {
|
|
14076
13670
|
throw new Error(
|
|
14077
13671
|
`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
|
|
14078
13672
|
);
|
|
13673
|
+
}
|
|
14079
13674
|
if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
|
|
14080
13675
|
if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
|
|
14081
13676
|
}
|
|
@@ -14239,6 +13834,7 @@ function swapInstances() {
|
|
|
14239
13834
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14240
13835
|
instance.object.__r3f = instance;
|
|
14241
13836
|
setFiberRef(fiber, instance.object);
|
|
13837
|
+
delete instance.appliedOnce;
|
|
14242
13838
|
applyProps(instance.object, instance.props);
|
|
14243
13839
|
if (instance.props.attach) {
|
|
14244
13840
|
attach(parent, instance);
|
|
@@ -14312,8 +13908,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14312
13908
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14313
13909
|
if (isTailSibling) swapInstances();
|
|
14314
13910
|
},
|
|
14315
|
-
finalizeInitialChildren: () =>
|
|
14316
|
-
|
|
13911
|
+
finalizeInitialChildren: (instance) => {
|
|
13912
|
+
for (const prop in instance.props) {
|
|
13913
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13914
|
+
}
|
|
13915
|
+
return false;
|
|
13916
|
+
},
|
|
13917
|
+
commitMount(instance) {
|
|
13918
|
+
const resolved = {};
|
|
13919
|
+
for (const prop in instance.props) {
|
|
13920
|
+
const value = instance.props[prop];
|
|
13921
|
+
if (isFromRef(value)) {
|
|
13922
|
+
const ref = value[FROM_REF];
|
|
13923
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13924
|
+
}
|
|
13925
|
+
}
|
|
13926
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14317
13927
|
},
|
|
14318
13928
|
getPublicInstance: (instance) => instance?.object,
|
|
14319
13929
|
prepareForCommit: () => null,
|
|
@@ -14526,14 +14136,17 @@ function createRoot(canvas) {
|
|
|
14526
14136
|
if (!prevRoot) _roots.set(canvas, { fiber, store });
|
|
14527
14137
|
let onCreated;
|
|
14528
14138
|
let lastCamera;
|
|
14529
|
-
|
|
14139
|
+
const lastConfiguredProps = {};
|
|
14530
14140
|
let configured = false;
|
|
14531
14141
|
let pending = null;
|
|
14532
14142
|
return {
|
|
14533
14143
|
async configure(props = {}) {
|
|
14534
14144
|
let resolve;
|
|
14535
14145
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14536
|
-
|
|
14146
|
+
const {
|
|
14147
|
+
id: canvasId,
|
|
14148
|
+
primaryCanvas,
|
|
14149
|
+
scheduler: schedulerConfig,
|
|
14537
14150
|
gl: glConfig,
|
|
14538
14151
|
renderer: rendererConfig,
|
|
14539
14152
|
size: propsSize,
|
|
@@ -14541,10 +14154,6 @@ function createRoot(canvas) {
|
|
|
14541
14154
|
events,
|
|
14542
14155
|
onCreated: onCreatedCallback,
|
|
14543
14156
|
shadows = false,
|
|
14544
|
-
linear = false,
|
|
14545
|
-
flat = false,
|
|
14546
|
-
textureColorSpace = SRGBColorSpace,
|
|
14547
|
-
legacy = false,
|
|
14548
14157
|
orthographic = false,
|
|
14549
14158
|
frameloop = "always",
|
|
14550
14159
|
dpr = [1, 2],
|
|
@@ -14555,11 +14164,15 @@ function createRoot(canvas) {
|
|
|
14555
14164
|
onDragOverMissed,
|
|
14556
14165
|
onDropMissed,
|
|
14557
14166
|
autoUpdateFrustum = true,
|
|
14558
|
-
occlusion = false
|
|
14167
|
+
occlusion = false,
|
|
14168
|
+
_sizeProps,
|
|
14169
|
+
forceEven
|
|
14559
14170
|
} = props;
|
|
14560
|
-
|
|
14171
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14172
|
+
const state = store.getState();
|
|
14561
14173
|
const defaultGPUProps = {
|
|
14562
|
-
canvas
|
|
14174
|
+
canvas,
|
|
14175
|
+
antialias: true
|
|
14563
14176
|
};
|
|
14564
14177
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14565
14178
|
throw new Error(
|
|
@@ -14570,22 +14183,61 @@ function createRoot(canvas) {
|
|
|
14570
14183
|
throw new Error("Cannot use both gl and renderer props at the same time");
|
|
14571
14184
|
}
|
|
14572
14185
|
let renderer = state.internal.actualRenderer;
|
|
14573
|
-
if (!state.internal.actualRenderer) {
|
|
14186
|
+
if (primaryCanvas && !state.internal.actualRenderer) {
|
|
14187
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14188
|
+
renderer = primary.renderer;
|
|
14189
|
+
state.internal.actualRenderer = renderer;
|
|
14190
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14191
|
+
primary.store.setState((prev) => ({
|
|
14192
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14193
|
+
}));
|
|
14194
|
+
state.set((prev) => ({
|
|
14195
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14196
|
+
renderer,
|
|
14197
|
+
primaryStore: primary.store,
|
|
14198
|
+
internal: {
|
|
14199
|
+
...prev.internal,
|
|
14200
|
+
canvasTarget,
|
|
14201
|
+
isMultiCanvas: true,
|
|
14202
|
+
isSecondary: true,
|
|
14203
|
+
targetId: primaryCanvas
|
|
14204
|
+
}
|
|
14205
|
+
}));
|
|
14206
|
+
} else if (!state.internal.actualRenderer) {
|
|
14574
14207
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14575
14208
|
if (!renderer.hasInitialized?.()) {
|
|
14209
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14210
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14211
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14212
|
+
canvas.width = size2.width * pixelRatio;
|
|
14213
|
+
canvas.height = size2.height * pixelRatio;
|
|
14214
|
+
}
|
|
14576
14215
|
await renderer.init();
|
|
14577
14216
|
}
|
|
14578
14217
|
const backend = renderer.backend;
|
|
14579
14218
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14580
14219
|
state.internal.actualRenderer = renderer;
|
|
14581
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14220
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14221
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14222
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14223
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14224
|
+
state.set((prev) => ({
|
|
14225
|
+
internal: {
|
|
14226
|
+
...prev.internal,
|
|
14227
|
+
canvasTarget,
|
|
14228
|
+
unregisterPrimary
|
|
14229
|
+
}
|
|
14230
|
+
}));
|
|
14231
|
+
}
|
|
14582
14232
|
}
|
|
14583
14233
|
let raycaster = state.raycaster;
|
|
14584
14234
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
14585
14235
|
const { params, ...options } = raycastOptions || {};
|
|
14586
14236
|
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
|
|
14587
|
-
if (!is.equ(params, raycaster.params, shallowLoose))
|
|
14237
|
+
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14588
14238
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14239
|
+
}
|
|
14240
|
+
let tempCamera = state.camera;
|
|
14589
14241
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14590
14242
|
lastCamera = cameraOptions;
|
|
14591
14243
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14605,6 +14257,7 @@ function createRoot(canvas) {
|
|
|
14605
14257
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14606
14258
|
}
|
|
14607
14259
|
state.set({ camera });
|
|
14260
|
+
tempCamera = camera;
|
|
14608
14261
|
raycaster.camera = camera;
|
|
14609
14262
|
}
|
|
14610
14263
|
if (!state.scene) {
|
|
@@ -14622,7 +14275,7 @@ function createRoot(canvas) {
|
|
|
14622
14275
|
rootScene: scene,
|
|
14623
14276
|
internal: { ...prev.internal, container: scene }
|
|
14624
14277
|
}));
|
|
14625
|
-
const camera =
|
|
14278
|
+
const camera = tempCamera;
|
|
14626
14279
|
if (camera && !camera.parent) scene.add(camera);
|
|
14627
14280
|
}
|
|
14628
14281
|
if (events && !state.events.handlers) {
|
|
@@ -14636,9 +14289,17 @@ function createRoot(canvas) {
|
|
|
14636
14289
|
wasEnabled = enabled;
|
|
14637
14290
|
});
|
|
14638
14291
|
}
|
|
14292
|
+
if (_sizeProps !== void 0) {
|
|
14293
|
+
state.set({ _sizeProps });
|
|
14294
|
+
}
|
|
14295
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14296
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14297
|
+
}
|
|
14639
14298
|
const size = computeInitialSize(canvas, propsSize);
|
|
14640
|
-
if (!is.equ(size, state.size, shallowLoose)) {
|
|
14299
|
+
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14300
|
+
const wasImperative = state._sizeImperative;
|
|
14641
14301
|
state.setSize(size.width, size.height, size.top, size.left);
|
|
14302
|
+
if (!wasImperative) state.set({ _sizeImperative: false });
|
|
14642
14303
|
}
|
|
14643
14304
|
if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
|
|
14644
14305
|
state.setDpr(dpr);
|
|
@@ -14660,10 +14321,10 @@ function createRoot(canvas) {
|
|
|
14660
14321
|
lastConfiguredProps.performance = performance;
|
|
14661
14322
|
}
|
|
14662
14323
|
if (!state.xr) {
|
|
14663
|
-
const handleXRFrame = (timestamp,
|
|
14324
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14664
14325
|
const state2 = store.getState();
|
|
14665
14326
|
if (state2.frameloop === "never") return;
|
|
14666
|
-
advance(timestamp
|
|
14327
|
+
advance(timestamp);
|
|
14667
14328
|
};
|
|
14668
14329
|
const actualRenderer = state.internal.actualRenderer;
|
|
14669
14330
|
const handleSessionChange = () => {
|
|
@@ -14675,16 +14336,16 @@ function createRoot(canvas) {
|
|
|
14675
14336
|
};
|
|
14676
14337
|
const xr = {
|
|
14677
14338
|
connect() {
|
|
14678
|
-
const { gl, renderer: renderer2
|
|
14679
|
-
const
|
|
14680
|
-
|
|
14681
|
-
|
|
14339
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14340
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14341
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14342
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14682
14343
|
},
|
|
14683
14344
|
disconnect() {
|
|
14684
|
-
const { gl, renderer: renderer2
|
|
14685
|
-
const
|
|
14686
|
-
|
|
14687
|
-
|
|
14345
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14346
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14347
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14348
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14688
14349
|
}
|
|
14689
14350
|
};
|
|
14690
14351
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14696,41 +14357,92 @@ function createRoot(canvas) {
|
|
|
14696
14357
|
const oldType = renderer.shadowMap.type;
|
|
14697
14358
|
renderer.shadowMap.enabled = !!shadows;
|
|
14698
14359
|
if (is.boo(shadows)) {
|
|
14699
|
-
renderer.shadowMap.type =
|
|
14360
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
14700
14361
|
} else if (is.str(shadows)) {
|
|
14362
|
+
if (shadows === "soft") {
|
|
14363
|
+
notifyDepreciated({
|
|
14364
|
+
heading: 'shadows="soft" is deprecated',
|
|
14365
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14366
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14367
|
+
});
|
|
14368
|
+
}
|
|
14701
14369
|
const types = {
|
|
14702
14370
|
basic: BasicShadowMap,
|
|
14703
14371
|
percentage: PCFShadowMap,
|
|
14704
|
-
soft:
|
|
14372
|
+
soft: PCFShadowMap,
|
|
14705
14373
|
variance: VSMShadowMap
|
|
14706
14374
|
};
|
|
14707
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14375
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
14708
14376
|
} else if (is.obj(shadows)) {
|
|
14709
14377
|
Object.assign(renderer.shadowMap, shadows);
|
|
14710
14378
|
}
|
|
14711
|
-
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
|
|
14379
|
+
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
|
|
14712
14380
|
renderer.shadowMap.needsUpdate = true;
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14383
|
+
if (!configured) {
|
|
14384
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
14385
|
+
renderer.toneMapping = ACESFilmicToneMapping;
|
|
14713
14386
|
}
|
|
14714
14387
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14715
14388
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14716
14389
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14717
14390
|
}
|
|
14718
|
-
|
|
14719
|
-
|
|
14391
|
+
const r3fProps = ["textureColorSpace"];
|
|
14392
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14393
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14394
|
+
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14395
|
+
const glProps = {};
|
|
14396
|
+
for (const key in glConfig) {
|
|
14397
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14398
|
+
}
|
|
14399
|
+
applyProps(renderer, glProps);
|
|
14400
|
+
}
|
|
14720
14401
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14721
14402
|
const currentRenderer = state.renderer;
|
|
14722
14403
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14723
|
-
|
|
14404
|
+
const rendererProps = {};
|
|
14405
|
+
for (const key in rendererConfig) {
|
|
14406
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14407
|
+
}
|
|
14408
|
+
applyProps(currentRenderer, rendererProps);
|
|
14724
14409
|
}
|
|
14725
14410
|
}
|
|
14726
14411
|
const scheduler = getScheduler();
|
|
14727
14412
|
const rootId = state.internal.rootId;
|
|
14728
14413
|
if (!rootId) {
|
|
14729
|
-
const newRootId = scheduler.generateRootId();
|
|
14414
|
+
const newRootId = canvasId || scheduler.generateRootId();
|
|
14730
14415
|
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14731
14416
|
getState: () => store.getState(),
|
|
14732
14417
|
onError: (err) => store.getState().setError(err)
|
|
14733
14418
|
});
|
|
14419
|
+
const unregisterCanvasTarget = scheduler.register(
|
|
14420
|
+
() => {
|
|
14421
|
+
const state2 = store.getState();
|
|
14422
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14423
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14424
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14425
|
+
}
|
|
14426
|
+
},
|
|
14427
|
+
{
|
|
14428
|
+
id: `${newRootId}_canvasTarget`,
|
|
14429
|
+
rootId: newRootId,
|
|
14430
|
+
phase: "start",
|
|
14431
|
+
system: true
|
|
14432
|
+
}
|
|
14433
|
+
);
|
|
14434
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14435
|
+
() => {
|
|
14436
|
+
const state2 = store.getState();
|
|
14437
|
+
state2.events.flush?.();
|
|
14438
|
+
},
|
|
14439
|
+
{
|
|
14440
|
+
id: `${newRootId}_events`,
|
|
14441
|
+
rootId: newRootId,
|
|
14442
|
+
phase: "input",
|
|
14443
|
+
system: true
|
|
14444
|
+
}
|
|
14445
|
+
);
|
|
14734
14446
|
const unregisterFrustum = scheduler.register(
|
|
14735
14447
|
() => {
|
|
14736
14448
|
const state2 = store.getState();
|
|
@@ -14741,7 +14453,7 @@ function createRoot(canvas) {
|
|
|
14741
14453
|
{
|
|
14742
14454
|
id: `${newRootId}_frustum`,
|
|
14743
14455
|
rootId: newRootId,
|
|
14744
|
-
|
|
14456
|
+
before: "render",
|
|
14745
14457
|
system: true
|
|
14746
14458
|
}
|
|
14747
14459
|
);
|
|
@@ -14753,7 +14465,7 @@ function createRoot(canvas) {
|
|
|
14753
14465
|
{
|
|
14754
14466
|
id: `${newRootId}_visibility`,
|
|
14755
14467
|
rootId: newRootId,
|
|
14756
|
-
|
|
14468
|
+
before: "render",
|
|
14757
14469
|
system: true,
|
|
14758
14470
|
after: `${newRootId}_frustum`
|
|
14759
14471
|
}
|
|
@@ -14765,18 +14477,22 @@ function createRoot(canvas) {
|
|
|
14765
14477
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14766
14478
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14767
14479
|
try {
|
|
14768
|
-
if (state2.
|
|
14480
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14769
14481
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14770
14482
|
} catch (error) {
|
|
14771
14483
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14772
14484
|
}
|
|
14773
14485
|
},
|
|
14774
14486
|
{
|
|
14775
|
-
|
|
14487
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14488
|
+
id: canvasId || `${newRootId}_render`,
|
|
14776
14489
|
rootId: newRootId,
|
|
14777
14490
|
phase: "render",
|
|
14778
|
-
system: true
|
|
14491
|
+
system: true,
|
|
14779
14492
|
// Internal flag: this is a system job, not user-controlled
|
|
14493
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14494
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14495
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14780
14496
|
}
|
|
14781
14497
|
);
|
|
14782
14498
|
state.set((state2) => ({
|
|
@@ -14785,6 +14501,8 @@ function createRoot(canvas) {
|
|
|
14785
14501
|
rootId: newRootId,
|
|
14786
14502
|
unregisterRoot: () => {
|
|
14787
14503
|
unregisterRoot();
|
|
14504
|
+
unregisterCanvasTarget();
|
|
14505
|
+
unregisterEventsFlush();
|
|
14788
14506
|
unregisterFrustum();
|
|
14789
14507
|
unregisterVisibility();
|
|
14790
14508
|
unregisterRender();
|
|
@@ -14843,15 +14561,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14843
14561
|
const renderer = state.internal.actualRenderer;
|
|
14844
14562
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14845
14563
|
if (unregisterRoot) unregisterRoot();
|
|
14564
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14565
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14566
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14567
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14846
14568
|
state.events.disconnect?.();
|
|
14847
14569
|
cleanupHelperGroup(root.store);
|
|
14848
|
-
renderer
|
|
14849
|
-
|
|
14850
|
-
|
|
14570
|
+
if (state.isLegacy && renderer) {
|
|
14571
|
+
;
|
|
14572
|
+
renderer.renderLists?.dispose?.();
|
|
14573
|
+
renderer.forceContextLoss?.();
|
|
14574
|
+
}
|
|
14575
|
+
if (!state.internal.isSecondary) {
|
|
14576
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14577
|
+
}
|
|
14851
14578
|
dispose(state.scene);
|
|
14852
14579
|
_roots.delete(canvas);
|
|
14853
14580
|
if (callback) callback(canvas);
|
|
14854
|
-
} catch
|
|
14581
|
+
} catch {
|
|
14855
14582
|
}
|
|
14856
14583
|
}, 500);
|
|
14857
14584
|
}
|
|
@@ -14859,36 +14586,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14859
14586
|
}
|
|
14860
14587
|
}
|
|
14861
14588
|
function createPortal(children, container, state) {
|
|
14862
|
-
return /* @__PURE__ */ jsx(
|
|
14589
|
+
return /* @__PURE__ */ jsx(Portal, { children, container, state });
|
|
14863
14590
|
}
|
|
14864
|
-
function
|
|
14591
|
+
function Portal({ children, container, state }) {
|
|
14865
14592
|
const isRef = useCallback((obj) => obj && "current" in obj, []);
|
|
14866
|
-
const [resolvedContainer,
|
|
14593
|
+
const [resolvedContainer, _setResolvedContainer] = useState(() => {
|
|
14867
14594
|
if (isRef(container)) return container.current ?? null;
|
|
14868
14595
|
return container;
|
|
14869
14596
|
});
|
|
14597
|
+
const setResolvedContainer = useCallback(
|
|
14598
|
+
(newContainer) => {
|
|
14599
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14600
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14601
|
+
},
|
|
14602
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14603
|
+
);
|
|
14870
14604
|
useMemo(() => {
|
|
14871
|
-
if (isRef(container)) {
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
const updated = container.current;
|
|
14876
|
-
if (updated && updated !== resolvedContainer) {
|
|
14877
|
-
setResolvedContainer(updated);
|
|
14878
|
-
}
|
|
14879
|
-
});
|
|
14880
|
-
} else if (current !== resolvedContainer) {
|
|
14881
|
-
setResolvedContainer(current);
|
|
14882
|
-
}
|
|
14883
|
-
} else if (container !== resolvedContainer) {
|
|
14884
|
-
setResolvedContainer(container);
|
|
14605
|
+
if (isRef(container) && !container.current) {
|
|
14606
|
+
return queueMicrotask(() => {
|
|
14607
|
+
setResolvedContainer(container.current);
|
|
14608
|
+
});
|
|
14885
14609
|
}
|
|
14886
|
-
|
|
14610
|
+
setResolvedContainer(container);
|
|
14611
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14887
14612
|
if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
|
|
14888
14613
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14889
|
-
return /* @__PURE__ */ jsx(
|
|
14614
|
+
return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14890
14615
|
}
|
|
14891
|
-
function
|
|
14616
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14892
14617
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14893
14618
|
const previousRoot = useStore();
|
|
14894
14619
|
const [raycaster] = useState(() => new Raycaster());
|
|
@@ -14909,11 +14634,12 @@ function Portal({ state = {}, children, container }) {
|
|
|
14909
14634
|
};
|
|
14910
14635
|
}, [portalScene, container, injectScene]);
|
|
14911
14636
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14637
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14912
14638
|
let viewport = void 0;
|
|
14913
|
-
if (injectState.camera && size) {
|
|
14639
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14914
14640
|
const camera = injectState.camera;
|
|
14915
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(),
|
|
14916
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14641
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
14642
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14917
14643
|
}
|
|
14918
14644
|
return {
|
|
14919
14645
|
// The intersect consists of the previous root state
|
|
@@ -14930,7 +14656,7 @@ function Portal({ state = {}, children, container }) {
|
|
|
14930
14656
|
previousRoot,
|
|
14931
14657
|
// Events, size and viewport can be overridden by the inject layer
|
|
14932
14658
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14933
|
-
size:
|
|
14659
|
+
size: resolvedSize,
|
|
14934
14660
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14935
14661
|
// Layers are allowed to override events
|
|
14936
14662
|
setEvents: (events2) => injectState.set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
@@ -14942,9 +14668,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14942
14668
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14943
14669
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14944
14670
|
onMutate(previousRoot.getState());
|
|
14945
|
-
previousRoot.subscribe(onMutate);
|
|
14946
14671
|
return store;
|
|
14947
14672
|
}, [previousRoot, container]);
|
|
14673
|
+
useIsomorphicLayoutEffect(() => {
|
|
14674
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14675
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14676
|
+
return unsubscribe;
|
|
14677
|
+
}, [previousRoot, usePortalStore]);
|
|
14948
14678
|
return (
|
|
14949
14679
|
// @ts-ignore, reconciler types are not maintained
|
|
14950
14680
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -14958,21 +14688,59 @@ function flushSync(fn) {
|
|
|
14958
14688
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14959
14689
|
}
|
|
14960
14690
|
|
|
14691
|
+
function parseBackground(background) {
|
|
14692
|
+
if (!background) return null;
|
|
14693
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14694
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14695
|
+
return {
|
|
14696
|
+
...rest,
|
|
14697
|
+
preset,
|
|
14698
|
+
files: envMap || files,
|
|
14699
|
+
backgroundFiles: backgroundMap,
|
|
14700
|
+
background: true
|
|
14701
|
+
};
|
|
14702
|
+
}
|
|
14703
|
+
if (typeof background === "number") {
|
|
14704
|
+
return { color: background, background: true };
|
|
14705
|
+
}
|
|
14706
|
+
if (typeof background === "string") {
|
|
14707
|
+
if (background in presetsObj) {
|
|
14708
|
+
return { preset: background, background: true };
|
|
14709
|
+
}
|
|
14710
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14711
|
+
return { files: background, background: true };
|
|
14712
|
+
}
|
|
14713
|
+
return { color: background, background: true };
|
|
14714
|
+
}
|
|
14715
|
+
if (background.isColor) {
|
|
14716
|
+
return { color: background, background: true };
|
|
14717
|
+
}
|
|
14718
|
+
return null;
|
|
14719
|
+
}
|
|
14720
|
+
|
|
14721
|
+
function clearHmrCaches(store) {
|
|
14722
|
+
store.setState((state) => ({
|
|
14723
|
+
nodes: {},
|
|
14724
|
+
uniforms: {},
|
|
14725
|
+
buffers: {},
|
|
14726
|
+
gpuStorage: {},
|
|
14727
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14728
|
+
}));
|
|
14729
|
+
}
|
|
14730
|
+
|
|
14961
14731
|
function CanvasImpl({
|
|
14962
14732
|
ref,
|
|
14963
14733
|
children,
|
|
14964
14734
|
fallback,
|
|
14965
14735
|
resize,
|
|
14966
14736
|
style,
|
|
14737
|
+
id,
|
|
14967
14738
|
gl,
|
|
14968
|
-
renderer,
|
|
14739
|
+
renderer: rendererProp,
|
|
14969
14740
|
events = createPointerEvents,
|
|
14970
14741
|
eventSource,
|
|
14971
14742
|
eventPrefix,
|
|
14972
14743
|
shadows,
|
|
14973
|
-
linear,
|
|
14974
|
-
flat,
|
|
14975
|
-
legacy,
|
|
14976
14744
|
orthographic,
|
|
14977
14745
|
frameloop,
|
|
14978
14746
|
dpr,
|
|
@@ -14980,14 +14748,34 @@ function CanvasImpl({
|
|
|
14980
14748
|
raycaster,
|
|
14981
14749
|
camera,
|
|
14982
14750
|
scene,
|
|
14751
|
+
autoUpdateFrustum,
|
|
14752
|
+
occlusion,
|
|
14983
14753
|
onPointerMissed,
|
|
14984
14754
|
onDragOverMissed,
|
|
14985
14755
|
onDropMissed,
|
|
14986
14756
|
onCreated,
|
|
14757
|
+
hmr,
|
|
14758
|
+
width,
|
|
14759
|
+
height,
|
|
14760
|
+
background,
|
|
14761
|
+
forceEven,
|
|
14987
14762
|
...props
|
|
14988
14763
|
}) {
|
|
14764
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14765
|
+
let primaryCanvas;
|
|
14766
|
+
let scheduler;
|
|
14767
|
+
let renderer;
|
|
14768
|
+
if (isRendererConfig) {
|
|
14769
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14770
|
+
primaryCanvas = pc;
|
|
14771
|
+
scheduler = sc;
|
|
14772
|
+
renderer = Object.keys(rest).length > 0 ? rest : rendererProp;
|
|
14773
|
+
} else {
|
|
14774
|
+
renderer = rendererProp;
|
|
14775
|
+
}
|
|
14989
14776
|
React.useMemo(() => extend(THREE), []);
|
|
14990
14777
|
const Bridge = useBridge();
|
|
14778
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
14991
14779
|
const hasInitialSizeRef = React.useRef(false);
|
|
14992
14780
|
const measureConfig = React.useMemo(() => {
|
|
14993
14781
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15004,7 +14792,21 @@ function CanvasImpl({
|
|
|
15004
14792
|
};
|
|
15005
14793
|
}, [resize, hasInitialSizeRef.current]);
|
|
15006
14794
|
const [containerRef, containerRect] = useMeasure(measureConfig);
|
|
15007
|
-
|
|
14795
|
+
const effectiveSize = React.useMemo(() => {
|
|
14796
|
+
let w = width ?? containerRect.width;
|
|
14797
|
+
let h = height ?? containerRect.height;
|
|
14798
|
+
if (forceEven) {
|
|
14799
|
+
w = Math.ceil(w / 2) * 2;
|
|
14800
|
+
h = Math.ceil(h / 2) * 2;
|
|
14801
|
+
}
|
|
14802
|
+
return {
|
|
14803
|
+
width: w,
|
|
14804
|
+
height: h,
|
|
14805
|
+
top: containerRect.top,
|
|
14806
|
+
left: containerRect.left
|
|
14807
|
+
};
|
|
14808
|
+
}, [width, height, containerRect, forceEven]);
|
|
14809
|
+
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15008
14810
|
hasInitialSizeRef.current = true;
|
|
15009
14811
|
}
|
|
15010
14812
|
const canvasRef = React.useRef(null);
|
|
@@ -15023,7 +14825,7 @@ function CanvasImpl({
|
|
|
15023
14825
|
useIsomorphicLayoutEffect(() => {
|
|
15024
14826
|
effectActiveRef.current = true;
|
|
15025
14827
|
const canvas = canvasRef.current;
|
|
15026
|
-
if (
|
|
14828
|
+
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15027
14829
|
if (!root.current) {
|
|
15028
14830
|
root.current = createRoot(canvas);
|
|
15029
14831
|
notifyAlpha({
|
|
@@ -15043,21 +14845,26 @@ function CanvasImpl({
|
|
|
15043
14845
|
async function run() {
|
|
15044
14846
|
if (!effectActiveRef.current || !root.current) return;
|
|
15045
14847
|
await root.current.configure({
|
|
14848
|
+
id,
|
|
14849
|
+
primaryCanvas,
|
|
14850
|
+
scheduler,
|
|
15046
14851
|
gl,
|
|
15047
14852
|
renderer,
|
|
15048
14853
|
scene,
|
|
15049
14854
|
events,
|
|
15050
14855
|
shadows,
|
|
15051
|
-
linear,
|
|
15052
|
-
flat,
|
|
15053
|
-
legacy,
|
|
15054
14856
|
orthographic,
|
|
15055
14857
|
frameloop,
|
|
15056
14858
|
dpr,
|
|
15057
14859
|
performance,
|
|
15058
14860
|
raycaster,
|
|
15059
14861
|
camera,
|
|
15060
|
-
|
|
14862
|
+
autoUpdateFrustum,
|
|
14863
|
+
occlusion,
|
|
14864
|
+
size: effectiveSize,
|
|
14865
|
+
// Store size props for reset functionality
|
|
14866
|
+
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14867
|
+
forceEven,
|
|
15061
14868
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15062
14869
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15063
14870
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15081,7 +14888,10 @@ function CanvasImpl({
|
|
|
15081
14888
|
});
|
|
15082
14889
|
if (!effectActiveRef.current || !root.current) return;
|
|
15083
14890
|
root.current.render(
|
|
15084
|
-
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */
|
|
14891
|
+
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
14892
|
+
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
14893
|
+
children ?? null
|
|
14894
|
+
] }) }) })
|
|
15085
14895
|
);
|
|
15086
14896
|
}
|
|
15087
14897
|
run();
|
|
@@ -15102,7 +14912,29 @@ function CanvasImpl({
|
|
|
15102
14912
|
root.current = null;
|
|
15103
14913
|
};
|
|
15104
14914
|
}
|
|
15105
|
-
}, []);
|
|
14915
|
+
}, []);
|
|
14916
|
+
React.useEffect(() => {
|
|
14917
|
+
if (hmr === false) return;
|
|
14918
|
+
const canvas = canvasRef.current;
|
|
14919
|
+
if (!canvas) return;
|
|
14920
|
+
const handleHMR = () => {
|
|
14921
|
+
queueMicrotask(() => {
|
|
14922
|
+
const rootEntry = _roots.get(canvas);
|
|
14923
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
14924
|
+
});
|
|
14925
|
+
};
|
|
14926
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
14927
|
+
const hot = import.meta.hot;
|
|
14928
|
+
hot.on("vite:afterUpdate", handleHMR);
|
|
14929
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
14930
|
+
}
|
|
14931
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
14932
|
+
const hot = module.hot;
|
|
14933
|
+
hot.addStatusHandler((status) => {
|
|
14934
|
+
if (status === "idle") handleHMR();
|
|
14935
|
+
});
|
|
14936
|
+
}
|
|
14937
|
+
}, [hmr]);
|
|
15106
14938
|
const pointerEvents = eventSource ? "none" : "auto";
|
|
15107
14939
|
return /* @__PURE__ */ jsx(
|
|
15108
14940
|
"div",
|
|
@@ -15117,7 +14949,16 @@ function CanvasImpl({
|
|
|
15117
14949
|
...style
|
|
15118
14950
|
},
|
|
15119
14951
|
...props,
|
|
15120
|
-
children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
14952
|
+
children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
14953
|
+
"canvas",
|
|
14954
|
+
{
|
|
14955
|
+
ref: canvasRef,
|
|
14956
|
+
id,
|
|
14957
|
+
className: "r3f-canvas",
|
|
14958
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
14959
|
+
children: fallback
|
|
14960
|
+
}
|
|
14961
|
+
) })
|
|
15121
14962
|
}
|
|
15122
14963
|
);
|
|
15123
14964
|
}
|
|
@@ -15125,6 +14966,114 @@ function Canvas(props) {
|
|
|
15125
14966
|
return /* @__PURE__ */ jsx(FiberProvider, { children: /* @__PURE__ */ jsx(CanvasImpl, { ...props }) });
|
|
15126
14967
|
}
|
|
15127
14968
|
|
|
14969
|
+
var __defProp = Object.defineProperty;
|
|
14970
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14971
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14972
|
+
var _a;
|
|
14973
|
+
const INTERNAL_DATA = Symbol("ScopedStore.data");
|
|
14974
|
+
_a = INTERNAL_DATA;
|
|
14975
|
+
const _ScopedStore = class _ScopedStore {
|
|
14976
|
+
constructor(data) {
|
|
14977
|
+
__publicField(this, _a);
|
|
14978
|
+
this[INTERNAL_DATA] = data;
|
|
14979
|
+
return new Proxy(this, {
|
|
14980
|
+
get(target, prop, receiver) {
|
|
14981
|
+
if (typeof prop === "string") {
|
|
14982
|
+
if (prop === "scope" || prop === "has" || prop === "keys") {
|
|
14983
|
+
return Reflect.get(target, prop, receiver);
|
|
14984
|
+
}
|
|
14985
|
+
return target[INTERNAL_DATA][prop];
|
|
14986
|
+
}
|
|
14987
|
+
return Reflect.get(target, prop, receiver);
|
|
14988
|
+
},
|
|
14989
|
+
has(target, prop) {
|
|
14990
|
+
return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
|
|
14991
|
+
},
|
|
14992
|
+
ownKeys(target) {
|
|
14993
|
+
return Reflect.ownKeys(target[INTERNAL_DATA]);
|
|
14994
|
+
},
|
|
14995
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
14996
|
+
if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
|
|
14997
|
+
return {
|
|
14998
|
+
configurable: true,
|
|
14999
|
+
enumerable: true,
|
|
15000
|
+
value: target[INTERNAL_DATA][prop]
|
|
15001
|
+
};
|
|
15002
|
+
}
|
|
15003
|
+
return void 0;
|
|
15004
|
+
}
|
|
15005
|
+
});
|
|
15006
|
+
}
|
|
15007
|
+
/**
|
|
15008
|
+
* Access a nested scope by key.
|
|
15009
|
+
* If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
|
|
15010
|
+
*/
|
|
15011
|
+
scope(key) {
|
|
15012
|
+
const data = this[INTERNAL_DATA][key];
|
|
15013
|
+
return new _ScopedStore(
|
|
15014
|
+
data && typeof data === "object" ? data : {}
|
|
15015
|
+
);
|
|
15016
|
+
}
|
|
15017
|
+
/**
|
|
15018
|
+
* Check if a key exists in the store.
|
|
15019
|
+
*/
|
|
15020
|
+
has(key) {
|
|
15021
|
+
return key in this[INTERNAL_DATA];
|
|
15022
|
+
}
|
|
15023
|
+
/**
|
|
15024
|
+
* Get all keys in the store.
|
|
15025
|
+
*/
|
|
15026
|
+
keys() {
|
|
15027
|
+
return Object.keys(this[INTERNAL_DATA]);
|
|
15028
|
+
}
|
|
15029
|
+
};
|
|
15030
|
+
let ScopedStore = _ScopedStore;
|
|
15031
|
+
function createScopedStore(data) {
|
|
15032
|
+
return new ScopedStore(data);
|
|
15033
|
+
}
|
|
15034
|
+
function createLazyCreatorState(state) {
|
|
15035
|
+
let _uniforms = null;
|
|
15036
|
+
let _nodes = null;
|
|
15037
|
+
let _buffers = null;
|
|
15038
|
+
let _gpuStorage = null;
|
|
15039
|
+
return Object.create(state, {
|
|
15040
|
+
uniforms: {
|
|
15041
|
+
get() {
|
|
15042
|
+
return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
|
|
15043
|
+
}
|
|
15044
|
+
},
|
|
15045
|
+
nodes: {
|
|
15046
|
+
get() {
|
|
15047
|
+
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15048
|
+
}
|
|
15049
|
+
},
|
|
15050
|
+
buffers: {
|
|
15051
|
+
get() {
|
|
15052
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15053
|
+
}
|
|
15054
|
+
},
|
|
15055
|
+
gpuStorage: {
|
|
15056
|
+
get() {
|
|
15057
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15058
|
+
}
|
|
15059
|
+
}
|
|
15060
|
+
});
|
|
15061
|
+
}
|
|
15062
|
+
|
|
15063
|
+
function resolvePrimary(local) {
|
|
15064
|
+
return local.getState().primaryStore ?? local;
|
|
15065
|
+
}
|
|
15066
|
+
function usePrimaryStore() {
|
|
15067
|
+
const local = useStore();
|
|
15068
|
+
const primary = local.getState().primaryStore;
|
|
15069
|
+
return useMemo(() => primary ?? local, [primary, local]);
|
|
15070
|
+
}
|
|
15071
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15072
|
+
const local = useStore();
|
|
15073
|
+
const primary = resolvePrimary(local);
|
|
15074
|
+
return primary(selector, equalityFn);
|
|
15075
|
+
}
|
|
15076
|
+
|
|
15128
15077
|
function addTexture(set, key, value) {
|
|
15129
15078
|
set((state) => {
|
|
15130
15079
|
const newMap = new Map(state.textures);
|
|
@@ -15164,6 +15113,27 @@ function createTextureOperations(set) {
|
|
|
15164
15113
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15165
15114
|
};
|
|
15166
15115
|
}
|
|
15116
|
+
function extractTSLValue(value) {
|
|
15117
|
+
if (value === null || value === void 0) return value;
|
|
15118
|
+
if (typeof value !== "object") return value;
|
|
15119
|
+
const node = value;
|
|
15120
|
+
if (!node.isNode) return value;
|
|
15121
|
+
if (node.isConstNode) {
|
|
15122
|
+
return node.value;
|
|
15123
|
+
}
|
|
15124
|
+
if ("value" in node) {
|
|
15125
|
+
let extractedValue = node.value;
|
|
15126
|
+
if (typeof node.traverse === "function") {
|
|
15127
|
+
node.traverse((n) => {
|
|
15128
|
+
if (n.isConstNode) {
|
|
15129
|
+
extractedValue = n.value;
|
|
15130
|
+
}
|
|
15131
|
+
});
|
|
15132
|
+
}
|
|
15133
|
+
return extractedValue;
|
|
15134
|
+
}
|
|
15135
|
+
return value;
|
|
15136
|
+
}
|
|
15167
15137
|
function vectorize(inObject) {
|
|
15168
15138
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15169
15139
|
if (typeof inObject === "string") {
|
|
@@ -15176,9 +15146,16 @@ function vectorize(inObject) {
|
|
|
15176
15146
|
}
|
|
15177
15147
|
if (typeof inObject !== "object") return inObject;
|
|
15178
15148
|
const obj = inObject;
|
|
15149
|
+
if (obj.isNode) {
|
|
15150
|
+
return extractTSLValue(inObject);
|
|
15151
|
+
}
|
|
15179
15152
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15180
15153
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15181
15154
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15155
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15156
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15157
|
+
return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15158
|
+
}
|
|
15182
15159
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15183
15160
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15184
15161
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15202,7 +15179,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15202
15179
|
|
|
15203
15180
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15204
15181
|
function useUniforms(creatorOrScope, scope) {
|
|
15205
|
-
const store =
|
|
15182
|
+
const store = usePrimaryStore();
|
|
15206
15183
|
const removeUniforms2 = useCallback(
|
|
15207
15184
|
(names, targetScope) => {
|
|
15208
15185
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15238,21 +15215,55 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15238
15215
|
},
|
|
15239
15216
|
[store]
|
|
15240
15217
|
);
|
|
15218
|
+
const rebuildUniforms = useCallback(
|
|
15219
|
+
(targetScope) => {
|
|
15220
|
+
store.setState((state) => {
|
|
15221
|
+
let newUniforms = {};
|
|
15222
|
+
if (targetScope && targetScope !== "root") {
|
|
15223
|
+
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15224
|
+
newUniforms = rest;
|
|
15225
|
+
} else if (targetScope === "root") {
|
|
15226
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15227
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15228
|
+
}
|
|
15229
|
+
}
|
|
15230
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15231
|
+
});
|
|
15232
|
+
},
|
|
15233
|
+
[store]
|
|
15234
|
+
);
|
|
15241
15235
|
const inputForMemoization = useMemo(() => {
|
|
15242
|
-
|
|
15236
|
+
let raw = creatorOrScope;
|
|
15237
|
+
if (is.fun(creatorOrScope)) {
|
|
15238
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15239
|
+
raw = creatorOrScope(wrappedState);
|
|
15240
|
+
}
|
|
15241
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15242
|
+
const normalized = {};
|
|
15243
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15244
|
+
normalized[key] = vectorize(value);
|
|
15245
|
+
}
|
|
15246
|
+
return normalized;
|
|
15247
|
+
}
|
|
15248
|
+
return raw;
|
|
15243
15249
|
}, [creatorOrScope, store]);
|
|
15244
15250
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15251
|
+
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15252
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15253
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15254
|
+
const readerDep = isReader ? storeUniforms : null;
|
|
15255
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15245
15256
|
const uniforms = useMemo(() => {
|
|
15246
|
-
const state = store.getState();
|
|
15247
|
-
const set = store.setState;
|
|
15248
15257
|
if (memoizedInput === void 0) {
|
|
15249
|
-
return
|
|
15258
|
+
return storeUniforms;
|
|
15250
15259
|
}
|
|
15251
15260
|
if (typeof memoizedInput === "string") {
|
|
15252
|
-
const scopeData =
|
|
15261
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15253
15262
|
if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
|
|
15254
15263
|
return {};
|
|
15255
15264
|
}
|
|
15265
|
+
const state = store.getState();
|
|
15266
|
+
const set = store.setState;
|
|
15256
15267
|
if (typeof memoizedInput !== "object" || memoizedInput === null) {
|
|
15257
15268
|
throw new Error("Invalid uniform input");
|
|
15258
15269
|
}
|
|
@@ -15296,8 +15307,23 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15296
15307
|
}
|
|
15297
15308
|
}
|
|
15298
15309
|
return result;
|
|
15299
|
-
}, [store, memoizedInput, scope]);
|
|
15300
|
-
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
|
|
15310
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15311
|
+
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15312
|
+
}
|
|
15313
|
+
function rebuildAllUniforms(store, scope) {
|
|
15314
|
+
store = store.getState().primaryStore ?? store;
|
|
15315
|
+
store.setState((state) => {
|
|
15316
|
+
let newUniforms = {};
|
|
15317
|
+
if (scope && scope !== "root") {
|
|
15318
|
+
const { [scope]: _, ...rest } = state.uniforms;
|
|
15319
|
+
newUniforms = rest;
|
|
15320
|
+
} else if (scope === "root") {
|
|
15321
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15322
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15323
|
+
}
|
|
15324
|
+
}
|
|
15325
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15326
|
+
});
|
|
15301
15327
|
}
|
|
15302
15328
|
function removeUniforms(set, names, scope) {
|
|
15303
15329
|
set((state) => {
|
|
@@ -15362,15 +15388,17 @@ function isSameThreeType(a, b) {
|
|
|
15362
15388
|
}
|
|
15363
15389
|
|
|
15364
15390
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15391
|
+
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
15365
15392
|
function useUniform(name, value) {
|
|
15366
15393
|
const store = useStore();
|
|
15394
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15367
15395
|
return useMemo(() => {
|
|
15368
15396
|
const state = store.getState();
|
|
15369
15397
|
const set = store.setState;
|
|
15370
15398
|
const existing = state.uniforms[name];
|
|
15371
15399
|
if (existing && isUniformNode(existing)) {
|
|
15372
|
-
if (value !== void 0) {
|
|
15373
|
-
existing.value = value;
|
|
15400
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15401
|
+
existing.value = typeof value === "string" ? new Color(value) : value;
|
|
15374
15402
|
}
|
|
15375
15403
|
return existing;
|
|
15376
15404
|
}
|
|
@@ -15379,7 +15407,24 @@ function useUniform(name, value) {
|
|
|
15379
15407
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15380
15408
|
);
|
|
15381
15409
|
}
|
|
15382
|
-
|
|
15410
|
+
if (isUniformNode(value)) {
|
|
15411
|
+
const node2 = value;
|
|
15412
|
+
if (typeof node2.setName === "function") {
|
|
15413
|
+
node2.setName(name);
|
|
15414
|
+
}
|
|
15415
|
+
set((s) => ({
|
|
15416
|
+
uniforms: { ...s.uniforms, [name]: node2 }
|
|
15417
|
+
}));
|
|
15418
|
+
return node2;
|
|
15419
|
+
}
|
|
15420
|
+
let node;
|
|
15421
|
+
if (isTSLNode$1(value)) {
|
|
15422
|
+
node = uniform(value);
|
|
15423
|
+
} else if (typeof value === "string") {
|
|
15424
|
+
node = uniform(new Color(value));
|
|
15425
|
+
} else {
|
|
15426
|
+
node = uniform(value);
|
|
15427
|
+
}
|
|
15383
15428
|
if (typeof node.setName === "function") {
|
|
15384
15429
|
node.setName(name);
|
|
15385
15430
|
}
|
|
@@ -15390,12 +15435,12 @@ function useUniform(name, value) {
|
|
|
15390
15435
|
}
|
|
15391
15436
|
}));
|
|
15392
15437
|
return node;
|
|
15393
|
-
}, [store, name]);
|
|
15438
|
+
}, [store, name, hmrVersion]);
|
|
15394
15439
|
}
|
|
15395
15440
|
|
|
15396
15441
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15397
15442
|
function useNodes(creatorOrScope, scope) {
|
|
15398
|
-
const store =
|
|
15443
|
+
const store = usePrimaryStore();
|
|
15399
15444
|
const removeNodes2 = useCallback(
|
|
15400
15445
|
(names, targetScope) => {
|
|
15401
15446
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15431,19 +15476,46 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15431
15476
|
},
|
|
15432
15477
|
[store]
|
|
15433
15478
|
);
|
|
15479
|
+
const rebuildNodes = useCallback(
|
|
15480
|
+
(targetScope) => {
|
|
15481
|
+
store.setState((state) => {
|
|
15482
|
+
let newNodes = state.nodes;
|
|
15483
|
+
if (targetScope && targetScope !== "root") {
|
|
15484
|
+
const { [targetScope]: _, ...rest } = state.nodes;
|
|
15485
|
+
newNodes = rest;
|
|
15486
|
+
} else if (targetScope === "root") {
|
|
15487
|
+
newNodes = {};
|
|
15488
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15489
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15490
|
+
}
|
|
15491
|
+
} else {
|
|
15492
|
+
newNodes = {};
|
|
15493
|
+
}
|
|
15494
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15495
|
+
});
|
|
15496
|
+
},
|
|
15497
|
+
[store]
|
|
15498
|
+
);
|
|
15499
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15500
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15501
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15502
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15503
|
+
const readerDep = isReader ? storeNodes : null;
|
|
15504
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15434
15505
|
const nodes = useMemo(() => {
|
|
15435
|
-
const state = store.getState();
|
|
15436
|
-
const set = store.setState;
|
|
15437
15506
|
if (creatorOrScope === void 0) {
|
|
15438
|
-
return
|
|
15507
|
+
return storeNodes;
|
|
15439
15508
|
}
|
|
15440
15509
|
if (typeof creatorOrScope === "string") {
|
|
15441
|
-
const scopeData =
|
|
15510
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15442
15511
|
if (scopeData && !isTSLNode(scopeData)) return scopeData;
|
|
15443
15512
|
return {};
|
|
15444
15513
|
}
|
|
15514
|
+
const state = store.getState();
|
|
15515
|
+
const set = store.setState;
|
|
15445
15516
|
const creator = creatorOrScope;
|
|
15446
|
-
const
|
|
15517
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15518
|
+
const created = creator(wrappedState);
|
|
15447
15519
|
const result = {};
|
|
15448
15520
|
let hasNewNodes = false;
|
|
15449
15521
|
if (scope) {
|
|
@@ -15452,7 +15524,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15452
15524
|
if (currentScope[name]) {
|
|
15453
15525
|
result[name] = currentScope[name];
|
|
15454
15526
|
} else {
|
|
15455
|
-
|
|
15527
|
+
node.setName?.(`${scope}.${name}`);
|
|
15456
15528
|
result[name] = node;
|
|
15457
15529
|
hasNewNodes = true;
|
|
15458
15530
|
}
|
|
@@ -15472,7 +15544,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15472
15544
|
if (existing && isTSLNode(existing)) {
|
|
15473
15545
|
result[name] = existing;
|
|
15474
15546
|
} else {
|
|
15475
|
-
|
|
15547
|
+
node.setName?.(name);
|
|
15476
15548
|
result[name] = node;
|
|
15477
15549
|
hasNewNodes = true;
|
|
15478
15550
|
}
|
|
@@ -15481,8 +15553,26 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15481
15553
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15482
15554
|
}
|
|
15483
15555
|
return result;
|
|
15484
|
-
}, [store,
|
|
15485
|
-
return { ...nodes, removeNodes: removeNodes2, clearNodes };
|
|
15556
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15557
|
+
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15558
|
+
}
|
|
15559
|
+
function rebuildAllNodes(store, scope) {
|
|
15560
|
+
store = store.getState().primaryStore ?? store;
|
|
15561
|
+
store.setState((state) => {
|
|
15562
|
+
let newNodes = state.nodes;
|
|
15563
|
+
if (scope && scope !== "root") {
|
|
15564
|
+
const { [scope]: _, ...rest } = state.nodes;
|
|
15565
|
+
newNodes = rest;
|
|
15566
|
+
} else if (scope === "root") {
|
|
15567
|
+
newNodes = {};
|
|
15568
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15569
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15570
|
+
}
|
|
15571
|
+
} else {
|
|
15572
|
+
newNodes = {};
|
|
15573
|
+
}
|
|
15574
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15575
|
+
});
|
|
15486
15576
|
}
|
|
15487
15577
|
function removeNodes(set, names, scope) {
|
|
15488
15578
|
set((state) => {
|
|
@@ -15512,17 +15602,364 @@ function clearRootNodes(set) {
|
|
|
15512
15602
|
});
|
|
15513
15603
|
}
|
|
15514
15604
|
function useLocalNodes(creator) {
|
|
15515
|
-
const store =
|
|
15516
|
-
const uniforms =
|
|
15517
|
-
const nodes =
|
|
15518
|
-
const textures =
|
|
15605
|
+
const store = usePrimaryStore();
|
|
15606
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15607
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15608
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15609
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15519
15610
|
return useMemo(() => {
|
|
15611
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15612
|
+
return creator(wrappedState);
|
|
15613
|
+
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
15614
|
+
}
|
|
15615
|
+
|
|
15616
|
+
const isBufferLike = (value) => {
|
|
15617
|
+
if (value === null || typeof value !== "object") return false;
|
|
15618
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15619
|
+
if ("isBufferAttribute" in value) return true;
|
|
15620
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15621
|
+
return false;
|
|
15622
|
+
};
|
|
15623
|
+
const disposeBuffer = (buffer) => {
|
|
15624
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15625
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15626
|
+
buffer.dispose();
|
|
15627
|
+
}
|
|
15628
|
+
};
|
|
15629
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15630
|
+
const store = usePrimaryStore();
|
|
15631
|
+
const removeBuffers = useCallback(
|
|
15632
|
+
(names, targetScope) => {
|
|
15633
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15634
|
+
store.setState((state) => {
|
|
15635
|
+
if (targetScope) {
|
|
15636
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15637
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15638
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15639
|
+
}
|
|
15640
|
+
const buffers2 = { ...state.buffers };
|
|
15641
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15642
|
+
return { buffers: buffers2 };
|
|
15643
|
+
});
|
|
15644
|
+
},
|
|
15645
|
+
[store]
|
|
15646
|
+
);
|
|
15647
|
+
const clearBuffers = useCallback(
|
|
15648
|
+
(targetScope) => {
|
|
15649
|
+
store.setState((state) => {
|
|
15650
|
+
if (targetScope && targetScope !== "root") {
|
|
15651
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15652
|
+
return { buffers: rest };
|
|
15653
|
+
}
|
|
15654
|
+
if (targetScope === "root") {
|
|
15655
|
+
const buffers2 = {};
|
|
15656
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15657
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15658
|
+
}
|
|
15659
|
+
return { buffers: buffers2 };
|
|
15660
|
+
}
|
|
15661
|
+
return { buffers: {} };
|
|
15662
|
+
});
|
|
15663
|
+
},
|
|
15664
|
+
[store]
|
|
15665
|
+
);
|
|
15666
|
+
const rebuildBuffers = useCallback(
|
|
15667
|
+
(targetScope) => {
|
|
15668
|
+
store.setState((state) => {
|
|
15669
|
+
let newBuffers = state.buffers;
|
|
15670
|
+
if (targetScope && targetScope !== "root") {
|
|
15671
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15672
|
+
newBuffers = rest;
|
|
15673
|
+
} else if (targetScope === "root") {
|
|
15674
|
+
newBuffers = {};
|
|
15675
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15676
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15677
|
+
}
|
|
15678
|
+
} else {
|
|
15679
|
+
newBuffers = {};
|
|
15680
|
+
}
|
|
15681
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15682
|
+
});
|
|
15683
|
+
},
|
|
15684
|
+
[store]
|
|
15685
|
+
);
|
|
15686
|
+
const disposeBuffers = useCallback(
|
|
15687
|
+
(names, targetScope) => {
|
|
15688
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15689
|
+
const state = store.getState();
|
|
15690
|
+
for (const name of nameArray) {
|
|
15691
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15692
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15693
|
+
disposeBuffer(buffer);
|
|
15694
|
+
}
|
|
15695
|
+
}
|
|
15696
|
+
removeBuffers(names, targetScope);
|
|
15697
|
+
},
|
|
15698
|
+
[store, removeBuffers]
|
|
15699
|
+
);
|
|
15700
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15701
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15702
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15703
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15704
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15705
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15706
|
+
const buffers = useMemo(() => {
|
|
15707
|
+
if (creatorOrScope === void 0) {
|
|
15708
|
+
return storeBuffers;
|
|
15709
|
+
}
|
|
15710
|
+
if (typeof creatorOrScope === "string") {
|
|
15711
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15712
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15713
|
+
return {};
|
|
15714
|
+
}
|
|
15715
|
+
const state = store.getState();
|
|
15716
|
+
const set = store.setState;
|
|
15717
|
+
const creator = creatorOrScope;
|
|
15718
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15719
|
+
const created = creator(wrappedState);
|
|
15720
|
+
const result = {};
|
|
15721
|
+
let hasNewBuffers = false;
|
|
15722
|
+
if (scope) {
|
|
15723
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15724
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15725
|
+
if (currentScope[name]) {
|
|
15726
|
+
result[name] = currentScope[name];
|
|
15727
|
+
} else {
|
|
15728
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15729
|
+
buffer.setName(`${scope}.${name}`);
|
|
15730
|
+
}
|
|
15731
|
+
result[name] = buffer;
|
|
15732
|
+
hasNewBuffers = true;
|
|
15733
|
+
}
|
|
15734
|
+
}
|
|
15735
|
+
if (hasNewBuffers) {
|
|
15736
|
+
set((s) => ({
|
|
15737
|
+
buffers: {
|
|
15738
|
+
...s.buffers,
|
|
15739
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15740
|
+
}
|
|
15741
|
+
}));
|
|
15742
|
+
}
|
|
15743
|
+
return result;
|
|
15744
|
+
}
|
|
15745
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15746
|
+
const existing = state.buffers[name];
|
|
15747
|
+
if (existing && isBufferLike(existing)) {
|
|
15748
|
+
result[name] = existing;
|
|
15749
|
+
} else {
|
|
15750
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15751
|
+
buffer.setName(name);
|
|
15752
|
+
}
|
|
15753
|
+
result[name] = buffer;
|
|
15754
|
+
hasNewBuffers = true;
|
|
15755
|
+
}
|
|
15756
|
+
}
|
|
15757
|
+
if (hasNewBuffers) {
|
|
15758
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15759
|
+
}
|
|
15760
|
+
return result;
|
|
15761
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15762
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15763
|
+
}
|
|
15764
|
+
function rebuildAllBuffers(store, scope) {
|
|
15765
|
+
store = store.getState().primaryStore ?? store;
|
|
15766
|
+
store.setState((state) => {
|
|
15767
|
+
let newBuffers = state.buffers;
|
|
15768
|
+
if (scope && scope !== "root") {
|
|
15769
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15770
|
+
newBuffers = rest;
|
|
15771
|
+
} else if (scope === "root") {
|
|
15772
|
+
newBuffers = {};
|
|
15773
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15774
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15775
|
+
}
|
|
15776
|
+
} else {
|
|
15777
|
+
newBuffers = {};
|
|
15778
|
+
}
|
|
15779
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15780
|
+
});
|
|
15781
|
+
}
|
|
15782
|
+
|
|
15783
|
+
const isStorageLike = (value) => {
|
|
15784
|
+
if (value === null || typeof value !== "object") return false;
|
|
15785
|
+
if ("isTexture" in value) return true;
|
|
15786
|
+
if ("isData3DTexture" in value) return true;
|
|
15787
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15788
|
+
return false;
|
|
15789
|
+
};
|
|
15790
|
+
const disposeStorage = (storage) => {
|
|
15791
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15792
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15793
|
+
storage.dispose();
|
|
15794
|
+
}
|
|
15795
|
+
};
|
|
15796
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15797
|
+
const store = usePrimaryStore();
|
|
15798
|
+
const removeStorage = useCallback(
|
|
15799
|
+
(names, targetScope) => {
|
|
15800
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15801
|
+
store.setState((state) => {
|
|
15802
|
+
if (targetScope) {
|
|
15803
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15804
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15805
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15806
|
+
}
|
|
15807
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15808
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15809
|
+
return { gpuStorage: gpuStorage2 };
|
|
15810
|
+
});
|
|
15811
|
+
},
|
|
15812
|
+
[store]
|
|
15813
|
+
);
|
|
15814
|
+
const clearStorage = useCallback(
|
|
15815
|
+
(targetScope) => {
|
|
15816
|
+
store.setState((state) => {
|
|
15817
|
+
if (targetScope && targetScope !== "root") {
|
|
15818
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15819
|
+
return { gpuStorage: rest };
|
|
15820
|
+
}
|
|
15821
|
+
if (targetScope === "root") {
|
|
15822
|
+
const gpuStorage2 = {};
|
|
15823
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15824
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15825
|
+
}
|
|
15826
|
+
return { gpuStorage: gpuStorage2 };
|
|
15827
|
+
}
|
|
15828
|
+
return { gpuStorage: {} };
|
|
15829
|
+
});
|
|
15830
|
+
},
|
|
15831
|
+
[store]
|
|
15832
|
+
);
|
|
15833
|
+
const rebuildStorage = useCallback(
|
|
15834
|
+
(targetScope) => {
|
|
15835
|
+
store.setState((state) => {
|
|
15836
|
+
let newStorage = state.gpuStorage;
|
|
15837
|
+
if (targetScope && targetScope !== "root") {
|
|
15838
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15839
|
+
newStorage = rest;
|
|
15840
|
+
} else if (targetScope === "root") {
|
|
15841
|
+
newStorage = {};
|
|
15842
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15843
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15844
|
+
}
|
|
15845
|
+
} else {
|
|
15846
|
+
newStorage = {};
|
|
15847
|
+
}
|
|
15848
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15849
|
+
});
|
|
15850
|
+
},
|
|
15851
|
+
[store]
|
|
15852
|
+
);
|
|
15853
|
+
const disposeStorageFn = useCallback(
|
|
15854
|
+
(names, targetScope) => {
|
|
15855
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15856
|
+
const state = store.getState();
|
|
15857
|
+
for (const name of nameArray) {
|
|
15858
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15859
|
+
if (storage && isStorageLike(storage)) {
|
|
15860
|
+
disposeStorage(storage);
|
|
15861
|
+
}
|
|
15862
|
+
}
|
|
15863
|
+
removeStorage(names, targetScope);
|
|
15864
|
+
},
|
|
15865
|
+
[store, removeStorage]
|
|
15866
|
+
);
|
|
15867
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15868
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15869
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15870
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15871
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15872
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15873
|
+
const gpuStorage = useMemo(() => {
|
|
15874
|
+
if (creatorOrScope === void 0) {
|
|
15875
|
+
return storeStorage;
|
|
15876
|
+
}
|
|
15877
|
+
if (typeof creatorOrScope === "string") {
|
|
15878
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15879
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15880
|
+
return {};
|
|
15881
|
+
}
|
|
15520
15882
|
const state = store.getState();
|
|
15521
|
-
|
|
15522
|
-
|
|
15883
|
+
const set = store.setState;
|
|
15884
|
+
const creator = creatorOrScope;
|
|
15885
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15886
|
+
const created = creator(wrappedState);
|
|
15887
|
+
const result = {};
|
|
15888
|
+
let hasNewStorage = false;
|
|
15889
|
+
if (scope) {
|
|
15890
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15891
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15892
|
+
if (currentScope[name]) {
|
|
15893
|
+
result[name] = currentScope[name];
|
|
15894
|
+
} else {
|
|
15895
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15896
|
+
storage.setName(`${scope}.${name}`);
|
|
15897
|
+
}
|
|
15898
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15899
|
+
storage.name = `${scope}.${name}`;
|
|
15900
|
+
}
|
|
15901
|
+
result[name] = storage;
|
|
15902
|
+
hasNewStorage = true;
|
|
15903
|
+
}
|
|
15904
|
+
}
|
|
15905
|
+
if (hasNewStorage) {
|
|
15906
|
+
set((s) => ({
|
|
15907
|
+
gpuStorage: {
|
|
15908
|
+
...s.gpuStorage,
|
|
15909
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
15910
|
+
}
|
|
15911
|
+
}));
|
|
15912
|
+
}
|
|
15913
|
+
return result;
|
|
15914
|
+
}
|
|
15915
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15916
|
+
const existing = state.gpuStorage[name];
|
|
15917
|
+
if (existing && isStorageLike(existing)) {
|
|
15918
|
+
result[name] = existing;
|
|
15919
|
+
} else {
|
|
15920
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15921
|
+
storage.setName(name);
|
|
15922
|
+
}
|
|
15923
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15924
|
+
storage.name = name;
|
|
15925
|
+
}
|
|
15926
|
+
result[name] = storage;
|
|
15927
|
+
hasNewStorage = true;
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
15930
|
+
if (hasNewStorage) {
|
|
15931
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
15932
|
+
}
|
|
15933
|
+
return result;
|
|
15934
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15935
|
+
return {
|
|
15936
|
+
...gpuStorage,
|
|
15937
|
+
removeStorage,
|
|
15938
|
+
clearStorage,
|
|
15939
|
+
rebuildStorage,
|
|
15940
|
+
disposeStorage: disposeStorageFn
|
|
15941
|
+
};
|
|
15942
|
+
}
|
|
15943
|
+
function rebuildAllStorage(store, scope) {
|
|
15944
|
+
store = store.getState().primaryStore ?? store;
|
|
15945
|
+
store.setState((state) => {
|
|
15946
|
+
let newStorage = state.gpuStorage;
|
|
15947
|
+
if (scope && scope !== "root") {
|
|
15948
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
15949
|
+
newStorage = rest;
|
|
15950
|
+
} else if (scope === "root") {
|
|
15951
|
+
newStorage = {};
|
|
15952
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15953
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15954
|
+
}
|
|
15955
|
+
} else {
|
|
15956
|
+
newStorage = {};
|
|
15957
|
+
}
|
|
15958
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15959
|
+
});
|
|
15523
15960
|
}
|
|
15524
15961
|
|
|
15525
|
-
function
|
|
15962
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15526
15963
|
const store = useStore();
|
|
15527
15964
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15528
15965
|
const callbacksRanRef = useRef(false);
|
|
@@ -15532,12 +15969,16 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15532
15969
|
mainCBRef.current = mainCB;
|
|
15533
15970
|
setupCBRef.current = setupCB;
|
|
15534
15971
|
const [rebuildVersion, setRebuildVersion] = useState(0);
|
|
15972
|
+
useEffect(() => {
|
|
15973
|
+
callbacksRanRef.current = false;
|
|
15974
|
+
scenePassCacheRef.current = null;
|
|
15975
|
+
}, []);
|
|
15535
15976
|
const clearPasses = useCallback(() => {
|
|
15536
15977
|
store.setState({ passes: {} });
|
|
15537
15978
|
}, [store]);
|
|
15538
15979
|
const reset = useCallback(() => {
|
|
15539
15980
|
store.setState({
|
|
15540
|
-
|
|
15981
|
+
renderPipeline: null,
|
|
15541
15982
|
passes: {}
|
|
15542
15983
|
});
|
|
15543
15984
|
callbacksRanRef.current = false;
|
|
@@ -15550,13 +15991,13 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15550
15991
|
}, []);
|
|
15551
15992
|
useLayoutEffect(() => {
|
|
15552
15993
|
if (isLegacy) {
|
|
15553
|
-
throw new Error("
|
|
15994
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15554
15995
|
}
|
|
15555
15996
|
if (!renderer || !scene || !camera) return;
|
|
15556
15997
|
const state = store.getState();
|
|
15557
15998
|
const set = store.setState;
|
|
15558
15999
|
try {
|
|
15559
|
-
let pp = state.
|
|
16000
|
+
let pp = state.renderPipeline;
|
|
15560
16001
|
let currentPasses = { ...state.passes };
|
|
15561
16002
|
let justCreatedPP = false;
|
|
15562
16003
|
if (!pp) {
|
|
@@ -15573,7 +16014,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15573
16014
|
}
|
|
15574
16015
|
currentPasses.scenePass = scenePass;
|
|
15575
16016
|
if (!pp.outputNode || justCreatedPP) pp.outputNode = scenePass;
|
|
15576
|
-
set({
|
|
16017
|
+
set({ renderPipeline: pp, passes: currentPasses });
|
|
15577
16018
|
const shouldRunCallbacks = justCreatedPP || !callbacksRanRef.current || !cacheValid;
|
|
15578
16019
|
if (shouldRunCallbacks) {
|
|
15579
16020
|
if (setupCBRef.current) {
|
|
@@ -15595,22 +16036,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15595
16036
|
callbacksRanRef.current = true;
|
|
15596
16037
|
}
|
|
15597
16038
|
} catch (error) {
|
|
15598
|
-
console.error("[
|
|
16039
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15599
16040
|
}
|
|
15600
16041
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15601
16042
|
const passes = useThree((s) => s.passes);
|
|
15602
|
-
const
|
|
16043
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15603
16044
|
return {
|
|
15604
16045
|
passes,
|
|
15605
|
-
|
|
16046
|
+
renderPipeline,
|
|
15606
16047
|
clearPasses,
|
|
15607
16048
|
reset,
|
|
15608
16049
|
rebuild,
|
|
15609
|
-
// isReady indicates if
|
|
15610
|
-
isReady:
|
|
16050
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16051
|
+
isReady: renderPipeline !== null
|
|
15611
16052
|
};
|
|
15612
16053
|
}
|
|
15613
16054
|
|
|
15614
16055
|
extend(THREE);
|
|
15615
16056
|
|
|
15616
|
-
export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS,
|
|
16057
|
+
export { Block, Canvas, Environment, EnvironmentCube, EnvironmentMap, EnvironmentPortal, ErrorBoundary, FROM_REF, IsObject, ONCE, Portal, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createScopedStore, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, fromRef, getInstanceProps, getPrimary, getPrimaryIds, getRootState, getUuidPrefix, hasConstructor, hasPrimary, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isFromRef, isObject3D, isOnce, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, unregisterPrimary, updateCamera, updateFrustum, useBridge, useBuffers, useEnvironment, useFrame, useGPUStorage, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, useRenderPipeline, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms, waitForPrimary };
|