@react-three/fiber 10.0.0-alpha.1 → 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 +1364 -1446
- package/dist/index.d.cts +1888 -1702
- package/dist/index.d.mts +1888 -1702
- package/dist/index.d.ts +1888 -1702
- package/dist/index.mjs +1324 -1425
- package/dist/legacy.cjs +1321 -1428
- package/dist/legacy.d.cts +1889 -1703
- package/dist/legacy.d.mts +1889 -1703
- package/dist/legacy.d.ts +1889 -1703
- package/dist/legacy.mjs +1281 -1407
- package/dist/webgpu/index.cjs +2046 -1497
- package/dist/webgpu/index.d.cts +2163 -1725
- package/dist/webgpu/index.d.mts +2163 -1725
- package/dist/webgpu/index.d.ts +2163 -1725
- package/dist/webgpu/index.mjs +1999 -1476
- package/package.json +8 -5
- package/react-reconciler/constants.js +1 -9
- package/react-reconciler/index.js +4 -20
- package/readme.md +244 -318
package/dist/webgpu/index.mjs
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as webgpu from 'three/webgpu';
|
|
2
|
-
import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1,
|
|
2
|
+
import { RenderTarget, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Scene, WebGLCubeRenderTarget, HalfFloatType, Color, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, 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
|
}
|
|
@@ -538,7 +956,7 @@ function applyProps(object, props) {
|
|
|
538
956
|
const rootState = instance && findInitialRoot(instance).getState();
|
|
539
957
|
const prevHandlers = instance?.eventCount;
|
|
540
958
|
for (const prop in props) {
|
|
541
|
-
|
|
959
|
+
const value = props[prop];
|
|
542
960
|
if (RESERVED_PROPS.includes(prop)) continue;
|
|
543
961
|
if (instance && EVENT_REGEX.test(prop)) {
|
|
544
962
|
if (typeof value === "function") instance.handlers[prop] = value;
|
|
@@ -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;
|
|
@@ -690,14 +1176,19 @@ function createEvents(store) {
|
|
|
690
1176
|
for (const hit of hits) {
|
|
691
1177
|
let eventObject = hit.object;
|
|
692
1178
|
while (eventObject) {
|
|
693
|
-
if (eventObject.__r3f?.eventCount)
|
|
1179
|
+
if (eventObject.__r3f?.eventCount) {
|
|
694
1180
|
intersections.push({ ...hit, eventObject });
|
|
1181
|
+
}
|
|
695
1182
|
eventObject = eventObject.parent;
|
|
696
1183
|
}
|
|
697
1184
|
}
|
|
698
|
-
if ("pointerId" in event
|
|
699
|
-
|
|
700
|
-
|
|
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
|
+
}
|
|
701
1192
|
}
|
|
702
1193
|
}
|
|
703
1194
|
return intersections;
|
|
@@ -710,28 +1201,26 @@ function createEvents(store) {
|
|
|
710
1201
|
if (state) {
|
|
711
1202
|
const { raycaster, pointer, camera, internal } = state;
|
|
712
1203
|
const unprojectedPoint = new Vector3(pointer.x, pointer.y, 0).unproject(camera);
|
|
713
|
-
const hasPointerCapture = (id) =>
|
|
1204
|
+
const hasPointerCapture = (id) => {
|
|
1205
|
+
const pointerState = internal.pointerMap.get(id);
|
|
1206
|
+
return pointerState?.captured.has(hit.eventObject) ?? false;
|
|
1207
|
+
};
|
|
714
1208
|
const setPointerCapture = (id) => {
|
|
715
1209
|
const captureData = { intersection: hit, target: event.target };
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
} else {
|
|
719
|
-
internal.capturedMap.set(id, /* @__PURE__ */ new Map([[hit.eventObject, captureData]]));
|
|
720
|
-
}
|
|
1210
|
+
const pointerState = getPointerState(internal, id);
|
|
1211
|
+
pointerState.captured.set(hit.eventObject, captureData);
|
|
721
1212
|
event.target.setPointerCapture(id);
|
|
722
1213
|
};
|
|
723
1214
|
const releasePointerCapture = (id) => {
|
|
724
|
-
|
|
725
|
-
if (captures) {
|
|
726
|
-
releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
|
|
727
|
-
}
|
|
1215
|
+
releaseInternalPointerCapture(internal, hit.eventObject, id);
|
|
728
1216
|
};
|
|
729
|
-
|
|
730
|
-
for (
|
|
731
|
-
|
|
1217
|
+
const extractEventProps = {};
|
|
1218
|
+
for (const prop in event) {
|
|
1219
|
+
const property = event[prop];
|
|
732
1220
|
if (typeof property !== "function") extractEventProps[prop] = property;
|
|
733
1221
|
}
|
|
734
|
-
|
|
1222
|
+
const eventPointerId = "pointerId" in event ? event.pointerId : void 0;
|
|
1223
|
+
const raycastEvent = {
|
|
735
1224
|
...hit,
|
|
736
1225
|
...extractEventProps,
|
|
737
1226
|
pointer,
|
|
@@ -741,18 +1230,19 @@ function createEvents(store) {
|
|
|
741
1230
|
unprojectedPoint,
|
|
742
1231
|
ray: raycaster.ray,
|
|
743
1232
|
camera,
|
|
1233
|
+
pointerId: eventPointerId,
|
|
744
1234
|
// Hijack stopPropagation, which just sets a flag
|
|
745
1235
|
stopPropagation() {
|
|
746
|
-
const
|
|
1236
|
+
const pointerState = eventPointerId !== void 0 ? internal.pointerMap.get(eventPointerId) : void 0;
|
|
747
1237
|
if (
|
|
748
1238
|
// ...if this pointer hasn't been captured
|
|
749
|
-
!
|
|
750
|
-
|
|
1239
|
+
!pointerState?.captured.size || // ... or if the hit object is capturing the pointer
|
|
1240
|
+
pointerState.captured.has(hit.eventObject)
|
|
751
1241
|
) {
|
|
752
1242
|
raycastEvent.stopped = localState.stopped = true;
|
|
753
|
-
if (
|
|
1243
|
+
if (pointerState?.hovered.size && Array.from(pointerState.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
|
|
754
1244
|
const higher = intersections.slice(0, intersections.indexOf(hit));
|
|
755
|
-
cancelPointer([...higher, hit]);
|
|
1245
|
+
cancelPointer([...higher, hit], eventPointerId);
|
|
756
1246
|
}
|
|
757
1247
|
}
|
|
758
1248
|
},
|
|
@@ -768,15 +1258,18 @@ function createEvents(store) {
|
|
|
768
1258
|
}
|
|
769
1259
|
return intersections;
|
|
770
1260
|
}
|
|
771
|
-
function cancelPointer(intersections) {
|
|
1261
|
+
function cancelPointer(intersections, pointerId) {
|
|
772
1262
|
const { internal } = store.getState();
|
|
773
|
-
|
|
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) {
|
|
774
1267
|
if (!intersections.length || !intersections.find(
|
|
775
1268
|
(hit) => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId
|
|
776
1269
|
)) {
|
|
777
1270
|
const eventObject = hoveredObj.eventObject;
|
|
778
1271
|
const instance = eventObject.__r3f;
|
|
779
|
-
|
|
1272
|
+
pointerState.hovered.delete(hoveredId);
|
|
780
1273
|
if (instance?.eventCount) {
|
|
781
1274
|
const handlers = instance.handlers;
|
|
782
1275
|
const data = { ...hoveredObj, intersections };
|
|
@@ -805,41 +1298,118 @@ function createEvents(store) {
|
|
|
805
1298
|
instance?.handlers.onDropMissed?.(event);
|
|
806
1299
|
}
|
|
807
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
|
+
}
|
|
808
1347
|
function handlePointer(name) {
|
|
809
1348
|
switch (name) {
|
|
810
1349
|
case "onPointerLeave":
|
|
811
|
-
case "onPointerCancel":
|
|
812
1350
|
case "onDragLeave":
|
|
813
1351
|
return () => cancelPointer([]);
|
|
1352
|
+
// Global cancel of these events
|
|
1353
|
+
case "onPointerCancel":
|
|
1354
|
+
return (event) => {
|
|
1355
|
+
const pointerId = getPointerId(event);
|
|
1356
|
+
cleanupPointer(pointerId);
|
|
1357
|
+
};
|
|
814
1358
|
case "onLostPointerCapture":
|
|
815
1359
|
return (event) => {
|
|
816
1360
|
const { internal } = store.getState();
|
|
817
|
-
|
|
1361
|
+
const pointerId = getPointerId(event);
|
|
1362
|
+
const pointerState = internal.pointerMap.get(pointerId);
|
|
1363
|
+
if (pointerState?.captured.size) {
|
|
818
1364
|
requestAnimationFrame(() => {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1365
|
+
const pointerState2 = internal.pointerMap.get(pointerId);
|
|
1366
|
+
if (pointerState2?.captured.size) {
|
|
1367
|
+
pointerState2.captured.clear();
|
|
822
1368
|
}
|
|
1369
|
+
cancelPointer([], pointerId);
|
|
823
1370
|
});
|
|
824
1371
|
}
|
|
825
1372
|
};
|
|
826
1373
|
}
|
|
827
1374
|
return function handleEvent(event) {
|
|
828
1375
|
const state = store.getState();
|
|
829
|
-
const { onPointerMissed, onDragOverMissed, onDropMissed, internal } = state;
|
|
1376
|
+
const { onPointerMissed, onDragOverMissed, onDropMissed, internal, events } = state;
|
|
1377
|
+
const pointerId = getPointerId(event);
|
|
830
1378
|
internal.lastEvent.current = event;
|
|
831
|
-
if (!
|
|
1379
|
+
if (!events.enabled) return;
|
|
832
1380
|
const isPointerMove = name === "onPointerMove";
|
|
833
1381
|
const isDragOver = name === "onDragOver";
|
|
834
1382
|
const isDrop = name === "onDrop";
|
|
835
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
|
+
}
|
|
836
1403
|
const filter = isPointerMove || isDragOver || isDrop ? filterPointerEvents : void 0;
|
|
837
1404
|
const hits = intersect(event, filter);
|
|
838
|
-
const delta = isClickEvent ? calculateDistance(event) : 0;
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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 ?? [];
|
|
843
1413
|
if (isClickEvent && !hits.length) {
|
|
844
1414
|
if (delta <= 2) {
|
|
845
1415
|
pointerMissed(event, internal.interaction);
|
|
@@ -854,7 +1424,9 @@ function createEvents(store) {
|
|
|
854
1424
|
dropMissed(event, internal.interaction);
|
|
855
1425
|
if (onDropMissed) onDropMissed(event);
|
|
856
1426
|
}
|
|
857
|
-
if (isPointerMove || isDragOver)
|
|
1427
|
+
if (isPointerMove || isDragOver) {
|
|
1428
|
+
cancelPointer(hits, pointerId);
|
|
1429
|
+
}
|
|
858
1430
|
function onIntersect(data) {
|
|
859
1431
|
const eventObject = data.eventObject;
|
|
860
1432
|
const instance = eventObject.__r3f;
|
|
@@ -863,9 +1435,10 @@ function createEvents(store) {
|
|
|
863
1435
|
if (isPointerMove) {
|
|
864
1436
|
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
|
|
865
1437
|
const id = makeId(data);
|
|
866
|
-
const
|
|
1438
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1439
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
867
1440
|
if (!hoveredItem) {
|
|
868
|
-
|
|
1441
|
+
pointerState2.hovered.set(id, data);
|
|
869
1442
|
handlers.onPointerOver?.(data);
|
|
870
1443
|
handlers.onPointerEnter?.(data);
|
|
871
1444
|
} else if (hoveredItem.stopped) {
|
|
@@ -875,9 +1448,10 @@ function createEvents(store) {
|
|
|
875
1448
|
handlers.onPointerMove?.(data);
|
|
876
1449
|
} else if (isDragOver) {
|
|
877
1450
|
const id = makeId(data);
|
|
878
|
-
const
|
|
1451
|
+
const pointerState2 = getPointerState(internal, pointerId);
|
|
1452
|
+
const hoveredItem = pointerState2.hovered.get(id);
|
|
879
1453
|
if (!hoveredItem) {
|
|
880
|
-
|
|
1454
|
+
pointerState2.hovered.set(id, data);
|
|
881
1455
|
handlers.onDragOverEnter?.(data);
|
|
882
1456
|
} else if (hoveredItem.stopped) {
|
|
883
1457
|
data.stopPropagation();
|
|
@@ -888,18 +1462,18 @@ function createEvents(store) {
|
|
|
888
1462
|
} else {
|
|
889
1463
|
const handler = handlers[name];
|
|
890
1464
|
if (handler) {
|
|
891
|
-
if (!isClickEvent ||
|
|
1465
|
+
if (!isClickEvent || initialHits.includes(eventObject)) {
|
|
892
1466
|
pointerMissed(
|
|
893
1467
|
event,
|
|
894
|
-
internal.interaction.filter((object) => !
|
|
1468
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
895
1469
|
);
|
|
896
1470
|
handler(data);
|
|
897
1471
|
}
|
|
898
1472
|
} else {
|
|
899
|
-
if (isClickEvent &&
|
|
1473
|
+
if (isClickEvent && initialHits.includes(eventObject)) {
|
|
900
1474
|
pointerMissed(
|
|
901
1475
|
event,
|
|
902
|
-
internal.interaction.filter((object) => !
|
|
1476
|
+
internal.interaction.filter((object) => !initialHits.includes(object))
|
|
903
1477
|
);
|
|
904
1478
|
}
|
|
905
1479
|
}
|
|
@@ -908,7 +1482,15 @@ function createEvents(store) {
|
|
|
908
1482
|
handleIntersects(hits, event, delta, onIntersect);
|
|
909
1483
|
};
|
|
910
1484
|
}
|
|
911
|
-
|
|
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 };
|
|
912
1494
|
}
|
|
913
1495
|
const DOM_EVENTS = {
|
|
914
1496
|
onClick: ["click", false],
|
|
@@ -927,11 +1509,16 @@ const DOM_EVENTS = {
|
|
|
927
1509
|
onLostPointerCapture: ["lostpointercapture", true]
|
|
928
1510
|
};
|
|
929
1511
|
function createPointerEvents(store) {
|
|
930
|
-
const { handlePointer } = createEvents(store);
|
|
1512
|
+
const { handlePointer, flushDeferredPointers, processDeferredPointer } = createEvents(store);
|
|
1513
|
+
let nextXRPointerId = XR_POINTER_ID_START;
|
|
1514
|
+
const xrPointers = /* @__PURE__ */ new Map();
|
|
931
1515
|
return {
|
|
932
1516
|
priority: 1,
|
|
933
1517
|
enabled: true,
|
|
934
|
-
|
|
1518
|
+
frameTimedRaycasts: true,
|
|
1519
|
+
alwaysFireOnScroll: true,
|
|
1520
|
+
updateOnFrame: false,
|
|
1521
|
+
compute(event, state) {
|
|
935
1522
|
state.pointer.set(event.offsetX / state.size.width * 2 - 1, -(event.offsetY / state.size.height) * 2 + 1);
|
|
936
1523
|
state.raycaster.setFromCamera(state.pointer, state.camera);
|
|
937
1524
|
},
|
|
@@ -940,11 +1527,33 @@ function createPointerEvents(store) {
|
|
|
940
1527
|
(acc, key) => ({ ...acc, [key]: handlePointer(key) }),
|
|
941
1528
|
{}
|
|
942
1529
|
),
|
|
943
|
-
update: () => {
|
|
1530
|
+
update: (pointerId) => {
|
|
1531
|
+
const { events, internal } = store.getState();
|
|
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: () => {
|
|
944
1549
|
const { events, internal } = store.getState();
|
|
945
|
-
|
|
1550
|
+
flushDeferredPointers();
|
|
1551
|
+
if (events.updateOnFrame && internal.lastEvent?.current && events.handlers) {
|
|
1552
|
+
events.handlers.onPointerMove(internal.lastEvent.current);
|
|
1553
|
+
}
|
|
946
1554
|
},
|
|
947
1555
|
connect: (target) => {
|
|
1556
|
+
if (!target) return;
|
|
948
1557
|
const { set, events } = store.getState();
|
|
949
1558
|
events.disconnect?.();
|
|
950
1559
|
set((state) => ({ events: { ...state.events, connected: target } }));
|
|
@@ -968,6 +1577,33 @@ function createPointerEvents(store) {
|
|
|
968
1577
|
}
|
|
969
1578
|
set((state) => ({ events: { ...state.events, connected: void 0 } }));
|
|
970
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);
|
|
971
1607
|
}
|
|
972
1608
|
};
|
|
973
1609
|
}
|
|
@@ -1029,6 +1665,14 @@ function notifyAlpha({ message, link }) {
|
|
|
1029
1665
|
}
|
|
1030
1666
|
}
|
|
1031
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
|
+
|
|
1032
1676
|
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1033
1677
|
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
1034
1678
|
const createStore = (invalidate, advance) => {
|
|
@@ -1058,23 +1702,29 @@ const createStore = (invalidate, advance) => {
|
|
|
1058
1702
|
set,
|
|
1059
1703
|
get,
|
|
1060
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,
|
|
1061
1707
|
gl: null,
|
|
1062
1708
|
renderer: null,
|
|
1063
1709
|
camera: null,
|
|
1064
1710
|
frustum: new Frustum(),
|
|
1065
1711
|
autoUpdateFrustum: true,
|
|
1066
1712
|
raycaster: null,
|
|
1067
|
-
events: {
|
|
1713
|
+
events: {
|
|
1714
|
+
priority: 1,
|
|
1715
|
+
enabled: true,
|
|
1716
|
+
connected: false,
|
|
1717
|
+
frameTimedRaycasts: true,
|
|
1718
|
+
alwaysFireOnScroll: true,
|
|
1719
|
+
updateOnFrame: false
|
|
1720
|
+
},
|
|
1068
1721
|
scene: null,
|
|
1069
1722
|
rootScene: null,
|
|
1070
1723
|
xr: null,
|
|
1071
1724
|
inspector: null,
|
|
1072
1725
|
invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
|
|
1073
1726
|
advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
|
|
1074
|
-
|
|
1075
|
-
linear: false,
|
|
1076
|
-
flat: false,
|
|
1077
|
-
textureColorSpace: "srgb",
|
|
1727
|
+
textureColorSpace: SRGBColorSpace,
|
|
1078
1728
|
isLegacy: false,
|
|
1079
1729
|
webGPUSupported: false,
|
|
1080
1730
|
isNative: false,
|
|
@@ -1114,10 +1764,40 @@ const createStore = (invalidate, advance) => {
|
|
|
1114
1764
|
getCurrentViewport
|
|
1115
1765
|
},
|
|
1116
1766
|
setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
|
|
1117
|
-
setSize: (width, height, top
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1767
|
+
setSize: (width, height, top, left) => {
|
|
1768
|
+
const state2 = get();
|
|
1769
|
+
if (width === void 0) {
|
|
1770
|
+
set({ _sizeImperative: false });
|
|
1771
|
+
if (state2._sizeProps) {
|
|
1772
|
+
const { width: propW, height: propH } = state2._sizeProps;
|
|
1773
|
+
if (propW !== void 0 || propH !== void 0) {
|
|
1774
|
+
const currentSize = state2.size;
|
|
1775
|
+
const newSize = {
|
|
1776
|
+
width: propW ?? currentSize.width,
|
|
1777
|
+
height: propH ?? currentSize.height,
|
|
1778
|
+
top: currentSize.top,
|
|
1779
|
+
left: currentSize.left
|
|
1780
|
+
};
|
|
1781
|
+
set((s) => ({
|
|
1782
|
+
size: newSize,
|
|
1783
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
|
|
1784
|
+
}));
|
|
1785
|
+
getScheduler().invalidate();
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
const w = width;
|
|
1791
|
+
const h = height ?? width;
|
|
1792
|
+
const t = top ?? state2.size.top;
|
|
1793
|
+
const l = left ?? state2.size.left;
|
|
1794
|
+
const size = { width: w, height: h, top: t, left: l };
|
|
1795
|
+
set((s) => ({
|
|
1796
|
+
size,
|
|
1797
|
+
viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
|
|
1798
|
+
_sizeImperative: true
|
|
1799
|
+
}));
|
|
1800
|
+
getScheduler().invalidate();
|
|
1121
1801
|
},
|
|
1122
1802
|
setDpr: (dpr) => set((state2) => {
|
|
1123
1803
|
const resolved = calculateDpr(dpr);
|
|
@@ -1125,25 +1805,36 @@ const createStore = (invalidate, advance) => {
|
|
|
1125
1805
|
}),
|
|
1126
1806
|
setFrameloop: (frameloop = "always") => {
|
|
1127
1807
|
set(() => ({ frameloop }));
|
|
1808
|
+
getScheduler().frameloop = frameloop;
|
|
1128
1809
|
},
|
|
1129
1810
|
setError: (error) => set(() => ({ error })),
|
|
1130
1811
|
error: null,
|
|
1131
|
-
//* TSL State (managed via hooks: useUniforms, useNodes, useTextures,
|
|
1812
|
+
//* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
|
|
1132
1813
|
uniforms: {},
|
|
1133
1814
|
nodes: {},
|
|
1815
|
+
buffers: {},
|
|
1816
|
+
gpuStorage: {},
|
|
1134
1817
|
textures: /* @__PURE__ */ new Map(),
|
|
1135
|
-
|
|
1818
|
+
_textureRefs: /* @__PURE__ */ new Map(),
|
|
1819
|
+
renderPipeline: null,
|
|
1136
1820
|
passes: {},
|
|
1821
|
+
_hmrVersion: 0,
|
|
1822
|
+
_sizeImperative: false,
|
|
1823
|
+
_sizeProps: null,
|
|
1137
1824
|
previousRoot: void 0,
|
|
1138
1825
|
internal: {
|
|
1139
1826
|
// Events
|
|
1140
1827
|
interaction: [],
|
|
1141
|
-
hovered: /* @__PURE__ */ new Map(),
|
|
1142
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(),
|
|
1143
1835
|
initialClick: [0, 0],
|
|
1144
1836
|
initialHits: [],
|
|
1145
1837
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
1146
|
-
lastEvent: React.createRef(),
|
|
1147
1838
|
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1148
1839
|
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1149
1840
|
// Occlusion system (WebGPU only)
|
|
@@ -1182,8 +1873,7 @@ const createStore = (invalidate, advance) => {
|
|
|
1182
1873
|
const currentState = rootStore.getState();
|
|
1183
1874
|
if (!currentState.isLegacy && currentState.internal.actualRenderer) {
|
|
1184
1875
|
const stack = new Error().stack || "";
|
|
1185
|
-
|
|
1186
|
-
if (!isInternalAccess) {
|
|
1876
|
+
if (!isInternalRendererAccess(stack)) {
|
|
1187
1877
|
const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
|
|
1188
1878
|
notifyDepreciated({
|
|
1189
1879
|
heading: "Accessing state.gl in WebGPU mode",
|
|
@@ -1226,13 +1916,22 @@ const createStore = (invalidate, advance) => {
|
|
|
1226
1916
|
rootStore.subscribe(() => {
|
|
1227
1917
|
const { camera, size, viewport, set, internal } = rootStore.getState();
|
|
1228
1918
|
const actualRenderer = internal.actualRenderer;
|
|
1919
|
+
const canvasTarget = internal.canvasTarget;
|
|
1229
1920
|
if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
|
|
1230
1921
|
oldSize = size;
|
|
1231
1922
|
oldDpr = viewport.dpr;
|
|
1232
1923
|
updateCamera(camera, size);
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
+
}
|
|
1236
1935
|
}
|
|
1237
1936
|
if (camera !== oldCamera) {
|
|
1238
1937
|
oldCamera = camera;
|
|
@@ -1287,1092 +1986,67 @@ function loadingFn(extensions, onProgress) {
|
|
|
1287
1986
|
);
|
|
1288
1987
|
};
|
|
1289
1988
|
}
|
|
1290
|
-
function useLoader(loader, input, extensions, onProgress) {
|
|
1291
|
-
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];
|
|
1292
1992
|
const fn = loadingFn(extensions, onProgress);
|
|
1293
|
-
const results =
|
|
1993
|
+
const results = cacheKeys.map(
|
|
1994
|
+
(key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
|
|
1995
|
+
);
|
|
1294
1996
|
return Array.isArray(input) ? results : results[0];
|
|
1295
1997
|
}
|
|
1296
|
-
useLoader.preload = function(loader, input, extensions, onProgress) {
|
|
1297
|
-
const
|
|
1298
|
-
|
|
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
|
+
);
|
|
1299
2004
|
};
|
|
1300
|
-
useLoader.clear = function(loader, input) {
|
|
1301
|
-
const
|
|
1302
|
-
|
|
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]));
|
|
1303
2008
|
};
|
|
1304
2009
|
useLoader.loader = getLoader;
|
|
1305
2010
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
else {
|
|
1346
|
-
const constraintType = before ? "before" : "after";
|
|
1347
|
-
console.warn(`[useFrame] Phase "${before ?? after}" not found for '${constraintType}' constraint`);
|
|
1348
|
-
}
|
|
1349
|
-
this.phases.splice(insertIndex, 0, node);
|
|
1350
|
-
this.phaseMap.set(name, node);
|
|
1351
|
-
this.invalidateCache();
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Get ordered list of phase names
|
|
1355
|
-
*/
|
|
1356
|
-
getOrderedPhases() {
|
|
1357
|
-
if (this.orderedNamesCache === null) this.orderedNamesCache = this.phases.map((p) => p.name);
|
|
1358
|
-
return this.orderedNamesCache;
|
|
1359
|
-
}
|
|
1360
|
-
/**
|
|
1361
|
-
* Check if a phase exists
|
|
1362
|
-
*/
|
|
1363
|
-
hasPhase(name) {
|
|
1364
|
-
return this.phaseMap.has(name);
|
|
1365
|
-
}
|
|
1366
|
-
/**
|
|
1367
|
-
* Get the index of a phase (-1 if not found)
|
|
1368
|
-
*/
|
|
1369
|
-
getPhaseIndex(name) {
|
|
1370
|
-
if (!name) return -1;
|
|
1371
|
-
return this.phases.findIndex((p) => p.name === name);
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Ensure a phase exists, creating an auto-generated one if needed.
|
|
1375
|
-
* Used for resolving before/after constraints.
|
|
1376
|
-
*
|
|
1377
|
-
* @param name - The phase name to ensure exists
|
|
1378
|
-
* @returns The phase name (may be auto-generated like 'before:render')
|
|
1379
|
-
*/
|
|
1380
|
-
ensurePhase(name) {
|
|
1381
|
-
if (this.phaseMap.has(name)) return name;
|
|
1382
|
-
const node = { name, isAutoGenerated: true };
|
|
1383
|
-
this.phases.push(node);
|
|
1384
|
-
this.phaseMap.set(name, node);
|
|
1385
|
-
this.invalidateCache();
|
|
1386
|
-
return name;
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Resolve where a job with before/after constraints should go.
|
|
1390
|
-
* Creates auto-generated phases if needed.
|
|
1391
|
-
*
|
|
1392
|
-
* @param before - Phase(s) to run before
|
|
1393
|
-
* @param after - Phase(s) to run after
|
|
1394
|
-
* @returns The resolved phase name
|
|
1395
|
-
*/
|
|
1396
|
-
resolveConstraintPhase(before, after) {
|
|
1397
|
-
const beforeArr = before ? Array.isArray(before) ? before : [before] : [];
|
|
1398
|
-
const afterArr = after ? Array.isArray(after) ? after : [after] : [];
|
|
1399
|
-
if (beforeArr.length > 0) {
|
|
1400
|
-
return this.ensureAutoPhase(beforeArr[0], "before", 0);
|
|
1401
|
-
}
|
|
1402
|
-
if (afterArr.length > 0) {
|
|
1403
|
-
return this.ensureAutoPhase(afterArr[0], "after", 1);
|
|
1404
|
-
}
|
|
1405
|
-
return "update";
|
|
1406
|
-
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Ensure an auto-generated phase exists relative to a target phase.
|
|
1409
|
-
* Creates the phase if it doesn't exist, inserting it at the correct position.
|
|
1410
|
-
*
|
|
1411
|
-
* @param target - The target phase name to position relative to
|
|
1412
|
-
* @param prefix - Prefix for auto-generated phase name ('before' or 'after')
|
|
1413
|
-
* @param offset - Insertion offset (0 for before, 1 for after)
|
|
1414
|
-
* @returns The auto-generated phase name
|
|
1415
|
-
*/
|
|
1416
|
-
ensureAutoPhase(target, prefix, offset) {
|
|
1417
|
-
const autoName = `${prefix}:${target}`;
|
|
1418
|
-
if (this.phaseMap.has(autoName)) return autoName;
|
|
1419
|
-
const node = { name: autoName, isAutoGenerated: true };
|
|
1420
|
-
const targetIndex = this.getPhaseIndex(target);
|
|
1421
|
-
if (targetIndex !== -1) this.phases.splice(targetIndex + offset, 0, node);
|
|
1422
|
-
else this.phases.push(node);
|
|
1423
|
-
this.phaseMap.set(autoName, node);
|
|
1424
|
-
this.invalidateCache();
|
|
1425
|
-
return autoName;
|
|
1426
|
-
}
|
|
1427
|
-
// Internal --------------------------------
|
|
1428
|
-
invalidateCache() {
|
|
1429
|
-
this.orderedNamesCache = null;
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function rebuildSortedJobs(jobs, phaseGraph) {
|
|
1434
|
-
const orderedPhases = phaseGraph.getOrderedPhases();
|
|
1435
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
1436
|
-
for (const phase of orderedPhases) {
|
|
1437
|
-
buckets.set(phase, []);
|
|
1438
|
-
}
|
|
1439
|
-
for (const job of jobs.values()) {
|
|
1440
|
-
if (!job.enabled) continue;
|
|
1441
|
-
let bucket = buckets.get(job.phase);
|
|
1442
|
-
if (!bucket) {
|
|
1443
|
-
bucket = [];
|
|
1444
|
-
buckets.set(job.phase, bucket);
|
|
1445
|
-
}
|
|
1446
|
-
bucket.push(job);
|
|
1447
|
-
}
|
|
1448
|
-
const sortedBuckets = [];
|
|
1449
|
-
for (const phase of orderedPhases) {
|
|
1450
|
-
const bucket = buckets.get(phase);
|
|
1451
|
-
if (!bucket || bucket.length === 0) continue;
|
|
1452
|
-
bucket.sort((a, b) => {
|
|
1453
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1454
|
-
return a.index - b.index;
|
|
1455
|
-
});
|
|
1456
|
-
sortedBuckets.push(hasCrossJobConstraints(bucket) ? topologicalSort(bucket) : bucket);
|
|
1457
|
-
}
|
|
1458
|
-
for (const [phase, bucket] of buckets) {
|
|
1459
|
-
if (!orderedPhases.includes(phase) && bucket.length > 0) {
|
|
1460
|
-
bucket.sort((a, b) => {
|
|
1461
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1462
|
-
return a.index - b.index;
|
|
1463
|
-
});
|
|
1464
|
-
sortedBuckets.push(bucket);
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
return sortedBuckets.flat();
|
|
1468
|
-
}
|
|
1469
|
-
function hasCrossJobConstraints(bucket) {
|
|
1470
|
-
const jobIds = new Set(bucket.map((j) => j.id));
|
|
1471
|
-
for (const job of bucket) {
|
|
1472
|
-
for (const ref of job.before) {
|
|
1473
|
-
if (jobIds.has(ref)) return true;
|
|
1474
|
-
}
|
|
1475
|
-
for (const ref of job.after) {
|
|
1476
|
-
if (jobIds.has(ref)) return true;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
return false;
|
|
1480
|
-
}
|
|
1481
|
-
function topologicalSort(jobs) {
|
|
1482
|
-
const n = jobs.length;
|
|
1483
|
-
if (n <= 1) return jobs;
|
|
1484
|
-
const jobMap = /* @__PURE__ */ new Map();
|
|
1485
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
1486
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
1487
|
-
for (const job of jobs) {
|
|
1488
|
-
jobMap.set(job.id, job);
|
|
1489
|
-
inDegree.set(job.id, 0);
|
|
1490
|
-
adjacency.set(job.id, []);
|
|
1491
|
-
}
|
|
1492
|
-
for (const job of jobs) {
|
|
1493
|
-
for (const ref of job.before) {
|
|
1494
|
-
if (jobMap.has(ref)) {
|
|
1495
|
-
adjacency.get(job.id).push(ref);
|
|
1496
|
-
inDegree.set(ref, inDegree.get(ref) + 1);
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
for (const ref of job.after) {
|
|
1500
|
-
if (jobMap.has(ref)) {
|
|
1501
|
-
adjacency.get(ref).push(job.id);
|
|
1502
|
-
inDegree.set(job.id, inDegree.get(job.id) + 1);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
const queue = [];
|
|
1507
|
-
for (const job of jobs) {
|
|
1508
|
-
if (inDegree.get(job.id) === 0) {
|
|
1509
|
-
queue.push(job);
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
queue.sort((a, b) => {
|
|
1513
|
-
if (a.priority !== b.priority) return b.priority - a.priority;
|
|
1514
|
-
return a.index - b.index;
|
|
1515
|
-
});
|
|
1516
|
-
const result = [];
|
|
1517
|
-
while (queue.length > 0) {
|
|
1518
|
-
const job = queue.shift();
|
|
1519
|
-
result.push(job);
|
|
1520
|
-
const neighbors = adjacency.get(job.id) || [];
|
|
1521
|
-
for (const neighborId of neighbors) {
|
|
1522
|
-
const newDegree = inDegree.get(neighborId) - 1;
|
|
1523
|
-
inDegree.set(neighborId, newDegree);
|
|
1524
|
-
if (newDegree === 0) {
|
|
1525
|
-
const neighbor = jobMap.get(neighborId);
|
|
1526
|
-
insertSorted(queue, neighbor);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
if (result.length !== n) {
|
|
1531
|
-
console.warn("[useFrame] Circular dependency detected in job constraints");
|
|
1532
|
-
const resultIds = new Set(result.map((j) => j.id));
|
|
1533
|
-
for (const job of jobs) {
|
|
1534
|
-
if (!resultIds.has(job.id)) result.push(job);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
return result;
|
|
1538
|
-
}
|
|
1539
|
-
function insertSorted(arr, job) {
|
|
1540
|
-
let i = 0;
|
|
1541
|
-
while (i < arr.length) {
|
|
1542
|
-
const cmp = arr[i];
|
|
1543
|
-
if (job.priority > cmp.priority || job.priority === cmp.priority && job.index < cmp.index) {
|
|
1544
|
-
break;
|
|
1545
|
-
}
|
|
1546
|
-
i++;
|
|
1547
|
-
}
|
|
1548
|
-
arr.splice(i, 0, job);
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
function shouldRun(job, now) {
|
|
1552
|
-
if (!job.enabled) return false;
|
|
1553
|
-
if (!job.fps) return true;
|
|
1554
|
-
const minInterval = 1e3 / job.fps;
|
|
1555
|
-
const lastRun = job.lastRun ?? 0;
|
|
1556
|
-
const elapsed = now - lastRun;
|
|
1557
|
-
if (elapsed < minInterval) return false;
|
|
1558
|
-
if (job.drop) {
|
|
1559
|
-
job.lastRun = now;
|
|
1560
|
-
} else {
|
|
1561
|
-
const steps = Math.floor(elapsed / minInterval);
|
|
1562
|
-
job.lastRun = lastRun + steps * minInterval;
|
|
1563
|
-
if (job.lastRun < now - minInterval) {
|
|
1564
|
-
job.lastRun = now;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
return true;
|
|
1568
|
-
}
|
|
1569
|
-
function resetJobTiming(job) {
|
|
1570
|
-
job.lastRun = void 0;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
var __defProp = Object.defineProperty;
|
|
1574
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1575
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1576
|
-
const hmrData = (() => {
|
|
1577
|
-
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") return void 0;
|
|
1578
|
-
if (typeof import_meta_hot !== "undefined") return import_meta_hot;
|
|
1579
|
-
try {
|
|
1580
|
-
return (0, eval)("import.meta.hot");
|
|
1581
|
-
} catch {
|
|
1582
|
-
return void 0;
|
|
1583
|
-
}
|
|
1584
|
-
})();
|
|
1585
|
-
const _Scheduler = class _Scheduler {
|
|
1586
|
-
//* Constructor ================================
|
|
1587
|
-
constructor() {
|
|
1588
|
-
//* Critical State ================================
|
|
1589
|
-
__publicField(this, "roots", /* @__PURE__ */ new Map());
|
|
1590
|
-
__publicField(this, "phaseGraph");
|
|
1591
|
-
__publicField(this, "loopState", {
|
|
1592
|
-
running: false,
|
|
1593
|
-
rafHandle: null,
|
|
1594
|
-
lastTime: null,
|
|
1595
|
-
// null = uninitialized, 0+ = valid timestamp
|
|
1596
|
-
frameCount: 0,
|
|
1597
|
-
elapsedTime: 0,
|
|
1598
|
-
createdAt: performance.now()
|
|
1599
|
-
});
|
|
1600
|
-
__publicField(this, "stoppedTime", 0);
|
|
1601
|
-
//* Private State ================================
|
|
1602
|
-
__publicField(this, "nextRootIndex", 0);
|
|
1603
|
-
__publicField(this, "globalBeforeJobs", /* @__PURE__ */ new Map());
|
|
1604
|
-
__publicField(this, "globalAfterJobs", /* @__PURE__ */ new Map());
|
|
1605
|
-
__publicField(this, "nextGlobalIndex", 0);
|
|
1606
|
-
__publicField(this, "idleCallbacks", /* @__PURE__ */ new Set());
|
|
1607
|
-
__publicField(this, "nextJobIndex", 0);
|
|
1608
|
-
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1609
|
-
__publicField(this, "pendingFrames", 0);
|
|
1610
|
-
__publicField(this, "_frameloop", "always");
|
|
1611
|
-
//* Independent Mode & Error Handling State ================================
|
|
1612
|
-
__publicField(this, "_independent", false);
|
|
1613
|
-
__publicField(this, "errorHandler", null);
|
|
1614
|
-
__publicField(this, "rootReadyCallbacks", /* @__PURE__ */ new Set());
|
|
1615
|
-
//* Core Loop Execution Methods ================================
|
|
1616
|
-
/**
|
|
1617
|
-
* Main RAF loop callback.
|
|
1618
|
-
* Executes frame, handles demand mode, and schedules next frame.
|
|
1619
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1620
|
-
* @returns {void}
|
|
1621
|
-
* @private
|
|
1622
|
-
*/
|
|
1623
|
-
__publicField(this, "loop", (timestamp) => {
|
|
1624
|
-
if (!this.loopState.running) return;
|
|
1625
|
-
this.executeFrame(timestamp);
|
|
1626
|
-
if (this._frameloop === "demand") {
|
|
1627
|
-
this.pendingFrames = Math.max(0, this.pendingFrames - 1);
|
|
1628
|
-
if (this.pendingFrames === 0) {
|
|
1629
|
-
this.notifyIdle(timestamp);
|
|
1630
|
-
return this.stop();
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
this.loopState.rafHandle = requestAnimationFrame(this.loop);
|
|
1634
|
-
});
|
|
1635
|
-
this.phaseGraph = new PhaseGraph();
|
|
1636
|
-
}
|
|
1637
|
-
static get instance() {
|
|
1638
|
-
return globalThis[_Scheduler.INSTANCE_KEY] ?? null;
|
|
1639
|
-
}
|
|
1640
|
-
static set instance(value) {
|
|
1641
|
-
globalThis[_Scheduler.INSTANCE_KEY] = value;
|
|
1642
|
-
}
|
|
1643
|
-
/**
|
|
1644
|
-
* Get the global scheduler instance (creates if doesn't exist).
|
|
1645
|
-
* Uses HMR data to preserve instance across hot reloads.
|
|
1646
|
-
* @returns {Scheduler} The singleton scheduler instance
|
|
1647
|
-
*/
|
|
1648
|
-
static get() {
|
|
1649
|
-
if (!_Scheduler.instance && hmrData?.data?.scheduler) {
|
|
1650
|
-
_Scheduler.instance = hmrData.data.scheduler;
|
|
1651
|
-
}
|
|
1652
|
-
if (!_Scheduler.instance) {
|
|
1653
|
-
_Scheduler.instance = new _Scheduler();
|
|
1654
|
-
if (hmrData?.data) {
|
|
1655
|
-
hmrData.data.scheduler = _Scheduler.instance;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
return _Scheduler.instance;
|
|
1659
|
-
}
|
|
1660
|
-
/**
|
|
1661
|
-
* Reset the singleton instance. Stops the loop and clears all state.
|
|
1662
|
-
* Primarily used for testing to ensure clean state between tests.
|
|
1663
|
-
* @returns {void}
|
|
1664
|
-
*/
|
|
1665
|
-
static reset() {
|
|
1666
|
-
if (_Scheduler.instance) {
|
|
1667
|
-
_Scheduler.instance.stop();
|
|
1668
|
-
_Scheduler.instance = null;
|
|
1669
|
-
}
|
|
1670
|
-
if (hmrData?.data) {
|
|
1671
|
-
hmrData.data.scheduler = null;
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
//* Getters & Setters ================================
|
|
1675
|
-
get phases() {
|
|
1676
|
-
return this.phaseGraph.getOrderedPhases();
|
|
1677
|
-
}
|
|
1678
|
-
get frameloop() {
|
|
1679
|
-
return this._frameloop;
|
|
1680
|
-
}
|
|
1681
|
-
set frameloop(mode) {
|
|
1682
|
-
if (this._frameloop === mode) return;
|
|
1683
|
-
const wasAlways = this._frameloop === "always";
|
|
1684
|
-
this._frameloop = mode;
|
|
1685
|
-
if (mode === "always" && !this.loopState.running && this.roots.size > 0) this.start();
|
|
1686
|
-
else if (mode !== "always" && wasAlways) this.stop();
|
|
1687
|
-
}
|
|
1688
|
-
get isRunning() {
|
|
1689
|
-
return this.loopState.running;
|
|
1690
|
-
}
|
|
1691
|
-
get isReady() {
|
|
1692
|
-
return this.roots.size > 0;
|
|
1693
|
-
}
|
|
1694
|
-
get independent() {
|
|
1695
|
-
return this._independent;
|
|
1696
|
-
}
|
|
1697
|
-
set independent(value) {
|
|
1698
|
-
this._independent = value;
|
|
1699
|
-
if (value) this.ensureDefaultRoot();
|
|
1700
|
-
}
|
|
1701
|
-
//* Root Management Methods ================================
|
|
1702
|
-
/**
|
|
1703
|
-
* Register a root (Canvas) with the scheduler.
|
|
1704
|
-
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1705
|
-
* @param {string} id - Unique identifier for this root
|
|
1706
|
-
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1707
|
-
* @returns {() => void} Unsubscribe function to remove this root
|
|
1708
|
-
*/
|
|
1709
|
-
registerRoot(id, options = {}) {
|
|
1710
|
-
if (this.roots.has(id)) {
|
|
1711
|
-
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1712
|
-
return () => this.unregisterRoot(id);
|
|
1713
|
-
}
|
|
1714
|
-
const entry = {
|
|
1715
|
-
id,
|
|
1716
|
-
getState: options.getState ?? (() => ({})),
|
|
1717
|
-
jobs: /* @__PURE__ */ new Map(),
|
|
1718
|
-
sortedJobs: [],
|
|
1719
|
-
needsRebuild: false
|
|
1720
|
-
};
|
|
1721
|
-
if (options.onError) {
|
|
1722
|
-
this.errorHandler = options.onError;
|
|
1723
|
-
}
|
|
1724
|
-
this.roots.set(id, entry);
|
|
1725
|
-
if (this.roots.size === 1) {
|
|
1726
|
-
this.notifyRootReady();
|
|
1727
|
-
if (this._frameloop === "always") this.start();
|
|
1728
|
-
}
|
|
1729
|
-
return () => this.unregisterRoot(id);
|
|
1730
|
-
}
|
|
1731
|
-
/**
|
|
1732
|
-
* Unregister a root from the scheduler.
|
|
1733
|
-
* Cleans up all job state listeners for this root's jobs.
|
|
1734
|
-
* The last root to unregister stops the RAF loop.
|
|
1735
|
-
* @param {string} id - The root ID to unregister
|
|
1736
|
-
* @returns {void}
|
|
1737
|
-
*/
|
|
1738
|
-
unregisterRoot(id) {
|
|
1739
|
-
const root = this.roots.get(id);
|
|
1740
|
-
if (!root) return;
|
|
1741
|
-
for (const jobId of root.jobs.keys()) {
|
|
1742
|
-
this.jobStateListeners.delete(jobId);
|
|
1743
|
-
}
|
|
1744
|
-
this.roots.delete(id);
|
|
1745
|
-
if (this.roots.size === 0) {
|
|
1746
|
-
this.stop();
|
|
1747
|
-
this.errorHandler = null;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
/**
|
|
1751
|
-
* Subscribe to be notified when a root becomes available.
|
|
1752
|
-
* Fires immediately if a root already exists.
|
|
1753
|
-
* @param {() => void} callback - Function called when first root registers
|
|
1754
|
-
* @returns {() => void} Unsubscribe function
|
|
1755
|
-
*/
|
|
1756
|
-
onRootReady(callback) {
|
|
1757
|
-
if (this.roots.size > 0) {
|
|
1758
|
-
callback();
|
|
1759
|
-
return () => {
|
|
1760
|
-
};
|
|
1761
|
-
}
|
|
1762
|
-
this.rootReadyCallbacks.add(callback);
|
|
1763
|
-
return () => this.rootReadyCallbacks.delete(callback);
|
|
1764
|
-
}
|
|
1765
|
-
/**
|
|
1766
|
-
* Notify all registered root-ready callbacks.
|
|
1767
|
-
* Called when the first root registers.
|
|
1768
|
-
* @returns {void}
|
|
1769
|
-
* @private
|
|
1770
|
-
*/
|
|
1771
|
-
notifyRootReady() {
|
|
1772
|
-
for (const cb of this.rootReadyCallbacks) {
|
|
1773
|
-
try {
|
|
1774
|
-
cb();
|
|
1775
|
-
} catch (error) {
|
|
1776
|
-
console.error("[Scheduler] Error in root-ready callback:", error);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
this.rootReadyCallbacks.clear();
|
|
1780
|
-
}
|
|
1781
|
-
/**
|
|
1782
|
-
* Ensure a default root exists for independent mode.
|
|
1783
|
-
* Creates a minimal root with no state provider.
|
|
1784
|
-
* @returns {void}
|
|
1785
|
-
* @private
|
|
1786
|
-
*/
|
|
1787
|
-
ensureDefaultRoot() {
|
|
1788
|
-
if (!this.roots.has("__default__")) {
|
|
1789
|
-
this.registerRoot("__default__");
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
/**
|
|
1793
|
-
* Trigger error handling for job errors.
|
|
1794
|
-
* Uses the bound error handler if available, otherwise logs to console.
|
|
1795
|
-
* @param {Error} error - The error to handle
|
|
1796
|
-
* @returns {void}
|
|
1797
|
-
*/
|
|
1798
|
-
triggerError(error) {
|
|
1799
|
-
if (this.errorHandler) this.errorHandler(error);
|
|
1800
|
-
else console.error("[Scheduler]", error);
|
|
1801
|
-
}
|
|
1802
|
-
//* Phase Management Methods ================================
|
|
1803
|
-
/**
|
|
1804
|
-
* Add a named phase to the scheduler's execution order.
|
|
1805
|
-
* Marks all roots for rebuild to incorporate the new phase.
|
|
1806
|
-
* @param {string} name - The phase name (e.g., 'physics', 'postprocess')
|
|
1807
|
-
* @param {AddPhaseOptions} [options] - Positioning options (before/after other phases)
|
|
1808
|
-
* @returns {void}
|
|
1809
|
-
* @example
|
|
1810
|
-
* scheduler.addPhase('physics', { before: 'update' });
|
|
1811
|
-
* scheduler.addPhase('postprocess', { after: 'render' });
|
|
1812
|
-
*/
|
|
1813
|
-
addPhase(name, options) {
|
|
1814
|
-
this.phaseGraph.addPhase(name, options);
|
|
1815
|
-
for (const root of this.roots.values()) {
|
|
1816
|
-
root.needsRebuild = true;
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
/**
|
|
1820
|
-
* Check if a phase exists in the scheduler.
|
|
1821
|
-
* @param {string} name - The phase name to check
|
|
1822
|
-
* @returns {boolean} True if the phase exists
|
|
1823
|
-
*/
|
|
1824
|
-
hasPhase(name) {
|
|
1825
|
-
return this.phaseGraph.hasPhase(name);
|
|
1826
|
-
}
|
|
1827
|
-
//* Global Job Registration Methods (Deprecated APIs) ================================
|
|
1828
|
-
/**
|
|
1829
|
-
* Register a global job that runs once per frame (not per-root).
|
|
1830
|
-
* Used internally by deprecated addEffect/addAfterEffect APIs.
|
|
1831
|
-
* @param {'before' | 'after'} phase - When to run: 'before' all roots or 'after' all roots
|
|
1832
|
-
* @param {string} id - Unique identifier for this global job
|
|
1833
|
-
* @param {(timestamp: number) => void} callback - Function called each frame with RAF timestamp
|
|
1834
|
-
* @returns {() => void} Unsubscribe function to remove this global job
|
|
1835
|
-
* @deprecated Use useFrame with phases instead
|
|
1836
|
-
*/
|
|
1837
|
-
registerGlobal(phase, id, callback) {
|
|
1838
|
-
const job = { id, callback };
|
|
1839
|
-
if (phase === "before") {
|
|
1840
|
-
this.globalBeforeJobs.set(id, job);
|
|
1841
|
-
} else {
|
|
1842
|
-
this.globalAfterJobs.set(id, job);
|
|
1843
|
-
}
|
|
1844
|
-
return () => {
|
|
1845
|
-
if (phase === "before") this.globalBeforeJobs.delete(id);
|
|
1846
|
-
else this.globalAfterJobs.delete(id);
|
|
1847
|
-
};
|
|
1848
|
-
}
|
|
1849
|
-
//* Idle Callback Methods (Deprecated API) ================================
|
|
1850
|
-
/**
|
|
1851
|
-
* Register an idle callback that fires when the loop stops.
|
|
1852
|
-
* Used internally by deprecated addTail API.
|
|
1853
|
-
* @param {(timestamp: number) => void} callback - Function called when loop becomes idle
|
|
1854
|
-
* @returns {() => void} Unsubscribe function to remove this idle callback
|
|
1855
|
-
* @deprecated Use demand mode with invalidate() instead
|
|
1856
|
-
*/
|
|
1857
|
-
onIdle(callback) {
|
|
1858
|
-
this.idleCallbacks.add(callback);
|
|
1859
|
-
return () => this.idleCallbacks.delete(callback);
|
|
1860
|
-
}
|
|
1861
|
-
/**
|
|
1862
|
-
* Notify all registered idle callbacks.
|
|
1863
|
-
* Called when the loop stops in demand mode.
|
|
1864
|
-
* @param {number} timestamp - The RAF timestamp when idle occurred
|
|
1865
|
-
* @returns {void}
|
|
1866
|
-
* @private
|
|
1867
|
-
*/
|
|
1868
|
-
notifyIdle(timestamp) {
|
|
1869
|
-
for (const cb of this.idleCallbacks) {
|
|
1870
|
-
try {
|
|
1871
|
-
cb(timestamp);
|
|
1872
|
-
} catch (error) {
|
|
1873
|
-
console.error("[Scheduler] Error in idle callback:", error);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
//* Job Registration & Management Methods ================================
|
|
1878
|
-
/**
|
|
1879
|
-
* Register a job (frame callback) with a specific root.
|
|
1880
|
-
* This is the core registration method used by useFrame internally.
|
|
1881
|
-
* @param {FrameNextCallback} callback - The function to call each frame
|
|
1882
|
-
* @param {JobOptions & { rootId?: string; system?: boolean }} [options] - Job configuration
|
|
1883
|
-
* @param {string} [options.rootId] - Target root ID (defaults to first registered root)
|
|
1884
|
-
* @param {string} [options.id] - Unique job ID (auto-generated if not provided)
|
|
1885
|
-
* @param {string} [options.phase] - Execution phase (defaults to 'update')
|
|
1886
|
-
* @param {number} [options.priority] - Priority within phase (higher = earlier, default 0)
|
|
1887
|
-
* @param {number} [options.fps] - FPS throttle limit
|
|
1888
|
-
* @param {boolean} [options.drop] - Drop frames when behind (default true)
|
|
1889
|
-
* @param {boolean} [options.enabled] - Whether job is active (default true)
|
|
1890
|
-
* @param {boolean} [options.system] - Internal flag for system jobs (not user-facing)
|
|
1891
|
-
* @returns {() => void} Unsubscribe function to remove this job
|
|
1892
|
-
*/
|
|
1893
|
-
register(callback, options = {}) {
|
|
1894
|
-
const rootId = options.rootId;
|
|
1895
|
-
const root = rootId ? this.roots.get(rootId) : this.roots.values().next().value;
|
|
1896
|
-
if (!root) {
|
|
1897
|
-
console.warn("[Scheduler] No root registered. Is this inside a Canvas?");
|
|
1898
|
-
return () => {
|
|
1899
|
-
};
|
|
1900
|
-
}
|
|
1901
|
-
const id = options.id ?? this.generateJobId();
|
|
1902
|
-
let phase = options.phase ?? "update";
|
|
1903
|
-
if (!options.phase && (options.before || options.after)) {
|
|
1904
|
-
phase = this.phaseGraph.resolveConstraintPhase(options.before, options.after);
|
|
1905
|
-
}
|
|
1906
|
-
const before = this.normalizeConstraints(options.before);
|
|
1907
|
-
const after = this.normalizeConstraints(options.after);
|
|
1908
|
-
const job = {
|
|
1909
|
-
id,
|
|
1910
|
-
callback,
|
|
1911
|
-
phase,
|
|
1912
|
-
before,
|
|
1913
|
-
after,
|
|
1914
|
-
priority: options.priority ?? 0,
|
|
1915
|
-
index: this.nextJobIndex++,
|
|
1916
|
-
fps: options.fps,
|
|
1917
|
-
drop: options.drop ?? true,
|
|
1918
|
-
enabled: options.enabled ?? true,
|
|
1919
|
-
system: options.system ?? false
|
|
1920
|
-
};
|
|
1921
|
-
if (root.jobs.has(id)) {
|
|
1922
|
-
console.warn(`[useFrame] Job with id "${id}" already exists, replacing`);
|
|
1923
|
-
}
|
|
1924
|
-
root.jobs.set(id, job);
|
|
1925
|
-
root.needsRebuild = true;
|
|
1926
|
-
return () => this.unregister(id, root.id);
|
|
1927
|
-
}
|
|
1928
|
-
/**
|
|
1929
|
-
* Unregister a job by its ID.
|
|
1930
|
-
* Searches all roots if rootId is not provided.
|
|
1931
|
-
* @param {string} id - The job ID to unregister
|
|
1932
|
-
* @param {string} [rootId] - Optional root ID to search (searches all if not provided)
|
|
1933
|
-
* @returns {void}
|
|
1934
|
-
*/
|
|
1935
|
-
unregister(id, rootId) {
|
|
1936
|
-
const root = rootId ? this.roots.get(rootId) : Array.from(this.roots.values()).find((r) => r.jobs.has(id));
|
|
1937
|
-
if (root?.jobs.delete(id)) {
|
|
1938
|
-
root.needsRebuild = true;
|
|
1939
|
-
this.jobStateListeners.delete(id);
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
/**
|
|
1943
|
-
* Update a job's options dynamically.
|
|
1944
|
-
* Searches all roots to find the job by ID.
|
|
1945
|
-
* Phase/constraint changes trigger a rebuild of the sorted job list.
|
|
1946
|
-
* @param {string} id - The job ID to update
|
|
1947
|
-
* @param {Partial<JobOptions>} options - The options to update
|
|
1948
|
-
* @returns {void}
|
|
1949
|
-
*/
|
|
1950
|
-
updateJob(id, options) {
|
|
1951
|
-
let job;
|
|
1952
|
-
let root;
|
|
1953
|
-
for (const r of this.roots.values()) {
|
|
1954
|
-
job = r.jobs.get(id);
|
|
1955
|
-
if (job) {
|
|
1956
|
-
root = r;
|
|
1957
|
-
break;
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
if (!job || !root) return;
|
|
1961
|
-
if (options.priority !== void 0) job.priority = options.priority;
|
|
1962
|
-
if (options.fps !== void 0) job.fps = options.fps;
|
|
1963
|
-
if (options.drop !== void 0) job.drop = options.drop;
|
|
1964
|
-
if (options.enabled !== void 0) {
|
|
1965
|
-
const wasEnabled = job.enabled;
|
|
1966
|
-
job.enabled = options.enabled;
|
|
1967
|
-
if (!wasEnabled && job.enabled) resetJobTiming(job);
|
|
1968
|
-
if (wasEnabled !== job.enabled) root.needsRebuild = true;
|
|
1969
|
-
}
|
|
1970
|
-
if (options.phase !== void 0 || options.before !== void 0 || options.after !== void 0) {
|
|
1971
|
-
if (options.phase) job.phase = options.phase;
|
|
1972
|
-
if (options.before !== void 0) job.before = this.normalizeConstraints(options.before);
|
|
1973
|
-
if (options.after !== void 0) job.after = this.normalizeConstraints(options.after);
|
|
1974
|
-
root.needsRebuild = true;
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
//* Job State Management Methods ================================
|
|
1978
|
-
/**
|
|
1979
|
-
* Check if a job is currently paused (disabled).
|
|
1980
|
-
* @param {string} id - The job ID to check
|
|
1981
|
-
* @returns {boolean} True if the job exists and is paused
|
|
1982
|
-
*/
|
|
1983
|
-
isJobPaused(id) {
|
|
1984
|
-
for (const root of this.roots.values()) {
|
|
1985
|
-
const job = root.jobs.get(id);
|
|
1986
|
-
if (job) return !job.enabled;
|
|
1987
|
-
}
|
|
1988
|
-
return false;
|
|
1989
|
-
}
|
|
1990
|
-
/**
|
|
1991
|
-
* Subscribe to state changes for a specific job.
|
|
1992
|
-
* Listener is called when job is paused or resumed.
|
|
1993
|
-
* @param {string} id - The job ID to subscribe to
|
|
1994
|
-
* @param {() => void} listener - Callback invoked on state changes
|
|
1995
|
-
* @returns {() => void} Unsubscribe function
|
|
1996
|
-
*/
|
|
1997
|
-
subscribeJobState(id, listener) {
|
|
1998
|
-
if (!this.jobStateListeners.has(id)) {
|
|
1999
|
-
this.jobStateListeners.set(id, /* @__PURE__ */ new Set());
|
|
2000
|
-
}
|
|
2001
|
-
this.jobStateListeners.get(id).add(listener);
|
|
2002
|
-
return () => {
|
|
2003
|
-
this.jobStateListeners.get(id)?.delete(listener);
|
|
2004
|
-
if (this.jobStateListeners.get(id)?.size === 0) {
|
|
2005
|
-
this.jobStateListeners.delete(id);
|
|
2006
|
-
}
|
|
2007
|
-
};
|
|
2008
|
-
}
|
|
2009
|
-
/**
|
|
2010
|
-
* Notify all listeners that a job's state has changed.
|
|
2011
|
-
* @param {string} id - The job ID that changed
|
|
2012
|
-
* @returns {void}
|
|
2013
|
-
* @private
|
|
2014
|
-
*/
|
|
2015
|
-
notifyJobStateChange(id) {
|
|
2016
|
-
this.jobStateListeners.get(id)?.forEach((listener) => listener());
|
|
2017
|
-
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Pause a job by ID (sets enabled=false).
|
|
2020
|
-
* Notifies any subscribed state listeners.
|
|
2021
|
-
* @param {string} id - The job ID to pause
|
|
2022
|
-
* @returns {void}
|
|
2023
|
-
*/
|
|
2024
|
-
pauseJob(id) {
|
|
2025
|
-
this.updateJob(id, { enabled: false });
|
|
2026
|
-
this.notifyJobStateChange(id);
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Resume a paused job by ID (sets enabled=true).
|
|
2030
|
-
* Resets job timing to prevent frame accumulation.
|
|
2031
|
-
* Notifies any subscribed state listeners.
|
|
2032
|
-
* @param {string} id - The job ID to resume
|
|
2033
|
-
* @returns {void}
|
|
2034
|
-
*/
|
|
2035
|
-
resumeJob(id) {
|
|
2036
|
-
this.updateJob(id, { enabled: true });
|
|
2037
|
-
this.notifyJobStateChange(id);
|
|
2038
|
-
}
|
|
2039
|
-
//* Frame Loop Control Methods ================================
|
|
2040
|
-
/**
|
|
2041
|
-
* Start the requestAnimationFrame loop.
|
|
2042
|
-
* Resets timing state (elapsedTime, frameCount) on start.
|
|
2043
|
-
* No-op if already running.
|
|
2044
|
-
* @returns {void}
|
|
2045
|
-
*/
|
|
2046
|
-
start() {
|
|
2047
|
-
if (this.loopState.running) return;
|
|
2048
|
-
const { elapsedTime, createdAt } = this.loopState;
|
|
2049
|
-
let adjustedCreated = 0;
|
|
2050
|
-
if (this.stoppedTime > 0) {
|
|
2051
|
-
adjustedCreated = createdAt - (performance.now() - this.stoppedTime);
|
|
2052
|
-
this.stoppedTime = 0;
|
|
2053
|
-
}
|
|
2054
|
-
Object.assign(this.loopState, {
|
|
2055
|
-
running: true,
|
|
2056
|
-
elapsedTime: elapsedTime ?? 0,
|
|
2057
|
-
lastTime: performance.now(),
|
|
2058
|
-
createdAt: adjustedCreated > 0 ? adjustedCreated : performance.now(),
|
|
2059
|
-
frameCount: 0,
|
|
2060
|
-
rafHandle: requestAnimationFrame(this.loop)
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Stop the requestAnimationFrame loop.
|
|
2065
|
-
* Cancels any pending RAF callback.
|
|
2066
|
-
* No-op if not running.
|
|
2067
|
-
* @returns {void}
|
|
2068
|
-
*/
|
|
2069
|
-
stop() {
|
|
2070
|
-
if (!this.loopState.running) return;
|
|
2071
|
-
this.loopState.running = false;
|
|
2072
|
-
if (this.loopState.rafHandle !== null) {
|
|
2073
|
-
cancelAnimationFrame(this.loopState.rafHandle);
|
|
2074
|
-
this.loopState.rafHandle = null;
|
|
2075
|
-
}
|
|
2076
|
-
this.stoppedTime = performance.now();
|
|
2077
|
-
}
|
|
2078
|
-
/**
|
|
2079
|
-
* Request frames to be rendered in demand mode.
|
|
2080
|
-
* Accumulates pending frames (capped at 60) and starts the loop if not running.
|
|
2081
|
-
* No-op if frameloop is not 'demand'.
|
|
2082
|
-
* @param {number} [frames=1] - Number of frames to request
|
|
2083
|
-
* @param {boolean} [stackFrames=false] - Whether to add frames to existing pending count
|
|
2084
|
-
* - `false` (default): Sets pending frames to the specified value (replaces existing count)
|
|
2085
|
-
* - `true`: Adds frames to existing pending count (useful for accumulating invalidations)
|
|
2086
|
-
* @returns {void}
|
|
2087
|
-
* @example
|
|
2088
|
-
* // Request a single frame render
|
|
2089
|
-
* scheduler.invalidate();
|
|
2090
|
-
*
|
|
2091
|
-
* @example
|
|
2092
|
-
* // Request 5 frames (e.g., for animations)
|
|
2093
|
-
* scheduler.invalidate(5);
|
|
2094
|
-
*
|
|
2095
|
-
* @example
|
|
2096
|
-
* // Set pending frames to exactly 3 (don't stack with existing)
|
|
2097
|
-
* scheduler.invalidate(3, false);
|
|
2098
|
-
*
|
|
2099
|
-
* @example
|
|
2100
|
-
* // Add 2 more frames to existing pending count
|
|
2101
|
-
* scheduler.invalidate(2, true);
|
|
2102
|
-
*/
|
|
2103
|
-
invalidate(frames = 1, stackFrames = false) {
|
|
2104
|
-
if (this._frameloop !== "demand") return;
|
|
2105
|
-
const baseFrames = stackFrames ? this.pendingFrames : 0;
|
|
2106
|
-
this.pendingFrames = Math.min(60, baseFrames + frames);
|
|
2107
|
-
if (!this.loopState.running && this.pendingFrames > 0) this.start();
|
|
2108
|
-
}
|
|
2109
|
-
/**
|
|
2110
|
-
* Reset timing state for deterministic testing.
|
|
2111
|
-
* Preserves jobs and roots but resets lastTime, frameCount, elapsedTime, etc.
|
|
2112
|
-
* @returns {void}
|
|
2113
|
-
*/
|
|
2114
|
-
resetTiming() {
|
|
2115
|
-
this.loopState.lastTime = null;
|
|
2116
|
-
this.loopState.frameCount = 0;
|
|
2117
|
-
this.loopState.elapsedTime = 0;
|
|
2118
|
-
this.loopState.createdAt = performance.now();
|
|
2119
|
-
}
|
|
2120
|
-
//* Manual Stepping Methods ================================
|
|
2121
|
-
/**
|
|
2122
|
-
* Manually execute a single frame for all roots.
|
|
2123
|
-
* Useful for frameloop='never' mode or testing scenarios.
|
|
2124
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2125
|
-
* @returns {void}
|
|
2126
|
-
* @example
|
|
2127
|
-
* // Manual control mode
|
|
2128
|
-
* scheduler.frameloop = 'never';
|
|
2129
|
-
* scheduler.step(); // Execute one frame
|
|
2130
|
-
*/
|
|
2131
|
-
step(timestamp) {
|
|
2132
|
-
const now = timestamp ?? performance.now();
|
|
2133
|
-
this.executeFrame(now);
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Manually execute a single job by its ID.
|
|
2137
|
-
* Useful for testing individual job callbacks in isolation.
|
|
2138
|
-
* @param {string} id - The job ID to step
|
|
2139
|
-
* @param {number} [timestamp] - Optional timestamp (defaults to performance.now())
|
|
2140
|
-
* @returns {void}
|
|
2141
|
-
*/
|
|
2142
|
-
stepJob(id, timestamp) {
|
|
2143
|
-
let job;
|
|
2144
|
-
let root;
|
|
2145
|
-
for (const r of this.roots.values()) {
|
|
2146
|
-
job = r.jobs.get(id);
|
|
2147
|
-
if (job) {
|
|
2148
|
-
root = r;
|
|
2149
|
-
break;
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
if (!job || !root) {
|
|
2153
|
-
console.warn(`[Scheduler] Job "${id}" not found`);
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
const now = timestamp ?? performance.now();
|
|
2157
|
-
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
2158
|
-
const delta = deltaMs / 1e3;
|
|
2159
|
-
const elapsed = now - this.loopState.createdAt;
|
|
2160
|
-
const providedState = root.getState?.() ?? {};
|
|
2161
|
-
const frameState = {
|
|
2162
|
-
...providedState,
|
|
2163
|
-
time: now,
|
|
2164
|
-
delta,
|
|
2165
|
-
elapsed,
|
|
2166
|
-
frame: this.loopState.frameCount
|
|
2167
|
-
};
|
|
2168
|
-
try {
|
|
2169
|
-
job.callback(frameState, delta);
|
|
2170
|
-
} catch (error) {
|
|
2171
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2172
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
/**
|
|
2176
|
-
* Execute a single frame across all roots.
|
|
2177
|
-
* Order: globalBefore → each root's jobs → globalAfter
|
|
2178
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2179
|
-
* @returns {void}
|
|
2180
|
-
* @private
|
|
2181
|
-
*/
|
|
2182
|
-
executeFrame(timestamp) {
|
|
2183
|
-
const deltaMs = this.loopState.lastTime !== null ? timestamp - this.loopState.lastTime : 0;
|
|
2184
|
-
const delta = deltaMs / 1e3;
|
|
2185
|
-
this.loopState.lastTime = timestamp;
|
|
2186
|
-
this.loopState.frameCount++;
|
|
2187
|
-
this.loopState.elapsedTime += deltaMs;
|
|
2188
|
-
this.runGlobalJobs(this.globalBeforeJobs, timestamp);
|
|
2189
|
-
for (const root of this.roots.values()) {
|
|
2190
|
-
this.tickRoot(root, timestamp, delta);
|
|
2191
|
-
}
|
|
2192
|
-
this.runGlobalJobs(this.globalAfterJobs, timestamp);
|
|
2193
|
-
}
|
|
2194
|
-
/**
|
|
2195
|
-
* Run all global jobs from a job map.
|
|
2196
|
-
* Catches and logs errors without stopping execution.
|
|
2197
|
-
* @param {Map<string, GlobalJob>} jobs - The global jobs map to execute
|
|
2198
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2199
|
-
* @returns {void}
|
|
2200
|
-
* @private
|
|
2201
|
-
*/
|
|
2202
|
-
runGlobalJobs(jobs, timestamp) {
|
|
2203
|
-
for (const job of jobs.values()) {
|
|
2204
|
-
try {
|
|
2205
|
-
job.callback(timestamp);
|
|
2206
|
-
} catch (error) {
|
|
2207
|
-
console.error(`[Scheduler] Error in global job "${job.id}":`, error);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
/**
|
|
2212
|
-
* Execute all jobs for a single root in sorted order.
|
|
2213
|
-
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
2214
|
-
* Errors are caught and propagated via triggerError.
|
|
2215
|
-
* @param {RootEntry} root - The root entry to tick
|
|
2216
|
-
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
2217
|
-
* @param {number} delta - Time since last frame in seconds
|
|
2218
|
-
* @returns {void}
|
|
2219
|
-
* @private
|
|
2220
|
-
*/
|
|
2221
|
-
tickRoot(root, timestamp, delta) {
|
|
2222
|
-
if (root.needsRebuild) {
|
|
2223
|
-
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
2224
|
-
root.needsRebuild = false;
|
|
2225
|
-
}
|
|
2226
|
-
const providedState = root.getState?.() ?? {};
|
|
2227
|
-
const frameState = {
|
|
2228
|
-
...providedState,
|
|
2229
|
-
time: timestamp,
|
|
2230
|
-
delta,
|
|
2231
|
-
elapsed: this.loopState.elapsedTime / 1e3,
|
|
2232
|
-
// Convert ms to seconds
|
|
2233
|
-
frame: this.loopState.frameCount
|
|
2234
|
-
};
|
|
2235
|
-
for (const job of root.sortedJobs) {
|
|
2236
|
-
if (!shouldRun(job, timestamp)) continue;
|
|
2237
|
-
try {
|
|
2238
|
-
job.callback(frameState, delta);
|
|
2239
|
-
} catch (error) {
|
|
2240
|
-
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2241
|
-
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
//* Debug & Inspection Methods ================================
|
|
2246
|
-
/**
|
|
2247
|
-
* Get the total number of registered jobs across all roots.
|
|
2248
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2249
|
-
* @returns {number} Total job count
|
|
2250
|
-
*/
|
|
2251
|
-
getJobCount() {
|
|
2252
|
-
let count = 0;
|
|
2253
|
-
for (const root of this.roots.values()) {
|
|
2254
|
-
count += root.jobs.size;
|
|
2255
|
-
}
|
|
2256
|
-
return count + this.globalBeforeJobs.size + this.globalAfterJobs.size;
|
|
2257
|
-
}
|
|
2258
|
-
/**
|
|
2259
|
-
* Get all registered job IDs across all roots.
|
|
2260
|
-
* Includes both per-root jobs and global before/after jobs.
|
|
2261
|
-
* @returns {string[]} Array of all job IDs
|
|
2262
|
-
*/
|
|
2263
|
-
getJobIds() {
|
|
2264
|
-
const ids = [];
|
|
2265
|
-
for (const root of this.roots.values()) {
|
|
2266
|
-
ids.push(...root.jobs.keys());
|
|
2267
|
-
}
|
|
2268
|
-
ids.push(...this.globalBeforeJobs.keys());
|
|
2269
|
-
ids.push(...this.globalAfterJobs.keys());
|
|
2270
|
-
return ids;
|
|
2271
|
-
}
|
|
2272
|
-
/**
|
|
2273
|
-
* Get the number of registered roots (Canvas instances).
|
|
2274
|
-
* @returns {number} Number of registered roots
|
|
2275
|
-
*/
|
|
2276
|
-
getRootCount() {
|
|
2277
|
-
return this.roots.size;
|
|
2278
|
-
}
|
|
2279
|
-
/**
|
|
2280
|
-
* Check if any user (non-system) jobs are registered in a specific phase.
|
|
2281
|
-
* Used by the default render job to know if a user has taken over rendering.
|
|
2282
|
-
*
|
|
2283
|
-
* @param phase The phase to check
|
|
2284
|
-
* @param rootId Optional root ID to check (checks all roots if not provided)
|
|
2285
|
-
* @returns true if any user jobs exist in the phase
|
|
2286
|
-
*/
|
|
2287
|
-
hasUserJobsInPhase(phase, rootId) {
|
|
2288
|
-
const rootsToCheck = rootId ? [this.roots.get(rootId)].filter(Boolean) : Array.from(this.roots.values());
|
|
2289
|
-
return rootsToCheck.some((root) => {
|
|
2290
|
-
if (!root) return false;
|
|
2291
|
-
for (const job of root.jobs.values()) {
|
|
2292
|
-
if (job.phase === phase && !job.system && job.enabled) return true;
|
|
2293
|
-
}
|
|
2294
|
-
return false;
|
|
2295
|
-
});
|
|
2296
|
-
}
|
|
2297
|
-
//* Utility Methods ================================
|
|
2298
|
-
/**
|
|
2299
|
-
* Generate a unique root ID for automatic root registration.
|
|
2300
|
-
* @returns {string} A unique root ID in the format 'root_N'
|
|
2301
|
-
*/
|
|
2302
|
-
generateRootId() {
|
|
2303
|
-
return `root_${this.nextRootIndex++}`;
|
|
2304
|
-
}
|
|
2305
|
-
/**
|
|
2306
|
-
* Generate a unique job ID.
|
|
2307
|
-
* @returns {string} A unique job ID in the format 'job_N'
|
|
2308
|
-
* @private
|
|
2309
|
-
*/
|
|
2310
|
-
generateJobId() {
|
|
2311
|
-
return `job_${this.nextJobIndex}`;
|
|
2312
|
-
}
|
|
2313
|
-
/**
|
|
2314
|
-
* Normalize before/after constraints to a Set.
|
|
2315
|
-
* Handles undefined, single string, or array inputs.
|
|
2316
|
-
* @param {string | string[] | undefined} value - The constraint value(s)
|
|
2317
|
-
* @returns {Set<string>} Normalized Set of constraint strings
|
|
2318
|
-
* @private
|
|
2319
|
-
*/
|
|
2320
|
-
normalizeConstraints(value) {
|
|
2321
|
-
if (!value) return /* @__PURE__ */ new Set();
|
|
2322
|
-
if (Array.isArray(value)) return new Set(value);
|
|
2323
|
-
return /* @__PURE__ */ new Set([value]);
|
|
2324
|
-
}
|
|
2325
|
-
};
|
|
2326
|
-
//* Static State & Methods (Singleton Usage) ================================
|
|
2327
|
-
//* Cross-Bundle Singleton Key ==============================
|
|
2328
|
-
// Use Symbol.for() to ensure scheduler is shared across bundle boundaries
|
|
2329
|
-
// This prevents issues when mixing imports from @react-three/fiber and @react-three/fiber/webgpu
|
|
2330
|
-
__publicField(_Scheduler, "INSTANCE_KEY", Symbol.for("@react-three/fiber.scheduler"));
|
|
2331
|
-
let Scheduler = _Scheduler;
|
|
2332
|
-
const getScheduler = () => Scheduler.get();
|
|
2333
|
-
if (hmrData) {
|
|
2334
|
-
hmrData.accept?.();
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function useFrame(callback, priorityOrOptions) {
|
|
2338
|
-
const store = React.useContext(context);
|
|
2339
|
-
const isInsideCanvas = store !== null;
|
|
2340
|
-
const scheduler = getScheduler();
|
|
2341
|
-
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2342
|
-
id: priorityOrOptions.id,
|
|
2343
|
-
phase: priorityOrOptions.phase,
|
|
2344
|
-
priority: priorityOrOptions.priority,
|
|
2345
|
-
fps: priorityOrOptions.fps,
|
|
2346
|
-
drop: priorityOrOptions.drop,
|
|
2347
|
-
enabled: priorityOrOptions.enabled,
|
|
2348
|
-
before: priorityOrOptions.before,
|
|
2349
|
-
after: priorityOrOptions.after
|
|
2350
|
-
}) : "";
|
|
2351
|
-
const options = React.useMemo(() => {
|
|
2352
|
-
return typeof priorityOrOptions === "number" ? { priority: priorityOrOptions } : priorityOrOptions ?? {};
|
|
2353
|
-
}, [optionsKey]);
|
|
2354
|
-
const reactId = React.useId();
|
|
2355
|
-
const id = options.id ?? reactId;
|
|
2356
|
-
const callbackRef = useMutableCallback(callback);
|
|
2357
|
-
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2358
|
-
useIsomorphicLayoutEffect(() => {
|
|
2359
|
-
if (!callback) return;
|
|
2360
|
-
if (isInsideCanvas) {
|
|
2361
|
-
const state = store.getState();
|
|
2362
|
-
const rootId = state.internal.rootId;
|
|
2363
|
-
if (isLegacyPriority) {
|
|
2364
|
-
state.internal.priority++;
|
|
2365
|
-
let parentRoot = state.previousRoot;
|
|
2366
|
-
while (parentRoot) {
|
|
2367
|
-
const parentState = parentRoot.getState();
|
|
2368
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2369
|
-
parentRoot = parentState?.previousRoot;
|
|
2370
|
-
}
|
|
2371
|
-
notifyDepreciated({
|
|
2372
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2373
|
-
body: 'Using useFrame(callback, number) to control render order is deprecated.\n\nFor custom rendering, use: useFrame(callback, { phase: "render" })\nFor execution order within update phase, use: useFrame(callback, { priority: number })',
|
|
2374
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2375
|
-
});
|
|
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
|
+
});
|
|
2376
2050
|
}
|
|
2377
2051
|
const wrappedCallback = (frameState, delta) => {
|
|
2378
2052
|
const localState = store.getState();
|
|
@@ -2406,20 +2080,14 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2406
2080
|
}
|
|
2407
2081
|
};
|
|
2408
2082
|
} else {
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
unregisterJob = registerOutside();
|
|
2418
|
-
});
|
|
2419
|
-
return () => {
|
|
2420
|
-
unsubReady();
|
|
2421
|
-
unregisterJob?.();
|
|
2422
|
-
};
|
|
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
|
+
);
|
|
2423
2091
|
}
|
|
2424
2092
|
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2425
2093
|
const isPaused = React.useSyncExternalStore(
|
|
@@ -2510,22 +2178,29 @@ function buildFromCache(input, textureCache) {
|
|
|
2510
2178
|
function useTexture(input, optionsOrOnLoad) {
|
|
2511
2179
|
const renderer = useThree((state) => state.internal.actualRenderer);
|
|
2512
2180
|
const store = useStore();
|
|
2513
|
-
const textureCache = useThree((state) => state.textures);
|
|
2514
2181
|
const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
|
|
2515
|
-
const { onLoad, cache =
|
|
2182
|
+
const { onLoad, cache = true } = options;
|
|
2183
|
+
const onLoadRef = useRef(onLoad);
|
|
2184
|
+
onLoadRef.current = onLoad;
|
|
2185
|
+
const onLoadCalledForRef = useRef(null);
|
|
2516
2186
|
const urls = useMemo(() => getUrls(input), [input]);
|
|
2517
2187
|
const cachedResult = useMemo(() => {
|
|
2518
2188
|
if (!cache) return null;
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2189
|
+
const textures = store.getState().textures;
|
|
2190
|
+
if (!allUrlsCached(urls, textures)) return null;
|
|
2191
|
+
return buildFromCache(input, textures);
|
|
2192
|
+
}, [cache, urls, input, store]);
|
|
2522
2193
|
const loadedTextures = useLoader(
|
|
2523
2194
|
TextureLoader,
|
|
2524
2195
|
IsObject(input) ? Object.values(input) : input
|
|
2525
2196
|
);
|
|
2197
|
+
const inputKey = urls.join("\0");
|
|
2526
2198
|
useLayoutEffect(() => {
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2199
|
+
if (cachedResult) return;
|
|
2200
|
+
if (onLoadCalledForRef.current === inputKey) return;
|
|
2201
|
+
onLoadCalledForRef.current = inputKey;
|
|
2202
|
+
onLoadRef.current?.(loadedTextures);
|
|
2203
|
+
}, [cachedResult, loadedTextures, inputKey]);
|
|
2529
2204
|
useEffect(() => {
|
|
2530
2205
|
if (cachedResult) return;
|
|
2531
2206
|
if ("initTexture" in renderer) {
|
|
@@ -2558,8 +2233,6 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2558
2233
|
}, [input, loadedTextures, cachedResult]);
|
|
2559
2234
|
useEffect(() => {
|
|
2560
2235
|
if (!cache) return;
|
|
2561
|
-
if (cachedResult) return;
|
|
2562
|
-
const set = store.setState;
|
|
2563
2236
|
const urlTextureMap = [];
|
|
2564
2237
|
if (typeof input === "string") {
|
|
2565
2238
|
urlTextureMap.push([input, mappedTextures]);
|
|
@@ -2573,18 +2246,32 @@ function useTexture(input, optionsOrOnLoad) {
|
|
|
2573
2246
|
urlTextureMap.push([url, textureRecord[key]]);
|
|
2574
2247
|
}
|
|
2575
2248
|
}
|
|
2576
|
-
|
|
2577
|
-
const
|
|
2578
|
-
let
|
|
2249
|
+
store.setState((state) => {
|
|
2250
|
+
const refs = new Map(state._textureRefs);
|
|
2251
|
+
let textures = state.textures;
|
|
2252
|
+
let added = false;
|
|
2579
2253
|
for (const [url, texture] of urlTextureMap) {
|
|
2580
|
-
if (!
|
|
2581
|
-
|
|
2582
|
-
|
|
2254
|
+
if (!textures.has(url)) {
|
|
2255
|
+
if (!added) {
|
|
2256
|
+
textures = new Map(textures);
|
|
2257
|
+
added = true;
|
|
2258
|
+
}
|
|
2259
|
+
textures.set(url, texture);
|
|
2583
2260
|
}
|
|
2261
|
+
refs.set(url, (refs.get(url) ?? 0) + 1);
|
|
2584
2262
|
}
|
|
2585
|
-
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 };
|
|
2586
2273
|
});
|
|
2587
|
-
}, [cache, input, mappedTextures, store
|
|
2274
|
+
}, [cache, input, mappedTextures, store]);
|
|
2588
2275
|
return mappedTextures;
|
|
2589
2276
|
}
|
|
2590
2277
|
useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
|
|
@@ -2600,106 +2287,90 @@ const Texture = ({
|
|
|
2600
2287
|
return /* @__PURE__ */ jsx(Fragment, { children: children?.(ret) });
|
|
2601
2288
|
};
|
|
2602
2289
|
|
|
2603
|
-
function
|
|
2604
|
-
if (entry instanceof Texture$1) return entry;
|
|
2605
|
-
if (entry && typeof entry === "object" && "value" in entry && entry.value instanceof Texture$1) {
|
|
2606
|
-
return entry.value;
|
|
2607
|
-
}
|
|
2608
|
-
return null;
|
|
2609
|
-
}
|
|
2610
|
-
function useTextures() {
|
|
2290
|
+
function useTextures(selector) {
|
|
2611
2291
|
const store = useStore();
|
|
2612
|
-
|
|
2613
|
-
const set = store.setState;
|
|
2292
|
+
const registry = useMemo(() => {
|
|
2614
2293
|
const getState = store.getState;
|
|
2615
|
-
const
|
|
2616
|
-
|
|
2617
|
-
const newMap = new Map(state.textures);
|
|
2618
|
-
newMap.set(key, value);
|
|
2619
|
-
return { textures: newMap };
|
|
2620
|
-
});
|
|
2621
|
-
};
|
|
2622
|
-
const addMultiple = (items) => {
|
|
2623
|
-
set((state) => {
|
|
2624
|
-
const newMap = new Map(state.textures);
|
|
2625
|
-
const entries = items instanceof Map ? items.entries() : Object.entries(items);
|
|
2626
|
-
for (const [key, value] of entries) {
|
|
2627
|
-
newMap.set(key, value);
|
|
2628
|
-
}
|
|
2629
|
-
return { textures: newMap };
|
|
2630
|
-
});
|
|
2631
|
-
};
|
|
2632
|
-
const remove = (key) => {
|
|
2633
|
-
set((state) => {
|
|
2634
|
-
const newMap = new Map(state.textures);
|
|
2635
|
-
newMap.delete(key);
|
|
2636
|
-
return { textures: newMap };
|
|
2637
|
-
});
|
|
2638
|
-
};
|
|
2639
|
-
const removeMultiple = (keys) => {
|
|
2640
|
-
set((state) => {
|
|
2641
|
-
const newMap = new Map(state.textures);
|
|
2642
|
-
for (const key of keys) newMap.delete(key);
|
|
2643
|
-
return { textures: newMap };
|
|
2644
|
-
});
|
|
2645
|
-
};
|
|
2646
|
-
const dispose = (key) => {
|
|
2647
|
-
const entry = getState().textures.get(key);
|
|
2648
|
-
if (entry) {
|
|
2649
|
-
const tex = getTextureValue(entry);
|
|
2650
|
-
tex?.dispose();
|
|
2651
|
-
}
|
|
2652
|
-
remove(key);
|
|
2653
|
-
};
|
|
2654
|
-
const disposeMultiple = (keys) => {
|
|
2655
|
-
const textures = getState().textures;
|
|
2656
|
-
for (const key of keys) {
|
|
2657
|
-
const entry = textures.get(key);
|
|
2658
|
-
if (entry) {
|
|
2659
|
-
const tex = getTextureValue(entry);
|
|
2660
|
-
tex?.dispose();
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
removeMultiple(keys);
|
|
2664
|
-
};
|
|
2665
|
-
const disposeAll = () => {
|
|
2666
|
-
const textures = getState().textures;
|
|
2667
|
-
for (const entry of textures.values()) {
|
|
2668
|
-
const tex = getTextureValue(entry);
|
|
2669
|
-
tex?.dispose();
|
|
2670
|
-
}
|
|
2671
|
-
set({ textures: /* @__PURE__ */ new Map() });
|
|
2672
|
-
};
|
|
2294
|
+
const setState = store.setState;
|
|
2295
|
+
const getOne = (key) => getState().textures.get(key);
|
|
2673
2296
|
return {
|
|
2674
|
-
|
|
2675
|
-
get textures() {
|
|
2297
|
+
get all() {
|
|
2676
2298
|
return getState().textures;
|
|
2677
2299
|
},
|
|
2678
|
-
|
|
2679
|
-
|
|
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
|
+
},
|
|
2680
2307
|
has: (key) => getState().textures.has(key),
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
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
|
+
}
|
|
2691
2342
|
};
|
|
2692
2343
|
}, [store]);
|
|
2344
|
+
const subscribe = selector ? () => selector(registry) : (state) => state.textures;
|
|
2345
|
+
const selected = useThree(subscribe);
|
|
2346
|
+
return selector ? selected : registry;
|
|
2693
2347
|
}
|
|
2694
2348
|
|
|
2695
|
-
function useRenderTarget(
|
|
2349
|
+
function useRenderTarget(widthOrOptions, heightOrOptions, options) {
|
|
2696
2350
|
const isLegacy = useThree((s) => s.isLegacy);
|
|
2697
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
|
+
}
|
|
2698
2369
|
return useMemo(() => {
|
|
2699
2370
|
const w = width ?? size.width;
|
|
2700
2371
|
const h = height ?? size.height;
|
|
2701
|
-
return new RenderTarget(w, h,
|
|
2702
|
-
}, [width, height, size.width, size.height,
|
|
2372
|
+
return new RenderTarget(w, h, opts);
|
|
2373
|
+
}, [width, height, size.width, size.height, opts, isLegacy]);
|
|
2703
2374
|
}
|
|
2704
2375
|
|
|
2705
2376
|
function useStore() {
|
|
@@ -2749,28 +2420,18 @@ function addTail(callback) {
|
|
|
2749
2420
|
function invalidate(state, frames = 1, stackFrames = false) {
|
|
2750
2421
|
getScheduler().invalidate(frames, stackFrames);
|
|
2751
2422
|
}
|
|
2752
|
-
function advance(timestamp
|
|
2423
|
+
function advance(timestamp) {
|
|
2753
2424
|
getScheduler().step(timestamp);
|
|
2754
2425
|
}
|
|
2755
2426
|
|
|
2756
|
-
const version = "10.0.0-alpha.
|
|
2427
|
+
const version = "10.0.0-alpha.3";
|
|
2757
2428
|
const packageData = {
|
|
2758
2429
|
version: version};
|
|
2759
2430
|
|
|
2760
2431
|
function Xb(Tt) {
|
|
2761
2432
|
return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
|
|
2762
2433
|
}
|
|
2763
|
-
var Rm = { exports: {} }, Og = { exports: {} };
|
|
2764
|
-
/**
|
|
2765
|
-
* @license React
|
|
2766
|
-
* react-reconciler.production.js
|
|
2767
|
-
*
|
|
2768
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2769
|
-
*
|
|
2770
|
-
* This source code is licensed under the MIT license found in the
|
|
2771
|
-
* LICENSE file in the root directory of this source tree.
|
|
2772
|
-
*/
|
|
2773
|
-
var _b;
|
|
2434
|
+
var Rm = { exports: {} }, Og = { exports: {} }, _b;
|
|
2774
2435
|
function Kb() {
|
|
2775
2436
|
return _b || (_b = 1, (function(Tt) {
|
|
2776
2437
|
Tt.exports = function(m) {
|
|
@@ -3842,7 +3503,6 @@ Error generating stack: ` + l.message + `
|
|
|
3842
3503
|
if (J === cl || J === jc) throw J;
|
|
3843
3504
|
var Ge = Yn(29, J, null, P.mode);
|
|
3844
3505
|
return Ge.lanes = H, Ge.return = P, Ge;
|
|
3845
|
-
} finally {
|
|
3846
3506
|
}
|
|
3847
3507
|
};
|
|
3848
3508
|
}
|
|
@@ -4496,7 +4156,6 @@ Error generating stack: ` + l.message + `
|
|
|
4496
4156
|
var h = r.lastRenderedState, y = d(h, a);
|
|
4497
4157
|
if (c.hasEagerState = true, c.eagerState = y, jn(y, h)) return go(t, r, c, 0), Ne === null && Bn(), false;
|
|
4498
4158
|
} catch {
|
|
4499
|
-
} finally {
|
|
4500
4159
|
}
|
|
4501
4160
|
if (a = yo(t, r, c, l), a !== null) return nt(a, t, l), ns(a, r, l), true;
|
|
4502
4161
|
}
|
|
@@ -6917,10 +6576,7 @@ Error generating stack: ` + l.message + `
|
|
|
6917
6576
|
function vr(t, r) {
|
|
6918
6577
|
Sf(t, r), (t = t.alternate) && Sf(t, r);
|
|
6919
6578
|
}
|
|
6920
|
-
var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy");
|
|
6921
|
-
var gc = Symbol.for("react.activity");
|
|
6922
|
-
var $r = Symbol.for("react.memo_cache_sentinel");
|
|
6923
|
-
var Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6579
|
+
var ie = {}, Fm = React__default, tt = Tb, Lt = Object.assign, hc = Symbol.for("react.element"), zs = Symbol.for("react.transitional.element"), sa = Symbol.for("react.portal"), $a = Symbol.for("react.fragment"), kf = Symbol.for("react.strict_mode"), Cs = Symbol.for("react.profiler"), mc = Symbol.for("react.consumer"), Io = Symbol.for("react.context"), Zi = Symbol.for("react.forward_ref"), Va = Symbol.for("react.suspense"), Te = Symbol.for("react.suspense_list"), wf = Symbol.for("react.memo"), ua = Symbol.for("react.lazy"), gc = Symbol.for("react.activity"), $r = Symbol.for("react.memo_cache_sentinel"), Pf = Symbol.iterator, xf = Symbol.for("react.client.reference"), ca = Array.isArray, M = Fm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Yp = m.rendererVersion, zf = m.rendererPackageName, Cf = m.extraDevToolsConfig, Ts = m.getPublicInstance, Hm = m.getRootHostContext, Xp = m.getChildHostContext, Am = m.prepareForCommit, _s = m.resetAfterCommit, Vr = m.createInstance;
|
|
6924
6580
|
m.cloneMutableInstance;
|
|
6925
6581
|
var yc = m.appendInitialChild, Kp = m.finalizeInitialChildren, Rs = m.shouldSetTextContent, bc = m.createTextInstance;
|
|
6926
6582
|
m.cloneMutableTextInstance;
|
|
@@ -7289,17 +6945,7 @@ No matching component was found for:
|
|
|
7289
6945
|
}, Tt.exports.default = Tt.exports, Object.defineProperty(Tt.exports, "__esModule", { value: true });
|
|
7290
6946
|
})(Og)), Og.exports;
|
|
7291
6947
|
}
|
|
7292
|
-
var Mg = { exports: {} };
|
|
7293
|
-
/**
|
|
7294
|
-
* @license React
|
|
7295
|
-
* react-reconciler.development.js
|
|
7296
|
-
*
|
|
7297
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
7298
|
-
*
|
|
7299
|
-
* This source code is licensed under the MIT license found in the
|
|
7300
|
-
* LICENSE file in the root directory of this source tree.
|
|
7301
|
-
*/
|
|
7302
|
-
var Rb;
|
|
6948
|
+
var Mg = { exports: {} }, Rb;
|
|
7303
6949
|
function e0() {
|
|
7304
6950
|
return Rb || (Rb = 1, (function(Tt) {
|
|
7305
6951
|
process.env.NODE_ENV !== "production" && (Tt.exports = function(m) {
|
|
@@ -13066,10 +12712,7 @@ Check the render method of %s.`, G(di) || "Unknown")), i = zo(n), i.payload = {
|
|
|
13066
12712
|
function Ic() {
|
|
13067
12713
|
return di;
|
|
13068
12714
|
}
|
|
13069
|
-
var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy");
|
|
13070
|
-
var Ds = Symbol.for("react.activity");
|
|
13071
|
-
var Bh = Symbol.for("react.memo_cache_sentinel");
|
|
13072
|
-
var ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
12715
|
+
var le = {}, qm = React__default, St = Tb, ze = Object.assign, Uh = Symbol.for("react.element"), Ho = Symbol.for("react.transitional.element"), Ao = Symbol.for("react.portal"), ol = Symbol.for("react.fragment"), Lc = Symbol.for("react.strict_mode"), Uf = Symbol.for("react.profiler"), ei = Symbol.for("react.consumer"), on = Symbol.for("react.context"), jn = Symbol.for("react.forward_ref"), Nc = Symbol.for("react.suspense"), Bf = Symbol.for("react.suspense_list"), al = Symbol.for("react.memo"), kt = Symbol.for("react.lazy"), Ds = Symbol.for("react.activity"), Bh = Symbol.for("react.memo_cache_sentinel"), ni = Symbol.iterator, il = Symbol.for("react.client.reference"), fn = Array.isArray, x = qm.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, Jt = m.rendererVersion, Zt = m.rendererPackageName, jo = m.extraDevToolsConfig, ot = m.getPublicInstance, Zr = m.getRootHostContext, Dn = m.getChildHostContext, Ws = m.prepareForCommit, pa = m.resetAfterCommit, Fc = m.createInstance;
|
|
13073
12716
|
m.cloneMutableInstance;
|
|
13074
12717
|
var bn = m.appendInitialChild, Ue = m.finalizeInitialChildren, ue = m.shouldSetTextContent, Do = m.createTextInstance;
|
|
13075
12718
|
m.cloneMutableTextInstance;
|
|
@@ -14037,15 +13680,6 @@ function n0() {
|
|
|
14037
13680
|
var t0 = n0();
|
|
14038
13681
|
const r0 = Xb(t0);
|
|
14039
13682
|
|
|
14040
|
-
/**
|
|
14041
|
-
* @license React
|
|
14042
|
-
* react-reconciler-constants.production.js
|
|
14043
|
-
*
|
|
14044
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14045
|
-
*
|
|
14046
|
-
* This source code is licensed under the MIT license found in the
|
|
14047
|
-
* LICENSE file in the root directory of this source tree.
|
|
14048
|
-
*/
|
|
14049
13683
|
const t = 1, o = 8, r = 32, e = 2;
|
|
14050
13684
|
|
|
14051
13685
|
function createReconciler(config) {
|
|
@@ -14072,10 +13706,11 @@ function extend(objects) {
|
|
|
14072
13706
|
function validateInstance(type, props) {
|
|
14073
13707
|
const name = toPascalCase(type);
|
|
14074
13708
|
const target = catalogue[name];
|
|
14075
|
-
if (type !== "primitive" && !target)
|
|
13709
|
+
if (type !== "primitive" && !target) {
|
|
14076
13710
|
throw new Error(
|
|
14077
13711
|
`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`
|
|
14078
13712
|
);
|
|
13713
|
+
}
|
|
14079
13714
|
if (type === "primitive" && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
|
|
14080
13715
|
if (props.args !== void 0 && !Array.isArray(props.args)) throw new Error("R3F: The args prop must be an array!");
|
|
14081
13716
|
}
|
|
@@ -14228,6 +13863,7 @@ function swapInstances() {
|
|
|
14228
13863
|
}
|
|
14229
13864
|
}
|
|
14230
13865
|
if (instance.isHidden) unhideInstance(instance);
|
|
13866
|
+
if (isObject3D(instance.object)) removeInteractivity(findInitialRoot(instance), instance.object);
|
|
14231
13867
|
if (instance.object.__r3f) delete instance.object.__r3f;
|
|
14232
13868
|
if (instance.type !== "primitive") disposeOnIdle(instance.object);
|
|
14233
13869
|
}
|
|
@@ -14239,6 +13875,9 @@ function swapInstances() {
|
|
|
14239
13875
|
instance.object = instance.props.object ?? new target(...instance.props.args ?? []);
|
|
14240
13876
|
instance.object.__r3f = instance;
|
|
14241
13877
|
setFiberRef(fiber, instance.object);
|
|
13878
|
+
delete instance.appliedOnce;
|
|
13879
|
+
instance.eventCount = 0;
|
|
13880
|
+
instance.handlers = {};
|
|
14242
13881
|
applyProps(instance.object, instance.props);
|
|
14243
13882
|
if (instance.props.attach) {
|
|
14244
13883
|
attach(parent, instance);
|
|
@@ -14312,8 +13951,22 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14312
13951
|
const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
|
|
14313
13952
|
if (isTailSibling) swapInstances();
|
|
14314
13953
|
},
|
|
14315
|
-
finalizeInitialChildren: () =>
|
|
14316
|
-
|
|
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);
|
|
14317
13970
|
},
|
|
14318
13971
|
getPublicInstance: (instance) => instance?.object,
|
|
14319
13972
|
prepareForCommit: () => null,
|
|
@@ -14440,11 +14093,15 @@ const reconciler = /* @__PURE__ */ createReconciler({
|
|
|
14440
14093
|
cloneMutableTextInstance(textInstance) {
|
|
14441
14094
|
return textInstance;
|
|
14442
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.
|
|
14443
14102
|
cloneRootViewTransitionContainer(_rootContainer) {
|
|
14444
|
-
throw new Error("Not implemented.");
|
|
14445
14103
|
},
|
|
14446
14104
|
removeRootViewTransitionClone(_rootContainer, _clone) {
|
|
14447
|
-
throw new Error("Not implemented.");
|
|
14448
14105
|
},
|
|
14449
14106
|
// https://github.com/facebook/react/pull/32465
|
|
14450
14107
|
createFragmentInstance: (_fiber) => null,
|
|
@@ -14526,14 +14183,18 @@ function createRoot(canvas) {
|
|
|
14526
14183
|
if (!prevRoot) _roots.set(canvas, { fiber, store });
|
|
14527
14184
|
let onCreated;
|
|
14528
14185
|
let lastCamera;
|
|
14529
|
-
|
|
14186
|
+
const lastConfiguredProps = {};
|
|
14530
14187
|
let configured = false;
|
|
14531
14188
|
let pending = null;
|
|
14189
|
+
let rendererSetup = null;
|
|
14532
14190
|
return {
|
|
14533
14191
|
async configure(props = {}) {
|
|
14534
14192
|
let resolve;
|
|
14535
14193
|
pending = new Promise((_resolve) => resolve = _resolve);
|
|
14536
|
-
|
|
14194
|
+
const {
|
|
14195
|
+
id: canvasId,
|
|
14196
|
+
primaryCanvas,
|
|
14197
|
+
scheduler: schedulerConfig,
|
|
14537
14198
|
gl: glConfig,
|
|
14538
14199
|
renderer: rendererConfig,
|
|
14539
14200
|
size: propsSize,
|
|
@@ -14541,10 +14202,6 @@ function createRoot(canvas) {
|
|
|
14541
14202
|
events,
|
|
14542
14203
|
onCreated: onCreatedCallback,
|
|
14543
14204
|
shadows = false,
|
|
14544
|
-
linear = false,
|
|
14545
|
-
flat = false,
|
|
14546
|
-
textureColorSpace = SRGBColorSpace,
|
|
14547
|
-
legacy = false,
|
|
14548
14205
|
orthographic = false,
|
|
14549
14206
|
frameloop = "always",
|
|
14550
14207
|
dpr = [1, 2],
|
|
@@ -14555,11 +14212,15 @@ function createRoot(canvas) {
|
|
|
14555
14212
|
onDragOverMissed,
|
|
14556
14213
|
onDropMissed,
|
|
14557
14214
|
autoUpdateFrustum = true,
|
|
14558
|
-
occlusion = false
|
|
14215
|
+
occlusion = false,
|
|
14216
|
+
_sizeProps,
|
|
14217
|
+
forceEven
|
|
14559
14218
|
} = props;
|
|
14219
|
+
const textureColorSpace = is.obj(glConfig) && !is.fun(glConfig) && !isRenderer(glConfig) && glConfig.textureColorSpace || is.obj(rendererConfig) && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && rendererConfig.textureColorSpace || SRGBColorSpace;
|
|
14560
14220
|
let state = store.getState();
|
|
14561
14221
|
const defaultGPUProps = {
|
|
14562
|
-
canvas
|
|
14222
|
+
canvas,
|
|
14223
|
+
antialias: true
|
|
14563
14224
|
};
|
|
14564
14225
|
if (glConfig && !R3F_BUILD_LEGACY) {
|
|
14565
14226
|
throw new Error(
|
|
@@ -14571,21 +14232,72 @@ function createRoot(canvas) {
|
|
|
14571
14232
|
}
|
|
14572
14233
|
let renderer = state.internal.actualRenderer;
|
|
14573
14234
|
if (!state.internal.actualRenderer) {
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
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
|
+
});
|
|
14577
14288
|
}
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
state.internal.actualRenderer
|
|
14581
|
-
state.set({ webGPUSupported: isWebGPUBackend, renderer });
|
|
14289
|
+
await rendererSetup;
|
|
14290
|
+
state = store.getState();
|
|
14291
|
+
renderer = state.internal.actualRenderer;
|
|
14582
14292
|
}
|
|
14583
14293
|
let raycaster = state.raycaster;
|
|
14584
14294
|
if (!raycaster) state.set({ raycaster: raycaster = new Raycaster() });
|
|
14585
14295
|
const { params, ...options } = raycastOptions || {};
|
|
14586
14296
|
if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options });
|
|
14587
|
-
if (!is.equ(params, raycaster.params, shallowLoose))
|
|
14297
|
+
if (!is.equ(params, raycaster.params, shallowLoose)) {
|
|
14588
14298
|
applyProps(raycaster, { params: { ...raycaster.params, ...params } });
|
|
14299
|
+
}
|
|
14300
|
+
let tempCamera = state.camera;
|
|
14589
14301
|
if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
|
|
14590
14302
|
lastCamera = cameraOptions;
|
|
14591
14303
|
const isCamera = cameraOptions?.isCamera;
|
|
@@ -14605,6 +14317,7 @@ function createRoot(canvas) {
|
|
|
14605
14317
|
if (!state.camera && !cameraOptions?.rotation) camera.lookAt(0, 0, 0);
|
|
14606
14318
|
}
|
|
14607
14319
|
state.set({ camera });
|
|
14320
|
+
tempCamera = camera;
|
|
14608
14321
|
raycaster.camera = camera;
|
|
14609
14322
|
}
|
|
14610
14323
|
if (!state.scene) {
|
|
@@ -14622,7 +14335,7 @@ function createRoot(canvas) {
|
|
|
14622
14335
|
rootScene: scene,
|
|
14623
14336
|
internal: { ...prev.internal, container: scene }
|
|
14624
14337
|
}));
|
|
14625
|
-
const camera =
|
|
14338
|
+
const camera = tempCamera;
|
|
14626
14339
|
if (camera && !camera.parent) scene.add(camera);
|
|
14627
14340
|
}
|
|
14628
14341
|
if (events && !state.events.handlers) {
|
|
@@ -14636,9 +14349,17 @@ function createRoot(canvas) {
|
|
|
14636
14349
|
wasEnabled = enabled;
|
|
14637
14350
|
});
|
|
14638
14351
|
}
|
|
14352
|
+
if (_sizeProps !== void 0) {
|
|
14353
|
+
state.set({ _sizeProps });
|
|
14354
|
+
}
|
|
14355
|
+
if (forceEven !== void 0 && state.internal.forceEven !== forceEven) {
|
|
14356
|
+
state.set((prev) => ({ internal: { ...prev.internal, forceEven } }));
|
|
14357
|
+
}
|
|
14639
14358
|
const size = computeInitialSize(canvas, propsSize);
|
|
14640
|
-
if (!is.equ(size, state.size, shallowLoose)) {
|
|
14359
|
+
if (!state._sizeImperative && !is.equ(size, state.size, shallowLoose)) {
|
|
14360
|
+
const wasImperative = state._sizeImperative;
|
|
14641
14361
|
state.setSize(size.width, size.height, size.top, size.left);
|
|
14362
|
+
if (!wasImperative) state.set({ _sizeImperative: false });
|
|
14642
14363
|
}
|
|
14643
14364
|
if (dpr !== void 0 && !is.equ(dpr, lastConfiguredProps.dpr, shallowLoose)) {
|
|
14644
14365
|
state.setDpr(dpr);
|
|
@@ -14660,10 +14381,10 @@ function createRoot(canvas) {
|
|
|
14660
14381
|
lastConfiguredProps.performance = performance;
|
|
14661
14382
|
}
|
|
14662
14383
|
if (!state.xr) {
|
|
14663
|
-
const handleXRFrame = (timestamp,
|
|
14384
|
+
const handleXRFrame = (timestamp, _frame) => {
|
|
14664
14385
|
const state2 = store.getState();
|
|
14665
14386
|
if (state2.frameloop === "never") return;
|
|
14666
|
-
advance(timestamp
|
|
14387
|
+
advance(timestamp);
|
|
14667
14388
|
};
|
|
14668
14389
|
const actualRenderer = state.internal.actualRenderer;
|
|
14669
14390
|
const handleSessionChange = () => {
|
|
@@ -14675,16 +14396,16 @@ function createRoot(canvas) {
|
|
|
14675
14396
|
};
|
|
14676
14397
|
const xr = {
|
|
14677
14398
|
connect() {
|
|
14678
|
-
const { gl, renderer: renderer2
|
|
14679
|
-
const
|
|
14680
|
-
|
|
14681
|
-
|
|
14399
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14400
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14401
|
+
xrManager.addEventListener("sessionstart", handleSessionChange);
|
|
14402
|
+
xrManager.addEventListener("sessionend", handleSessionChange);
|
|
14682
14403
|
},
|
|
14683
14404
|
disconnect() {
|
|
14684
|
-
const { gl, renderer: renderer2
|
|
14685
|
-
const
|
|
14686
|
-
|
|
14687
|
-
|
|
14405
|
+
const { gl, renderer: renderer2 } = store.getState();
|
|
14406
|
+
const xrManager = (renderer2 || gl).xr;
|
|
14407
|
+
xrManager.removeEventListener("sessionstart", handleSessionChange);
|
|
14408
|
+
xrManager.removeEventListener("sessionend", handleSessionChange);
|
|
14688
14409
|
}
|
|
14689
14410
|
};
|
|
14690
14411
|
if (typeof renderer.xr?.addEventListener === "function") xr.connect();
|
|
@@ -14696,41 +14417,92 @@ function createRoot(canvas) {
|
|
|
14696
14417
|
const oldType = renderer.shadowMap.type;
|
|
14697
14418
|
renderer.shadowMap.enabled = !!shadows;
|
|
14698
14419
|
if (is.boo(shadows)) {
|
|
14699
|
-
renderer.shadowMap.type =
|
|
14420
|
+
renderer.shadowMap.type = PCFShadowMap;
|
|
14700
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
|
+
}
|
|
14701
14429
|
const types = {
|
|
14702
14430
|
basic: BasicShadowMap,
|
|
14703
14431
|
percentage: PCFShadowMap,
|
|
14704
|
-
soft:
|
|
14432
|
+
soft: PCFShadowMap,
|
|
14705
14433
|
variance: VSMShadowMap
|
|
14706
14434
|
};
|
|
14707
|
-
renderer.shadowMap.type = types[shadows] ??
|
|
14435
|
+
renderer.shadowMap.type = types[shadows] ?? PCFShadowMap;
|
|
14708
14436
|
} else if (is.obj(shadows)) {
|
|
14709
14437
|
Object.assign(renderer.shadowMap, shadows);
|
|
14710
14438
|
}
|
|
14711
|
-
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type)
|
|
14439
|
+
if (oldEnabled !== renderer.shadowMap.enabled || oldType !== renderer.shadowMap.type) {
|
|
14712
14440
|
renderer.shadowMap.needsUpdate = true;
|
|
14441
|
+
}
|
|
14442
|
+
}
|
|
14443
|
+
if (!configured) {
|
|
14444
|
+
renderer.outputColorSpace = SRGBColorSpace;
|
|
14445
|
+
renderer.toneMapping = ACESFilmicToneMapping;
|
|
14713
14446
|
}
|
|
14714
14447
|
if (textureColorSpace !== lastConfiguredProps.textureColorSpace) {
|
|
14715
14448
|
if (state.textureColorSpace !== textureColorSpace) state.set(() => ({ textureColorSpace }));
|
|
14716
14449
|
lastConfiguredProps.textureColorSpace = textureColorSpace;
|
|
14717
14450
|
}
|
|
14718
|
-
|
|
14719
|
-
|
|
14451
|
+
const r3fProps = ["textureColorSpace"];
|
|
14452
|
+
const constructorOnlyProps = ["samples", "antialias", "alpha", "canvas", "powerPreference"];
|
|
14453
|
+
const nonApplyProps = [...r3fProps, ...constructorOnlyProps];
|
|
14454
|
+
if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, renderer, shallowLoose)) {
|
|
14455
|
+
const glProps = {};
|
|
14456
|
+
for (const key in glConfig) {
|
|
14457
|
+
if (!nonApplyProps.includes(key)) glProps[key] = glConfig[key];
|
|
14458
|
+
}
|
|
14459
|
+
applyProps(renderer, glProps);
|
|
14460
|
+
}
|
|
14720
14461
|
if (rendererConfig && !is.fun(rendererConfig) && !isRenderer(rendererConfig) && state.renderer) {
|
|
14721
14462
|
const currentRenderer = state.renderer;
|
|
14722
14463
|
if (!is.equ(rendererConfig, currentRenderer, shallowLoose)) {
|
|
14723
|
-
|
|
14464
|
+
const rendererProps = {};
|
|
14465
|
+
for (const key in rendererConfig) {
|
|
14466
|
+
if (!nonApplyProps.includes(key)) rendererProps[key] = rendererConfig[key];
|
|
14467
|
+
}
|
|
14468
|
+
applyProps(currentRenderer, rendererProps);
|
|
14724
14469
|
}
|
|
14725
14470
|
}
|
|
14726
14471
|
const scheduler = getScheduler();
|
|
14727
14472
|
const rootId = state.internal.rootId;
|
|
14728
14473
|
if (!rootId) {
|
|
14729
|
-
const newRootId = scheduler.generateRootId();
|
|
14474
|
+
const newRootId = canvasId || scheduler.generateRootId();
|
|
14730
14475
|
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14731
14476
|
getState: () => store.getState(),
|
|
14732
14477
|
onError: (err) => store.getState().setError(err)
|
|
14733
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
|
+
);
|
|
14734
14506
|
const unregisterFrustum = scheduler.register(
|
|
14735
14507
|
() => {
|
|
14736
14508
|
const state2 = store.getState();
|
|
@@ -14741,7 +14513,7 @@ function createRoot(canvas) {
|
|
|
14741
14513
|
{
|
|
14742
14514
|
id: `${newRootId}_frustum`,
|
|
14743
14515
|
rootId: newRootId,
|
|
14744
|
-
|
|
14516
|
+
before: "render",
|
|
14745
14517
|
system: true
|
|
14746
14518
|
}
|
|
14747
14519
|
);
|
|
@@ -14753,7 +14525,7 @@ function createRoot(canvas) {
|
|
|
14753
14525
|
{
|
|
14754
14526
|
id: `${newRootId}_visibility`,
|
|
14755
14527
|
rootId: newRootId,
|
|
14756
|
-
|
|
14528
|
+
before: "render",
|
|
14757
14529
|
system: true,
|
|
14758
14530
|
after: `${newRootId}_frustum`
|
|
14759
14531
|
}
|
|
@@ -14765,18 +14537,22 @@ function createRoot(canvas) {
|
|
|
14765
14537
|
const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
|
|
14766
14538
|
if (userHandlesRender || state2.internal.priority) return;
|
|
14767
14539
|
try {
|
|
14768
|
-
if (state2.
|
|
14540
|
+
if (state2.renderPipeline?.render) state2.renderPipeline.render();
|
|
14769
14541
|
else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
|
|
14770
14542
|
} catch (error) {
|
|
14771
14543
|
state2.setError(error instanceof Error ? error : new Error(String(error)));
|
|
14772
14544
|
}
|
|
14773
14545
|
},
|
|
14774
14546
|
{
|
|
14775
|
-
|
|
14547
|
+
// Use canvas ID directly as job ID if available, otherwise use generated rootId
|
|
14548
|
+
id: canvasId || `${newRootId}_render`,
|
|
14776
14549
|
rootId: newRootId,
|
|
14777
14550
|
phase: "render",
|
|
14778
|
-
system: true
|
|
14551
|
+
system: true,
|
|
14779
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 }
|
|
14780
14556
|
}
|
|
14781
14557
|
);
|
|
14782
14558
|
state.set((state2) => ({
|
|
@@ -14785,6 +14561,8 @@ function createRoot(canvas) {
|
|
|
14785
14561
|
rootId: newRootId,
|
|
14786
14562
|
unregisterRoot: () => {
|
|
14787
14563
|
unregisterRoot();
|
|
14564
|
+
unregisterCanvasTarget();
|
|
14565
|
+
unregisterEventsFlush();
|
|
14788
14566
|
unregisterFrustum();
|
|
14789
14567
|
unregisterVisibility();
|
|
14790
14568
|
unregisterRender();
|
|
@@ -14843,15 +14621,24 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14843
14621
|
const renderer = state.internal.actualRenderer;
|
|
14844
14622
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14845
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();
|
|
14846
14628
|
state.events.disconnect?.();
|
|
14847
14629
|
cleanupHelperGroup(root.store);
|
|
14848
|
-
renderer
|
|
14849
|
-
|
|
14850
|
-
|
|
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
|
+
}
|
|
14851
14638
|
dispose(state.scene);
|
|
14852
14639
|
_roots.delete(canvas);
|
|
14853
14640
|
if (callback) callback(canvas);
|
|
14854
|
-
} catch
|
|
14641
|
+
} catch {
|
|
14855
14642
|
}
|
|
14856
14643
|
}, 500);
|
|
14857
14644
|
}
|
|
@@ -14859,36 +14646,34 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14859
14646
|
}
|
|
14860
14647
|
}
|
|
14861
14648
|
function createPortal(children, container, state) {
|
|
14862
|
-
return /* @__PURE__ */ jsx(
|
|
14649
|
+
return /* @__PURE__ */ jsx(Portal, { children, container, state });
|
|
14863
14650
|
}
|
|
14864
|
-
function
|
|
14651
|
+
function Portal({ children, container, state }) {
|
|
14865
14652
|
const isRef = useCallback((obj) => obj && "current" in obj, []);
|
|
14866
|
-
const [resolvedContainer,
|
|
14653
|
+
const [resolvedContainer, _setResolvedContainer] = useState(() => {
|
|
14867
14654
|
if (isRef(container)) return container.current ?? null;
|
|
14868
14655
|
return container;
|
|
14869
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
|
+
);
|
|
14870
14664
|
useMemo(() => {
|
|
14871
|
-
if (isRef(container)) {
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
const updated = container.current;
|
|
14876
|
-
if (updated && updated !== resolvedContainer) {
|
|
14877
|
-
setResolvedContainer(updated);
|
|
14878
|
-
}
|
|
14879
|
-
});
|
|
14880
|
-
} else if (current !== resolvedContainer) {
|
|
14881
|
-
setResolvedContainer(current);
|
|
14882
|
-
}
|
|
14883
|
-
} else if (container !== resolvedContainer) {
|
|
14884
|
-
setResolvedContainer(container);
|
|
14665
|
+
if (isRef(container) && !container.current) {
|
|
14666
|
+
return queueMicrotask(() => {
|
|
14667
|
+
setResolvedContainer(container.current);
|
|
14668
|
+
});
|
|
14885
14669
|
}
|
|
14886
|
-
|
|
14670
|
+
setResolvedContainer(container);
|
|
14671
|
+
}, [container, isRef, setResolvedContainer]);
|
|
14887
14672
|
if (!resolvedContainer) return /* @__PURE__ */ jsx(Fragment, {});
|
|
14888
14673
|
const portalKey = resolvedContainer.uuid ?? `portal-${resolvedContainer.id ?? "unknown"}`;
|
|
14889
|
-
return /* @__PURE__ */ jsx(
|
|
14674
|
+
return /* @__PURE__ */ jsx(PortalInner, { children, container: resolvedContainer, state }, portalKey);
|
|
14890
14675
|
}
|
|
14891
|
-
function
|
|
14676
|
+
function PortalInner({ state = {}, children, container }) {
|
|
14892
14677
|
const { events, size, injectScene = true, ...rest } = state;
|
|
14893
14678
|
const previousRoot = useStore();
|
|
14894
14679
|
const [raycaster] = useState(() => new Raycaster());
|
|
@@ -14909,12 +14694,14 @@ function Portal({ state = {}, children, container }) {
|
|
|
14909
14694
|
};
|
|
14910
14695
|
}, [portalScene, container, injectScene]);
|
|
14911
14696
|
const inject = useMutableCallback((rootState, injectState) => {
|
|
14697
|
+
const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
|
|
14912
14698
|
let viewport = void 0;
|
|
14913
|
-
if (injectState.camera && size) {
|
|
14699
|
+
if (injectState.camera && (size || injectState.size)) {
|
|
14914
14700
|
const camera = injectState.camera;
|
|
14915
|
-
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(),
|
|
14916
|
-
if (camera !== rootState.camera) updateCamera(camera,
|
|
14701
|
+
viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
|
|
14702
|
+
if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
|
|
14917
14703
|
}
|
|
14704
|
+
const set = injectState.set;
|
|
14918
14705
|
return {
|
|
14919
14706
|
// The intersect consists of the previous root state
|
|
14920
14707
|
...rootState,
|
|
@@ -14930,10 +14717,10 @@ function Portal({ state = {}, children, container }) {
|
|
|
14930
14717
|
previousRoot,
|
|
14931
14718
|
// Events, size and viewport can be overridden by the inject layer
|
|
14932
14719
|
events: { ...rootState.events, ...injectState.events, ...events },
|
|
14933
|
-
size:
|
|
14720
|
+
size: resolvedSize,
|
|
14934
14721
|
viewport: { ...rootState.viewport, ...viewport },
|
|
14935
14722
|
// Layers are allowed to override events
|
|
14936
|
-
setEvents: (events2) =>
|
|
14723
|
+
setEvents: (events2) => set((state2) => ({ ...state2, events: { ...state2.events, ...events2 } })),
|
|
14937
14724
|
// Container for child attachment - the portalScene (injected or container itself)
|
|
14938
14725
|
internal: { ...rootState.internal, ...injectState.internal, container: portalScene }
|
|
14939
14726
|
};
|
|
@@ -14942,9 +14729,13 @@ function Portal({ state = {}, children, container }) {
|
|
|
14942
14729
|
const store = createWithEqualityFn((set, get) => ({ ...rest, set, get }));
|
|
14943
14730
|
const onMutate = (prev) => store.setState((state2) => inject.current(prev, state2));
|
|
14944
14731
|
onMutate(previousRoot.getState());
|
|
14945
|
-
previousRoot.subscribe(onMutate);
|
|
14946
14732
|
return store;
|
|
14947
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]);
|
|
14948
14739
|
return (
|
|
14949
14740
|
// @ts-ignore, reconciler types are not maintained
|
|
14950
14741
|
/* @__PURE__ */ jsx(Fragment, { children: reconciler.createPortal(
|
|
@@ -14958,21 +14749,72 @@ function flushSync(fn) {
|
|
|
14958
14749
|
return reconciler.flushSyncFromReconciler(fn);
|
|
14959
14750
|
}
|
|
14960
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
|
+
|
|
14961
14805
|
function CanvasImpl({
|
|
14962
14806
|
ref,
|
|
14963
14807
|
children,
|
|
14964
14808
|
fallback,
|
|
14965
14809
|
resize,
|
|
14966
14810
|
style,
|
|
14811
|
+
id,
|
|
14967
14812
|
gl,
|
|
14968
|
-
renderer,
|
|
14813
|
+
renderer: rendererProp,
|
|
14969
14814
|
events = createPointerEvents,
|
|
14970
14815
|
eventSource,
|
|
14971
14816
|
eventPrefix,
|
|
14972
14817
|
shadows,
|
|
14973
|
-
linear,
|
|
14974
|
-
flat,
|
|
14975
|
-
legacy,
|
|
14976
14818
|
orthographic,
|
|
14977
14819
|
frameloop,
|
|
14978
14820
|
dpr,
|
|
@@ -14980,14 +14822,23 @@ function CanvasImpl({
|
|
|
14980
14822
|
raycaster,
|
|
14981
14823
|
camera,
|
|
14982
14824
|
scene,
|
|
14825
|
+
autoUpdateFrustum,
|
|
14826
|
+
occlusion,
|
|
14983
14827
|
onPointerMissed,
|
|
14984
14828
|
onDragOverMissed,
|
|
14985
14829
|
onDropMissed,
|
|
14986
14830
|
onCreated,
|
|
14831
|
+
hmr,
|
|
14832
|
+
width,
|
|
14833
|
+
height,
|
|
14834
|
+
background,
|
|
14835
|
+
forceEven,
|
|
14987
14836
|
...props
|
|
14988
14837
|
}) {
|
|
14838
|
+
const { primaryCanvas, scheduler, renderer } = parseRendererConfig(rendererProp);
|
|
14989
14839
|
React.useMemo(() => extend(THREE), []);
|
|
14990
14840
|
const Bridge = useBridge();
|
|
14841
|
+
const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
|
|
14991
14842
|
const hasInitialSizeRef = React.useRef(false);
|
|
14992
14843
|
const measureConfig = React.useMemo(() => {
|
|
14993
14844
|
if (!hasInitialSizeRef.current) {
|
|
@@ -15004,7 +14855,21 @@ function CanvasImpl({
|
|
|
15004
14855
|
};
|
|
15005
14856
|
}, [resize, hasInitialSizeRef.current]);
|
|
15006
14857
|
const [containerRef, containerRect] = useMeasure(measureConfig);
|
|
15007
|
-
|
|
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,
|
|
14868
|
+
top: containerRect.top,
|
|
14869
|
+
left: containerRect.left
|
|
14870
|
+
};
|
|
14871
|
+
}, [width, height, containerRect, forceEven]);
|
|
14872
|
+
if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
|
|
15008
14873
|
hasInitialSizeRef.current = true;
|
|
15009
14874
|
}
|
|
15010
14875
|
const canvasRef = React.useRef(null);
|
|
@@ -15015,6 +14880,7 @@ function CanvasImpl({
|
|
|
15015
14880
|
const handleDropMissed = useMutableCallback(onDropMissed);
|
|
15016
14881
|
const [block, setBlock] = React.useState(false);
|
|
15017
14882
|
const [error, setError] = React.useState(false);
|
|
14883
|
+
const [fallbackVisible, setFallbackVisible] = React.useState(false);
|
|
15018
14884
|
if (block) throw block;
|
|
15019
14885
|
if (error) throw error;
|
|
15020
14886
|
const root = React.useRef(null);
|
|
@@ -15022,8 +14888,9 @@ function CanvasImpl({
|
|
|
15022
14888
|
const unsubscribeErrorRef = React.useRef(null);
|
|
15023
14889
|
useIsomorphicLayoutEffect(() => {
|
|
15024
14890
|
effectActiveRef.current = true;
|
|
14891
|
+
if (fallbackVisible) return;
|
|
15025
14892
|
const canvas = canvasRef.current;
|
|
15026
|
-
if (
|
|
14893
|
+
if (effectiveSize.width > 0 && effectiveSize.height > 0 && canvas) {
|
|
15027
14894
|
if (!root.current) {
|
|
15028
14895
|
root.current = createRoot(canvas);
|
|
15029
14896
|
notifyAlpha({
|
|
@@ -15042,22 +14909,27 @@ function CanvasImpl({
|
|
|
15042
14909
|
}
|
|
15043
14910
|
async function run() {
|
|
15044
14911
|
if (!effectActiveRef.current || !root.current) return;
|
|
15045
|
-
await root.current.configure({
|
|
14912
|
+
const configured = await root.current.configure({
|
|
14913
|
+
id,
|
|
14914
|
+
primaryCanvas,
|
|
14915
|
+
scheduler,
|
|
15046
14916
|
gl,
|
|
15047
14917
|
renderer,
|
|
15048
14918
|
scene,
|
|
15049
14919
|
events,
|
|
15050
14920
|
shadows,
|
|
15051
|
-
linear,
|
|
15052
|
-
flat,
|
|
15053
|
-
legacy,
|
|
15054
14921
|
orthographic,
|
|
15055
14922
|
frameloop,
|
|
15056
14923
|
dpr,
|
|
15057
14924
|
performance,
|
|
15058
14925
|
raycaster,
|
|
15059
14926
|
camera,
|
|
15060
|
-
|
|
14927
|
+
autoUpdateFrustum,
|
|
14928
|
+
occlusion,
|
|
14929
|
+
size: effectiveSize,
|
|
14930
|
+
// Store size props for reset functionality
|
|
14931
|
+
_sizeProps: width !== void 0 || height !== void 0 ? { width, height } : null,
|
|
14932
|
+
forceEven,
|
|
15061
14933
|
// Pass mutable reference to onPointerMissed so it's free to update
|
|
15062
14934
|
onPointerMissed: (...args) => handlePointerMissed.current?.(...args),
|
|
15063
14935
|
onDragOverMissed: (...args) => handleDragOverMissed.current?.(...args),
|
|
@@ -15078,10 +14950,25 @@ function CanvasImpl({
|
|
|
15078
14950
|
}
|
|
15079
14951
|
onCreated?.(state);
|
|
15080
14952
|
}
|
|
15081
|
-
})
|
|
15082
|
-
|
|
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;
|
|
15083
14967
|
root.current.render(
|
|
15084
|
-
/* @__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
|
+
] }) }) })
|
|
15085
14972
|
);
|
|
15086
14973
|
}
|
|
15087
14974
|
run();
|
|
@@ -15102,7 +14989,29 @@ function CanvasImpl({
|
|
|
15102
14989
|
root.current = null;
|
|
15103
14990
|
};
|
|
15104
14991
|
}
|
|
15105
|
-
}, []);
|
|
14992
|
+
}, []);
|
|
14993
|
+
React.useEffect(() => {
|
|
14994
|
+
if (hmr === false) return;
|
|
14995
|
+
const canvas = canvasRef.current;
|
|
14996
|
+
if (!canvas) return;
|
|
14997
|
+
const handleHMR = () => {
|
|
14998
|
+
queueMicrotask(() => {
|
|
14999
|
+
const rootEntry = _roots.get(canvas);
|
|
15000
|
+
if (rootEntry?.store) clearHmrCaches(rootEntry.store);
|
|
15001
|
+
});
|
|
15002
|
+
};
|
|
15003
|
+
if (typeof import.meta !== "undefined" && import.meta.hot) {
|
|
15004
|
+
const hot = import.meta.hot;
|
|
15005
|
+
hot.on("vite:afterUpdate", handleHMR);
|
|
15006
|
+
return () => hot.off?.("vite:afterUpdate", handleHMR);
|
|
15007
|
+
}
|
|
15008
|
+
if (typeof module !== "undefined" && module.hot) {
|
|
15009
|
+
const hot = module.hot;
|
|
15010
|
+
hot.addStatusHandler((status) => {
|
|
15011
|
+
if (status === "idle") handleHMR();
|
|
15012
|
+
});
|
|
15013
|
+
}
|
|
15014
|
+
}, [hmr]);
|
|
15106
15015
|
const pointerEvents = eventSource ? "none" : "auto";
|
|
15107
15016
|
return /* @__PURE__ */ jsx(
|
|
15108
15017
|
"div",
|
|
@@ -15117,7 +15026,20 @@ function CanvasImpl({
|
|
|
15117
15026
|
...style
|
|
15118
15027
|
},
|
|
15119
15028
|
...props,
|
|
15120
|
-
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
|
+
) })
|
|
15121
15043
|
}
|
|
15122
15044
|
);
|
|
15123
15045
|
}
|
|
@@ -15125,6 +15047,114 @@ function Canvas(props) {
|
|
|
15125
15047
|
return /* @__PURE__ */ jsx(FiberProvider, { children: /* @__PURE__ */ jsx(CanvasImpl, { ...props }) });
|
|
15126
15048
|
}
|
|
15127
15049
|
|
|
15050
|
+
var __defProp = Object.defineProperty;
|
|
15051
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15052
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
15053
|
+
var _a;
|
|
15054
|
+
const INTERNAL_DATA = Symbol("ScopedStore.data");
|
|
15055
|
+
_a = INTERNAL_DATA;
|
|
15056
|
+
const _ScopedStore = class _ScopedStore {
|
|
15057
|
+
constructor(data) {
|
|
15058
|
+
__publicField(this, _a);
|
|
15059
|
+
this[INTERNAL_DATA] = data;
|
|
15060
|
+
return new Proxy(this, {
|
|
15061
|
+
get(target, prop, receiver) {
|
|
15062
|
+
if (typeof prop === "string") {
|
|
15063
|
+
if (prop === "scope" || prop === "has" || prop === "keys") {
|
|
15064
|
+
return Reflect.get(target, prop, receiver);
|
|
15065
|
+
}
|
|
15066
|
+
return target[INTERNAL_DATA][prop];
|
|
15067
|
+
}
|
|
15068
|
+
return Reflect.get(target, prop, receiver);
|
|
15069
|
+
},
|
|
15070
|
+
has(target, prop) {
|
|
15071
|
+
return typeof prop === "string" ? prop in target[INTERNAL_DATA] : Reflect.has(target, prop);
|
|
15072
|
+
},
|
|
15073
|
+
ownKeys(target) {
|
|
15074
|
+
return Reflect.ownKeys(target[INTERNAL_DATA]);
|
|
15075
|
+
},
|
|
15076
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
15077
|
+
if (typeof prop === "string" && prop in target[INTERNAL_DATA]) {
|
|
15078
|
+
return {
|
|
15079
|
+
configurable: true,
|
|
15080
|
+
enumerable: true,
|
|
15081
|
+
value: target[INTERNAL_DATA][prop]
|
|
15082
|
+
};
|
|
15083
|
+
}
|
|
15084
|
+
return void 0;
|
|
15085
|
+
}
|
|
15086
|
+
});
|
|
15087
|
+
}
|
|
15088
|
+
/**
|
|
15089
|
+
* Access a nested scope by key.
|
|
15090
|
+
* If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
|
|
15091
|
+
*/
|
|
15092
|
+
scope(key) {
|
|
15093
|
+
const data = this[INTERNAL_DATA][key];
|
|
15094
|
+
return new _ScopedStore(
|
|
15095
|
+
data && typeof data === "object" ? data : {}
|
|
15096
|
+
);
|
|
15097
|
+
}
|
|
15098
|
+
/**
|
|
15099
|
+
* Check if a key exists in the store.
|
|
15100
|
+
*/
|
|
15101
|
+
has(key) {
|
|
15102
|
+
return key in this[INTERNAL_DATA];
|
|
15103
|
+
}
|
|
15104
|
+
/**
|
|
15105
|
+
* Get all keys in the store.
|
|
15106
|
+
*/
|
|
15107
|
+
keys() {
|
|
15108
|
+
return Object.keys(this[INTERNAL_DATA]);
|
|
15109
|
+
}
|
|
15110
|
+
};
|
|
15111
|
+
let ScopedStore = _ScopedStore;
|
|
15112
|
+
function createScopedStore(data) {
|
|
15113
|
+
return new ScopedStore(data);
|
|
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
|
+
}
|
|
15157
|
+
|
|
15128
15158
|
function addTexture(set, key, value) {
|
|
15129
15159
|
set((state) => {
|
|
15130
15160
|
const newMap = new Map(state.textures);
|
|
@@ -15164,6 +15194,27 @@ function createTextureOperations(set) {
|
|
|
15164
15194
|
removeMultiple: (keys) => removeTextures(set, keys)
|
|
15165
15195
|
};
|
|
15166
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
|
+
}
|
|
15167
15218
|
function vectorize(inObject) {
|
|
15168
15219
|
if (inObject === null || inObject === void 0) return inObject;
|
|
15169
15220
|
if (typeof inObject === "string") {
|
|
@@ -15176,9 +15227,16 @@ function vectorize(inObject) {
|
|
|
15176
15227
|
}
|
|
15177
15228
|
if (typeof inObject !== "object") return inObject;
|
|
15178
15229
|
const obj = inObject;
|
|
15230
|
+
if (obj.isNode) {
|
|
15231
|
+
return extractTSLValue(inObject);
|
|
15232
|
+
}
|
|
15179
15233
|
if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
|
|
15180
15234
|
if (obj.isMatrix3 || obj.isMatrix4) return inObject;
|
|
15181
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
|
+
}
|
|
15182
15240
|
if ("x" in obj && "y" in obj && typeof obj.x === "number" && typeof obj.y === "number") {
|
|
15183
15241
|
if ("w" in obj && typeof obj.w === "number" && "z" in obj && typeof obj.z === "number") {
|
|
15184
15242
|
return new Vector4(obj.x, obj.y, obj.z, obj.w);
|
|
@@ -15202,7 +15260,7 @@ function useCompareMemoize(value, deep) {
|
|
|
15202
15260
|
|
|
15203
15261
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
15204
15262
|
function useUniforms(creatorOrScope, scope) {
|
|
15205
|
-
const store =
|
|
15263
|
+
const store = usePrimaryStore();
|
|
15206
15264
|
const removeUniforms2 = useCallback(
|
|
15207
15265
|
(names, targetScope) => {
|
|
15208
15266
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15238,21 +15296,55 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15238
15296
|
},
|
|
15239
15297
|
[store]
|
|
15240
15298
|
);
|
|
15299
|
+
const rebuildUniforms = useCallback(
|
|
15300
|
+
(targetScope) => {
|
|
15301
|
+
store.setState((state) => {
|
|
15302
|
+
let newUniforms = {};
|
|
15303
|
+
if (targetScope && targetScope !== "root") {
|
|
15304
|
+
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15305
|
+
newUniforms = rest;
|
|
15306
|
+
} else if (targetScope === "root") {
|
|
15307
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15308
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15309
|
+
}
|
|
15310
|
+
}
|
|
15311
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15312
|
+
});
|
|
15313
|
+
},
|
|
15314
|
+
[store]
|
|
15315
|
+
);
|
|
15241
15316
|
const inputForMemoization = useMemo(() => {
|
|
15242
|
-
|
|
15317
|
+
let raw = creatorOrScope;
|
|
15318
|
+
if (is.fun(creatorOrScope)) {
|
|
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;
|
|
15328
|
+
}
|
|
15329
|
+
return raw;
|
|
15243
15330
|
}, [creatorOrScope, store]);
|
|
15244
15331
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
15332
|
+
const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
|
|
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;
|
|
15245
15337
|
const uniforms = useMemo(() => {
|
|
15246
|
-
const state = store.getState();
|
|
15247
|
-
const set = store.setState;
|
|
15248
15338
|
if (memoizedInput === void 0) {
|
|
15249
|
-
return
|
|
15339
|
+
return storeUniforms;
|
|
15250
15340
|
}
|
|
15251
15341
|
if (typeof memoizedInput === "string") {
|
|
15252
|
-
const scopeData =
|
|
15342
|
+
const scopeData = storeUniforms[memoizedInput];
|
|
15253
15343
|
if (scopeData && !isUniformNode$1(scopeData)) return scopeData;
|
|
15254
15344
|
return {};
|
|
15255
15345
|
}
|
|
15346
|
+
const state = store.getState();
|
|
15347
|
+
const set = store.setState;
|
|
15256
15348
|
if (typeof memoizedInput !== "object" || memoizedInput === null) {
|
|
15257
15349
|
throw new Error("Invalid uniform input");
|
|
15258
15350
|
}
|
|
@@ -15296,8 +15388,23 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
15296
15388
|
}
|
|
15297
15389
|
}
|
|
15298
15390
|
return result;
|
|
15299
|
-
}, [store, memoizedInput, scope]);
|
|
15300
|
-
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
|
|
15391
|
+
}, [store, memoizedInput, scope, readerDep, creatorDep]);
|
|
15392
|
+
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
|
|
15393
|
+
}
|
|
15394
|
+
function rebuildAllUniforms(store, scope) {
|
|
15395
|
+
store = store.getState().primaryStore ?? store;
|
|
15396
|
+
store.setState((state) => {
|
|
15397
|
+
let newUniforms = {};
|
|
15398
|
+
if (scope && scope !== "root") {
|
|
15399
|
+
const { [scope]: _, ...rest } = state.uniforms;
|
|
15400
|
+
newUniforms = rest;
|
|
15401
|
+
} else if (scope === "root") {
|
|
15402
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15403
|
+
if (!isUniformNode$1(value)) newUniforms[key] = value;
|
|
15404
|
+
}
|
|
15405
|
+
}
|
|
15406
|
+
return { uniforms: newUniforms, _hmrVersion: state._hmrVersion + 1 };
|
|
15407
|
+
});
|
|
15301
15408
|
}
|
|
15302
15409
|
function removeUniforms(set, names, scope) {
|
|
15303
15410
|
set((state) => {
|
|
@@ -15362,15 +15469,17 @@ function isSameThreeType(a, b) {
|
|
|
15362
15469
|
}
|
|
15363
15470
|
|
|
15364
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;
|
|
15365
15473
|
function useUniform(name, value) {
|
|
15366
15474
|
const store = useStore();
|
|
15475
|
+
const hmrVersion = useThree((s) => s._hmrVersion);
|
|
15367
15476
|
return useMemo(() => {
|
|
15368
15477
|
const state = store.getState();
|
|
15369
15478
|
const set = store.setState;
|
|
15370
15479
|
const existing = state.uniforms[name];
|
|
15371
15480
|
if (existing && isUniformNode(existing)) {
|
|
15372
|
-
if (value !== void 0) {
|
|
15373
|
-
existing.value = value;
|
|
15481
|
+
if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
|
|
15482
|
+
existing.value = typeof value === "string" ? new Color(value) : value;
|
|
15374
15483
|
}
|
|
15375
15484
|
return existing;
|
|
15376
15485
|
}
|
|
@@ -15379,7 +15488,24 @@ function useUniform(name, value) {
|
|
|
15379
15488
|
`[useUniform] Uniform "${name}" not found. Create it first with: useUniform('${name}', initialValue)`
|
|
15380
15489
|
);
|
|
15381
15490
|
}
|
|
15382
|
-
|
|
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
|
+
}
|
|
15383
15509
|
if (typeof node.setName === "function") {
|
|
15384
15510
|
node.setName(name);
|
|
15385
15511
|
}
|
|
@@ -15390,12 +15516,12 @@ function useUniform(name, value) {
|
|
|
15390
15516
|
}
|
|
15391
15517
|
}));
|
|
15392
15518
|
return node;
|
|
15393
|
-
}, [store, name]);
|
|
15519
|
+
}, [store, name, hmrVersion]);
|
|
15394
15520
|
}
|
|
15395
15521
|
|
|
15396
15522
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
15397
15523
|
function useNodes(creatorOrScope, scope) {
|
|
15398
|
-
const store =
|
|
15524
|
+
const store = usePrimaryStore();
|
|
15399
15525
|
const removeNodes2 = useCallback(
|
|
15400
15526
|
(names, targetScope) => {
|
|
15401
15527
|
const nameArray = Array.isArray(names) ? names : [names];
|
|
@@ -15431,19 +15557,46 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15431
15557
|
},
|
|
15432
15558
|
[store]
|
|
15433
15559
|
);
|
|
15560
|
+
const rebuildNodes = useCallback(
|
|
15561
|
+
(targetScope) => {
|
|
15562
|
+
store.setState((state) => {
|
|
15563
|
+
let newNodes = state.nodes;
|
|
15564
|
+
if (targetScope && targetScope !== "root") {
|
|
15565
|
+
const { [targetScope]: _, ...rest } = state.nodes;
|
|
15566
|
+
newNodes = rest;
|
|
15567
|
+
} else if (targetScope === "root") {
|
|
15568
|
+
newNodes = {};
|
|
15569
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15570
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15571
|
+
}
|
|
15572
|
+
} else {
|
|
15573
|
+
newNodes = {};
|
|
15574
|
+
}
|
|
15575
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15576
|
+
});
|
|
15577
|
+
},
|
|
15578
|
+
[store]
|
|
15579
|
+
);
|
|
15580
|
+
const isReader = creatorOrScope === void 0 || typeof creatorOrScope === "string";
|
|
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;
|
|
15434
15586
|
const nodes = useMemo(() => {
|
|
15435
|
-
const state = store.getState();
|
|
15436
|
-
const set = store.setState;
|
|
15437
15587
|
if (creatorOrScope === void 0) {
|
|
15438
|
-
return
|
|
15588
|
+
return storeNodes;
|
|
15439
15589
|
}
|
|
15440
15590
|
if (typeof creatorOrScope === "string") {
|
|
15441
|
-
const scopeData =
|
|
15591
|
+
const scopeData = storeNodes[creatorOrScope];
|
|
15442
15592
|
if (scopeData && !isTSLNode(scopeData)) return scopeData;
|
|
15443
15593
|
return {};
|
|
15444
15594
|
}
|
|
15595
|
+
const state = store.getState();
|
|
15596
|
+
const set = store.setState;
|
|
15445
15597
|
const creator = creatorOrScope;
|
|
15446
|
-
const
|
|
15598
|
+
const wrappedState = createLazyCreatorState(state);
|
|
15599
|
+
const created = creator(wrappedState);
|
|
15447
15600
|
const result = {};
|
|
15448
15601
|
let hasNewNodes = false;
|
|
15449
15602
|
if (scope) {
|
|
@@ -15452,7 +15605,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15452
15605
|
if (currentScope[name]) {
|
|
15453
15606
|
result[name] = currentScope[name];
|
|
15454
15607
|
} else {
|
|
15455
|
-
|
|
15608
|
+
node.setName?.(`${scope}.${name}`);
|
|
15456
15609
|
result[name] = node;
|
|
15457
15610
|
hasNewNodes = true;
|
|
15458
15611
|
}
|
|
@@ -15472,7 +15625,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15472
15625
|
if (existing && isTSLNode(existing)) {
|
|
15473
15626
|
result[name] = existing;
|
|
15474
15627
|
} else {
|
|
15475
|
-
|
|
15628
|
+
node.setName?.(name);
|
|
15476
15629
|
result[name] = node;
|
|
15477
15630
|
hasNewNodes = true;
|
|
15478
15631
|
}
|
|
@@ -15481,8 +15634,26 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15481
15634
|
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15482
15635
|
}
|
|
15483
15636
|
return result;
|
|
15484
|
-
}, [store,
|
|
15485
|
-
return { ...nodes, removeNodes: removeNodes2, clearNodes };
|
|
15637
|
+
}, [store, scopeDep, readerDep, creatorDep]);
|
|
15638
|
+
return { ...nodes, removeNodes: removeNodes2, clearNodes, rebuildNodes };
|
|
15639
|
+
}
|
|
15640
|
+
function rebuildAllNodes(store, scope) {
|
|
15641
|
+
store = store.getState().primaryStore ?? store;
|
|
15642
|
+
store.setState((state) => {
|
|
15643
|
+
let newNodes = state.nodes;
|
|
15644
|
+
if (scope && scope !== "root") {
|
|
15645
|
+
const { [scope]: _, ...rest } = state.nodes;
|
|
15646
|
+
newNodes = rest;
|
|
15647
|
+
} else if (scope === "root") {
|
|
15648
|
+
newNodes = {};
|
|
15649
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15650
|
+
if (!isTSLNode(value)) newNodes[key] = value;
|
|
15651
|
+
}
|
|
15652
|
+
} else {
|
|
15653
|
+
newNodes = {};
|
|
15654
|
+
}
|
|
15655
|
+
return { nodes: newNodes, _hmrVersion: state._hmrVersion + 1 };
|
|
15656
|
+
});
|
|
15486
15657
|
}
|
|
15487
15658
|
function removeNodes(set, names, scope) {
|
|
15488
15659
|
set((state) => {
|
|
@@ -15512,17 +15683,365 @@ function clearRootNodes(set) {
|
|
|
15512
15683
|
});
|
|
15513
15684
|
}
|
|
15514
15685
|
function useLocalNodes(creator) {
|
|
15515
|
-
const store =
|
|
15516
|
-
const uniforms =
|
|
15517
|
-
const nodes =
|
|
15518
|
-
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);
|
|
15519
15691
|
return useMemo(() => {
|
|
15692
|
+
const wrappedState = createLazyCreatorState(store.getState());
|
|
15693
|
+
return creator(wrappedState);
|
|
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
|
+
}
|
|
15520
15963
|
const state = store.getState();
|
|
15521
|
-
|
|
15522
|
-
|
|
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
|
+
});
|
|
15523
16041
|
}
|
|
15524
16042
|
|
|
15525
|
-
|
|
16043
|
+
const RenderPipelineCtor = RenderPipeline ?? PostProcessing;
|
|
16044
|
+
function useRenderPipeline(mainCB, setupCB) {
|
|
15526
16045
|
const store = useStore();
|
|
15527
16046
|
const { scene, camera, renderer, isLegacy } = useThree();
|
|
15528
16047
|
const callbacksRanRef = useRef(false);
|
|
@@ -15532,12 +16051,16 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15532
16051
|
mainCBRef.current = mainCB;
|
|
15533
16052
|
setupCBRef.current = setupCB;
|
|
15534
16053
|
const [rebuildVersion, setRebuildVersion] = useState(0);
|
|
16054
|
+
useEffect(() => {
|
|
16055
|
+
callbacksRanRef.current = false;
|
|
16056
|
+
scenePassCacheRef.current = null;
|
|
16057
|
+
}, []);
|
|
15535
16058
|
const clearPasses = useCallback(() => {
|
|
15536
16059
|
store.setState({ passes: {} });
|
|
15537
16060
|
}, [store]);
|
|
15538
16061
|
const reset = useCallback(() => {
|
|
15539
16062
|
store.setState({
|
|
15540
|
-
|
|
16063
|
+
renderPipeline: null,
|
|
15541
16064
|
passes: {}
|
|
15542
16065
|
});
|
|
15543
16066
|
callbacksRanRef.current = false;
|
|
@@ -15550,18 +16073,18 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15550
16073
|
}, []);
|
|
15551
16074
|
useLayoutEffect(() => {
|
|
15552
16075
|
if (isLegacy) {
|
|
15553
|
-
throw new Error("
|
|
16076
|
+
throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
|
|
15554
16077
|
}
|
|
15555
16078
|
if (!renderer || !scene || !camera) return;
|
|
15556
16079
|
const state = store.getState();
|
|
15557
16080
|
const set = store.setState;
|
|
15558
16081
|
try {
|
|
15559
|
-
let
|
|
16082
|
+
let pipeline = state.renderPipeline;
|
|
15560
16083
|
let currentPasses = { ...state.passes };
|
|
15561
|
-
let
|
|
15562
|
-
if (!
|
|
15563
|
-
|
|
15564
|
-
|
|
16084
|
+
let justCreatedPipeline = false;
|
|
16085
|
+
if (!pipeline) {
|
|
16086
|
+
pipeline = new RenderPipelineCtor(renderer);
|
|
16087
|
+
justCreatedPipeline = true;
|
|
15565
16088
|
}
|
|
15566
16089
|
const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
|
|
15567
16090
|
let scenePass;
|
|
@@ -15572,9 +16095,9 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15572
16095
|
scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
|
|
15573
16096
|
}
|
|
15574
16097
|
currentPasses.scenePass = scenePass;
|
|
15575
|
-
if (!
|
|
15576
|
-
set({
|
|
15577
|
-
const shouldRunCallbacks =
|
|
16098
|
+
if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
|
|
16099
|
+
set({ renderPipeline: pipeline, passes: currentPasses });
|
|
16100
|
+
const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
|
|
15578
16101
|
if (shouldRunCallbacks) {
|
|
15579
16102
|
if (setupCBRef.current) {
|
|
15580
16103
|
const freshState = store.getState();
|
|
@@ -15595,22 +16118,22 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15595
16118
|
callbacksRanRef.current = true;
|
|
15596
16119
|
}
|
|
15597
16120
|
} catch (error) {
|
|
15598
|
-
console.error("[
|
|
16121
|
+
console.error("[useRenderPipeline] Setup error:", error);
|
|
15599
16122
|
}
|
|
15600
16123
|
}, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
|
|
15601
16124
|
const passes = useThree((s) => s.passes);
|
|
15602
|
-
const
|
|
16125
|
+
const renderPipeline = useThree((s) => s.renderPipeline);
|
|
15603
16126
|
return {
|
|
15604
16127
|
passes,
|
|
15605
|
-
|
|
16128
|
+
renderPipeline,
|
|
15606
16129
|
clearPasses,
|
|
15607
16130
|
reset,
|
|
15608
16131
|
rebuild,
|
|
15609
|
-
// isReady indicates if
|
|
15610
|
-
isReady:
|
|
16132
|
+
// isReady indicates if RenderPipeline is configured and ready for rendering
|
|
16133
|
+
isReady: renderPipeline !== null
|
|
15611
16134
|
};
|
|
15612
16135
|
}
|
|
15613
16136
|
|
|
15614
16137
|
extend(THREE);
|
|
15615
16138
|
|
|
15616
|
-
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 };
|