@react-three/fiber 10.0.0-canary.4bc4a97 → 10.0.0-canary.4bf885a

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.
@@ -1,9 +1,8 @@
1
1
  import * as webgpu from 'three/webgpu';
2
- import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, Vector3, Vector2, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color, Vector4, RenderPipeline } from 'three/webgpu';
3
- import { Inspector } from 'three/addons/inspector/Inspector.js';
2
+ import { CubeRenderTarget, RenderTarget, Frustum, Matrix4, Group, BoxGeometry, MeshBasicNodeMaterial, Mesh, Node, NodeUpdateType, Layers, ShaderMaterial, Uniform, SRGBColorSpace, RGBAFormat, UnsignedByteType, TextureLoader, Texture as Texture$1, CubeReflectionMapping, EquirectangularReflectionMapping, CubeTextureLoader, Vector3, Vector2, CanvasTarget, Raycaster, OrthographicCamera, PerspectiveCamera, Scene, PCFShadowMap, VSMShadowMap, BasicShadowMap, ACESFilmicToneMapping, WebGPURenderer, HalfFloatType, Color, Vector4, RenderPipeline } from 'three/webgpu';
4
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
4
  import * as React from 'react';
6
- import React__default, { useRef, useMemo, useLayoutEffect, useEffect, useContext, useImperativeHandle, useCallback, useState } from 'react';
5
+ import React__default, { useContext, useRef, useMemo, useLayoutEffect, useEffect, useImperativeHandle, useCallback, useState } from 'react';
7
6
  import useMeasure from 'react-use-measure';
8
7
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
9
8
  import { createWithEqualityFn } from 'zustand/traditional';
@@ -12,6 +11,7 @@ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
12
11
  import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
13
12
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
14
13
  import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
14
+ import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js';
15
15
  import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
16
16
  import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
17
17
  import { GainMapLoader } from '@monogrid/gainmap-js';
@@ -34,6 +34,10 @@ function _mergeNamespaces(n, m) {
34
34
 
35
35
  const R3F_BUILD_LEGACY = false;
36
36
  const R3F_BUILD_WEBGPU = true;
37
+ async function loadInspector() {
38
+ const { Inspector } = await import('three/addons/inspector/Inspector.js');
39
+ return Inspector;
40
+ }
37
41
  const WebGLRenderer = class WebGLRenderer2 {
38
42
  constructor() {
39
43
  throw new Error(
@@ -47,13 +51,13 @@ const WebGLCubeRenderTarget = null;
47
51
  const THREE = /*#__PURE__*/_mergeNamespaces({
48
52
  __proto__: null,
49
53
  CubeRenderTargetCompat: CubeRenderTarget,
50
- Inspector: Inspector,
51
54
  R3F_BUILD_LEGACY: R3F_BUILD_LEGACY,
52
55
  R3F_BUILD_WEBGPU: R3F_BUILD_WEBGPU,
53
56
  RenderTargetCompat: RenderTarget,
54
57
  WebGLCubeRenderTarget: WebGLCubeRenderTarget,
55
58
  WebGLRenderTarget: WebGLRenderTarget,
56
- WebGLRenderer: WebGLRenderer
59
+ WebGLRenderer: WebGLRenderer,
60
+ loadInspector: loadInspector
57
61
  }, [webgpu]);
58
62
 
59
63
  const primaryRegistry = /* @__PURE__ */ new Map();
@@ -300,7 +304,6 @@ function prepare(target, root, type, props) {
300
304
  }
301
305
  function invalidateInstance(instance) {
302
306
  if (!instance.parent) return;
303
- instance.props.onUpdate?.(instance.object);
304
307
  const state = instance.root?.getState?.();
305
308
  if (state && state.internal.frames === 0) state.invalidate();
306
309
  }
@@ -516,8 +519,10 @@ function hasVisibilityHandlers(handlers) {
516
519
  }
517
520
 
518
521
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
519
- function fromRef(ref) {
520
- return { [FROM_REF]: ref };
522
+ function fromRef(ref, transform) {
523
+ const marker = { [FROM_REF]: ref };
524
+ if (transform) marker.transform = transform;
525
+ return marker;
521
526
  }
522
527
  function isFromRef(value) {
523
528
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -531,6 +536,63 @@ function isOnce(value) {
531
536
  return value !== null && typeof value === "object" && ONCE in value;
532
537
  }
533
538
 
539
+ const shownNotices = /* @__PURE__ */ new Set();
540
+ function notifyDepreciated({ heading, body, link }) {
541
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
542
+ return;
543
+ }
544
+ if (shownNotices.has(heading)) return;
545
+ shownNotices.add(heading);
546
+ const caller = getCallerFrame();
547
+ if (caller) {
548
+ console.log();
549
+ }
550
+ const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
551
+ if (caller) {
552
+ console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
553
+ }
554
+ if (body || link) {
555
+ let message = "";
556
+ if (body) message += body;
557
+ if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
558
+ console.warn(`%c${message}`, "font-weight: bold;");
559
+ }
560
+ }
561
+ function getCallerFrame(depth = 3) {
562
+ const stack = new Error().stack;
563
+ if (!stack) return null;
564
+ const lines = stack.split("\n");
565
+ const frame = lines[depth];
566
+ if (!frame) return null;
567
+ let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
568
+ if (!match) {
569
+ match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
570
+ }
571
+ if (!match) return null;
572
+ const [, fn, url, line] = match;
573
+ return {
574
+ functionName: fn ?? "<anonymous>",
575
+ location: formatLocation(url, Number(line))
576
+ };
577
+ }
578
+ function formatLocation(url, line) {
579
+ const clean = url.split("?")[0];
580
+ const file = clean.split("/").pop() ?? clean;
581
+ return `${file}:${line}`;
582
+ }
583
+ function notifyAlpha({ message, link }) {
584
+ if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
585
+ return;
586
+ }
587
+ if (shownNotices.has(message)) return;
588
+ shownNotices.add(message);
589
+ const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
590
+ console.log(`%c\u{1F52C} ${message}`, boxStyle);
591
+ {
592
+ console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
593
+ }
594
+ }
595
+
534
596
  const RESERVED_PROPS = [
535
597
  "children",
536
598
  "key",
@@ -540,7 +602,6 @@ const RESERVED_PROPS = [
540
602
  "dispose",
541
603
  "attach",
542
604
  "object",
543
- "onUpdate",
544
605
  // Behavior flags
545
606
  "dispose"
546
607
  ];
@@ -618,11 +679,9 @@ function diffProps(instance, newProps) {
618
679
  for (const prop in instance.props) {
619
680
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
620
681
  const { root, key } = resolve(instance.object, prop);
621
- if (root.constructor && root.constructor.length === 0) {
682
+ if (typeof root?.constructor === "function") {
622
683
  const ctor = getMemoizedPrototype(root);
623
684
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
624
- } else {
625
- changedProps[prop] = 0;
626
685
  }
627
686
  }
628
687
  return changedProps;
@@ -634,6 +693,13 @@ function applyProps(object, props) {
634
693
  for (const prop in props) {
635
694
  const value = props[prop];
636
695
  if (RESERVED_PROPS.includes(prop)) continue;
696
+ if (prop === "onUpdate") {
697
+ notifyDepreciated({
698
+ heading: "onUpdate is no longer an R3F prop",
699
+ body: "v10 removed the onUpdate hook: R3F no longer calls it after prop updates. The value is assigned to the object by name. On a Texture that sets three's Texture.onUpdate, which the renderer calls after a GPU upload, not after prop changes. On a node it replaces the Node.onUpdate() method. On other objects it is never called.\n\nUse an effect keyed on the props that change, read the object in useFrame, or use a ref callback.",
700
+ link: "https://docs.pmnd.rs/react-three-fiber/migration/v10#onupdate-removed"
701
+ });
702
+ }
637
703
  if (instance && EVENT_REGEX.test(prop)) {
638
704
  if (typeof value === "function") instance.handlers[prop] = value;
639
705
  else delete instance.handlers[prop];
@@ -705,380 +771,51 @@ function applyProps(object, props) {
705
771
  root[key].format === RGBAFormat && root[key].type === UnsignedByteType) {
706
772
  root[key].colorSpace = rootState.textureColorSpace;
707
773
  }
708
- }
709
- }
710
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
711
- const object2 = instance.object;
712
- const index = rootState.internal.interaction.indexOf(object2);
713
- if (index > -1) rootState.internal.interaction.splice(index, 1);
714
- if (instance.eventCount && object2.raycast !== null) {
715
- rootState.internal.interaction.push(object2);
716
- }
717
- }
718
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
719
- const object2 = instance.object;
720
- const root = findInitialRoot(instance);
721
- const visibilityHandlers = {
722
- onFramed: instance.handlers.onFramed,
723
- onOccluded: instance.handlers.onOccluded,
724
- onVisible: instance.handlers.onVisible
725
- };
726
- if (hasVisibilityHandlers(visibilityHandlers)) {
727
- if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
728
- registerVisibility(root, object2, visibilityHandlers);
729
- }
730
- } else {
731
- unregisterVisibility(root, object2);
732
- }
733
- }
734
- if (instance && instance.props.attach === void 0) {
735
- if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
736
- else if (instance.object.isMaterial) instance.props.attach = "material";
737
- }
738
- if (instance) invalidateInstance(instance);
739
- return object;
740
- }
741
-
742
- const shownNotices = /* @__PURE__ */ new Set();
743
- function notifyDepreciated({ heading, body, link }) {
744
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
745
- return;
746
- }
747
- if (shownNotices.has(heading)) return;
748
- shownNotices.add(heading);
749
- const caller = getCallerFrame();
750
- if (caller) {
751
- console.log();
752
- }
753
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
754
- if (caller) {
755
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
756
- }
757
- if (body || link) {
758
- let message = "";
759
- if (body) message += body;
760
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
761
- console.warn(`%c${message}`, "font-weight: bold;");
762
- }
763
- }
764
- function getCallerFrame(depth = 3) {
765
- const stack = new Error().stack;
766
- if (!stack) return null;
767
- const lines = stack.split("\n");
768
- const frame = lines[depth];
769
- if (!frame) return null;
770
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
771
- if (!match) {
772
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
773
- }
774
- if (!match) return null;
775
- const [, fn, url, line] = match;
776
- return {
777
- functionName: fn ?? "<anonymous>",
778
- location: formatLocation(url, Number(line))
779
- };
780
- }
781
- function formatLocation(url, line) {
782
- const clean = url.split("?")[0];
783
- const file = clean.split("/").pop() ?? clean;
784
- return `${file}:${line}`;
785
- }
786
- function notifyAlpha({ message, link }) {
787
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
788
- return;
789
- }
790
- if (shownNotices.has(message)) return;
791
- shownNotices.add(message);
792
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
793
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
794
- {
795
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
796
- }
797
- }
798
-
799
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
800
- function isInternalRendererAccess(stack, callerDepth = 2) {
801
- if (!stack) return false;
802
- const caller = stack.split("\n")[callerDepth] ?? "";
803
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
804
- }
805
-
806
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
807
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
808
- const createStore = (invalidate, advance) => {
809
- const rootStore = createWithEqualityFn((set, get) => {
810
- const position = new Vector3();
811
- const defaultTarget = new Vector3();
812
- const tempTarget = new Vector3();
813
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
814
- const { width, height, top, left } = size;
815
- const aspect = width / height;
816
- if (target.isVector3) tempTarget.copy(target);
817
- else tempTarget.set(...target);
818
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
819
- if (isOrthographicCamera(camera)) {
820
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
821
- } else {
822
- const fov = camera.fov * Math.PI / 180;
823
- const h = 2 * Math.tan(fov / 2) * distance;
824
- const w = h * (width / height);
825
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
826
- }
827
- }
828
- let performanceTimeout = void 0;
829
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
830
- const pointer = new Vector2();
831
- const rootState = {
832
- set,
833
- get,
834
- // Mock objects that have to be configured
835
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
836
- primaryStore: null,
837
- gl: null,
838
- renderer: null,
839
- camera: null,
840
- frustum: new Frustum(),
841
- autoUpdateFrustum: true,
842
- raycaster: null,
843
- events: {
844
- priority: 1,
845
- enabled: true,
846
- connected: false,
847
- frameTimedRaycasts: true,
848
- alwaysFireOnScroll: true,
849
- updateOnFrame: false
850
- },
851
- scene: null,
852
- rootScene: null,
853
- xr: null,
854
- inspector: null,
855
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
856
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
857
- textureColorSpace: SRGBColorSpace,
858
- isLegacy: false,
859
- webGPUSupported: false,
860
- isNative: false,
861
- controls: null,
862
- pointer,
863
- mouse: pointer,
864
- frameloop: "always",
865
- onPointerMissed: void 0,
866
- onDragOverMissed: void 0,
867
- onDropMissed: void 0,
868
- performance: {
869
- current: 1,
870
- min: 0.5,
871
- max: 1,
872
- debounce: 200,
873
- regress: () => {
874
- const state2 = get();
875
- if (performanceTimeout) clearTimeout(performanceTimeout);
876
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
877
- performanceTimeout = setTimeout(
878
- () => setPerformanceCurrent(get().performance.max),
879
- state2.performance.debounce
880
- );
881
- }
882
- },
883
- size: { width: 0, height: 0, top: 0, left: 0 },
884
- viewport: {
885
- initialDpr: 0,
886
- dpr: 0,
887
- width: 0,
888
- height: 0,
889
- top: 0,
890
- left: 0,
891
- aspect: 0,
892
- distance: 0,
893
- factor: 0,
894
- getCurrentViewport
895
- },
896
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
897
- setSize: (width, height, top, left) => {
898
- const state2 = get();
899
- if (width === void 0) {
900
- set({ _sizeImperative: false });
901
- if (state2._sizeProps) {
902
- const { width: propW, height: propH } = state2._sizeProps;
903
- if (propW !== void 0 || propH !== void 0) {
904
- const currentSize = state2.size;
905
- const newSize = {
906
- width: propW ?? currentSize.width,
907
- height: propH ?? currentSize.height,
908
- top: currentSize.top,
909
- left: currentSize.left
910
- };
911
- set((s) => ({
912
- size: newSize,
913
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
914
- }));
915
- getScheduler().invalidate();
916
- }
917
- }
918
- return;
919
- }
920
- const w = width;
921
- const h = height ?? width;
922
- const t = top ?? state2.size.top;
923
- const l = left ?? state2.size.left;
924
- const size = { width: w, height: h, top: t, left: l };
925
- set((s) => ({
926
- size,
927
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
928
- _sizeImperative: true
929
- }));
930
- getScheduler().invalidate();
931
- },
932
- setDpr: (dpr) => set((state2) => {
933
- const resolved = calculateDpr(dpr);
934
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
935
- }),
936
- setFrameloop: (frameloop = "always") => {
937
- set(() => ({ frameloop }));
938
- getScheduler().frameloop = frameloop;
939
- },
940
- setError: (error) => set(() => ({ error })),
941
- error: null,
942
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
943
- uniforms: {},
944
- nodes: {},
945
- buffers: {},
946
- gpuStorage: {},
947
- textures: /* @__PURE__ */ new Map(),
948
- _textureRefs: /* @__PURE__ */ new Map(),
949
- renderPipeline: null,
950
- passes: {},
951
- _hmrVersion: 0,
952
- _sizeImperative: false,
953
- _sizeProps: null,
954
- previousRoot: void 0,
955
- internal: {
956
- // Events
957
- interaction: [],
958
- subscribers: [],
959
- // Per-pointer state (new unified structure)
960
- pointerMap: /* @__PURE__ */ new Map(),
961
- pointerDirty: /* @__PURE__ */ new Map(),
962
- lastEvent: React.createRef(),
963
- // Deprecated but kept for backwards compatibility
964
- hovered: /* @__PURE__ */ new Map(),
965
- initialClick: [0, 0],
966
- initialHits: [],
967
- capturedMap: /* @__PURE__ */ new Map(),
968
- // Visibility tracking (onFramed, onOccluded, onVisible)
969
- visibilityRegistry: /* @__PURE__ */ new Map(),
970
- // Occlusion system (WebGPU only)
971
- occlusionEnabled: false,
972
- occlusionObserver: null,
973
- occlusionCache: /* @__PURE__ */ new Map(),
974
- helperGroup: null,
975
- // Updates
976
- active: false,
977
- frames: 0,
978
- priority: 0,
979
- subscribe: (ref, priority, store) => {
980
- const internal = get().internal;
981
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
982
- internal.subscribers.push({ ref, priority, store });
983
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
984
- return () => {
985
- const internal2 = get().internal;
986
- if (internal2?.subscribers) {
987
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
988
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
989
- }
990
- };
991
- },
992
- // Renderer Storage (single source of truth)
993
- actualRenderer: null,
994
- // Scheduler for useFrameNext (initialized in renderer.tsx)
995
- scheduler: null
996
- }
997
- };
998
- return rootState;
999
- });
1000
- const state = rootStore.getState();
1001
- Object.defineProperty(state, "gl", {
1002
- get() {
1003
- const currentState = rootStore.getState();
1004
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1005
- const stack = new Error().stack || "";
1006
- if (!isInternalRendererAccess(stack)) {
1007
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1008
- notifyDepreciated({
1009
- heading: "Accessing state.gl in WebGPU mode",
1010
- body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
1011
- });
1012
- }
1013
- }
1014
- return currentState.internal.actualRenderer;
1015
- },
1016
- set(value) {
1017
- rootStore.getState().internal.actualRenderer = value;
1018
- },
1019
- enumerable: true,
1020
- configurable: true
1021
- });
1022
- Object.defineProperty(state, "renderer", {
1023
- get() {
1024
- return rootStore.getState().internal.actualRenderer;
1025
- },
1026
- set(value) {
1027
- rootStore.getState().internal.actualRenderer = value;
1028
- },
1029
- enumerable: true,
1030
- configurable: true
1031
- });
1032
- let oldScene = state.scene;
1033
- rootStore.subscribe(() => {
1034
- const currentState = rootStore.getState();
1035
- const { scene, rootScene, set } = currentState;
1036
- if (scene !== oldScene) {
1037
- oldScene = scene;
1038
- if (scene?.isScene && scene !== rootScene) {
1039
- set({ rootScene: scene });
1040
- }
1041
- }
1042
- });
1043
- let oldSize = state.size;
1044
- let oldDpr = state.viewport.dpr;
1045
- let oldCamera = state.camera;
1046
- rootStore.subscribe(() => {
1047
- const { camera, size, viewport, set, internal } = rootStore.getState();
1048
- const actualRenderer = internal.actualRenderer;
1049
- const canvasTarget = internal.canvasTarget;
1050
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1051
- oldSize = size;
1052
- oldDpr = viewport.dpr;
1053
- updateCamera(camera, size);
1054
- if (internal.isSecondary && canvasTarget) {
1055
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1056
- canvasTarget.setSize(size.width, size.height, false);
1057
- } else {
1058
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1059
- actualRenderer.setSize(size.width, size.height, false);
1060
- if (canvasTarget) {
1061
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1062
- canvasTarget.setSize(size.width, size.height, false);
1063
- }
1064
- }
1065
- }
1066
- if (camera !== oldCamera) {
1067
- oldCamera = camera;
1068
- const { rootScene } = rootStore.getState();
1069
- if (camera && rootScene && !camera.parent) {
1070
- rootScene.add(camera);
1071
- }
1072
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1073
- const currentState = rootStore.getState();
1074
- if (currentState.autoUpdateFrustum && camera) {
1075
- updateFrustum(camera, currentState.frustum);
774
+ }
775
+ }
776
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
777
+ const object2 = instance.object;
778
+ const index = rootState.internal.interaction.indexOf(object2);
779
+ if (index > -1) rootState.internal.interaction.splice(index, 1);
780
+ if (instance.eventCount && object2.raycast !== null) {
781
+ rootState.internal.interaction.push(object2);
782
+ }
783
+ }
784
+ if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
785
+ const object2 = instance.object;
786
+ const root = findInitialRoot(instance);
787
+ const visibilityHandlers = {
788
+ onFramed: instance.handlers.onFramed,
789
+ onOccluded: instance.handlers.onOccluded,
790
+ onVisible: instance.handlers.onVisible
791
+ };
792
+ if (hasVisibilityHandlers(visibilityHandlers)) {
793
+ if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
794
+ registerVisibility(root, object2, visibilityHandlers);
1076
795
  }
796
+ } else {
797
+ unregisterVisibility(root, object2);
1077
798
  }
1078
- });
1079
- rootStore.subscribe((state2) => invalidate(state2));
1080
- return rootStore;
1081
- };
799
+ }
800
+ if (instance && instance.props.attach === void 0) {
801
+ if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
802
+ else if (instance.object.isMaterial) instance.props.attach = "material";
803
+ }
804
+ if (instance) invalidateInstance(instance);
805
+ return object;
806
+ }
807
+
808
+ const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
809
+ const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
810
+
811
+ function useStore() {
812
+ const store = useContext(context);
813
+ if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
814
+ return store;
815
+ }
816
+ function useThree$1(selector = (state) => state, equalityFn) {
817
+ return useStore()(selector, equalityFn);
818
+ }
1082
819
 
1083
820
  const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1084
821
  const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
@@ -1123,7 +860,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1123
860
  const results = cacheKeys.map(
1124
861
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1125
862
  );
1126
- return Array.isArray(input) ? results : results[0];
863
+ const stableRef = useRef(null);
864
+ const previous = stableRef.current;
865
+ const stable = previous !== null && previous.length === results.length && previous.every((value, i) => value === results[i]) ? previous : results;
866
+ stableRef.current = stable;
867
+ return Array.isArray(input) ? stable : stable[0];
1127
868
  }
1128
869
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1129
870
  const inputs = Array.isArray(input) ? input : [input];
@@ -1138,7 +879,7 @@ useLoader.clear = function(loader, input, cacheKey) {
1138
879
  };
1139
880
  useLoader.loader = getLoader$1;
1140
881
 
1141
- function useFrame(callback, priorityOrOptions) {
882
+ function useFrame$1(callback, priorityOrOptions) {
1142
883
  const store = React.useContext(context);
1143
884
  const isInsideCanvas = store !== null;
1144
885
  const scheduler = getScheduler();
@@ -1243,6 +984,13 @@ function useFrame(callback, priorityOrOptions) {
1243
984
  * Use for controlling the entire frame loop, adding phases, etc.
1244
985
  */
1245
986
  scheduler: scheduler2,
987
+ /**
988
+ * The root that currently owns this job. Resolve on access because ambient
989
+ * jobs can be adopted by a Canvas after the controls object is created.
990
+ */
991
+ get rootId() {
992
+ return getScheduler().getJobRootId(id);
993
+ },
1246
994
  /**
1247
995
  * Manually step this job only.
1248
996
  * Bypasses FPS limiting - always runs.
@@ -1259,6 +1007,14 @@ function useFrame(callback, priorityOrOptions) {
1259
1007
  stepAll: (timestamp) => {
1260
1008
  getScheduler().step(timestamp);
1261
1009
  },
1010
+ /**
1011
+ * Request frames for this job's owning root without waking sibling roots.
1012
+ */
1013
+ invalidate: (frames, stackFrames) => {
1014
+ const scheduler3 = getScheduler();
1015
+ const rootId = scheduler3.getJobRootId(id);
1016
+ if (rootId) scheduler3.invalidateRoot(rootId, frames, stackFrames);
1017
+ },
1262
1018
  /**
1263
1019
  * Pause this job (set enabled=false).
1264
1020
  * Job remains registered but won't run.
@@ -1288,6 +1044,11 @@ function getUrls(input) {
1288
1044
  if (Array.isArray(input)) return input;
1289
1045
  return Object.values(input);
1290
1046
  }
1047
+ function inputSignature(input) {
1048
+ if (typeof input === "string") return input;
1049
+ if (Array.isArray(input)) return input.join("\0");
1050
+ return Object.keys(input).sort().map((key) => `${key}\0${input[key]}`).join("");
1051
+ }
1291
1052
  function allUrlsCached(urls, textureCache) {
1292
1053
  return urls.every((url) => textureCache.has(url));
1293
1054
  }
@@ -1306,31 +1067,44 @@ function buildFromCache(input, textureCache) {
1306
1067
  }
1307
1068
  }
1308
1069
  function useTexture(input, optionsOrOnLoad) {
1309
- const renderer = useThree((state) => state.internal.actualRenderer);
1070
+ const renderer = useThree$1((state) => state.internal.actualRenderer);
1310
1071
  const store = useStore();
1311
1072
  const options = typeof optionsOrOnLoad === "function" ? { onLoad: optionsOrOnLoad } : optionsOrOnLoad ?? {};
1312
1073
  const { onLoad, cache = true } = options;
1313
1074
  const onLoadRef = useRef(onLoad);
1314
1075
  onLoadRef.current = onLoad;
1315
1076
  const onLoadCalledForRef = useRef(null);
1316
- const urls = useMemo(() => getUrls(input), [input]);
1077
+ const inputKey = useMemo(() => inputSignature(input), [input]);
1078
+ const stableInput = useMemo(() => input, [inputKey]);
1079
+ const urls = useMemo(() => getUrls(stableInput), [stableInput]);
1317
1080
  const cachedResult = useMemo(() => {
1318
1081
  if (!cache) return null;
1319
1082
  const textures = store.getState().textures;
1320
1083
  if (!allUrlsCached(urls, textures)) return null;
1321
- return buildFromCache(input, textures);
1322
- }, [cache, urls, input, store]);
1084
+ return buildFromCache(stableInput, textures);
1085
+ }, [cache, urls, stableInput, store]);
1323
1086
  const loadedTextures = useLoader(
1324
1087
  TextureLoader,
1325
- IsObject(input) ? Object.values(input) : input
1088
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1326
1089
  );
1327
- const inputKey = urls.join("\0");
1090
+ const mappedTextures = useMemo(() => {
1091
+ if (cachedResult) return cachedResult;
1092
+ if (IsObject(stableInput)) {
1093
+ const keyed = {};
1094
+ const textureArray = loadedTextures;
1095
+ let i = 0;
1096
+ for (const key in stableInput) keyed[key] = textureArray[i++];
1097
+ return keyed;
1098
+ } else {
1099
+ return loadedTextures;
1100
+ }
1101
+ }, [stableInput, loadedTextures, cachedResult]);
1328
1102
  useLayoutEffect(() => {
1329
1103
  if (cachedResult) return;
1330
1104
  if (onLoadCalledForRef.current === inputKey) return;
1331
1105
  onLoadCalledForRef.current = inputKey;
1332
- onLoadRef.current?.(loadedTextures);
1333
- }, [cachedResult, loadedTextures, inputKey]);
1106
+ onLoadRef.current?.(mappedTextures);
1107
+ }, [cachedResult, mappedTextures, inputKey]);
1334
1108
  useEffect(() => {
1335
1109
  if (cachedResult) return;
1336
1110
  if ("initTexture" in renderer) {
@@ -1349,30 +1123,18 @@ function useTexture(input, optionsOrOnLoad) {
1349
1123
  });
1350
1124
  }
1351
1125
  }, [renderer, loadedTextures, cachedResult]);
1352
- const mappedTextures = useMemo(() => {
1353
- if (cachedResult) return cachedResult;
1354
- if (IsObject(input)) {
1355
- const keyed = {};
1356
- const textureArray = loadedTextures;
1357
- let i = 0;
1358
- for (const key in input) keyed[key] = textureArray[i++];
1359
- return keyed;
1360
- } else {
1361
- return loadedTextures;
1362
- }
1363
- }, [input, loadedTextures, cachedResult]);
1364
1126
  useEffect(() => {
1365
1127
  if (!cache) return;
1366
1128
  const urlTextureMap = [];
1367
- if (typeof input === "string") {
1368
- urlTextureMap.push([input, mappedTextures]);
1369
- } else if (Array.isArray(input)) {
1129
+ if (typeof stableInput === "string") {
1130
+ urlTextureMap.push([stableInput, mappedTextures]);
1131
+ } else if (Array.isArray(stableInput)) {
1370
1132
  const textureArray = mappedTextures;
1371
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1372
- } else if (IsObject(input)) {
1133
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1134
+ } else if (IsObject(stableInput)) {
1373
1135
  const textureRecord = mappedTextures;
1374
- for (const key in input) {
1375
- const url = input[key];
1136
+ for (const key in stableInput) {
1137
+ const url = stableInput[key];
1376
1138
  urlTextureMap.push([url, textureRecord[key]]);
1377
1139
  }
1378
1140
  }
@@ -1401,7 +1163,7 @@ function useTexture(input, optionsOrOnLoad) {
1401
1163
  }
1402
1164
  return { _textureRefs: refs };
1403
1165
  });
1404
- }, [cache, input, mappedTextures, store]);
1166
+ }, [cache, stableInput, mappedTextures, store]);
1405
1167
  return mappedTextures;
1406
1168
  }
1407
1169
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1472,13 +1234,13 @@ function useTextures(selector) {
1472
1234
  };
1473
1235
  }, [store]);
1474
1236
  const subscribe = selector ? () => selector(registry) : (state) => state.textures;
1475
- const selected = useThree(subscribe);
1237
+ const selected = useThree$1(subscribe);
1476
1238
  return selector ? selected : registry;
1477
1239
  }
1478
1240
 
1479
1241
  function useRenderTarget(widthOrOptions, heightOrOptions, options) {
1480
- const isLegacy = useThree((s) => s.isLegacy);
1481
- const size = useThree((s) => s.size);
1242
+ const isLegacy = useThree$1((s) => s.isLegacy);
1243
+ const size = useThree$1((s) => s.size);
1482
1244
  let width;
1483
1245
  let height;
1484
1246
  let opts;
@@ -1535,7 +1297,7 @@ function useEnvironment({
1535
1297
  const { extension, isCubemap } = getExtension(files);
1536
1298
  const loader = getLoader(extension);
1537
1299
  if (!loader) throw new Error("useEnvironment: Unrecognized file extension: " + files);
1538
- const renderer = useThree((state) => state.renderer);
1300
+ const renderer = useThree$1((state) => state.renderer);
1539
1301
  useLayoutEffect(() => {
1540
1302
  if (extension !== "webp" && extension !== "jpg" && extension !== "jpeg") return;
1541
1303
  function clearGainmapTexture() {
@@ -1562,7 +1324,7 @@ function useEnvironment({
1562
1324
  texture = texture.renderTarget?.texture;
1563
1325
  }
1564
1326
  texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
1565
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1327
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1566
1328
  return texture;
1567
1329
  }
1568
1330
  const preloadDefaultOptions = {
@@ -1615,22 +1377,15 @@ function getExtension(files) {
1615
1377
  const isCubemap = isArray(files) && files.length === 6;
1616
1378
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1617
1379
  const firstEntry = isArray(files) ? files[0] : files;
1618
- 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();
1380
+ const firstExtension = firstEntry.split(".").pop()?.split("?")?.shift()?.toLowerCase();
1381
+ const extension = isCubemap ? firstExtension === "hdr" ? "hdr-cube" : "cube" : isGainmap ? "webp" : firstEntry.startsWith("data:application/exr") ? "exr" : firstEntry.startsWith("data:application/hdr") ? "hdr" : firstEntry.startsWith("data:image/jpeg") ? "jpg" : firstExtension;
1619
1382
  return { extension, isCubemap, isGainmap };
1620
1383
  }
1621
1384
  function getLoader(extension) {
1622
- const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1385
+ const loader = extension === "cube" ? CubeTextureLoader : extension === "hdr-cube" ? HDRCubeTextureLoader : extension === "hdr" ? HDRLoader : extension === "exr" ? EXRLoader : extension === "jpg" || extension === "jpeg" ? UltraHDRLoader : extension === "webp" ? GainMapLoader : null;
1623
1386
  return loader;
1624
1387
  }
1625
1388
 
1626
- function useStore() {
1627
- const store = useContext(context);
1628
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1629
- return store;
1630
- }
1631
- function useThree(selector = (state) => state, equalityFn) {
1632
- return useStore()(selector, equalityFn);
1633
- }
1634
1389
  function useInstanceHandle(ref) {
1635
1390
  const instance = useRef(null);
1636
1391
  useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1670,22 +1425,28 @@ function addTail(callback) {
1670
1425
  return getScheduler().onIdle(callback);
1671
1426
  }
1672
1427
  function invalidate(state, frames = 1, stackFrames = false) {
1673
- getScheduler().invalidate(frames, stackFrames);
1428
+ const rootId = state?.internal?.rootId;
1429
+ const scheduler = getScheduler();
1430
+ if (rootId) scheduler.invalidateRoot(rootId, frames, stackFrames);
1431
+ else scheduler.invalidate(frames, stackFrames);
1674
1432
  }
1675
- function advance(timestamp) {
1676
- getScheduler().step(timestamp);
1433
+ function advance(timestamp, runGlobalEffects, state) {
1434
+ const rootId = state?.internal?.rootId;
1435
+ const scheduler = getScheduler();
1436
+ if (rootId) scheduler.stepRoot(rootId, timestamp);
1437
+ else scheduler.step(timestamp);
1677
1438
  }
1678
1439
 
1679
- const version = "10.0.0-alpha.3";
1440
+ const version = "10.0.0-alpha.5";
1680
1441
  const packageData = {
1681
1442
  version: version};
1682
1443
 
1683
1444
  function Xb(Tt) {
1684
1445
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
1685
1446
  }
1686
- var Rm = { exports: {} }, Og = { exports: {} }, _b;
1447
+ var Rm = { exports: {} }, Og = { exports: {} }, _b$1;
1687
1448
  function Kb() {
1688
- return _b || (_b = 1, (function(Tt) {
1449
+ return _b$1 || (_b$1 = 1, (function(Tt) {
1689
1450
  Tt.exports = function(m) {
1690
1451
  function Yn(t, r, a, l) {
1691
1452
  return new uc(t, r, a, l);
@@ -13535,7 +13296,10 @@ function extend(objects) {
13535
13296
  catalogue[Component] = objects;
13536
13297
  return Component;
13537
13298
  } else {
13538
- Object.assign(catalogue, objects);
13299
+ for (const name in objects) {
13300
+ const object = objects[name];
13301
+ if (isConstructor(object)) catalogue[name] = object;
13302
+ }
13539
13303
  }
13540
13304
  }
13541
13305
  function validateInstance(type, props) {
@@ -13883,7 +13647,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13883
13647
  getRootHostContext: () => NO_CONTEXT,
13884
13648
  getChildHostContext: () => NO_CONTEXT,
13885
13649
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13886
- validateInstance(type, newProps);
13650
+ validateInstance(instance.type, newProps);
13887
13651
  let reconstruct = false;
13888
13652
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13889
13653
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13910,8 +13674,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13910
13674
  for (const prop in instance.props) {
13911
13675
  const value = instance.props[prop];
13912
13676
  if (isFromRef(value)) {
13913
- const ref = value[FROM_REF];
13914
- if (ref.current != null) resolved[prop] = ref.current;
13677
+ const { [FROM_REF]: ref, transform } = value;
13678
+ if (ref.current != null) resolved[prop] = transform ? transform(ref.current) : ref.current;
13915
13679
  }
13916
13680
  }
13917
13681
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14055,6 +13819,335 @@ const reconciler = /* @__PURE__ */ createReconciler({
14055
13819
  getSuspendedCommitReason: (_state, _rootContainer) => null
14056
13820
  });
14057
13821
 
13822
+ const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
13823
+ function isInternalRendererAccess(stack, callerDepth = 2) {
13824
+ if (!stack) return false;
13825
+ const caller = stack.split("\n")[callerDepth] ?? "";
13826
+ return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
13827
+ }
13828
+
13829
+ const createStore = (invalidate, advance) => {
13830
+ const rootStore = createWithEqualityFn((set, get) => {
13831
+ const position = new Vector3();
13832
+ const defaultTarget = new Vector3();
13833
+ const tempTarget = new Vector3();
13834
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
13835
+ const { width, height, top, left } = size;
13836
+ const aspect = width / height;
13837
+ if (target.isVector3) tempTarget.copy(target);
13838
+ else tempTarget.set(...target);
13839
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
13840
+ if (isOrthographicCamera(camera)) {
13841
+ return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
13842
+ } else {
13843
+ const fov = camera.fov * Math.PI / 180;
13844
+ const h = 2 * Math.tan(fov / 2) * distance;
13845
+ const w = h * (width / height);
13846
+ return { width: w, height: h, top, left, factor: width / w, distance, aspect };
13847
+ }
13848
+ }
13849
+ let performanceTimeout = void 0;
13850
+ const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
13851
+ const pointer = new Vector2();
13852
+ const rootState = {
13853
+ set,
13854
+ get,
13855
+ // Mock objects that have to be configured
13856
+ // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
13857
+ primaryStore: null,
13858
+ gl: null,
13859
+ renderer: null,
13860
+ camera: null,
13861
+ frustum: new Frustum(),
13862
+ autoUpdateFrustum: true,
13863
+ raycaster: null,
13864
+ events: {
13865
+ priority: 1,
13866
+ enabled: true,
13867
+ connected: false,
13868
+ frameTimedRaycasts: true,
13869
+ alwaysFireOnScroll: true,
13870
+ updateOnFrame: false
13871
+ },
13872
+ scene: null,
13873
+ rootScene: null,
13874
+ xr: null,
13875
+ inspector: null,
13876
+ invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
13877
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
13878
+ textureColorSpace: SRGBColorSpace,
13879
+ isLegacy: false,
13880
+ webGPUSupported: false,
13881
+ isNative: false,
13882
+ controls: null,
13883
+ pointer,
13884
+ mouse: pointer,
13885
+ frameloop: "always",
13886
+ onPointerMissed: void 0,
13887
+ onDragOverMissed: void 0,
13888
+ onDropMissed: void 0,
13889
+ performance: {
13890
+ current: 1,
13891
+ min: 0.5,
13892
+ max: 1,
13893
+ debounce: 200,
13894
+ regress: () => {
13895
+ const state2 = get();
13896
+ if (performanceTimeout) clearTimeout(performanceTimeout);
13897
+ if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
13898
+ performanceTimeout = setTimeout(
13899
+ () => setPerformanceCurrent(get().performance.max),
13900
+ state2.performance.debounce
13901
+ );
13902
+ }
13903
+ },
13904
+ size: { width: 0, height: 0, top: 0, left: 0 },
13905
+ viewport: {
13906
+ initialDpr: 0,
13907
+ dpr: 0,
13908
+ width: 0,
13909
+ height: 0,
13910
+ top: 0,
13911
+ left: 0,
13912
+ aspect: 0,
13913
+ distance: 0,
13914
+ factor: 0,
13915
+ getCurrentViewport
13916
+ },
13917
+ setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
13918
+ setSize: (width, height, top, left) => {
13919
+ const state2 = get();
13920
+ if (width === void 0) {
13921
+ set({ _sizeImperative: false });
13922
+ if (state2._sizeProps) {
13923
+ const { width: propW, height: propH } = state2._sizeProps;
13924
+ if (propW !== void 0 || propH !== void 0) {
13925
+ const currentSize = state2.size;
13926
+ const newSize = {
13927
+ width: propW ?? currentSize.width,
13928
+ height: propH ?? currentSize.height,
13929
+ top: currentSize.top,
13930
+ left: currentSize.left
13931
+ };
13932
+ set((s) => ({
13933
+ size: newSize,
13934
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
13935
+ }));
13936
+ get().invalidate();
13937
+ }
13938
+ }
13939
+ return;
13940
+ }
13941
+ const w = width;
13942
+ const h = height ?? width;
13943
+ const t = top ?? state2.size.top;
13944
+ const l = left ?? state2.size.left;
13945
+ const size = { width: w, height: h, top: t, left: l };
13946
+ set((s) => ({
13947
+ size,
13948
+ viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
13949
+ _sizeImperative: true
13950
+ }));
13951
+ get().invalidate();
13952
+ },
13953
+ setDpr: (dpr) => set((state2) => {
13954
+ const resolved = calculateDpr(dpr);
13955
+ return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
13956
+ }),
13957
+ setFrameloop: (frameloop = "always") => {
13958
+ const rootId = get().internal.rootId;
13959
+ if (rootId) getScheduler().setRootFrameloop(rootId, frameloop);
13960
+ set(() => ({ frameloop }));
13961
+ },
13962
+ setError: (error) => set(() => ({ error })),
13963
+ error: null,
13964
+ //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
13965
+ uniforms: {},
13966
+ nodes: {},
13967
+ buffers: {},
13968
+ gpuStorage: {},
13969
+ textures: /* @__PURE__ */ new Map(),
13970
+ _textureRefs: /* @__PURE__ */ new Map(),
13971
+ renderPipeline: null,
13972
+ passes: {},
13973
+ _hmrVersion: 0,
13974
+ _sizeImperative: false,
13975
+ _sizeProps: null,
13976
+ previousRoot: void 0,
13977
+ internal: {
13978
+ // Events
13979
+ interaction: [],
13980
+ subscribers: [],
13981
+ // Per-pointer state (new unified structure)
13982
+ pointerMap: /* @__PURE__ */ new Map(),
13983
+ pointerDirty: /* @__PURE__ */ new Map(),
13984
+ lastEvent: React.createRef(),
13985
+ // Deprecated but kept for backwards compatibility
13986
+ hovered: /* @__PURE__ */ new Map(),
13987
+ initialClick: [0, 0],
13988
+ initialHits: [],
13989
+ capturedMap: /* @__PURE__ */ new Map(),
13990
+ // Visibility tracking (onFramed, onOccluded, onVisible)
13991
+ visibilityRegistry: /* @__PURE__ */ new Map(),
13992
+ // Occlusion system (WebGPU only)
13993
+ occlusionEnabled: false,
13994
+ occlusionObserver: null,
13995
+ occlusionCache: /* @__PURE__ */ new Map(),
13996
+ helperGroup: null,
13997
+ // Updates
13998
+ active: false,
13999
+ frames: 0,
14000
+ priority: 0,
14001
+ subscribe: (ref, priority, store) => {
14002
+ const internal = get().internal;
14003
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0);
14004
+ internal.subscribers.push({ ref, priority, store });
14005
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
14006
+ return () => {
14007
+ const internal2 = get().internal;
14008
+ if (internal2?.subscribers) {
14009
+ internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
14010
+ internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
14011
+ }
14012
+ };
14013
+ },
14014
+ // Renderer Storage (single source of truth)
14015
+ actualRenderer: null,
14016
+ // Scheduler for useFrameNext (initialized in renderer.tsx)
14017
+ scheduler: null,
14018
+ // Replaces the default render call when set (see setRenderOverride)
14019
+ renderOverride: null
14020
+ }
14021
+ };
14022
+ return rootState;
14023
+ });
14024
+ const state = rootStore.getState();
14025
+ Object.defineProperty(state, "gl", {
14026
+ get() {
14027
+ const currentState = rootStore.getState();
14028
+ if (!currentState.isLegacy && currentState.internal.actualRenderer) {
14029
+ const stack = new Error().stack || "";
14030
+ if (!isInternalRendererAccess(stack)) {
14031
+ const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
14032
+ notifyDepreciated({
14033
+ heading: "Accessing state.gl in WebGPU mode",
14034
+ body: "Please use state.renderer instead. state.gl is deprecated and will be removed in future versions.\n\nFor backwards compatibility, state.gl currently maps to state.renderer, but this may cause issues with libraries expecting WebGLRenderer.\n\nAccessed from:\n" + cleanedStack
14035
+ });
14036
+ }
14037
+ }
14038
+ return currentState.internal.actualRenderer;
14039
+ },
14040
+ set(value) {
14041
+ rootStore.getState().internal.actualRenderer = value;
14042
+ },
14043
+ enumerable: true,
14044
+ configurable: true
14045
+ });
14046
+ Object.defineProperty(state, "renderer", {
14047
+ get() {
14048
+ return rootStore.getState().internal.actualRenderer;
14049
+ },
14050
+ set(value) {
14051
+ rootStore.getState().internal.actualRenderer = value;
14052
+ },
14053
+ enumerable: true,
14054
+ configurable: true
14055
+ });
14056
+ let oldScene = state.scene;
14057
+ rootStore.subscribe(() => {
14058
+ const currentState = rootStore.getState();
14059
+ const { scene, rootScene, set } = currentState;
14060
+ if (scene !== oldScene) {
14061
+ oldScene = scene;
14062
+ if (scene?.isScene && scene !== rootScene) {
14063
+ set({ rootScene: scene });
14064
+ }
14065
+ }
14066
+ });
14067
+ let oldSize = state.size;
14068
+ let oldDpr = state.viewport.dpr;
14069
+ let oldCamera = state.camera;
14070
+ rootStore.subscribe(() => {
14071
+ const { camera, size, viewport, set, internal } = rootStore.getState();
14072
+ const actualRenderer = internal.actualRenderer;
14073
+ const canvasTarget = internal.canvasTarget;
14074
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
14075
+ oldSize = size;
14076
+ oldDpr = viewport.dpr;
14077
+ updateCamera(camera, size);
14078
+ const resizeTarget = canvasTarget ?? actualRenderer;
14079
+ if (viewport.dpr > 0) resizeTarget.setPixelRatio(viewport.dpr);
14080
+ resizeTarget.setSize(size.width, size.height, false);
14081
+ const activeTarget = actualRenderer.getCanvasTarget?.();
14082
+ if (!canvasTarget || activeTarget !== canvasTarget) internal.canvasTargetSizeDirty = true;
14083
+ }
14084
+ if (camera !== oldCamera) {
14085
+ oldCamera = camera;
14086
+ const { rootScene } = rootStore.getState();
14087
+ if (camera && rootScene && !camera.parent) {
14088
+ rootScene.add(camera);
14089
+ }
14090
+ set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
14091
+ const currentState = rootStore.getState();
14092
+ if (currentState.autoUpdateFrustum && camera) {
14093
+ updateFrustum(camera, currentState.frustum);
14094
+ }
14095
+ }
14096
+ });
14097
+ rootStore.subscribe((state2) => invalidate(state2));
14098
+ return rootStore;
14099
+ };
14100
+
14101
+ const R3F_EXTENSIONS = Symbol.for("@react-three/fiber.extensions");
14102
+ function getRegistry() {
14103
+ return globalThis[R3F_EXTENSIONS] ?? (globalThis[R3F_EXTENSIONS] = {
14104
+ extensions: /* @__PURE__ */ new Map(),
14105
+ roots: /* @__PURE__ */ new Set(),
14106
+ applied: /* @__PURE__ */ new WeakMap()
14107
+ });
14108
+ }
14109
+ function setupRoot(registry, store, extension) {
14110
+ let applied = registry.applied.get(store);
14111
+ if (!applied) registry.applied.set(store, applied = /* @__PURE__ */ new Map());
14112
+ if (applied.has(extension.name)) return;
14113
+ applied.set(extension.name, extension);
14114
+ const patch = extension.setup?.(store);
14115
+ if (patch) store.setState(patch);
14116
+ }
14117
+ function registerRootExtension(extension) {
14118
+ const registry = getRegistry();
14119
+ registry.extensions.set(extension.name, extension);
14120
+ for (const store of registry.roots) setupRoot(registry, store, extension);
14121
+ return () => {
14122
+ if (registry.extensions.get(extension.name) === extension) registry.extensions.delete(extension.name);
14123
+ };
14124
+ }
14125
+ function setRenderOverride(store, render) {
14126
+ store.getState().internal.renderOverride = render;
14127
+ }
14128
+ function attachRootExtensions(store) {
14129
+ const registry = getRegistry();
14130
+ registry.roots.add(store);
14131
+ for (const extension of registry.extensions.values()) setupRoot(registry, store, extension);
14132
+ }
14133
+ function current(registry, setUpBy) {
14134
+ return registry.extensions.get(setUpBy.name) ?? setUpBy;
14135
+ }
14136
+ function detachRootExtensions(store) {
14137
+ const registry = getRegistry();
14138
+ if (!registry.roots.delete(store)) return;
14139
+ const applied = registry.applied.get(store);
14140
+ registry.applied.delete(store);
14141
+ if (!applied) return;
14142
+ for (const setUpBy of applied.values()) current(registry, setUpBy).dispose?.(store);
14143
+ }
14144
+ function notifyRootExtensionsHmr(store) {
14145
+ const registry = getRegistry();
14146
+ const applied = registry.applied.get(store);
14147
+ if (!applied) return;
14148
+ for (const setUpBy of applied.values()) current(registry, setUpBy).hmr?.(store);
14149
+ }
14150
+
14058
14151
  const isRenderer = (def) => !!def?.render;
14059
14152
  const _roots = /* @__PURE__ */ new Map();
14060
14153
  const shallowLoose = { objects: "shallow", strict: false };
@@ -14192,9 +14285,8 @@ function createRoot(canvas) {
14192
14285
  if (!renderer.hasInitialized?.()) {
14193
14286
  const size2 = computeInitialSize(canvas, propsSize);
14194
14287
  if (size2.width > 0 && size2.height > 0) {
14195
- const pixelRatio = calculateDpr(dpr);
14196
- canvas.width = size2.width * pixelRatio;
14197
- canvas.height = size2.height * pixelRatio;
14288
+ renderer.setPixelRatio(calculateDpr(dpr));
14289
+ renderer.setSize(size2.width, size2.height, false);
14198
14290
  }
14199
14291
  await renderer.init();
14200
14292
  }
@@ -14203,7 +14295,7 @@ function createRoot(canvas) {
14203
14295
  state.internal.actualRenderer = renderer;
14204
14296
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14205
14297
  if (canvasId && !state.internal.isSecondary) {
14206
- const canvasTarget = new CanvasTarget(canvas);
14298
+ const canvasTarget = renderer.getCanvasTarget?.();
14207
14299
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14208
14300
  state.set((prev) => ({
14209
14301
  internal: {
@@ -14317,7 +14409,7 @@ function createRoot(canvas) {
14317
14409
  const handleXRFrame = (timestamp, _frame) => {
14318
14410
  const state2 = store.getState();
14319
14411
  if (state2.frameloop === "never") return;
14320
- advance(timestamp);
14412
+ advance(timestamp, true, state2);
14321
14413
  };
14322
14414
  const actualRenderer = state.internal.actualRenderer;
14323
14415
  const handleSessionChange = () => {
@@ -14325,7 +14417,7 @@ function createRoot(canvas) {
14325
14417
  const renderer2 = state2.internal.actualRenderer;
14326
14418
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14327
14419
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14328
- if (!renderer2.xr.isPresenting) invalidate();
14420
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14329
14421
  };
14330
14422
  const xr = {
14331
14423
  connect() {
@@ -14407,14 +14499,23 @@ function createRoot(canvas) {
14407
14499
  const newRootId = canvasId || scheduler.generateRootId();
14408
14500
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14409
14501
  getState: () => store.getState(),
14410
- onError: (err) => store.getState().setError(err)
14502
+ onError: (err) => store.getState().setError(err),
14503
+ frameloop: store.getState().frameloop,
14504
+ before: schedulerConfig?.before,
14505
+ after: schedulerConfig?.after,
14506
+ order: schedulerConfig?.order
14411
14507
  });
14412
14508
  const unregisterCanvasTarget = scheduler.register(
14413
14509
  () => {
14414
14510
  const state2 = store.getState();
14415
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14511
+ const canvasTarget = state2.internal.canvasTarget;
14512
+ if (canvasTarget) {
14416
14513
  const renderer2 = state2.internal.actualRenderer;
14417
- renderer2.setCanvasTarget(state2.internal.canvasTarget);
14514
+ if (renderer2.getCanvasTarget() !== canvasTarget) renderer2.setCanvasTarget(canvasTarget);
14515
+ if (state2.internal.canvasTargetSizeDirty) {
14516
+ state2.internal.canvasTargetSizeDirty = false;
14517
+ renderer2.backend?.updateSize?.();
14518
+ }
14418
14519
  }
14419
14520
  },
14420
14521
  {
@@ -14470,7 +14571,8 @@ function createRoot(canvas) {
14470
14571
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14471
14572
  if (userHandlesRender || state2.internal.priority) return;
14472
14573
  try {
14473
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14574
+ const renderOverride = state2.internal.renderOverride;
14575
+ if (renderOverride) renderOverride();
14474
14576
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14475
14577
  } catch (error) {
14476
14578
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14483,8 +14585,7 @@ function createRoot(canvas) {
14483
14585
  phase: "render",
14484
14586
  system: true,
14485
14587
  // Internal flag: this is a system job, not user-controlled
14486
- // Apply scheduler config for render ordering and rate limiting
14487
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14588
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14488
14589
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14489
14590
  }
14490
14591
  );
@@ -14503,8 +14604,22 @@ function createRoot(canvas) {
14503
14604
  scheduler
14504
14605
  }
14505
14606
  }));
14607
+ } else {
14608
+ const constraintsChanged = !is.equ(schedulerConfig?.before, lastConfiguredProps.schedulerBefore, shallowLoose) || !is.equ(schedulerConfig?.after, lastConfiguredProps.schedulerAfter, shallowLoose);
14609
+ if (constraintsChanged) {
14610
+ scheduler.setRootConstraints(rootId, {
14611
+ before: schedulerConfig?.before,
14612
+ after: schedulerConfig?.after
14613
+ });
14614
+ }
14615
+ if (schedulerConfig?.order !== lastConfiguredProps.schedulerOrder) {
14616
+ scheduler.setRootOrder(rootId, schedulerConfig?.order ?? 0);
14617
+ }
14506
14618
  }
14507
- scheduler.frameloop = frameloop;
14619
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14620
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14621
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14622
+ attachRootExtensions(store);
14508
14623
  onCreated = onCreatedCallback;
14509
14624
  configured = true;
14510
14625
  resolve();
@@ -14546,18 +14661,24 @@ function unmountComponentAtNode(canvas, callback) {
14546
14661
  const fiber = root?.fiber;
14547
14662
  if (fiber) {
14548
14663
  const state = root?.store.getState();
14549
- if (state) state.internal.active = false;
14664
+ if (state) {
14665
+ state.internal.active = false;
14666
+ const unregisterRoot = state.internal.unregisterRoot;
14667
+ if (unregisterRoot) {
14668
+ unregisterRoot();
14669
+ state.internal.unregisterRoot = void 0;
14670
+ }
14671
+ }
14550
14672
  reconciler.updateContainer(null, fiber, null, () => {
14673
+ if (root?.store) detachRootExtensions(root.store);
14551
14674
  if (state) {
14552
14675
  setTimeout(() => {
14553
14676
  try {
14554
14677
  const renderer = state.internal.actualRenderer;
14555
- const unregisterRoot = state.internal.unregisterRoot;
14556
- if (unregisterRoot) unregisterRoot();
14557
14678
  const unregisterPrimary = state.internal.unregisterPrimary;
14558
14679
  if (unregisterPrimary) unregisterPrimary();
14559
14680
  const canvasTarget = state.internal.canvasTarget;
14560
- if (canvasTarget?.dispose) canvasTarget.dispose();
14681
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14561
14682
  state.events.disconnect?.();
14562
14683
  cleanupHelperGroup(root.store);
14563
14684
  if (state.isLegacy && renderer) {
@@ -14698,16 +14819,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14698
14819
  const oldbg = target.background;
14699
14820
  const oldenv = target.environment;
14700
14821
  const oldSceneProps = {
14701
- // @ts-ignore
14702
14822
  backgroundBlurriness: target.backgroundBlurriness,
14703
- // @ts-ignore
14704
14823
  backgroundIntensity: target.backgroundIntensity,
14705
- // @ts-ignore
14706
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14707
- // @ts-ignore
14824
+ backgroundRotation: target.backgroundRotation.clone(),
14708
14825
  environmentIntensity: target.environmentIntensity,
14709
- // @ts-ignore
14710
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14826
+ environmentRotation: target.environmentRotation.clone()
14711
14827
  };
14712
14828
  if (background !== "only") target.environment = texture;
14713
14829
  if (background) target.background = texture;
@@ -14719,7 +14835,7 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14719
14835
  };
14720
14836
  }
14721
14837
  function EnvironmentMap({ scene, background = false, map, ...config }) {
14722
- const defaultScene = useThree((state) => state.scene);
14838
+ const defaultScene = useThree$1((state) => state.scene);
14723
14839
  React.useLayoutEffect(() => {
14724
14840
  if (map) return setEnvProps(background, scene, defaultScene, map, config);
14725
14841
  });
@@ -14737,7 +14853,7 @@ function EnvironmentCube({
14737
14853
  ...rest
14738
14854
  }) {
14739
14855
  const texture = useEnvironment(rest);
14740
- const defaultScene = useThree((state) => state.scene);
14856
+ const defaultScene = useThree$1((state) => state.scene);
14741
14857
  React.useLayoutEffect(() => {
14742
14858
  return setEnvProps(background, scene, defaultScene, texture, {
14743
14859
  backgroundBlurriness: blur ?? backgroundBlurriness,
@@ -14774,9 +14890,9 @@ function EnvironmentPortal({
14774
14890
  preset = void 0,
14775
14891
  extensions
14776
14892
  }) {
14777
- const renderer = useThree((state) => state.renderer);
14778
- const isLegacy = useThree((state) => state.isLegacy);
14779
- const defaultScene = useThree((state) => state.scene);
14893
+ const renderer = useThree$1((state) => state.renderer);
14894
+ const isLegacy = useThree$1((state) => state.isLegacy);
14895
+ const defaultScene = useThree$1((state) => state.scene);
14780
14896
  const camera = React.useRef(null);
14781
14897
  const [virtualScene] = React.useState(() => new Scene());
14782
14898
  const fbo = React.useMemo(() => {
@@ -14820,7 +14936,7 @@ function EnvironmentPortal({
14820
14936
  environmentRotation
14821
14937
  ]);
14822
14938
  let count = 1;
14823
- useFrame(() => {
14939
+ useFrame$1(() => {
14824
14940
  if (frames === Infinity || count < frames) {
14825
14941
  const autoClear = renderer.autoClear;
14826
14942
  renderer.autoClear = true;
@@ -14860,7 +14976,7 @@ function EnvironmentGround(props) {
14860
14976
  ] });
14861
14977
  }
14862
14978
  function EnvironmentColor({ color, scene }) {
14863
- const defaultScene = useThree((state) => state.scene);
14979
+ const defaultScene = useThree$1((state) => state.scene);
14864
14980
  React.useLayoutEffect(() => {
14865
14981
  if (color === void 0) return;
14866
14982
  const target = resolveScene(scene || defaultScene);
@@ -14909,156 +15025,27 @@ function parseBackground(background) {
14909
15025
  return { preset: background, background: true };
14910
15026
  }
14911
15027
  if (/^(https?:\/\/|\/|\.\/|\.\.\/)|\.(hdr|exr|jpg|jpeg|png|webp|gif)$/i.test(background)) {
14912
- return { files: background, background: true };
14913
- }
14914
- return { color: background, background: true };
14915
- }
14916
- if (background.isColor) {
14917
- return { color: background, background: true };
14918
- }
14919
- return null;
14920
- }
14921
-
14922
- function parseRendererConfig(rendererProp) {
14923
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14924
- if (isRendererConfig) {
14925
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14926
- return {
14927
- primaryCanvas: pc,
14928
- scheduler: sc,
14929
- renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14930
- };
14931
- }
14932
- return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14933
- }
14934
-
14935
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14936
- const ROOT_SCOPE = "";
14937
- const registries = /* @__PURE__ */ new WeakMap();
14938
- function getKindRegistry(store, kind) {
14939
- let registry = registries.get(store);
14940
- if (!registry) {
14941
- registry = {
14942
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14943
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14944
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14945
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14946
- };
14947
- registries.set(store, registry);
14948
- }
14949
- return registry[kind];
14950
- }
14951
- function resolvePrimary$1(store) {
14952
- return store.getState().primaryStore ?? store;
14953
- }
14954
- function isScopeValid(store, kind, scopeKey) {
14955
- return getKindRegistry(store, kind).valid.has(scopeKey);
14956
- }
14957
- function peekStaged(store, kind, scopeKey) {
14958
- return getKindRegistry(store, kind).staged.get(scopeKey);
14959
- }
14960
- function stageEntries(store, kind, scopeKey, entries) {
14961
- const { staged } = getKindRegistry(store, kind);
14962
- const current = staged.get(scopeKey);
14963
- if (current) Object.assign(current, entries);
14964
- else staged.set(scopeKey, { ...entries });
14965
- }
14966
- function readCommittedScope(map, scopeKey, isLeaf) {
14967
- if (scopeKey === ROOT_SCOPE) {
14968
- const leaves = {};
14969
- for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14970
- return leaves;
14971
- }
14972
- const scopeData = map[scopeKey];
14973
- if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14974
- return {};
14975
- }
14976
- function withStagedOverlay(store, kind, committed) {
14977
- const { staged } = getKindRegistry(store, kind);
14978
- if (staged.size === 0) return committed;
14979
- const view = { ...committed };
14980
- for (const [scopeKey, entries] of staged) {
14981
- if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
14982
- else view[scopeKey] = { ...view[scopeKey], ...entries };
14983
- }
14984
- return view;
14985
- }
14986
- function flushStagedScope(store, kind, scopeKey, isLeaf) {
14987
- const registry = getKindRegistry(store, kind);
14988
- const staged = registry.staged.get(scopeKey);
14989
- if (!staged) return;
14990
- registry.staged.delete(scopeKey);
14991
- const replace = !registry.valid.has(scopeKey);
14992
- store.setState((state) => {
14993
- const map = state[kind];
14994
- if (scopeKey === ROOT_SCOPE) {
14995
- if (!replace) return { [kind]: { ...map, ...staged } };
14996
- const next = {};
14997
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
14998
- return { [kind]: { ...next, ...staged } };
14999
- }
15000
- const current = replace ? {} : map[scopeKey] ?? {};
15001
- return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15002
- });
15003
- registry.valid.add(scopeKey);
15004
- }
15005
- function invalidateResource(store, kind, scopeKey) {
15006
- const registry = getKindRegistry(store, kind);
15007
- if (scopeKey === void 0) {
15008
- registry.valid.clear();
15009
- registry.staged.clear();
15010
- } else {
15011
- registry.valid.delete(scopeKey);
15012
- registry.staged.delete(scopeKey);
15013
- }
15014
- }
15015
- function rebuildResource(store, kind, userScope) {
15016
- const primary = resolvePrimary$1(store);
15017
- invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15018
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15019
- }
15020
- function invalidateAllResources(store) {
15021
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15022
- }
15023
- function removeResourceEntries(store, kind, names, scope, isLeaf) {
15024
- const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15025
- if (staged) for (const name of names) delete staged[name];
15026
- store.setState((state) => {
15027
- const map = state[kind];
15028
- if (scope) {
15029
- const currentScope = { ...map[scope] };
15030
- for (const name of names) delete currentScope[name];
15031
- return { [kind]: { ...map, [scope]: currentScope } };
15032
- }
15033
- const next = { ...map };
15034
- for (const name of names) if (isLeaf(next[name])) delete next[name];
15035
- return { [kind]: next };
15036
- });
15037
- }
15038
- function clearResourceEntries(store, kind, scope, isLeaf) {
15039
- const registry = getKindRegistry(store, kind);
15040
- if (scope && scope !== "root") registry.staged.delete(scope);
15041
- else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15042
- else registry.staged.clear();
15043
- store.setState((state) => {
15044
- const map = state[kind];
15045
- if (scope && scope !== "root") {
15046
- const { [scope]: _, ...rest } = map;
15047
- return { [kind]: rest };
15048
- }
15049
- if (scope === "root") {
15050
- const next = {};
15051
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15052
- return { [kind]: next };
15028
+ return { files: background, background: true };
15053
15029
  }
15054
- return { [kind]: {} };
15055
- });
15030
+ return { color: background, background: true };
15031
+ }
15032
+ if (background.isColor) {
15033
+ return { color: background, background: true };
15034
+ }
15035
+ return null;
15056
15036
  }
15057
15037
 
15058
- function clearHmrCaches(store) {
15059
- const primary = store.getState().primaryStore ?? store;
15060
- invalidateAllResources(primary);
15061
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15038
+ function parseRendererConfig(rendererProp) {
15039
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15040
+ if (isRendererConfig) {
15041
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15042
+ return {
15043
+ primaryCanvas: pc,
15044
+ scheduler: sc,
15045
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
15046
+ };
15047
+ }
15048
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
15062
15049
  }
15063
15050
 
15064
15051
  function CanvasImpl({
@@ -15098,9 +15085,9 @@ function CanvasImpl({
15098
15085
  React.useMemo(() => extend(THREE), []);
15099
15086
  const Bridge = useBridge();
15100
15087
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15101
- const hasInitialSizeRef = React.useRef(false);
15088
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15102
15089
  const measureConfig = React.useMemo(() => {
15103
- if (!hasInitialSizeRef.current) {
15090
+ if (!hasInitialSize) {
15104
15091
  return {
15105
15092
  ...resize,
15106
15093
  scroll: resize?.scroll ?? true,
@@ -15112,7 +15099,7 @@ function CanvasImpl({
15112
15099
  debounce: { scroll: 50, resize: 0 },
15113
15100
  ...resize
15114
15101
  };
15115
- }, [resize, hasInitialSizeRef.current]);
15102
+ }, [hasInitialSize, resize]);
15116
15103
  const [containerRef, containerRect] = useMeasure(measureConfig);
15117
15104
  const effectiveSize = React.useMemo(() => {
15118
15105
  let w = width ?? containerRect.width;
@@ -15128,9 +15115,7 @@ function CanvasImpl({
15128
15115
  left: containerRect.left
15129
15116
  };
15130
15117
  }, [width, height, containerRect, forceEven]);
15131
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15132
- hasInitialSizeRef.current = true;
15133
- }
15118
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15134
15119
  const canvasRef = React.useRef(null);
15135
15120
  const divRef = React.useRef(null);
15136
15121
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15244,6 +15229,7 @@ function CanvasImpl({
15244
15229
  const canvas = canvasRef.current;
15245
15230
  if (canvas) {
15246
15231
  return () => {
15232
+ if (canvas.isConnected) return;
15247
15233
  unmountComponentAtNode(canvas);
15248
15234
  root.current = null;
15249
15235
  };
@@ -15256,7 +15242,7 @@ function CanvasImpl({
15256
15242
  const handleHMR = () => {
15257
15243
  queueMicrotask(() => {
15258
15244
  const rootEntry = _roots.get(canvas);
15259
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15245
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15260
15246
  });
15261
15247
  };
15262
15248
  if (typeof import.meta !== "undefined" && import.meta.hot) {
@@ -15302,20 +15288,159 @@ function CanvasImpl({
15302
15288
  }
15303
15289
  );
15304
15290
  }
15305
- function Canvas(props) {
15291
+ function Canvas$1(props) {
15306
15292
  return /* @__PURE__ */ jsx(FiberProvider, { children: /* @__PURE__ */ jsx(CanvasImpl, { ...props }) });
15307
15293
  }
15308
15294
 
15295
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
15296
+ const ROOT_SCOPE = "";
15297
+ const registries = /* @__PURE__ */ new WeakMap();
15298
+ function getKindRegistry(store, kind) {
15299
+ let registry = registries.get(store);
15300
+ if (!registry) {
15301
+ registry = {
15302
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15303
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15304
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15305
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
15306
+ };
15307
+ registries.set(store, registry);
15308
+ }
15309
+ return registry[kind];
15310
+ }
15311
+ function resolvePrimary$1(store) {
15312
+ return store.getState().primaryStore ?? store;
15313
+ }
15314
+ function isScopeValid(store, kind, scopeKey) {
15315
+ return getKindRegistry(store, kind).valid.has(scopeKey);
15316
+ }
15317
+ function peekStaged(store, kind, scopeKey) {
15318
+ return getKindRegistry(store, kind).staged.get(scopeKey);
15319
+ }
15320
+ function stageEntries(store, kind, scopeKey, entries) {
15321
+ const { staged } = getKindRegistry(store, kind);
15322
+ const current = staged.get(scopeKey);
15323
+ if (current) Object.assign(current, entries);
15324
+ else staged.set(scopeKey, { ...entries });
15325
+ }
15326
+ function readCommittedScope(map, scopeKey, isLeaf) {
15327
+ if (scopeKey === ROOT_SCOPE) {
15328
+ const leaves = {};
15329
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
15330
+ return leaves;
15331
+ }
15332
+ const scopeData = map[scopeKey];
15333
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
15334
+ return {};
15335
+ }
15336
+ function withStagedOverlay(store, kind, committed) {
15337
+ const { staged } = getKindRegistry(store, kind);
15338
+ if (staged.size === 0) return committed;
15339
+ const view = { ...committed };
15340
+ for (const [scopeKey, entries] of staged) {
15341
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15342
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
15343
+ }
15344
+ return view;
15345
+ }
15346
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
15347
+ const registry = getKindRegistry(store, kind);
15348
+ const staged = registry.staged.get(scopeKey);
15349
+ if (!staged) return;
15350
+ registry.staged.delete(scopeKey);
15351
+ const replace = !registry.valid.has(scopeKey);
15352
+ store.setState((state) => {
15353
+ const map = state[kind];
15354
+ if (scopeKey === ROOT_SCOPE) {
15355
+ if (!replace) return { [kind]: { ...map, ...staged } };
15356
+ const next = {};
15357
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15358
+ return { [kind]: { ...next, ...staged } };
15359
+ }
15360
+ const current = replace ? {} : map[scopeKey] ?? {};
15361
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15362
+ });
15363
+ registry.valid.add(scopeKey);
15364
+ }
15365
+ function invalidateResource(store, kind, scopeKey) {
15366
+ const registry = getKindRegistry(store, kind);
15367
+ if (scopeKey === void 0) {
15368
+ registry.valid.clear();
15369
+ registry.staged.clear();
15370
+ } else {
15371
+ registry.valid.delete(scopeKey);
15372
+ registry.staged.delete(scopeKey);
15373
+ }
15374
+ }
15375
+ function rebuildResource(store, kind, userScope) {
15376
+ const primary = resolvePrimary$1(store);
15377
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15378
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15379
+ }
15380
+ function invalidateAllResources(store) {
15381
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15382
+ }
15383
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
15384
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15385
+ if (staged) for (const name of names) delete staged[name];
15386
+ store.setState((state) => {
15387
+ const map = state[kind];
15388
+ if (scope) {
15389
+ const currentScope = { ...map[scope] };
15390
+ for (const name of names) delete currentScope[name];
15391
+ return { [kind]: { ...map, [scope]: currentScope } };
15392
+ }
15393
+ const next = { ...map };
15394
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
15395
+ return { [kind]: next };
15396
+ });
15397
+ }
15398
+ function clearResourceEntries(store, kind, scope, isLeaf) {
15399
+ const registry = getKindRegistry(store, kind);
15400
+ if (scope && scope !== "root") registry.staged.delete(scope);
15401
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15402
+ else registry.staged.clear();
15403
+ store.setState((state) => {
15404
+ const map = state[kind];
15405
+ if (scope && scope !== "root") {
15406
+ const { [scope]: _, ...rest } = map;
15407
+ return { [kind]: rest };
15408
+ }
15409
+ if (scope === "root") {
15410
+ const next = {};
15411
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15412
+ return { [kind]: next };
15413
+ }
15414
+ return { [kind]: {} };
15415
+ });
15416
+ }
15417
+
15418
+ const isObjectLike = (value) => value !== null && (typeof value === "object" || typeof value === "function");
15419
+ const hasValidLegacyNodeMarkers = (value) => {
15420
+ const hasUuid = "uuid" in value;
15421
+ const hasNodeType = "nodeType" in value;
15422
+ const uuid = Reflect.get(value, "uuid");
15423
+ const nodeType = Reflect.get(value, "nodeType");
15424
+ return (hasUuid || hasNodeType) && (!hasUuid || uuid === void 0 || typeof uuid === "string") && (!hasNodeType || nodeType === void 0 || nodeType === null || typeof nodeType === "string");
15425
+ };
15426
+ const isUniformNode = (value) => isObjectLike(value) && "isUniformNode" in value && value.isUniformNode === true;
15427
+ const isTSLNode = (value) => isObjectLike(value) && (Reflect.get(value, "isNode") === true || Reflect.get(value, "shaderNode") !== void 0 && typeof Reflect.get(value, "id") === "number" || hasValidLegacyNodeMarkers(value));
15428
+ const isBufferLike = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView) || isObjectLike(value) && ("isBufferAttribute" in value || "isInterleavedBufferAttribute" in value || isTSLNode(value));
15429
+ const isStorageLike = (value) => isObjectLike(value) && ("isTexture" in value || "isData3DTexture" in value || isTSLNode(value));
15430
+
15309
15431
  var __defProp = Object.defineProperty;
15310
15432
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15311
15433
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15312
- var _a;
15434
+ var _a, _b;
15313
15435
  const INTERNAL_DATA = Symbol("ScopedStore.data");
15314
- _a = INTERNAL_DATA;
15436
+ const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
15437
+ _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
15315
15438
  const _ScopedStore = class _ScopedStore {
15316
- constructor(data) {
15439
+ constructor(data, isLeaf) {
15440
+ __publicField(this, _b);
15317
15441
  __publicField(this, _a);
15318
15442
  this[INTERNAL_DATA] = data;
15443
+ this[INTERNAL_IS_LEAF] = isLeaf;
15319
15444
  return new Proxy(this, {
15320
15445
  get(target, prop, receiver) {
15321
15446
  if (typeof prop === "string") {
@@ -15349,10 +15474,10 @@ const _ScopedStore = class _ScopedStore {
15349
15474
  * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15350
15475
  */
15351
15476
  scope(key) {
15352
- const data = this[INTERNAL_DATA][key];
15353
- return new _ScopedStore(
15354
- data && typeof data === "object" ? data : {}
15355
- );
15477
+ const value = this[INTERNAL_DATA][key];
15478
+ const isLeaf = this[INTERNAL_IS_LEAF];
15479
+ const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
15480
+ return new _ScopedStore(scope, isLeaf);
15356
15481
  }
15357
15482
  /**
15358
15483
  * Check if a key exists in the store.
@@ -15368,8 +15493,8 @@ const _ScopedStore = class _ScopedStore {
15368
15493
  }
15369
15494
  };
15370
15495
  let ScopedStore = _ScopedStore;
15371
- function createScopedStore(data) {
15372
- return new ScopedStore(data);
15496
+ function createScopedStore(data, isLeaf) {
15497
+ return new ScopedStore(data, isLeaf);
15373
15498
  }
15374
15499
  function createLazyCreatorState(state, store) {
15375
15500
  let _uniforms = null;
@@ -15380,22 +15505,22 @@ function createLazyCreatorState(state, store) {
15380
15505
  return Object.create(state, {
15381
15506
  uniforms: {
15382
15507
  get() {
15383
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15508
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
15384
15509
  }
15385
15510
  },
15386
15511
  nodes: {
15387
15512
  get() {
15388
- return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15513
+ return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
15389
15514
  }
15390
15515
  },
15391
15516
  buffers: {
15392
15517
  get() {
15393
- return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15518
+ return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
15394
15519
  }
15395
15520
  },
15396
15521
  gpuStorage: {
15397
15522
  get() {
15398
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15523
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
15399
15524
  }
15400
15525
  }
15401
15526
  });
@@ -15415,44 +15540,10 @@ function usePrimaryThree(selector = (state) => state, equalityFn) {
15415
15540
  return primary(selector, equalityFn);
15416
15541
  }
15417
15542
 
15418
- function addTexture(set, key, value) {
15419
- set((state) => {
15420
- const newMap = new Map(state.textures);
15421
- newMap.set(key, value);
15422
- return { textures: newMap };
15423
- });
15424
- }
15425
- function addTextures(set, items) {
15426
- set((state) => {
15427
- const newMap = new Map(state.textures);
15428
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
15429
- for (const [key, value] of entries) {
15430
- newMap.set(key, value);
15431
- }
15432
- return { textures: newMap };
15433
- });
15434
- }
15435
- function removeTexture(set, key) {
15436
- set((state) => {
15437
- const newMap = new Map(state.textures);
15438
- newMap.delete(key);
15439
- return { textures: newMap };
15440
- });
15441
- }
15442
- function removeTextures(set, keys) {
15443
- set((state) => {
15444
- const newMap = new Map(state.textures);
15445
- for (const key of keys) newMap.delete(key);
15446
- return { textures: newMap };
15447
- });
15448
- }
15449
- function createTextureOperations(set) {
15450
- return {
15451
- add: (key, value) => addTexture(set, key, value),
15452
- addMultiple: (items) => addTextures(set, items),
15453
- remove: (key) => removeTexture(set, key),
15454
- removeMultiple: (keys) => removeTextures(set, keys)
15455
- };
15543
+ function scopedNodeName(scope, name) {
15544
+ const raw = scope ? `${scope}_${name}` : name;
15545
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15546
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15456
15547
  }
15457
15548
  function extractTSLValue(value) {
15458
15549
  if (value === null || value === void 0) return value;
@@ -15491,7 +15582,7 @@ function vectorize(inObject) {
15491
15582
  return extractTSLValue(inObject);
15492
15583
  }
15493
15584
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15494
- if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15585
+ if (obj.isMatrix2 || obj.isMatrix3 || obj.isMatrix4) return inObject;
15495
15586
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15496
15587
  if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15497
15588
  const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
@@ -15518,8 +15609,26 @@ function useCompareMemoize(value, deep) {
15518
15609
  return ref.current;
15519
15610
  }
15520
15611
 
15612
+ function clearHmrCaches(store) {
15613
+ const primary = store.getState().primaryStore ?? store;
15614
+ invalidateAllResources(primary);
15615
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15616
+ }
15617
+
15618
+ let registered = false;
15619
+ function ensureTSLExtension() {
15620
+ if (registered) return;
15621
+ registered = true;
15622
+ registerRootExtension({
15623
+ name: "@react-three/tsl",
15624
+ // Resolves primaryStore itself, so every canvas sharing a renderer refreshes the same resources.
15625
+ hmr: clearHmrCaches
15626
+ });
15627
+ }
15628
+
15521
15629
  function useScopedResource(options) {
15522
15630
  const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15631
+ ensureTSLExtension();
15523
15632
  const scopeKey = scope ?? ROOT_SCOPE;
15524
15633
  const version = store((s) => s._hmrVersion);
15525
15634
  const entries = useMemo(() => {
@@ -15549,22 +15658,21 @@ function useScopedResource(options) {
15549
15658
  return entries;
15550
15659
  }
15551
15660
 
15552
- const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15553
15661
  function useUniforms(creatorOrScope, scope) {
15554
15662
  const store = usePrimaryStore();
15555
15663
  const removeUniforms2 = useCallback(
15556
- (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15664
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode),
15557
15665
  [store]
15558
15666
  );
15559
15667
  const clearUniforms = useCallback(
15560
- (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15668
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode),
15561
15669
  [store]
15562
15670
  );
15563
15671
  const rebuildUniforms = useCallback(
15564
15672
  (targetScope) => rebuildResource(store, "uniforms", targetScope),
15565
15673
  [store]
15566
15674
  );
15567
- const inputForMemoization = useMemo(() => {
15675
+ const processedInput = useMemo(() => {
15568
15676
  let raw = creatorOrScope;
15569
15677
  if (is.fun(creatorOrScope)) {
15570
15678
  const wrappedState = createLazyCreatorState(store.getState(), store);
@@ -15575,23 +15683,24 @@ function useUniforms(creatorOrScope, scope) {
15575
15683
  for (const [key, value] of Object.entries(raw)) {
15576
15684
  normalized[key] = vectorize(value);
15577
15685
  }
15578
- return normalized;
15686
+ return { candidates: raw, normalized };
15579
15687
  }
15580
- return raw;
15688
+ return { candidates: raw, normalized: raw };
15581
15689
  }, [creatorOrScope, store]);
15582
- const memoizedInput = useCompareMemoize(inputForMemoization);
15690
+ const memoizedInput = useCompareMemoize(processedInput.normalized);
15583
15691
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15584
15692
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15585
15693
  const created = useScopedResource({
15586
15694
  store,
15587
15695
  kind: "uniforms",
15588
15696
  scope: isReader ? void 0 : scope,
15589
- isLeaf: isUniformNode$1,
15697
+ isLeaf: isUniformNode,
15590
15698
  input: memoizedInput,
15591
15699
  create: () => {
15592
15700
  if (isReader) return {};
15593
- if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15594
- return memoizedInput;
15701
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
15702
+ throw new Error("Invalid uniform input");
15703
+ return processedInput.candidates;
15595
15704
  },
15596
15705
  prepare: (name, candidate) => createUniform(name, candidate, scope),
15597
15706
  reconcile: (existing, candidate) => {
@@ -15613,7 +15722,7 @@ function useUniforms(creatorOrScope, scope) {
15613
15722
  uniforms = storeUniforms;
15614
15723
  } else if (typeof memoizedInput === "string") {
15615
15724
  const scopeData = storeUniforms[memoizedInput];
15616
- uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15725
+ uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
15617
15726
  }
15618
15727
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15619
15728
  }
@@ -15628,7 +15737,7 @@ function removeUniforms(set, names, scope) {
15628
15737
  return { uniforms: { ...state.uniforms, [scope]: currentScope } };
15629
15738
  }
15630
15739
  const uniforms = { ...state.uniforms };
15631
- for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
15740
+ for (const name of names) if (isUniformNode(uniforms[name])) delete uniforms[name];
15632
15741
  return { uniforms };
15633
15742
  });
15634
15743
  }
@@ -15642,7 +15751,7 @@ function clearRootUniforms(set) {
15642
15751
  set((state) => {
15643
15752
  const uniforms = {};
15644
15753
  for (const [key, value] of Object.entries(state.uniforms)) {
15645
- if (!isUniformNode$1(value)) uniforms[key] = value;
15754
+ if (!isUniformNode(value)) uniforms[key] = value;
15646
15755
  }
15647
15756
  return { uniforms };
15648
15757
  });
@@ -15652,8 +15761,7 @@ function createUniform(inName, node, scope) {
15652
15761
  const inValue = vectorize(node);
15653
15762
  const newUniform = uniform(inValue);
15654
15763
  if (typeof newUniform.setName === "function") {
15655
- const name = scope ? `${scope}_${inName}` : inName;
15656
- newUniform.setName(name);
15764
+ newUniform.setName(scopedNodeName(scope, inName));
15657
15765
  }
15658
15766
  return newUniform;
15659
15767
  }
@@ -15679,12 +15787,10 @@ function hasEqualsMethod(value) {
15679
15787
  function isSameThreeType(a, b) {
15680
15788
  const obj_a = a;
15681
15789
  const obj_b = b;
15682
- return obj_a.isColor && obj_b.isColor || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15790
+ return obj_a.isColor && obj_b.isColor || obj_a.isMatrix2 && obj_b.isMatrix2 || obj_a.isMatrix3 && obj_b.isMatrix3 || obj_a.isMatrix4 && obj_b.isMatrix4 || obj_a.isEuler && obj_b.isEuler || obj_a.isQuaternion && obj_b.isQuaternion;
15683
15791
  }
15684
15792
 
15685
15793
  const uniformOf = uniform;
15686
- const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15687
- const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15688
15794
  function useUniform(name, value) {
15689
15795
  const store = useStore();
15690
15796
  const registered = useScopedResource({
@@ -15696,7 +15802,7 @@ function useUniform(name, value) {
15696
15802
  create: () => value === void 0 ? {} : { [name]: value },
15697
15803
  prepare: createNamedUniform,
15698
15804
  reconcile: (existing2) => {
15699
- if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15805
+ if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
15700
15806
  existing2.value = typeof value === "string" ? new Color(value) : value;
15701
15807
  }
15702
15808
  }
@@ -15712,7 +15818,7 @@ function createNamedUniform(name, value) {
15712
15818
  let node;
15713
15819
  if (isUniformNode(value)) {
15714
15820
  node = value;
15715
- } else if (isTSLNode$1(value)) {
15821
+ } else if (isTSLNode(value)) {
15716
15822
  node = uniformOf(value);
15717
15823
  } else if (typeof value === "string") {
15718
15824
  node = uniform(new Color(value));
@@ -15725,7 +15831,6 @@ function createNamedUniform(name, value) {
15725
15831
  return node;
15726
15832
  }
15727
15833
 
15728
- const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15729
15834
  function useNodes(creatorOrScope, scope) {
15730
15835
  const store = usePrimaryStore();
15731
15836
  const removeNodes2 = useCallback(
@@ -15752,7 +15857,8 @@ function useNodes(creatorOrScope, scope) {
15752
15857
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15753
15858
  },
15754
15859
  prepare: (name, node) => {
15755
- node.setName?.(scope ? `${scope}.${name}` : name);
15860
+ const setName = Reflect.get(node, "setName");
15861
+ if (typeof setName === "function") setName.call(node, scopedNodeName(scope, name));
15756
15862
  return node;
15757
15863
  }
15758
15864
  });
@@ -15807,13 +15913,6 @@ function useLocalNodes(creator) {
15807
15913
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15808
15914
  }
15809
15915
 
15810
- const isBufferLike = (value) => {
15811
- if (value === null || typeof value !== "object") return false;
15812
- if (ArrayBuffer.isView(value)) return true;
15813
- if ("isBufferAttribute" in value) return true;
15814
- if ("uuid" in value || "nodeType" in value) return true;
15815
- return false;
15816
- };
15817
15916
  const disposeBuffer = (buffer) => {
15818
15917
  if (buffer === null || typeof buffer !== "object") return;
15819
15918
  if ("dispose" in buffer && typeof buffer.dispose === "function") {
@@ -15860,9 +15959,8 @@ function useBuffers(creatorOrScope, scope) {
15860
15959
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15861
15960
  },
15862
15961
  prepare: (name, buffer) => {
15863
- if ("setName" in buffer && typeof buffer.setName === "function") {
15864
- buffer.setName(scope ? `${scope}.${name}` : name);
15865
- }
15962
+ const setName = Reflect.get(buffer, "setName");
15963
+ if (typeof setName === "function") setName.call(buffer, scopedNodeName(scope, name));
15866
15964
  return buffer;
15867
15965
  }
15868
15966
  });
@@ -15879,13 +15977,6 @@ function rebuildAllBuffers(store, scope) {
15879
15977
  rebuildResource(store, "buffers", scope);
15880
15978
  }
15881
15979
 
15882
- const isStorageLike = (value) => {
15883
- if (value === null || typeof value !== "object") return false;
15884
- if ("isTexture" in value) return true;
15885
- if ("isData3DTexture" in value) return true;
15886
- if ("uuid" in value || "nodeType" in value) return true;
15887
- return false;
15888
- };
15889
15980
  const disposeStorage = (storage) => {
15890
15981
  if (storage === null || typeof storage !== "object") return;
15891
15982
  if ("dispose" in storage && typeof storage.dispose === "function") {
@@ -15932,10 +16023,9 @@ function useGPUStorage(creatorOrScope, scope) {
15932
16023
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15933
16024
  },
15934
16025
  prepare: (name, storage) => {
15935
- const label = scope ? `${scope}.${name}` : name;
15936
- if ("setName" in storage && typeof storage.setName === "function") {
15937
- storage.setName(label);
15938
- }
16026
+ const label = scopedNodeName(scope, name);
16027
+ const setName = Reflect.get(storage, "setName");
16028
+ if (typeof setName === "function") setName.call(storage, label);
15939
16029
  if ("name" in storage && typeof storage.name === "string") {
15940
16030
  storage.name = label;
15941
16031
  }
@@ -15961,11 +16051,58 @@ function rebuildAllStorage(store, scope) {
15961
16051
  rebuildResource(store, "gpuStorage", scope);
15962
16052
  }
15963
16053
 
16054
+ function addTexture(set, key, value) {
16055
+ set((state) => {
16056
+ const newMap = new Map(state.textures);
16057
+ newMap.set(key, value);
16058
+ return { textures: newMap };
16059
+ });
16060
+ }
16061
+ function addTextures(set, items) {
16062
+ set((state) => {
16063
+ const newMap = new Map(state.textures);
16064
+ const entries = items instanceof Map ? items.entries() : Object.entries(items);
16065
+ for (const [key, value] of entries) {
16066
+ newMap.set(key, value);
16067
+ }
16068
+ return { textures: newMap };
16069
+ });
16070
+ }
16071
+ function removeTexture(set, key) {
16072
+ set((state) => {
16073
+ const newMap = new Map(state.textures);
16074
+ newMap.delete(key);
16075
+ return { textures: newMap };
16076
+ });
16077
+ }
16078
+ function removeTextures(set, keys) {
16079
+ set((state) => {
16080
+ const newMap = new Map(state.textures);
16081
+ for (const key of keys) newMap.delete(key);
16082
+ return { textures: newMap };
16083
+ });
16084
+ }
16085
+ function createTextureOperations(set) {
16086
+ return {
16087
+ add: (key, value) => addTexture(set, key, value),
16088
+ addMultiple: (items) => addTextures(set, items),
16089
+ remove: (key) => removeTexture(set, key),
16090
+ removeMultiple: (keys) => removeTextures(set, keys)
16091
+ };
16092
+ }
16093
+
16094
+ function renderThroughPipeline(store) {
16095
+ return () => {
16096
+ const pipeline = store.getState().renderPipeline;
16097
+ if (pipeline?.render) pipeline.render();
16098
+ };
16099
+ }
15964
16100
  function useRenderPipeline(mainCB, setupCB) {
15965
16101
  const store = useStore();
15966
- const { scene, camera, renderer, isLegacy } = useThree();
16102
+ const { scene, camera, renderer, isLegacy } = useThree$1();
15967
16103
  const callbacksRanRef = useRef(false);
15968
16104
  const scenePassCacheRef = useRef(null);
16105
+ const forceScenePassRebuildRef = useRef(false);
15969
16106
  const mainCBRef = useRef(mainCB);
15970
16107
  const setupCBRef = useRef(setupCB);
15971
16108
  mainCBRef.current = mainCB;
@@ -15973,7 +16110,7 @@ function useRenderPipeline(mainCB, setupCB) {
15973
16110
  const [rebuildVersion, setRebuildVersion] = useState(0);
15974
16111
  useEffect(() => {
15975
16112
  callbacksRanRef.current = false;
15976
- scenePassCacheRef.current = null;
16113
+ forceScenePassRebuildRef.current = true;
15977
16114
  }, []);
15978
16115
  const clearPasses = useCallback(() => {
15979
16116
  store.setState({ passes: {} });
@@ -15983,12 +16120,15 @@ function useRenderPipeline(mainCB, setupCB) {
15983
16120
  renderPipeline: null,
15984
16121
  passes: {}
15985
16122
  });
16123
+ setRenderOverride(store, null);
16124
+ scenePassCacheRef.current?.scenePass.dispose();
15986
16125
  callbacksRanRef.current = false;
15987
16126
  scenePassCacheRef.current = null;
16127
+ forceScenePassRebuildRef.current = false;
15988
16128
  }, [store]);
15989
16129
  const rebuild = useCallback(() => {
15990
16130
  callbacksRanRef.current = false;
15991
- scenePassCacheRef.current = null;
16131
+ forceScenePassRebuildRef.current = true;
15992
16132
  setRebuildVersion((v) => v + 1);
15993
16133
  }, []);
15994
16134
  useLayoutEffect(() => {
@@ -15996,6 +16136,9 @@ function useRenderPipeline(mainCB, setupCB) {
15996
16136
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
15997
16137
  }
15998
16138
  if (!renderer || !scene || !camera) return;
16139
+ if (!("isWebGPURenderer" in renderer)) {
16140
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16141
+ }
15999
16142
  const state = store.getState();
16000
16143
  const set = store.setState;
16001
16144
  try {
@@ -16006,21 +16149,34 @@ function useRenderPipeline(mainCB, setupCB) {
16006
16149
  pipeline = new RenderPipeline(renderer);
16007
16150
  justCreatedPipeline = true;
16008
16151
  }
16009
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16152
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16153
+ let outgoingScenePass = null;
16010
16154
  let scenePass;
16011
16155
  if (cacheValid) {
16012
16156
  scenePass = scenePassCacheRef.current.scenePass;
16013
16157
  } else {
16158
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16014
16159
  scenePass = pass(scene, camera);
16015
16160
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16161
+ forceScenePassRebuildRef.current = false;
16016
16162
  }
16017
16163
  currentPasses.scenePass = scenePass;
16018
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16164
+ let outputNodeChanged = false;
16165
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16166
+ pipeline.outputNode = scenePass;
16167
+ outputNodeChanged = true;
16168
+ }
16019
16169
  set({ renderPipeline: pipeline, passes: currentPasses });
16170
+ setRenderOverride(store, renderThroughPipeline(store));
16020
16171
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16021
16172
  if (shouldRunCallbacks) {
16022
16173
  if (setupCBRef.current) {
16023
- const freshState = store.getState();
16174
+ const freshState = Object.assign({}, store.getState(), {
16175
+ renderPipeline: pipeline,
16176
+ passes: { ...currentPasses, scenePass },
16177
+ renderer,
16178
+ isLegacy: false
16179
+ });
16024
16180
  const setupResult = setupCBRef.current(freshState);
16025
16181
  if (setupResult && typeof setupResult === "object") {
16026
16182
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16028,32 +16184,37 @@ function useRenderPipeline(mainCB, setupCB) {
16028
16184
  }
16029
16185
  }
16030
16186
  if (mainCBRef.current) {
16031
- const freshState = store.getState();
16187
+ const freshState = Object.assign({}, store.getState(), {
16188
+ renderPipeline: pipeline,
16189
+ passes: { ...currentPasses, scenePass },
16190
+ renderer,
16191
+ isLegacy: false
16192
+ });
16032
16193
  const mainResult = mainCBRef.current(freshState);
16033
16194
  if (mainResult && typeof mainResult === "object") {
16034
16195
  currentPasses = { ...currentPasses, ...mainResult };
16035
16196
  set({ passes: currentPasses });
16036
16197
  }
16037
16198
  }
16199
+ outputNodeChanged = true;
16038
16200
  callbacksRanRef.current = true;
16039
16201
  }
16202
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16203
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16040
16204
  } catch (error) {
16041
16205
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16042
16206
  }
16043
16207
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16044
- const passes = useThree((s) => s.passes);
16045
- const renderPipeline = useThree((s) => s.renderPipeline);
16046
- return {
16047
- passes,
16048
- renderPipeline,
16049
- clearPasses,
16050
- reset,
16051
- rebuild,
16052
- // isReady indicates if RenderPipeline is configured and ready for rendering
16053
- isReady: renderPipeline !== null
16054
- };
16208
+ const passes = useThree$1((s) => s.passes);
16209
+ const renderPipeline = useThree$1((s) => s.renderPipeline);
16210
+ const actions = { passes, clearPasses, reset, rebuild };
16211
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16212
+ return { ...actions, isReady: false, renderPipeline: null };
16055
16213
  }
16056
16214
 
16057
16215
  extend(THREE);
16216
+ const useThree = useThree$1;
16217
+ const useFrame = useFrame$1;
16218
+ const Canvas = Canvas$1;
16058
16219
 
16059
- 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, 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$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, removeInteractivity, removeNodes, removeUniforms, resolve, swapInteractivity, 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 };
16220
+ 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, 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$1 as isRef, isRenderer, isTexture, isVectorLike, once, prepare, presetsObj, rebuildAllBuffers, rebuildAllNodes, rebuildAllStorage, rebuildAllUniforms, reconciler, registerPrimary, registerRootExtension, removeInteractivity, removeNodes, removeUniforms, resolve, setRenderOverride, swapInteractivity, 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 };