@react-three/fiber 10.0.0-alpha.2 → 10.0.0-canary.0706a92
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 +1274 -1405
- package/dist/index.d.cts +1888 -1713
- package/dist/index.d.mts +1888 -1713
- package/dist/index.d.ts +1888 -1713
- package/dist/index.mjs +1235 -1384
- package/dist/legacy.cjs +1231 -1386
- package/dist/legacy.d.cts +1889 -1714
- package/dist/legacy.d.mts +1889 -1714
- package/dist/legacy.d.ts +1889 -1714
- package/dist/legacy.mjs +1192 -1365
- package/dist/webgpu/index.cjs +1832 -1516
- package/dist/webgpu/index.d.cts +2082 -1737
- package/dist/webgpu/index.d.mts +2082 -1737
- package/dist/webgpu/index.d.ts +2082 -1737
- package/dist/webgpu/index.mjs +1790 -1496
- package/package.json +4 -1
- 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, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, Vector4, RenderPipeline, 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 = {
|
|
@@ -274,7 +665,7 @@ function createOcclusionObserverNode(store, uniform) {
|
|
|
274
665
|
let occlusionSetupPromise = null;
|
|
275
666
|
function enableOcclusion(store) {
|
|
276
667
|
const state = store.getState();
|
|
277
|
-
const { internal, renderer
|
|
668
|
+
const { internal, renderer } = state;
|
|
278
669
|
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
279
670
|
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
280
671
|
if (!hasOcclusionSupport) {
|
|
@@ -382,6 +773,17 @@ function unregisterVisibility(store, object) {
|
|
|
382
773
|
internal.visibilityRegistry.delete(object.uuid);
|
|
383
774
|
internal.occlusionCache.delete(object);
|
|
384
775
|
}
|
|
776
|
+
function updateVisibilityHandlers(store, object, handlers) {
|
|
777
|
+
const { internal } = store.getState();
|
|
778
|
+
const entry = internal.visibilityRegistry.get(object.uuid);
|
|
779
|
+
if (!entry) return false;
|
|
780
|
+
entry.handlers = handlers;
|
|
781
|
+
if ((handlers.onOccluded || handlers.onVisible) && !internal.occlusionEnabled) {
|
|
782
|
+
object.occlusionTest = true;
|
|
783
|
+
enableOcclusion(store);
|
|
784
|
+
}
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
385
787
|
function checkVisibility(state) {
|
|
386
788
|
const { internal, camera } = state;
|
|
387
789
|
const registry = internal.visibilityRegistry;
|
|
@@ -437,6 +839,22 @@ function hasVisibilityHandlers(handlers) {
|
|
|
437
839
|
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
438
840
|
}
|
|
439
841
|
|
|
842
|
+
const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
|
|
843
|
+
function fromRef(ref) {
|
|
844
|
+
return { [FROM_REF]: ref };
|
|
845
|
+
}
|
|
846
|
+
function isFromRef(value) {
|
|
847
|
+
return value !== null && typeof value === "object" && FROM_REF in value;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const ONCE = Symbol.for("@react-three/fiber.once");
|
|
851
|
+
function once(...args) {
|
|
852
|
+
return { [ONCE]: args.length ? args : true };
|
|
853
|
+
}
|
|
854
|
+
function isOnce(value) {
|
|
855
|
+
return value !== null && typeof value === "object" && ONCE in value;
|
|
856
|
+
}
|
|
857
|
+
|
|
440
858
|
const RESERVED_PROPS = [
|
|
441
859
|
"children",
|
|
442
860
|
"key",
|
|
@@ -507,7 +925,7 @@ function getMemoizedPrototype(root) {
|
|
|
507
925
|
ctor = new root.constructor();
|
|
508
926
|
MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
|
|
509
927
|
}
|
|
510
|
-
} catch
|
|
928
|
+
} catch {
|
|
511
929
|
}
|
|
512
930
|
return ctor;
|
|
513
931
|
}
|
|
@@ -553,6 +971,22 @@ function applyProps(object, props) {
|
|
|
553
971
|
continue;
|
|
554
972
|
}
|
|
555
973
|
if (value === void 0) continue;
|
|
974
|
+
if (isFromRef(value)) continue;
|
|
975
|
+
if (isOnce(value)) {
|
|
976
|
+
if (instance?.appliedOnce?.has(prop)) continue;
|
|
977
|
+
if (instance) {
|
|
978
|
+
instance.appliedOnce ?? (instance.appliedOnce = /* @__PURE__ */ new Set());
|
|
979
|
+
instance.appliedOnce.add(prop);
|
|
980
|
+
}
|
|
981
|
+
const { root: targetRoot, key: targetKey } = resolve(object, prop);
|
|
982
|
+
const args = value[ONCE];
|
|
983
|
+
if (typeof targetRoot[targetKey] === "function") {
|
|
984
|
+
targetRoot[targetKey](...args === true ? [] : args);
|
|
985
|
+
} else if (args !== true && args.length > 0) {
|
|
986
|
+
targetRoot[targetKey] = args[0];
|
|
987
|
+
}
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
556
990
|
let { root, key, target } = resolve(object, prop);
|
|
557
991
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
558
992
|
throw Error(`R3F: Cannot set "${prop}". Ensure it is an object before setting "${key}".`);
|
|
@@ -573,9 +1007,25 @@ function applyProps(object, props) {
|
|
|
573
1007
|
} else if (isVectorLike(target) && is.num(value)) {
|
|
574
1008
|
if ("setScalar" in target && typeof target.setScalar === "function") target.setScalar(value);
|
|
575
1009
|
else target.set(value);
|
|
1010
|
+
} else if (root instanceof ShaderMaterial && key === "uniforms" && is.obj(value)) {
|
|
1011
|
+
if (!is.obj(root.uniforms)) root.uniforms = {};
|
|
1012
|
+
const uniforms = root.uniforms;
|
|
1013
|
+
const nextUniforms = value;
|
|
1014
|
+
for (const name in nextUniforms) {
|
|
1015
|
+
const uniform = nextUniforms[name];
|
|
1016
|
+
const targetUniform = uniforms[name];
|
|
1017
|
+
if (targetUniform) Object.assign(targetUniform, uniform);
|
|
1018
|
+
else {
|
|
1019
|
+
const nextUniform = uniform instanceof Uniform ? uniform.clone() : new Uniform(uniform.value);
|
|
1020
|
+
uniforms[name] = nextUniform;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
576
1023
|
} else {
|
|
577
1024
|
root[key] = value;
|
|
578
|
-
if (
|
|
1025
|
+
if (key.endsWith("Node") && root.isMaterial) {
|
|
1026
|
+
root.needsUpdate = true;
|
|
1027
|
+
}
|
|
1028
|
+
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
1029
|
root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
|
|
580
1030
|
root[key].colorSpace = rootState.textureColorSpace;
|
|
581
1031
|
}
|
|
@@ -588,6 +1038,9 @@ function applyProps(object, props) {
|
|
|
588
1038
|
if (instance.eventCount && object2.raycast !== null) {
|
|
589
1039
|
rootState.internal.interaction.push(object2);
|
|
590
1040
|
}
|
|
1041
|
+
}
|
|
1042
|
+
if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
|
|
1043
|
+
const object2 = instance.object;
|
|
591
1044
|
const root = findInitialRoot(instance);
|
|
592
1045
|
const visibilityHandlers = {
|
|
593
1046
|
onFramed: instance.handlers.onFramed,
|
|
@@ -595,7 +1048,9 @@ function applyProps(object, props) {
|
|
|
595
1048
|
onVisible: instance.handlers.onVisible
|
|
596
1049
|
};
|
|
597
1050
|
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
598
|
-
|
|
1051
|
+
if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
|
|
1052
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
1053
|
+
}
|
|
599
1054
|
} else {
|
|
600
1055
|
unregisterVisibility(root, object2);
|
|
601
1056
|
}
|
|
@@ -608,38 +1063,60 @@ function applyProps(object, props) {
|
|
|
608
1063
|
return object;
|
|
609
1064
|
}
|
|
610
1065
|
|
|
1066
|
+
const DEFAULT_POINTER_ID = 0;
|
|
1067
|
+
const XR_POINTER_ID_START = 1e3;
|
|
1068
|
+
function getPointerState(internal, pointerId) {
|
|
1069
|
+
let state = internal.pointerMap.get(pointerId);
|
|
1070
|
+
if (!state) {
|
|
1071
|
+
state = {
|
|
1072
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1073
|
+
captured: /* @__PURE__ */ new Map(),
|
|
1074
|
+
initialClick: [0, 0],
|
|
1075
|
+
initialHits: []
|
|
1076
|
+
};
|
|
1077
|
+
internal.pointerMap.set(pointerId, state);
|
|
1078
|
+
}
|
|
1079
|
+
return state;
|
|
1080
|
+
}
|
|
1081
|
+
function getPointerId(event) {
|
|
1082
|
+
return "pointerId" in event ? event.pointerId : DEFAULT_POINTER_ID;
|
|
1083
|
+
}
|
|
611
1084
|
function makeId(event) {
|
|
612
1085
|
return (event.eventObject || event.object).uuid + "/" + event.index + event.instanceId;
|
|
613
1086
|
}
|
|
614
|
-
function releaseInternalPointerCapture(
|
|
615
|
-
const
|
|
1087
|
+
function releaseInternalPointerCapture(internal, obj, pointerId) {
|
|
1088
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1089
|
+
if (!pointerState) return;
|
|
1090
|
+
const captureData = pointerState.captured.get(obj);
|
|
616
1091
|
if (captureData) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
capturedMap.delete(pointerId);
|
|
620
|
-
captureData.target.releasePointerCapture(pointerId);
|
|
621
|
-
}
|
|
1092
|
+
pointerState.captured.delete(obj);
|
|
1093
|
+
captureData.target.releasePointerCapture(pointerId);
|
|
622
1094
|
}
|
|
623
1095
|
}
|
|
624
1096
|
function removeInteractivity(store, object) {
|
|
625
1097
|
const { internal } = store.getState();
|
|
626
1098
|
internal.interaction = internal.interaction.filter((o) => o !== object);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1099
|
+
for (const [pointerId, pointerState] of internal.pointerMap) {
|
|
1100
|
+
pointerState.initialHits = pointerState.initialHits.filter((o) => o !== object);
|
|
1101
|
+
pointerState.hovered.forEach((value, key) => {
|
|
1102
|
+
if (value.eventObject === object || value.object === object) {
|
|
1103
|
+
pointerState.hovered.delete(key);
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
if (pointerState.captured.has(object)) {
|
|
1107
|
+
releaseInternalPointerCapture(internal, object, pointerId);
|
|
631
1108
|
}
|
|
632
|
-
}
|
|
633
|
-
internal.capturedMap.forEach((captures, pointerId) => {
|
|
634
|
-
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
635
|
-
});
|
|
1109
|
+
}
|
|
636
1110
|
unregisterVisibility(store, object);
|
|
637
1111
|
}
|
|
638
1112
|
function createEvents(store) {
|
|
639
|
-
function calculateDistance(event) {
|
|
1113
|
+
function calculateDistance(event, pointerId) {
|
|
640
1114
|
const { internal } = store.getState();
|
|
641
|
-
const
|
|
642
|
-
|
|
1115
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1116
|
+
if (!pointerState) return 0;
|
|
1117
|
+
const [initialX, initialY] = pointerState.initialClick;
|
|
1118
|
+
const dx = event.offsetX - initialX;
|
|
1119
|
+
const dy = event.offsetY - initialY;
|
|
643
1120
|
return Math.round(Math.sqrt(dx * dx + dy * dy));
|
|
644
1121
|
}
|
|
645
1122
|
function filterPointerEvents(objects) {
|
|
@@ -675,6 +1152,15 @@ function createEvents(store) {
|
|
|
675
1152
|
return state2.raycaster.camera ? state2.raycaster.intersectObject(obj, true) : [];
|
|
676
1153
|
}
|
|
677
1154
|
let hits = eventsObjects.flatMap(handleRaycast).sort((a, b) => {
|
|
1155
|
+
const aInteractivePriority = a.object.userData?.interactivePriority;
|
|
1156
|
+
const bInteractivePriority = b.object.userData?.interactivePriority;
|
|
1157
|
+
if (aInteractivePriority !== void 0 || bInteractivePriority !== void 0) {
|
|
1158
|
+
if (aInteractivePriority !== void 0 && bInteractivePriority === void 0) return -1;
|
|
1159
|
+
if (bInteractivePriority !== void 0 && aInteractivePriority === void 0) return 1;
|
|
1160
|
+
if (aInteractivePriority !== bInteractivePriority) {
|
|
1161
|
+
return (bInteractivePriority ?? 0) - (aInteractivePriority ?? 0);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
678
1164
|
const aState = getRootState(a.object);
|
|
679
1165
|
const bState = getRootState(b.object);
|
|
680
1166
|
const aPriority = aState?.events?.priority ?? 1;
|
|
@@ -696,9 +1182,13 @@ function createEvents(store) {
|
|
|
696
1182
|
eventObject = eventObject.parent;
|
|
697
1183
|
}
|
|
698
1184
|
}
|
|
699
|
-
if ("pointerId" in event
|
|
700
|
-
|
|
701
|
-
|
|
1185
|
+
if ("pointerId" in event) {
|
|
1186
|
+
const pointerId = event.pointerId;
|
|
1187
|
+
const pointerState = state.internal.pointerMap.get(pointerId);
|
|
1188
|
+
if (pointerState?.captured.size) {
|
|
1189
|
+
for (const captureData of pointerState.captured.values()) {
|
|
1190
|
+
if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
|
|
1191
|
+
}
|
|
702
1192
|
}
|
|
703
1193
|
}
|
|
704
1194
|
return intersections;
|
|
@@ -711,27 +1201,25 @@ function createEvents(store) {
|
|
|
711
1201
|
if (state) {
|
|
712
1202
|
const { raycaster, pointer, camera, internal } = state;
|
|
713
1203
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
714
|
-
const hasPointerCapture = (id) =>
|
|
1204
|
+
const hasPointerCapture = (id) => {
|
|
1205
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1206
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1207
|
+
};
|
|
715
1208
|
const setPointerCapture = (id) => {
|
|
716
1209
|
const captureData = { intersection: hit, target: event.target };
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
} else {
|
|
720
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
721
|
-
}
|
|
1210
|
+
const pointerState = getPointerState(internal, id);
|
|
1211
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
722
1212
|
event.target.setPointerCapture(id);
|
|
723
1213
|
};
|
|
724
1214
|
const releasePointerCapture = (id) => {
|
|
725
|
-
|
|
726
|
-
if (captures) {
|
|
727
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
728
|
-
}
|
|
1215
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
729
1216
|
};
|
|
730
1217
|
const extractEventProps = {};
|
|
731
1218
|
for (const prop in event) {
|
|
732
1219
|
const property = event[prop];
|
|
733
1220
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
734
1221
|
}
|
|
1222
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
735
1223
|
const raycastEvent = {
|
|
736
1224
|
...hit,
|
|
737
1225
|
...extractEventProps,
|
|
@@ -742,18 +1230,19 @@ function createEvents(store) {
|
|
|
742
1230
|
unprojectedPoint,
|
|
743
1231
|
ray: raycaster.ray,
|
|
744
1232
|
camera,
|
|
1233
|
+
pointerId: eventPointerId,
|
|
745
1234
|
// Hijack stopPropagation, which just sets a flag
|
|
746
1235
|
stopPropagation() {
|
|
747
|
-
const
|
|
1236
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
748
1237
|
if (
|
|
749
1238
|
// ...if this pointer hasn't been captured
|
|
750
|
-
!
|
|
751
|
-
|
|
1239
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1240
|
+
pointerState.captured.has(hit.eventObject)
|
|
752
1241
|
) {
|
|
753
1242
|
raycastEvent.stopped = localState.stopped = true;
|
|
754
|
-
if (
|
|
1243
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
755
1244
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
756
|
-
cancelPointer([...higher, hit]);
|
|
1245
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
757
1246
|
}
|
|
758
1247
|
}
|
|
759
1248
|
},
|
|
@@ -769,15 +1258,18 @@ function createEvents(store) {
|
|
|
769
1258
|
}
|
|
770
1259
|
return intersections;
|
|
771
1260
|
}
|
|
772
|
-
function cancelPointer(intersections) {
|
|
1261
|
+
function cancelPointer(intersections, pointerId) {
|
|
773
1262
|
const { internal } = store.getState();
|
|
774
|
-
|
|
1263
|
+
const pid = pointerId ?? DEFAULT_POINTER_ID;
|
|
1264
|
+
const pointerState = internal.pointerMap.get(pid);
|
|
1265
|
+
if (!pointerState) return;
|
|
1266
|
+
for (const [hoveredId, hoveredObj] of pointerState.hovered) {
|
|
775
1267
|
if (!intersections.length || !intersections.find(
|
|
776
1268
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
777
1269
|
)) {
|
|
778
1270
|
const eventObject = hoveredObj.eventObject;
|
|
779
1271
|
const instance = eventObject.__r3f;
|
|
780
|
-
|
|
1272
|
+
pointerState.hovered.delete(hoveredId);
|
|
781
1273
|
if (instance?.eventCount) {
|
|
782
1274
|
const handlers = instance.handlers;
|
|
783
1275
|
const data = { ...hoveredObj, intersections };
|
|
@@ -806,41 +1298,118 @@ function createEvents(store) {
|
|
|
806
1298
|
instance?.handlers.onDropMissed?.(event);
|
|
807
1299
|
}
|
|
808
1300
|
}
|
|
1301
|
+
function cleanupPointer(pointerId) {
|
|
1302
|
+
const { internal } = store.getState();
|
|
1303
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1304
|
+
if (pointerState) {
|
|
1305
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1306
|
+
const eventObject = hoveredObj.eventObject;
|
|
1307
|
+
const instance = eventObject.__r3f;
|
|
1308
|
+
if (instance?.eventCount) {
|
|
1309
|
+
const handlers = instance.handlers;
|
|
1310
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1311
|
+
handlers.onPointerOut?.(data);
|
|
1312
|
+
handlers.onPointerLeave?.(data);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
internal.pointerMap.delete(pointerId);
|
|
1316
|
+
}
|
|
1317
|
+
internal.pointerDirty.delete(pointerId);
|
|
1318
|
+
}
|
|
1319
|
+
function processDeferredPointer(event, pointerId) {
|
|
1320
|
+
const state = store.getState();
|
|
1321
|
+
const { internal } = state;
|
|
1322
|
+
if (!state.events.enabled) return;
|
|
1323
|
+
const filter = filterPointerEvents;
|
|
1324
|
+
const hits = intersect(event, filter);
|
|
1325
|
+
cancelPointer(hits, pointerId);
|
|
1326
|
+
function onIntersect(data) {
|
|
1327
|
+
const eventObject = data.eventObject;
|
|
1328
|
+
const instance = eventObject.__r3f;
|
|
1329
|
+
if (!instance?.eventCount) return;
|
|
1330
|
+
const handlers = instance.handlers;
|
|
1331
|
+
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
1332
|
+
const id = makeId(data);
|
|
1333
|
+
const pointerState = getPointerState(internal, pointerId);
|
|
1334
|
+
const hoveredItem = pointerState.hovered.get(id);
|
|
1335
|
+
if (!hoveredItem) {
|
|
1336
|
+
pointerState.hovered.set(id, data);
|
|
1337
|
+
handlers.onPointerOver?.(data);
|
|
1338
|
+
handlers.onPointerEnter?.(data);
|
|
1339
|
+
} else if (hoveredItem.stopped) {
|
|
1340
|
+
data.stopPropagation();
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
handlers.onPointerMove?.(data);
|
|
1344
|
+
}
|
|
1345
|
+
handleIntersects(hits, event, 0, onIntersect);
|
|
1346
|
+
}
|
|
809
1347
|
function handlePointer(name) {
|
|
810
1348
|
switch (name) {
|
|
811
1349
|
case "onPointerLeave":
|
|
812
|
-
case "onPointerCancel":
|
|
813
1350
|
case "onDragLeave":
|
|
814
1351
|
return () => cancelPointer([]);
|
|
1352
|
+
// Global cancel of these events
|
|
1353
|
+
case "onPointerCancel":
|
|
1354
|
+
return (event) => {
|
|
1355
|
+
const pointerId = getPointerId(event);
|
|
1356
|
+
cleanupPointer(pointerId);
|
|
1357
|
+
};
|
|
815
1358
|
case "onLostPointerCapture":
|
|
816
1359
|
return (event) => {
|
|
817
1360
|
const { internal } = store.getState();
|
|
818
|
-
|
|
1361
|
+
const pointerId = getPointerId(event);
|
|
1362
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1363
|
+
if (pointerState?.captured.size) {
|
|
819
1364
|
requestAnimationFrame(() => {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1365
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1366
|
+
if (pointerState2?.captured.size) {
|
|
1367
|
+
pointerState2.captured.clear();
|
|
823
1368
|
}
|
|
1369
|
+
cancelPointer([], pointerId);
|
|
824
1370
|
});
|
|
825
1371
|
}
|
|
826
1372
|
};
|
|
827
1373
|
}
|
|
828
1374
|
return function handleEvent(event) {
|
|
829
1375
|
const state = store.getState();
|
|
830
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1376
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1377
|
+
const pointerId = getPointerId(event);
|
|
831
1378
|
internal.lastEvent.current = event;
|
|
832
|
-
if (!
|
|
1379
|
+
if (!events.enabled) return;
|
|
833
1380
|
const isPointerMove = name === "onPointerMove";
|
|
834
1381
|
const isDragOver = name === "onDragOver";
|
|
835
1382
|
const isDrop = name === "onDrop";
|
|
836
1383
|
const isClickEvent = name === "onClick" || name === "onContextMenu" || name === "onDoubleClick";
|
|
1384
|
+
const isPointerDown = name === "onPointerDown";
|
|
1385
|
+
const isPointerUp = name === "onPointerUp";
|
|
1386
|
+
const isWheel = name === "onWheel";
|
|
1387
|
+
const canDeferRaycasts = events.frameTimedRaycasts && state.frameloop === "always";
|
|
1388
|
+
if (isPointerMove && canDeferRaycasts) {
|
|
1389
|
+
events.compute?.(event, state);
|
|
1390
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
if (isWheel && canDeferRaycasts && !events.alwaysFireOnScroll) {
|
|
1394
|
+
events.compute?.(event, state);
|
|
1395
|
+
internal.pointerDirty.set(pointerId, event);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if ((isClickEvent || isPointerDown || isPointerUp) && internal.pointerDirty.has(pointerId)) {
|
|
1399
|
+
const deferredEvent = internal.pointerDirty.get(pointerId);
|
|
1400
|
+
internal.pointerDirty.delete(pointerId);
|
|
1401
|
+
processDeferredPointer(deferredEvent, pointerId);
|
|
1402
|
+
}
|
|
837
1403
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
838
1404
|
const hits = intersect(event, filter);
|
|
839
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
840
|
-
if (
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
1405
|
+
const delta = isClickEvent ? calculateDistance(event, pointerId) : 0;
|
|
1406
|
+
if (isPointerDown) {
|
|
1407
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1408
|
+
pointerState2.initialClick = [event.offsetX, event.offsetY];
|
|
1409
|
+
pointerState2.initialHits = hits.map((hit) => hit.eventObject);
|
|
1410
|
+
}
|
|
1411
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1412
|
+
const initialHits = pointerState?.initialHits ?? [];
|
|
844
1413
|
if (isClickEvent && !hits.length) {
|
|
845
1414
|
if (delta <= 2) {
|
|
846
1415
|
pointerMissed(event, internal.interaction);
|
|
@@ -855,7 +1424,9 @@ function createEvents(store) {
|
|
|
855
1424
|
dropMissed(event, internal.interaction);
|
|
856
1425
|
if (onDropMissed) onDropMissed(event);
|
|
857
1426
|
}
|
|
858
|
-
if (isPointerMove || isDragOver)
|
|
1427
|
+
if (isPointerMove || isDragOver) {
|
|
1428
|
+
cancelPointer(hits, pointerId);
|
|
1429
|
+
}
|
|
859
1430
|
function onIntersect(data) {
|
|
860
1431
|
const eventObject = data.eventObject;
|
|
861
1432
|
const instance = eventObject.__r3f;
|
|
@@ -864,9 +1435,10 @@ function createEvents(store) {
|
|
|
864
1435
|
if (isPointerMove) {
|
|
865
1436
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
866
1437
|
const id = makeId(data);
|
|
867
|
-
const
|
|
1438
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1439
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
868
1440
|
if (!hoveredItem) {
|
|
869
|
-
|
|
1441
|
+
pointerState2.hovered.set(id, data);
|
|
870
1442
|
handlers.onPointerOver?.(data);
|
|
871
1443
|
handlers.onPointerEnter?.(data);
|
|
872
1444
|
} else if (hoveredItem.stopped) {
|
|
@@ -876,9 +1448,10 @@ function createEvents(store) {
|
|
|
876
1448
|
handlers.onPointerMove?.(data);
|
|
877
1449
|
} else if (isDragOver) {
|
|
878
1450
|
const id = makeId(data);
|
|
879
|
-
const
|
|
1451
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1452
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
880
1453
|
if (!hoveredItem) {
|
|
881
|
-
|
|
1454
|
+
pointerState2.hovered.set(id, data);
|
|
882
1455
|
handlers.onDragOverEnter?.(data);
|
|
883
1456
|
} else if (hoveredItem.stopped) {
|
|
884
1457
|
data.stopPropagation();
|
|
@@ -889,18 +1462,18 @@ function createEvents(store) {
|
|
|
889
1462
|
} else {
|
|
890
1463
|
const handler = handlers[name];
|
|
891
1464
|
if (handler) {
|
|
892
|
-
if (!isClickEvent ||
|
|
1465
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
893
1466
|
pointerMissed(
|
|
894
1467
|
event,
|
|
895
|
-
internal.interaction.filter((object) => !
|
|
1468
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
896
1469
|
);
|
|
897
1470
|
handler(data);
|
|
898
1471
|
}
|
|
899
1472
|
} else {
|
|
900
|
-
if (isClickEvent &&
|
|
1473
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
901
1474
|
pointerMissed(
|
|
902
1475
|
event,
|
|
903
|
-
internal.interaction.filter((object) => !
|
|
1476
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
904
1477
|
);
|
|
905
1478
|
}
|
|
906
1479
|
}
|
|
@@ -909,7 +1482,15 @@ function createEvents(store) {
|
|
|
909
1482
|
handleIntersects(hits, event, delta, onIntersect);
|
|
910
1483
|
};
|
|
911
1484
|
}
|
|
912
|
-
|
|
1485
|
+
function flushDeferredPointers() {
|
|
1486
|
+
const { internal, events } = store.getState();
|
|
1487
|
+
if (!events.frameTimedRaycasts) return;
|
|
1488
|
+
for (const [pointerId, event] of internal.pointerDirty) {
|
|
1489
|
+
processDeferredPointer(event, pointerId);
|
|
1490
|
+
}
|
|
1491
|
+
internal.pointerDirty.clear();
|
|
1492
|
+
}
|
|
1493
|
+
return { handlePointer, flushDeferredPointers, processDeferredPointer };
|
|
913
1494
|
}
|
|
914
1495
|
const DOM_EVENTS = {
|
|
915
1496
|
onClick: ["click", false],
|
|
@@ -928,11 +1509,16 @@ const DOM_EVENTS = {
|
|
|
928
1509
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
929
1510
|
};
|
|
930
1511
|
function createPointerEvents(store) {
|
|
931
|
-
const { handlePointer } = createEvents(store);
|
|
1512
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1513
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1514
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
932
1515
|
return {
|
|
933
1516
|
priority: 1,
|
|
934
1517
|
enabled: true,
|
|
935
|
-
|
|
1518
|
+
frameTimedRaycasts: true,
|
|
1519
|
+
alwaysFireOnScroll: true,
|
|
1520
|
+
updateOnFrame: false,
|
|
1521
|
+
compute(event, state) {
|
|
936
1522
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
937
1523
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
938
1524
|
},
|
|
@@ -941,11 +1527,33 @@ function createPointerEvents(store) {
|
|
|
941
1527
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
942
1528
|
{}
|
|
943
1529
|
),
|
|
944
|
-
update: () => {
|
|
1530
|
+
update: (pointerId) => {
|
|
945
1531
|
const { events, internal } = store.getState();
|
|
946
|
-
if (
|
|
1532
|
+
if (!events.handlers) return;
|
|
1533
|
+
if (pointerId !== void 0) {
|
|
1534
|
+
const event = internal.pointerDirty.get(pointerId);
|
|
1535
|
+
if (event) {
|
|
1536
|
+
internal.pointerDirty.delete(pointerId);
|
|
1537
|
+
processDeferredPointer(event, pointerId);
|
|
1538
|
+
} else if (internal.lastEvent?.current) {
|
|
1539
|
+
processDeferredPointer(internal.lastEvent.current, pointerId);
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
flushDeferredPointers();
|
|
1543
|
+
if (internal.lastEvent?.current) {
|
|
1544
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
flush: () => {
|
|
1549
|
+
const { events, internal } = store.getState();
|
|
1550
|
+
flushDeferredPointers();
|
|
1551
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1552
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1553
|
+
}
|
|
947
1554
|
},
|
|
948
1555
|
connect: (target) => {
|
|
1556
|
+
if (!target) return;
|
|
949
1557
|
const { set, events } = store.getState();
|
|
950
1558
|
events.disconnect?.();
|
|
951
1559
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -969,6 +1577,33 @@ function createPointerEvents(store) {
|
|
|
969
1577
|
}
|
|
970
1578
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
971
1579
|
}
|
|
1580
|
+
},
|
|
1581
|
+
// PREVIEW (A8): the XR pointer registration API may change before stable.
|
|
1582
|
+
registerPointer: (config) => {
|
|
1583
|
+
const pointerId = nextXRPointerId++;
|
|
1584
|
+
xrPointers.set(pointerId, config);
|
|
1585
|
+
const { internal } = store.getState();
|
|
1586
|
+
getPointerState(internal, pointerId);
|
|
1587
|
+
return pointerId;
|
|
1588
|
+
},
|
|
1589
|
+
unregisterPointer: (pointerId) => {
|
|
1590
|
+
xrPointers.delete(pointerId);
|
|
1591
|
+
const { internal } = store.getState();
|
|
1592
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1593
|
+
if (pointerState) {
|
|
1594
|
+
for (const [, hoveredObj] of pointerState.hovered) {
|
|
1595
|
+
const eventObject = hoveredObj.eventObject;
|
|
1596
|
+
const instance = eventObject.__r3f;
|
|
1597
|
+
if (instance?.eventCount) {
|
|
1598
|
+
const handlers = instance.handlers;
|
|
1599
|
+
const data = { ...hoveredObj, intersections: [] };
|
|
1600
|
+
handlers.onPointerOut?.(data);
|
|
1601
|
+
handlers.onPointerLeave?.(data);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
internal.pointerMap.delete(pointerId);
|
|
1605
|
+
}
|
|
1606
|
+
internal.pointerDirty.delete(pointerId);
|
|
972
1607
|
}
|
|
973
1608
|
};
|
|
974
1609
|
}
|
|
@@ -1030,6 +1665,14 @@ function notifyAlpha({ message, link }) {
|
|
|
1030
1665
|
}
|
|
1031
1666
|
}
|
|
1032
1667
|
|
|
1668
|
+
const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
|
|
1669
|
+
const INTERNAL_FIBER_MARKERS = ["packages/fiber/src", "packages/fiber/dist", "@react-three/fiber", "@react-three_fiber"];
|
|
1670
|
+
function isInternalRendererAccess(stack, callerDepth = 2) {
|
|
1671
|
+
if (!stack) return false;
|
|
1672
|
+
const caller = stack.split("\n")[callerDepth] ?? "";
|
|
1673
|
+
return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m)) || INTERNAL_FIBER_MARKERS.some((m) => caller.includes(m));
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1033
1676
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1034
1677
|
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
1035
1678
|
const createStore = (invalidate, advance) => {
|
|
@@ -1059,23 +1702,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1059
1702
|
set,
|
|
1060
1703
|
get,
|
|
1061
1704
|
// Mock objects that have to be configured
|
|
1705
|
+
// primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
|
|
1706
|
+
primaryStore: null,
|
|
1062
1707
|
gl: null,
|
|
1063
1708
|
renderer: null,
|
|
1064
1709
|
camera: null,
|
|
1065
1710
|
frustum: new Frustum(),
|
|
1066
1711
|
autoUpdateFrustum: true,
|
|
1067
1712
|
raycaster: null,
|
|
1068
|
-
events: {
|
|
1713
|
+
events: {
|
|
1714
|
+
priority: 1,
|
|
1715
|
+
enabled: true,
|
|
1716
|
+
connected: false,
|
|
1717
|
+
frameTimedRaycasts: true,
|
|
1718
|
+
alwaysFireOnScroll: true,
|
|
1719
|
+
updateOnFrame: false
|
|
1720
|
+
},
|
|
1069
1721
|
scene: null,
|
|
1070
1722
|
rootScene: null,
|
|
1071
1723
|
xr: null,
|
|
1072
1724
|
inspector: null,
|
|
1073
1725
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1074
1726
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1075
|
-
|
|
1076
|
-
linear: false,
|
|
1077
|
-
flat: false,
|
|
1078
|
-
textureColorSpace: "srgb",
|
|
1727
|
+
textureColorSpace: SRGBColorSpace,
|
|
1079
1728
|
isLegacy: false,
|
|
1080
1729
|
webGPUSupported: false,
|
|
1081
1730
|
isNative: false,
|
|
@@ -1133,6 +1782,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1133
1782
|
size: newSize,
|
|
1134
1783
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1135
1784
|
}));
|
|
1785
|
+
getScheduler().invalidate();
|
|
1136
1786
|
}
|
|
1137
1787
|
}
|
|
1138
1788
|
return;
|
|
@@ -1147,6 +1797,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1147
1797
|
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1148
1798
|
_sizeImperative: true
|
|
1149
1799
|
}));
|
|
1800
|
+
getScheduler().invalidate();
|
|
1150
1801
|
},
|
|
1151
1802
|
setDpr: (dpr) => set((state2) => {
|
|
1152
1803
|
const resolved = calculateDpr(dpr);
|
|
@@ -1154,14 +1805,18 @@ const createStore = (invalidate, advance) => {
|
|
|
1154
1805
|
}),
|
|
1155
1806
|
setFrameloop: (frameloop = "always") => {
|
|
1156
1807
|
set(() => ({ frameloop }));
|
|
1808
|
+
getScheduler().frameloop = frameloop;
|
|
1157
1809
|
},
|
|
1158
1810
|
setError: (error) => set(() => ({ error })),
|
|
1159
1811
|
error: null,
|
|
1160
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1812
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1161
1813
|
uniforms: {},
|
|
1162
1814
|
nodes: {},
|
|
1815
|
+
buffers: {},
|
|
1816
|
+
gpuStorage: {},
|
|
1163
1817
|
textures: /* @__PURE__ */ new Map(),
|
|
1164
|
-
|
|
1818
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1819
|
+
renderPipeline: null,
|
|
1165
1820
|
passes: {},
|
|
1166
1821
|
_hmrVersion: 0,
|
|
1167
1822
|
_sizeImperative: false,
|
|
@@ -1170,12 +1825,16 @@ const createStore = (invalidate, advance) => {
|
|
|
1170
1825
|
internal: {
|
|
1171
1826
|
// Events
|
|
1172
1827
|
interaction: [],
|
|
1173
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1174
1828
|
subscribers: [],
|
|
1829
|
+
// Per-pointer state (new unified structure)
|
|
1830
|
+
pointerMap: /* @__PURE__ */ new Map(),
|
|
1831
|
+
pointerDirty: /* @__PURE__ */ new Map(),
|
|
1832
|
+
lastEvent: React.createRef(),
|
|
1833
|
+
// Deprecated but kept for backwards compatibility
|
|
1834
|
+
hovered: /* @__PURE__ */ new Map(),
|
|
1175
1835
|
initialClick: [0, 0],
|
|
1176
1836
|
initialHits: [],
|
|
1177
1837
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1178
|
-
lastEvent: React.createRef(),
|
|
1179
1838
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1180
1839
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1181
1840
|
// Occlusion system (WebGPU only)
|
|
@@ -1214,8 +1873,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1214
1873
|
const currentState = rootStore.getState();
|
|
1215
1874
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
1216
1875
|
const stack = new Error().stack || "";
|
|
1217
|
-
|
|
1218
|
-
if (!isInternalAccess) {
|
|
1876
|
+
if (!isInternalRendererAccess(stack)) {
|
|
1219
1877
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
1220
1878
|
notifyDepreciated({
|
|
1221
1879
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -1258,13 +1916,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1258
1916
|
rootStore.subscribe(() => {
|
|
1259
1917
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1260
1918
|
const actualRenderer = internal.actualRenderer;
|
|
1919
|
+
const canvasTarget = internal.canvasTarget;
|
|
1261
1920
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1262
1921
|
oldSize = size;
|
|
1263
1922
|
oldDpr = viewport.dpr;
|
|
1264
1923
|
updateCamera(camera, size);
|
|
1265
|
-
if (
|
|
1266
|
-
|
|
1267
|
-
|
|
1924
|
+
if (internal.isSecondary && canvasTarget) {
|
|
1925
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1926
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1927
|
+
} else {
|
|
1928
|
+
if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
|
|
1929
|
+
actualRenderer.setSize(size.width, size.height, false);
|
|
1930
|
+
if (canvasTarget) {
|
|
1931
|
+
if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
|
|
1932
|
+
canvasTarget.setSize(size.width, size.height, false);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1268
1935
|
}
|
|
1269
1936
|
if (camera !== oldCamera) {
|
|
1270
1937
|
oldCamera = camera;
|
|
@@ -1319,1140 +1986,108 @@ function loadingFn(extensions, onProgress) {
|
|
|
1319
1986
|
);
|
|
1320
1987
|
};
|
|
1321
1988
|
}
|
|
1322
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
1323
|
-
const
|
|
1989
|
+
function useLoader(loader, input, extensions, onProgress, cacheKey) {
|
|
1990
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
1991
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
1324
1992
|
const fn = loadingFn(extensions, onProgress);
|
|
1325
|
-
const results =
|
|
1993
|
+
const results = cacheKeys.map(
|
|
1994
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
1995
|
+
);
|
|
1326
1996
|
return Array.isArray(input) ? results : results[0];
|
|
1327
1997
|
}
|
|
1328
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1998
|
+
useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
|
|
1999
|
+
const inputs = Array.isArray(input) ? input : [input];
|
|
2000
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2001
|
+
cacheKeys.forEach(
|
|
2002
|
+
(key, index) => preload(() => loadingFn(extensions, onProgress)(loader, inputs[index]), [loader, key])
|
|
2003
|
+
);
|
|
1331
2004
|
};
|
|
1332
|
-
useLoader.clear = function(loader, input) {
|
|
1333
|
-
const
|
|
1334
|
-
|
|
2005
|
+
useLoader.clear = function(loader, input, cacheKey) {
|
|
2006
|
+
const cacheKeys = Array.isArray(cacheKey ?? input) ? cacheKey ?? input : [cacheKey ?? input];
|
|
2007
|
+
cacheKeys.forEach((key) => clear([loader, key]));
|
|
1335
2008
|
};
|
|
1336
2009
|
useLoader.loader = getLoader;
|
|
1337
2010
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
2011
|
+
function useFrame(callback, priorityOrOptions) {
|
|
2012
|
+
const store = React.useContext(context);
|
|
2013
|
+
const isInsideCanvas = store !== null;
|
|
2014
|
+
const scheduler = getScheduler();
|
|
2015
|
+
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2016
|
+
id: priorityOrOptions.id,
|
|
2017
|
+
phase: priorityOrOptions.phase,
|
|
2018
|
+
priority: priorityOrOptions.priority,
|
|
2019
|
+
fps: priorityOrOptions.fps,
|
|
2020
|
+
drop: priorityOrOptions.drop,
|
|
2021
|
+
enabled: priorityOrOptions.enabled,
|
|
2022
|
+
before: priorityOrOptions.before,
|
|
2023
|
+
after: priorityOrOptions.after
|
|
2024
|
+
}) : "";
|
|
2025
|
+
const options = React.useMemo(() => {
|
|
2026
|
+
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2027
|
+
}, [optionsKey]);
|
|
2028
|
+
const reactId = React.useId();
|
|
2029
|
+
const id = options.id ?? reactId;
|
|
2030
|
+
const callbackRef = useMutableCallback(callback);
|
|
2031
|
+
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2032
|
+
useIsomorphicLayoutEffect(() => {
|
|
2033
|
+
if (!callback) return;
|
|
2034
|
+
if (isInsideCanvas) {
|
|
2035
|
+
const state = store.getState();
|
|
2036
|
+
const rootId = state.internal.rootId;
|
|
2037
|
+
if (isLegacyPriority) {
|
|
2038
|
+
state.internal.priority++;
|
|
2039
|
+
let parentRoot = state.previousRoot;
|
|
2040
|
+
while (parentRoot) {
|
|
2041
|
+
const parentState = parentRoot.getState();
|
|
2042
|
+
if (parentState?.internal) parentState.internal.priority++;
|
|
2043
|
+
parentRoot = parentState?.previousRoot;
|
|
2044
|
+
}
|
|
2045
|
+
notifyDepreciated({
|
|
2046
|
+
heading: "useFrame with numeric priority is deprecated",
|
|
2047
|
+
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 })',
|
|
2048
|
+
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2049
|
+
});
|
|
2050
|
+
}
|
|
2051
|
+
const wrappedCallback = (frameState, delta) => {
|
|
2052
|
+
const localState = store.getState();
|
|
2053
|
+
const mergedState = {
|
|
2054
|
+
...localState,
|
|
2055
|
+
time: frameState.time,
|
|
2056
|
+
delta: frameState.delta,
|
|
2057
|
+
elapsed: frameState.elapsed,
|
|
2058
|
+
frame: frameState.frame
|
|
2059
|
+
};
|
|
2060
|
+
callbackRef.current?.(mergedState, delta);
|
|
2061
|
+
};
|
|
2062
|
+
const unregister = scheduler.register(wrappedCallback, {
|
|
2063
|
+
id,
|
|
2064
|
+
rootId,
|
|
2065
|
+
...options
|
|
2066
|
+
});
|
|
2067
|
+
return () => {
|
|
2068
|
+
unregister();
|
|
2069
|
+
if (isLegacyPriority) {
|
|
2070
|
+
const currentState = store.getState();
|
|
2071
|
+
if (currentState.internal) {
|
|
2072
|
+
currentState.internal.priority--;
|
|
2073
|
+
let parentRoot = currentState.previousRoot;
|
|
2074
|
+
while (parentRoot) {
|
|
2075
|
+
const parentState = parentRoot.getState();
|
|
2076
|
+
if (parentState?.internal) parentState.internal.priority--;
|
|
2077
|
+
parentRoot = parentState?.previousRoot;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
1378
2082
|
} else {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
* Get ordered list of phase names
|
|
1388
|
-
*/
|
|
1389
|
-
getOrderedPhases() {
|
|
1390
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1391
|
-
return this.orderedNamesCache;
|
|
1392
|
-
}
|
|
1393
|
-
/**
|
|
1394
|
-
* Check if a phase exists
|
|
1395
|
-
*/
|
|
1396
|
-
hasPhase(name) {
|
|
1397
|
-
return this.phaseMap.has(name);
|
|
1398
|
-
}
|
|
1399
|
-
/**
|
|
1400
|
-
* Get the index of a phase (-1 if not found)
|
|
1401
|
-
*/
|
|
1402
|
-
getPhaseIndex(name) {
|
|
1403
|
-
if (!name) return -1;
|
|
1404
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1405
|
-
}
|
|
1406
|
-
/**
|
|
1407
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1408
|
-
* Used for resolving before/after constraints.
|
|
1409
|
-
*
|
|
1410
|
-
* @param name - The phase name to ensure exists
|
|
1411
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1412
|
-
*/
|
|
1413
|
-
ensurePhase(name) {
|
|
1414
|
-
if (this.phaseMap.has(name)) return name;
|
|
1415
|
-
const node = { name, isAutoGenerated: true };
|
|
1416
|
-
this.phases.push(node);
|
|
1417
|
-
this.phaseMap.set(name, node);
|
|
1418
|
-
this.invalidateCache();
|
|
1419
|
-
return name;
|
|
1420
|
-
}
|
|
1421
|
-
/**
|
|
1422
|
-
* Resolve where a job with before/after constraints should go.
|
|
1423
|
-
* Creates auto-generated phases if needed.
|
|
1424
|
-
*
|
|
1425
|
-
* @param before - Phase(s) to run before
|
|
1426
|
-
* @param after - Phase(s) to run after
|
|
1427
|
-
* @returns The resolved phase name
|
|
1428
|
-
*/
|
|
1429
|
-
resolveConstraintPhase(before, after) {
|
|
1430
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1431
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1432
|
-
if (beforeArr.length > 0) {
|
|
1433
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1434
|
-
}
|
|
1435
|
-
if (afterArr.length > 0) {
|
|
1436
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1437
|
-
}
|
|
1438
|
-
return "update";
|
|
1439
|
-
}
|
|
1440
|
-
/**
|
|
1441
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1442
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1443
|
-
*
|
|
1444
|
-
* @param target - The target phase name to position relative to
|
|
1445
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1446
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1447
|
-
* @returns The auto-generated phase name
|
|
1448
|
-
*/
|
|
1449
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1450
|
-
const autoName = `${prefix}:${target}`;
|
|
1451
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1452
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1453
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1454
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1455
|
-
else this.phases.push(node);
|
|
1456
|
-
this.phaseMap.set(autoName, node);
|
|
1457
|
-
this.invalidateCache();
|
|
1458
|
-
return autoName;
|
|
1459
|
-
}
|
|
1460
|
-
// Internal --------------------------------
|
|
1461
|
-
invalidateCache() {
|
|
1462
|
-
this.orderedNamesCache = null;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1467
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1468
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1469
|
-
for (const phase of orderedPhases) {
|
|
1470
|
-
buckets.set(phase, []);
|
|
1471
|
-
}
|
|
1472
|
-
for (const job of jobs.values()) {
|
|
1473
|
-
if (!job.enabled) continue;
|
|
1474
|
-
let bucket = buckets.get(job.phase);
|
|
1475
|
-
if (!bucket) {
|
|
1476
|
-
bucket = [];
|
|
1477
|
-
buckets.set(job.phase, bucket);
|
|
1478
|
-
}
|
|
1479
|
-
bucket.push(job);
|
|
1480
|
-
}
|
|
1481
|
-
const sortedBuckets = [];
|
|
1482
|
-
for (const phase of orderedPhases) {
|
|
1483
|
-
const bucket = buckets.get(phase);
|
|
1484
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1485
|
-
bucket.sort((a, b) => {
|
|
1486
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1487
|
-
return a.index - b.index;
|
|
1488
|
-
});
|
|
1489
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1490
|
-
}
|
|
1491
|
-
for (const [phase, bucket] of buckets) {
|
|
1492
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1493
|
-
bucket.sort((a, b) => {
|
|
1494
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1495
|
-
return a.index - b.index;
|
|
1496
|
-
});
|
|
1497
|
-
sortedBuckets.push(bucket);
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
return sortedBuckets.flat();
|
|
1501
|
-
}
|
|
1502
|
-
function hasCrossJobConstraints(bucket) {
|
|
1503
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1504
|
-
for (const job of bucket) {
|
|
1505
|
-
for (const ref of job.before) {
|
|
1506
|
-
if (jobIds.has(ref)) return true;
|
|
1507
|
-
}
|
|
1508
|
-
for (const ref of job.after) {
|
|
1509
|
-
if (jobIds.has(ref)) return true;
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
return false;
|
|
1513
|
-
}
|
|
1514
|
-
function topologicalSort(jobs) {
|
|
1515
|
-
const n = jobs.length;
|
|
1516
|
-
if (n <= 1) return jobs;
|
|
1517
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1518
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1519
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1520
|
-
for (const job of jobs) {
|
|
1521
|
-
jobMap.set(job.id, job);
|
|
1522
|
-
inDegree.set(job.id, 0);
|
|
1523
|
-
adjacency.set(job.id, []);
|
|
1524
|
-
}
|
|
1525
|
-
for (const job of jobs) {
|
|
1526
|
-
for (const ref of job.before) {
|
|
1527
|
-
if (jobMap.has(ref)) {
|
|
1528
|
-
adjacency.get(job.id).push(ref);
|
|
1529
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
for (const ref of job.after) {
|
|
1533
|
-
if (jobMap.has(ref)) {
|
|
1534
|
-
adjacency.get(ref).push(job.id);
|
|
1535
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
const queue = [];
|
|
1540
|
-
for (const job of jobs) {
|
|
1541
|
-
if (inDegree.get(job.id) === 0) {
|
|
1542
|
-
queue.push(job);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
queue.sort((a, b) => {
|
|
1546
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1547
|
-
return a.index - b.index;
|
|
1548
|
-
});
|
|
1549
|
-
const result = [];
|
|
1550
|
-
while (queue.length > 0) {
|
|
1551
|
-
const job = queue.shift();
|
|
1552
|
-
result.push(job);
|
|
1553
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1554
|
-
for (const neighborId of neighbors) {
|
|
1555
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1556
|
-
inDegree.set(neighborId, newDegree);
|
|
1557
|
-
if (newDegree === 0) {
|
|
1558
|
-
const neighbor = jobMap.get(neighborId);
|
|
1559
|
-
insertSorted(queue, neighbor);
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
if (result.length !== n) {
|
|
1564
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1565
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1566
|
-
for (const job of jobs) {
|
|
1567
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
return result;
|
|
1571
|
-
}
|
|
1572
|
-
function insertSorted(arr, job) {
|
|
1573
|
-
let i = 0;
|
|
1574
|
-
while (i < arr.length) {
|
|
1575
|
-
const cmp = arr[i];
|
|
1576
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1577
|
-
break;
|
|
1578
|
-
}
|
|
1579
|
-
i++;
|
|
1580
|
-
}
|
|
1581
|
-
arr.splice(i, 0, job);
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
function shouldRun(job, now) {
|
|
1585
|
-
if (!job.enabled) return false;
|
|
1586
|
-
if (!job.fps) return true;
|
|
1587
|
-
const minInterval = 1e3 / job.fps;
|
|
1588
|
-
const lastRun = job.lastRun ?? 0;
|
|
1589
|
-
const elapsed = now - lastRun;
|
|
1590
|
-
if (elapsed < minInterval) return false;
|
|
1591
|
-
if (job.drop) {
|
|
1592
|
-
job.lastRun = now;
|
|
1593
|
-
} else {
|
|
1594
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1595
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1596
|
-
if (job.lastRun < now - minInterval) {
|
|
1597
|
-
job.lastRun = now;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
return true;
|
|
1601
|
-
}
|
|
1602
|
-
function resetJobTiming(job) {
|
|
1603
|
-
job.lastRun = void 0;
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
var __defProp$1 = Object.defineProperty;
|
|
1607
|
-
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1608
|
-
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1609
|
-
const hmrData = (() => {
|
|
1610
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1611
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1612
|
-
try {
|
|
1613
|
-
return (0, eval)("import.meta.hot");
|
|
1614
|
-
} catch {
|
|
1615
|
-
return void 0;
|
|
1616
|
-
}
|
|
1617
|
-
})();
|
|
1618
|
-
const _Scheduler = class _Scheduler {
|
|
1619
|
-
//* Constructor ================================
|
|
1620
|
-
constructor() {
|
|
1621
|
-
//* Critical State ================================
|
|
1622
|
-
__publicField$1(this, "roots", /* @__PURE__ */ new Map());
|
|
1623
|
-
__publicField$1(this, "phaseGraph");
|
|
1624
|
-
__publicField$1(this, "loopState", {
|
|
1625
|
-
running: false,
|
|
1626
|
-
rafHandle: null,
|
|
1627
|
-
lastTime: null,
|
|
1628
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1629
|
-
frameCount: 0,
|
|
1630
|
-
elapsedTime: 0,
|
|
1631
|
-
createdAt: performance.now()
|
|
1632
|
-
});
|
|
1633
|
-
__publicField$1(this, "stoppedTime", 0);
|
|
1634
|
-
//* Private State ================================
|
|
1635
|
-
__publicField$1(this, "nextRootIndex", 0);
|
|
1636
|
-
__publicField$1(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1637
|
-
__publicField$1(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1638
|
-
__publicField$1(this, "nextGlobalIndex", 0);
|
|
1639
|
-
__publicField$1(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1640
|
-
__publicField$1(this, "nextJobIndex", 0);
|
|
1641
|
-
__publicField$1(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1642
|
-
__publicField$1(this, "pendingFrames", 0);
|
|
1643
|
-
__publicField$1(this, "_frameloop", "always");
|
|
1644
|
-
//* Independent Mode & Error Handling State ================================
|
|
1645
|
-
__publicField$1(this, "_independent", false);
|
|
1646
|
-
__publicField$1(this, "errorHandler", null);
|
|
1647
|
-
__publicField$1(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1648
|
-
//* Core Loop Execution Methods ================================
|
|
1649
|
-
/**
|
|
1650
|
-
* Main RAF loop callback.
|
|
1651
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1652
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1653
|
-
* @returns {void}
|
|
1654
|
-
* @private
|
|
1655
|
-
*/
|
|
1656
|
-
__publicField$1(this, "loop", (timestamp) => {
|
|
1657
|
-
if (!this.loopState.running) return;
|
|
1658
|
-
this.executeFrame(timestamp);
|
|
1659
|
-
if (this._frameloop === "demand") {
|
|
1660
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1661
|
-
if (this.pendingFrames === 0) {
|
|
1662
|
-
this.notifyIdle(timestamp);
|
|
1663
|
-
return this.stop();
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1667
|
-
});
|
|
1668
|
-
this.phaseGraph = new PhaseGraph();
|
|
1669
|
-
}
|
|
1670
|
-
static get instance() {
|
|
1671
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1672
|
-
}
|
|
1673
|
-
static set instance(value) {
|
|
1674
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1675
|
-
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1678
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1679
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1680
|
-
*/
|
|
1681
|
-
static get() {
|
|
1682
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1683
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1684
|
-
}
|
|
1685
|
-
if (!_Scheduler.instance) {
|
|
1686
|
-
_Scheduler.instance = new _Scheduler();
|
|
1687
|
-
if (hmrData?.data) {
|
|
1688
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
return _Scheduler.instance;
|
|
1692
|
-
}
|
|
1693
|
-
/**
|
|
1694
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1695
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1696
|
-
* @returns {void}
|
|
1697
|
-
*/
|
|
1698
|
-
static reset() {
|
|
1699
|
-
if (_Scheduler.instance) {
|
|
1700
|
-
_Scheduler.instance.stop();
|
|
1701
|
-
_Scheduler.instance = null;
|
|
1702
|
-
}
|
|
1703
|
-
if (hmrData?.data) {
|
|
1704
|
-
hmrData.data.scheduler = null;
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
//* Getters & Setters ================================
|
|
1708
|
-
get phases() {
|
|
1709
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1710
|
-
}
|
|
1711
|
-
get frameloop() {
|
|
1712
|
-
return this._frameloop;
|
|
1713
|
-
}
|
|
1714
|
-
set frameloop(mode) {
|
|
1715
|
-
if (this._frameloop === mode) return;
|
|
1716
|
-
const wasAlways = this._frameloop === "always";
|
|
1717
|
-
this._frameloop = mode;
|
|
1718
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1719
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1720
|
-
}
|
|
1721
|
-
get isRunning() {
|
|
1722
|
-
return this.loopState.running;
|
|
1723
|
-
}
|
|
1724
|
-
get isReady() {
|
|
1725
|
-
return this.roots.size > 0;
|
|
1726
|
-
}
|
|
1727
|
-
get independent() {
|
|
1728
|
-
return this._independent;
|
|
1729
|
-
}
|
|
1730
|
-
set independent(value) {
|
|
1731
|
-
this._independent = value;
|
|
1732
|
-
if (value) this.ensureDefaultRoot();
|
|
1733
|
-
}
|
|
1734
|
-
//* Root Management Methods ================================
|
|
1735
|
-
/**
|
|
1736
|
-
* Register a root (Canvas) with the scheduler.
|
|
1737
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1738
|
-
* @param {string} id - Unique identifier for this root
|
|
1739
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1740
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1741
|
-
*/
|
|
1742
|
-
registerRoot(id, options = {}) {
|
|
1743
|
-
if (this.roots.has(id)) {
|
|
1744
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1745
|
-
return () => this.unregisterRoot(id);
|
|
1746
|
-
}
|
|
1747
|
-
const entry = {
|
|
1748
|
-
id,
|
|
1749
|
-
getState: options.getState ?? (() => ({})),
|
|
1750
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1751
|
-
sortedJobs: [],
|
|
1752
|
-
needsRebuild: false
|
|
1753
|
-
};
|
|
1754
|
-
if (options.onError) {
|
|
1755
|
-
this.errorHandler = options.onError;
|
|
1756
|
-
}
|
|
1757
|
-
this.roots.set(id, entry);
|
|
1758
|
-
if (this.roots.size === 1) {
|
|
1759
|
-
this.notifyRootReady();
|
|
1760
|
-
if (this._frameloop === "always") this.start();
|
|
1761
|
-
}
|
|
1762
|
-
return () => this.unregisterRoot(id);
|
|
1763
|
-
}
|
|
1764
|
-
/**
|
|
1765
|
-
* Unregister a root from the scheduler.
|
|
1766
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1767
|
-
* The last root to unregister stops the RAF loop.
|
|
1768
|
-
* @param {string} id - The root ID to unregister
|
|
1769
|
-
* @returns {void}
|
|
1770
|
-
*/
|
|
1771
|
-
unregisterRoot(id) {
|
|
1772
|
-
const root = this.roots.get(id);
|
|
1773
|
-
if (!root) return;
|
|
1774
|
-
for (const jobId of root.jobs.keys()) {
|
|
1775
|
-
this.jobStateListeners.delete(jobId);
|
|
1776
|
-
}
|
|
1777
|
-
this.roots.delete(id);
|
|
1778
|
-
if (this.roots.size === 0) {
|
|
1779
|
-
this.stop();
|
|
1780
|
-
this.errorHandler = null;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
/**
|
|
1784
|
-
* Subscribe to be notified when a root becomes available.
|
|
1785
|
-
* Fires immediately if a root already exists.
|
|
1786
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1787
|
-
* @returns {() => void} Unsubscribe function
|
|
1788
|
-
*/
|
|
1789
|
-
onRootReady(callback) {
|
|
1790
|
-
if (this.roots.size > 0) {
|
|
1791
|
-
callback();
|
|
1792
|
-
return () => {
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
this.rootReadyCallbacks.add(callback);
|
|
1796
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
|
-
* Notify all registered root-ready callbacks.
|
|
1800
|
-
* Called when the first root registers.
|
|
1801
|
-
* @returns {void}
|
|
1802
|
-
* @private
|
|
1803
|
-
*/
|
|
1804
|
-
notifyRootReady() {
|
|
1805
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1806
|
-
try {
|
|
1807
|
-
cb();
|
|
1808
|
-
} catch (error) {
|
|
1809
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1810
|
-
}
|
|
1811
|
-
}
|
|
1812
|
-
this.rootReadyCallbacks.clear();
|
|
1813
|
-
}
|
|
1814
|
-
/**
|
|
1815
|
-
* Ensure a default root exists for independent mode.
|
|
1816
|
-
* Creates a minimal root with no state provider.
|
|
1817
|
-
* @returns {void}
|
|
1818
|
-
* @private
|
|
1819
|
-
*/
|
|
1820
|
-
ensureDefaultRoot() {
|
|
1821
|
-
if (!this.roots.has("__default__")) {
|
|
1822
|
-
this.registerRoot("__default__");
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
/**
|
|
1826
|
-
* Trigger error handling for job errors.
|
|
1827
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1828
|
-
* @param {Error} error - The error to handle
|
|
1829
|
-
* @returns {void}
|
|
1830
|
-
*/
|
|
1831
|
-
triggerError(error) {
|
|
1832
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1833
|
-
else console.error("[Scheduler]", error);
|
|
1834
|
-
}
|
|
1835
|
-
//* Phase Management Methods ================================
|
|
1836
|
-
/**
|
|
1837
|
-
* Add a named phase to the scheduler's execution order.
|
|
1838
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1839
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1840
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1841
|
-
* @returns {void}
|
|
1842
|
-
* @example
|
|
1843
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1844
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1845
|
-
*/
|
|
1846
|
-
addPhase(name, options) {
|
|
1847
|
-
this.phaseGraph.addPhase(name, options);
|
|
1848
|
-
for (const root of this.roots.values()) {
|
|
1849
|
-
root.needsRebuild = true;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
/**
|
|
1853
|
-
* Check if a phase exists in the scheduler.
|
|
1854
|
-
* @param {string} name - The phase name to check
|
|
1855
|
-
* @returns {boolean} True if the phase exists
|
|
1856
|
-
*/
|
|
1857
|
-
hasPhase(name) {
|
|
1858
|
-
return this.phaseGraph.hasPhase(name);
|
|
1859
|
-
}
|
|
1860
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1861
|
-
/**
|
|
1862
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1863
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1864
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1865
|
-
* @param {string} id - Unique identifier for this global job
|
|
1866
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1867
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1868
|
-
* @deprecated Use useFrame with phases instead
|
|
1869
|
-
*/
|
|
1870
|
-
registerGlobal(phase, id, callback) {
|
|
1871
|
-
const job = { id, callback };
|
|
1872
|
-
if (phase === "before") {
|
|
1873
|
-
this.globalBeforeJobs.set(id, job);
|
|
1874
|
-
} else {
|
|
1875
|
-
this.globalAfterJobs.set(id, job);
|
|
1876
|
-
}
|
|
1877
|
-
return () => {
|
|
1878
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1879
|
-
else this.globalAfterJobs.delete(id);
|
|
1880
|
-
};
|
|
1881
|
-
}
|
|
1882
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1883
|
-
/**
|
|
1884
|
-
* Register an idle callback that fires when the loop stops.
|
|
1885
|
-
* Used internally by deprecated addTail API.
|
|
1886
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1887
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1888
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1889
|
-
*/
|
|
1890
|
-
onIdle(callback) {
|
|
1891
|
-
this.idleCallbacks.add(callback);
|
|
1892
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1893
|
-
}
|
|
1894
|
-
/**
|
|
1895
|
-
* Notify all registered idle callbacks.
|
|
1896
|
-
* Called when the loop stops in demand mode.
|
|
1897
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1898
|
-
* @returns {void}
|
|
1899
|
-
* @private
|
|
1900
|
-
*/
|
|
1901
|
-
notifyIdle(timestamp) {
|
|
1902
|
-
for (const cb of this.idleCallbacks) {
|
|
1903
|
-
try {
|
|
1904
|
-
cb(timestamp);
|
|
1905
|
-
} catch (error) {
|
|
1906
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
//* Job Registration & Management Methods ================================
|
|
1911
|
-
/**
|
|
1912
|
-
* Register a job (frame callback) with a specific root.
|
|
1913
|
-
* This is the core registration method used by useFrame internally.
|
|
1914
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1915
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1916
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1917
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1918
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1919
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1920
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1921
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1922
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1923
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1924
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1925
|
-
*/
|
|
1926
|
-
register(callback, options = {}) {
|
|
1927
|
-
const rootId = options.rootId;
|
|
1928
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1929
|
-
if (!root) {
|
|
1930
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1931
|
-
return () => {
|
|
1932
|
-
};
|
|
1933
|
-
}
|
|
1934
|
-
const id = options.id ?? this.generateJobId();
|
|
1935
|
-
let phase = options.phase ?? "update";
|
|
1936
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1937
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1938
|
-
}
|
|
1939
|
-
const before = this.normalizeConstraints(options.before);
|
|
1940
|
-
const after = this.normalizeConstraints(options.after);
|
|
1941
|
-
const job = {
|
|
1942
|
-
id,
|
|
1943
|
-
callback,
|
|
1944
|
-
phase,
|
|
1945
|
-
before,
|
|
1946
|
-
after,
|
|
1947
|
-
priority: options.priority ?? 0,
|
|
1948
|
-
index: this.nextJobIndex++,
|
|
1949
|
-
fps: options.fps,
|
|
1950
|
-
drop: options.drop ?? true,
|
|
1951
|
-
enabled: options.enabled ?? true,
|
|
1952
|
-
system: options.system ?? false
|
|
1953
|
-
};
|
|
1954
|
-
if (root.jobs.has(id)) {
|
|
1955
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1956
|
-
}
|
|
1957
|
-
root.jobs.set(id, job);
|
|
1958
|
-
root.needsRebuild = true;
|
|
1959
|
-
return () => this.unregister(id, root.id);
|
|
1960
|
-
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Unregister a job by its ID.
|
|
1963
|
-
* Searches all roots if rootId is not provided.
|
|
1964
|
-
* @param {string} id - The job ID to unregister
|
|
1965
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1966
|
-
* @returns {void}
|
|
1967
|
-
*/
|
|
1968
|
-
unregister(id, rootId) {
|
|
1969
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1970
|
-
if (root?.jobs.delete(id)) {
|
|
1971
|
-
root.needsRebuild = true;
|
|
1972
|
-
this.jobStateListeners.delete(id);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
/**
|
|
1976
|
-
* Update a job's options dynamically.
|
|
1977
|
-
* Searches all roots to find the job by ID.
|
|
1978
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1979
|
-
* @param {string} id - The job ID to update
|
|
1980
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1981
|
-
* @returns {void}
|
|
1982
|
-
*/
|
|
1983
|
-
updateJob(id, options) {
|
|
1984
|
-
let job;
|
|
1985
|
-
let root;
|
|
1986
|
-
for (const r of this.roots.values()) {
|
|
1987
|
-
job = r.jobs.get(id);
|
|
1988
|
-
if (job) {
|
|
1989
|
-
root = r;
|
|
1990
|
-
break;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
if (!job || !root) return;
|
|
1994
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
1995
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
1996
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
1997
|
-
if (options.enabled !== void 0) {
|
|
1998
|
-
const wasEnabled = job.enabled;
|
|
1999
|
-
job.enabled = options.enabled;
|
|
2000
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
2001
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
2002
|
-
}
|
|
2003
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
2004
|
-
if (options.phase) job.phase = options.phase;
|
|
2005
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
2006
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
2007
|
-
root.needsRebuild = true;
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
//* Job State Management Methods ================================
|
|
2011
|
-
/**
|
|
2012
|
-
* Check if a job is currently paused (disabled).
|
|
2013
|
-
* @param {string} id - The job ID to check
|
|
2014
|
-
* @returns {boolean} True if the job exists and is paused
|
|
2015
|
-
*/
|
|
2016
|
-
isJobPaused(id) {
|
|
2017
|
-
for (const root of this.roots.values()) {
|
|
2018
|
-
const job = root.jobs.get(id);
|
|
2019
|
-
if (job) return !job.enabled;
|
|
2020
|
-
}
|
|
2021
|
-
return false;
|
|
2022
|
-
}
|
|
2023
|
-
/**
|
|
2024
|
-
* Subscribe to state changes for a specific job.
|
|
2025
|
-
* Listener is called when job is paused or resumed.
|
|
2026
|
-
* @param {string} id - The job ID to subscribe to
|
|
2027
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
2028
|
-
* @returns {() => void} Unsubscribe function
|
|
2029
|
-
*/
|
|
2030
|
-
subscribeJobState(id, listener) {
|
|
2031
|
-
if (!this.jobStateListeners.has(id)) {
|
|
2032
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2033
|
-
}
|
|
2034
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2035
|
-
return () => {
|
|
2036
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2037
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2038
|
-
this.jobStateListeners.delete(id);
|
|
2039
|
-
}
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Notify all listeners that a job's state has changed.
|
|
2044
|
-
* @param {string} id - The job ID that changed
|
|
2045
|
-
* @returns {void}
|
|
2046
|
-
* @private
|
|
2047
|
-
*/
|
|
2048
|
-
notifyJobStateChange(id) {
|
|
2049
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2050
|
-
}
|
|
2051
|
-
/**
|
|
2052
|
-
* Pause a job by ID (sets enabled=false).
|
|
2053
|
-
* Notifies any subscribed state listeners.
|
|
2054
|
-
* @param {string} id - The job ID to pause
|
|
2055
|
-
* @returns {void}
|
|
2056
|
-
*/
|
|
2057
|
-
pauseJob(id) {
|
|
2058
|
-
this.updateJob(id, { enabled: false });
|
|
2059
|
-
this.notifyJobStateChange(id);
|
|
2060
|
-
}
|
|
2061
|
-
/**
|
|
2062
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2063
|
-
* Resets job timing to prevent frame accumulation.
|
|
2064
|
-
* Notifies any subscribed state listeners.
|
|
2065
|
-
* @param {string} id - The job ID to resume
|
|
2066
|
-
* @returns {void}
|
|
2067
|
-
*/
|
|
2068
|
-
resumeJob(id) {
|
|
2069
|
-
this.updateJob(id, { enabled: true });
|
|
2070
|
-
this.notifyJobStateChange(id);
|
|
2071
|
-
}
|
|
2072
|
-
//* Frame Loop Control Methods ================================
|
|
2073
|
-
/**
|
|
2074
|
-
* Start the requestAnimationFrame loop.
|
|
2075
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2076
|
-
* No-op if already running.
|
|
2077
|
-
* @returns {void}
|
|
2078
|
-
*/
|
|
2079
|
-
start() {
|
|
2080
|
-
if (this.loopState.running) return;
|
|
2081
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2082
|
-
let adjustedCreated = 0;
|
|
2083
|
-
if (this.stoppedTime > 0) {
|
|
2084
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2085
|
-
this.stoppedTime = 0;
|
|
2086
|
-
}
|
|
2087
|
-
Object.assign(this.loopState, {
|
|
2088
|
-
running: true,
|
|
2089
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2090
|
-
lastTime: performance.now(),
|
|
2091
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2092
|
-
frameCount: 0,
|
|
2093
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
/**
|
|
2097
|
-
* Stop the requestAnimationFrame loop.
|
|
2098
|
-
* Cancels any pending RAF callback.
|
|
2099
|
-
* No-op if not running.
|
|
2100
|
-
* @returns {void}
|
|
2101
|
-
*/
|
|
2102
|
-
stop() {
|
|
2103
|
-
if (!this.loopState.running) return;
|
|
2104
|
-
this.loopState.running = false;
|
|
2105
|
-
if (this.loopState.rafHandle !== null) {
|
|
2106
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2107
|
-
this.loopState.rafHandle = null;
|
|
2108
|
-
}
|
|
2109
|
-
this.stoppedTime = performance.now();
|
|
2110
|
-
}
|
|
2111
|
-
/**
|
|
2112
|
-
* Request frames to be rendered in demand mode.
|
|
2113
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2114
|
-
* No-op if frameloop is not 'demand'.
|
|
2115
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2116
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2117
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2118
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2119
|
-
* @returns {void}
|
|
2120
|
-
* @example
|
|
2121
|
-
* // Request a single frame render
|
|
2122
|
-
* scheduler.invalidate();
|
|
2123
|
-
*
|
|
2124
|
-
* @example
|
|
2125
|
-
* // Request 5 frames (e.g., for animations)
|
|
2126
|
-
* scheduler.invalidate(5);
|
|
2127
|
-
*
|
|
2128
|
-
* @example
|
|
2129
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2130
|
-
* scheduler.invalidate(3, false);
|
|
2131
|
-
*
|
|
2132
|
-
* @example
|
|
2133
|
-
* // Add 2 more frames to existing pending count
|
|
2134
|
-
* scheduler.invalidate(2, true);
|
|
2135
|
-
*/
|
|
2136
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2137
|
-
if (this._frameloop !== "demand") return;
|
|
2138
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2139
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2140
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2141
|
-
}
|
|
2142
|
-
/**
|
|
2143
|
-
* Reset timing state for deterministic testing.
|
|
2144
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2145
|
-
* @returns {void}
|
|
2146
|
-
*/
|
|
2147
|
-
resetTiming() {
|
|
2148
|
-
this.loopState.lastTime = null;
|
|
2149
|
-
this.loopState.frameCount = 0;
|
|
2150
|
-
this.loopState.elapsedTime = 0;
|
|
2151
|
-
this.loopState.createdAt = performance.now();
|
|
2152
|
-
}
|
|
2153
|
-
//* Manual Stepping Methods ================================
|
|
2154
|
-
/**
|
|
2155
|
-
* Manually execute a single frame for all roots.
|
|
2156
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2157
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2158
|
-
* @returns {void}
|
|
2159
|
-
* @example
|
|
2160
|
-
* // Manual control mode
|
|
2161
|
-
* scheduler.frameloop = 'never';
|
|
2162
|
-
* scheduler.step(); // Execute one frame
|
|
2163
|
-
*/
|
|
2164
|
-
step(timestamp) {
|
|
2165
|
-
const now = timestamp ?? performance.now();
|
|
2166
|
-
this.executeFrame(now);
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* Manually execute a single job by its ID.
|
|
2170
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2171
|
-
* @param {string} id - The job ID to step
|
|
2172
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2173
|
-
* @returns {void}
|
|
2174
|
-
*/
|
|
2175
|
-
stepJob(id, timestamp) {
|
|
2176
|
-
let job;
|
|
2177
|
-
let root;
|
|
2178
|
-
for (const r of this.roots.values()) {
|
|
2179
|
-
job = r.jobs.get(id);
|
|
2180
|
-
if (job) {
|
|
2181
|
-
root = r;
|
|
2182
|
-
break;
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
if (!job || !root) {
|
|
2186
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2187
|
-
return;
|
|
2188
|
-
}
|
|
2189
|
-
const now = timestamp ?? performance.now();
|
|
2190
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2191
|
-
const delta = deltaMs / 1e3;
|
|
2192
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2193
|
-
const providedState = root.getState?.() ?? {};
|
|
2194
|
-
const frameState = {
|
|
2195
|
-
...providedState,
|
|
2196
|
-
time: now,
|
|
2197
|
-
delta,
|
|
2198
|
-
elapsed,
|
|
2199
|
-
frame: this.loopState.frameCount
|
|
2200
|
-
};
|
|
2201
|
-
try {
|
|
2202
|
-
job.callback(frameState, delta);
|
|
2203
|
-
} catch (error) {
|
|
2204
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2205
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
/**
|
|
2209
|
-
* Execute a single frame across all roots.
|
|
2210
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2211
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2212
|
-
* @returns {void}
|
|
2213
|
-
* @private
|
|
2214
|
-
*/
|
|
2215
|
-
executeFrame(timestamp) {
|
|
2216
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2217
|
-
const delta = deltaMs / 1e3;
|
|
2218
|
-
this.loopState.lastTime = timestamp;
|
|
2219
|
-
this.loopState.frameCount++;
|
|
2220
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2221
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2222
|
-
for (const root of this.roots.values()) {
|
|
2223
|
-
this.tickRoot(root, timestamp, delta);
|
|
2224
|
-
}
|
|
2225
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2226
|
-
}
|
|
2227
|
-
/**
|
|
2228
|
-
* Run all global jobs from a job map.
|
|
2229
|
-
* Catches and logs errors without stopping execution.
|
|
2230
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2231
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2232
|
-
* @returns {void}
|
|
2233
|
-
* @private
|
|
2234
|
-
*/
|
|
2235
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2236
|
-
for (const job of jobs.values()) {
|
|
2237
|
-
try {
|
|
2238
|
-
job.callback(timestamp);
|
|
2239
|
-
} catch (error) {
|
|
2240
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
/**
|
|
2245
|
-
* Execute all jobs for a single root in sorted order.
|
|
2246
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2247
|
-
* Errors are caught and propagated via triggerError.
|
|
2248
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2249
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2250
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2251
|
-
* @returns {void}
|
|
2252
|
-
* @private
|
|
2253
|
-
*/
|
|
2254
|
-
tickRoot(root, timestamp, delta) {
|
|
2255
|
-
if (root.needsRebuild) {
|
|
2256
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2257
|
-
root.needsRebuild = false;
|
|
2258
|
-
}
|
|
2259
|
-
const providedState = root.getState?.() ?? {};
|
|
2260
|
-
const frameState = {
|
|
2261
|
-
...providedState,
|
|
2262
|
-
time: timestamp,
|
|
2263
|
-
delta,
|
|
2264
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2265
|
-
// Convert ms to seconds
|
|
2266
|
-
frame: this.loopState.frameCount
|
|
2267
|
-
};
|
|
2268
|
-
for (const job of root.sortedJobs) {
|
|
2269
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2270
|
-
try {
|
|
2271
|
-
job.callback(frameState, delta);
|
|
2272
|
-
} catch (error) {
|
|
2273
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2274
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
//* Debug & Inspection Methods ================================
|
|
2279
|
-
/**
|
|
2280
|
-
* Get the total number of registered jobs across all roots.
|
|
2281
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2282
|
-
* @returns {number} Total job count
|
|
2283
|
-
*/
|
|
2284
|
-
getJobCount() {
|
|
2285
|
-
let count = 0;
|
|
2286
|
-
for (const root of this.roots.values()) {
|
|
2287
|
-
count += root.jobs.size;
|
|
2288
|
-
}
|
|
2289
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2290
|
-
}
|
|
2291
|
-
/**
|
|
2292
|
-
* Get all registered job IDs across all roots.
|
|
2293
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2294
|
-
* @returns {string[]} Array of all job IDs
|
|
2295
|
-
*/
|
|
2296
|
-
getJobIds() {
|
|
2297
|
-
const ids = [];
|
|
2298
|
-
for (const root of this.roots.values()) {
|
|
2299
|
-
ids.push(...root.jobs.keys());
|
|
2300
|
-
}
|
|
2301
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2302
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2303
|
-
return ids;
|
|
2304
|
-
}
|
|
2305
|
-
/**
|
|
2306
|
-
* Get the number of registered roots (Canvas instances).
|
|
2307
|
-
* @returns {number} Number of registered roots
|
|
2308
|
-
*/
|
|
2309
|
-
getRootCount() {
|
|
2310
|
-
return this.roots.size;
|
|
2311
|
-
}
|
|
2312
|
-
/**
|
|
2313
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2314
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2315
|
-
*
|
|
2316
|
-
* @param phase The phase to check
|
|
2317
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2318
|
-
* @returns true if any user jobs exist in the phase
|
|
2319
|
-
*/
|
|
2320
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2321
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2322
|
-
return rootsToCheck.some((root) => {
|
|
2323
|
-
if (!root) return false;
|
|
2324
|
-
for (const job of root.jobs.values()) {
|
|
2325
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2326
|
-
}
|
|
2327
|
-
return false;
|
|
2328
|
-
});
|
|
2329
|
-
}
|
|
2330
|
-
//* Utility Methods ================================
|
|
2331
|
-
/**
|
|
2332
|
-
* Generate a unique root ID for automatic root registration.
|
|
2333
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2334
|
-
*/
|
|
2335
|
-
generateRootId() {
|
|
2336
|
-
return `root_${this.nextRootIndex++}`;
|
|
2337
|
-
}
|
|
2338
|
-
/**
|
|
2339
|
-
* Generate a unique job ID.
|
|
2340
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2341
|
-
* @private
|
|
2342
|
-
*/
|
|
2343
|
-
generateJobId() {
|
|
2344
|
-
return `job_${this.nextJobIndex}`;
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Normalize before/after constraints to a Set.
|
|
2348
|
-
* Handles undefined, single string, or array inputs.
|
|
2349
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2350
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2351
|
-
* @private
|
|
2352
|
-
*/
|
|
2353
|
-
normalizeConstraints(value) {
|
|
2354
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2355
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2356
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2357
|
-
}
|
|
2358
|
-
};
|
|
2359
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2360
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2361
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2362
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2363
|
-
__publicField$1(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2364
|
-
let Scheduler = _Scheduler;
|
|
2365
|
-
const getScheduler = () => Scheduler.get();
|
|
2366
|
-
if (hmrData) {
|
|
2367
|
-
hmrData.accept?.();
|
|
2368
|
-
}
|
|
2369
|
-
|
|
2370
|
-
function useFrame(callback, priorityOrOptions) {
|
|
2371
|
-
const store = React.useContext(context);
|
|
2372
|
-
const isInsideCanvas = store !== null;
|
|
2373
|
-
const scheduler = getScheduler();
|
|
2374
|
-
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2375
|
-
id: priorityOrOptions.id,
|
|
2376
|
-
phase: priorityOrOptions.phase,
|
|
2377
|
-
priority: priorityOrOptions.priority,
|
|
2378
|
-
fps: priorityOrOptions.fps,
|
|
2379
|
-
drop: priorityOrOptions.drop,
|
|
2380
|
-
enabled: priorityOrOptions.enabled,
|
|
2381
|
-
before: priorityOrOptions.before,
|
|
2382
|
-
after: priorityOrOptions.after
|
|
2383
|
-
}) : "";
|
|
2384
|
-
const options = React.useMemo(() => {
|
|
2385
|
-
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2386
|
-
}, [optionsKey]);
|
|
2387
|
-
const reactId = React.useId();
|
|
2388
|
-
const id = options.id ?? reactId;
|
|
2389
|
-
const callbackRef = useMutableCallback(callback);
|
|
2390
|
-
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2391
|
-
useIsomorphicLayoutEffect(() => {
|
|
2392
|
-
if (!callback) return;
|
|
2393
|
-
if (isInsideCanvas) {
|
|
2394
|
-
const state = store.getState();
|
|
2395
|
-
const rootId = state.internal.rootId;
|
|
2396
|
-
if (isLegacyPriority) {
|
|
2397
|
-
state.internal.priority++;
|
|
2398
|
-
let parentRoot = state.previousRoot;
|
|
2399
|
-
while (parentRoot) {
|
|
2400
|
-
const parentState = parentRoot.getState();
|
|
2401
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2402
|
-
parentRoot = parentState?.previousRoot;
|
|
2403
|
-
}
|
|
2404
|
-
notifyDepreciated({
|
|
2405
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2406
|
-
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 })',
|
|
2407
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2408
|
-
});
|
|
2409
|
-
}
|
|
2410
|
-
const wrappedCallback = (frameState, delta) => {
|
|
2411
|
-
const localState = store.getState();
|
|
2412
|
-
const mergedState = {
|
|
2413
|
-
...localState,
|
|
2414
|
-
time: frameState.time,
|
|
2415
|
-
delta: frameState.delta,
|
|
2416
|
-
elapsed: frameState.elapsed,
|
|
2417
|
-
frame: frameState.frame
|
|
2418
|
-
};
|
|
2419
|
-
callbackRef.current?.(mergedState, delta);
|
|
2420
|
-
};
|
|
2421
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2422
|
-
id,
|
|
2423
|
-
rootId,
|
|
2424
|
-
...options
|
|
2425
|
-
});
|
|
2426
|
-
return () => {
|
|
2427
|
-
unregister();
|
|
2428
|
-
if (isLegacyPriority) {
|
|
2429
|
-
const currentState = store.getState();
|
|
2430
|
-
if (currentState.internal) {
|
|
2431
|
-
currentState.internal.priority--;
|
|
2432
|
-
let parentRoot = currentState.previousRoot;
|
|
2433
|
-
while (parentRoot) {
|
|
2434
|
-
const parentState = parentRoot.getState();
|
|
2435
|
-
if (parentState?.internal) parentState.internal.priority--;
|
|
2436
|
-
parentRoot = parentState?.previousRoot;
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
};
|
|
2441
|
-
} else {
|
|
2442
|
-
const registerOutside = () => {
|
|
2443
|
-
return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
|
|
2444
|
-
};
|
|
2445
|
-
if (scheduler.independent || scheduler.isReady) {
|
|
2446
|
-
return registerOutside();
|
|
2447
|
-
}
|
|
2448
|
-
let unregisterJob = null;
|
|
2449
|
-
const unsubReady = scheduler.onRootReady(() => {
|
|
2450
|
-
unregisterJob = registerOutside();
|
|
2451
|
-
});
|
|
2452
|
-
return () => {
|
|
2453
|
-
unsubReady();
|
|
2454
|
-
unregisterJob?.();
|
|
2455
|
-
};
|
|
2083
|
+
return scheduler.register(
|
|
2084
|
+
(state, delta) => {
|
|
2085
|
+
const frameState = state;
|
|
2086
|
+
if (!frameState.renderer) return;
|
|
2087
|
+
callbackRef.current?.(frameState, delta);
|
|
2088
|
+
},
|
|
2089
|
+
{ id, ...options }
|
|
2090
|
+
);
|
|
2456
2091
|
}
|
|
2457
2092
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2458
2093
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2543,22 +2178,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2543
2178
|
function useTexture(input, optionsOrOnLoad) {
|
|
2544
2179
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2545
2180
|
const store = useStore();
|
|
2546
|
-
const textureCache = useThree((state) => state.textures);
|
|
2547
2181
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2548
|
-
const { onLoad, cache =
|
|
2182
|
+
const { onLoad, cache = true } = options;
|
|
2183
|
+
const onLoadRef = useRef(onLoad);
|
|
2184
|
+
onLoadRef.current = onLoad;
|
|
2185
|
+
const onLoadCalledForRef = useRef(null);
|
|
2549
2186
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2550
2187
|
const cachedResult = useMemo(() => {
|
|
2551
2188
|
if (!cache) return null;
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2189
|
+
const textures = store.getState().textures;
|
|
2190
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2191
|
+
return buildFromCache(input, textures);
|
|
2192
|
+
}, [cache, urls, input, store]);
|
|
2555
2193
|
const loadedTextures = useLoader(
|
|
2556
2194
|
TextureLoader,
|
|
2557
2195
|
IsObject(input) ? Object.values(input) : input
|
|
2558
2196
|
);
|
|
2197
|
+
const inputKey = urls.join("\0");
|
|
2559
2198
|
useLayoutEffect(() => {
|
|
2560
|
-
if (
|
|
2561
|
-
|
|
2199
|
+
if (cachedResult) return;
|
|
2200
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2201
|
+
onLoadCalledForRef.current = inputKey;
|
|
2202
|
+
onLoadRef.current?.(loadedTextures);
|
|
2203
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2562
2204
|
useEffect(() => {
|
|
2563
2205
|
if (cachedResult) return;
|
|
2564
2206
|
if ("initTexture" in renderer) {
|
|
@@ -2591,8 +2233,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2591
2233
|
}, [input, loadedTextures, cachedResult]);
|
|
2592
2234
|
useEffect(() => {
|
|
2593
2235
|
if (!cache) return;
|
|
2594
|
-
if (cachedResult) return;
|
|
2595
|
-
const set = store.setState;
|
|
2596
2236
|
const urlTextureMap = [];
|
|
2597
2237
|
if (typeof input === "string") {
|
|
2598
2238
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2606,18 +2246,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2606
2246
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2607
2247
|
}
|
|
2608
2248
|
}
|
|
2609
|
-
|
|
2610
|
-
const
|
|
2611
|
-
let
|
|
2249
|
+
store.setState((state) => {
|
|
2250
|
+
const refs = new Map(state._textureRefs);
|
|
2251
|
+
let textures = state.textures;
|
|
2252
|
+
let added = false;
|
|
2612
2253
|
for (const [url, texture] of urlTextureMap) {
|
|
2613
|
-
if (!
|
|
2614
|
-
|
|
2615
|
-
|
|
2254
|
+
if (!textures.has(url)) {
|
|
2255
|
+
if (!added) {
|
|
2256
|
+
textures = new Map(textures);
|
|
2257
|
+
added = true;
|
|
2258
|
+
}
|
|
2259
|
+
textures.set(url, texture);
|
|
2616
2260
|
}
|
|
2261
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2617
2262
|
}
|
|
2618
|
-
return
|
|
2263
|
+
return added ? { textures, _textureRefs: refs } : { _textureRefs: refs };
|
|
2264
|
+
});
|
|
2265
|
+
return () => store.setState((state) => {
|
|
2266
|
+
const refs = new Map(state._textureRefs);
|
|
2267
|
+
for (const [url] of urlTextureMap) {
|
|
2268
|
+
const next = (refs.get(url) ?? 0) - 1;
|
|
2269
|
+
if (next <= 0) refs.delete(url);
|
|
2270
|
+
else refs.set(url, next);
|
|
2271
|
+
}
|
|
2272
|
+
return { _textureRefs: refs };
|
|
2619
2273
|
});
|
|
2620
|
-
}, [cache, input, mappedTextures, store
|
|
2274
|
+
}, [cache, input, mappedTextures, store]);
|
|
2621
2275
|
return mappedTextures;
|
|
2622
2276
|
}
|
|
2623
2277
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -2633,106 +2287,90 @@ const Texture = ({
|
|
|
2633
2287
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
2634
2288
|
};
|
|
2635
2289
|
|
|
2636
|
-
function
|
|
2637
|
-
if (entry instanceof Texture$1) return entry;
|
|
2638
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
|
|
2639
|
-
return entry.value;
|
|
2640
|
-
}
|
|
2641
|
-
return null;
|
|
2642
|
-
}
|
|
2643
|
-
function useTextures() {
|
|
2290
|
+
function useTextures(selector) {
|
|
2644
2291
|
const store = useStore();
|
|
2645
|
-
|
|
2646
|
-
const set = store.setState;
|
|
2292
|
+
const registry = useMemo(() => {
|
|
2647
2293
|
const getState = store.getState;
|
|
2648
|
-
const
|
|
2649
|
-
|
|
2650
|
-
const newMap = new Map(state.textures);
|
|
2651
|
-
newMap.set(key, value);
|
|
2652
|
-
return { textures: newMap };
|
|
2653
|
-
});
|
|
2654
|
-
};
|
|
2655
|
-
const addMultiple = (items) => {
|
|
2656
|
-
set((state) => {
|
|
2657
|
-
const newMap = new Map(state.textures);
|
|
2658
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2659
|
-
for (const [key, value] of entries) {
|
|
2660
|
-
newMap.set(key, value);
|
|
2661
|
-
}
|
|
2662
|
-
return { textures: newMap };
|
|
2663
|
-
});
|
|
2664
|
-
};
|
|
2665
|
-
const remove = (key) => {
|
|
2666
|
-
set((state) => {
|
|
2667
|
-
const newMap = new Map(state.textures);
|
|
2668
|
-
newMap.delete(key);
|
|
2669
|
-
return { textures: newMap };
|
|
2670
|
-
});
|
|
2671
|
-
};
|
|
2672
|
-
const removeMultiple = (keys) => {
|
|
2673
|
-
set((state) => {
|
|
2674
|
-
const newMap = new Map(state.textures);
|
|
2675
|
-
for (const key of keys) newMap.delete(key);
|
|
2676
|
-
return { textures: newMap };
|
|
2677
|
-
});
|
|
2678
|
-
};
|
|
2679
|
-
const dispose = (key) => {
|
|
2680
|
-
const entry = getState().textures.get(key);
|
|
2681
|
-
if (entry) {
|
|
2682
|
-
const tex = getTextureValue(entry);
|
|
2683
|
-
tex?.dispose();
|
|
2684
|
-
}
|
|
2685
|
-
remove(key);
|
|
2686
|
-
};
|
|
2687
|
-
const disposeMultiple = (keys) => {
|
|
2688
|
-
const textures = getState().textures;
|
|
2689
|
-
for (const key of keys) {
|
|
2690
|
-
const entry = textures.get(key);
|
|
2691
|
-
if (entry) {
|
|
2692
|
-
const tex = getTextureValue(entry);
|
|
2693
|
-
tex?.dispose();
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
removeMultiple(keys);
|
|
2697
|
-
};
|
|
2698
|
-
const disposeAll = () => {
|
|
2699
|
-
const textures = getState().textures;
|
|
2700
|
-
for (const entry of textures.values()) {
|
|
2701
|
-
const tex = getTextureValue(entry);
|
|
2702
|
-
tex?.dispose();
|
|
2703
|
-
}
|
|
2704
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2705
|
-
};
|
|
2294
|
+
const setState = store.setState;
|
|
2295
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2706
2296
|
return {
|
|
2707
|
-
|
|
2708
|
-
get textures() {
|
|
2297
|
+
get all() {
|
|
2709
2298
|
return getState().textures;
|
|
2710
2299
|
},
|
|
2711
|
-
|
|
2712
|
-
|
|
2300
|
+
get(input) {
|
|
2301
|
+
if (typeof input === "string") return getOne(input);
|
|
2302
|
+
if (Array.isArray(input)) return input.map(getOne);
|
|
2303
|
+
const out = {};
|
|
2304
|
+
for (const name in input) out[name] = getOne(input[name]);
|
|
2305
|
+
return out;
|
|
2306
|
+
},
|
|
2713
2307
|
has: (key) => getState().textures.has(key),
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2308
|
+
add(keyOrRecord, texture) {
|
|
2309
|
+
setState((state) => {
|
|
2310
|
+
const textures = new Map(state.textures);
|
|
2311
|
+
if (typeof keyOrRecord === "string") {
|
|
2312
|
+
textures.set(keyOrRecord, texture);
|
|
2313
|
+
} else {
|
|
2314
|
+
for (const key in keyOrRecord) textures.set(key, keyOrRecord[key]);
|
|
2315
|
+
}
|
|
2316
|
+
return { textures };
|
|
2317
|
+
});
|
|
2318
|
+
},
|
|
2319
|
+
dispose(key, options) {
|
|
2320
|
+
const state = getState();
|
|
2321
|
+
const refs = state._textureRefs.get(key) ?? 0;
|
|
2322
|
+
if (refs > 0 && !options?.force) {
|
|
2323
|
+
console.warn(
|
|
2324
|
+
`[useTextures] "${key}" still has ${refs} active reference(s); skipping dispose. Pass { force: true } to override.`
|
|
2325
|
+
);
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
state.textures.get(key)?.dispose();
|
|
2329
|
+
setState((s) => {
|
|
2330
|
+
const textures = new Map(s.textures);
|
|
2331
|
+
textures.delete(key);
|
|
2332
|
+
const nextRefs = new Map(s._textureRefs);
|
|
2333
|
+
nextRefs.delete(key);
|
|
2334
|
+
return { textures, _textureRefs: nextRefs };
|
|
2335
|
+
});
|
|
2336
|
+
return true;
|
|
2337
|
+
},
|
|
2338
|
+
disposeAll() {
|
|
2339
|
+
for (const texture of getState().textures.values()) texture.dispose();
|
|
2340
|
+
setState({ textures: /* @__PURE__ */ new Map(), _textureRefs: /* @__PURE__ */ new Map() });
|
|
2341
|
+
}
|
|
2724
2342
|
};
|
|
2725
2343
|
}, [store]);
|
|
2344
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2345
|
+
const selected = useThree(subscribe);
|
|
2346
|
+
return selector ? selected : registry;
|
|
2726
2347
|
}
|
|
2727
2348
|
|
|
2728
|
-
function useRenderTarget(
|
|
2349
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2729
2350
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2730
2351
|
const size = useThree((s) => s.size);
|
|
2352
|
+
let width;
|
|
2353
|
+
let height;
|
|
2354
|
+
let opts;
|
|
2355
|
+
if (typeof widthOrOptions === "object") {
|
|
2356
|
+
opts = widthOrOptions;
|
|
2357
|
+
} else if (typeof widthOrOptions === "number") {
|
|
2358
|
+
width = widthOrOptions;
|
|
2359
|
+
if (typeof heightOrOptions === "object") {
|
|
2360
|
+
height = widthOrOptions;
|
|
2361
|
+
opts = heightOrOptions;
|
|
2362
|
+
} else if (typeof heightOrOptions === "number") {
|
|
2363
|
+
height = heightOrOptions;
|
|
2364
|
+
opts = options;
|
|
2365
|
+
} else {
|
|
2366
|
+
height = widthOrOptions;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2731
2369
|
return useMemo(() => {
|
|
2732
2370
|
const w = width ?? size.width;
|
|
2733
2371
|
const h = height ?? size.height;
|
|
2734
|
-
return new RenderTarget(w, h,
|
|
2735
|
-
}, [width, height, size.width, size.height,
|
|
2372
|
+
return new RenderTarget(w, h, opts);
|
|
2373
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2736
2374
|
}
|
|
2737
2375
|
|
|
2738
2376
|
function useStore() {
|
|
@@ -2782,11 +2420,11 @@ function addTail(callback) {
|
|
|
2782
2420
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2783
2421
|
getScheduler().invalidate(frames, stackFrames);
|
|
2784
2422
|
}
|
|
2785
|
-
function advance(timestamp
|
|
2423
|
+
function advance(timestamp) {
|
|
2786
2424
|
getScheduler().step(timestamp);
|
|
2787
2425
|
}
|
|
2788
2426
|
|
|
2789
|
-
const version = "10.0.0-alpha.
|
|
2427
|
+
const version = "10.0.0-alpha.3";
|
|
2790
2428
|
const packageData = {
|
|
2791
2429
|
version: version};
|
|
2792
2430
|
|
|
@@ -14225,6 +13863,7 @@ function swapInstances() {
|
|
|
14225
13863
|
}
|
|
14226
13864
|
}
|
|
14227
13865
|
if (instance.isHidden) unhideInstance(instance);
|
|
13866
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14228
13867
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14229
13868
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14230
13869
|
}
|
|
@@ -14236,6 +13875,9 @@ function swapInstances() {
|
|
|
14236
13875
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14237
13876
|
instance.object.__r3f = instance;
|
|
14238
13877
|
setFiberRef(fiber, instance.object);
|
|
13878
|
+
delete instance.appliedOnce;
|
|
13879
|
+
instance.eventCount = 0;
|
|
13880
|
+
instance.handlers = {};
|
|
14239
13881
|
applyProps(instance.object, instance.props);
|
|
14240
13882
|
if (instance.props.attach) {
|
|
14241
13883
|
attach(parent, instance);
|
|
@@ -14309,8 +13951,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14309
13951
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14310
13952
|
if (isTailSibling) swapInstances();
|
|
14311
13953
|
},
|
|
14312
|
-
finalizeInitialChildren: () =>
|
|
14313
|
-
|
|
13954
|
+
finalizeInitialChildren: (instance) => {
|
|
13955
|
+
for (const prop in instance.props) {
|
|
13956
|
+
if (isFromRef(instance.props[prop])) return true;
|
|
13957
|
+
}
|
|
13958
|
+
return false;
|
|
13959
|
+
},
|
|
13960
|
+
commitMount(instance) {
|
|
13961
|
+
const resolved = {};
|
|
13962
|
+
for (const prop in instance.props) {
|
|
13963
|
+
const value = instance.props[prop];
|
|
13964
|
+
if (isFromRef(value)) {
|
|
13965
|
+
const ref = value[FROM_REF];
|
|
13966
|
+
if (ref.current != null) resolved[prop] = ref.current;
|
|
13967
|
+
}
|
|
13968
|
+
}
|
|
13969
|
+
if (Object.keys(resolved).length) applyProps(instance.object, resolved);
|
|
14314
13970
|
},
|
|
14315
13971
|
getPublicInstance: (instance) => instance?.object,
|
|
14316
13972
|
prepareForCommit: () => null,
|
|
@@ -14437,11 +14093,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14437
14093
|
cloneMutableTextInstance(textInstance) {
|
|
14438
14094
|
return textInstance;
|
|
14439
14095
|
},
|
|
14096
|
+
// React 19.2 added these for root-level view transitions. They only run for hosts that opt into
|
|
14097
|
+
// view transitions by returning a container from cloneRootViewTransitionContainer; R3F renders
|
|
14098
|
+
// into a WebGL/WebGPU canvas, where cloning the root has no meaning, so we never opt in and
|
|
14099
|
+
// React never calls them. They stay as no-ops rather than throws: the host config is shared by
|
|
14100
|
+
// every root, and a throw here would turn a React-internal probe into a hard crash for users.
|
|
14101
|
+
// Guarded by a test asserting a full render + unmount never reaches them.
|
|
14440
14102
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14441
|
-
throw new Error("Not implemented.");
|
|
14442
14103
|
},
|
|
14443
14104
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14444
|
-
throw new Error("Not implemented.");
|
|
14445
14105
|
},
|
|
14446
14106
|
// https://github.com/facebook/react/pull/32465
|
|
14447
14107
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14526,11 +14186,15 @@ function createRoot(canvas) {
|
|
|
14526
14186
|
const lastConfiguredProps = {};
|
|
14527
14187
|
let configured = false;
|
|
14528
14188
|
let pending = null;
|
|
14189
|
+
let rendererSetup = null;
|
|
14529
14190
|
return {
|
|
14530
14191
|
async configure(props = {}) {
|
|
14531
14192
|
let resolve;
|
|
14532
14193
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14533
14194
|
const {
|
|
14195
|
+
id: canvasId,
|
|
14196
|
+
primaryCanvas,
|
|
14197
|
+
scheduler: schedulerConfig,
|
|
14534
14198
|
gl: glConfig,
|
|
14535
14199
|
renderer: rendererConfig,
|
|
14536
14200
|
size: propsSize,
|
|
@@ -14538,10 +14202,6 @@ function createRoot(canvas) {
|
|
|
14538
14202
|
events,
|
|
14539
14203
|
onCreated: onCreatedCallback,
|
|
14540
14204
|
shadows = false,
|
|
14541
|
-
linear = false,
|
|
14542
|
-
flat = false,
|
|
14543
|
-
textureColorSpace = SRGBColorSpace,
|
|
14544
|
-
legacy = false,
|
|
14545
14205
|
orthographic = false,
|
|
14546
14206
|
frameloop = "always",
|
|
14547
14207
|
dpr = [1, 2],
|
|
@@ -14553,11 +14213,14 @@ function createRoot(canvas) {
|
|
|
14553
14213
|
onDropMissed,
|
|
14554
14214
|
autoUpdateFrustum = true,
|
|
14555
14215
|
occlusion = false,
|
|
14556
|
-
_sizeProps
|
|
14216
|
+
_sizeProps,
|
|
14217
|
+
forceEven
|
|
14557
14218
|
} = props;
|
|
14558
|
-
const
|
|
14219
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14220
|
+
let state = store.getState();
|
|
14559
14221
|
const defaultGPUProps = {
|
|
14560
|
-
canvas
|
|
14222
|
+
canvas,
|
|
14223
|
+
antialias: true
|
|
14561
14224
|
};
|
|
14562
14225
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14563
14226
|
throw new Error(
|
|
@@ -14569,14 +14232,63 @@ function createRoot(canvas) {
|
|
|
14569
14232
|
}
|
|
14570
14233
|
let renderer = state.internal.actualRenderer;
|
|
14571
14234
|
if (!state.internal.actualRenderer) {
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
|
|
14235
|
+
if (!rendererSetup) {
|
|
14236
|
+
rendererSetup = (async () => {
|
|
14237
|
+
if (primaryCanvas) {
|
|
14238
|
+
const primary = await waitForPrimary(primaryCanvas);
|
|
14239
|
+
renderer = primary.renderer;
|
|
14240
|
+
state.internal.actualRenderer = renderer;
|
|
14241
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14242
|
+
primary.store.setState((prev) => ({
|
|
14243
|
+
internal: { ...prev.internal, isMultiCanvas: true }
|
|
14244
|
+
}));
|
|
14245
|
+
state.set((prev) => ({
|
|
14246
|
+
webGPUSupported: primary.store.getState().webGPUSupported,
|
|
14247
|
+
renderer,
|
|
14248
|
+
primaryStore: primary.store,
|
|
14249
|
+
internal: {
|
|
14250
|
+
...prev.internal,
|
|
14251
|
+
canvasTarget,
|
|
14252
|
+
isMultiCanvas: true,
|
|
14253
|
+
isSecondary: true,
|
|
14254
|
+
targetId: primaryCanvas
|
|
14255
|
+
}
|
|
14256
|
+
}));
|
|
14257
|
+
} else {
|
|
14258
|
+
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14259
|
+
if (!renderer.hasInitialized?.()) {
|
|
14260
|
+
const size2 = computeInitialSize(canvas, propsSize);
|
|
14261
|
+
if (size2.width > 0 && size2.height > 0) {
|
|
14262
|
+
const pixelRatio = calculateDpr(dpr);
|
|
14263
|
+
canvas.width = size2.width * pixelRatio;
|
|
14264
|
+
canvas.height = size2.height * pixelRatio;
|
|
14265
|
+
}
|
|
14266
|
+
await renderer.init();
|
|
14267
|
+
}
|
|
14268
|
+
const backend = renderer.backend;
|
|
14269
|
+
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14270
|
+
state.internal.actualRenderer = renderer;
|
|
14271
|
+
state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
|
|
14272
|
+
if (canvasId && !state.internal.isSecondary) {
|
|
14273
|
+
const canvasTarget = new CanvasTarget(canvas);
|
|
14274
|
+
const unregisterPrimary = registerPrimary(canvasId, renderer, store);
|
|
14275
|
+
state.set((prev) => ({
|
|
14276
|
+
internal: {
|
|
14277
|
+
...prev.internal,
|
|
14278
|
+
canvasTarget,
|
|
14279
|
+
unregisterPrimary
|
|
14280
|
+
}
|
|
14281
|
+
}));
|
|
14282
|
+
}
|
|
14283
|
+
}
|
|
14284
|
+
})().catch((err) => {
|
|
14285
|
+
rendererSetup = null;
|
|
14286
|
+
throw err;
|
|
14287
|
+
});
|
|
14575
14288
|
}
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
state.internal.actualRenderer
|
|
14579
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14289
|
+
await rendererSetup;
|
|
14290
|
+
state = store.getState();
|
|
14291
|
+
renderer = state.internal.actualRenderer;
|
|
14580
14292
|
}
|
|
14581
14293
|
let raycaster = state.raycaster;
|
|
14582
14294
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
@@ -14585,6 +14297,7 @@ function createRoot(canvas) {
|
|
|
14585
14297
|
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14586
14298
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14587
14299
|
}
|
|
14300
|
+
let tempCamera = state.camera;
|
|
14588
14301
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14589
14302
|
lastCamera = cameraOptions;
|
|
14590
14303
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14604,6 +14317,7 @@ function createRoot(canvas) {
|
|
|
14604
14317
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14605
14318
|
}
|
|
14606
14319
|
state.set({ camera });
|
|
14320
|
+
tempCamera = camera;
|
|
14607
14321
|
raycaster.camera = camera;
|
|
14608
14322
|
}
|
|
14609
14323
|
if (!state.scene) {
|
|
@@ -14621,7 +14335,7 @@ function createRoot(canvas) {
|
|
|
14621
14335
|
rootScene: scene,
|
|
14622
14336
|
internal: { ...prev.internal, container: scene }
|
|
14623
14337
|
}));
|
|
14624
|
-
const camera =
|
|
14338
|
+
const camera = tempCamera;
|
|
14625
14339
|
if (camera && !camera.parent) scene.add(camera);
|
|
14626
14340
|
}
|
|
14627
14341
|
if (events && !state.events.handlers) {
|
|
@@ -14638,6 +14352,9 @@ function createRoot(canvas) {
|
|
|
14638
14352
|
if (_sizeProps !== void 0) {
|
|
14639
14353
|
state.set({ _sizeProps });
|
|
14640
14354
|
}
|
|
14355
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14356
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14357
|
+
}
|
|
14641
14358
|
const size = computeInitialSize(canvas, propsSize);
|
|
14642
14359
|
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14643
14360
|
const wasImperative = state._sizeImperative;
|
|
@@ -14664,10 +14381,10 @@ function createRoot(canvas) {
|
|
|
14664
14381
|
lastConfiguredProps.performance = performance;
|
|
14665
14382
|
}
|
|
14666
14383
|
if (!state.xr) {
|
|
14667
|
-
const handleXRFrame = (timestamp,
|
|
14384
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14668
14385
|
const state2 = store.getState();
|
|
14669
14386
|
if (state2.frameloop === "never") return;
|
|
14670
|
-
advance(timestamp
|
|
14387
|
+
advance(timestamp);
|
|
14671
14388
|
};
|
|
14672
14389
|
const actualRenderer = state.internal.actualRenderer;
|
|
14673
14390
|
const handleSessionChange = () => {
|
|
@@ -14679,16 +14396,16 @@ function createRoot(canvas) {
|
|
|
14679
14396
|
};
|
|
14680
14397
|
const xr = {
|
|
14681
14398
|
connect() {
|
|
14682
|
-
const { gl, renderer: renderer2
|
|
14683
|
-
const
|
|
14684
|
-
|
|
14685
|
-
|
|
14399
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14400
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14401
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14402
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14686
14403
|
},
|
|
14687
14404
|
disconnect() {
|
|
14688
|
-
const { gl, renderer: renderer2
|
|
14689
|
-
const
|
|
14690
|
-
|
|
14691
|
-
|
|
14405
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14406
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14407
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14408
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14692
14409
|
}
|
|
14693
14410
|
};
|
|
14694
14411
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14700,15 +14417,22 @@ function createRoot(canvas) {
|
|
|
14700
14417
|
const oldType = renderer.shadowMap.type;
|
|
14701
14418
|
renderer.shadowMap.enabled = !!shadows;
|
|
14702
14419
|
if (is.boo(shadows)) {
|
|
14703
|
-
renderer.shadowMap.type =
|
|
14420
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
14704
14421
|
} else if (is.str(shadows)) {
|
|
14422
|
+
if (shadows === "soft") {
|
|
14423
|
+
notifyDepreciated({
|
|
14424
|
+
heading: 'shadows="soft" is deprecated',
|
|
14425
|
+
body: "Three has depreciated soft and improved basic PCFShadows, we converted for you.",
|
|
14426
|
+
link: "https://github.com/mrdoob/three.js/wiki/Migration-Guide?utm_source=chatgpt.com#181--182"
|
|
14427
|
+
});
|
|
14428
|
+
}
|
|
14705
14429
|
const types = {
|
|
14706
14430
|
basic: BasicShadowMap,
|
|
14707
14431
|
percentage: PCFShadowMap,
|
|
14708
|
-
soft:
|
|
14432
|
+
soft: PCFShadowMap,
|
|
14709
14433
|
variance: VSMShadowMap
|
|
14710
14434
|
};
|
|
14711
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14435
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
14712
14436
|
} else if (is.obj(shadows)) {
|
|
14713
14437
|
Object.assign(renderer.shadowMap, shadows);
|
|
14714
14438
|
}
|
|
@@ -14716,27 +14440,69 @@ function createRoot(canvas) {
|
|
|
14716
14440
|
renderer.shadowMap.needsUpdate = true;
|
|
14717
14441
|
}
|
|
14718
14442
|
}
|
|
14443
|
+
if (!configured) {
|
|
14444
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
14445
|
+
renderer.toneMapping = ACESFilmicToneMapping;
|
|
14446
|
+
}
|
|
14719
14447
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14720
14448
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14721
14449
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14722
14450
|
}
|
|
14451
|
+
const r3fProps = ["textureColorSpace"];
|
|
14452
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14453
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14723
14454
|
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14724
|
-
|
|
14455
|
+
const glProps = {};
|
|
14456
|
+
for (const key in glConfig) {
|
|
14457
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14458
|
+
}
|
|
14459
|
+
applyProps(renderer, glProps);
|
|
14725
14460
|
}
|
|
14726
14461
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14727
14462
|
const currentRenderer = state.renderer;
|
|
14728
14463
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14729
|
-
|
|
14464
|
+
const rendererProps = {};
|
|
14465
|
+
for (const key in rendererConfig) {
|
|
14466
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14467
|
+
}
|
|
14468
|
+
applyProps(currentRenderer, rendererProps);
|
|
14730
14469
|
}
|
|
14731
14470
|
}
|
|
14732
14471
|
const scheduler = getScheduler();
|
|
14733
14472
|
const rootId = state.internal.rootId;
|
|
14734
14473
|
if (!rootId) {
|
|
14735
|
-
const newRootId = scheduler.generateRootId();
|
|
14474
|
+
const newRootId = canvasId || scheduler.generateRootId();
|
|
14736
14475
|
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14737
14476
|
getState: () => store.getState(),
|
|
14738
14477
|
onError: (err) => store.getState().setError(err)
|
|
14739
14478
|
});
|
|
14479
|
+
const unregisterCanvasTarget = scheduler.register(
|
|
14480
|
+
() => {
|
|
14481
|
+
const state2 = store.getState();
|
|
14482
|
+
if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
|
|
14483
|
+
const renderer2 = state2.internal.actualRenderer;
|
|
14484
|
+
renderer2.setCanvasTarget(state2.internal.canvasTarget);
|
|
14485
|
+
}
|
|
14486
|
+
},
|
|
14487
|
+
{
|
|
14488
|
+
id: `${newRootId}_canvasTarget`,
|
|
14489
|
+
rootId: newRootId,
|
|
14490
|
+
phase: "start",
|
|
14491
|
+
system: true
|
|
14492
|
+
}
|
|
14493
|
+
);
|
|
14494
|
+
const unregisterEventsFlush = scheduler.register(
|
|
14495
|
+
() => {
|
|
14496
|
+
const state2 = store.getState();
|
|
14497
|
+
state2.events.flush?.();
|
|
14498
|
+
},
|
|
14499
|
+
{
|
|
14500
|
+
id: `${newRootId}_events`,
|
|
14501
|
+
rootId: newRootId,
|
|
14502
|
+
phase: "input",
|
|
14503
|
+
system: true
|
|
14504
|
+
}
|
|
14505
|
+
);
|
|
14740
14506
|
const unregisterFrustum = scheduler.register(
|
|
14741
14507
|
() => {
|
|
14742
14508
|
const state2 = store.getState();
|
|
@@ -14747,7 +14513,7 @@ function createRoot(canvas) {
|
|
|
14747
14513
|
{
|
|
14748
14514
|
id: `${newRootId}_frustum`,
|
|
14749
14515
|
rootId: newRootId,
|
|
14750
|
-
|
|
14516
|
+
before: "render",
|
|
14751
14517
|
system: true
|
|
14752
14518
|
}
|
|
14753
14519
|
);
|
|
@@ -14759,7 +14525,7 @@ function createRoot(canvas) {
|
|
|
14759
14525
|
{
|
|
14760
14526
|
id: `${newRootId}_visibility`,
|
|
14761
14527
|
rootId: newRootId,
|
|
14762
|
-
|
|
14528
|
+
before: "render",
|
|
14763
14529
|
system: true,
|
|
14764
14530
|
after: `${newRootId}_frustum`
|
|
14765
14531
|
}
|
|
@@ -14771,18 +14537,22 @@ function createRoot(canvas) {
|
|
|
14771
14537
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14772
14538
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14773
14539
|
try {
|
|
14774
|
-
if (state2.
|
|
14540
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14775
14541
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14776
14542
|
} catch (error) {
|
|
14777
14543
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14778
14544
|
}
|
|
14779
14545
|
},
|
|
14780
14546
|
{
|
|
14781
|
-
|
|
14547
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14548
|
+
id: canvasId || `${newRootId}_render`,
|
|
14782
14549
|
rootId: newRootId,
|
|
14783
14550
|
phase: "render",
|
|
14784
|
-
system: true
|
|
14551
|
+
system: true,
|
|
14785
14552
|
// Internal flag: this is a system job, not user-controlled
|
|
14553
|
+
// Apply scheduler config for render ordering and rate limiting
|
|
14554
|
+
...schedulerConfig?.after && { after: schedulerConfig.after },
|
|
14555
|
+
...schedulerConfig?.fps && { fps: schedulerConfig.fps }
|
|
14786
14556
|
}
|
|
14787
14557
|
);
|
|
14788
14558
|
state.set((state2) => ({
|
|
@@ -14791,6 +14561,8 @@ function createRoot(canvas) {
|
|
|
14791
14561
|
rootId: newRootId,
|
|
14792
14562
|
unregisterRoot: () => {
|
|
14793
14563
|
unregisterRoot();
|
|
14564
|
+
unregisterCanvasTarget();
|
|
14565
|
+
unregisterEventsFlush();
|
|
14794
14566
|
unregisterFrustum();
|
|
14795
14567
|
unregisterVisibility();
|
|
14796
14568
|
unregisterRender();
|
|
@@ -14849,15 +14621,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14849
14621
|
const renderer = state.internal.actualRenderer;
|
|
14850
14622
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14851
14623
|
if (unregisterRoot) unregisterRoot();
|
|
14624
|
+
const unregisterPrimary = state.internal.unregisterPrimary;
|
|
14625
|
+
if (unregisterPrimary) unregisterPrimary();
|
|
14626
|
+
const canvasTarget = state.internal.canvasTarget;
|
|
14627
|
+
if (canvasTarget?.dispose) canvasTarget.dispose();
|
|
14852
14628
|
state.events.disconnect?.();
|
|
14853
14629
|
cleanupHelperGroup(root.store);
|
|
14854
|
-
renderer
|
|
14855
|
-
|
|
14856
|
-
|
|
14630
|
+
if (state.isLegacy && renderer) {
|
|
14631
|
+
;
|
|
14632
|
+
renderer.renderLists?.dispose?.();
|
|
14633
|
+
renderer.forceContextLoss?.();
|
|
14634
|
+
}
|
|
14635
|
+
if (!state.internal.isSecondary) {
|
|
14636
|
+
if (renderer?.xr) state.xr.disconnect();
|
|
14637
|
+
}
|
|
14857
14638
|
dispose(state.scene);
|
|
14858
14639
|
_roots.delete(canvas);
|
|
14859
14640
|
if (callback) callback(canvas);
|
|
14860
|
-
} catch
|
|
14641
|
+
} catch {
|
|
14861
14642
|
}
|
|
14862
14643
|
}, 500);
|
|
14863
14644
|
}
|
|
@@ -14865,36 +14646,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14865
14646
|
}
|
|
14866
14647
|
}
|
|
14867
14648
|
function createPortal(children, container, state) {
|
|
14868
|
-
return /* @__PURE__ */ jsx(
|
|
14649
|
+
return /* @__PURE__ */ jsx(Portal, { children, container, state });
|
|
14869
14650
|
}
|
|
14870
|
-
function
|
|
14651
|
+
function Portal({ children, container, state }) {
|
|
14871
14652
|
const isRef = useCallback((obj) => obj && "current" in obj, []);
|
|
14872
|
-
const [resolvedContainer,
|
|
14653
|
+
const [resolvedContainer, _setResolvedContainer] = useState(() => {
|
|
14873
14654
|
if (isRef(container)) return container.current ?? null;
|
|
14874
14655
|
return container;
|
|
14875
14656
|
});
|
|
14657
|
+
const setResolvedContainer = useCallback(
|
|
14658
|
+
(newContainer) => {
|
|
14659
|
+
if (!newContainer || newContainer === resolvedContainer) return;
|
|
14660
|
+
_setResolvedContainer(isRef(newContainer) ? newContainer.current : newContainer);
|
|
14661
|
+
},
|
|
14662
|
+
[resolvedContainer, _setResolvedContainer, isRef]
|
|
14663
|
+
);
|
|
14876
14664
|
useMemo(() => {
|
|
14877
|
-
if (isRef(container)) {
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
const updated = container.current;
|
|
14882
|
-
if (updated && updated !== resolvedContainer) {
|
|
14883
|
-
setResolvedContainer(updated);
|
|
14884
|
-
}
|
|
14885
|
-
});
|
|
14886
|
-
} else if (current !== resolvedContainer) {
|
|
14887
|
-
setResolvedContainer(current);
|
|
14888
|
-
}
|
|
14889
|
-
} else if (container !== resolvedContainer) {
|
|
14890
|
-
setResolvedContainer(container);
|
|
14665
|
+
if (isRef(container) && !container.current) {
|
|
14666
|
+
return queueMicrotask(() => {
|
|
14667
|
+
setResolvedContainer(container.current);
|
|
14668
|
+
});
|
|
14891
14669
|
}
|
|
14892
|
-
|
|
14670
|
+
setResolvedContainer(container);
|
|
14671
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14893
14672
|
if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
|
|
14894
14673
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14895
|
-
return /* @__PURE__ */ jsx(
|
|
14674
|
+
return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14896
14675
|
}
|
|
14897
|
-
function
|
|
14676
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14898
14677
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14899
14678
|
const previousRoot = useStore();
|
|
14900
14679
|
const [raycaster] = useState(() => new Raycaster());
|
|
@@ -14915,12 +14694,14 @@ function Portal({ state = {}, children, container }) {
|
|
|
14915
14694
|
};
|
|
14916
14695
|
}, [portalScene, container, injectScene]);
|
|
14917
14696
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14697
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14918
14698
|
let viewport = void 0;
|
|
14919
|
-
if (injectState.camera && size) {
|
|
14699
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14920
14700
|
const camera = injectState.camera;
|
|
14921
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(),
|
|
14922
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14701
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
14702
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14923
14703
|
}
|
|
14704
|
+
const set = injectState.set;
|
|
14924
14705
|
return {
|
|
14925
14706
|
// The intersect consists of the previous root state
|
|
14926
14707
|
...rootState,
|
|
@@ -14936,10 +14717,10 @@ function Portal({ state = {}, children, container }) {
|
|
|
14936
14717
|
previousRoot,
|
|
14937
14718
|
// Events, size and viewport can be overridden by the inject layer
|
|
14938
14719
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14939
|
-
size:
|
|
14720
|
+
size: resolvedSize,
|
|
14940
14721
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14941
14722
|
// Layers are allowed to override events
|
|
14942
|
-
setEvents: (events2) =>
|
|
14723
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
14943
14724
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
14944
14725
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
14945
14726
|
};
|
|
@@ -14948,9 +14729,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14948
14729
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14949
14730
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14950
14731
|
onMutate(previousRoot.getState());
|
|
14951
|
-
previousRoot.subscribe(onMutate);
|
|
14952
14732
|
return store;
|
|
14953
14733
|
}, [previousRoot, container]);
|
|
14734
|
+
useIsomorphicLayoutEffect(() => {
|
|
14735
|
+
const onMutate = (prev) => usePortalStore.setState((state2) => inject.current(prev, state2));
|
|
14736
|
+
const unsubscribe = previousRoot.subscribe(onMutate);
|
|
14737
|
+
return unsubscribe;
|
|
14738
|
+
}, [previousRoot, usePortalStore]);
|
|
14954
14739
|
return (
|
|
14955
14740
|
// @ts-ignore, reconciler types are not maintained
|
|
14956
14741
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -14964,21 +14749,72 @@ function flushSync(fn) {
|
|
|
14964
14749
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14965
14750
|
}
|
|
14966
14751
|
|
|
14752
|
+
function parseBackground(background) {
|
|
14753
|
+
if (!background) return null;
|
|
14754
|
+
if (typeof background === "object" && !background.isColor) {
|
|
14755
|
+
const { backgroundMap, envMap, files, preset, ...rest } = background;
|
|
14756
|
+
return {
|
|
14757
|
+
...rest,
|
|
14758
|
+
preset,
|
|
14759
|
+
files: envMap || files,
|
|
14760
|
+
backgroundFiles: backgroundMap,
|
|
14761
|
+
background: true
|
|
14762
|
+
};
|
|
14763
|
+
}
|
|
14764
|
+
if (typeof background === "number") {
|
|
14765
|
+
return { color: background, background: true };
|
|
14766
|
+
}
|
|
14767
|
+
if (typeof background === "string") {
|
|
14768
|
+
if (background in presetsObj) {
|
|
14769
|
+
return { preset: background, background: true };
|
|
14770
|
+
}
|
|
14771
|
+
if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
|
|
14772
|
+
return { files: background, background: true };
|
|
14773
|
+
}
|
|
14774
|
+
return { color: background, background: true };
|
|
14775
|
+
}
|
|
14776
|
+
if (background.isColor) {
|
|
14777
|
+
return { color: background, background: true };
|
|
14778
|
+
}
|
|
14779
|
+
return null;
|
|
14780
|
+
}
|
|
14781
|
+
|
|
14782
|
+
function parseRendererConfig(rendererProp) {
|
|
14783
|
+
const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
|
|
14784
|
+
if (isRendererConfig) {
|
|
14785
|
+
const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
|
|
14786
|
+
return {
|
|
14787
|
+
primaryCanvas: pc,
|
|
14788
|
+
scheduler: sc,
|
|
14789
|
+
renderer: Object.keys(rest).length > 0 ? rest : rendererProp
|
|
14790
|
+
};
|
|
14791
|
+
}
|
|
14792
|
+
return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
|
|
14793
|
+
}
|
|
14794
|
+
|
|
14795
|
+
function clearHmrCaches(store) {
|
|
14796
|
+
store.setState((state) => ({
|
|
14797
|
+
nodes: {},
|
|
14798
|
+
uniforms: {},
|
|
14799
|
+
buffers: {},
|
|
14800
|
+
gpuStorage: {},
|
|
14801
|
+
_hmrVersion: state._hmrVersion + 1
|
|
14802
|
+
}));
|
|
14803
|
+
}
|
|
14804
|
+
|
|
14967
14805
|
function CanvasImpl({
|
|
14968
14806
|
ref,
|
|
14969
14807
|
children,
|
|
14970
14808
|
fallback,
|
|
14971
14809
|
resize,
|
|
14972
14810
|
style,
|
|
14811
|
+
id,
|
|
14973
14812
|
gl,
|
|
14974
|
-
renderer,
|
|
14813
|
+
renderer: rendererProp,
|
|
14975
14814
|
events = createPointerEvents,
|
|
14976
14815
|
eventSource,
|
|
14977
14816
|
eventPrefix,
|
|
14978
14817
|
shadows,
|
|
14979
|
-
linear,
|
|
14980
|
-
flat,
|
|
14981
|
-
legacy,
|
|
14982
14818
|
orthographic,
|
|
14983
14819
|
frameloop,
|
|
14984
14820
|
dpr,
|
|
@@ -14986,6 +14822,8 @@ function CanvasImpl({
|
|
|
14986
14822
|
raycaster,
|
|
14987
14823
|
camera,
|
|
14988
14824
|
scene,
|
|
14825
|
+
autoUpdateFrustum,
|
|
14826
|
+
occlusion,
|
|
14989
14827
|
onPointerMissed,
|
|
14990
14828
|
onDragOverMissed,
|
|
14991
14829
|
onDropMissed,
|
|
@@ -14993,10 +14831,14 @@ function CanvasImpl({
|
|
|
14993
14831
|
hmr,
|
|
14994
14832
|
width,
|
|
14995
14833
|
height,
|
|
14834
|
+
background,
|
|
14835
|
+
forceEven,
|
|
14996
14836
|
...props
|
|
14997
14837
|
}) {
|
|
14838
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
14998
14839
|
React.useMemo(() => extend(THREE), []);
|
|
14999
14840
|
const Bridge = useBridge();
|
|
14841
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
15000
14842
|
const hasInitialSizeRef = React.useRef(false);
|
|
15001
14843
|
const measureConfig = React.useMemo(() => {
|
|
15002
14844
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15013,15 +14855,20 @@ function CanvasImpl({
|
|
|
15013
14855
|
};
|
|
15014
14856
|
}, [resize, hasInitialSizeRef.current]);
|
|
15015
14857
|
const [containerRef, containerRect] = useMeasure(measureConfig);
|
|
15016
|
-
const effectiveSize = React.useMemo(
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
14858
|
+
const effectiveSize = React.useMemo(() => {
|
|
14859
|
+
let w = width ?? containerRect.width;
|
|
14860
|
+
let h = height ?? containerRect.height;
|
|
14861
|
+
if (forceEven) {
|
|
14862
|
+
w = Math.ceil(w / 2) * 2;
|
|
14863
|
+
h = Math.ceil(h / 2) * 2;
|
|
14864
|
+
}
|
|
14865
|
+
return {
|
|
14866
|
+
width: w,
|
|
14867
|
+
height: h,
|
|
15020
14868
|
top: containerRect.top,
|
|
15021
14869
|
left: containerRect.left
|
|
15022
|
-
}
|
|
15023
|
-
|
|
15024
|
-
);
|
|
14870
|
+
};
|
|
14871
|
+
}, [width, height, containerRect, forceEven]);
|
|
15025
14872
|
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15026
14873
|
hasInitialSizeRef.current = true;
|
|
15027
14874
|
}
|
|
@@ -15033,6 +14880,7 @@ function CanvasImpl({
|
|
|
15033
14880
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15034
14881
|
const [block, setBlock] = React.useState(false);
|
|
15035
14882
|
const [error, setError] = React.useState(false);
|
|
14883
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15036
14884
|
if (block) throw block;
|
|
15037
14885
|
if (error) throw error;
|
|
15038
14886
|
const root = React.useRef(null);
|
|
@@ -15040,6 +14888,7 @@ function CanvasImpl({
|
|
|
15040
14888
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15041
14889
|
useIsomorphicLayoutEffect(() => {
|
|
15042
14890
|
effectActiveRef.current = true;
|
|
14891
|
+
if (fallbackVisible) return;
|
|
15043
14892
|
const canvas = canvasRef.current;
|
|
15044
14893
|
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15045
14894
|
if (!root.current) {
|
|
@@ -15060,24 +14909,27 @@ function CanvasImpl({
|
|
|
15060
14909
|
}
|
|
15061
14910
|
async function run() {
|
|
15062
14911
|
if (!effectActiveRef.current || !root.current) return;
|
|
15063
|
-
await root.current.configure({
|
|
14912
|
+
const configured = await root.current.configure({
|
|
14913
|
+
id,
|
|
14914
|
+
primaryCanvas,
|
|
14915
|
+
scheduler,
|
|
15064
14916
|
gl,
|
|
15065
14917
|
renderer,
|
|
15066
14918
|
scene,
|
|
15067
14919
|
events,
|
|
15068
14920
|
shadows,
|
|
15069
|
-
linear,
|
|
15070
|
-
flat,
|
|
15071
|
-
legacy,
|
|
15072
14921
|
orthographic,
|
|
15073
14922
|
frameloop,
|
|
15074
14923
|
dpr,
|
|
15075
14924
|
performance,
|
|
15076
14925
|
raycaster,
|
|
15077
14926
|
camera,
|
|
14927
|
+
autoUpdateFrustum,
|
|
14928
|
+
occlusion,
|
|
15078
14929
|
size: effectiveSize,
|
|
15079
14930
|
// Store size props for reset functionality
|
|
15080
14931
|
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14932
|
+
forceEven,
|
|
15081
14933
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15082
14934
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15083
14935
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15098,10 +14950,25 @@ function CanvasImpl({
|
|
|
15098
14950
|
}
|
|
15099
14951
|
onCreated?.(state);
|
|
15100
14952
|
}
|
|
15101
|
-
})
|
|
15102
|
-
|
|
14953
|
+
}).then(
|
|
14954
|
+
() => true,
|
|
14955
|
+
// Renderer setup failed (e.g. no WebGL/WebGPU support). The `fallback` prop lives
|
|
14956
|
+
// inside <canvas>, which browsers don't display, so surface it as visible DOM
|
|
14957
|
+
// instead; with no fallback, rethrow to an external error boundary. (#3757)
|
|
14958
|
+
(setupError) => {
|
|
14959
|
+
if (effectActiveRef.current) {
|
|
14960
|
+
if (fallback != null) setFallbackVisible(true);
|
|
14961
|
+
else setError(setupError);
|
|
14962
|
+
}
|
|
14963
|
+
return false;
|
|
14964
|
+
}
|
|
14965
|
+
);
|
|
14966
|
+
if (!configured || !effectActiveRef.current || !root.current) return;
|
|
15103
14967
|
root.current.render(
|
|
15104
|
-
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */
|
|
14968
|
+
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsxs(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: [
|
|
14969
|
+
backgroundProps && /* @__PURE__ */ jsx(Environment, { ...backgroundProps }),
|
|
14970
|
+
children ?? null
|
|
14971
|
+
] }) }) })
|
|
15105
14972
|
);
|
|
15106
14973
|
}
|
|
15107
14974
|
run();
|
|
@@ -15128,20 +14995,15 @@ function CanvasImpl({
|
|
|
15128
14995
|
const canvas = canvasRef.current;
|
|
15129
14996
|
if (!canvas) return;
|
|
15130
14997
|
const handleHMR = () => {
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
rootEntry
|
|
15134
|
-
|
|
15135
|
-
uniforms: {},
|
|
15136
|
-
_hmrVersion: state._hmrVersion + 1
|
|
15137
|
-
}));
|
|
15138
|
-
}
|
|
14998
|
+
queueMicrotask(() => {
|
|
14999
|
+
const rootEntry = _roots.get(canvas);
|
|
15000
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15001
|
+
});
|
|
15139
15002
|
};
|
|
15140
15003
|
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15141
15004
|
const hot = import.meta.hot;
|
|
15142
15005
|
hot.on("vite:afterUpdate", handleHMR);
|
|
15143
|
-
return () => hot.
|
|
15144
|
-
});
|
|
15006
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15145
15007
|
}
|
|
15146
15008
|
if (typeof module !== "undefined" && module.hot) {
|
|
15147
15009
|
const hot = module.hot;
|
|
@@ -15164,7 +15026,20 @@ function CanvasImpl({
|
|
|
15164
15026
|
...style
|
|
15165
15027
|
},
|
|
15166
15028
|
...props,
|
|
15167
|
-
children:
|
|
15029
|
+
children: fallbackVisible ? (
|
|
15030
|
+
// Renderer setup failed: render the fallback as visible DOM. Inside <canvas> (below)
|
|
15031
|
+
// it exists in the tree but browsers never paint it, which is the whole bug (#3757).
|
|
15032
|
+
fallback
|
|
15033
|
+
) : /* @__PURE__ */ jsx("div", { ref: containerRef, className: "r3f-canvas-container", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
|
|
15034
|
+
"canvas",
|
|
15035
|
+
{
|
|
15036
|
+
ref: canvasRef,
|
|
15037
|
+
id,
|
|
15038
|
+
className: "r3f-canvas",
|
|
15039
|
+
style: { display: "block", width: "100%", height: "100%" },
|
|
15040
|
+
children: fallback
|
|
15041
|
+
}
|
|
15042
|
+
) })
|
|
15168
15043
|
}
|
|
15169
15044
|
);
|
|
15170
15045
|
}
|
|
@@ -15237,6 +15112,48 @@ let ScopedStore = _ScopedStore;
|
|
|
15237
15112
|
function createScopedStore(data) {
|
|
15238
15113
|
return new ScopedStore(data);
|
|
15239
15114
|
}
|
|
15115
|
+
function createLazyCreatorState(state) {
|
|
15116
|
+
let _uniforms = null;
|
|
15117
|
+
let _nodes = null;
|
|
15118
|
+
let _buffers = null;
|
|
15119
|
+
let _gpuStorage = null;
|
|
15120
|
+
return Object.create(state, {
|
|
15121
|
+
uniforms: {
|
|
15122
|
+
get() {
|
|
15123
|
+
return _uniforms ?? (_uniforms = createScopedStore(state.uniforms));
|
|
15124
|
+
}
|
|
15125
|
+
},
|
|
15126
|
+
nodes: {
|
|
15127
|
+
get() {
|
|
15128
|
+
return _nodes ?? (_nodes = createScopedStore(state.nodes));
|
|
15129
|
+
}
|
|
15130
|
+
},
|
|
15131
|
+
buffers: {
|
|
15132
|
+
get() {
|
|
15133
|
+
return _buffers ?? (_buffers = createScopedStore(state.buffers));
|
|
15134
|
+
}
|
|
15135
|
+
},
|
|
15136
|
+
gpuStorage: {
|
|
15137
|
+
get() {
|
|
15138
|
+
return _gpuStorage ?? (_gpuStorage = createScopedStore(state.gpuStorage));
|
|
15139
|
+
}
|
|
15140
|
+
}
|
|
15141
|
+
});
|
|
15142
|
+
}
|
|
15143
|
+
|
|
15144
|
+
function resolvePrimary(local) {
|
|
15145
|
+
return local.getState().primaryStore ?? local;
|
|
15146
|
+
}
|
|
15147
|
+
function usePrimaryStore() {
|
|
15148
|
+
const local = useStore();
|
|
15149
|
+
const primary = local.getState().primaryStore;
|
|
15150
|
+
return useMemo(() => primary ?? local, [primary, local]);
|
|
15151
|
+
}
|
|
15152
|
+
function usePrimaryThree(selector = (state) => state, equalityFn) {
|
|
15153
|
+
const local = useStore();
|
|
15154
|
+
const primary = resolvePrimary(local);
|
|
15155
|
+
return primary(selector, equalityFn);
|
|
15156
|
+
}
|
|
15240
15157
|
|
|
15241
15158
|
function addTexture(set, key, value) {
|
|
15242
15159
|
set((state) => {
|
|
@@ -15277,6 +15194,27 @@ function createTextureOperations(set) {
|
|
|
15277
15194
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15278
15195
|
};
|
|
15279
15196
|
}
|
|
15197
|
+
function extractTSLValue(value) {
|
|
15198
|
+
if (value === null || value === void 0) return value;
|
|
15199
|
+
if (typeof value !== "object") return value;
|
|
15200
|
+
const node = value;
|
|
15201
|
+
if (!node.isNode) return value;
|
|
15202
|
+
if (node.isConstNode) {
|
|
15203
|
+
return node.value;
|
|
15204
|
+
}
|
|
15205
|
+
if ("value" in node) {
|
|
15206
|
+
let extractedValue = node.value;
|
|
15207
|
+
if (typeof node.traverse === "function") {
|
|
15208
|
+
node.traverse((n) => {
|
|
15209
|
+
if (n.isConstNode) {
|
|
15210
|
+
extractedValue = n.value;
|
|
15211
|
+
}
|
|
15212
|
+
});
|
|
15213
|
+
}
|
|
15214
|
+
return extractedValue;
|
|
15215
|
+
}
|
|
15216
|
+
return value;
|
|
15217
|
+
}
|
|
15280
15218
|
function vectorize(inObject) {
|
|
15281
15219
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15282
15220
|
if (typeof inObject === "string") {
|
|
@@ -15289,9 +15227,16 @@ function vectorize(inObject) {
|
|
|
15289
15227
|
}
|
|
15290
15228
|
if (typeof inObject !== "object") return inObject;
|
|
15291
15229
|
const obj = inObject;
|
|
15230
|
+
if (obj.isNode) {
|
|
15231
|
+
return extractTSLValue(inObject);
|
|
15232
|
+
}
|
|
15292
15233
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15293
15234
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15294
15235
|
if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
|
|
15236
|
+
if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
|
|
15237
|
+
const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
|
|
15238
|
+
return new Color(obj.r * scale, obj.g * scale, obj.b * scale);
|
|
15239
|
+
}
|
|
15295
15240
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15296
15241
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15297
15242
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15315,7 +15260,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15315
15260
|
|
|
15316
15261
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15317
15262
|
function useUniforms(creatorOrScope, scope) {
|
|
15318
|
-
const store =
|
|
15263
|
+
const store = usePrimaryStore();
|
|
15319
15264
|
const removeUniforms2 = useCallback(
|
|
15320
15265
|
(names, targetScope) => {
|
|
15321
15266
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15354,17 +15299,14 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15354
15299
|
const rebuildUniforms = useCallback(
|
|
15355
15300
|
(targetScope) => {
|
|
15356
15301
|
store.setState((state) => {
|
|
15357
|
-
let newUniforms =
|
|
15302
|
+
let newUniforms = {};
|
|
15358
15303
|
if (targetScope && targetScope !== "root") {
|
|
15359
15304
|
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15360
15305
|
newUniforms = rest;
|
|
15361
15306
|
} else if (targetScope === "root") {
|
|
15362
|
-
newUniforms = {};
|
|
15363
15307
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15364
15308
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15365
15309
|
}
|
|
15366
|
-
} else {
|
|
15367
|
-
newUniforms = {};
|
|
15368
15310
|
}
|
|
15369
15311
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15370
15312
|
});
|
|
@@ -15372,20 +15314,26 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15372
15314
|
[store]
|
|
15373
15315
|
);
|
|
15374
15316
|
const inputForMemoization = useMemo(() => {
|
|
15317
|
+
let raw = creatorOrScope;
|
|
15375
15318
|
if (is.fun(creatorOrScope)) {
|
|
15376
|
-
const
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15319
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15320
|
+
raw = creatorOrScope(wrappedState);
|
|
15321
|
+
}
|
|
15322
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
15323
|
+
const normalized = {};
|
|
15324
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
15325
|
+
normalized[key] = vectorize(value);
|
|
15326
|
+
}
|
|
15327
|
+
return normalized;
|
|
15383
15328
|
}
|
|
15384
|
-
return
|
|
15329
|
+
return raw;
|
|
15385
15330
|
}, [creatorOrScope, store]);
|
|
15386
15331
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15387
15332
|
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
15388
|
-
const storeUniforms =
|
|
15333
|
+
const storeUniforms = usePrimaryThree((s) => s.uniforms);
|
|
15334
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15335
|
+
const readerDep = isReader ? storeUniforms : null;
|
|
15336
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15389
15337
|
const uniforms = useMemo(() => {
|
|
15390
15338
|
if (memoizedInput === void 0) {
|
|
15391
15339
|
return storeUniforms;
|
|
@@ -15440,28 +15388,20 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15440
15388
|
}
|
|
15441
15389
|
}
|
|
15442
15390
|
return result;
|
|
15443
|
-
}, [
|
|
15444
|
-
store,
|
|
15445
|
-
memoizedInput,
|
|
15446
|
-
scope,
|
|
15447
|
-
// Only include storeUniforms in deps for reader modes to enable reactivity
|
|
15448
|
-
isReader ? storeUniforms : null
|
|
15449
|
-
]);
|
|
15391
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15450
15392
|
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15451
15393
|
}
|
|
15452
15394
|
function rebuildAllUniforms(store, scope) {
|
|
15395
|
+
store = store.getState().primaryStore ?? store;
|
|
15453
15396
|
store.setState((state) => {
|
|
15454
|
-
let newUniforms =
|
|
15397
|
+
let newUniforms = {};
|
|
15455
15398
|
if (scope && scope !== "root") {
|
|
15456
15399
|
const { [scope]: _, ...rest } = state.uniforms;
|
|
15457
15400
|
newUniforms = rest;
|
|
15458
15401
|
} else if (scope === "root") {
|
|
15459
|
-
newUniforms = {};
|
|
15460
15402
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15461
15403
|
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15462
15404
|
}
|
|
15463
|
-
} else {
|
|
15464
|
-
newUniforms = {};
|
|
15465
15405
|
}
|
|
15466
15406
|
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15467
15407
|
});
|
|
@@ -15529,15 +15469,17 @@ function isSameThreeType(a, b) {
|
|
|
15529
15469
|
}
|
|
15530
15470
|
|
|
15531
15471
|
const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15472
|
+
const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
|
|
15532
15473
|
function useUniform(name, value) {
|
|
15533
15474
|
const store = useStore();
|
|
15475
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15534
15476
|
return useMemo(() => {
|
|
15535
15477
|
const state = store.getState();
|
|
15536
15478
|
const set = store.setState;
|
|
15537
15479
|
const existing = state.uniforms[name];
|
|
15538
15480
|
if (existing && isUniformNode(existing)) {
|
|
15539
|
-
if (value !== void 0) {
|
|
15540
|
-
existing.value = value;
|
|
15481
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15482
|
+
existing.value = typeof value === "string" ? new Color(value) : value;
|
|
15541
15483
|
}
|
|
15542
15484
|
return existing;
|
|
15543
15485
|
}
|
|
@@ -15546,7 +15488,24 @@ function useUniform(name, value) {
|
|
|
15546
15488
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15547
15489
|
);
|
|
15548
15490
|
}
|
|
15549
|
-
|
|
15491
|
+
if (isUniformNode(value)) {
|
|
15492
|
+
const node2 = value;
|
|
15493
|
+
if (typeof node2.setName === "function") {
|
|
15494
|
+
node2.setName(name);
|
|
15495
|
+
}
|
|
15496
|
+
set((s) => ({
|
|
15497
|
+
uniforms: { ...s.uniforms, [name]: node2 }
|
|
15498
|
+
}));
|
|
15499
|
+
return node2;
|
|
15500
|
+
}
|
|
15501
|
+
let node;
|
|
15502
|
+
if (isTSLNode$1(value)) {
|
|
15503
|
+
node = uniform(value);
|
|
15504
|
+
} else if (typeof value === "string") {
|
|
15505
|
+
node = uniform(new Color(value));
|
|
15506
|
+
} else {
|
|
15507
|
+
node = uniform(value);
|
|
15508
|
+
}
|
|
15550
15509
|
if (typeof node.setName === "function") {
|
|
15551
15510
|
node.setName(name);
|
|
15552
15511
|
}
|
|
@@ -15557,12 +15516,12 @@ function useUniform(name, value) {
|
|
|
15557
15516
|
}
|
|
15558
15517
|
}));
|
|
15559
15518
|
return node;
|
|
15560
|
-
}, [store, name]);
|
|
15519
|
+
}, [store, name, hmrVersion]);
|
|
15561
15520
|
}
|
|
15562
15521
|
|
|
15563
15522
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15564
15523
|
function useNodes(creatorOrScope, scope) {
|
|
15565
|
-
const store =
|
|
15524
|
+
const store = usePrimaryStore();
|
|
15566
15525
|
const removeNodes2 = useCallback(
|
|
15567
15526
|
(names, targetScope) => {
|
|
15568
15527
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15619,8 +15578,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15619
15578
|
[store]
|
|
15620
15579
|
);
|
|
15621
15580
|
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15622
|
-
const storeNodes =
|
|
15623
|
-
const hmrVersion =
|
|
15581
|
+
const storeNodes = usePrimaryThree((s) => s.nodes);
|
|
15582
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15583
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15584
|
+
const readerDep = isReader ? storeNodes : null;
|
|
15585
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15624
15586
|
const nodes = useMemo(() => {
|
|
15625
15587
|
if (creatorOrScope === void 0) {
|
|
15626
15588
|
return storeNodes;
|
|
@@ -15633,11 +15595,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15633
15595
|
const state = store.getState();
|
|
15634
15596
|
const set = store.setState;
|
|
15635
15597
|
const creator = creatorOrScope;
|
|
15636
|
-
const wrappedState =
|
|
15637
|
-
...state,
|
|
15638
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15639
|
-
nodes: createScopedStore(state.nodes)
|
|
15640
|
-
};
|
|
15598
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15641
15599
|
const created = creator(wrappedState);
|
|
15642
15600
|
const result = {};
|
|
15643
15601
|
let hasNewNodes = false;
|
|
@@ -15647,7 +15605,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15647
15605
|
if (currentScope[name]) {
|
|
15648
15606
|
result[name] = currentScope[name];
|
|
15649
15607
|
} else {
|
|
15650
|
-
|
|
15608
|
+
node.setName?.(`${scope}.${name}`);
|
|
15651
15609
|
result[name] = node;
|
|
15652
15610
|
hasNewNodes = true;
|
|
15653
15611
|
}
|
|
@@ -15667,7 +15625,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15667
15625
|
if (existing && isTSLNode(existing)) {
|
|
15668
15626
|
result[name] = existing;
|
|
15669
15627
|
} else {
|
|
15670
|
-
|
|
15628
|
+
node.setName?.(name);
|
|
15671
15629
|
result[name] = node;
|
|
15672
15630
|
hasNewNodes = true;
|
|
15673
15631
|
}
|
|
@@ -15676,18 +15634,11 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15676
15634
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15677
15635
|
}
|
|
15678
15636
|
return result;
|
|
15679
|
-
}, [
|
|
15680
|
-
store,
|
|
15681
|
-
typeof creatorOrScope === "string" ? creatorOrScope : scope,
|
|
15682
|
-
// Only include storeNodes in deps for reader modes to enable reactivity
|
|
15683
|
-
// Creator mode intentionally excludes it to avoid re-running creator on unrelated changes
|
|
15684
|
-
isReader ? storeNodes : null,
|
|
15685
|
-
// Include hmrVersion for creator modes to allow rebuildNodes() to bust the cache
|
|
15686
|
-
isReader ? null : hmrVersion
|
|
15687
|
-
]);
|
|
15637
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15688
15638
|
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15689
15639
|
}
|
|
15690
15640
|
function rebuildAllNodes(store, scope) {
|
|
15641
|
+
store = store.getState().primaryStore ?? store;
|
|
15691
15642
|
store.setState((state) => {
|
|
15692
15643
|
let newNodes = state.nodes;
|
|
15693
15644
|
if (scope && scope !== "root") {
|
|
@@ -15732,22 +15683,365 @@ function clearRootNodes(set) {
|
|
|
15732
15683
|
});
|
|
15733
15684
|
}
|
|
15734
15685
|
function useLocalNodes(creator) {
|
|
15735
|
-
const store =
|
|
15736
|
-
const uniforms =
|
|
15737
|
-
const nodes =
|
|
15738
|
-
const textures =
|
|
15686
|
+
const store = usePrimaryStore();
|
|
15687
|
+
const uniforms = usePrimaryThree((s) => s.uniforms);
|
|
15688
|
+
const nodes = usePrimaryThree((s) => s.nodes);
|
|
15689
|
+
const textures = usePrimaryThree((s) => s.textures);
|
|
15690
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15739
15691
|
return useMemo(() => {
|
|
15740
|
-
const
|
|
15741
|
-
const wrappedState = {
|
|
15742
|
-
...state,
|
|
15743
|
-
uniforms: createScopedStore(state.uniforms),
|
|
15744
|
-
nodes: createScopedStore(state.nodes)
|
|
15745
|
-
};
|
|
15692
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15746
15693
|
return creator(wrappedState);
|
|
15747
|
-
}, [store, creator, uniforms, nodes, textures]);
|
|
15694
|
+
}, [store, creator, uniforms, nodes, textures, hmrVersion]);
|
|
15695
|
+
}
|
|
15696
|
+
|
|
15697
|
+
const isBufferLike = (value) => {
|
|
15698
|
+
if (value === null || typeof value !== "object") return false;
|
|
15699
|
+
if (ArrayBuffer.isView(value)) return true;
|
|
15700
|
+
if ("isBufferAttribute" in value) return true;
|
|
15701
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15702
|
+
return false;
|
|
15703
|
+
};
|
|
15704
|
+
const disposeBuffer = (buffer) => {
|
|
15705
|
+
if (buffer === null || typeof buffer !== "object") return;
|
|
15706
|
+
if ("dispose" in buffer && typeof buffer.dispose === "function") {
|
|
15707
|
+
buffer.dispose();
|
|
15708
|
+
}
|
|
15709
|
+
};
|
|
15710
|
+
function useBuffers(creatorOrScope, scope) {
|
|
15711
|
+
const store = usePrimaryStore();
|
|
15712
|
+
const removeBuffers = useCallback(
|
|
15713
|
+
(names, targetScope) => {
|
|
15714
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15715
|
+
store.setState((state) => {
|
|
15716
|
+
if (targetScope) {
|
|
15717
|
+
const currentScope = { ...state.buffers[targetScope] };
|
|
15718
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15719
|
+
return { buffers: { ...state.buffers, [targetScope]: currentScope } };
|
|
15720
|
+
}
|
|
15721
|
+
const buffers2 = { ...state.buffers };
|
|
15722
|
+
for (const name of nameArray) if (isBufferLike(buffers2[name])) delete buffers2[name];
|
|
15723
|
+
return { buffers: buffers2 };
|
|
15724
|
+
});
|
|
15725
|
+
},
|
|
15726
|
+
[store]
|
|
15727
|
+
);
|
|
15728
|
+
const clearBuffers = useCallback(
|
|
15729
|
+
(targetScope) => {
|
|
15730
|
+
store.setState((state) => {
|
|
15731
|
+
if (targetScope && targetScope !== "root") {
|
|
15732
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15733
|
+
return { buffers: rest };
|
|
15734
|
+
}
|
|
15735
|
+
if (targetScope === "root") {
|
|
15736
|
+
const buffers2 = {};
|
|
15737
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15738
|
+
if (!isBufferLike(value)) buffers2[key] = value;
|
|
15739
|
+
}
|
|
15740
|
+
return { buffers: buffers2 };
|
|
15741
|
+
}
|
|
15742
|
+
return { buffers: {} };
|
|
15743
|
+
});
|
|
15744
|
+
},
|
|
15745
|
+
[store]
|
|
15746
|
+
);
|
|
15747
|
+
const rebuildBuffers = useCallback(
|
|
15748
|
+
(targetScope) => {
|
|
15749
|
+
store.setState((state) => {
|
|
15750
|
+
let newBuffers = state.buffers;
|
|
15751
|
+
if (targetScope && targetScope !== "root") {
|
|
15752
|
+
const { [targetScope]: _, ...rest } = state.buffers;
|
|
15753
|
+
newBuffers = rest;
|
|
15754
|
+
} else if (targetScope === "root") {
|
|
15755
|
+
newBuffers = {};
|
|
15756
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15757
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15758
|
+
}
|
|
15759
|
+
} else {
|
|
15760
|
+
newBuffers = {};
|
|
15761
|
+
}
|
|
15762
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15763
|
+
});
|
|
15764
|
+
},
|
|
15765
|
+
[store]
|
|
15766
|
+
);
|
|
15767
|
+
const disposeBuffers = useCallback(
|
|
15768
|
+
(names, targetScope) => {
|
|
15769
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15770
|
+
const state = store.getState();
|
|
15771
|
+
for (const name of nameArray) {
|
|
15772
|
+
const buffer = targetScope ? state.buffers[targetScope]?.[name] : state.buffers[name];
|
|
15773
|
+
if (buffer && isBufferLike(buffer)) {
|
|
15774
|
+
disposeBuffer(buffer);
|
|
15775
|
+
}
|
|
15776
|
+
}
|
|
15777
|
+
removeBuffers(names, targetScope);
|
|
15778
|
+
},
|
|
15779
|
+
[store, removeBuffers]
|
|
15780
|
+
);
|
|
15781
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15782
|
+
const storeBuffers = usePrimaryThree((s) => s.buffers);
|
|
15783
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15784
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15785
|
+
const readerDep = isReader ? storeBuffers : null;
|
|
15786
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15787
|
+
const buffers = useMemo(() => {
|
|
15788
|
+
if (creatorOrScope === void 0) {
|
|
15789
|
+
return storeBuffers;
|
|
15790
|
+
}
|
|
15791
|
+
if (typeof creatorOrScope === "string") {
|
|
15792
|
+
const scopeData = storeBuffers[creatorOrScope];
|
|
15793
|
+
if (scopeData && !isBufferLike(scopeData)) return scopeData;
|
|
15794
|
+
return {};
|
|
15795
|
+
}
|
|
15796
|
+
const state = store.getState();
|
|
15797
|
+
const set = store.setState;
|
|
15798
|
+
const creator = creatorOrScope;
|
|
15799
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15800
|
+
const created = creator(wrappedState);
|
|
15801
|
+
const result = {};
|
|
15802
|
+
let hasNewBuffers = false;
|
|
15803
|
+
if (scope) {
|
|
15804
|
+
const currentScope = state.buffers[scope] ?? {};
|
|
15805
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15806
|
+
if (currentScope[name]) {
|
|
15807
|
+
result[name] = currentScope[name];
|
|
15808
|
+
} else {
|
|
15809
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15810
|
+
buffer.setName(`${scope}.${name}`);
|
|
15811
|
+
}
|
|
15812
|
+
result[name] = buffer;
|
|
15813
|
+
hasNewBuffers = true;
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
if (hasNewBuffers) {
|
|
15817
|
+
set((s) => ({
|
|
15818
|
+
buffers: {
|
|
15819
|
+
...s.buffers,
|
|
15820
|
+
[scope]: { ...s.buffers[scope], ...result }
|
|
15821
|
+
}
|
|
15822
|
+
}));
|
|
15823
|
+
}
|
|
15824
|
+
return result;
|
|
15825
|
+
}
|
|
15826
|
+
for (const [name, buffer] of Object.entries(created)) {
|
|
15827
|
+
const existing = state.buffers[name];
|
|
15828
|
+
if (existing && isBufferLike(existing)) {
|
|
15829
|
+
result[name] = existing;
|
|
15830
|
+
} else {
|
|
15831
|
+
if ("setName" in buffer && typeof buffer.setName === "function") {
|
|
15832
|
+
buffer.setName(name);
|
|
15833
|
+
}
|
|
15834
|
+
result[name] = buffer;
|
|
15835
|
+
hasNewBuffers = true;
|
|
15836
|
+
}
|
|
15837
|
+
}
|
|
15838
|
+
if (hasNewBuffers) {
|
|
15839
|
+
set((s) => ({ buffers: { ...s.buffers, ...result } }));
|
|
15840
|
+
}
|
|
15841
|
+
return result;
|
|
15842
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15843
|
+
return { ...buffers, removeBuffers, clearBuffers, rebuildBuffers, disposeBuffers };
|
|
15844
|
+
}
|
|
15845
|
+
function rebuildAllBuffers(store, scope) {
|
|
15846
|
+
store = store.getState().primaryStore ?? store;
|
|
15847
|
+
store.setState((state) => {
|
|
15848
|
+
let newBuffers = state.buffers;
|
|
15849
|
+
if (scope && scope !== "root") {
|
|
15850
|
+
const { [scope]: _, ...rest } = state.buffers;
|
|
15851
|
+
newBuffers = rest;
|
|
15852
|
+
} else if (scope === "root") {
|
|
15853
|
+
newBuffers = {};
|
|
15854
|
+
for (const [key, value] of Object.entries(state.buffers)) {
|
|
15855
|
+
if (!isBufferLike(value)) newBuffers[key] = value;
|
|
15856
|
+
}
|
|
15857
|
+
} else {
|
|
15858
|
+
newBuffers = {};
|
|
15859
|
+
}
|
|
15860
|
+
return { buffers: newBuffers, _hmrVersion: state._hmrVersion + 1 };
|
|
15861
|
+
});
|
|
15862
|
+
}
|
|
15863
|
+
|
|
15864
|
+
const isStorageLike = (value) => {
|
|
15865
|
+
if (value === null || typeof value !== "object") return false;
|
|
15866
|
+
if ("isTexture" in value) return true;
|
|
15867
|
+
if ("isData3DTexture" in value) return true;
|
|
15868
|
+
if ("uuid" in value || "nodeType" in value) return true;
|
|
15869
|
+
return false;
|
|
15870
|
+
};
|
|
15871
|
+
const disposeStorage = (storage) => {
|
|
15872
|
+
if (storage === null || typeof storage !== "object") return;
|
|
15873
|
+
if ("dispose" in storage && typeof storage.dispose === "function") {
|
|
15874
|
+
storage.dispose();
|
|
15875
|
+
}
|
|
15876
|
+
};
|
|
15877
|
+
function useGPUStorage(creatorOrScope, scope) {
|
|
15878
|
+
const store = usePrimaryStore();
|
|
15879
|
+
const removeStorage = useCallback(
|
|
15880
|
+
(names, targetScope) => {
|
|
15881
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15882
|
+
store.setState((state) => {
|
|
15883
|
+
if (targetScope) {
|
|
15884
|
+
const currentScope = { ...state.gpuStorage[targetScope] };
|
|
15885
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15886
|
+
return { gpuStorage: { ...state.gpuStorage, [targetScope]: currentScope } };
|
|
15887
|
+
}
|
|
15888
|
+
const gpuStorage2 = { ...state.gpuStorage };
|
|
15889
|
+
for (const name of nameArray) if (isStorageLike(gpuStorage2[name])) delete gpuStorage2[name];
|
|
15890
|
+
return { gpuStorage: gpuStorage2 };
|
|
15891
|
+
});
|
|
15892
|
+
},
|
|
15893
|
+
[store]
|
|
15894
|
+
);
|
|
15895
|
+
const clearStorage = useCallback(
|
|
15896
|
+
(targetScope) => {
|
|
15897
|
+
store.setState((state) => {
|
|
15898
|
+
if (targetScope && targetScope !== "root") {
|
|
15899
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15900
|
+
return { gpuStorage: rest };
|
|
15901
|
+
}
|
|
15902
|
+
if (targetScope === "root") {
|
|
15903
|
+
const gpuStorage2 = {};
|
|
15904
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15905
|
+
if (!isStorageLike(value)) gpuStorage2[key] = value;
|
|
15906
|
+
}
|
|
15907
|
+
return { gpuStorage: gpuStorage2 };
|
|
15908
|
+
}
|
|
15909
|
+
return { gpuStorage: {} };
|
|
15910
|
+
});
|
|
15911
|
+
},
|
|
15912
|
+
[store]
|
|
15913
|
+
);
|
|
15914
|
+
const rebuildStorage = useCallback(
|
|
15915
|
+
(targetScope) => {
|
|
15916
|
+
store.setState((state) => {
|
|
15917
|
+
let newStorage = state.gpuStorage;
|
|
15918
|
+
if (targetScope && targetScope !== "root") {
|
|
15919
|
+
const { [targetScope]: _, ...rest } = state.gpuStorage;
|
|
15920
|
+
newStorage = rest;
|
|
15921
|
+
} else if (targetScope === "root") {
|
|
15922
|
+
newStorage = {};
|
|
15923
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
15924
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
15925
|
+
}
|
|
15926
|
+
} else {
|
|
15927
|
+
newStorage = {};
|
|
15928
|
+
}
|
|
15929
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
15930
|
+
});
|
|
15931
|
+
},
|
|
15932
|
+
[store]
|
|
15933
|
+
);
|
|
15934
|
+
const disposeStorageFn = useCallback(
|
|
15935
|
+
(names, targetScope) => {
|
|
15936
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15937
|
+
const state = store.getState();
|
|
15938
|
+
for (const name of nameArray) {
|
|
15939
|
+
const storage = targetScope ? state.gpuStorage[targetScope]?.[name] : state.gpuStorage[name];
|
|
15940
|
+
if (storage && isStorageLike(storage)) {
|
|
15941
|
+
disposeStorage(storage);
|
|
15942
|
+
}
|
|
15943
|
+
}
|
|
15944
|
+
removeStorage(names, targetScope);
|
|
15945
|
+
},
|
|
15946
|
+
[store, removeStorage]
|
|
15947
|
+
);
|
|
15948
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
15949
|
+
const storeStorage = usePrimaryThree((s) => s.gpuStorage);
|
|
15950
|
+
const hmrVersion = usePrimaryThree((s) => s._hmrVersion);
|
|
15951
|
+
const scopeDep = typeof creatorOrScope === "string" ? creatorOrScope : scope;
|
|
15952
|
+
const readerDep = isReader ? storeStorage : null;
|
|
15953
|
+
const creatorDep = isReader ? null : hmrVersion;
|
|
15954
|
+
const gpuStorage = useMemo(() => {
|
|
15955
|
+
if (creatorOrScope === void 0) {
|
|
15956
|
+
return storeStorage;
|
|
15957
|
+
}
|
|
15958
|
+
if (typeof creatorOrScope === "string") {
|
|
15959
|
+
const scopeData = storeStorage[creatorOrScope];
|
|
15960
|
+
if (scopeData && !isStorageLike(scopeData)) return scopeData;
|
|
15961
|
+
return {};
|
|
15962
|
+
}
|
|
15963
|
+
const state = store.getState();
|
|
15964
|
+
const set = store.setState;
|
|
15965
|
+
const creator = creatorOrScope;
|
|
15966
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15967
|
+
const created = creator(wrappedState);
|
|
15968
|
+
const result = {};
|
|
15969
|
+
let hasNewStorage = false;
|
|
15970
|
+
if (scope) {
|
|
15971
|
+
const currentScope = state.gpuStorage[scope] ?? {};
|
|
15972
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15973
|
+
if (currentScope[name]) {
|
|
15974
|
+
result[name] = currentScope[name];
|
|
15975
|
+
} else {
|
|
15976
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
15977
|
+
storage.setName(`${scope}.${name}`);
|
|
15978
|
+
}
|
|
15979
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
15980
|
+
storage.name = `${scope}.${name}`;
|
|
15981
|
+
}
|
|
15982
|
+
result[name] = storage;
|
|
15983
|
+
hasNewStorage = true;
|
|
15984
|
+
}
|
|
15985
|
+
}
|
|
15986
|
+
if (hasNewStorage) {
|
|
15987
|
+
set((s) => ({
|
|
15988
|
+
gpuStorage: {
|
|
15989
|
+
...s.gpuStorage,
|
|
15990
|
+
[scope]: { ...s.gpuStorage[scope], ...result }
|
|
15991
|
+
}
|
|
15992
|
+
}));
|
|
15993
|
+
}
|
|
15994
|
+
return result;
|
|
15995
|
+
}
|
|
15996
|
+
for (const [name, storage] of Object.entries(created)) {
|
|
15997
|
+
const existing = state.gpuStorage[name];
|
|
15998
|
+
if (existing && isStorageLike(existing)) {
|
|
15999
|
+
result[name] = existing;
|
|
16000
|
+
} else {
|
|
16001
|
+
if ("setName" in storage && typeof storage.setName === "function") {
|
|
16002
|
+
storage.setName(name);
|
|
16003
|
+
}
|
|
16004
|
+
if ("name" in storage && typeof storage.name === "string") {
|
|
16005
|
+
storage.name = name;
|
|
16006
|
+
}
|
|
16007
|
+
result[name] = storage;
|
|
16008
|
+
hasNewStorage = true;
|
|
16009
|
+
}
|
|
16010
|
+
}
|
|
16011
|
+
if (hasNewStorage) {
|
|
16012
|
+
set((s) => ({ gpuStorage: { ...s.gpuStorage, ...result } }));
|
|
16013
|
+
}
|
|
16014
|
+
return result;
|
|
16015
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
16016
|
+
return {
|
|
16017
|
+
...gpuStorage,
|
|
16018
|
+
removeStorage,
|
|
16019
|
+
clearStorage,
|
|
16020
|
+
rebuildStorage,
|
|
16021
|
+
disposeStorage: disposeStorageFn
|
|
16022
|
+
};
|
|
16023
|
+
}
|
|
16024
|
+
function rebuildAllStorage(store, scope) {
|
|
16025
|
+
store = store.getState().primaryStore ?? store;
|
|
16026
|
+
store.setState((state) => {
|
|
16027
|
+
let newStorage = state.gpuStorage;
|
|
16028
|
+
if (scope && scope !== "root") {
|
|
16029
|
+
const { [scope]: _, ...rest } = state.gpuStorage;
|
|
16030
|
+
newStorage = rest;
|
|
16031
|
+
} else if (scope === "root") {
|
|
16032
|
+
newStorage = {};
|
|
16033
|
+
for (const [key, value] of Object.entries(state.gpuStorage)) {
|
|
16034
|
+
if (!isStorageLike(value)) newStorage[key] = value;
|
|
16035
|
+
}
|
|
16036
|
+
} else {
|
|
16037
|
+
newStorage = {};
|
|
16038
|
+
}
|
|
16039
|
+
return { gpuStorage: newStorage, _hmrVersion: state._hmrVersion + 1 };
|
|
16040
|
+
});
|
|
15748
16041
|
}
|
|
15749
16042
|
|
|
15750
|
-
|
|
16043
|
+
const RenderPipelineCtor = RenderPipeline ?? PostProcessing;
|
|
16044
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15751
16045
|
const store = useStore();
|
|
15752
16046
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15753
16047
|
const callbacksRanRef = useRef(false);
|
|
@@ -15766,7 +16060,7 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15766
16060
|
}, [store]);
|
|
15767
16061
|
const reset = useCallback(() => {
|
|
15768
16062
|
store.setState({
|
|
15769
|
-
|
|
16063
|
+
renderPipeline: null,
|
|
15770
16064
|
passes: {}
|
|
15771
16065
|
});
|
|
15772
16066
|
callbacksRanRef.current = false;
|
|
@@ -15779,18 +16073,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15779
16073
|
}, []);
|
|
15780
16074
|
useLayoutEffect(() => {
|
|
15781
16075
|
if (isLegacy) {
|
|
15782
|
-
throw new Error("
|
|
16076
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15783
16077
|
}
|
|
15784
16078
|
if (!renderer || !scene || !camera) return;
|
|
15785
16079
|
const state = store.getState();
|
|
15786
16080
|
const set = store.setState;
|
|
15787
16081
|
try {
|
|
15788
|
-
let
|
|
16082
|
+
let pipeline = state.renderPipeline;
|
|
15789
16083
|
let currentPasses = { ...state.passes };
|
|
15790
|
-
let
|
|
15791
|
-
if (!
|
|
15792
|
-
|
|
15793
|
-
|
|
16084
|
+
let justCreatedPipeline = false;
|
|
16085
|
+
if (!pipeline) {
|
|
16086
|
+
pipeline = new RenderPipelineCtor(renderer);
|
|
16087
|
+
justCreatedPipeline = true;
|
|
15794
16088
|
}
|
|
15795
16089
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
15796
16090
|
let scenePass;
|
|
@@ -15801,9 +16095,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15801
16095
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
15802
16096
|
}
|
|
15803
16097
|
currentPasses.scenePass = scenePass;
|
|
15804
|
-
if (!
|
|
15805
|
-
set({
|
|
15806
|
-
const shouldRunCallbacks =
|
|
16098
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
16099
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
16100
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
15807
16101
|
if (shouldRunCallbacks) {
|
|
15808
16102
|
if (setupCBRef.current) {
|
|
15809
16103
|
const freshState = store.getState();
|
|
@@ -15824,22 +16118,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15824
16118
|
callbacksRanRef.current = true;
|
|
15825
16119
|
}
|
|
15826
16120
|
} catch (error) {
|
|
15827
|
-
console.error("[
|
|
16121
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15828
16122
|
}
|
|
15829
16123
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15830
16124
|
const passes = useThree((s) => s.passes);
|
|
15831
|
-
const
|
|
16125
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15832
16126
|
return {
|
|
15833
16127
|
passes,
|
|
15834
|
-
|
|
16128
|
+
renderPipeline,
|
|
15835
16129
|
clearPasses,
|
|
15836
16130
|
reset,
|
|
15837
16131
|
rebuild,
|
|
15838
|
-
// isReady indicates if
|
|
15839
|
-
isReady:
|
|
16132
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16133
|
+
isReady: renderPipeline !== null
|
|
15840
16134
|
};
|
|
15841
16135
|
}
|
|
15842
16136
|
|
|
15843
16137
|
extend(THREE);
|
|
15844
16138
|
|
|
15845
|
-
export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS,
|
|
16139
|
+
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 };
|