@react-three/fiber 10.0.0-alpha.0 → 10.0.0-alpha.1
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/dist/index.cjs +498 -80
- package/dist/index.d.cts +199 -23
- package/dist/index.d.mts +199 -23
- package/dist/index.d.ts +199 -23
- package/dist/index.mjs +499 -83
- package/dist/legacy.cjs +494 -79
- package/dist/legacy.d.cts +200 -22
- package/dist/legacy.d.mts +200 -22
- package/dist/legacy.d.ts +200 -22
- package/dist/legacy.mjs +494 -81
- package/dist/webgpu/index.cjs +586 -145
- package/dist/webgpu/index.d.cts +244 -36
- package/dist/webgpu/index.d.mts +244 -36
- package/dist/webgpu/index.d.ts +244 -36
- package/dist/webgpu/index.mjs +586 -147
- package/package.json +1 -1
package/dist/webgpu/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as webgpu from 'three/webgpu';
|
|
2
|
-
import { Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
|
|
2
|
+
import { RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, SRGBColorSpace, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFSoftShadowMap, VSMShadowMap, PCFShadowMap, BasicShadowMap, WebGPURenderer, Color, Vector4, PostProcessing } from 'three/webgpu';
|
|
3
3
|
import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
4
4
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import * as React from 'react';
|
|
@@ -34,12 +34,15 @@ const WebGLRenderer = class WebGLRenderer2 {
|
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
+
const WebGLRenderTarget = null;
|
|
37
38
|
|
|
38
39
|
const THREE = /*#__PURE__*/_mergeNamespaces({
|
|
39
40
|
__proto__: null,
|
|
40
41
|
Inspector: Inspector,
|
|
41
42
|
R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
|
|
42
43
|
R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
|
|
44
|
+
RenderTargetCompat: RenderTarget,
|
|
45
|
+
WebGLRenderTarget: WebGLRenderTarget,
|
|
43
46
|
WebGLRenderer: WebGLRenderer
|
|
44
47
|
}, [webgpu]);
|
|
45
48
|
|
|
@@ -148,6 +151,13 @@ function updateCamera(camera, size) {
|
|
|
148
151
|
}
|
|
149
152
|
camera.updateProjectionMatrix();
|
|
150
153
|
}
|
|
154
|
+
const frustumMatrix = new Matrix4();
|
|
155
|
+
function updateFrustum(camera, frustum) {
|
|
156
|
+
const target = frustum ?? new Frustum();
|
|
157
|
+
frustumMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
158
|
+
target.setFromProjectionMatrix(frustumMatrix);
|
|
159
|
+
return target;
|
|
160
|
+
}
|
|
151
161
|
|
|
152
162
|
const REACT_INTERNAL_PROPS = ["children", "key", "ref"];
|
|
153
163
|
function findInitialRoot(instance) {
|
|
@@ -228,6 +238,205 @@ function invalidateInstance(instance) {
|
|
|
228
238
|
if (state && state.internal.frames === 0) state.invalidate();
|
|
229
239
|
}
|
|
230
240
|
|
|
241
|
+
const tempFrustum = new Frustum();
|
|
242
|
+
let hasWarnedWebGL = false;
|
|
243
|
+
let tslModule = null;
|
|
244
|
+
async function loadTSL() {
|
|
245
|
+
if (tslModule) return tslModule;
|
|
246
|
+
try {
|
|
247
|
+
const tsl = await import('three/tsl');
|
|
248
|
+
tslModule = { uniform: tsl.uniform, nodeObject: tsl.nodeObject };
|
|
249
|
+
return tslModule;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function createOcclusionObserverNode(store, uniform) {
|
|
255
|
+
const node = new Node("float");
|
|
256
|
+
node.updateType = NodeUpdateType.OBJECT;
|
|
257
|
+
node.update = function(frame) {
|
|
258
|
+
const { internal } = store.getState();
|
|
259
|
+
const registry = internal.visibilityRegistry;
|
|
260
|
+
const cache = internal.occlusionCache;
|
|
261
|
+
for (const entry of registry.values()) {
|
|
262
|
+
const { object, handlers } = entry;
|
|
263
|
+
if (handlers.onOccluded || handlers.onVisible) {
|
|
264
|
+
const isOccluded = frame.renderer.isOccluded(object);
|
|
265
|
+
cache.set(object, isOccluded);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
node.setup = function() {
|
|
270
|
+
return uniform(0);
|
|
271
|
+
};
|
|
272
|
+
return node;
|
|
273
|
+
}
|
|
274
|
+
let occlusionSetupPromise = null;
|
|
275
|
+
function enableOcclusion(store) {
|
|
276
|
+
const state = store.getState();
|
|
277
|
+
const { internal, renderer, rootScene } = state;
|
|
278
|
+
if (internal.occlusionEnabled || occlusionSetupPromise) return;
|
|
279
|
+
const hasOcclusionSupport = typeof renderer?.isOccluded === "function";
|
|
280
|
+
if (!hasOcclusionSupport) {
|
|
281
|
+
if (!hasWarnedWebGL) {
|
|
282
|
+
console.warn(
|
|
283
|
+
"[R3F] Warning: onOccluded/onVisible occlusion queries require WebGPU renderer. Occlusion events will not fire on WebGL."
|
|
284
|
+
);
|
|
285
|
+
hasWarnedWebGL = true;
|
|
286
|
+
}
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
occlusionSetupPromise = setupOcclusion(store);
|
|
290
|
+
}
|
|
291
|
+
async function setupOcclusion(store) {
|
|
292
|
+
const state = store.getState();
|
|
293
|
+
const { internal, rootScene, set } = state;
|
|
294
|
+
const tsl = await loadTSL();
|
|
295
|
+
if (!tsl) {
|
|
296
|
+
console.warn("[R3F] Warning: TSL module not available. Occlusion queries disabled.");
|
|
297
|
+
occlusionSetupPromise = null;
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const { uniform, nodeObject } = tsl;
|
|
301
|
+
let helperGroup = internal.helperGroup;
|
|
302
|
+
if (!helperGroup) {
|
|
303
|
+
helperGroup = new Group();
|
|
304
|
+
helperGroup.name = "__r3fInternal";
|
|
305
|
+
helperGroup.__r3fInternal = true;
|
|
306
|
+
rootScene.add(helperGroup);
|
|
307
|
+
}
|
|
308
|
+
const geometry = new BoxGeometry(1, 1, 1);
|
|
309
|
+
const material = new MeshBasicNodeMaterial({
|
|
310
|
+
transparent: true,
|
|
311
|
+
opacity: 0
|
|
312
|
+
});
|
|
313
|
+
const observerNode = nodeObject(createOcclusionObserverNode(store, uniform));
|
|
314
|
+
material.colorNode = observerNode;
|
|
315
|
+
material.needsUpdate = true;
|
|
316
|
+
const mesh = new Mesh(geometry, material);
|
|
317
|
+
mesh.name = "__r3fOcclusionObserver";
|
|
318
|
+
mesh.scale.setScalar(1e-4);
|
|
319
|
+
mesh.frustumCulled = false;
|
|
320
|
+
mesh.__r3fInternal = true;
|
|
321
|
+
helperGroup.add(mesh);
|
|
322
|
+
set((state2) => ({
|
|
323
|
+
internal: {
|
|
324
|
+
...state2.internal,
|
|
325
|
+
helperGroup,
|
|
326
|
+
occlusionObserver: mesh,
|
|
327
|
+
occlusionEnabled: true
|
|
328
|
+
}
|
|
329
|
+
}));
|
|
330
|
+
occlusionSetupPromise = null;
|
|
331
|
+
}
|
|
332
|
+
function disableOcclusion(store) {
|
|
333
|
+
const { internal, set } = store.getState();
|
|
334
|
+
if (!internal.occlusionEnabled) return;
|
|
335
|
+
if (internal.occlusionObserver) {
|
|
336
|
+
internal.occlusionObserver.removeFromParent();
|
|
337
|
+
internal.occlusionObserver.geometry.dispose();
|
|
338
|
+
internal.occlusionObserver.material.dispose();
|
|
339
|
+
}
|
|
340
|
+
internal.occlusionCache.clear();
|
|
341
|
+
set((state) => ({
|
|
342
|
+
internal: {
|
|
343
|
+
...state.internal,
|
|
344
|
+
occlusionObserver: null,
|
|
345
|
+
occlusionEnabled: false
|
|
346
|
+
}
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
function cleanupHelperGroup(store) {
|
|
350
|
+
const { internal, set } = store.getState();
|
|
351
|
+
disableOcclusion(store);
|
|
352
|
+
if (internal.helperGroup) {
|
|
353
|
+
internal.helperGroup.removeFromParent();
|
|
354
|
+
set((state) => ({
|
|
355
|
+
internal: {
|
|
356
|
+
...state.internal,
|
|
357
|
+
helperGroup: null
|
|
358
|
+
}
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function registerVisibility(store, object, handlers) {
|
|
363
|
+
const { internal } = store.getState();
|
|
364
|
+
const registry = internal.visibilityRegistry;
|
|
365
|
+
const entry = {
|
|
366
|
+
object,
|
|
367
|
+
handlers,
|
|
368
|
+
lastFramedState: null,
|
|
369
|
+
lastOccludedState: null,
|
|
370
|
+
lastVisibleState: null
|
|
371
|
+
};
|
|
372
|
+
registry.set(object.uuid, entry);
|
|
373
|
+
if (handlers.onOccluded || handlers.onVisible) {
|
|
374
|
+
object.occlusionTest = true;
|
|
375
|
+
if (!internal.occlusionEnabled) {
|
|
376
|
+
enableOcclusion(store);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function unregisterVisibility(store, object) {
|
|
381
|
+
const { internal } = store.getState();
|
|
382
|
+
internal.visibilityRegistry.delete(object.uuid);
|
|
383
|
+
internal.occlusionCache.delete(object);
|
|
384
|
+
}
|
|
385
|
+
function checkVisibility(state) {
|
|
386
|
+
const { internal, camera } = state;
|
|
387
|
+
const registry = internal.visibilityRegistry;
|
|
388
|
+
if (registry.size === 0) return;
|
|
389
|
+
updateFrustum(camera, tempFrustum);
|
|
390
|
+
for (const entry of registry.values()) {
|
|
391
|
+
const { object, handlers, lastFramedState, lastOccludedState, lastVisibleState } = entry;
|
|
392
|
+
let inFrustum = null;
|
|
393
|
+
const computeFrustum = () => {
|
|
394
|
+
if (inFrustum === null) {
|
|
395
|
+
if (object.geometry?.boundingSphere === null) {
|
|
396
|
+
object.geometry?.computeBoundingSphere();
|
|
397
|
+
}
|
|
398
|
+
inFrustum = tempFrustum.intersectsObject(object);
|
|
399
|
+
}
|
|
400
|
+
return inFrustum;
|
|
401
|
+
};
|
|
402
|
+
if (handlers.onFramed) {
|
|
403
|
+
const currentInFrustum = computeFrustum();
|
|
404
|
+
if (currentInFrustum !== lastFramedState) {
|
|
405
|
+
entry.lastFramedState = currentInFrustum;
|
|
406
|
+
handlers.onFramed(currentInFrustum);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
let currentOcclusion = null;
|
|
410
|
+
if (handlers.onOccluded && internal.occlusionEnabled) {
|
|
411
|
+
currentOcclusion = internal.occlusionCache.get(object) ?? null;
|
|
412
|
+
if (currentOcclusion !== null && currentOcclusion !== lastOccludedState) {
|
|
413
|
+
entry.lastOccludedState = currentOcclusion;
|
|
414
|
+
handlers.onOccluded(currentOcclusion);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (handlers.onVisible) {
|
|
418
|
+
const currentInFrustum = computeFrustum();
|
|
419
|
+
if (!handlers.onFramed && currentInFrustum !== lastFramedState) {
|
|
420
|
+
entry.lastFramedState = currentInFrustum;
|
|
421
|
+
}
|
|
422
|
+
let isOccluded = currentOcclusion;
|
|
423
|
+
if (isOccluded === null && internal.occlusionEnabled) {
|
|
424
|
+
isOccluded = internal.occlusionCache.get(object) ?? null;
|
|
425
|
+
}
|
|
426
|
+
if (isOccluded === null) isOccluded = false;
|
|
427
|
+
const isVisible = currentInFrustum && !isOccluded && object.visible;
|
|
428
|
+
if (isVisible !== lastVisibleState) {
|
|
429
|
+
entry.lastVisibleState = isVisible;
|
|
430
|
+
handlers.onVisible(isVisible);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function hasVisibilityHandlers(handlers) {
|
|
436
|
+
if (!handlers) return false;
|
|
437
|
+
return !!(handlers.onFramed || handlers.onOccluded || handlers.onVisible);
|
|
438
|
+
}
|
|
439
|
+
|
|
231
440
|
const RESERVED_PROPS = [
|
|
232
441
|
"children",
|
|
233
442
|
"key",
|
|
@@ -242,6 +451,7 @@ const RESERVED_PROPS = [
|
|
|
242
451
|
"dispose"
|
|
243
452
|
];
|
|
244
453
|
const EVENT_REGEX = /^on(Pointer|Drag|Drop|Click|DoubleClick|ContextMenu|Wheel)/;
|
|
454
|
+
const VISIBILITY_EVENT_REGEX = /^on(Framed|Occluded|Visible)$/;
|
|
245
455
|
const INDEX_REGEX = /-\d+$/;
|
|
246
456
|
const MEMOIZED_PROTOTYPES = /* @__PURE__ */ new Map();
|
|
247
457
|
const colorMaps = ["map", "emissiveMap", "sheenColorMap", "specularColorMap", "envMap"];
|
|
@@ -336,6 +546,12 @@ function applyProps(object, props) {
|
|
|
336
546
|
instance.eventCount = Object.keys(instance.handlers).length;
|
|
337
547
|
continue;
|
|
338
548
|
}
|
|
549
|
+
if (instance && VISIBILITY_EVENT_REGEX.test(prop)) {
|
|
550
|
+
if (typeof value === "function") instance.handlers[prop] = value;
|
|
551
|
+
else delete instance.handlers[prop];
|
|
552
|
+
instance.eventCount = Object.keys(instance.handlers).length;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
339
555
|
if (value === void 0) continue;
|
|
340
556
|
let { root, key, target } = resolve(object, prop);
|
|
341
557
|
if (target === void 0 && (typeof root !== "object" || root === null)) {
|
|
@@ -372,6 +588,17 @@ function applyProps(object, props) {
|
|
|
372
588
|
if (instance.eventCount && object2.raycast !== null) {
|
|
373
589
|
rootState.internal.interaction.push(object2);
|
|
374
590
|
}
|
|
591
|
+
const root = findInitialRoot(instance);
|
|
592
|
+
const visibilityHandlers = {
|
|
593
|
+
onFramed: instance.handlers.onFramed,
|
|
594
|
+
onOccluded: instance.handlers.onOccluded,
|
|
595
|
+
onVisible: instance.handlers.onVisible
|
|
596
|
+
};
|
|
597
|
+
if (hasVisibilityHandlers(visibilityHandlers)) {
|
|
598
|
+
registerVisibility(root, object2, visibilityHandlers);
|
|
599
|
+
} else {
|
|
600
|
+
unregisterVisibility(root, object2);
|
|
601
|
+
}
|
|
375
602
|
}
|
|
376
603
|
if (instance && instance.props.attach === void 0) {
|
|
377
604
|
if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
|
|
@@ -406,6 +633,7 @@ function removeInteractivity(store, object) {
|
|
|
406
633
|
internal.capturedMap.forEach((captures, pointerId) => {
|
|
407
634
|
releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
|
|
408
635
|
});
|
|
636
|
+
unregisterVisibility(store, object);
|
|
409
637
|
}
|
|
410
638
|
function createEvents(store) {
|
|
411
639
|
function calculateDistance(event) {
|
|
@@ -788,8 +1016,21 @@ function formatLocation(url, line) {
|
|
|
788
1016
|
const file = clean.split("/").pop() ?? clean;
|
|
789
1017
|
return `${file}:${line}`;
|
|
790
1018
|
}
|
|
1019
|
+
function notifyAlpha({ message, link }) {
|
|
1020
|
+
if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (shownNotices.has(message)) return;
|
|
1024
|
+
shownNotices.add(message);
|
|
1025
|
+
const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
|
|
1026
|
+
console.log(`%c\u{1F52C} ${message}`, boxStyle);
|
|
1027
|
+
{
|
|
1028
|
+
console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
791
1031
|
|
|
792
|
-
const
|
|
1032
|
+
const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
|
|
1033
|
+
const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
|
|
793
1034
|
const createStore = (invalidate, advance) => {
|
|
794
1035
|
const rootStore = createWithEqualityFn((set, get) => {
|
|
795
1036
|
const position = new Vector3();
|
|
@@ -820,6 +1061,8 @@ const createStore = (invalidate, advance) => {
|
|
|
820
1061
|
gl: null,
|
|
821
1062
|
renderer: null,
|
|
822
1063
|
camera: null,
|
|
1064
|
+
frustum: new Frustum(),
|
|
1065
|
+
autoUpdateFrustum: true,
|
|
823
1066
|
raycaster: null,
|
|
824
1067
|
events: { priority: 1, enabled: true, connected: false },
|
|
825
1068
|
scene: null,
|
|
@@ -901,6 +1144,13 @@ const createStore = (invalidate, advance) => {
|
|
|
901
1144
|
initialHits: [],
|
|
902
1145
|
capturedMap: /* @__PURE__ */ new Map(),
|
|
903
1146
|
lastEvent: React.createRef(),
|
|
1147
|
+
// Visibility tracking (onFramed, onOccluded, onVisible)
|
|
1148
|
+
visibilityRegistry: /* @__PURE__ */ new Map(),
|
|
1149
|
+
// Occlusion system (WebGPU only)
|
|
1150
|
+
occlusionEnabled: false,
|
|
1151
|
+
occlusionObserver: null,
|
|
1152
|
+
occlusionCache: /* @__PURE__ */ new Map(),
|
|
1153
|
+
helperGroup: null,
|
|
904
1154
|
// Updates
|
|
905
1155
|
active: false,
|
|
906
1156
|
frames: 0,
|
|
@@ -986,7 +1236,15 @@ const createStore = (invalidate, advance) => {
|
|
|
986
1236
|
}
|
|
987
1237
|
if (camera !== oldCamera) {
|
|
988
1238
|
oldCamera = camera;
|
|
1239
|
+
const { rootScene } = rootStore.getState();
|
|
1240
|
+
if (camera && rootScene && !camera.parent) {
|
|
1241
|
+
rootScene.add(camera);
|
|
1242
|
+
}
|
|
989
1243
|
set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
|
|
1244
|
+
const currentState = rootStore.getState();
|
|
1245
|
+
if (currentState.autoUpdateFrustum && camera) {
|
|
1246
|
+
updateFrustum(camera, currentState.frustum);
|
|
1247
|
+
}
|
|
990
1248
|
}
|
|
991
1249
|
});
|
|
992
1250
|
rootStore.subscribe((state2) => invalidate(state2));
|
|
@@ -1350,6 +1608,10 @@ const _Scheduler = class _Scheduler {
|
|
|
1350
1608
|
__publicField(this, "jobStateListeners", /* @__PURE__ */ new Map());
|
|
1351
1609
|
__publicField(this, "pendingFrames", 0);
|
|
1352
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());
|
|
1353
1615
|
//* Core Loop Execution Methods ================================
|
|
1354
1616
|
/**
|
|
1355
1617
|
* Main RAF loop callback.
|
|
@@ -1372,6 +1634,12 @@ const _Scheduler = class _Scheduler {
|
|
|
1372
1634
|
});
|
|
1373
1635
|
this.phaseGraph = new PhaseGraph();
|
|
1374
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
|
+
}
|
|
1375
1643
|
/**
|
|
1376
1644
|
* Get the global scheduler instance (creates if doesn't exist).
|
|
1377
1645
|
* Uses HMR data to preserve instance across hot reloads.
|
|
@@ -1420,29 +1688,43 @@ const _Scheduler = class _Scheduler {
|
|
|
1420
1688
|
get isRunning() {
|
|
1421
1689
|
return this.loopState.running;
|
|
1422
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
|
+
}
|
|
1423
1701
|
//* Root Management Methods ================================
|
|
1424
1702
|
/**
|
|
1425
1703
|
* Register a root (Canvas) with the scheduler.
|
|
1426
1704
|
* The first root to register starts the RAF loop (if frameloop='always').
|
|
1427
1705
|
* @param {string} id - Unique identifier for this root
|
|
1428
|
-
* @param {
|
|
1706
|
+
* @param {RootOptions} [options] - Optional configuration with getState and onError callbacks
|
|
1429
1707
|
* @returns {() => void} Unsubscribe function to remove this root
|
|
1430
1708
|
*/
|
|
1431
|
-
registerRoot(id,
|
|
1709
|
+
registerRoot(id, options = {}) {
|
|
1432
1710
|
if (this.roots.has(id)) {
|
|
1433
1711
|
console.warn(`[Scheduler] Root "${id}" already registered`);
|
|
1434
1712
|
return () => this.unregisterRoot(id);
|
|
1435
1713
|
}
|
|
1436
1714
|
const entry = {
|
|
1437
1715
|
id,
|
|
1438
|
-
getState,
|
|
1716
|
+
getState: options.getState ?? (() => ({})),
|
|
1439
1717
|
jobs: /* @__PURE__ */ new Map(),
|
|
1440
1718
|
sortedJobs: [],
|
|
1441
1719
|
needsRebuild: false
|
|
1442
1720
|
};
|
|
1721
|
+
if (options.onError) {
|
|
1722
|
+
this.errorHandler = options.onError;
|
|
1723
|
+
}
|
|
1443
1724
|
this.roots.set(id, entry);
|
|
1444
|
-
if (this.roots.size === 1
|
|
1445
|
-
this.
|
|
1725
|
+
if (this.roots.size === 1) {
|
|
1726
|
+
this.notifyRootReady();
|
|
1727
|
+
if (this._frameloop === "always") this.start();
|
|
1446
1728
|
}
|
|
1447
1729
|
return () => this.unregisterRoot(id);
|
|
1448
1730
|
}
|
|
@@ -1462,7 +1744,60 @@ const _Scheduler = class _Scheduler {
|
|
|
1462
1744
|
this.roots.delete(id);
|
|
1463
1745
|
if (this.roots.size === 0) {
|
|
1464
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
|
+
};
|
|
1465
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);
|
|
1466
1801
|
}
|
|
1467
1802
|
//* Phase Management Methods ================================
|
|
1468
1803
|
/**
|
|
@@ -1822,9 +2157,9 @@ const _Scheduler = class _Scheduler {
|
|
|
1822
2157
|
const deltaMs = this.loopState.lastTime !== null ? now - this.loopState.lastTime : 0;
|
|
1823
2158
|
const delta = deltaMs / 1e3;
|
|
1824
2159
|
const elapsed = now - this.loopState.createdAt;
|
|
1825
|
-
const
|
|
2160
|
+
const providedState = root.getState?.() ?? {};
|
|
1826
2161
|
const frameState = {
|
|
1827
|
-
...
|
|
2162
|
+
...providedState,
|
|
1828
2163
|
time: now,
|
|
1829
2164
|
delta,
|
|
1830
2165
|
elapsed,
|
|
@@ -1834,6 +2169,7 @@ const _Scheduler = class _Scheduler {
|
|
|
1834
2169
|
job.callback(frameState, delta);
|
|
1835
2170
|
} catch (error) {
|
|
1836
2171
|
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
2172
|
+
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
1837
2173
|
}
|
|
1838
2174
|
}
|
|
1839
2175
|
/**
|
|
@@ -1875,7 +2211,7 @@ const _Scheduler = class _Scheduler {
|
|
|
1875
2211
|
/**
|
|
1876
2212
|
* Execute all jobs for a single root in sorted order.
|
|
1877
2213
|
* Rebuilds sorted job list if needed, then dispatches each job.
|
|
1878
|
-
* Errors are caught and propagated
|
|
2214
|
+
* Errors are caught and propagated via triggerError.
|
|
1879
2215
|
* @param {RootEntry} root - The root entry to tick
|
|
1880
2216
|
* @param {number} timestamp - RAF timestamp in milliseconds
|
|
1881
2217
|
* @param {number} delta - Time since last frame in seconds
|
|
@@ -1887,10 +2223,9 @@ const _Scheduler = class _Scheduler {
|
|
|
1887
2223
|
root.sortedJobs = rebuildSortedJobs(root.jobs, this.phaseGraph);
|
|
1888
2224
|
root.needsRebuild = false;
|
|
1889
2225
|
}
|
|
1890
|
-
const
|
|
1891
|
-
if (!rootState) return;
|
|
2226
|
+
const providedState = root.getState?.() ?? {};
|
|
1892
2227
|
const frameState = {
|
|
1893
|
-
...
|
|
2228
|
+
...providedState,
|
|
1894
2229
|
time: timestamp,
|
|
1895
2230
|
delta,
|
|
1896
2231
|
elapsed: this.loopState.elapsedTime / 1e3,
|
|
@@ -1903,7 +2238,7 @@ const _Scheduler = class _Scheduler {
|
|
|
1903
2238
|
job.callback(frameState, delta);
|
|
1904
2239
|
} catch (error) {
|
|
1905
2240
|
console.error(`[Scheduler] Error in job "${job.id}":`, error);
|
|
1906
|
-
|
|
2241
|
+
this.triggerError(error instanceof Error ? error : new Error(String(error)));
|
|
1907
2242
|
}
|
|
1908
2243
|
}
|
|
1909
2244
|
}
|
|
@@ -1988,8 +2323,11 @@ const _Scheduler = class _Scheduler {
|
|
|
1988
2323
|
return /* @__PURE__ */ new Set([value]);
|
|
1989
2324
|
}
|
|
1990
2325
|
};
|
|
1991
|
-
//* Static State & Methods (
|
|
1992
|
-
|
|
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"));
|
|
1993
2331
|
let Scheduler = _Scheduler;
|
|
1994
2332
|
const getScheduler = () => Scheduler.get();
|
|
1995
2333
|
if (hmrData) {
|
|
@@ -1997,11 +2335,9 @@ if (hmrData) {
|
|
|
1997
2335
|
}
|
|
1998
2336
|
|
|
1999
2337
|
function useFrame(callback, priorityOrOptions) {
|
|
2000
|
-
const store =
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
return state.internal.rootId;
|
|
2004
|
-
}, [store]);
|
|
2338
|
+
const store = React.useContext(context);
|
|
2339
|
+
const isInsideCanvas = store !== null;
|
|
2340
|
+
const scheduler = getScheduler();
|
|
2005
2341
|
const optionsKey = typeof priorityOrOptions === "number" ? `p:${priorityOrOptions}` : priorityOrOptions ? JSON.stringify({
|
|
2006
2342
|
id: priorityOrOptions.id,
|
|
2007
2343
|
phase: priorityOrOptions.phase,
|
|
@@ -2021,55 +2357,71 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2021
2357
|
const isLegacyPriority = typeof priorityOrOptions === "number" && priorityOrOptions > 0;
|
|
2022
2358
|
useIsomorphicLayoutEffect(() => {
|
|
2023
2359
|
if (!callback) return;
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
if (isLegacyPriority) {
|
|
2028
|
-
state.internal.priority++;
|
|
2029
|
-
let parentRoot = state.previousRoot;
|
|
2030
|
-
while (parentRoot) {
|
|
2031
|
-
const parentState = parentRoot.getState();
|
|
2032
|
-
if (parentState?.internal) parentState.internal.priority++;
|
|
2033
|
-
parentRoot = parentState?.previousRoot;
|
|
2034
|
-
}
|
|
2035
|
-
notifyDepreciated({
|
|
2036
|
-
heading: "useFrame with numeric priority is deprecated",
|
|
2037
|
-
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 })',
|
|
2038
|
-
link: "https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe"
|
|
2039
|
-
});
|
|
2040
|
-
}
|
|
2041
|
-
const wrappedCallback = (frameState, delta) => {
|
|
2042
|
-
const localState = store.getState();
|
|
2043
|
-
const mergedState = {
|
|
2044
|
-
...localState,
|
|
2045
|
-
time: frameState.time,
|
|
2046
|
-
delta: frameState.delta,
|
|
2047
|
-
elapsed: frameState.elapsed,
|
|
2048
|
-
frame: frameState.frame
|
|
2049
|
-
};
|
|
2050
|
-
callbackRef.current?.(mergedState, delta);
|
|
2051
|
-
};
|
|
2052
|
-
const unregister = scheduler.register(wrappedCallback, {
|
|
2053
|
-
id,
|
|
2054
|
-
rootId,
|
|
2055
|
-
...options
|
|
2056
|
-
});
|
|
2057
|
-
return () => {
|
|
2058
|
-
unregister();
|
|
2360
|
+
if (isInsideCanvas) {
|
|
2361
|
+
const state = store.getState();
|
|
2362
|
+
const rootId = state.internal.rootId;
|
|
2059
2363
|
if (isLegacyPriority) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
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
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
const wrappedCallback = (frameState, delta) => {
|
|
2378
|
+
const localState = store.getState();
|
|
2379
|
+
const mergedState = {
|
|
2380
|
+
...localState,
|
|
2381
|
+
time: frameState.time,
|
|
2382
|
+
delta: frameState.delta,
|
|
2383
|
+
elapsed: frameState.elapsed,
|
|
2384
|
+
frame: frameState.frame
|
|
2385
|
+
};
|
|
2386
|
+
callbackRef.current?.(mergedState, delta);
|
|
2387
|
+
};
|
|
2388
|
+
const unregister = scheduler.register(wrappedCallback, {
|
|
2389
|
+
id,
|
|
2390
|
+
rootId,
|
|
2391
|
+
...options
|
|
2392
|
+
});
|
|
2393
|
+
return () => {
|
|
2394
|
+
unregister();
|
|
2395
|
+
if (isLegacyPriority) {
|
|
2396
|
+
const currentState = store.getState();
|
|
2397
|
+
if (currentState.internal) {
|
|
2398
|
+
currentState.internal.priority--;
|
|
2399
|
+
let parentRoot = currentState.previousRoot;
|
|
2400
|
+
while (parentRoot) {
|
|
2401
|
+
const parentState = parentRoot.getState();
|
|
2402
|
+
if (parentState?.internal) parentState.internal.priority--;
|
|
2403
|
+
parentRoot = parentState?.previousRoot;
|
|
2404
|
+
}
|
|
2068
2405
|
}
|
|
2069
2406
|
}
|
|
2407
|
+
};
|
|
2408
|
+
} else {
|
|
2409
|
+
const registerOutside = () => {
|
|
2410
|
+
return scheduler.register((state, delta) => callbackRef.current?.(state, delta), { id, ...options });
|
|
2411
|
+
};
|
|
2412
|
+
if (scheduler.independent || scheduler.isReady) {
|
|
2413
|
+
return registerOutside();
|
|
2070
2414
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2415
|
+
let unregisterJob = null;
|
|
2416
|
+
const unsubReady = scheduler.onRootReady(() => {
|
|
2417
|
+
unregisterJob = registerOutside();
|
|
2418
|
+
});
|
|
2419
|
+
return () => {
|
|
2420
|
+
unsubReady();
|
|
2421
|
+
unregisterJob?.();
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
}, [store, scheduler, id, optionsKey, isLegacyPriority, isInsideCanvas]);
|
|
2073
2425
|
const isPaused = React.useSyncExternalStore(
|
|
2074
2426
|
// Subscribe function
|
|
2075
2427
|
React.useCallback(
|
|
@@ -2084,7 +2436,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2084
2436
|
React.useCallback(() => false, [])
|
|
2085
2437
|
);
|
|
2086
2438
|
const controls = React.useMemo(() => {
|
|
2087
|
-
const
|
|
2439
|
+
const scheduler2 = getScheduler();
|
|
2088
2440
|
return {
|
|
2089
2441
|
/** The job's unique ID */
|
|
2090
2442
|
id,
|
|
@@ -2092,7 +2444,7 @@ function useFrame(callback, priorityOrOptions) {
|
|
|
2092
2444
|
* Access to the global scheduler for frame loop control.
|
|
2093
2445
|
* Use for controlling the entire frame loop, adding phases, etc.
|
|
2094
2446
|
*/
|
|
2095
|
-
scheduler,
|
|
2447
|
+
scheduler: scheduler2,
|
|
2096
2448
|
/**
|
|
2097
2449
|
* Manually step this job only.
|
|
2098
2450
|
* Bypasses FPS limiting - always runs.
|
|
@@ -2340,6 +2692,16 @@ function useTextures() {
|
|
|
2340
2692
|
}, [store]);
|
|
2341
2693
|
}
|
|
2342
2694
|
|
|
2695
|
+
function useRenderTarget(width, height, options) {
|
|
2696
|
+
const isLegacy = useThree((s) => s.isLegacy);
|
|
2697
|
+
const size = useThree((s) => s.size);
|
|
2698
|
+
return useMemo(() => {
|
|
2699
|
+
const w = width ?? size.width;
|
|
2700
|
+
const h = height ?? size.height;
|
|
2701
|
+
return new RenderTarget(w, h, options);
|
|
2702
|
+
}, [width, height, size.width, size.height, options, isLegacy]);
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2343
2705
|
function useStore() {
|
|
2344
2706
|
const store = useContext(context);
|
|
2345
2707
|
if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
|
|
@@ -2391,7 +2753,7 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
|
|
|
2391
2753
|
getScheduler().step(timestamp);
|
|
2392
2754
|
}
|
|
2393
2755
|
|
|
2394
|
-
const version = "10.0.0-alpha.
|
|
2756
|
+
const version = "10.0.0-alpha.1";
|
|
2395
2757
|
const packageData = {
|
|
2396
2758
|
version: version};
|
|
2397
2759
|
|
|
@@ -13692,7 +14054,8 @@ function createReconciler(config) {
|
|
|
13692
14054
|
return reconciler2;
|
|
13693
14055
|
}
|
|
13694
14056
|
const NoEventPriority = 0;
|
|
13695
|
-
const
|
|
14057
|
+
const R3F_CATALOGUE = Symbol.for("@react-three/fiber.catalogue");
|
|
14058
|
+
const catalogue = globalThis[R3F_CATALOGUE] ?? (globalThis[R3F_CATALOGUE] = {});
|
|
13696
14059
|
const PREFIX_REGEX = /^three(?=[A-Z])/;
|
|
13697
14060
|
const toPascalCase = (type) => `${type[0].toUpperCase()}${type.slice(1)}`;
|
|
13698
14061
|
let i = 0;
|
|
@@ -14190,7 +14553,9 @@ function createRoot(canvas) {
|
|
|
14190
14553
|
camera: cameraOptions,
|
|
14191
14554
|
onPointerMissed,
|
|
14192
14555
|
onDragOverMissed,
|
|
14193
|
-
onDropMissed
|
|
14556
|
+
onDropMissed,
|
|
14557
|
+
autoUpdateFrustum = true,
|
|
14558
|
+
occlusion = false
|
|
14194
14559
|
} = props;
|
|
14195
14560
|
let state = store.getState();
|
|
14196
14561
|
const defaultGPUProps = {
|
|
@@ -14207,7 +14572,9 @@ function createRoot(canvas) {
|
|
|
14207
14572
|
let renderer = state.internal.actualRenderer;
|
|
14208
14573
|
if (!state.internal.actualRenderer) {
|
|
14209
14574
|
renderer = await resolveRenderer(rendererConfig, defaultGPUProps, WebGPURenderer);
|
|
14210
|
-
|
|
14575
|
+
if (!renderer.hasInitialized?.()) {
|
|
14576
|
+
await renderer.init();
|
|
14577
|
+
}
|
|
14211
14578
|
const backend = renderer.backend;
|
|
14212
14579
|
const isWebGPUBackend = backend && "isWebGPUBackend" in backend;
|
|
14213
14580
|
state.internal.actualRenderer = renderer;
|
|
@@ -14255,6 +14622,8 @@ function createRoot(canvas) {
|
|
|
14255
14622
|
rootScene: scene,
|
|
14256
14623
|
internal: { ...prev.internal, container: scene }
|
|
14257
14624
|
}));
|
|
14625
|
+
const camera = state.camera;
|
|
14626
|
+
if (camera && !camera.parent) scene.add(camera);
|
|
14258
14627
|
}
|
|
14259
14628
|
if (events && !state.events.handlers) {
|
|
14260
14629
|
state.set({ events: events(store) });
|
|
@@ -14282,6 +14651,10 @@ function createRoot(canvas) {
|
|
|
14282
14651
|
if (!state.onPointerMissed) state.set({ onPointerMissed });
|
|
14283
14652
|
if (!state.onDragOverMissed) state.set({ onDragOverMissed });
|
|
14284
14653
|
if (!state.onDropMissed) state.set({ onDropMissed });
|
|
14654
|
+
if (state.autoUpdateFrustum !== autoUpdateFrustum) state.set({ autoUpdateFrustum });
|
|
14655
|
+
if (occlusion && !state.internal.occlusionEnabled) {
|
|
14656
|
+
enableOcclusion(store);
|
|
14657
|
+
}
|
|
14285
14658
|
if (performance && !is.equ(performance, lastConfiguredProps.performance, shallowLoose)) {
|
|
14286
14659
|
state.set((state2) => ({ performance: { ...state2.performance, ...performance } }));
|
|
14287
14660
|
lastConfiguredProps.performance = performance;
|
|
@@ -14354,7 +14727,37 @@ function createRoot(canvas) {
|
|
|
14354
14727
|
const rootId = state.internal.rootId;
|
|
14355
14728
|
if (!rootId) {
|
|
14356
14729
|
const newRootId = scheduler.generateRootId();
|
|
14357
|
-
const unregisterRoot = scheduler.registerRoot(newRootId,
|
|
14730
|
+
const unregisterRoot = scheduler.registerRoot(newRootId, {
|
|
14731
|
+
getState: () => store.getState(),
|
|
14732
|
+
onError: (err) => store.getState().setError(err)
|
|
14733
|
+
});
|
|
14734
|
+
const unregisterFrustum = scheduler.register(
|
|
14735
|
+
() => {
|
|
14736
|
+
const state2 = store.getState();
|
|
14737
|
+
if (state2.autoUpdateFrustum && state2.camera) {
|
|
14738
|
+
updateFrustum(state2.camera, state2.frustum);
|
|
14739
|
+
}
|
|
14740
|
+
},
|
|
14741
|
+
{
|
|
14742
|
+
id: `${newRootId}_frustum`,
|
|
14743
|
+
rootId: newRootId,
|
|
14744
|
+
phase: "preRender",
|
|
14745
|
+
system: true
|
|
14746
|
+
}
|
|
14747
|
+
);
|
|
14748
|
+
const unregisterVisibility = scheduler.register(
|
|
14749
|
+
() => {
|
|
14750
|
+
const state2 = store.getState();
|
|
14751
|
+
checkVisibility(state2);
|
|
14752
|
+
},
|
|
14753
|
+
{
|
|
14754
|
+
id: `${newRootId}_visibility`,
|
|
14755
|
+
rootId: newRootId,
|
|
14756
|
+
phase: "preRender",
|
|
14757
|
+
system: true,
|
|
14758
|
+
after: `${newRootId}_frustum`
|
|
14759
|
+
}
|
|
14760
|
+
);
|
|
14358
14761
|
const unregisterRender = scheduler.register(
|
|
14359
14762
|
() => {
|
|
14360
14763
|
const state2 = store.getState();
|
|
@@ -14382,6 +14785,8 @@ function createRoot(canvas) {
|
|
|
14382
14785
|
rootId: newRootId,
|
|
14383
14786
|
unregisterRoot: () => {
|
|
14384
14787
|
unregisterRoot();
|
|
14788
|
+
unregisterFrustum();
|
|
14789
|
+
unregisterVisibility();
|
|
14385
14790
|
unregisterRender();
|
|
14386
14791
|
},
|
|
14387
14792
|
scheduler
|
|
@@ -14439,6 +14844,7 @@ function unmountComponentAtNode(canvas, callback) {
|
|
|
14439
14844
|
const unregisterRoot = state.internal.unregisterRoot;
|
|
14440
14845
|
if (unregisterRoot) unregisterRoot();
|
|
14441
14846
|
state.events.disconnect?.();
|
|
14847
|
+
cleanupHelperGroup(root.store);
|
|
14442
14848
|
renderer?.renderLists?.dispose?.();
|
|
14443
14849
|
renderer?.forceContextLoss?.();
|
|
14444
14850
|
if (renderer?.xr) state.xr.disconnect();
|
|
@@ -14618,7 +15024,22 @@ function CanvasImpl({
|
|
|
14618
15024
|
effectActiveRef.current = true;
|
|
14619
15025
|
const canvas = canvasRef.current;
|
|
14620
15026
|
if (containerRect.width > 0 && containerRect.height > 0 && canvas) {
|
|
14621
|
-
if (!root.current)
|
|
15027
|
+
if (!root.current) {
|
|
15028
|
+
root.current = createRoot(canvas);
|
|
15029
|
+
notifyAlpha({
|
|
15030
|
+
message: "React Three Fiber v10 is in ALPHA - expect breaking changes",
|
|
15031
|
+
link: "https://github.com/pmndrs/react-three-fiber/discussions"
|
|
15032
|
+
});
|
|
15033
|
+
const rootEntry = _roots.get(canvas);
|
|
15034
|
+
if (rootEntry?.store) {
|
|
15035
|
+
if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
|
|
15036
|
+
unsubscribeErrorRef.current = rootEntry.store.subscribe((state) => {
|
|
15037
|
+
if (state.error && effectActiveRef.current) {
|
|
15038
|
+
setError(state.error);
|
|
15039
|
+
}
|
|
15040
|
+
});
|
|
15041
|
+
}
|
|
15042
|
+
}
|
|
14622
15043
|
async function run() {
|
|
14623
15044
|
if (!effectActiveRef.current || !root.current) return;
|
|
14624
15045
|
await root.current.configure({
|
|
@@ -14659,15 +15080,9 @@ function CanvasImpl({
|
|
|
14659
15080
|
}
|
|
14660
15081
|
});
|
|
14661
15082
|
if (!effectActiveRef.current || !root.current) return;
|
|
14662
|
-
|
|
15083
|
+
root.current.render(
|
|
14663
15084
|
/* @__PURE__ */ jsx(Bridge, { children: /* @__PURE__ */ jsx(ErrorBoundary, { set: setError, children: /* @__PURE__ */ jsx(React.Suspense, { fallback: /* @__PURE__ */ jsx(Block, { set: setBlock }), children: children ?? null }) }) })
|
|
14664
15085
|
);
|
|
14665
|
-
if (unsubscribeErrorRef.current) unsubscribeErrorRef.current();
|
|
14666
|
-
unsubscribeErrorRef.current = store.subscribe((state) => {
|
|
14667
|
-
if (state.error && effectActiveRef.current) {
|
|
14668
|
-
setError(state.error);
|
|
14669
|
-
}
|
|
14670
|
-
});
|
|
14671
15086
|
}
|
|
14672
15087
|
run();
|
|
14673
15088
|
}
|
|
@@ -14788,11 +15203,46 @@ function useCompareMemoize(value, deep) {
|
|
|
14788
15203
|
const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
|
|
14789
15204
|
function useUniforms(creatorOrScope, scope) {
|
|
14790
15205
|
const store = useStore();
|
|
15206
|
+
const removeUniforms2 = useCallback(
|
|
15207
|
+
(names, targetScope) => {
|
|
15208
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15209
|
+
store.setState((state) => {
|
|
15210
|
+
if (targetScope) {
|
|
15211
|
+
const currentScope = { ...state.uniforms[targetScope] };
|
|
15212
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15213
|
+
return { uniforms: { ...state.uniforms, [targetScope]: currentScope } };
|
|
15214
|
+
}
|
|
15215
|
+
const uniforms2 = { ...state.uniforms };
|
|
15216
|
+
for (const name of nameArray) if (isUniformNode$1(uniforms2[name])) delete uniforms2[name];
|
|
15217
|
+
return { uniforms: uniforms2 };
|
|
15218
|
+
});
|
|
15219
|
+
},
|
|
15220
|
+
[store]
|
|
15221
|
+
);
|
|
15222
|
+
const clearUniforms = useCallback(
|
|
15223
|
+
(targetScope) => {
|
|
15224
|
+
store.setState((state) => {
|
|
15225
|
+
if (targetScope && targetScope !== "root") {
|
|
15226
|
+
const { [targetScope]: _, ...rest } = state.uniforms;
|
|
15227
|
+
return { uniforms: rest };
|
|
15228
|
+
}
|
|
15229
|
+
if (targetScope === "root") {
|
|
15230
|
+
const uniforms2 = {};
|
|
15231
|
+
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
15232
|
+
if (!isUniformNode$1(value)) uniforms2[key] = value;
|
|
15233
|
+
}
|
|
15234
|
+
return { uniforms: uniforms2 };
|
|
15235
|
+
}
|
|
15236
|
+
return { uniforms: {} };
|
|
15237
|
+
});
|
|
15238
|
+
},
|
|
15239
|
+
[store]
|
|
15240
|
+
);
|
|
14791
15241
|
const inputForMemoization = useMemo(() => {
|
|
14792
15242
|
return is.fun(creatorOrScope) ? creatorOrScope(store.getState()) : creatorOrScope;
|
|
14793
15243
|
}, [creatorOrScope, store]);
|
|
14794
15244
|
const memoizedInput = useCompareMemoize(inputForMemoization);
|
|
14795
|
-
|
|
15245
|
+
const uniforms = useMemo(() => {
|
|
14796
15246
|
const state = store.getState();
|
|
14797
15247
|
const set = store.setState;
|
|
14798
15248
|
if (memoizedInput === void 0) {
|
|
@@ -14838,44 +15288,26 @@ function useUniforms(creatorOrScope, scope) {
|
|
|
14838
15288
|
set((s) => ({
|
|
14839
15289
|
uniforms: {
|
|
14840
15290
|
...s.uniforms,
|
|
14841
|
-
[scope]: {
|
|
14842
|
-
...s.uniforms[scope],
|
|
14843
|
-
...result
|
|
14844
|
-
}
|
|
15291
|
+
[scope]: { ...s.uniforms[scope], ...result }
|
|
14845
15292
|
}
|
|
14846
15293
|
}));
|
|
14847
15294
|
} else {
|
|
14848
|
-
set((s) => ({
|
|
14849
|
-
uniforms: {
|
|
14850
|
-
...s.uniforms,
|
|
14851
|
-
...result
|
|
14852
|
-
}
|
|
14853
|
-
}));
|
|
15295
|
+
set((s) => ({ uniforms: { ...s.uniforms, ...result } }));
|
|
14854
15296
|
}
|
|
14855
15297
|
}
|
|
14856
15298
|
return result;
|
|
14857
15299
|
}, [store, memoizedInput, scope]);
|
|
15300
|
+
return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms };
|
|
14858
15301
|
}
|
|
14859
15302
|
function removeUniforms(set, names, scope) {
|
|
14860
15303
|
set((state) => {
|
|
14861
15304
|
if (scope) {
|
|
14862
15305
|
const currentScope = { ...state.uniforms[scope] };
|
|
14863
|
-
for (const name of names)
|
|
14864
|
-
|
|
14865
|
-
}
|
|
14866
|
-
return {
|
|
14867
|
-
uniforms: {
|
|
14868
|
-
...state.uniforms,
|
|
14869
|
-
[scope]: currentScope
|
|
14870
|
-
}
|
|
14871
|
-
};
|
|
15306
|
+
for (const name of names) delete currentScope[name];
|
|
15307
|
+
return { uniforms: { ...state.uniforms, [scope]: currentScope } };
|
|
14872
15308
|
}
|
|
14873
15309
|
const uniforms = { ...state.uniforms };
|
|
14874
|
-
for (const name of names)
|
|
14875
|
-
if (isUniformNode$1(uniforms[name])) {
|
|
14876
|
-
delete uniforms[name];
|
|
14877
|
-
}
|
|
14878
|
-
}
|
|
15310
|
+
for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
|
|
14879
15311
|
return { uniforms };
|
|
14880
15312
|
});
|
|
14881
15313
|
}
|
|
@@ -14889,9 +15321,7 @@ function clearRootUniforms(set) {
|
|
|
14889
15321
|
set((state) => {
|
|
14890
15322
|
const uniforms = {};
|
|
14891
15323
|
for (const [key, value] of Object.entries(state.uniforms)) {
|
|
14892
|
-
if (!isUniformNode$1(value))
|
|
14893
|
-
uniforms[key] = value;
|
|
14894
|
-
}
|
|
15324
|
+
if (!isUniformNode$1(value)) uniforms[key] = value;
|
|
14895
15325
|
}
|
|
14896
15326
|
return { uniforms };
|
|
14897
15327
|
});
|
|
@@ -14966,7 +15396,42 @@ function useUniform(name, value) {
|
|
|
14966
15396
|
const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
|
|
14967
15397
|
function useNodes(creatorOrScope, scope) {
|
|
14968
15398
|
const store = useStore();
|
|
14969
|
-
|
|
15399
|
+
const removeNodes2 = useCallback(
|
|
15400
|
+
(names, targetScope) => {
|
|
15401
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
15402
|
+
store.setState((state) => {
|
|
15403
|
+
if (targetScope) {
|
|
15404
|
+
const currentScope = { ...state.nodes[targetScope] };
|
|
15405
|
+
for (const name of nameArray) delete currentScope[name];
|
|
15406
|
+
return { nodes: { ...state.nodes, [targetScope]: currentScope } };
|
|
15407
|
+
}
|
|
15408
|
+
const nodes2 = { ...state.nodes };
|
|
15409
|
+
for (const name of nameArray) if (isTSLNode(nodes2[name])) delete nodes2[name];
|
|
15410
|
+
return { nodes: nodes2 };
|
|
15411
|
+
});
|
|
15412
|
+
},
|
|
15413
|
+
[store]
|
|
15414
|
+
);
|
|
15415
|
+
const clearNodes = useCallback(
|
|
15416
|
+
(targetScope) => {
|
|
15417
|
+
store.setState((state) => {
|
|
15418
|
+
if (targetScope && targetScope !== "root") {
|
|
15419
|
+
const { [targetScope]: _, ...rest } = state.nodes;
|
|
15420
|
+
return { nodes: rest };
|
|
15421
|
+
}
|
|
15422
|
+
if (targetScope === "root") {
|
|
15423
|
+
const nodes2 = {};
|
|
15424
|
+
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15425
|
+
if (!isTSLNode(value)) nodes2[key] = value;
|
|
15426
|
+
}
|
|
15427
|
+
return { nodes: nodes2 };
|
|
15428
|
+
}
|
|
15429
|
+
return { nodes: {} };
|
|
15430
|
+
});
|
|
15431
|
+
},
|
|
15432
|
+
[store]
|
|
15433
|
+
);
|
|
15434
|
+
const nodes = useMemo(() => {
|
|
14970
15435
|
const state = store.getState();
|
|
14971
15436
|
const set = store.setState;
|
|
14972
15437
|
if (creatorOrScope === void 0) {
|
|
@@ -14974,9 +15439,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
14974
15439
|
}
|
|
14975
15440
|
if (typeof creatorOrScope === "string") {
|
|
14976
15441
|
const scopeData = state.nodes[creatorOrScope];
|
|
14977
|
-
if (scopeData && !isTSLNode(scopeData))
|
|
14978
|
-
return scopeData;
|
|
14979
|
-
}
|
|
15442
|
+
if (scopeData && !isTSLNode(scopeData)) return scopeData;
|
|
14980
15443
|
return {};
|
|
14981
15444
|
}
|
|
14982
15445
|
const creator = creatorOrScope;
|
|
@@ -14989,9 +15452,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
14989
15452
|
if (currentScope[name]) {
|
|
14990
15453
|
result[name] = currentScope[name];
|
|
14991
15454
|
} else {
|
|
14992
|
-
if (typeof node.label === "function") {
|
|
14993
|
-
node.setName(`${scope}.${name}`);
|
|
14994
|
-
}
|
|
15455
|
+
if (typeof node.label === "function") node.setName(`${scope}.${name}`);
|
|
14995
15456
|
result[name] = node;
|
|
14996
15457
|
hasNewNodes = true;
|
|
14997
15458
|
}
|
|
@@ -15000,10 +15461,7 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15000
15461
|
set((s) => ({
|
|
15001
15462
|
nodes: {
|
|
15002
15463
|
...s.nodes,
|
|
15003
|
-
[scope]: {
|
|
15004
|
-
...s.nodes[scope],
|
|
15005
|
-
...result
|
|
15006
|
-
}
|
|
15464
|
+
[scope]: { ...s.nodes[scope], ...result }
|
|
15007
15465
|
}
|
|
15008
15466
|
}));
|
|
15009
15467
|
}
|
|
@@ -15014,44 +15472,27 @@ function useNodes(creatorOrScope, scope) {
|
|
|
15014
15472
|
if (existing && isTSLNode(existing)) {
|
|
15015
15473
|
result[name] = existing;
|
|
15016
15474
|
} else {
|
|
15017
|
-
if (typeof node.label === "function")
|
|
15018
|
-
node.setName(name);
|
|
15019
|
-
}
|
|
15475
|
+
if (typeof node.label === "function") node.setName(name);
|
|
15020
15476
|
result[name] = node;
|
|
15021
15477
|
hasNewNodes = true;
|
|
15022
15478
|
}
|
|
15023
15479
|
}
|
|
15024
15480
|
if (hasNewNodes) {
|
|
15025
|
-
set((s) => ({
|
|
15026
|
-
nodes: {
|
|
15027
|
-
...s.nodes,
|
|
15028
|
-
...result
|
|
15029
|
-
}
|
|
15030
|
-
}));
|
|
15481
|
+
set((s) => ({ nodes: { ...s.nodes, ...result } }));
|
|
15031
15482
|
}
|
|
15032
15483
|
return result;
|
|
15033
15484
|
}, [store, typeof creatorOrScope === "string" ? creatorOrScope : scope]);
|
|
15485
|
+
return { ...nodes, removeNodes: removeNodes2, clearNodes };
|
|
15034
15486
|
}
|
|
15035
15487
|
function removeNodes(set, names, scope) {
|
|
15036
15488
|
set((state) => {
|
|
15037
15489
|
if (scope) {
|
|
15038
15490
|
const currentScope = { ...state.nodes[scope] };
|
|
15039
|
-
for (const name of names)
|
|
15040
|
-
|
|
15041
|
-
}
|
|
15042
|
-
return {
|
|
15043
|
-
nodes: {
|
|
15044
|
-
...state.nodes,
|
|
15045
|
-
[scope]: currentScope
|
|
15046
|
-
}
|
|
15047
|
-
};
|
|
15491
|
+
for (const name of names) delete currentScope[name];
|
|
15492
|
+
return { nodes: { ...state.nodes, [scope]: currentScope } };
|
|
15048
15493
|
}
|
|
15049
15494
|
const nodes = { ...state.nodes };
|
|
15050
|
-
for (const name of names)
|
|
15051
|
-
if (isTSLNode(nodes[name])) {
|
|
15052
|
-
delete nodes[name];
|
|
15053
|
-
}
|
|
15054
|
-
}
|
|
15495
|
+
for (const name of names) if (isTSLNode(nodes[name])) delete nodes[name];
|
|
15055
15496
|
return { nodes };
|
|
15056
15497
|
});
|
|
15057
15498
|
}
|
|
@@ -15065,9 +15506,7 @@ function clearRootNodes(set) {
|
|
|
15065
15506
|
set((state) => {
|
|
15066
15507
|
const nodes = {};
|
|
15067
15508
|
for (const [key, value] of Object.entries(state.nodes)) {
|
|
15068
|
-
if (!isTSLNode(value))
|
|
15069
|
-
nodes[key] = value;
|
|
15070
|
-
}
|
|
15509
|
+
if (!isTSLNode(value)) nodes[key] = value;
|
|
15071
15510
|
}
|
|
15072
15511
|
return { nodes };
|
|
15073
15512
|
});
|
|
@@ -15174,4 +15613,4 @@ function usePostProcessing(mainCB, setupCB) {
|
|
|
15174
15613
|
|
|
15175
15614
|
extend(THREE);
|
|
15176
15615
|
|
|
15177
|
-
export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };
|
|
15616
|
+
export { Block, Canvas, ErrorBoundary, IsObject, R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, REACT_INTERNAL_PROPS, RESERVED_PROPS, Scheduler, Texture, _roots, act, addAfterEffect, addEffect, addTail, advance, applyProps, attach, buildGraph, calculateDpr, clearNodeScope, clearRootNodes, clearRootUniforms, clearScope, context, createEvents, createPointerEvents, createPortal, createRoot, createStore, createTextureOperations, detach, diffProps, dispose, createPointerEvents as events, extend, findInitialRoot, flushSync, getInstanceProps, getRootState, getScheduler, getUuidPrefix, hasConstructor, invalidate, invalidateInstance, is, isColorRepresentation, isCopyable, isObject3D, isOrthographicCamera, isRef, isRenderer, isTexture, isVectorLike, prepare, reconciler, removeInteractivity, removeNodes, removeUniforms, resolve, unmountComponentAtNode, updateCamera, updateFrustum, useBridge, useFrame, useGraph, useInstanceHandle, useIsomorphicLayoutEffect, useLoader, useLocalNodes, useMutableCallback, useNodes, usePostProcessing, useRenderTarget, useStore, useTexture, useTextures, useThree, useUniform, useUniforms };
|