@react-three/fiber 10.0.0-canary.da7a768 → 10.0.0-canary.dc0ec15

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,8 +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';
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';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import * as React from 'react';
5
- 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';
6
6
  import useMeasure from 'react-use-measure';
7
7
  import { useFiber, useContextBridge, traverseFiber, FiberProvider } from 'its-fine';
8
8
  import { createWithEqualityFn } from 'zustand/traditional';
@@ -11,6 +11,7 @@ export { Scheduler, getScheduler } from '@pmndrs/scheduler';
11
11
  import Tb, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
12
12
  import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
13
13
  import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';
14
+ import { HDRCubeTextureLoader } from 'three/examples/jsm/loaders/HDRCubeTextureLoader.js';
14
15
  import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
15
16
  import { UltraHDRLoader } from 'three/examples/jsm/loaders/UltraHDRLoader.js';
16
17
  import { GainMapLoader } from '@monogrid/gainmap-js';
@@ -303,7 +304,6 @@ function prepare(target, root, type, props) {
303
304
  }
304
305
  function invalidateInstance(instance) {
305
306
  if (!instance.parent) return;
306
- instance.props.onUpdate?.(instance.object);
307
307
  const state = instance.root?.getState?.();
308
308
  if (state && state.internal.frames === 0) state.invalidate();
309
309
  }
@@ -519,8 +519,10 @@ function hasVisibilityHandlers(handlers) {
519
519
  }
520
520
 
521
521
  const FROM_REF = Symbol.for("@react-three/fiber.fromRef");
522
- function fromRef(ref) {
523
- return { [FROM_REF]: ref };
522
+ function fromRef(ref, transform) {
523
+ const marker = { [FROM_REF]: ref };
524
+ if (transform) marker.transform = transform;
525
+ return marker;
524
526
  }
525
527
  function isFromRef(value) {
526
528
  return value !== null && typeof value === "object" && FROM_REF in value;
@@ -534,6 +536,63 @@ function isOnce(value) {
534
536
  return value !== null && typeof value === "object" && ONCE in value;
535
537
  }
536
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
+
537
596
  const RESERVED_PROPS = [
538
597
  "children",
539
598
  "key",
@@ -543,7 +602,6 @@ const RESERVED_PROPS = [
543
602
  "dispose",
544
603
  "attach",
545
604
  "object",
546
- "onUpdate",
547
605
  // Behavior flags
548
606
  "dispose"
549
607
  ];
@@ -621,11 +679,9 @@ function diffProps(instance, newProps) {
621
679
  for (const prop in instance.props) {
622
680
  if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
623
681
  const { root, key } = resolve(instance.object, prop);
624
- if (root.constructor && root.constructor.length === 0) {
682
+ if (typeof root?.constructor === "function") {
625
683
  const ctor = getMemoizedPrototype(root);
626
684
  if (!is.und(ctor)) changedProps[prop] = ctor[key];
627
- } else {
628
- changedProps[prop] = 0;
629
685
  }
630
686
  }
631
687
  return changedProps;
@@ -637,6 +693,13 @@ function applyProps(object, props) {
637
693
  for (const prop in props) {
638
694
  const value = props[prop];
639
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
+ }
640
703
  if (instance && EVENT_REGEX.test(prop)) {
641
704
  if (typeof value === "function") instance.handlers[prop] = value;
642
705
  else delete instance.handlers[prop];
@@ -712,376 +775,47 @@ function applyProps(object, props) {
712
775
  }
713
776
  if (instance?.parent && rootState?.internal && instance.object?.isObject3D && prevHandlers !== instance.eventCount) {
714
777
  const object2 = instance.object;
715
- const index = rootState.internal.interaction.indexOf(object2);
716
- if (index > -1) rootState.internal.interaction.splice(index, 1);
717
- if (instance.eventCount && object2.raycast !== null) {
718
- rootState.internal.interaction.push(object2);
719
- }
720
- }
721
- if (instance?.parent && rootState?.internal && instance.object?.isObject3D) {
722
- const object2 = instance.object;
723
- const root = findInitialRoot(instance);
724
- const visibilityHandlers = {
725
- onFramed: instance.handlers.onFramed,
726
- onOccluded: instance.handlers.onOccluded,
727
- onVisible: instance.handlers.onVisible
728
- };
729
- if (hasVisibilityHandlers(visibilityHandlers)) {
730
- if (!updateVisibilityHandlers(root, object2, visibilityHandlers)) {
731
- registerVisibility(root, object2, visibilityHandlers);
732
- }
733
- } else {
734
- unregisterVisibility(root, object2);
735
- }
736
- }
737
- if (instance && instance.props.attach === void 0) {
738
- if (instance.object.isBufferGeometry) instance.props.attach = "geometry";
739
- else if (instance.object.isMaterial) instance.props.attach = "material";
740
- }
741
- if (instance) invalidateInstance(instance);
742
- return object;
743
- }
744
-
745
- const shownNotices = /* @__PURE__ */ new Set();
746
- function notifyDepreciated({ heading, body, link }) {
747
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_DEPRECATION_WARNINGS !== "true") {
748
- return;
749
- }
750
- if (shownNotices.has(heading)) return;
751
- shownNotices.add(heading);
752
- const caller = getCallerFrame();
753
- if (caller) {
754
- console.log();
755
- }
756
- const boxStyle = "background: #ff9800; color: #1a1a1a; padding: 8px 12px; border-radius: 4px; font-weight: 500;";
757
- if (caller) {
758
- console.log(`%c\u26A0\uFE0F ${heading}`, boxStyle);
759
- }
760
- if (body || link) {
761
- let message = "";
762
- if (body) message += body;
763
- if (link) message += (body ? "\n\n" : "") + `More info: ${link}`;
764
- console.warn(`%c${message}`, "font-weight: bold;");
765
- }
766
- }
767
- function getCallerFrame(depth = 3) {
768
- const stack = new Error().stack;
769
- if (!stack) return null;
770
- const lines = stack.split("\n");
771
- const frame = lines[depth];
772
- if (!frame) return null;
773
- let match = frame.match(/^\s*at (?:(.+?) )?\(?(.+?):(\d+):(\d+)\)?$/);
774
- if (!match) {
775
- match = frame.match(/^(?:(.+?)@)?(.+?):(\d+):(\d+)$/);
776
- }
777
- if (!match) return null;
778
- const [, fn, url, line] = match;
779
- return {
780
- functionName: fn ?? "<anonymous>",
781
- location: formatLocation(url, Number(line))
782
- };
783
- }
784
- function formatLocation(url, line) {
785
- const clean = url.split("?")[0];
786
- const file = clean.split("/").pop() ?? clean;
787
- return `${file}:${line}`;
788
- }
789
- function notifyAlpha({ message, link }) {
790
- if (typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== void 0) && process.env.R3F_SHOW_ALPHA_WARNINGS !== "true") {
791
- return;
792
- }
793
- if (shownNotices.has(message)) return;
794
- shownNotices.add(message);
795
- const boxStyle = "background: #6366f1; color: #ffffff; padding: 6px 10px; border-radius: 4px; font-weight: 500;";
796
- console.log(`%c\u{1F52C} ${message}`, boxStyle);
797
- {
798
- console.log(`%cMore info: ${link}`, "color: #6366f1; font-weight: normal;");
799
- }
800
- }
801
-
802
- const INTERNAL_LIB_MARKERS = ["zustand", "setState", "Object.assign"];
803
- function isInternalRendererAccess(stack, callerDepth = 2) {
804
- if (!stack) return false;
805
- const caller = stack.split("\n")[callerDepth] ?? "";
806
- return INTERNAL_LIB_MARKERS.some((m) => caller.includes(m));
807
- }
808
-
809
- const R3F_CONTEXT = Symbol.for("@react-three/fiber.context");
810
- const context = globalThis[R3F_CONTEXT] ?? (globalThis[R3F_CONTEXT] = React.createContext(null));
811
- const createStore = (invalidate, advance) => {
812
- const rootStore = createWithEqualityFn((set, get) => {
813
- const position = new Vector3();
814
- const defaultTarget = new Vector3();
815
- const tempTarget = new Vector3();
816
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
817
- const { width, height, top, left } = size;
818
- const aspect = width / height;
819
- if (target.isVector3) tempTarget.copy(target);
820
- else tempTarget.set(...target);
821
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
822
- if (isOrthographicCamera(camera)) {
823
- return { width: width / camera.zoom, height: height / camera.zoom, top, left, factor: 1, distance, aspect };
824
- } else {
825
- const fov = camera.fov * Math.PI / 180;
826
- const h = 2 * Math.tan(fov / 2) * distance;
827
- const w = h * (width / height);
828
- return { width: w, height: h, top, left, factor: width / w, distance, aspect };
829
- }
830
- }
831
- let performanceTimeout = void 0;
832
- const setPerformanceCurrent = (current) => set((state2) => ({ performance: { ...state2.performance, current } }));
833
- const pointer = new Vector2();
834
- const rootState = {
835
- set,
836
- get,
837
- // Mock objects that have to be configured
838
- // primaryStore is set after store creation (self-reference for primary, primary's store for secondary)
839
- primaryStore: null,
840
- gl: null,
841
- renderer: null,
842
- camera: null,
843
- frustum: new Frustum(),
844
- autoUpdateFrustum: true,
845
- raycaster: null,
846
- events: {
847
- priority: 1,
848
- enabled: true,
849
- connected: false,
850
- frameTimedRaycasts: true,
851
- alwaysFireOnScroll: true,
852
- updateOnFrame: false
853
- },
854
- scene: null,
855
- rootScene: null,
856
- xr: null,
857
- inspector: null,
858
- invalidate: (frames = 1, stackFrames = false) => invalidate(get(), frames, stackFrames),
859
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
860
- textureColorSpace: SRGBColorSpace,
861
- isLegacy: false,
862
- webGPUSupported: false,
863
- isNative: false,
864
- controls: null,
865
- pointer,
866
- mouse: pointer,
867
- frameloop: "always",
868
- onPointerMissed: void 0,
869
- onDragOverMissed: void 0,
870
- onDropMissed: void 0,
871
- performance: {
872
- current: 1,
873
- min: 0.5,
874
- max: 1,
875
- debounce: 200,
876
- regress: () => {
877
- const state2 = get();
878
- if (performanceTimeout) clearTimeout(performanceTimeout);
879
- if (state2.performance.current !== state2.performance.min) setPerformanceCurrent(state2.performance.min);
880
- performanceTimeout = setTimeout(
881
- () => setPerformanceCurrent(get().performance.max),
882
- state2.performance.debounce
883
- );
884
- }
885
- },
886
- size: { width: 0, height: 0, top: 0, left: 0 },
887
- viewport: {
888
- initialDpr: 0,
889
- dpr: 0,
890
- width: 0,
891
- height: 0,
892
- top: 0,
893
- left: 0,
894
- aspect: 0,
895
- distance: 0,
896
- factor: 0,
897
- getCurrentViewport
898
- },
899
- setEvents: (events) => set((state2) => ({ ...state2, events: { ...state2.events, ...events } })),
900
- setSize: (width, height, top, left) => {
901
- const state2 = get();
902
- if (width === void 0) {
903
- set({ _sizeImperative: false });
904
- if (state2._sizeProps) {
905
- const { width: propW, height: propH } = state2._sizeProps;
906
- if (propW !== void 0 || propH !== void 0) {
907
- const currentSize = state2.size;
908
- const newSize = {
909
- width: propW ?? currentSize.width,
910
- height: propH ?? currentSize.height,
911
- top: currentSize.top,
912
- left: currentSize.left
913
- };
914
- set((s) => ({
915
- size: newSize,
916
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, newSize) }
917
- }));
918
- getScheduler().invalidate();
919
- }
920
- }
921
- return;
922
- }
923
- const w = width;
924
- const h = height ?? width;
925
- const t = top ?? state2.size.top;
926
- const l = left ?? state2.size.left;
927
- const size = { width: w, height: h, top: t, left: l };
928
- set((s) => ({
929
- size,
930
- viewport: { ...s.viewport, ...getCurrentViewport(state2.camera, defaultTarget, size) },
931
- _sizeImperative: true
932
- }));
933
- getScheduler().invalidate();
934
- },
935
- setDpr: (dpr) => set((state2) => {
936
- const resolved = calculateDpr(dpr);
937
- return { viewport: { ...state2.viewport, dpr: resolved, initialDpr: state2.viewport.initialDpr || resolved } };
938
- }),
939
- setFrameloop: (frameloop = "always") => {
940
- set(() => ({ frameloop }));
941
- getScheduler().frameloop = frameloop;
942
- },
943
- setError: (error) => set(() => ({ error })),
944
- error: null,
945
- //* TSL State (managed via hooks: useUniforms, useNodes, useBuffers, useGPUStorage, useTextures, useRenderPipeline) ==============================
946
- uniforms: {},
947
- nodes: {},
948
- buffers: {},
949
- gpuStorage: {},
950
- textures: /* @__PURE__ */ new Map(),
951
- _textureRefs: /* @__PURE__ */ new Map(),
952
- renderPipeline: null,
953
- passes: {},
954
- _hmrVersion: 0,
955
- _sizeImperative: false,
956
- _sizeProps: null,
957
- previousRoot: void 0,
958
- internal: {
959
- // Events
960
- interaction: [],
961
- subscribers: [],
962
- // Per-pointer state (new unified structure)
963
- pointerMap: /* @__PURE__ */ new Map(),
964
- pointerDirty: /* @__PURE__ */ new Map(),
965
- lastEvent: React.createRef(),
966
- // Deprecated but kept for backwards compatibility
967
- hovered: /* @__PURE__ */ new Map(),
968
- initialClick: [0, 0],
969
- initialHits: [],
970
- capturedMap: /* @__PURE__ */ new Map(),
971
- // Visibility tracking (onFramed, onOccluded, onVisible)
972
- visibilityRegistry: /* @__PURE__ */ new Map(),
973
- // Occlusion system (WebGPU only)
974
- occlusionEnabled: false,
975
- occlusionObserver: null,
976
- occlusionCache: /* @__PURE__ */ new Map(),
977
- helperGroup: null,
978
- // Updates
979
- active: false,
980
- frames: 0,
981
- priority: 0,
982
- subscribe: (ref, priority, store) => {
983
- const internal = get().internal;
984
- internal.priority = internal.priority + (priority > 0 ? 1 : 0);
985
- internal.subscribers.push({ ref, priority, store });
986
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
987
- return () => {
988
- const internal2 = get().internal;
989
- if (internal2?.subscribers) {
990
- internal2.priority = internal2.priority - (priority > 0 ? 1 : 0);
991
- internal2.subscribers = internal2.subscribers.filter((s) => s.ref !== ref);
992
- }
993
- };
994
- },
995
- // Renderer Storage (single source of truth)
996
- actualRenderer: null,
997
- // Scheduler for useFrameNext (initialized in renderer.tsx)
998
- scheduler: null
999
- }
1000
- };
1001
- return rootState;
1002
- });
1003
- const state = rootStore.getState();
1004
- Object.defineProperty(state, "gl", {
1005
- get() {
1006
- const currentState = rootStore.getState();
1007
- if (!currentState.isLegacy && currentState.internal.actualRenderer) {
1008
- const stack = new Error().stack || "";
1009
- if (!isInternalRendererAccess(stack)) {
1010
- const cleanedStack = stack.split("\n").slice(2).join("\n") || "Stack trace unavailable";
1011
- notifyDepreciated({
1012
- heading: "Accessing state.gl in WebGPU mode",
1013
- 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
1014
- });
1015
- }
1016
- }
1017
- return currentState.internal.actualRenderer;
1018
- },
1019
- set(value) {
1020
- rootStore.getState().internal.actualRenderer = value;
1021
- },
1022
- enumerable: true,
1023
- configurable: true
1024
- });
1025
- Object.defineProperty(state, "renderer", {
1026
- get() {
1027
- return rootStore.getState().internal.actualRenderer;
1028
- },
1029
- set(value) {
1030
- rootStore.getState().internal.actualRenderer = value;
1031
- },
1032
- enumerable: true,
1033
- configurable: true
1034
- });
1035
- let oldScene = state.scene;
1036
- rootStore.subscribe(() => {
1037
- const currentState = rootStore.getState();
1038
- const { scene, rootScene, set } = currentState;
1039
- if (scene !== oldScene) {
1040
- oldScene = scene;
1041
- if (scene?.isScene && scene !== rootScene) {
1042
- set({ rootScene: scene });
1043
- }
1044
- }
1045
- });
1046
- let oldSize = state.size;
1047
- let oldDpr = state.viewport.dpr;
1048
- let oldCamera = state.camera;
1049
- rootStore.subscribe(() => {
1050
- const { camera, size, viewport, set, internal } = rootStore.getState();
1051
- const actualRenderer = internal.actualRenderer;
1052
- const canvasTarget = internal.canvasTarget;
1053
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1054
- oldSize = size;
1055
- oldDpr = viewport.dpr;
1056
- updateCamera(camera, size);
1057
- if (internal.isSecondary && canvasTarget) {
1058
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1059
- canvasTarget.setSize(size.width, size.height, false);
1060
- } else {
1061
- if (viewport.dpr > 0) actualRenderer.setPixelRatio(viewport.dpr);
1062
- actualRenderer.setSize(size.width, size.height, false);
1063
- if (canvasTarget) {
1064
- if (viewport.dpr > 0) canvasTarget.setPixelRatio(viewport.dpr);
1065
- canvasTarget.setSize(size.width, size.height, false);
1066
- }
1067
- }
1068
- }
1069
- if (camera !== oldCamera) {
1070
- oldCamera = camera;
1071
- const { rootScene } = rootStore.getState();
1072
- if (camera && rootScene && !camera.parent) {
1073
- rootScene.add(camera);
1074
- }
1075
- set((state2) => ({ viewport: { ...state2.viewport, ...state2.viewport.getCurrentViewport(camera) } }));
1076
- const currentState = rootStore.getState();
1077
- if (currentState.autoUpdateFrustum && camera) {
1078
- updateFrustum(camera, currentState.frustum);
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);
1079
795
  }
796
+ } else {
797
+ unregisterVisibility(root, object2);
1080
798
  }
1081
- });
1082
- rootStore.subscribe((state2) => invalidate(state2));
1083
- return rootStore;
1084
- };
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
+ }
1085
819
 
1086
820
  const memoizedLoaders = /* @__PURE__ */ new WeakMap();
1087
821
  const isConstructor$1 = (value) => typeof value === "function" && value?.prototype?.constructor === value;
@@ -1126,7 +860,11 @@ function useLoader(loader, input, extensions, onProgress, cacheKey) {
1126
860
  const results = cacheKeys.map(
1127
861
  (key, index) => suspend(() => fn(loader, inputs[index]), [loader, key], { equal: is.equ })
1128
862
  );
1129
- 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];
1130
868
  }
1131
869
  useLoader.preload = function(loader, input, extensions, onProgress, cacheKey) {
1132
870
  const inputs = Array.isArray(input) ? input : [input];
@@ -1246,6 +984,13 @@ function useFrame$1(callback, priorityOrOptions) {
1246
984
  * Use for controlling the entire frame loop, adding phases, etc.
1247
985
  */
1248
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
+ },
1249
994
  /**
1250
995
  * Manually step this job only.
1251
996
  * Bypasses FPS limiting - always runs.
@@ -1262,6 +1007,14 @@ function useFrame$1(callback, priorityOrOptions) {
1262
1007
  stepAll: (timestamp) => {
1263
1008
  getScheduler().step(timestamp);
1264
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
+ },
1265
1018
  /**
1266
1019
  * Pause this job (set enabled=false).
1267
1020
  * Job remains registered but won't run.
@@ -1291,6 +1044,11 @@ function getUrls(input) {
1291
1044
  if (Array.isArray(input)) return input;
1292
1045
  return Object.values(input);
1293
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
+ }
1294
1052
  function allUrlsCached(urls, textureCache) {
1295
1053
  return urls.every((url) => textureCache.has(url));
1296
1054
  }
@@ -1316,24 +1074,37 @@ function useTexture(input, optionsOrOnLoad) {
1316
1074
  const onLoadRef = useRef(onLoad);
1317
1075
  onLoadRef.current = onLoad;
1318
1076
  const onLoadCalledForRef = useRef(null);
1319
- 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]);
1320
1080
  const cachedResult = useMemo(() => {
1321
1081
  if (!cache) return null;
1322
1082
  const textures = store.getState().textures;
1323
1083
  if (!allUrlsCached(urls, textures)) return null;
1324
- return buildFromCache(input, textures);
1325
- }, [cache, urls, input, store]);
1084
+ return buildFromCache(stableInput, textures);
1085
+ }, [cache, urls, stableInput, store]);
1326
1086
  const loadedTextures = useLoader(
1327
1087
  TextureLoader,
1328
- IsObject(input) ? Object.values(input) : input
1088
+ IsObject(stableInput) ? Object.values(stableInput) : stableInput
1329
1089
  );
1330
- 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]);
1331
1102
  useLayoutEffect(() => {
1332
1103
  if (cachedResult) return;
1333
1104
  if (onLoadCalledForRef.current === inputKey) return;
1334
1105
  onLoadCalledForRef.current = inputKey;
1335
- onLoadRef.current?.(loadedTextures);
1336
- }, [cachedResult, loadedTextures, inputKey]);
1106
+ onLoadRef.current?.(mappedTextures);
1107
+ }, [cachedResult, mappedTextures, inputKey]);
1337
1108
  useEffect(() => {
1338
1109
  if (cachedResult) return;
1339
1110
  if ("initTexture" in renderer) {
@@ -1352,30 +1123,18 @@ function useTexture(input, optionsOrOnLoad) {
1352
1123
  });
1353
1124
  }
1354
1125
  }, [renderer, loadedTextures, cachedResult]);
1355
- const mappedTextures = useMemo(() => {
1356
- if (cachedResult) return cachedResult;
1357
- if (IsObject(input)) {
1358
- const keyed = {};
1359
- const textureArray = loadedTextures;
1360
- let i = 0;
1361
- for (const key in input) keyed[key] = textureArray[i++];
1362
- return keyed;
1363
- } else {
1364
- return loadedTextures;
1365
- }
1366
- }, [input, loadedTextures, cachedResult]);
1367
1126
  useEffect(() => {
1368
1127
  if (!cache) return;
1369
1128
  const urlTextureMap = [];
1370
- if (typeof input === "string") {
1371
- urlTextureMap.push([input, mappedTextures]);
1372
- } else if (Array.isArray(input)) {
1129
+ if (typeof stableInput === "string") {
1130
+ urlTextureMap.push([stableInput, mappedTextures]);
1131
+ } else if (Array.isArray(stableInput)) {
1373
1132
  const textureArray = mappedTextures;
1374
- input.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1375
- } else if (IsObject(input)) {
1133
+ stableInput.forEach((url, i) => urlTextureMap.push([url, textureArray[i]]));
1134
+ } else if (IsObject(stableInput)) {
1376
1135
  const textureRecord = mappedTextures;
1377
- for (const key in input) {
1378
- const url = input[key];
1136
+ for (const key in stableInput) {
1137
+ const url = stableInput[key];
1379
1138
  urlTextureMap.push([url, textureRecord[key]]);
1380
1139
  }
1381
1140
  }
@@ -1404,7 +1163,7 @@ function useTexture(input, optionsOrOnLoad) {
1404
1163
  }
1405
1164
  return { _textureRefs: refs };
1406
1165
  });
1407
- }, [cache, input, mappedTextures, store]);
1166
+ }, [cache, stableInput, mappedTextures, store]);
1408
1167
  return mappedTextures;
1409
1168
  }
1410
1169
  useTexture.preload = (url) => useLoader.preload(TextureLoader, url);
@@ -1565,7 +1324,7 @@ function useEnvironment({
1565
1324
  texture = texture.renderTarget?.texture;
1566
1325
  }
1567
1326
  texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
1568
- texture.colorSpace = colorSpace ?? (isCubemap ? "srgb" : "srgb-linear");
1327
+ texture.colorSpace = colorSpace ?? (isCubemap && extension !== "hdr-cube" ? "srgb" : "srgb-linear");
1569
1328
  return texture;
1570
1329
  }
1571
1330
  const preloadDefaultOptions = {
@@ -1618,22 +1377,15 @@ function getExtension(files) {
1618
1377
  const isCubemap = isArray(files) && files.length === 6;
1619
1378
  const isGainmap = isArray(files) && files.length === 3 && files.some((file) => file.endsWith("json"));
1620
1379
  const firstEntry = isArray(files) ? files[0] : files;
1621
- 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;
1622
1382
  return { extension, isCubemap, isGainmap };
1623
1383
  }
1624
1384
  function getLoader(extension) {
1625
- 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;
1626
1386
  return loader;
1627
1387
  }
1628
1388
 
1629
- function useStore() {
1630
- const store = useContext(context);
1631
- if (!store) throw new Error("R3F: Hooks can only be used within the Canvas component!");
1632
- return store;
1633
- }
1634
- function useThree$1(selector = (state) => state, equalityFn) {
1635
- return useStore()(selector, equalityFn);
1636
- }
1637
1389
  function useInstanceHandle(ref) {
1638
1390
  const instance = useRef(null);
1639
1391
  useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
@@ -1673,22 +1425,28 @@ function addTail(callback) {
1673
1425
  return getScheduler().onIdle(callback);
1674
1426
  }
1675
1427
  function invalidate(state, frames = 1, stackFrames = false) {
1676
- 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);
1677
1432
  }
1678
- function advance(timestamp) {
1679
- 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);
1680
1438
  }
1681
1439
 
1682
- const version = "10.0.0-alpha.3";
1440
+ const version = "10.0.0-alpha.5";
1683
1441
  const packageData = {
1684
1442
  version: version};
1685
1443
 
1686
1444
  function Xb(Tt) {
1687
1445
  return Tt && Tt.__esModule && Object.prototype.hasOwnProperty.call(Tt, "default") ? Tt.default : Tt;
1688
1446
  }
1689
- var Rm = { exports: {} }, Og = { exports: {} }, _b;
1447
+ var Rm = { exports: {} }, Og = { exports: {} }, _b$1;
1690
1448
  function Kb() {
1691
- return _b || (_b = 1, (function(Tt) {
1449
+ return _b$1 || (_b$1 = 1, (function(Tt) {
1692
1450
  Tt.exports = function(m) {
1693
1451
  function Yn(t, r, a, l) {
1694
1452
  return new uc(t, r, a, l);
@@ -13538,7 +13296,10 @@ function extend(objects) {
13538
13296
  catalogue[Component] = objects;
13539
13297
  return Component;
13540
13298
  } else {
13541
- Object.assign(catalogue, objects);
13299
+ for (const name in objects) {
13300
+ const object = objects[name];
13301
+ if (isConstructor(object)) catalogue[name] = object;
13302
+ }
13542
13303
  }
13543
13304
  }
13544
13305
  function validateInstance(type, props) {
@@ -13886,7 +13647,7 @@ const reconciler = /* @__PURE__ */ createReconciler({
13886
13647
  getRootHostContext: () => NO_CONTEXT,
13887
13648
  getChildHostContext: () => NO_CONTEXT,
13888
13649
  commitUpdate(instance, type, oldProps, newProps, fiber) {
13889
- validateInstance(type, newProps);
13650
+ validateInstance(instance.type, newProps);
13890
13651
  let reconstruct = false;
13891
13652
  if (instance.type === "primitive" && oldProps.object !== newProps.object) reconstruct = true;
13892
13653
  else if (newProps.args?.length !== oldProps.args?.length) reconstruct = true;
@@ -13913,8 +13674,8 @@ const reconciler = /* @__PURE__ */ createReconciler({
13913
13674
  for (const prop in instance.props) {
13914
13675
  const value = instance.props[prop];
13915
13676
  if (isFromRef(value)) {
13916
- const ref = value[FROM_REF];
13917
- 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;
13918
13679
  }
13919
13680
  }
13920
13681
  if (Object.keys(resolved).length) applyProps(instance.object, resolved);
@@ -14058,6 +13819,335 @@ const reconciler = /* @__PURE__ */ createReconciler({
14058
13819
  getSuspendedCommitReason: (_state, _rootContainer) => null
14059
13820
  });
14060
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
+
14061
14151
  const isRenderer = (def) => !!def?.render;
14062
14152
  const _roots = /* @__PURE__ */ new Map();
14063
14153
  const shallowLoose = { objects: "shallow", strict: false };
@@ -14195,9 +14285,8 @@ function createRoot(canvas) {
14195
14285
  if (!renderer.hasInitialized?.()) {
14196
14286
  const size2 = computeInitialSize(canvas, propsSize);
14197
14287
  if (size2.width > 0 && size2.height > 0) {
14198
- const pixelRatio = calculateDpr(dpr);
14199
- canvas.width = size2.width * pixelRatio;
14200
- canvas.height = size2.height * pixelRatio;
14288
+ renderer.setPixelRatio(calculateDpr(dpr));
14289
+ renderer.setSize(size2.width, size2.height, false);
14201
14290
  }
14202
14291
  await renderer.init();
14203
14292
  }
@@ -14206,7 +14295,7 @@ function createRoot(canvas) {
14206
14295
  state.internal.actualRenderer = renderer;
14207
14296
  state.set({ webGPUSupported: isWebGPUBackend, renderer, primaryStore: store });
14208
14297
  if (canvasId && !state.internal.isSecondary) {
14209
- const canvasTarget = new CanvasTarget(canvas);
14298
+ const canvasTarget = renderer.getCanvasTarget?.();
14210
14299
  const unregisterPrimary = registerPrimary(canvasId, renderer, store);
14211
14300
  state.set((prev) => ({
14212
14301
  internal: {
@@ -14320,7 +14409,7 @@ function createRoot(canvas) {
14320
14409
  const handleXRFrame = (timestamp, _frame) => {
14321
14410
  const state2 = store.getState();
14322
14411
  if (state2.frameloop === "never") return;
14323
- advance(timestamp);
14412
+ advance(timestamp, true, state2);
14324
14413
  };
14325
14414
  const actualRenderer = state.internal.actualRenderer;
14326
14415
  const handleSessionChange = () => {
@@ -14328,7 +14417,7 @@ function createRoot(canvas) {
14328
14417
  const renderer2 = state2.internal.actualRenderer;
14329
14418
  actualRenderer.xr.enabled = actualRenderer.xr.isPresenting;
14330
14419
  renderer2.xr.setAnimationLoop(renderer2.xr.isPresenting ? handleXRFrame : null);
14331
- if (!renderer2.xr.isPresenting) invalidate();
14420
+ if (!renderer2.xr.isPresenting) invalidate(state2);
14332
14421
  };
14333
14422
  const xr = {
14334
14423
  connect() {
@@ -14410,14 +14499,23 @@ function createRoot(canvas) {
14410
14499
  const newRootId = canvasId || scheduler.generateRootId();
14411
14500
  const unregisterRoot = scheduler.registerRoot(newRootId, {
14412
14501
  getState: () => store.getState(),
14413
- 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
14414
14507
  });
14415
14508
  const unregisterCanvasTarget = scheduler.register(
14416
14509
  () => {
14417
14510
  const state2 = store.getState();
14418
- if (state2.internal.isMultiCanvas && state2.internal.canvasTarget) {
14511
+ const canvasTarget = state2.internal.canvasTarget;
14512
+ if (canvasTarget) {
14419
14513
  const renderer2 = state2.internal.actualRenderer;
14420
- 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
+ }
14421
14519
  }
14422
14520
  },
14423
14521
  {
@@ -14473,7 +14571,8 @@ function createRoot(canvas) {
14473
14571
  const userHandlesRender = scheduler.hasUserJobsInPhase("render", newRootId);
14474
14572
  if (userHandlesRender || state2.internal.priority) return;
14475
14573
  try {
14476
- if (state2.renderPipeline?.render) state2.renderPipeline.render();
14574
+ const renderOverride = state2.internal.renderOverride;
14575
+ if (renderOverride) renderOverride();
14477
14576
  else if (renderer2?.render) renderer2.render(state2.scene, state2.camera);
14478
14577
  } catch (error) {
14479
14578
  state2.setError(error instanceof Error ? error : new Error(String(error)));
@@ -14486,8 +14585,7 @@ function createRoot(canvas) {
14486
14585
  phase: "render",
14487
14586
  system: true,
14488
14587
  // Internal flag: this is a system job, not user-controlled
14489
- // Apply scheduler config for render ordering and rate limiting
14490
- ...schedulerConfig?.after && { after: schedulerConfig.after },
14588
+ // FPS throttles only the default render job; Canvas ordering belongs to the root.
14491
14589
  ...schedulerConfig?.fps && { fps: schedulerConfig.fps }
14492
14590
  }
14493
14591
  );
@@ -14506,8 +14604,22 @@ function createRoot(canvas) {
14506
14604
  scheduler
14507
14605
  }
14508
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
+ }
14509
14618
  }
14510
- scheduler.frameloop = frameloop;
14619
+ lastConfiguredProps.schedulerBefore = schedulerConfig?.before;
14620
+ lastConfiguredProps.schedulerAfter = schedulerConfig?.after;
14621
+ lastConfiguredProps.schedulerOrder = schedulerConfig?.order;
14622
+ attachRootExtensions(store);
14511
14623
  onCreated = onCreatedCallback;
14512
14624
  configured = true;
14513
14625
  resolve();
@@ -14549,18 +14661,24 @@ function unmountComponentAtNode(canvas, callback) {
14549
14661
  const fiber = root?.fiber;
14550
14662
  if (fiber) {
14551
14663
  const state = root?.store.getState();
14552
- 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
+ }
14553
14672
  reconciler.updateContainer(null, fiber, null, () => {
14673
+ if (root?.store) detachRootExtensions(root.store);
14554
14674
  if (state) {
14555
14675
  setTimeout(() => {
14556
14676
  try {
14557
14677
  const renderer = state.internal.actualRenderer;
14558
- const unregisterRoot = state.internal.unregisterRoot;
14559
- if (unregisterRoot) unregisterRoot();
14560
14678
  const unregisterPrimary = state.internal.unregisterPrimary;
14561
14679
  if (unregisterPrimary) unregisterPrimary();
14562
14680
  const canvasTarget = state.internal.canvasTarget;
14563
- if (canvasTarget?.dispose) canvasTarget.dispose();
14681
+ if (state.internal.isSecondary && canvasTarget?.dispose) canvasTarget.dispose();
14564
14682
  state.events.disconnect?.();
14565
14683
  cleanupHelperGroup(root.store);
14566
14684
  if (state.isLegacy && renderer) {
@@ -14630,11 +14748,24 @@ function PortalInner({ state = {}, children, container }) {
14630
14748
  }
14631
14749
  };
14632
14750
  }, [portalScene, container, injectScene]);
14751
+ const lastRootState = useRef(null);
14633
14752
  const inject = useMutableCallback((rootState, injectState) => {
14753
+ const followed = {};
14754
+ const lastRoot = lastRootState.current;
14755
+ if (lastRoot) {
14756
+ for (const key in rootState) {
14757
+ const field = key;
14758
+ const changed = rootState[field] !== lastRoot[field];
14759
+ const inherited = !(key in rest) && injectState[field] === lastRoot[field];
14760
+ if (changed && inherited) followed[field] = rootState[field];
14761
+ }
14762
+ }
14763
+ lastRootState.current = rootState;
14634
14764
  const resolvedSize = { ...rootState.size, ...injectState.size, ...size };
14635
14765
  let viewport = void 0;
14636
- if (injectState.camera && (size || injectState.size)) {
14637
- const camera = injectState.camera;
14766
+ const portalCamera = followed.camera ?? injectState.camera;
14767
+ if (portalCamera && (size || injectState.size)) {
14768
+ const camera = portalCamera;
14638
14769
  viewport = rootState.viewport.getCurrentViewport(camera, new Vector3(), resolvedSize);
14639
14770
  if (camera !== rootState.camera) updateCamera(camera, resolvedSize);
14640
14771
  }
@@ -14643,6 +14774,7 @@ function PortalInner({ state = {}, children, container }) {
14643
14774
  // The intersect consists of the previous root state
14644
14775
  ...rootState,
14645
14776
  ...injectState,
14777
+ ...followed,
14646
14778
  // Portals have their own scene - always a real THREE.Scene (injected if needed)
14647
14779
  scene: portalScene,
14648
14780
  // rootScene always points to the actual THREE.Scene, even inside portals
@@ -14701,16 +14833,11 @@ function setEnvProps(background, scene, defaultScene, texture, sceneProps = {})
14701
14833
  const oldbg = target.background;
14702
14834
  const oldenv = target.environment;
14703
14835
  const oldSceneProps = {
14704
- // @ts-ignore
14705
14836
  backgroundBlurriness: target.backgroundBlurriness,
14706
- // @ts-ignore
14707
14837
  backgroundIntensity: target.backgroundIntensity,
14708
- // @ts-ignore
14709
- backgroundRotation: target.backgroundRotation?.clone?.() ?? [0, 0, 0],
14710
- // @ts-ignore
14838
+ backgroundRotation: target.backgroundRotation.clone(),
14711
14839
  environmentIntensity: target.environmentIntensity,
14712
- // @ts-ignore
14713
- environmentRotation: target.environmentRotation?.clone?.() ?? [0, 0, 0]
14840
+ environmentRotation: target.environmentRotation.clone()
14714
14841
  };
14715
14842
  if (background !== "only") target.environment = texture;
14716
14843
  if (background) target.background = texture;
@@ -14915,153 +15042,24 @@ function parseBackground(background) {
14915
15042
  return { files: background, background: true };
14916
15043
  }
14917
15044
  return { color: background, background: true };
14918
- }
14919
- if (background.isColor) {
14920
- return { color: background, background: true };
14921
- }
14922
- return null;
14923
- }
14924
-
14925
- function parseRendererConfig(rendererProp) {
14926
- const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
14927
- if (isRendererConfig) {
14928
- const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
14929
- return {
14930
- primaryCanvas: pc,
14931
- scheduler: sc,
14932
- renderer: Object.keys(rest).length > 0 ? rest : rendererProp
14933
- };
14934
- }
14935
- return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
14936
- }
14937
-
14938
- const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
14939
- const ROOT_SCOPE = "";
14940
- const registries = /* @__PURE__ */ new WeakMap();
14941
- function getKindRegistry(store, kind) {
14942
- let registry = registries.get(store);
14943
- if (!registry) {
14944
- registry = {
14945
- nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14946
- uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14947
- buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
14948
- gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
14949
- };
14950
- registries.set(store, registry);
14951
- }
14952
- return registry[kind];
14953
- }
14954
- function resolvePrimary$1(store) {
14955
- return store.getState().primaryStore ?? store;
14956
- }
14957
- function isScopeValid(store, kind, scopeKey) {
14958
- return getKindRegistry(store, kind).valid.has(scopeKey);
14959
- }
14960
- function peekStaged(store, kind, scopeKey) {
14961
- return getKindRegistry(store, kind).staged.get(scopeKey);
14962
- }
14963
- function stageEntries(store, kind, scopeKey, entries) {
14964
- const { staged } = getKindRegistry(store, kind);
14965
- const current = staged.get(scopeKey);
14966
- if (current) Object.assign(current, entries);
14967
- else staged.set(scopeKey, { ...entries });
14968
- }
14969
- function readCommittedScope(map, scopeKey, isLeaf) {
14970
- if (scopeKey === ROOT_SCOPE) {
14971
- const leaves = {};
14972
- for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
14973
- return leaves;
14974
- }
14975
- const scopeData = map[scopeKey];
14976
- if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
14977
- return {};
14978
- }
14979
- function withStagedOverlay(store, kind, committed) {
14980
- const { staged } = getKindRegistry(store, kind);
14981
- if (staged.size === 0) return committed;
14982
- const view = { ...committed };
14983
- for (const [scopeKey, entries] of staged) {
14984
- if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
14985
- else view[scopeKey] = { ...view[scopeKey], ...entries };
14986
- }
14987
- return view;
14988
- }
14989
- function flushStagedScope(store, kind, scopeKey, isLeaf) {
14990
- const registry = getKindRegistry(store, kind);
14991
- const staged = registry.staged.get(scopeKey);
14992
- if (!staged) return;
14993
- registry.staged.delete(scopeKey);
14994
- const replace = !registry.valid.has(scopeKey);
14995
- store.setState((state) => {
14996
- const map = state[kind];
14997
- if (scopeKey === ROOT_SCOPE) {
14998
- if (!replace) return { [kind]: { ...map, ...staged } };
14999
- const next = {};
15000
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15001
- return { [kind]: { ...next, ...staged } };
15002
- }
15003
- const current = replace ? {} : map[scopeKey] ?? {};
15004
- return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15005
- });
15006
- registry.valid.add(scopeKey);
15007
- }
15008
- function invalidateResource(store, kind, scopeKey) {
15009
- const registry = getKindRegistry(store, kind);
15010
- if (scopeKey === void 0) {
15011
- registry.valid.clear();
15012
- registry.staged.clear();
15013
- } else {
15014
- registry.valid.delete(scopeKey);
15015
- registry.staged.delete(scopeKey);
15016
- }
15017
- }
15018
- function rebuildResource(store, kind, userScope) {
15019
- const primary = resolvePrimary$1(store);
15020
- invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15021
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15022
- }
15023
- function invalidateAllResources(store) {
15024
- for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15025
- }
15026
- function removeResourceEntries(store, kind, names, scope, isLeaf) {
15027
- const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15028
- if (staged) for (const name of names) delete staged[name];
15029
- store.setState((state) => {
15030
- const map = state[kind];
15031
- if (scope) {
15032
- const currentScope = { ...map[scope] };
15033
- for (const name of names) delete currentScope[name];
15034
- return { [kind]: { ...map, [scope]: currentScope } };
15035
- }
15036
- const next = { ...map };
15037
- for (const name of names) if (isLeaf(next[name])) delete next[name];
15038
- return { [kind]: next };
15039
- });
15040
- }
15041
- function clearResourceEntries(store, kind, scope, isLeaf) {
15042
- const registry = getKindRegistry(store, kind);
15043
- if (scope && scope !== "root") registry.staged.delete(scope);
15044
- else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15045
- else registry.staged.clear();
15046
- store.setState((state) => {
15047
- const map = state[kind];
15048
- if (scope && scope !== "root") {
15049
- const { [scope]: _, ...rest } = map;
15050
- return { [kind]: rest };
15051
- }
15052
- if (scope === "root") {
15053
- const next = {};
15054
- for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15055
- return { [kind]: next };
15056
- }
15057
- return { [kind]: {} };
15058
- });
15045
+ }
15046
+ if (background.isColor) {
15047
+ return { color: background, background: true };
15048
+ }
15049
+ return null;
15059
15050
  }
15060
15051
 
15061
- function clearHmrCaches(store) {
15062
- const primary = store.getState().primaryStore ?? store;
15063
- invalidateAllResources(primary);
15064
- primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15052
+ function parseRendererConfig(rendererProp) {
15053
+ const isRendererConfig = typeof rendererProp === "object" && rendererProp !== null && !("render" in rendererProp) && ("primaryCanvas" in rendererProp || "scheduler" in rendererProp);
15054
+ if (isRendererConfig) {
15055
+ const { primaryCanvas: pc, scheduler: sc, ...rest } = rendererProp;
15056
+ return {
15057
+ primaryCanvas: pc,
15058
+ scheduler: sc,
15059
+ renderer: Object.keys(rest).length > 0 ? rest : rendererProp
15060
+ };
15061
+ }
15062
+ return { primaryCanvas: void 0, scheduler: void 0, renderer: rendererProp };
15065
15063
  }
15066
15064
 
15067
15065
  function CanvasImpl({
@@ -15101,9 +15099,9 @@ function CanvasImpl({
15101
15099
  React.useMemo(() => extend(THREE), []);
15102
15100
  const Bridge = useBridge();
15103
15101
  const backgroundProps = React.useMemo(() => parseBackground(background), [background]);
15104
- const hasInitialSizeRef = React.useRef(false);
15102
+ const [hasInitialSize, setHasInitialSize] = React.useState(false);
15105
15103
  const measureConfig = React.useMemo(() => {
15106
- if (!hasInitialSizeRef.current) {
15104
+ if (!hasInitialSize) {
15107
15105
  return {
15108
15106
  ...resize,
15109
15107
  scroll: resize?.scroll ?? true,
@@ -15115,7 +15113,7 @@ function CanvasImpl({
15115
15113
  debounce: { scroll: 50, resize: 0 },
15116
15114
  ...resize
15117
15115
  };
15118
- }, [resize, hasInitialSizeRef.current]);
15116
+ }, [hasInitialSize, resize]);
15119
15117
  const [containerRef, containerRect] = useMeasure(measureConfig);
15120
15118
  const effectiveSize = React.useMemo(() => {
15121
15119
  let w = width ?? containerRect.width;
@@ -15131,9 +15129,7 @@ function CanvasImpl({
15131
15129
  left: containerRect.left
15132
15130
  };
15133
15131
  }, [width, height, containerRect, forceEven]);
15134
- if (!hasInitialSizeRef.current && effectiveSize.width > 0 && effectiveSize.height > 0) {
15135
- hasInitialSizeRef.current = true;
15136
- }
15132
+ if (!hasInitialSize && effectiveSize.width > 0 && effectiveSize.height > 0) setHasInitialSize(true);
15137
15133
  const canvasRef = React.useRef(null);
15138
15134
  const divRef = React.useRef(null);
15139
15135
  React.useImperativeHandle(ref, () => canvasRef.current);
@@ -15247,6 +15243,7 @@ function CanvasImpl({
15247
15243
  const canvas = canvasRef.current;
15248
15244
  if (canvas) {
15249
15245
  return () => {
15246
+ if (canvas.isConnected) return;
15250
15247
  unmountComponentAtNode(canvas);
15251
15248
  root.current = null;
15252
15249
  };
@@ -15259,7 +15256,7 @@ function CanvasImpl({
15259
15256
  const handleHMR = () => {
15260
15257
  queueMicrotask(() => {
15261
15258
  const rootEntry = _roots.get(canvas);
15262
- if (rootEntry?.store) clearHmrCaches(rootEntry.store);
15259
+ if (rootEntry?.store) notifyRootExtensionsHmr(rootEntry.store);
15263
15260
  });
15264
15261
  };
15265
15262
  if (typeof import.meta !== "undefined" && import.meta.hot) {
@@ -15305,20 +15302,159 @@ function CanvasImpl({
15305
15302
  }
15306
15303
  );
15307
15304
  }
15308
- function Canvas(props) {
15305
+ function Canvas$1(props) {
15309
15306
  return /* @__PURE__ */ jsx(FiberProvider, { children: /* @__PURE__ */ jsx(CanvasImpl, { ...props }) });
15310
15307
  }
15311
15308
 
15309
+ const RESOURCE_KINDS = ["nodes", "uniforms", "buffers", "gpuStorage"];
15310
+ const ROOT_SCOPE = "";
15311
+ const registries = /* @__PURE__ */ new WeakMap();
15312
+ function getKindRegistry(store, kind) {
15313
+ let registry = registries.get(store);
15314
+ if (!registry) {
15315
+ registry = {
15316
+ nodes: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15317
+ uniforms: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15318
+ buffers: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() },
15319
+ gpuStorage: { valid: /* @__PURE__ */ new Set(), staged: /* @__PURE__ */ new Map() }
15320
+ };
15321
+ registries.set(store, registry);
15322
+ }
15323
+ return registry[kind];
15324
+ }
15325
+ function resolvePrimary$1(store) {
15326
+ return store.getState().primaryStore ?? store;
15327
+ }
15328
+ function isScopeValid(store, kind, scopeKey) {
15329
+ return getKindRegistry(store, kind).valid.has(scopeKey);
15330
+ }
15331
+ function peekStaged(store, kind, scopeKey) {
15332
+ return getKindRegistry(store, kind).staged.get(scopeKey);
15333
+ }
15334
+ function stageEntries(store, kind, scopeKey, entries) {
15335
+ const { staged } = getKindRegistry(store, kind);
15336
+ const current = staged.get(scopeKey);
15337
+ if (current) Object.assign(current, entries);
15338
+ else staged.set(scopeKey, { ...entries });
15339
+ }
15340
+ function readCommittedScope(map, scopeKey, isLeaf) {
15341
+ if (scopeKey === ROOT_SCOPE) {
15342
+ const leaves = {};
15343
+ for (const [key, value] of Object.entries(map)) if (isLeaf(value)) leaves[key] = value;
15344
+ return leaves;
15345
+ }
15346
+ const scopeData = map[scopeKey];
15347
+ if (scopeData && typeof scopeData === "object" && !isLeaf(scopeData)) return scopeData;
15348
+ return {};
15349
+ }
15350
+ function withStagedOverlay(store, kind, committed) {
15351
+ const { staged } = getKindRegistry(store, kind);
15352
+ if (staged.size === 0) return committed;
15353
+ const view = { ...committed };
15354
+ for (const [scopeKey, entries] of staged) {
15355
+ if (scopeKey === ROOT_SCOPE) Object.assign(view, entries);
15356
+ else view[scopeKey] = { ...view[scopeKey], ...entries };
15357
+ }
15358
+ return view;
15359
+ }
15360
+ function flushStagedScope(store, kind, scopeKey, isLeaf) {
15361
+ const registry = getKindRegistry(store, kind);
15362
+ const staged = registry.staged.get(scopeKey);
15363
+ if (!staged) return;
15364
+ registry.staged.delete(scopeKey);
15365
+ const replace = !registry.valid.has(scopeKey);
15366
+ store.setState((state) => {
15367
+ const map = state[kind];
15368
+ if (scopeKey === ROOT_SCOPE) {
15369
+ if (!replace) return { [kind]: { ...map, ...staged } };
15370
+ const next = {};
15371
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15372
+ return { [kind]: { ...next, ...staged } };
15373
+ }
15374
+ const current = replace ? {} : map[scopeKey] ?? {};
15375
+ return { [kind]: { ...map, [scopeKey]: { ...current, ...staged } } };
15376
+ });
15377
+ registry.valid.add(scopeKey);
15378
+ }
15379
+ function invalidateResource(store, kind, scopeKey) {
15380
+ const registry = getKindRegistry(store, kind);
15381
+ if (scopeKey === void 0) {
15382
+ registry.valid.clear();
15383
+ registry.staged.clear();
15384
+ } else {
15385
+ registry.valid.delete(scopeKey);
15386
+ registry.staged.delete(scopeKey);
15387
+ }
15388
+ }
15389
+ function rebuildResource(store, kind, userScope) {
15390
+ const primary = resolvePrimary$1(store);
15391
+ invalidateResource(primary, kind, userScope === "root" ? ROOT_SCOPE : userScope);
15392
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15393
+ }
15394
+ function invalidateAllResources(store) {
15395
+ for (const kind of RESOURCE_KINDS) invalidateResource(store, kind);
15396
+ }
15397
+ function removeResourceEntries(store, kind, names, scope, isLeaf) {
15398
+ const staged = peekStaged(store, kind, scope ?? ROOT_SCOPE);
15399
+ if (staged) for (const name of names) delete staged[name];
15400
+ store.setState((state) => {
15401
+ const map = state[kind];
15402
+ if (scope) {
15403
+ const currentScope = { ...map[scope] };
15404
+ for (const name of names) delete currentScope[name];
15405
+ return { [kind]: { ...map, [scope]: currentScope } };
15406
+ }
15407
+ const next = { ...map };
15408
+ for (const name of names) if (isLeaf(next[name])) delete next[name];
15409
+ return { [kind]: next };
15410
+ });
15411
+ }
15412
+ function clearResourceEntries(store, kind, scope, isLeaf) {
15413
+ const registry = getKindRegistry(store, kind);
15414
+ if (scope && scope !== "root") registry.staged.delete(scope);
15415
+ else if (scope === "root") registry.staged.delete(ROOT_SCOPE);
15416
+ else registry.staged.clear();
15417
+ store.setState((state) => {
15418
+ const map = state[kind];
15419
+ if (scope && scope !== "root") {
15420
+ const { [scope]: _, ...rest } = map;
15421
+ return { [kind]: rest };
15422
+ }
15423
+ if (scope === "root") {
15424
+ const next = {};
15425
+ for (const [key, value] of Object.entries(map)) if (!isLeaf(value)) next[key] = value;
15426
+ return { [kind]: next };
15427
+ }
15428
+ return { [kind]: {} };
15429
+ });
15430
+ }
15431
+
15432
+ const isObjectLike = (value) => value !== null && (typeof value === "object" || typeof value === "function");
15433
+ const hasValidLegacyNodeMarkers = (value) => {
15434
+ const hasUuid = "uuid" in value;
15435
+ const hasNodeType = "nodeType" in value;
15436
+ const uuid = Reflect.get(value, "uuid");
15437
+ const nodeType = Reflect.get(value, "nodeType");
15438
+ return (hasUuid || hasNodeType) && (!hasUuid || uuid === void 0 || typeof uuid === "string") && (!hasNodeType || nodeType === void 0 || nodeType === null || typeof nodeType === "string");
15439
+ };
15440
+ const isUniformNode = (value) => isObjectLike(value) && "isUniformNode" in value && value.isUniformNode === true;
15441
+ const isTSLNode = (value) => isObjectLike(value) && (Reflect.get(value, "isNode") === true || Reflect.get(value, "shaderNode") !== void 0 && typeof Reflect.get(value, "id") === "number" || hasValidLegacyNodeMarkers(value));
15442
+ const isBufferLike = (value) => ArrayBuffer.isView(value) && !(value instanceof DataView) || isObjectLike(value) && ("isBufferAttribute" in value || "isInterleavedBufferAttribute" in value || isTSLNode(value));
15443
+ const isStorageLike = (value) => isObjectLike(value) && ("isTexture" in value || "isData3DTexture" in value || isTSLNode(value));
15444
+
15312
15445
  var __defProp = Object.defineProperty;
15313
15446
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15314
15447
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
15315
- var _a;
15448
+ var _a, _b;
15316
15449
  const INTERNAL_DATA = Symbol("ScopedStore.data");
15317
- _a = INTERNAL_DATA;
15450
+ const INTERNAL_IS_LEAF = Symbol("ScopedStore.isLeaf");
15451
+ _b = INTERNAL_DATA, _a = INTERNAL_IS_LEAF;
15318
15452
  const _ScopedStore = class _ScopedStore {
15319
- constructor(data) {
15453
+ constructor(data, isLeaf) {
15454
+ __publicField(this, _b);
15320
15455
  __publicField(this, _a);
15321
15456
  this[INTERNAL_DATA] = data;
15457
+ this[INTERNAL_IS_LEAF] = isLeaf;
15322
15458
  return new Proxy(this, {
15323
15459
  get(target, prop, receiver) {
15324
15460
  if (typeof prop === "string") {
@@ -15352,10 +15488,10 @@ const _ScopedStore = class _ScopedStore {
15352
15488
  * If the key doesn't exist or isn't a scope object, returns an empty ScopedStore.
15353
15489
  */
15354
15490
  scope(key) {
15355
- const data = this[INTERNAL_DATA][key];
15356
- return new _ScopedStore(
15357
- data && typeof data === "object" ? data : {}
15358
- );
15491
+ const value = this[INTERNAL_DATA][key];
15492
+ const isLeaf = this[INTERNAL_IS_LEAF];
15493
+ const scope = value && typeof value === "object" && !isLeaf(value) ? value : {};
15494
+ return new _ScopedStore(scope, isLeaf);
15359
15495
  }
15360
15496
  /**
15361
15497
  * Check if a key exists in the store.
@@ -15371,8 +15507,8 @@ const _ScopedStore = class _ScopedStore {
15371
15507
  }
15372
15508
  };
15373
15509
  let ScopedStore = _ScopedStore;
15374
- function createScopedStore(data) {
15375
- return new ScopedStore(data);
15510
+ function createScopedStore(data, isLeaf) {
15511
+ return new ScopedStore(data, isLeaf);
15376
15512
  }
15377
15513
  function createLazyCreatorState(state, store) {
15378
15514
  let _uniforms = null;
@@ -15383,22 +15519,22 @@ function createLazyCreatorState(state, store) {
15383
15519
  return Object.create(state, {
15384
15520
  uniforms: {
15385
15521
  get() {
15386
- return _uniforms ?? (_uniforms = createScopedStore(view("uniforms")));
15522
+ return _uniforms ?? (_uniforms = createScopedStore(view("uniforms"), isUniformNode));
15387
15523
  }
15388
15524
  },
15389
15525
  nodes: {
15390
15526
  get() {
15391
- return _nodes ?? (_nodes = createScopedStore(view("nodes")));
15527
+ return _nodes ?? (_nodes = createScopedStore(view("nodes"), isTSLNode));
15392
15528
  }
15393
15529
  },
15394
15530
  buffers: {
15395
15531
  get() {
15396
- return _buffers ?? (_buffers = createScopedStore(view("buffers")));
15532
+ return _buffers ?? (_buffers = createScopedStore(view("buffers"), isBufferLike));
15397
15533
  }
15398
15534
  },
15399
15535
  gpuStorage: {
15400
15536
  get() {
15401
- return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage")));
15537
+ return _gpuStorage ?? (_gpuStorage = createScopedStore(view("gpuStorage"), isStorageLike));
15402
15538
  }
15403
15539
  }
15404
15540
  });
@@ -15418,44 +15554,10 @@ function usePrimaryThree(selector = (state) => state, equalityFn) {
15418
15554
  return primary(selector, equalityFn);
15419
15555
  }
15420
15556
 
15421
- function addTexture(set, key, value) {
15422
- set((state) => {
15423
- const newMap = new Map(state.textures);
15424
- newMap.set(key, value);
15425
- return { textures: newMap };
15426
- });
15427
- }
15428
- function addTextures(set, items) {
15429
- set((state) => {
15430
- const newMap = new Map(state.textures);
15431
- const entries = items instanceof Map ? items.entries() : Object.entries(items);
15432
- for (const [key, value] of entries) {
15433
- newMap.set(key, value);
15434
- }
15435
- return { textures: newMap };
15436
- });
15437
- }
15438
- function removeTexture(set, key) {
15439
- set((state) => {
15440
- const newMap = new Map(state.textures);
15441
- newMap.delete(key);
15442
- return { textures: newMap };
15443
- });
15444
- }
15445
- function removeTextures(set, keys) {
15446
- set((state) => {
15447
- const newMap = new Map(state.textures);
15448
- for (const key of keys) newMap.delete(key);
15449
- return { textures: newMap };
15450
- });
15451
- }
15452
- function createTextureOperations(set) {
15453
- return {
15454
- add: (key, value) => addTexture(set, key, value),
15455
- addMultiple: (items) => addTextures(set, items),
15456
- remove: (key) => removeTexture(set, key),
15457
- removeMultiple: (keys) => removeTextures(set, keys)
15458
- };
15557
+ function scopedNodeName(scope, name) {
15558
+ const raw = scope ? `${scope}_${name}` : name;
15559
+ const safe = raw.replace(/[^A-Za-z0-9_]/g, "_");
15560
+ return /^[A-Za-z_]/.test(safe) ? safe : `_${safe}`;
15459
15561
  }
15460
15562
  function extractTSLValue(value) {
15461
15563
  if (value === null || value === void 0) return value;
@@ -15494,7 +15596,7 @@ function vectorize(inObject) {
15494
15596
  return extractTSLValue(inObject);
15495
15597
  }
15496
15598
  if (obj.isVector2 || obj.isVector3 || obj.isVector4) return inObject;
15497
- if (obj.isMatrix3 || obj.isMatrix4) return inObject;
15599
+ if (obj.isMatrix2 || obj.isMatrix3 || obj.isMatrix4) return inObject;
15498
15600
  if (obj.isColor || obj.isEuler || obj.isQuaternion || obj.isSpherical) return inObject;
15499
15601
  if ("r" in obj && "g" in obj && "b" in obj && typeof obj.r === "number" && typeof obj.g === "number" && typeof obj.b === "number") {
15500
15602
  const scale = obj.r > 1 || obj.g > 1 || obj.b > 1 ? 1 / 255 : 1;
@@ -15521,6 +15623,47 @@ function useCompareMemoize(value, deep) {
15521
15623
  return ref.current;
15522
15624
  }
15523
15625
 
15626
+ function clearHmrCaches(store) {
15627
+ const primary = store.getState().primaryStore ?? store;
15628
+ invalidateAllResources(primary);
15629
+ primary.setState((state) => ({ _hmrVersion: state._hmrVersion + 1 }));
15630
+ }
15631
+
15632
+ const SHARED_KEYS = ["uniforms", "nodes", "buffers", "gpuStorage", "_hmrVersion"];
15633
+ const following = /* @__PURE__ */ new WeakMap();
15634
+ function followPrimary(store) {
15635
+ const primary = store.getState().primaryStore;
15636
+ if (!primary || primary === store) return;
15637
+ const sync = () => {
15638
+ const source = primary.getState();
15639
+ const target = store.getState();
15640
+ let patch = null;
15641
+ for (const key of SHARED_KEYS) {
15642
+ if (target[key] !== source[key]) (patch ?? (patch = {}))[key] = source[key];
15643
+ }
15644
+ if (patch) store.setState(patch);
15645
+ };
15646
+ sync();
15647
+ following.set(store, primary.subscribe(sync));
15648
+ }
15649
+ function stopFollowing(store) {
15650
+ following.get(store)?.();
15651
+ following.delete(store);
15652
+ }
15653
+ let registered = false;
15654
+ function ensureTSLExtension() {
15655
+ if (registered) return;
15656
+ registered = true;
15657
+ registerRootExtension({
15658
+ name: "@react-three/tsl",
15659
+ setup: followPrimary,
15660
+ dispose: stopFollowing,
15661
+ // Resolves primaryStore itself, so every canvas sharing a renderer refreshes the same resources.
15662
+ hmr: clearHmrCaches
15663
+ });
15664
+ }
15665
+
15666
+ ensureTSLExtension();
15524
15667
  function useScopedResource(options) {
15525
15668
  const { store, kind, scope, isLeaf, create, prepare, reconcile, input } = options;
15526
15669
  const scopeKey = scope ?? ROOT_SCOPE;
@@ -15552,22 +15695,21 @@ function useScopedResource(options) {
15552
15695
  return entries;
15553
15696
  }
15554
15697
 
15555
- const isUniformNode$1 = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15556
15698
  function useUniforms(creatorOrScope, scope) {
15557
15699
  const store = usePrimaryStore();
15558
15700
  const removeUniforms2 = useCallback(
15559
- (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode$1),
15701
+ (names, targetScope) => removeResourceEntries(store, "uniforms", Array.isArray(names) ? names : [names], targetScope, isUniformNode),
15560
15702
  [store]
15561
15703
  );
15562
15704
  const clearUniforms = useCallback(
15563
- (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode$1),
15705
+ (targetScope) => clearResourceEntries(store, "uniforms", targetScope, isUniformNode),
15564
15706
  [store]
15565
15707
  );
15566
15708
  const rebuildUniforms = useCallback(
15567
15709
  (targetScope) => rebuildResource(store, "uniforms", targetScope),
15568
15710
  [store]
15569
15711
  );
15570
- const inputForMemoization = useMemo(() => {
15712
+ const processedInput = useMemo(() => {
15571
15713
  let raw = creatorOrScope;
15572
15714
  if (is.fun(creatorOrScope)) {
15573
15715
  const wrappedState = createLazyCreatorState(store.getState(), store);
@@ -15578,23 +15720,24 @@ function useUniforms(creatorOrScope, scope) {
15578
15720
  for (const [key, value] of Object.entries(raw)) {
15579
15721
  normalized[key] = vectorize(value);
15580
15722
  }
15581
- return normalized;
15723
+ return { candidates: raw, normalized };
15582
15724
  }
15583
- return raw;
15725
+ return { candidates: raw, normalized: raw };
15584
15726
  }, [creatorOrScope, store]);
15585
- const memoizedInput = useCompareMemoize(inputForMemoization);
15727
+ const memoizedInput = useCompareMemoize(processedInput.normalized);
15586
15728
  const isReader = memoizedInput === void 0 || typeof memoizedInput === "string";
15587
15729
  const storeUniforms = usePrimaryThree((s) => s.uniforms);
15588
15730
  const created = useScopedResource({
15589
15731
  store,
15590
15732
  kind: "uniforms",
15591
15733
  scope: isReader ? void 0 : scope,
15592
- isLeaf: isUniformNode$1,
15734
+ isLeaf: isUniformNode,
15593
15735
  input: memoizedInput,
15594
15736
  create: () => {
15595
15737
  if (isReader) return {};
15596
- if (typeof memoizedInput !== "object" || memoizedInput === null) throw new Error("Invalid uniform input");
15597
- return memoizedInput;
15738
+ if (typeof processedInput.candidates !== "object" || processedInput.candidates === null)
15739
+ throw new Error("Invalid uniform input");
15740
+ return processedInput.candidates;
15598
15741
  },
15599
15742
  prepare: (name, candidate) => createUniform(name, candidate, scope),
15600
15743
  reconcile: (existing, candidate) => {
@@ -15616,7 +15759,7 @@ function useUniforms(creatorOrScope, scope) {
15616
15759
  uniforms = storeUniforms;
15617
15760
  } else if (typeof memoizedInput === "string") {
15618
15761
  const scopeData = storeUniforms[memoizedInput];
15619
- uniforms = scopeData && !isUniformNode$1(scopeData) ? scopeData : {};
15762
+ uniforms = scopeData && !isUniformNode(scopeData) ? scopeData : {};
15620
15763
  }
15621
15764
  return { ...uniforms, removeUniforms: removeUniforms2, clearUniforms, rebuildUniforms };
15622
15765
  }
@@ -15631,7 +15774,7 @@ function removeUniforms(set, names, scope) {
15631
15774
  return { uniforms: { ...state.uniforms, [scope]: currentScope } };
15632
15775
  }
15633
15776
  const uniforms = { ...state.uniforms };
15634
- for (const name of names) if (isUniformNode$1(uniforms[name])) delete uniforms[name];
15777
+ for (const name of names) if (isUniformNode(uniforms[name])) delete uniforms[name];
15635
15778
  return { uniforms };
15636
15779
  });
15637
15780
  }
@@ -15645,7 +15788,7 @@ function clearRootUniforms(set) {
15645
15788
  set((state) => {
15646
15789
  const uniforms = {};
15647
15790
  for (const [key, value] of Object.entries(state.uniforms)) {
15648
- if (!isUniformNode$1(value)) uniforms[key] = value;
15791
+ if (!isUniformNode(value)) uniforms[key] = value;
15649
15792
  }
15650
15793
  return { uniforms };
15651
15794
  });
@@ -15655,8 +15798,7 @@ function createUniform(inName, node, scope) {
15655
15798
  const inValue = vectorize(node);
15656
15799
  const newUniform = uniform(inValue);
15657
15800
  if (typeof newUniform.setName === "function") {
15658
- const name = scope ? `${scope}_${inName}` : inName;
15659
- newUniform.setName(name);
15801
+ newUniform.setName(scopedNodeName(scope, inName));
15660
15802
  }
15661
15803
  return newUniform;
15662
15804
  }
@@ -15682,14 +15824,12 @@ function hasEqualsMethod(value) {
15682
15824
  function isSameThreeType(a, b) {
15683
15825
  const obj_a = a;
15684
15826
  const obj_b = b;
15685
- 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;
15827
+ 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;
15686
15828
  }
15687
15829
 
15688
15830
  const uniformOf = uniform;
15689
- const isUniformNode = (value) => value !== null && typeof value === "object" && "value" in value && "uuid" in value;
15690
- const isTSLNode$1 = (value) => value !== null && typeof value === "object" && "uuid" in value && "nodeType" in value;
15691
15831
  function useUniform(name, value) {
15692
- const store = useStore();
15832
+ const store = usePrimaryStore();
15693
15833
  const registered = useScopedResource({
15694
15834
  store,
15695
15835
  kind: "uniforms",
@@ -15699,7 +15839,7 @@ function useUniform(name, value) {
15699
15839
  create: () => value === void 0 ? {} : { [name]: value },
15700
15840
  prepare: createNamedUniform,
15701
15841
  reconcile: (existing2) => {
15702
- if (value !== void 0 && !isTSLNode$1(value) && !isUniformNode(value)) {
15842
+ if (value !== void 0 && !isTSLNode(value) && !isUniformNode(value)) {
15703
15843
  existing2.value = typeof value === "string" ? new Color(value) : value;
15704
15844
  }
15705
15845
  }
@@ -15715,7 +15855,7 @@ function createNamedUniform(name, value) {
15715
15855
  let node;
15716
15856
  if (isUniformNode(value)) {
15717
15857
  node = value;
15718
- } else if (isTSLNode$1(value)) {
15858
+ } else if (isTSLNode(value)) {
15719
15859
  node = uniformOf(value);
15720
15860
  } else if (typeof value === "string") {
15721
15861
  node = uniform(new Color(value));
@@ -15728,7 +15868,6 @@ function createNamedUniform(name, value) {
15728
15868
  return node;
15729
15869
  }
15730
15870
 
15731
- const isTSLNode = (value) => value !== null && typeof value === "object" && ("uuid" in value || "nodeType" in value);
15732
15871
  function useNodes(creatorOrScope, scope) {
15733
15872
  const store = usePrimaryStore();
15734
15873
  const removeNodes2 = useCallback(
@@ -15755,7 +15894,8 @@ function useNodes(creatorOrScope, scope) {
15755
15894
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15756
15895
  },
15757
15896
  prepare: (name, node) => {
15758
- node.setName?.(scope ? `${scope}.${name}` : name);
15897
+ const setName = Reflect.get(node, "setName");
15898
+ if (typeof setName === "function") setName.call(node, scopedNodeName(scope, name));
15759
15899
  return node;
15760
15900
  }
15761
15901
  });
@@ -15810,13 +15950,6 @@ function useLocalNodes(creator) {
15810
15950
  }, [store, creator, uniforms, nodes, textures, hmrVersion]);
15811
15951
  }
15812
15952
 
15813
- const isBufferLike = (value) => {
15814
- if (value === null || typeof value !== "object") return false;
15815
- if (ArrayBuffer.isView(value)) return true;
15816
- if ("isBufferAttribute" in value) return true;
15817
- if ("uuid" in value || "nodeType" in value) return true;
15818
- return false;
15819
- };
15820
15953
  const disposeBuffer = (buffer) => {
15821
15954
  if (buffer === null || typeof buffer !== "object") return;
15822
15955
  if ("dispose" in buffer && typeof buffer.dispose === "function") {
@@ -15863,9 +15996,8 @@ function useBuffers(creatorOrScope, scope) {
15863
15996
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15864
15997
  },
15865
15998
  prepare: (name, buffer) => {
15866
- if ("setName" in buffer && typeof buffer.setName === "function") {
15867
- buffer.setName(scope ? `${scope}.${name}` : name);
15868
- }
15999
+ const setName = Reflect.get(buffer, "setName");
16000
+ if (typeof setName === "function") setName.call(buffer, scopedNodeName(scope, name));
15869
16001
  return buffer;
15870
16002
  }
15871
16003
  });
@@ -15882,13 +16014,6 @@ function rebuildAllBuffers(store, scope) {
15882
16014
  rebuildResource(store, "buffers", scope);
15883
16015
  }
15884
16016
 
15885
- const isStorageLike = (value) => {
15886
- if (value === null || typeof value !== "object") return false;
15887
- if ("isTexture" in value) return true;
15888
- if ("isData3DTexture" in value) return true;
15889
- if ("uuid" in value || "nodeType" in value) return true;
15890
- return false;
15891
- };
15892
16017
  const disposeStorage = (storage) => {
15893
16018
  if (storage === null || typeof storage !== "object") return;
15894
16019
  if ("dispose" in storage && typeof storage.dispose === "function") {
@@ -15935,10 +16060,9 @@ function useGPUStorage(creatorOrScope, scope) {
15935
16060
  return creatorOrScope(createLazyCreatorState(store.getState(), store));
15936
16061
  },
15937
16062
  prepare: (name, storage) => {
15938
- const label = scope ? `${scope}.${name}` : name;
15939
- if ("setName" in storage && typeof storage.setName === "function") {
15940
- storage.setName(label);
15941
- }
16063
+ const label = scopedNodeName(scope, name);
16064
+ const setName = Reflect.get(storage, "setName");
16065
+ if (typeof setName === "function") setName.call(storage, label);
15942
16066
  if ("name" in storage && typeof storage.name === "string") {
15943
16067
  storage.name = label;
15944
16068
  }
@@ -15964,11 +16088,58 @@ function rebuildAllStorage(store, scope) {
15964
16088
  rebuildResource(store, "gpuStorage", scope);
15965
16089
  }
15966
16090
 
16091
+ function addTexture(set, key, value) {
16092
+ set((state) => {
16093
+ const newMap = new Map(state.textures);
16094
+ newMap.set(key, value);
16095
+ return { textures: newMap };
16096
+ });
16097
+ }
16098
+ function addTextures(set, items) {
16099
+ set((state) => {
16100
+ const newMap = new Map(state.textures);
16101
+ const entries = items instanceof Map ? items.entries() : Object.entries(items);
16102
+ for (const [key, value] of entries) {
16103
+ newMap.set(key, value);
16104
+ }
16105
+ return { textures: newMap };
16106
+ });
16107
+ }
16108
+ function removeTexture(set, key) {
16109
+ set((state) => {
16110
+ const newMap = new Map(state.textures);
16111
+ newMap.delete(key);
16112
+ return { textures: newMap };
16113
+ });
16114
+ }
16115
+ function removeTextures(set, keys) {
16116
+ set((state) => {
16117
+ const newMap = new Map(state.textures);
16118
+ for (const key of keys) newMap.delete(key);
16119
+ return { textures: newMap };
16120
+ });
16121
+ }
16122
+ function createTextureOperations(set) {
16123
+ return {
16124
+ add: (key, value) => addTexture(set, key, value),
16125
+ addMultiple: (items) => addTextures(set, items),
16126
+ remove: (key) => removeTexture(set, key),
16127
+ removeMultiple: (keys) => removeTextures(set, keys)
16128
+ };
16129
+ }
16130
+
16131
+ function renderThroughPipeline(store) {
16132
+ return () => {
16133
+ const pipeline = store.getState().renderPipeline;
16134
+ if (pipeline?.render) pipeline.render();
16135
+ };
16136
+ }
15967
16137
  function useRenderPipeline(mainCB, setupCB) {
15968
16138
  const store = useStore();
15969
16139
  const { scene, camera, renderer, isLegacy } = useThree$1();
15970
16140
  const callbacksRanRef = useRef(false);
15971
16141
  const scenePassCacheRef = useRef(null);
16142
+ const forceScenePassRebuildRef = useRef(false);
15972
16143
  const mainCBRef = useRef(mainCB);
15973
16144
  const setupCBRef = useRef(setupCB);
15974
16145
  mainCBRef.current = mainCB;
@@ -15976,7 +16147,7 @@ function useRenderPipeline(mainCB, setupCB) {
15976
16147
  const [rebuildVersion, setRebuildVersion] = useState(0);
15977
16148
  useEffect(() => {
15978
16149
  callbacksRanRef.current = false;
15979
- scenePassCacheRef.current = null;
16150
+ forceScenePassRebuildRef.current = true;
15980
16151
  }, []);
15981
16152
  const clearPasses = useCallback(() => {
15982
16153
  store.setState({ passes: {} });
@@ -15986,12 +16157,15 @@ function useRenderPipeline(mainCB, setupCB) {
15986
16157
  renderPipeline: null,
15987
16158
  passes: {}
15988
16159
  });
16160
+ setRenderOverride(store, null);
16161
+ scenePassCacheRef.current?.scenePass.dispose();
15989
16162
  callbacksRanRef.current = false;
15990
16163
  scenePassCacheRef.current = null;
16164
+ forceScenePassRebuildRef.current = false;
15991
16165
  }, [store]);
15992
16166
  const rebuild = useCallback(() => {
15993
16167
  callbacksRanRef.current = false;
15994
- scenePassCacheRef.current = null;
16168
+ forceScenePassRebuildRef.current = true;
15995
16169
  setRebuildVersion((v) => v + 1);
15996
16170
  }, []);
15997
16171
  useLayoutEffect(() => {
@@ -15999,6 +16173,9 @@ function useRenderPipeline(mainCB, setupCB) {
15999
16173
  throw new Error("useRenderPipeline is only available with WebGPU renderer. Set renderer prop on Canvas.");
16000
16174
  }
16001
16175
  if (!renderer || !scene || !camera) return;
16176
+ if (!("isWebGPURenderer" in renderer)) {
16177
+ throw new Error("useRenderPipeline: renderer is not a WebGPURenderer but isLegacy is false.");
16178
+ }
16002
16179
  const state = store.getState();
16003
16180
  const set = store.setState;
16004
16181
  try {
@@ -16009,21 +16186,34 @@ function useRenderPipeline(mainCB, setupCB) {
16009
16186
  pipeline = new RenderPipeline(renderer);
16010
16187
  justCreatedPipeline = true;
16011
16188
  }
16012
- const cacheValid = scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16189
+ const cacheValid = !forceScenePassRebuildRef.current && scenePassCacheRef.current && scenePassCacheRef.current.sceneUuid === scene.uuid && scenePassCacheRef.current.cameraUuid === camera.uuid;
16190
+ let outgoingScenePass = null;
16013
16191
  let scenePass;
16014
16192
  if (cacheValid) {
16015
16193
  scenePass = scenePassCacheRef.current.scenePass;
16016
16194
  } else {
16195
+ outgoingScenePass = scenePassCacheRef.current?.scenePass ?? null;
16017
16196
  scenePass = pass(scene, camera);
16018
16197
  scenePassCacheRef.current = { sceneUuid: scene.uuid, cameraUuid: camera.uuid, scenePass };
16198
+ forceScenePassRebuildRef.current = false;
16019
16199
  }
16020
16200
  currentPasses.scenePass = scenePass;
16021
- if (!pipeline.outputNode || justCreatedPipeline) pipeline.outputNode = scenePass;
16201
+ let outputNodeChanged = false;
16202
+ if (!pipeline.outputNode || justCreatedPipeline || pipeline.outputNode === outgoingScenePass) {
16203
+ pipeline.outputNode = scenePass;
16204
+ outputNodeChanged = true;
16205
+ }
16022
16206
  set({ renderPipeline: pipeline, passes: currentPasses });
16207
+ setRenderOverride(store, renderThroughPipeline(store));
16023
16208
  const shouldRunCallbacks = justCreatedPipeline || !callbacksRanRef.current || !cacheValid;
16024
16209
  if (shouldRunCallbacks) {
16025
16210
  if (setupCBRef.current) {
16026
- const freshState = store.getState();
16211
+ const freshState = Object.assign({}, store.getState(), {
16212
+ renderPipeline: pipeline,
16213
+ passes: { ...currentPasses, scenePass },
16214
+ renderer,
16215
+ isLegacy: false
16216
+ });
16027
16217
  const setupResult = setupCBRef.current(freshState);
16028
16218
  if (setupResult && typeof setupResult === "object") {
16029
16219
  currentPasses = { ...currentPasses, ...setupResult };
@@ -16031,34 +16221,37 @@ function useRenderPipeline(mainCB, setupCB) {
16031
16221
  }
16032
16222
  }
16033
16223
  if (mainCBRef.current) {
16034
- const freshState = store.getState();
16224
+ const freshState = Object.assign({}, store.getState(), {
16225
+ renderPipeline: pipeline,
16226
+ passes: { ...currentPasses, scenePass },
16227
+ renderer,
16228
+ isLegacy: false
16229
+ });
16035
16230
  const mainResult = mainCBRef.current(freshState);
16036
16231
  if (mainResult && typeof mainResult === "object") {
16037
16232
  currentPasses = { ...currentPasses, ...mainResult };
16038
16233
  set({ passes: currentPasses });
16039
16234
  }
16040
16235
  }
16236
+ outputNodeChanged = true;
16041
16237
  callbacksRanRef.current = true;
16042
16238
  }
16239
+ if (outputNodeChanged) pipeline.needsUpdate = true;
16240
+ if (outgoingScenePass && outgoingScenePass !== scenePass) outgoingScenePass.dispose();
16043
16241
  } catch (error) {
16044
16242
  console.warn("[useRenderPipeline] Setup failed; the render pipeline was not configured:", error);
16045
16243
  }
16046
16244
  }, [store, renderer, scene, camera, isLegacy, rebuildVersion]);
16047
16245
  const passes = useThree$1((s) => s.passes);
16048
16246
  const renderPipeline = useThree$1((s) => s.renderPipeline);
16049
- return {
16050
- passes,
16051
- renderPipeline,
16052
- clearPasses,
16053
- reset,
16054
- rebuild,
16055
- // isReady indicates if RenderPipeline is configured and ready for rendering
16056
- isReady: renderPipeline !== null
16057
- };
16247
+ const actions = { passes, clearPasses, reset, rebuild };
16248
+ if (renderPipeline) return { ...actions, isReady: true, renderPipeline };
16249
+ return { ...actions, isReady: false, renderPipeline: null };
16058
16250
  }
16059
16251
 
16060
16252
  extend(THREE);
16061
16253
  const useThree = useThree$1;
16062
16254
  const useFrame = useFrame$1;
16255
+ const Canvas = Canvas$1;
16063
16256
 
16064
- 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 };
16257
+ 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 };