@ericchen1990/pano-view 3.2.2 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7532,6 +7532,7 @@ var PanoViewer = forwardRef2(
7532
7532
  Canvas,
7533
7533
  {
7534
7534
  "aria-label": `${ariaLabel} canvas`,
7535
+ resize: { debounce: 0 },
7535
7536
  camera: {
7536
7537
  far: DEFAULT_PANORAMA_CAMERA_FAR,
7537
7538
  fov: normalizedInitialView.fov,
@@ -9715,8 +9716,8 @@ function usePanoViewer() {
9715
9716
  }
9716
9717
 
9717
9718
  // src/sphere-tile/sphere-tile.tsx
9718
- import { useFrame as useFrame9, useThree as useThree13 } from "@react-three/fiber";
9719
- import { memo, useCallback as useCallback9, useEffect as useEffect22, useMemo as useMemo14, useRef as useRef23, useState as useState10 } from "react";
9719
+ import { useFrame as useFrame9, useThree as useThree14 } from "@react-three/fiber";
9720
+ import { memo, useCallback as useCallback9, useEffect as useEffect23, useMemo as useMemo15, useRef as useRef23, useState as useState10 } from "react";
9720
9721
  import { BackSide as BackSide2, Frustum, Matrix4 as Matrix42, PerspectiveCamera as PerspectiveCamera3 } from "three";
9721
9722
 
9722
9723
  // src/asset-url.ts
@@ -10013,6 +10014,9 @@ var TileTextureManager = class {
10013
10014
  this.generation = 0;
10014
10015
  this.options = options;
10015
10016
  }
10017
+ get memoryBudgetBytes() {
10018
+ return this.options.maxBytes;
10019
+ }
10016
10020
  resume() {
10017
10021
  this.disposed = false;
10018
10022
  this.drain();
@@ -10080,7 +10084,7 @@ var TileTextureManager = class {
10080
10084
  this.totalBytes = 0;
10081
10085
  }
10082
10086
  /**
10083
- * Releases textures that are no longer rendered by any Tile instance.
10087
+ * Releases textures that are no longer rendered by any tile source.
10084
10088
  * Scene transitions call this after the outgoing panorama has been
10085
10089
  * snapshotted, so the incoming scene never competes with stale GPU tiles.
10086
10090
  */
@@ -10214,8 +10218,44 @@ var TileTextureManager = class {
10214
10218
  }
10215
10219
  };
10216
10220
 
10221
+ // src/tile/texture-manager-context.tsx
10222
+ import { useThree as useThree13 } from "@react-three/fiber";
10223
+ import {
10224
+ createContext as createContext15,
10225
+ useContext as useContext21,
10226
+ useEffect as useEffect21,
10227
+ useMemo as useMemo14
10228
+ } from "react";
10229
+ import { jsx as jsx15 } from "react/jsx-runtime";
10230
+ var TileTextureManagerContext = createContext15(null);
10231
+ function TileTextureManagerProvider({
10232
+ children,
10233
+ maxTextureMemoryMb = 128,
10234
+ maxConcurrentLoads = 4
10235
+ }) {
10236
+ const gl = useThree13((state) => state.gl);
10237
+ const anisotropy = gl.capabilities.getMaxAnisotropy();
10238
+ const manager = useMemo14(
10239
+ () => new TileTextureManager({
10240
+ anisotropy,
10241
+ maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
10242
+ concurrency: Math.max(1, Math.floor(maxConcurrentLoads)),
10243
+ retryCount: 1
10244
+ }),
10245
+ [anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
10246
+ );
10247
+ useEffect21(() => {
10248
+ manager.resume();
10249
+ return () => manager.dispose();
10250
+ }, [manager]);
10251
+ return /* @__PURE__ */ jsx15(TileTextureManagerContext.Provider, { value: manager, children });
10252
+ }
10253
+ function useSharedTileTextureManager() {
10254
+ return useContext21(TileTextureManagerContext);
10255
+ }
10256
+
10217
10257
  // src/tile/texture-hooks.ts
10218
- import { useEffect as useEffect21, useRef as useRef22, useState as useState9 } from "react";
10258
+ import { useEffect as useEffect22, useRef as useRef22, useState as useState9 } from "react";
10219
10259
  import {
10220
10260
  ClampToEdgeWrapping as ClampToEdgeWrapping2,
10221
10261
  LinearFilter as LinearFilter2,
@@ -10226,7 +10266,7 @@ function usePreviewTexture(url, anisotropy, onError) {
10226
10266
  const [texture, setTexture] = useState9(null);
10227
10267
  const onErrorRef = useRef22(onError);
10228
10268
  onErrorRef.current = onError;
10229
- useEffect21(() => {
10269
+ useEffect22(() => {
10230
10270
  if (!url) {
10231
10271
  setTexture(null);
10232
10272
  return;
@@ -10263,7 +10303,7 @@ function usePreviewTexture(url, anisotropy, onError) {
10263
10303
  active = false;
10264
10304
  };
10265
10305
  }, [anisotropy, url]);
10266
- useEffect21(
10306
+ useEffect22(
10267
10307
  () => () => {
10268
10308
  texture?.dispose();
10269
10309
  },
@@ -10279,7 +10319,7 @@ function useManagedTexture(manager, key, url, address) {
10279
10319
  snapshot: manager.getSnapshot(key)
10280
10320
  }));
10281
10321
  const snapshot = managedState.manager === manager && managedState.key === key && managedState.url === url ? managedState.snapshot : manager.getSnapshot(key);
10282
- useEffect21(
10322
+ useEffect22(
10283
10323
  () => manager.acquire(key, url, address, (nextSnapshot) => {
10284
10324
  setManagedState({
10285
10325
  manager,
@@ -10427,14 +10467,14 @@ function preferredSphereLevel(levels, height, dpr, verticalFov) {
10427
10467
  }
10428
10468
 
10429
10469
  // src/sphere-tile/sphere-tile.tsx
10430
- import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
10470
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
10431
10471
  function SpherePatch({
10432
10472
  address,
10433
10473
  texture,
10434
10474
  yawOffset,
10435
10475
  preview = false
10436
10476
  }) {
10437
- const geometry = useMemo14(
10477
+ const geometry = useMemo15(
10438
10478
  () => sphereTileGeometry(
10439
10479
  address,
10440
10480
  yawOffset,
@@ -10442,13 +10482,13 @@ function SpherePatch({
10442
10482
  ),
10443
10483
  [address, yawOffset, preview]
10444
10484
  );
10445
- useEffect22(() => () => geometry.dispose(), [geometry]);
10446
- return /* @__PURE__ */ jsx15(
10485
+ useEffect23(() => () => geometry.dispose(), [geometry]);
10486
+ return /* @__PURE__ */ jsx16(
10447
10487
  "mesh",
10448
10488
  {
10449
10489
  geometry,
10450
10490
  renderOrder: preview ? -1e3 : -900 + address.level,
10451
- children: /* @__PURE__ */ jsx15(
10491
+ children: /* @__PURE__ */ jsx16(
10452
10492
  PanoBasicMaterial,
10453
10493
  {
10454
10494
  map: texture,
@@ -10475,10 +10515,10 @@ var ManagedPatch = memo(function ManagedPatch2({
10475
10515
  url,
10476
10516
  address
10477
10517
  );
10478
- useEffect22(() => {
10518
+ useEffect23(() => {
10479
10519
  if (snapshot.texture) ready();
10480
10520
  }, [snapshot.texture, ready]);
10481
- return snapshot.texture ? /* @__PURE__ */ jsx15(
10521
+ return snapshot.texture ? /* @__PURE__ */ jsx16(
10482
10522
  SpherePatch,
10483
10523
  {
10484
10524
  address,
@@ -10506,7 +10546,7 @@ function SphereTileSource({
10506
10546
  onTileError,
10507
10547
  onLevelChange
10508
10548
  }) {
10509
- const { camera, gl, size } = useThree13();
10549
+ const { camera, gl, size } = useThree14();
10510
10550
  const anisotropy = gl.capabilities.getMaxAnisotropy();
10511
10551
  const preview = usePreviewTexture(
10512
10552
  resolveAssetUrl(baseUrl, previewUrl),
@@ -10520,14 +10560,15 @@ function SphereTileSource({
10520
10560
  onLevelChange
10521
10561
  });
10522
10562
  callbacks.current = { onReady, onLoadProgress, onTileError, onLevelChange };
10523
- const budget = Math.max(
10563
+ const sharedManager = useSharedTileTextureManager();
10564
+ const ownedBudget = Math.max(
10524
10565
  1,
10525
10566
  Number.isFinite(maxTextureMemoryMb) ? maxTextureMemoryMb : 128
10526
10567
  ) * 1024 * 1024;
10527
- const manager = useMemo14(
10568
+ const ownedManager = useMemo15(
10528
10569
  () => new TileTextureManager({
10529
10570
  anisotropy,
10530
- maxBytes: budget,
10571
+ maxBytes: ownedBudget,
10531
10572
  concurrency: Math.max(
10532
10573
  1,
10533
10574
  Math.floor(
@@ -10541,8 +10582,10 @@ function SphereTileSource({
10541
10582
  onProgress: (progress) => callbacks.current.onLoadProgress?.(progress),
10542
10583
  onError: (error) => callbacks.current.onTileError?.(error)
10543
10584
  }),
10544
- [anisotropy, budget, maxConcurrentLoads, retryCount]
10585
+ [anisotropy, ownedBudget, maxConcurrentLoads, retryCount]
10545
10586
  );
10587
+ const manager = sharedManager ?? ownedManager;
10588
+ const budget = manager.memoryBudgetBytes;
10546
10589
  const readyRef = useRef23(false);
10547
10590
  const ready = useCallback9(() => {
10548
10591
  if (!readyRef.current) {
@@ -10550,21 +10593,27 @@ function SphereTileSource({
10550
10593
  callbacks.current.onReady?.();
10551
10594
  }
10552
10595
  }, []);
10553
- useEffect22(() => {
10596
+ useEffect23(() => {
10554
10597
  if (preview) ready();
10555
10598
  }, [preview, ready]);
10556
- useEffect22(() => {
10599
+ useEffect23(() => {
10557
10600
  manager.resume();
10558
- return () => manager.dispose();
10559
- }, [manager]);
10560
- const resolveUrl = useMemo14(
10601
+ return () => {
10602
+ if (manager === ownedManager) {
10603
+ manager.dispose();
10604
+ return;
10605
+ }
10606
+ queueMicrotask(() => manager.releaseUnused());
10607
+ };
10608
+ }, [manager, ownedManager]);
10609
+ const resolveUrl = useMemo15(
10561
10610
  () => (address) => resolveAssetUrl(
10562
10611
  baseUrl,
10563
10612
  resolveTileUrl ? resolveTileUrl(address) : resolveSphereTemplateUrl(urlTemplate, address, indexBase)
10564
10613
  ),
10565
10614
  [baseUrl, resolveTileUrl, urlTemplate, indexBase]
10566
10615
  );
10567
- const candidates = useMemo14(
10616
+ const candidates = useMemo15(
10568
10617
  () => config.levels.map(
10569
10618
  (level, index) => sphereTileAddresses(level, index, config.tileSize).map((address) => ({
10570
10619
  address,
@@ -10573,12 +10622,12 @@ function SphereTileSource({
10573
10622
  ),
10574
10623
  [config, yawOffset]
10575
10624
  );
10576
- const previewAddress = useMemo14(
10625
+ const previewAddress = useMemo15(
10577
10626
  () => sphereTileAddresses({ width: 2, height: 1 }, 0, 2)[0],
10578
10627
  []
10579
10628
  );
10580
10629
  const [layers, setLayers] = useState10([]);
10581
- const scratch = useMemo14(
10630
+ const scratch = useMemo15(
10582
10631
  () => ({
10583
10632
  camera: new PerspectiveCamera3(),
10584
10633
  frustum: new Frustum(),
@@ -10648,7 +10697,7 @@ function SphereTileSource({
10648
10697
  }
10649
10698
  });
10650
10699
  return /* @__PURE__ */ jsxs9("group", { visible, children: [
10651
- preview ? /* @__PURE__ */ jsx15(
10700
+ preview ? /* @__PURE__ */ jsx16(
10652
10701
  SpherePatch,
10653
10702
  {
10654
10703
  address: previewAddress,
@@ -10658,7 +10707,7 @@ function SphereTileSource({
10658
10707
  }
10659
10708
  ) : null,
10660
10709
  loadMode !== "preview" ? layers.flatMap(
10661
- (layer) => layer.addresses.map((address) => /* @__PURE__ */ jsx15(
10710
+ (layer) => layer.addresses.map((address) => /* @__PURE__ */ jsx16(
10662
10711
  ManagedPatch,
10663
10712
  {
10664
10713
  address,
@@ -10674,14 +10723,14 @@ function SphereTileSource({
10674
10723
  }
10675
10724
  function SphereTile({ multires, ...props }) {
10676
10725
  const token = typeof multires === "string" ? multires : JSON.stringify(multires);
10677
- const config = useMemo14(() => parseSphereMultires(multires), [token]);
10726
+ const config = useMemo15(() => parseSphereMultires(multires), [token]);
10678
10727
  const sourceKey = `${props.baseUrl}\0${token}\0${props.previewUrl}\0${props.urlTemplate ?? ""}\0${props.indexBase ?? 1}`;
10679
- return /* @__PURE__ */ jsx15(SphereTileSource, { ...props, config }, sourceKey);
10728
+ return /* @__PURE__ */ jsx16(SphereTileSource, { ...props, config }, sourceKey);
10680
10729
  }
10681
10730
 
10682
10731
  // src/sphere.tsx
10683
- import { useThree as useThree14 } from "@react-three/fiber";
10684
- import { useEffect as useEffect23, useMemo as useMemo15, useRef as useRef24, useState as useState11 } from "react";
10732
+ import { useThree as useThree15 } from "@react-three/fiber";
10733
+ import { useEffect as useEffect24, useMemo as useMemo16, useRef as useRef24, useState as useState11 } from "react";
10685
10734
  import {
10686
10735
  BackSide as BackSide3,
10687
10736
  LinearFilter as LinearFilter3,
@@ -10691,14 +10740,14 @@ import {
10691
10740
  SRGBColorSpace as SRGBColorSpace3,
10692
10741
  TextureLoader as TextureLoader3
10693
10742
  } from "three";
10694
- import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
10743
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
10695
10744
  function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
10696
10745
  const [texture, setTexture] = useState11(null);
10697
10746
  const onLoadRef = useRef24(onLoad);
10698
10747
  const onErrorRef = useRef24(onError);
10699
10748
  onLoadRef.current = onLoad;
10700
10749
  onErrorRef.current = onError;
10701
- useEffect23(() => {
10750
+ useEffect24(() => {
10702
10751
  if (!url) {
10703
10752
  setTexture(null);
10704
10753
  return;
@@ -10737,7 +10786,7 @@ function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
10737
10786
  active = false;
10738
10787
  };
10739
10788
  }, [anisotropy, generateMipmaps, url]);
10740
- useEffect23(
10789
+ useEffect24(
10741
10790
  () => () => {
10742
10791
  texture?.dispose();
10743
10792
  },
@@ -10755,9 +10804,9 @@ function Sphere({
10755
10804
  onPreviewError,
10756
10805
  visible = true
10757
10806
  }) {
10758
- const gl = useThree14((state) => state.gl);
10807
+ const gl = useThree15((state) => state.gl);
10759
10808
  const anisotropy = gl.capabilities.getMaxAnisotropy();
10760
- const resolvedPreviewUrl = useMemo15(
10809
+ const resolvedPreviewUrl = useMemo16(
10761
10810
  () => resolveUrlAgainstFile(src, previewUrl),
10762
10811
  [previewUrl, src]
10763
10812
  );
@@ -10777,10 +10826,10 @@ function Sphere({
10777
10826
  );
10778
10827
  const displayTexture = texture ?? previewTexture;
10779
10828
  const readyRef = useRef24(false);
10780
- useEffect23(() => {
10829
+ useEffect24(() => {
10781
10830
  readyRef.current = false;
10782
10831
  }, [resolvedPreviewUrl, src]);
10783
- useEffect23(() => {
10832
+ useEffect24(() => {
10784
10833
  if (!displayTexture || readyRef.current) {
10785
10834
  return;
10786
10835
  }
@@ -10796,8 +10845,8 @@ function Sphere({
10796
10845
  rotation: [0, MathUtils6.degToRad(90 + yawOffset), 0],
10797
10846
  visible,
10798
10847
  children: [
10799
- /* @__PURE__ */ jsx16("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
10800
- /* @__PURE__ */ jsx16(
10848
+ /* @__PURE__ */ jsx17("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
10849
+ /* @__PURE__ */ jsx17(
10801
10850
  PanoBasicMaterial,
10802
10851
  {
10803
10852
  map: displayTexture,
@@ -10811,13 +10860,13 @@ function Sphere({
10811
10860
  }
10812
10861
 
10813
10862
  // src/video/pano-video.tsx
10814
- import { useThree as useThree15 } from "@react-three/fiber";
10863
+ import { useThree as useThree16 } from "@react-three/fiber";
10815
10864
  import {
10816
10865
  useCallback as useCallback10,
10817
- useContext as useContext21,
10818
- useEffect as useEffect24,
10866
+ useContext as useContext22,
10867
+ useEffect as useEffect25,
10819
10868
  useLayoutEffect as useLayoutEffect3,
10820
- useMemo as useMemo16,
10869
+ useMemo as useMemo17,
10821
10870
  useRef as useRef25,
10822
10871
  useState as useState12
10823
10872
  } from "react";
@@ -10831,7 +10880,7 @@ import {
10831
10880
  TextureLoader as TextureLoader4,
10832
10881
  VideoTexture
10833
10882
  } from "three";
10834
- import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
10883
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
10835
10884
  var EMPTY_TRACKS = [];
10836
10885
  var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
10837
10886
  function createEmptySnapshot() {
@@ -10915,11 +10964,11 @@ function applyEquirectangularLayout(texture, anisotropy) {
10915
10964
  texture.needsUpdate = true;
10916
10965
  }
10917
10966
  function usePosterTexture(poster, onError) {
10918
- const gl = useThree15((state) => state.gl);
10967
+ const gl = useThree16((state) => state.gl);
10919
10968
  const [texture, setTexture] = useState12(null);
10920
10969
  const onErrorRef = useRef25(onError);
10921
10970
  onErrorRef.current = onError;
10922
- useEffect24(() => {
10971
+ useEffect25(() => {
10923
10972
  let active = true;
10924
10973
  setTexture(null);
10925
10974
  if (!poster) {
@@ -10956,7 +11005,7 @@ function usePosterTexture(poster, onError) {
10956
11005
  active = false;
10957
11006
  };
10958
11007
  }, [gl, poster]);
10959
- useEffect24(
11008
+ useEffect25(
10960
11009
  () => () => {
10961
11010
  texture?.dispose();
10962
11011
  },
@@ -11014,16 +11063,16 @@ function PanoVideo({
11014
11063
  onTrackChange
11015
11064
  }) {
11016
11065
  usePanoMediaActivationIntent(autoPlay);
11017
- const gl = useThree15((state) => state.gl);
11018
- const host = useContext21(PanoVideoHostContext);
11019
- const overlay = useContext21(PanoChromeOverlayContext);
11020
- const variants = useMemo16(
11066
+ const gl = useThree16((state) => state.gl);
11067
+ const host = useContext22(PanoVideoHostContext);
11068
+ const overlay = useContext22(PanoChromeOverlayContext);
11069
+ const variants = useMemo17(
11021
11070
  () => resolvePanoVideoVariants({ poster, src, variants: variantsProp }),
11022
11071
  [poster, src, variantsProp]
11023
11072
  );
11024
11073
  const tracks = tracksProp ?? EMPTY_TRACKS;
11025
11074
  const captionsEnabled = captions !== false;
11026
- const captionAppearance = useMemo16(
11075
+ const captionAppearance = useMemo17(
11027
11076
  () => resolvePanoVideoCaptionAppearance(
11028
11077
  typeof captions === "object" ? captions : void 0
11029
11078
  ),
@@ -11088,7 +11137,7 @@ function PanoVideo({
11088
11137
  onEndedRef.current = onEnded;
11089
11138
  onVariantChangeRef.current = onVariantChange;
11090
11139
  onTrackChangeRef.current = onTrackChange;
11091
- const resource = useMemo16(() => {
11140
+ const resource = useMemo17(() => {
11092
11141
  if (typeof document === "undefined") {
11093
11142
  return null;
11094
11143
  }
@@ -11105,7 +11154,7 @@ function PanoVideo({
11105
11154
  texture.generateMipmaps = false;
11106
11155
  return { video, texture };
11107
11156
  }, []);
11108
- useEffect24(
11157
+ useEffect25(
11109
11158
  () => () => {
11110
11159
  if (!resource) {
11111
11160
  return;
@@ -11120,13 +11169,13 @@ function PanoVideo({
11120
11169
  },
11121
11170
  [resource]
11122
11171
  );
11123
- useEffect24(() => {
11172
+ useEffect25(() => {
11124
11173
  if (!resource) {
11125
11174
  return;
11126
11175
  }
11127
11176
  resource.texture.anisotropy = gl.capabilities.getMaxAnisotropy();
11128
11177
  }, [gl, resource]);
11129
- useEffect24(() => {
11178
+ useEffect25(() => {
11130
11179
  if (!resource || !overlay?.overlayElement) {
11131
11180
  return;
11132
11181
  }
@@ -11201,7 +11250,7 @@ function PanoVideo({
11201
11250
  captionAppearance
11202
11251
  ]
11203
11252
  );
11204
- const controller = useMemo16(
11253
+ const controller = useMemo17(
11205
11254
  () => ({
11206
11255
  subscribe: (onStoreChange) => {
11207
11256
  const store = storeRef.current;
@@ -11279,10 +11328,10 @@ function PanoVideo({
11279
11328
  };
11280
11329
  emitSnapshot();
11281
11330
  }, [emitSnapshot, playbackRates, resource]);
11282
- useEffect24(() => {
11331
+ useEffect25(() => {
11283
11332
  emitSnapshot();
11284
11333
  }, [emitSnapshot]);
11285
- useEffect24(() => {
11334
+ useEffect25(() => {
11286
11335
  if (!resource || !activeVariant || !mediaKey) {
11287
11336
  return;
11288
11337
  }
@@ -11339,7 +11388,7 @@ function PanoVideo({
11339
11388
  video.removeEventListener("error", handleError);
11340
11389
  };
11341
11390
  }, [mediaKey, resource]);
11342
- useEffect24(() => {
11391
+ useEffect25(() => {
11343
11392
  if (!resource) {
11344
11393
  return;
11345
11394
  }
@@ -11431,7 +11480,7 @@ function PanoVideo({
11431
11480
  }
11432
11481
  };
11433
11482
  }, [emitSnapshot, resource]);
11434
- useEffect24(() => {
11483
+ useEffect25(() => {
11435
11484
  if (!resource) {
11436
11485
  return;
11437
11486
  }
@@ -11451,7 +11500,7 @@ function PanoVideo({
11451
11500
  }
11452
11501
  video.preload = preload;
11453
11502
  }, [loop, muted, playsInline, preload, resource, volume]);
11454
- useEffect24(() => {
11503
+ useEffect25(() => {
11455
11504
  if (!resource) {
11456
11505
  return;
11457
11506
  }
@@ -11501,7 +11550,7 @@ function PanoVideo({
11501
11550
  video.removeEventListener("ratechange", handleVolume);
11502
11551
  };
11503
11552
  }, [emitSnapshot, resource]);
11504
- useEffect24(() => {
11553
+ useEffect25(() => {
11505
11554
  if (!resource) {
11506
11555
  return;
11507
11556
  }
@@ -11536,7 +11585,7 @@ function PanoVideo({
11536
11585
  video.textTracks.removeEventListener("change", applyModes);
11537
11586
  };
11538
11587
  }, [emitSnapshot, resource, trackId, tracks]);
11539
- useEffect24(() => {
11588
+ useEffect25(() => {
11540
11589
  if (!captionsEnabled) {
11541
11590
  setTrackId(null);
11542
11591
  return;
@@ -11554,8 +11603,8 @@ function PanoVideo({
11554
11603
  rotation: [0, MathUtils7.degToRad(90 + yawOffset), 0],
11555
11604
  visible,
11556
11605
  children: [
11557
- /* @__PURE__ */ jsx17("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
11558
- /* @__PURE__ */ jsx17(
11606
+ /* @__PURE__ */ jsx18("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
11607
+ /* @__PURE__ */ jsx18(
11559
11608
  PanoBasicMaterial,
11560
11609
  {
11561
11610
  map: activeTexture,
@@ -11566,7 +11615,7 @@ function PanoVideo({
11566
11615
  ]
11567
11616
  }
11568
11617
  ) : null,
11569
- captionsEnabled ? /* @__PURE__ */ jsx17(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
11618
+ captionsEnabled ? /* @__PURE__ */ jsx18(PanoVideoCaptions, { appearance: typeof captions === "object" ? captions : void 0 }) : null
11570
11619
  ] });
11571
11620
  }
11572
11621
 
@@ -11634,8 +11683,8 @@ function isSnapshotTransition(preset) {
11634
11683
  }
11635
11684
 
11636
11685
  // src/scene-transitions/particle-overlay.tsx
11637
- import { useFrame as useFrame10, useThree as useThree16 } from "@react-three/fiber";
11638
- import { useEffect as useEffect25, useMemo as useMemo17, useRef as useRef26 } from "react";
11686
+ import { useFrame as useFrame10, useThree as useThree17 } from "@react-three/fiber";
11687
+ import { useEffect as useEffect26, useMemo as useMemo18, useRef as useRef26 } from "react";
11639
11688
  import {
11640
11689
  DoubleSide as DoubleSide2,
11641
11690
  InstancedBufferAttribute,
@@ -11698,7 +11747,7 @@ function overlayProgress(elapsedTime, duration, startTime) {
11698
11747
  }
11699
11748
 
11700
11749
  // src/scene-transitions/particle-overlay.tsx
11701
- import { jsx as jsx18 } from "react/jsx-runtime";
11750
+ import { jsx as jsx19 } from "react/jsx-runtime";
11702
11751
  var PARTICLE_GRID = 48;
11703
11752
  var PARTICLE_COUNT = PARTICLE_GRID * PARTICLE_GRID;
11704
11753
  var PARTICLE_VERTEX_SHADER = `
@@ -11774,13 +11823,13 @@ function ParticleOverlay({
11774
11823
  runId,
11775
11824
  onFinish
11776
11825
  }) {
11777
- const { camera } = useThree16();
11826
+ const { camera } = useThree17();
11778
11827
  const meshRef = useRef26(null);
11779
11828
  const startRef = useRef26(null);
11780
11829
  const finishedRef = useRef26(false);
11781
11830
  const directionRef = useRef26(new Vector310());
11782
- const geometry = useMemo17(() => createParticleGeometry(), []);
11783
- const material = useMemo17(
11831
+ const geometry = useMemo18(() => createParticleGeometry(), []);
11832
+ const material = useMemo18(
11784
11833
  () => new ShaderMaterial2({
11785
11834
  transparent: true,
11786
11835
  depthTest: false,
@@ -11796,9 +11845,9 @@ function ParticleOverlay({
11796
11845
  }),
11797
11846
  [snapshot.texture]
11798
11847
  );
11799
- useEffect25(() => () => geometry.dispose(), [geometry]);
11800
- useEffect25(() => () => material.dispose(), [material]);
11801
- useEffect25(() => {
11848
+ useEffect26(() => () => geometry.dispose(), [geometry]);
11849
+ useEffect26(() => () => material.dispose(), [material]);
11850
+ useEffect26(() => {
11802
11851
  startRef.current = null;
11803
11852
  finishedRef.current = false;
11804
11853
  material.uniforms.progress.value = 0;
@@ -11820,7 +11869,7 @@ function ParticleOverlay({
11820
11869
  onFinish();
11821
11870
  }
11822
11871
  });
11823
- return /* @__PURE__ */ jsx18(
11872
+ return /* @__PURE__ */ jsx19(
11824
11873
  "mesh",
11825
11874
  {
11826
11875
  ref: meshRef,
@@ -11833,8 +11882,8 @@ function ParticleOverlay({
11833
11882
  }
11834
11883
 
11835
11884
  // src/scene-transitions/snapshot-overlay.tsx
11836
- import { useFrame as useFrame11, useThree as useThree17 } from "@react-three/fiber";
11837
- import { useEffect as useEffect26, useMemo as useMemo18, useRef as useRef27 } from "react";
11885
+ import { useFrame as useFrame11, useThree as useThree18 } from "@react-three/fiber";
11886
+ import { useEffect as useEffect27, useMemo as useMemo19, useRef as useRef27 } from "react";
11838
11887
  import { Vector3 as Vector311 } from "three";
11839
11888
 
11840
11889
  // src/scene-transitions/snapshot-shaders.ts
@@ -12114,7 +12163,7 @@ function createSnapshotMaterial(preset, texture) {
12114
12163
  }
12115
12164
 
12116
12165
  // src/scene-transitions/snapshot-overlay.tsx
12117
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
12166
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
12118
12167
  function SnapshotOverlay({
12119
12168
  snapshot,
12120
12169
  transition,
@@ -12122,19 +12171,19 @@ function SnapshotOverlay({
12122
12171
  runId,
12123
12172
  onFinish
12124
12173
  }) {
12125
- const { camera, gl } = useThree17();
12174
+ const { camera, gl } = useThree18();
12126
12175
  const meshRef = useRef27(null);
12127
12176
  const startRef = useRef27(null);
12128
12177
  const finishedRef = useRef27(false);
12129
12178
  const directionRef = useRef27(new Vector311());
12130
- const material = useMemo18(() => {
12179
+ const material = useMemo19(() => {
12131
12180
  if (!isSnapshotTransition(transition.preset)) {
12132
12181
  return null;
12133
12182
  }
12134
12183
  return createSnapshotMaterial(transition.preset, snapshot.texture);
12135
12184
  }, [snapshot.texture, transition.preset]);
12136
- useEffect26(() => () => material?.dispose(), [material]);
12137
- useEffect26(() => {
12185
+ useEffect27(() => () => material?.dispose(), [material]);
12186
+ useEffect27(() => {
12138
12187
  startRef.current = null;
12139
12188
  finishedRef.current = false;
12140
12189
  if (material) {
@@ -12164,20 +12213,20 @@ function SnapshotOverlay({
12164
12213
  return null;
12165
12214
  }
12166
12215
  return /* @__PURE__ */ jsxs12("mesh", { ref: meshRef, renderOrder: OVERLAY_RENDER_ORDER, frustumCulled: false, children: [
12167
- /* @__PURE__ */ jsx19("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
12168
- /* @__PURE__ */ jsx19("primitive", { attach: "material", object: material })
12216
+ /* @__PURE__ */ jsx20("primitive", { attach: "geometry", object: FULLSCREEN_PLANE }),
12217
+ /* @__PURE__ */ jsx20("primitive", { attach: "material", object: material })
12169
12218
  ] });
12170
12219
  }
12171
12220
 
12172
12221
  // src/scene-transitions/transition-overlay.tsx
12173
- import { jsx as jsx20 } from "react/jsx-runtime";
12222
+ import { jsx as jsx21 } from "react/jsx-runtime";
12174
12223
  function TransitionOverlay(props) {
12175
12224
  const { preset } = props.transition;
12176
12225
  switch (preset) {
12177
12226
  case "none":
12178
12227
  return null;
12179
12228
  case "particles":
12180
- return /* @__PURE__ */ jsx20(ParticleOverlay, { ...props });
12229
+ return /* @__PURE__ */ jsx21(ParticleOverlay, { ...props });
12181
12230
  case "crossfade":
12182
12231
  case "zoom":
12183
12232
  case "blackout":
@@ -12205,7 +12254,7 @@ function TransitionOverlay(props) {
12205
12254
  case "zoomBlur":
12206
12255
  case "hexDissolve":
12207
12256
  case "filmBurn":
12208
- return /* @__PURE__ */ jsx20(SnapshotOverlay, { ...props });
12257
+ return /* @__PURE__ */ jsx21(SnapshotOverlay, { ...props });
12209
12258
  default: {
12210
12259
  const exhaustive = preset;
12211
12260
  void exhaustive;
@@ -12214,44 +12263,6 @@ function TransitionOverlay(props) {
12214
12263
  }
12215
12264
  }
12216
12265
 
12217
- // src/tile/texture-manager-context.tsx
12218
- import { useThree as useThree18 } from "@react-three/fiber";
12219
- import {
12220
- createContext as createContext15,
12221
- useContext as useContext22,
12222
- useEffect as useEffect27,
12223
- useMemo as useMemo19
12224
- } from "react";
12225
- import { jsx as jsx21 } from "react/jsx-runtime";
12226
- var TileTextureManagerContext = createContext15(
12227
- null
12228
- );
12229
- function TileTextureManagerProvider({
12230
- children,
12231
- maxTextureMemoryMb = 128,
12232
- maxConcurrentLoads = 4
12233
- }) {
12234
- const gl = useThree18((state) => state.gl);
12235
- const anisotropy = gl.capabilities.getMaxAnisotropy();
12236
- const manager = useMemo19(
12237
- () => new TileTextureManager({
12238
- anisotropy,
12239
- maxBytes: Math.max(1, maxTextureMemoryMb) * 1024 * 1024,
12240
- concurrency: Math.max(1, Math.floor(maxConcurrentLoads)),
12241
- retryCount: 1
12242
- }),
12243
- [anisotropy, maxConcurrentLoads, maxTextureMemoryMb]
12244
- );
12245
- useEffect27(() => {
12246
- manager.resume();
12247
- return () => manager.dispose();
12248
- }, [manager]);
12249
- return /* @__PURE__ */ jsx21(TileTextureManagerContext.Provider, { value: manager, children });
12250
- }
12251
- function useSharedTileTextureManager() {
12252
- return useContext22(TileTextureManagerContext);
12253
- }
12254
-
12255
12266
  // src/tile/tile.tsx
12256
12267
  import { useFrame as useFrame12, useThree as useThree19 } from "@react-three/fiber";
12257
12268
  import { memo as memo2, useEffect as useEffect28, useMemo as useMemo20, useRef as useRef28, useState as useState13 } from "react";
@@ -12938,6 +12949,19 @@ function SceneSource({
12938
12949
  }
12939
12950
  );
12940
12951
  }
12952
+ case "sphereTile": {
12953
+ const { id: _id, type: _type, ...tileProps } = scene;
12954
+ return /* @__PURE__ */ jsx23(
12955
+ SphereTile,
12956
+ {
12957
+ ...tileProps,
12958
+ visible,
12959
+ loadMode: baseOnly ? "base" : "full",
12960
+ onReady,
12961
+ onPreviewError: onError
12962
+ }
12963
+ );
12964
+ }
12941
12965
  default: {
12942
12966
  const exhaustive = scene;
12943
12967
  void exhaustive;
@@ -13004,6 +13028,7 @@ function ScenesController({
13004
13028
  activeSceneId,
13005
13029
  transition: transitionInput,
13006
13030
  renderHotspots,
13031
+ onSceneReady,
13007
13032
  onTransitionEnd,
13008
13033
  onTransitionError,
13009
13034
  snapshotMaxPixels = 3686400,
@@ -13039,6 +13064,7 @@ function ScenesController({
13039
13064
  const phaseRef = useRef29(phase);
13040
13065
  const liveSlotRef = useRef29(liveSlot);
13041
13066
  const transitionFromRef = useRef29(initialScene);
13067
+ const readySceneRef = useRef29(null);
13042
13068
  slotsRef.current = slots;
13043
13069
  phaseRef.current = phase;
13044
13070
  if (externalPhaseRef) {
@@ -13079,6 +13105,7 @@ function ScenesController({
13079
13105
  return;
13080
13106
  }
13081
13107
  if (phaseRef.current === "transitioning" || phaseRef.current === "capturing") {
13108
+ if (requestedSceneIdRef.current === requested.id) return;
13082
13109
  requestedSceneIdRef.current = requested.id;
13083
13110
  captureReasonRef.current = "interrupt";
13084
13111
  setPhase("recapturing");
@@ -13087,7 +13114,16 @@ function ScenesController({
13087
13114
  }
13088
13115
  startPreload(requested);
13089
13116
  }, [activeSceneId, pendingSlot, reportError, sceneMap, startPreload]);
13117
+ const notifySceneReady = useCallback11((scene) => {
13118
+ if (!scene || readySceneRef.current === scene) return;
13119
+ readySceneRef.current = scene;
13120
+ onSceneReady?.(scene.id);
13121
+ }, [onSceneReady]);
13090
13122
  const handleReady = useCallback11((slot) => {
13123
+ if (slot === liveSlotRef.current && pendingSlot === null && phaseRef.current === "idle") {
13124
+ notifySceneReady(slotsRef.current[slot]);
13125
+ return;
13126
+ }
13091
13127
  if (slot !== pendingSlot || phaseRef.current !== "preloading") {
13092
13128
  return;
13093
13129
  }
@@ -13103,6 +13139,7 @@ function ScenesController({
13103
13139
  setActiveSlot(slot);
13104
13140
  setPendingSlot(null);
13105
13141
  setPhase("idle");
13142
+ notifySceneReady(nextScene);
13106
13143
  if (previousScene && nextScene) {
13107
13144
  onTransitionEnd?.({
13108
13145
  previousSceneId: previousScene.id,
@@ -13117,7 +13154,7 @@ function ScenesController({
13117
13154
  captureReasonRef.current = "start";
13118
13155
  setPhase("capturing");
13119
13156
  setCaptureRequestId((current) => current + 1);
13120
- }, [controlsRef, onTransitionEnd, pendingSlot, transition.preset]);
13157
+ }, [controlsRef, notifySceneReady, onTransitionEnd, pendingSlot, transition.preset]);
13121
13158
  const handleCaptured = useCallback11((nextSnapshot) => {
13122
13159
  setSnapshot((current) => {
13123
13160
  current?.dispose();
@@ -13135,11 +13172,12 @@ function ScenesController({
13135
13172
  });
13136
13173
  manager?.releaseUnused();
13137
13174
  if (pendingSlot !== null) {
13175
+ notifySceneReady(slotsRef.current[pendingSlot]);
13138
13176
  setLiveSlot(pendingSlot);
13139
13177
  }
13140
13178
  setPhase("transitioning");
13141
13179
  setTransitionRunId((current) => current + 1);
13142
- }, [manager, pendingSlot, sceneMap, startPreload]);
13180
+ }, [manager, notifySceneReady, pendingSlot, sceneMap, startPreload]);
13143
13181
  const finishTransition = useCallback11(() => {
13144
13182
  const nextSlot = pendingSlot;
13145
13183
  const previousScene = transitionFromRef.current;
@@ -13182,6 +13220,7 @@ function ScenesController({
13182
13220
  setPhase("idle");
13183
13221
  };
13184
13222
  const onContextRestored = () => {
13223
+ readySceneRef.current = null;
13185
13224
  manager?.resume();
13186
13225
  setRecoveryKey((current) => current + 1);
13187
13226
  };