@ericchen1990/pano-view 3.9.0 → 3.10.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
@@ -1284,8 +1284,8 @@ var require_howler = __commonJS({
1284
1284
  break;
1285
1285
  }
1286
1286
  }
1287
- if (cache && remCache) {
1288
- delete cache[self._src];
1287
+ if (cache2 && remCache) {
1288
+ delete cache2[self._src];
1289
1289
  }
1290
1290
  Howler4.noAudio = false;
1291
1291
  self._state = "unloaded";
@@ -1540,7 +1540,7 @@ var require_howler = __commonJS({
1540
1540
  _refreshBuffer: function(sound) {
1541
1541
  var self = this;
1542
1542
  sound._node.bufferSource = Howler4.ctx.createBufferSource();
1543
- sound._node.bufferSource.buffer = cache[self._src];
1543
+ sound._node.bufferSource.buffer = cache2[self._src];
1544
1544
  if (sound._panner) {
1545
1545
  sound._node.bufferSource.connect(sound._panner);
1546
1546
  } else {
@@ -1702,11 +1702,11 @@ var require_howler = __commonJS({
1702
1702
  self._node.removeEventListener("ended", self._endFn, false);
1703
1703
  }
1704
1704
  };
1705
- var cache = {};
1705
+ var cache2 = {};
1706
1706
  var loadBuffer = function(self) {
1707
1707
  var url = self._src;
1708
- if (cache[url]) {
1709
- self._duration = cache[url].duration;
1708
+ if (cache2[url]) {
1709
+ self._duration = cache2[url].duration;
1710
1710
  loadSound(self);
1711
1711
  return;
1712
1712
  }
@@ -1740,7 +1740,7 @@ var require_howler = __commonJS({
1740
1740
  self._html5 = true;
1741
1741
  self._webAudio = false;
1742
1742
  self._sounds = [];
1743
- delete cache[url];
1743
+ delete cache2[url];
1744
1744
  self.load();
1745
1745
  }
1746
1746
  };
@@ -1760,7 +1760,7 @@ var require_howler = __commonJS({
1760
1760
  };
1761
1761
  var success = function(buffer) {
1762
1762
  if (buffer && self._sounds.length > 0) {
1763
- cache[self._src] = buffer;
1763
+ cache2[self._src] = buffer;
1764
1764
  loadSound(self, buffer);
1765
1765
  } else {
1766
1766
  error();
@@ -2308,12 +2308,12 @@ import { Canvas } from "@react-three/fiber";
2308
2308
  import { createXRStore, XR } from "@react-three/xr";
2309
2309
  import {
2310
2310
  forwardRef as forwardRef2,
2311
- useCallback as useCallback6,
2311
+ useCallback as useCallback7,
2312
2312
  useContext as useContext16,
2313
2313
  useEffect as useEffect14,
2314
2314
  useImperativeHandle as useImperativeHandle2,
2315
2315
  useMemo as useMemo8,
2316
- useRef as useRef13,
2316
+ useRef as useRef14,
2317
2317
  useState as useState7,
2318
2318
  useSyncExternalStore as useSyncExternalStore8
2319
2319
  } from "react";
@@ -3875,6 +3875,18 @@ import { useSyncExternalStore } from "react";
3875
3875
  // src/webvr/host.ts
3876
3876
  import { createContext as createContext5 } from "react";
3877
3877
  var WebVRRuntimeContext = createContext5(null);
3878
+ var EMPTY_WEBVR_STATE = Object.freeze({
3879
+ available: false,
3880
+ enabled: false,
3881
+ mode: null
3882
+ });
3883
+ function createWebVRState(available, mode) {
3884
+ return Object.freeze({
3885
+ available,
3886
+ enabled: mode !== null,
3887
+ mode
3888
+ });
3889
+ }
3878
3890
  function createWebVRHost() {
3879
3891
  return {
3880
3892
  controller: null,
@@ -3884,18 +3896,28 @@ function createWebVRHost() {
3884
3896
  chrome: true,
3885
3897
  mode: null,
3886
3898
  setupOpen: false,
3887
- settings: { screensize: "auto", profileId: "cardboard-v1" }
3899
+ settings: { screensize: "auto", profileId: "cardboard-v1" },
3900
+ detectedScreensize: null
3888
3901
  },
3902
+ state: EMPTY_WEBVR_STATE,
3889
3903
  listeners: /* @__PURE__ */ new Set()
3890
3904
  };
3891
3905
  }
3892
3906
  function updateWebVRHost(host, snapshot) {
3893
- host.snapshot = { ...host.snapshot, ...snapshot };
3907
+ const previous = host.snapshot;
3908
+ const next = { ...previous, ...snapshot };
3909
+ host.snapshot = next;
3894
3910
  host.revision += 1;
3911
+ if (previous.available !== next.available || previous.mode !== next.mode) {
3912
+ host.state = createWebVRState(next.available, next.mode);
3913
+ }
3895
3914
  for (const listener of host.listeners) {
3896
3915
  listener();
3897
3916
  }
3898
3917
  }
3918
+ function getWebVRState(host) {
3919
+ return host.state;
3920
+ }
3899
3921
  function subscribeWebVRHost(host, listener) {
3900
3922
  host.listeners.add(listener);
3901
3923
  return () => {
@@ -6151,6 +6173,52 @@ function createPanoModalHost() {
6151
6173
  };
6152
6174
  }
6153
6175
 
6176
+ // src/overlay/partition.ts
6177
+ import {
6178
+ Children,
6179
+ Fragment,
6180
+ isValidElement
6181
+ } from "react";
6182
+ var PANO_OVERLAY_TYPE = /* @__PURE__ */ Symbol.for("pano-view.PanoOverlay");
6183
+ function isPanoOverlayType(type) {
6184
+ return typeof type === "function" && type[PANO_OVERLAY_TYPE] === true;
6185
+ }
6186
+ function isPanoOverlayElement(node) {
6187
+ return isValidElement(node) && isPanoOverlayType(node.type);
6188
+ }
6189
+ function isFragmentElement(node) {
6190
+ return isValidElement(node) && node.type === Fragment;
6191
+ }
6192
+ function flattenViewerChildren(children) {
6193
+ const flat = [];
6194
+ for (const item of Children.toArray(children)) {
6195
+ if (isFragmentElement(item)) {
6196
+ flat.push(...flattenViewerChildren(item.props.children));
6197
+ } else {
6198
+ flat.push(item);
6199
+ }
6200
+ }
6201
+ return flat;
6202
+ }
6203
+ function partitionPanoOverlay(children) {
6204
+ const items = flattenViewerChildren(children);
6205
+ const overlayElements = [];
6206
+ const sceneItems = [];
6207
+ let found = false;
6208
+ for (const item of items) {
6209
+ if (isPanoOverlayElement(item)) {
6210
+ found = true;
6211
+ overlayElements.push(item);
6212
+ } else {
6213
+ sceneItems.push(item);
6214
+ }
6215
+ }
6216
+ if (!found) {
6217
+ return { sceneChildren: children, overlayElements: [] };
6218
+ }
6219
+ return { sceneChildren: sceneItems, overlayElements };
6220
+ }
6221
+
6154
6222
  // src/overlay/pano-overlay.tsx
6155
6223
  import {
6156
6224
  createContext as createContext12,
@@ -6170,17 +6238,33 @@ function getOverlayVisibleRect(rect, viewport) {
6170
6238
  }
6171
6239
 
6172
6240
  // src/overlay/pano-overlay.tsx
6173
- import { jsx as jsx5 } from "react/jsx-runtime";
6241
+ import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
6174
6242
  var PanoOverlayContext = createContext12(null);
6175
6243
  var PanoModalHostContext = createContext12(null);
6244
+ var PanoOverlayOutletContext = createContext12(false);
6245
+ function PanoOverlay({ children }) {
6246
+ const inOutlet = useContext11(PanoOverlayOutletContext);
6247
+ if (!inOutlet) {
6248
+ throw new Error(
6249
+ "PanoOverlay must be a direct child of PanoViewer (or of a Fragment under PanoViewer), not nested inside scene components."
6250
+ );
6251
+ }
6252
+ return /* @__PURE__ */ jsx5(Fragment2, { children });
6253
+ }
6254
+ PanoOverlay[PANO_OVERLAY_TYPE] = true;
6255
+ var OVERLAY_HOOK_SCOPE = "PanoOverlay or PanoViewer.renderOverlay";
6176
6256
  function usePanoOverlay() {
6177
6257
  const context = useContext11(PanoOverlayContext);
6178
- if (!context) throw new Error("usePanoOverlay must be used inside PanoViewer.renderOverlay.");
6258
+ if (!context) {
6259
+ throw new Error(`usePanoOverlay must be used inside ${OVERLAY_HOOK_SCOPE}.`);
6260
+ }
6179
6261
  return context;
6180
6262
  }
6181
6263
  function usePanoModal(open) {
6182
6264
  const host = useContext11(PanoModalHostContext);
6183
- if (!host) throw new Error("usePanoModal must be used inside PanoViewer.renderOverlay.");
6265
+ if (!host) {
6266
+ throw new Error(`usePanoModal must be used inside ${OVERLAY_HOOK_SCOPE}.`);
6267
+ }
6184
6268
  useLayoutEffect3(() => open ? host.acquire() : void 0, [host, open]);
6185
6269
  }
6186
6270
  function PanoOverlayPortalProvider({
@@ -6931,7 +7015,7 @@ function cueText(cue) {
6931
7015
  }
6932
7016
 
6933
7017
  // src/video/pano-video-controls.tsx
6934
- import { Fragment, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
7018
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
6935
7019
  var DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE = {
6936
7020
  background: "rgba(16, 16, 16, 0.88)",
6937
7021
  color: "#f4f4f4",
@@ -7165,7 +7249,7 @@ function CaptionsMenu({
7165
7249
  id,
7166
7250
  labelledBy,
7167
7251
  role: "menu",
7168
- children: panel === "tracks" ? /* @__PURE__ */ jsxs4(Fragment, { children: [
7252
+ children: panel === "tracks" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
7169
7253
  /* @__PURE__ */ jsxs4("div", { className: "pano-video-chrome-menu-list", children: [
7170
7254
  /* @__PURE__ */ jsx9(
7171
7255
  ChromeMenuRadioItem,
@@ -7204,7 +7288,7 @@ function CaptionsMenu({
7204
7288
  secondary: /* @__PURE__ */ jsx9("span", { "aria-hidden": true, children: "\u203A" })
7205
7289
  }
7206
7290
  )
7207
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
7291
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
7208
7292
  /* @__PURE__ */ jsx9(
7209
7293
  ChromeMenuActionItem,
7210
7294
  {
@@ -7870,7 +7954,7 @@ function PanoVideoControlsHud({
7870
7954
  children: /* @__PURE__ */ jsx9(Icon, { path: "M3 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M7 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M11 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0" })
7871
7955
  }
7872
7956
  )
7873
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
7957
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
7874
7958
  rateButton,
7875
7959
  variantButton,
7876
7960
  captionsButton
@@ -7900,7 +7984,7 @@ function PanoVideoControls({ appearance }) {
7900
7984
  }
7901
7985
 
7902
7986
  // src/video/pano-video-chrome-bridge.tsx
7903
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
7987
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
7904
7988
  function PanoVideoChromeBridge({
7905
7989
  fullscreen,
7906
7990
  host,
@@ -7915,7 +7999,7 @@ function PanoVideoChromeBridge({
7915
7999
  return null;
7916
8000
  }
7917
8001
  const showDefaultControls = host.controller !== null && host.controls !== false && host.controlClaims === 0;
7918
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
8002
+ return /* @__PURE__ */ jsxs5(Fragment4, { children: [
7919
8003
  showDefaultControls ? /* @__PURE__ */ jsx10(
7920
8004
  PanoVideoControlsHud,
7921
8005
  {
@@ -7930,7 +8014,13 @@ function PanoVideoChromeBridge({
7930
8014
  }
7931
8015
 
7932
8016
  // src/webvr/chrome.tsx
7933
- import { useEffect as useEffect13, useState as useState6, useSyncExternalStore as useSyncExternalStore7 } from "react";
8017
+ import {
8018
+ useCallback as useCallback6,
8019
+ useEffect as useEffect13,
8020
+ useRef as useRef12,
8021
+ useState as useState6,
8022
+ useSyncExternalStore as useSyncExternalStore7
8023
+ } from "react";
7934
8024
 
7935
8025
  // src/webvr/appearance.ts
7936
8026
  var DEFAULT_WEBVR_CHROME_APPEARANCE = {
@@ -7996,6 +8086,7 @@ var WEBVR_PROFILES = {
7996
8086
  fov: 80,
7997
8087
  ipdMm: 60,
7998
8088
  screenToLensMm: 42,
8089
+ trayDistanceMm: 0,
7999
8090
  k1: 0,
8000
8091
  k2: 0
8001
8092
  },
@@ -8005,6 +8096,7 @@ var WEBVR_PROFILES = {
8005
8096
  fov: 80,
8006
8097
  ipdMm: 60,
8007
8098
  screenToLensMm: 42,
8099
+ trayDistanceMm: 35,
8008
8100
  k1: 0.441,
8009
8101
  k2: 0.156
8010
8102
  },
@@ -8014,6 +8106,7 @@ var WEBVR_PROFILES = {
8014
8106
  fov: 90,
8015
8107
  ipdMm: 64,
8016
8108
  screenToLensMm: 39,
8109
+ trayDistanceMm: 0,
8017
8110
  k1: 0.336,
8018
8111
  k2: 0.553
8019
8112
  },
@@ -8023,6 +8116,7 @@ var WEBVR_PROFILES = {
8023
8116
  fov: 96,
8024
8117
  ipdMm: 63.5,
8025
8118
  screenToLensMm: 50,
8119
+ trayDistanceMm: 0,
8026
8120
  k1: 0.215,
8027
8121
  k2: 0.215
8028
8122
  },
@@ -8032,6 +8126,7 @@ var WEBVR_PROFILES = {
8032
8126
  fov: 90,
8033
8127
  ipdMm: 61.5,
8034
8128
  screenToLensMm: 44,
8129
+ trayDistanceMm: 0,
8035
8130
  k1: 0.385,
8036
8131
  k2: 0.593
8037
8132
  }
@@ -8082,12 +8177,28 @@ function saveWebVRSettings(settings) {
8082
8177
  } catch {
8083
8178
  }
8084
8179
  }
8180
+ function clearWebVRSettings() {
8181
+ if (typeof localStorage === "undefined") {
8182
+ return;
8183
+ }
8184
+ try {
8185
+ localStorage.removeItem(STORAGE_KEY);
8186
+ } catch {
8187
+ }
8188
+ }
8189
+ function resolveInitialWebVRSettings(screensize, profile) {
8190
+ return {
8191
+ screensize: screensize ?? DEFAULT_WEBVR_SETTINGS.screensize,
8192
+ profileId: typeof profile === "string" ? profile : DEFAULT_WEBVR_SETTINGS.profileId
8193
+ };
8194
+ }
8085
8195
  function clamp2(value, min, max) {
8086
8196
  return Math.max(min, Math.min(max, Number.isFinite(value) ? value : min));
8087
8197
  }
8088
8198
 
8089
8199
  // src/webvr/chrome.tsx
8090
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
8200
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
8201
+ var SESSION_CHROME_IDLE_MS = 3e3;
8091
8202
  function resolveAppearance(value) {
8092
8203
  return {
8093
8204
  ...DEFAULT_WEBVR_CHROME_APPEARANCE,
@@ -8115,8 +8226,15 @@ function ChromeButton2({
8115
8226
  }
8116
8227
  );
8117
8228
  }
8229
+ function formatScreensizeLabel(settings, detectedScreensize) {
8230
+ if (settings.screensize === "auto") {
8231
+ return detectedScreensize === null ? "Auto" : `Auto \xB7 ${detectedScreensize.toFixed(1)}`;
8232
+ }
8233
+ return settings.screensize.toFixed(1);
8234
+ }
8118
8235
  function SetupPanel({
8119
8236
  appearance,
8237
+ detectedScreensize,
8120
8238
  host,
8121
8239
  settings
8122
8240
  }) {
@@ -8160,7 +8278,7 @@ function SetupPanel({
8160
8278
  children: "\u2039"
8161
8279
  }
8162
8280
  ),
8163
- /* @__PURE__ */ jsx11("strong", { className: "pano-webvr-setup-value", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
8281
+ /* @__PURE__ */ jsx11("strong", { className: "pano-webvr-setup-value", children: formatScreensizeLabel(settings, detectedScreensize) }),
8164
8282
  /* @__PURE__ */ jsx11(
8165
8283
  "button",
8166
8284
  {
@@ -8201,42 +8319,61 @@ function SetupPanel({
8201
8319
  )
8202
8320
  ] })
8203
8321
  ] }),
8322
+ /* @__PURE__ */ jsxs6("div", { className: "pano-webvr-setup-row", children: [
8323
+ /* @__PURE__ */ jsx11(
8324
+ ChromeButton2,
8325
+ {
8326
+ appearance,
8327
+ onClick: () => controller?.resetSettings(),
8328
+ children: "Reset"
8329
+ }
8330
+ ),
8331
+ /* @__PURE__ */ jsx11(
8332
+ ChromeButton2,
8333
+ {
8334
+ appearance,
8335
+ onClick: () => controller?.closeSetup(),
8336
+ children: "Close"
8337
+ }
8338
+ )
8339
+ ] })
8340
+ ]
8341
+ }
8342
+ );
8343
+ }
8344
+ function SessionActions({
8345
+ appearance,
8346
+ host,
8347
+ mode,
8348
+ visible
8349
+ }) {
8350
+ return /* @__PURE__ */ jsxs6(
8351
+ "div",
8352
+ {
8353
+ "aria-hidden": !visible,
8354
+ className: visible ? "pano-webvr-toolbar-actions" : "pano-webvr-toolbar-actions pano-webvr-toolbar-actions-hidden",
8355
+ inert: !visible ? true : void 0,
8356
+ children: [
8357
+ mode !== "webxr" ? /* @__PURE__ */ jsx11(
8358
+ ChromeButton2,
8359
+ {
8360
+ appearance,
8361
+ onClick: () => host.controller?.openSetup(),
8362
+ children: "VR Setup"
8363
+ }
8364
+ ) : null,
8204
8365
  /* @__PURE__ */ jsx11(
8205
8366
  ChromeButton2,
8206
8367
  {
8207
8368
  appearance,
8208
- onClick: () => controller?.closeSetup(),
8209
- children: "Close"
8369
+ onClick: () => void host.controller?.exitVR(),
8370
+ children: "Exit VR"
8210
8371
  }
8211
8372
  )
8212
8373
  ]
8213
8374
  }
8214
8375
  );
8215
8376
  }
8216
- function SessionActions({
8217
- appearance,
8218
- host,
8219
- mode
8220
- }) {
8221
- return /* @__PURE__ */ jsxs6("div", { className: "pano-webvr-toolbar-actions", children: [
8222
- mode !== "webxr" ? /* @__PURE__ */ jsx11(
8223
- ChromeButton2,
8224
- {
8225
- appearance,
8226
- onClick: () => host.controller?.openSetup(),
8227
- children: "VR Setup"
8228
- }
8229
- ) : null,
8230
- /* @__PURE__ */ jsx11(
8231
- ChromeButton2,
8232
- {
8233
- appearance,
8234
- onClick: () => void host.controller?.exitVR(),
8235
- children: "Exit VR"
8236
- }
8237
- )
8238
- ] });
8239
- }
8240
8377
  function RotatePhoneIcon2() {
8241
8378
  return /* @__PURE__ */ jsxs6(
8242
8379
  "svg",
@@ -8267,6 +8404,51 @@ function PortraitPrompt() {
8267
8404
  /* @__PURE__ */ jsx11("p", { className: "pano-webvr-portrait-text", children: "Rotate your phone to landscape" })
8268
8405
  ] });
8269
8406
  }
8407
+ function useSessionChromeVisibility(active, keepVisible, viewer) {
8408
+ const [visible, setVisible] = useState6(true);
8409
+ const idleTimerRef = useRef12(0);
8410
+ const bump = useCallback6(() => {
8411
+ setVisible(true);
8412
+ window.clearTimeout(idleTimerRef.current);
8413
+ if (!active || keepVisible) {
8414
+ return;
8415
+ }
8416
+ idleTimerRef.current = window.setTimeout(() => {
8417
+ setVisible(false);
8418
+ }, SESSION_CHROME_IDLE_MS);
8419
+ }, [active, keepVisible]);
8420
+ useEffect13(() => {
8421
+ if (!active) {
8422
+ window.clearTimeout(idleTimerRef.current);
8423
+ setVisible(true);
8424
+ return;
8425
+ }
8426
+ bump();
8427
+ }, [active, bump, keepVisible]);
8428
+ useEffect13(() => {
8429
+ if (!active || !viewer) {
8430
+ return;
8431
+ }
8432
+ const reveal = () => {
8433
+ bump();
8434
+ };
8435
+ viewer.addEventListener("pointerdown", reveal);
8436
+ viewer.addEventListener("wheel", reveal, { passive: true });
8437
+ viewer.addEventListener("keydown", reveal);
8438
+ return () => {
8439
+ viewer.removeEventListener("pointerdown", reveal);
8440
+ viewer.removeEventListener("wheel", reveal);
8441
+ viewer.removeEventListener("keydown", reveal);
8442
+ };
8443
+ }, [active, bump, viewer]);
8444
+ useEffect13(
8445
+ () => () => {
8446
+ window.clearTimeout(idleTimerRef.current);
8447
+ },
8448
+ []
8449
+ );
8450
+ return keepVisible || visible;
8451
+ }
8270
8452
  function WebVRChromeBridge({ host }) {
8271
8453
  useSyncExternalStore7(
8272
8454
  (listener) => subscribeWebVRHost(host, listener),
@@ -8274,11 +8456,29 @@ function WebVRChromeBridge({ host }) {
8274
8456
  () => host.revision
8275
8457
  );
8276
8458
  const portrait = usePortraitOrientation();
8277
- const { available, chrome, mode, settings, setupOpen } = host.snapshot;
8459
+ const {
8460
+ chrome,
8461
+ detectedScreensize,
8462
+ mode,
8463
+ settings,
8464
+ setupOpen
8465
+ } = host.snapshot;
8278
8466
  const [mounted, setMounted] = useState6(false);
8467
+ const [viewer, setViewer] = useState6(null);
8468
+ const toolbarRef = useRef12(null);
8279
8469
  useEffect13(() => {
8280
8470
  setMounted(true);
8281
8471
  }, []);
8472
+ useEffect13(() => {
8473
+ const node = toolbarRef.current?.closest("[data-pano-viewer]") ?? null;
8474
+ setViewer(node);
8475
+ }, [mode]);
8476
+ const sessionActive = mode !== null;
8477
+ const chromeVisible = useSessionChromeVisibility(
8478
+ sessionActive,
8479
+ setupOpen,
8480
+ viewer
8481
+ );
8282
8482
  if (chrome === false || !host.controller) {
8283
8483
  return null;
8284
8484
  }
@@ -8287,17 +8487,29 @@ function WebVRChromeBridge({ host }) {
8287
8487
  if (showPortraitPrompt) {
8288
8488
  return /* @__PURE__ */ jsx11(PortraitPrompt, {});
8289
8489
  }
8290
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
8291
- /* @__PURE__ */ jsx11("div", { className: "pano-webvr-toolbar", children: mode ? /* @__PURE__ */ jsx11(SessionActions, { appearance, host, mode }) : /* @__PURE__ */ jsx11(
8292
- ChromeButton2,
8490
+ if (!mode) {
8491
+ return null;
8492
+ }
8493
+ return /* @__PURE__ */ jsxs6(Fragment5, { children: [
8494
+ /* @__PURE__ */ jsx11("div", { className: "pano-webvr-toolbar", ref: toolbarRef, children: /* @__PURE__ */ jsx11(
8495
+ SessionActions,
8293
8496
  {
8294
8497
  appearance,
8295
- onClick: () => void host.controller?.enterVR(),
8296
- children: available ? "Enter VR" : "VR unavailable"
8498
+ host,
8499
+ mode,
8500
+ visible: chromeVisible
8297
8501
  }
8298
8502
  ) }),
8299
- mode === "fake" ? /* @__PURE__ */ jsx11("p", { className: "pano-webvr-hint", style: { color: appearance.color }, children: "Simulated VR Mode \xB7 Click the view to lock the mouse, then move to look around. Use a headset or phone for real VR." }) : null,
8300
- setupOpen ? /* @__PURE__ */ jsx11(SetupPanel, { appearance, host, settings }) : null
8503
+ mode === "fake" && chromeVisible ? /* @__PURE__ */ jsx11("p", { className: "pano-webvr-hint", style: { color: appearance.color }, children: "Simulated VR Mode \xB7 Click the view to lock the mouse, then move to look around. Use a headset or phone for real VR." }) : null,
8504
+ setupOpen ? /* @__PURE__ */ jsx11(
8505
+ SetupPanel,
8506
+ {
8507
+ appearance,
8508
+ detectedScreensize,
8509
+ host,
8510
+ settings
8511
+ }
8512
+ ) : null
8301
8513
  ] });
8302
8514
  }
8303
8515
 
@@ -8330,12 +8542,12 @@ function projectStereoHtmlPosition(el, camera, size, eye) {
8330
8542
  // src/webvr/xr-origin.tsx
8331
8543
  import { useFrame as useFrame7, useThree as useThree8 } from "@react-three/fiber";
8332
8544
  import { useXR as useXR2, XROrigin } from "@react-three/xr";
8333
- import { useRef as useRef12 } from "react";
8545
+ import { useRef as useRef13 } from "react";
8334
8546
  import { Vector3 as Vector36 } from "three";
8335
8547
  import { jsx as jsx12 } from "react/jsx-runtime";
8336
8548
  function PanoramaXROrigin() {
8337
- const originRef = useRef12(null);
8338
- const localHeadPositionRef = useRef12(new Vector36());
8549
+ const originRef = useRef13(null);
8550
+ const localHeadPositionRef = useRef13(new Vector36());
8339
8551
  const session2 = useXR2((state) => state.session);
8340
8552
  const gl = useThree8((state) => state.gl);
8341
8553
  useFrame7(() => {
@@ -8450,11 +8662,11 @@ var PanoViewer = forwardRef2(
8450
8662
  "aria-label": ariaLabel = "Panorama viewer",
8451
8663
  ...divProps
8452
8664
  }, ref) {
8453
- const rootRef = useRef13(null);
8454
- const controlsRef = useRef13(null);
8665
+ const rootRef = useRef14(null);
8666
+ const controlsRef = useRef14(null);
8455
8667
  const modalHost = useMemo8(() => createPanoModalHost(), []);
8456
8668
  const modalOpen = useSyncExternalStore8(modalHost.subscribe, modalHost.getSnapshot, () => false);
8457
- const setControlsRuntime = useCallback6((runtime) => {
8669
+ const setControlsRuntime = useCallback7((runtime) => {
8458
8670
  controlsRef.current = runtime;
8459
8671
  modalHost.connect(runtime);
8460
8672
  }, [modalHost]);
@@ -8514,12 +8726,12 @@ var PanoViewer = forwardRef2(
8514
8726
  }),
8515
8727
  [bgmHost, videoHost]
8516
8728
  );
8517
- const runMediaActivation = useCallback6(() => {
8729
+ const runMediaActivation = useCallback7(() => {
8518
8730
  const resume = mediaActivationControls.resumeAudio();
8519
8731
  mediaActivationOptions?.onActivate?.(mediaActivationControls);
8520
8732
  return resume;
8521
8733
  }, [mediaActivationControls, mediaActivationOptions]);
8522
- const activateMedia = useCallback6(() => {
8734
+ const activateMedia = useCallback7(() => {
8523
8735
  setMediaActivated(true);
8524
8736
  return runMediaActivation();
8525
8737
  }, [runMediaActivation]);
@@ -8601,7 +8813,7 @@ var PanoViewer = forwardRef2(
8601
8813
  ]);
8602
8814
  const limitsKey = JSON.stringify(viewLimits);
8603
8815
  const parsedLimits = useMemo8(() => parseViewLimits(viewLimits), [limitsKey]);
8604
- const fallbackViewRef = useRef13(DEFAULT_VIEW);
8816
+ const fallbackViewRef = useRef14(DEFAULT_VIEW);
8605
8817
  const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
8606
8818
  const normalizedMaxFov = Math.min(
8607
8819
  179,
@@ -8664,7 +8876,7 @@ var PanoViewer = forwardRef2(
8664
8876
  }),
8665
8877
  [controlOptions.keyboard]
8666
8878
  );
8667
- const enterFullscreen = useCallback6(async () => {
8879
+ const enterFullscreen = useCallback7(async () => {
8668
8880
  if (typeof document === "undefined") {
8669
8881
  return;
8670
8882
  }
@@ -8683,7 +8895,7 @@ var PanoViewer = forwardRef2(
8683
8895
  acquireIphoneLandscapeFullscreen(root, "viewer");
8684
8896
  }
8685
8897
  }, []);
8686
- const exitFullscreen = useCallback6(async () => {
8898
+ const exitFullscreen = useCallback7(async () => {
8687
8899
  if (typeof document === "undefined") {
8688
8900
  return;
8689
8901
  }
@@ -8695,7 +8907,7 @@ var PanoViewer = forwardRef2(
8695
8907
  await exitElementFullscreen();
8696
8908
  }
8697
8909
  }, []);
8698
- const toggleFullscreen = useCallback6(async () => {
8910
+ const toggleFullscreen = useCallback7(async () => {
8699
8911
  if (typeof document === "undefined") {
8700
8912
  return;
8701
8913
  }
@@ -8711,6 +8923,10 @@ var PanoViewer = forwardRef2(
8711
8923
  }, [enterFullscreen, exitFullscreen]);
8712
8924
  const [isViewerFullscreen, setIsViewerFullscreen] = useState7(false);
8713
8925
  const { context: overlayContext, setPortalContainer } = usePanoOverlayHost(isViewerFullscreen);
8926
+ const { sceneChildren, overlayElements } = useMemo8(
8927
+ () => partitionPanoOverlay(children),
8928
+ [children]
8929
+ );
8714
8930
  const iphoneFullscreenAllowsScroll = useSyncExternalStore8(
8715
8931
  subscribeIphoneLandscapeFullscreen,
8716
8932
  () => getIphoneLandscapeFullscreenSnapshot().allowsDocumentScroll,
@@ -8794,7 +9010,7 @@ var PanoViewer = forwardRef2(
8794
9010
  useEffect14(() => {
8795
9011
  setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
8796
9012
  }, [legacyAutoRotateOptions.autoRotate]);
8797
- const isFullscreenRef = useRef13(false);
9013
+ const isFullscreenRef = useRef14(false);
8798
9014
  isFullscreenRef.current = isViewerFullscreen;
8799
9015
  useImperativeHandle2(
8800
9016
  ref,
@@ -8834,6 +9050,8 @@ var PanoViewer = forwardRef2(
8834
9050
  isVRAvailable: () => webVRHost.snapshot.available,
8835
9051
  isVREnabled: () => webVRHost.snapshot.mode !== null,
8836
9052
  getVRMode: () => webVRHost.snapshot.mode,
9053
+ getVRState: () => webVRHost.state,
9054
+ subscribeVR: (onStoreChange) => subscribeWebVRHost(webVRHost, onStoreChange),
8837
9055
  requestVRPermission: () => webVRHost.controller?.requestPermission() ?? Promise.resolve(false),
8838
9056
  // -- Video --
8839
9057
  getVideo: () => videoHost.controller,
@@ -8966,7 +9184,7 @@ var PanoViewer = forwardRef2(
8966
9184
  /* @__PURE__ */ jsx13(PanoramaEventSurface, {}),
8967
9185
  contextMenuChannel.mount ? /* @__PURE__ */ jsx13(PanoContextMenu, { ...contextMenuChannel.props }) : null,
8968
9186
  /* @__PURE__ */ jsxs7(ControlClaimsContext.Provider, { value: controlClaims, children: [
8969
- children,
9187
+ sceneChildren,
8970
9188
  /* @__PURE__ */ jsx13(
8971
9189
  DefaultControlChannels,
8972
9190
  {
@@ -9011,15 +9229,18 @@ var PanoViewer = forwardRef2(
9011
9229
  contextMenuOverlay,
9012
9230
  hotspotAccessibilityControls
9013
9231
  ] }),
9014
- /* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
9232
+ /* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(PanoOverlayOutletContext.Provider, { value: true, children: /* @__PURE__ */ jsxs7(
9015
9233
  "div",
9016
9234
  {
9017
9235
  "data-pano-overlay": "",
9018
9236
  ref: setPortalContainer,
9019
9237
  style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
9020
- children: renderOverlay?.(overlayContext)
9238
+ children: [
9239
+ renderOverlay?.(overlayContext),
9240
+ overlayElements
9241
+ ]
9021
9242
  }
9022
- ) }) }) })
9243
+ ) }) }) }) })
9023
9244
  ]
9024
9245
  }
9025
9246
  );
@@ -9121,17 +9342,17 @@ import {
9121
9342
  // src/scenes.tsx
9122
9343
  import { useFrame as useFrame13, useThree as useThree16 } from "@react-three/fiber";
9123
9344
  import {
9124
- Fragment as Fragment4,
9125
- useCallback as useCallback8,
9345
+ Fragment as Fragment6,
9346
+ useCallback as useCallback9,
9126
9347
  useContext as useContext19,
9127
9348
  useEffect as useEffect23,
9128
9349
  useMemo as useMemo17,
9129
- useRef as useRef22,
9350
+ useRef as useRef23,
9130
9351
  useState as useState12
9131
9352
  } from "react";
9132
9353
  import {
9133
9354
  LinearSRGBColorSpace,
9134
- LinearFilter as LinearFilter4,
9355
+ LinearFilter as LinearFilter3,
9135
9356
  WebGLRenderTarget
9136
9357
  } from "three";
9137
9358
 
@@ -9204,7 +9425,7 @@ function isSnapshotTransition(preset) {
9204
9425
 
9205
9426
  // src/scene-transitions/snapshot-hold.tsx
9206
9427
  import { useFrame as useFrame8, useThree as useThree9 } from "@react-three/fiber";
9207
- import { useEffect as useEffect15, useMemo as useMemo9, useRef as useRef14 } from "react";
9428
+ import { useEffect as useEffect15, useMemo as useMemo9, useRef as useRef15 } from "react";
9208
9429
  import { Vector3 as Vector38 } from "three";
9209
9430
 
9210
9431
  // src/scene-transitions/overlay-utils.ts
@@ -9540,8 +9761,8 @@ function createSnapshotMaterial(preset, texture) {
9540
9761
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
9541
9762
  function SnapshotHold({ snapshot }) {
9542
9763
  const { camera, gl } = useThree9();
9543
- const meshRef = useRef14(null);
9544
- const directionRef = useRef14(new Vector38());
9764
+ const meshRef = useRef15(null);
9765
+ const directionRef = useRef15(new Vector38());
9545
9766
  const material = useMemo9(
9546
9767
  () => createSnapshotMaterial("crossfade", snapshot.texture),
9547
9768
  [snapshot.texture]
@@ -9624,7 +9845,7 @@ function phaseAfterSnapshot(preset, hold) {
9624
9845
 
9625
9846
  // src/scene-transitions/particle-overlay.tsx
9626
9847
  import { useFrame as useFrame9, useThree as useThree10 } from "@react-three/fiber";
9627
- import { useEffect as useEffect16, useMemo as useMemo10, useRef as useRef15 } from "react";
9848
+ import { useEffect as useEffect16, useMemo as useMemo10, useRef as useRef16 } from "react";
9628
9849
  import {
9629
9850
  DoubleSide as DoubleSide2,
9630
9851
  InstancedBufferAttribute,
@@ -9711,10 +9932,10 @@ function ParticleOverlay({
9711
9932
  onFinish
9712
9933
  }) {
9713
9934
  const { camera } = useThree10();
9714
- const meshRef = useRef15(null);
9715
- const startRef = useRef15(null);
9716
- const finishedRef = useRef15(false);
9717
- const directionRef = useRef15(new Vector39());
9935
+ const meshRef = useRef16(null);
9936
+ const startRef = useRef16(null);
9937
+ const finishedRef = useRef16(false);
9938
+ const directionRef = useRef16(new Vector39());
9718
9939
  const geometry = useMemo10(() => createParticleGeometry(), []);
9719
9940
  const material = useMemo10(
9720
9941
  () => new ShaderMaterial2({
@@ -9770,7 +9991,7 @@ function ParticleOverlay({
9770
9991
 
9771
9992
  // src/scene-transitions/snapshot-overlay.tsx
9772
9993
  import { useFrame as useFrame10, useThree as useThree11 } from "@react-three/fiber";
9773
- import { useEffect as useEffect17, useMemo as useMemo11, useRef as useRef16 } from "react";
9994
+ import { useEffect as useEffect17, useMemo as useMemo11, useRef as useRef17 } from "react";
9774
9995
  import { Vector3 as Vector310 } from "three";
9775
9996
  import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
9776
9997
  function SnapshotOverlay({
@@ -9781,10 +10002,10 @@ function SnapshotOverlay({
9781
10002
  onFinish
9782
10003
  }) {
9783
10004
  const { camera, gl } = useThree11();
9784
- const meshRef = useRef16(null);
9785
- const startRef = useRef16(null);
9786
- const finishedRef = useRef16(false);
9787
- const directionRef = useRef16(new Vector310());
10005
+ const meshRef = useRef17(null);
10006
+ const startRef = useRef17(null);
10007
+ const finishedRef = useRef17(false);
10008
+ const directionRef = useRef17(new Vector310());
9788
10009
  const material = useMemo11(() => {
9789
10010
  if (!isSnapshotTransition(transition.preset)) {
9790
10011
  return null;
@@ -9874,7 +10095,7 @@ function TransitionOverlay(props) {
9874
10095
 
9875
10096
  // src/sphere.tsx
9876
10097
  import { useThree as useThree12 } from "@react-three/fiber";
9877
- import { useEffect as useEffect18, useMemo as useMemo13, useRef as useRef18, useState as useState8 } from "react";
10098
+ import { useEffect as useEffect18, useMemo as useMemo13, useRef as useRef19, useState as useState8 } from "react";
9878
10099
  import {
9879
10100
  BackSide as BackSide2,
9880
10101
  LinearFilter,
@@ -9886,7 +10107,7 @@ import {
9886
10107
  } from "three";
9887
10108
 
9888
10109
  // src/pano-filter/pano-basic-material.tsx
9889
- import { useLayoutEffect as useLayoutEffect4, useMemo as useMemo12, useRef as useRef17 } from "react";
10110
+ import { useLayoutEffect as useLayoutEffect4, useMemo as useMemo12, useRef as useRef18 } from "react";
9890
10111
  import {
9891
10112
  Matrix4,
9892
10113
  Vector4
@@ -10086,9 +10307,9 @@ function PanoBasicMaterial({
10086
10307
  const preset = snapshot.enabled && target === "panorama" ? snapshot.preset : "none";
10087
10308
  const matrix = snapshot.enabled && (target === "panorama" || snapshot.colorMatrixScope === "all") ? snapshot.colorMatrix : null;
10088
10309
  const hasMatrix = matrix !== null;
10089
- const currentRef = useRef17({ matrix, intensity: snapshot.intensity });
10310
+ const currentRef = useRef18({ matrix, intensity: snapshot.intensity });
10090
10311
  currentRef.current = { matrix, intensity: snapshot.intensity };
10091
- const shaderRef = useRef17(null);
10312
+ const shaderRef = useRef18(null);
10092
10313
  const onBeforeCompile = useMemo12(() => {
10093
10314
  const compile = (shader) => {
10094
10315
  shaderRef.current = shader;
@@ -10136,8 +10357,8 @@ function PanoBasicMaterial({
10136
10357
  import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
10137
10358
  function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
10138
10359
  const [texture, setTexture] = useState8(null);
10139
- const onLoadRef = useRef18(onLoad);
10140
- const onErrorRef = useRef18(onError);
10360
+ const onLoadRef = useRef19(onLoad);
10361
+ const onErrorRef = useRef19(onError);
10141
10362
  onLoadRef.current = onLoad;
10142
10363
  onErrorRef.current = onError;
10143
10364
  useEffect18(() => {
@@ -10218,7 +10439,7 @@ function Sphere({
10218
10439
  onPreviewError
10219
10440
  );
10220
10441
  const displayTexture = texture ?? previewTexture;
10221
- const readyRef = useRef18(false);
10442
+ const readyRef = useRef19(false);
10222
10443
  const detailReporter = useSceneDetailReporter();
10223
10444
  useEffect18(() => {
10224
10445
  readyRef.current = false;
@@ -10264,13 +10485,14 @@ function Sphere({
10264
10485
 
10265
10486
  // src/sphere-tile/sphere-tile.tsx
10266
10487
  import { useFrame as useFrame11, useThree as useThree14 } from "@react-three/fiber";
10267
- import { memo, useCallback as useCallback7, useEffect as useEffect21, useMemo as useMemo15, useRef as useRef20, useState as useState10 } from "react";
10488
+ import { memo, useCallback as useCallback8, useEffect as useEffect21, useMemo as useMemo15, useRef as useRef21, useState as useState10 } from "react";
10268
10489
  import { BackSide as BackSide3, Frustum, Matrix4 as Matrix42, PerspectiveCamera as PerspectiveCamera4 } from "three";
10269
10490
 
10270
10491
  // src/tile/texture-manager.ts
10271
10492
  import {
10272
10493
  ClampToEdgeWrapping,
10273
10494
  LinearFilter as LinearFilter2,
10495
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter2,
10274
10496
  SRGBColorSpace as SRGBColorSpace2,
10275
10497
  TextureLoader as TextureLoader2
10276
10498
  } from "three";
@@ -10279,6 +10501,20 @@ var EMPTY_TEXTURE_SNAPSHOT = {
10279
10501
  texture: null,
10280
10502
  error: null
10281
10503
  };
10504
+ var MIPMAP_MEMORY_FACTOR = 4 / 3;
10505
+ function mipmappedTextureBytes(width, height) {
10506
+ return Math.ceil(width * height * 4 * MIPMAP_MEMORY_FACTOR);
10507
+ }
10508
+ function configureMipmappedTexture(texture, anisotropy) {
10509
+ texture.colorSpace = SRGBColorSpace2;
10510
+ texture.wrapS = ClampToEdgeWrapping;
10511
+ texture.wrapT = ClampToEdgeWrapping;
10512
+ texture.magFilter = LinearFilter2;
10513
+ texture.minFilter = LinearMipmapLinearFilter2;
10514
+ texture.generateMipmaps = true;
10515
+ texture.anisotropy = Math.max(1, anisotropy);
10516
+ texture.needsUpdate = true;
10517
+ }
10282
10518
  var TileTextureManager = class {
10283
10519
  constructor(options) {
10284
10520
  this.loader = new TextureLoader2();
@@ -10430,7 +10666,10 @@ var TileTextureManager = class {
10430
10666
  entry.texture = texture;
10431
10667
  entry.status = "loaded";
10432
10668
  entry.error = null;
10433
- entry.bytes = entry.address.width * entry.address.height * 4;
10669
+ entry.bytes = mipmappedTextureBytes(
10670
+ entry.address.width,
10671
+ entry.address.height
10672
+ );
10434
10673
  entry.lastUsed = performance.now();
10435
10674
  this.totalBytes += entry.bytes;
10436
10675
  this.loaded += 1;
@@ -10453,14 +10692,7 @@ var TileTextureManager = class {
10453
10692
  }
10454
10693
  }
10455
10694
  configureTexture(texture) {
10456
- texture.colorSpace = SRGBColorSpace2;
10457
- texture.wrapS = ClampToEdgeWrapping;
10458
- texture.wrapT = ClampToEdgeWrapping;
10459
- texture.magFilter = LinearFilter2;
10460
- texture.minFilter = LinearFilter2;
10461
- texture.generateMipmaps = false;
10462
- texture.anisotropy = Math.min(4, this.options.anisotropy);
10463
- texture.needsUpdate = true;
10695
+ configureMipmappedTexture(texture, this.options.anisotropy);
10464
10696
  }
10465
10697
  evict() {
10466
10698
  if (this.totalBytes <= this.options.maxBytes) {
@@ -10534,16 +10766,11 @@ function useSharedTileTextureManager() {
10534
10766
  }
10535
10767
 
10536
10768
  // src/tile/texture-hooks.ts
10537
- import { useEffect as useEffect20, useRef as useRef19, useState as useState9 } from "react";
10538
- import {
10539
- ClampToEdgeWrapping as ClampToEdgeWrapping2,
10540
- LinearFilter as LinearFilter3,
10541
- SRGBColorSpace as SRGBColorSpace3,
10542
- TextureLoader as TextureLoader3
10543
- } from "three";
10769
+ import { useEffect as useEffect20, useRef as useRef20, useState as useState9 } from "react";
10770
+ import { TextureLoader as TextureLoader3 } from "three";
10544
10771
  function usePreviewTexture(url, anisotropy, onError) {
10545
10772
  const [texture, setTexture] = useState9(null);
10546
- const onErrorRef = useRef19(onError);
10773
+ const onErrorRef = useRef20(onError);
10547
10774
  onErrorRef.current = onError;
10548
10775
  useEffect20(() => {
10549
10776
  if (!url) {
@@ -10560,14 +10787,7 @@ function usePreviewTexture(url, anisotropy, onError) {
10560
10787
  loadedTexture.dispose();
10561
10788
  return;
10562
10789
  }
10563
- loadedTexture.colorSpace = SRGBColorSpace3;
10564
- loadedTexture.wrapS = ClampToEdgeWrapping2;
10565
- loadedTexture.wrapT = ClampToEdgeWrapping2;
10566
- loadedTexture.magFilter = LinearFilter3;
10567
- loadedTexture.minFilter = LinearFilter3;
10568
- loadedTexture.generateMipmaps = false;
10569
- loadedTexture.anisotropy = Math.min(4, anisotropy);
10570
- loadedTexture.needsUpdate = true;
10790
+ configureMipmappedTexture(loadedTexture, anisotropy);
10571
10791
  setTexture(loadedTexture);
10572
10792
  },
10573
10793
  void 0,
@@ -10739,8 +10959,17 @@ function sphereTileGeometry(address, yawOffset2, radius = DEFAULT_PANORAMA_RADIU
10739
10959
  geometry.computeBoundingSphere();
10740
10960
  return geometry;
10741
10961
  }
10742
- function preferredSphereLevel(levels, height, dpr, verticalFov) {
10743
- const needed = height * dpr * 180 / Math.max(1, verticalFov);
10962
+ function tanHalfFov(verticalFov) {
10963
+ const fov = Math.min(179, Math.max(1, verticalFov));
10964
+ return Math.tan(MathUtils6.degToRad(fov) * 0.5);
10965
+ }
10966
+ function preferredSphereLevel(levels, height, dpr, verticalFov, width = height) {
10967
+ const pixelHeight = Math.max(1, height) * dpr;
10968
+ const pixelWidth = Math.max(1, width) * dpr;
10969
+ const tanHalfV = tanHalfFov(verticalFov);
10970
+ const tanHalfH = pixelWidth / pixelHeight * tanHalfV;
10971
+ const edgeScale = 1 + Math.max(tanHalfV, tanHalfH) ** 2;
10972
+ const needed = pixelHeight * 0.5 / tanHalfV * Math.PI * edgeScale;
10744
10973
  const index = levels.findIndex((level) => level.height >= needed);
10745
10974
  return index < 0 ? levels.length - 1 : index;
10746
10975
  }
@@ -10841,7 +11070,7 @@ function SphereTileSource({
10841
11070
  anisotropy,
10842
11071
  onPreviewError
10843
11072
  );
10844
- const callbacks = useRef20({
11073
+ const callbacks = useRef21({
10845
11074
  onReady,
10846
11075
  onLoadProgress,
10847
11076
  onTileError,
@@ -10874,8 +11103,8 @@ function SphereTileSource({
10874
11103
  );
10875
11104
  const manager = sharedManager ?? ownedManager;
10876
11105
  const budget = manager.memoryBudgetBytes;
10877
- const readyRef = useRef20(false);
10878
- const ready = useCallback7(() => {
11106
+ const readyRef = useRef21(false);
11107
+ const ready = useCallback8(() => {
10879
11108
  if (!readyRef.current) {
10880
11109
  readyRef.current = true;
10881
11110
  callbacks.current.onReady?.();
@@ -10916,10 +11145,10 @@ function SphereTileSource({
10916
11145
  );
10917
11146
  const [layers, setLayers] = useState10([]);
10918
11147
  const detailReporter = useSceneDetailReporter();
10919
- const detailReporterRef = useRef20(detailReporter);
11148
+ const detailReporterRef = useRef21(detailReporter);
10920
11149
  detailReporterRef.current = detailReporter;
10921
- const detailTokenRef = useRef20("");
10922
- const targetKeysRef = useRef20([]);
11150
+ const detailTokenRef = useRef21("");
11151
+ const targetKeysRef = useRef21([]);
10923
11152
  const scratch = useMemo15(
10924
11153
  () => ({
10925
11154
  camera: new PerspectiveCamera4(),
@@ -10968,7 +11197,13 @@ function SphereTileSource({
10968
11197
  scratch.camera.matrixWorldInverse
10969
11198
  );
10970
11199
  scratch.frustum.setFromProjectionMatrix(scratch.matrix);
10971
- let level = loadMode === "preview" ? -1 : loadMode === "base" ? 0 : preferredSphereLevel(config.levels, size.height, dpr, camera.fov);
11200
+ let level = loadMode === "preview" ? -1 : loadMode === "base" ? 0 : preferredSphereLevel(
11201
+ config.levels,
11202
+ size.height,
11203
+ dpr,
11204
+ camera.fov,
11205
+ size.width
11206
+ );
10972
11207
  const next = [];
10973
11208
  let residentBytes = 0;
10974
11209
  for (let index = 0; index <= level; index++) {
@@ -10976,7 +11211,7 @@ function SphereTileSource({
10976
11211
  ({ bounds }) => scratch.frustum.intersectsBox(bounds)
10977
11212
  ).map(({ address }) => address);
10978
11213
  const bytes = addresses.reduce(
10979
- (total, address) => total + address.width * address.height * 4,
11214
+ (total, address) => total + mipmappedTextureBytes(address.width, address.height),
10980
11215
  0
10981
11216
  );
10982
11217
  if (residentBytes + bytes > budget) {
@@ -11042,7 +11277,7 @@ function SphereTile({ multires, ...props }) {
11042
11277
 
11043
11278
  // src/tile/tile.tsx
11044
11279
  import { useFrame as useFrame12, useThree as useThree15 } from "@react-three/fiber";
11045
- import { memo as memo2, useEffect as useEffect22, useMemo as useMemo16, useRef as useRef21, useState as useState11 } from "react";
11280
+ import { memo as memo2, useEffect as useEffect22, useMemo as useMemo16, useRef as useRef22, useState as useState11 } from "react";
11046
11281
  import {
11047
11282
  Frustum as Frustum2,
11048
11283
  Matrix4 as Matrix43,
@@ -11055,6 +11290,7 @@ import {
11055
11290
  import {
11056
11291
  Box3 as Box32,
11057
11292
  Euler as Euler4,
11293
+ MathUtils as MathUtils7,
11058
11294
  Quaternion as Quaternion2,
11059
11295
  Vector3 as Vector312
11060
11296
  } from "three";
@@ -11149,8 +11385,19 @@ function makeTileAddress(face, level, faceSize, tileSize, col, row) {
11149
11385
  function tileKey(address) {
11150
11386
  return `${address.level}/${address.face}/${address.row}/${address.col}`;
11151
11387
  }
11152
- function getPreferredLevel(levels, viewportHeight, pixelRatio, verticalFov, currentLevel) {
11153
- const requiredFaceSize = viewportHeight * pixelRatio * (90 / Math.max(verticalFov, 1));
11388
+ function tanHalfFov2(verticalFov) {
11389
+ const fov = Math.min(179, Math.max(1, verticalFov));
11390
+ return Math.tan(MathUtils7.degToRad(fov) * 0.5);
11391
+ }
11392
+ function getPreferredLevel(levels, viewportHeight, pixelRatio, verticalFov, currentLevel, viewportWidth = viewportHeight) {
11393
+ const height = Math.max(1, viewportHeight) * pixelRatio;
11394
+ const width = Math.max(1, viewportWidth) * pixelRatio;
11395
+ const tanHalfV = tanHalfFov2(verticalFov);
11396
+ const requiredFaceSize = Math.max(
11397
+ height / tanHalfV,
11398
+ height * tanHalfV,
11399
+ width * width * tanHalfV / height
11400
+ );
11154
11401
  let preferredLevel = levels.length;
11155
11402
  for (let index = 0; index < levels.length; index += 1) {
11156
11403
  if (levels[index] >= requiredFaceSize) {
@@ -11481,9 +11728,9 @@ function Tile({
11481
11728
  onPreviewError
11482
11729
  );
11483
11730
  const sharedManager = useSharedTileTextureManager();
11484
- const progressRef = useRef21(onLoadProgress);
11485
- const errorRef = useRef21(onTileError);
11486
- const levelChangeRef = useRef21(onLevelChange);
11731
+ const progressRef = useRef22(onLoadProgress);
11732
+ const errorRef = useRef22(onTileError);
11733
+ const levelChangeRef = useRef22(onLevelChange);
11487
11734
  progressRef.current = onLoadProgress;
11488
11735
  errorRef.current = onTileError;
11489
11736
  levelChangeRef.current = onLevelChange;
@@ -11518,7 +11765,7 @@ function Tile({
11518
11765
  queueMicrotask(() => manager.releaseUnused());
11519
11766
  };
11520
11767
  }, [manager, ownedManager]);
11521
- const readyRef = useRef21(false);
11768
+ const readyRef = useRef22(false);
11522
11769
  useEffect22(() => {
11523
11770
  readyRef.current = false;
11524
11771
  }, [effectivePreviewUrl]);
@@ -11538,21 +11785,21 @@ function Tile({
11538
11785
  [baseUrl, resolveTileUrl, template]
11539
11786
  );
11540
11787
  const [layers, setLayers] = useState11([]);
11541
- const layerTokenRef = useRef21("");
11542
- const currentLevelRef = useRef21(1);
11543
- const lastUpdateAtRef = useRef21(-Infinity);
11544
- const visibilityDirtyRef = useRef21(true);
11545
- const lastVisibilityQuaternionRef = useRef21(camera.quaternion.clone());
11546
- const lastVisibilityFovRef = useRef21(Number.NaN);
11547
- const lastViewportRef = useRef21({ width: 0, height: 0, dpr: 0 });
11548
- const frustumRef = useRef21(new Frustum2());
11549
- const projectionMatrixRef = useRef21(new Matrix43());
11550
- const visibilityCameraRef = useRef21(new PerspectiveCamera5());
11551
- const trackedLoadModeRef = useRef21(loadMode);
11552
- const targetKeysRef = useRef21([]);
11553
- const detailTokenRef = useRef21("");
11788
+ const layerTokenRef = useRef22("");
11789
+ const currentLevelRef = useRef22(1);
11790
+ const lastUpdateAtRef = useRef22(-Infinity);
11791
+ const visibilityDirtyRef = useRef22(true);
11792
+ const lastVisibilityQuaternionRef = useRef22(camera.quaternion.clone());
11793
+ const lastVisibilityFovRef = useRef22(Number.NaN);
11794
+ const lastViewportRef = useRef22({ width: 0, height: 0, dpr: 0 });
11795
+ const frustumRef = useRef22(new Frustum2());
11796
+ const projectionMatrixRef = useRef22(new Matrix43());
11797
+ const visibilityCameraRef = useRef22(new PerspectiveCamera5());
11798
+ const trackedLoadModeRef = useRef22(loadMode);
11799
+ const targetKeysRef = useRef22([]);
11800
+ const detailTokenRef = useRef22("");
11554
11801
  const detailReporter = useSceneDetailReporter();
11555
- const detailReporterRef = useRef21(detailReporter);
11802
+ const detailReporterRef = useRef22(detailReporter);
11556
11803
  detailReporterRef.current = detailReporter;
11557
11804
  useEffect22(() => {
11558
11805
  currentLevelRef.current = 1;
@@ -11610,7 +11857,8 @@ function Tile({
11610
11857
  size.height,
11611
11858
  pixelRatio,
11612
11859
  camera.fov,
11613
- currentLevelRef.current
11860
+ currentLevelRef.current,
11861
+ size.width
11614
11862
  );
11615
11863
  if (targetLevel !== currentLevelRef.current) {
11616
11864
  currentLevelRef.current = targetLevel;
@@ -11736,8 +11984,8 @@ function SnapshotCapture({
11736
11984
  onCaptured
11737
11985
  }) {
11738
11986
  const { camera, gl, scene, size } = useThree16();
11739
- const capturedRequestRef = useRef22(0);
11740
- const renderTargetRef = useRef22(null);
11987
+ const capturedRequestRef = useRef23(0);
11988
+ const renderTargetRef = useRef23(null);
11741
11989
  useEffect23(
11742
11990
  () => () => {
11743
11991
  renderTargetRef.current?.dispose();
@@ -11761,8 +12009,8 @@ function SnapshotCapture({
11761
12009
  });
11762
12010
  nextTarget.texture.colorSpace = LinearSRGBColorSpace;
11763
12011
  nextTarget.texture.generateMipmaps = false;
11764
- nextTarget.texture.minFilter = LinearFilter4;
11765
- nextTarget.texture.magFilter = LinearFilter4;
12012
+ nextTarget.texture.minFilter = LinearFilter3;
12013
+ nextTarget.texture.magFilter = LinearFilter3;
11766
12014
  const previousTarget = gl.getRenderTarget();
11767
12015
  gl.setRenderTarget(nextTarget);
11768
12016
  gl.clear();
@@ -11819,20 +12067,20 @@ function ScenesController({
11819
12067
  const [transitionRunId, setTransitionRunId] = useState12(0);
11820
12068
  const [snapshot, setSnapshot] = useState12(null);
11821
12069
  const [recoveryKey, setRecoveryKey] = useState12(0);
11822
- const requestedSceneIdRef = useRef22(activeSceneId);
11823
- const captureReasonRef = useRef22("start");
11824
- const interactionReleaseRef = useRef22(null);
11825
- const slotsRef = useRef22(slots);
11826
- const phaseRef = useRef22(phase);
11827
- const liveSlotRef = useRef22(liveSlot);
11828
- const transitionFromRef = useRef22(initialScene);
11829
- const readySceneRef = useRef22(null);
11830
- const waitRef = useRef22(null);
11831
- const waitTimerRef = useRef22(null);
11832
- const detailGenerationRef = useRef22(0);
12070
+ const requestedSceneIdRef = useRef23(activeSceneId);
12071
+ const captureReasonRef = useRef23("start");
12072
+ const interactionReleaseRef = useRef23(null);
12073
+ const slotsRef = useRef23(slots);
12074
+ const phaseRef = useRef23(phase);
12075
+ const liveSlotRef = useRef23(liveSlot);
12076
+ const transitionFromRef = useRef23(initialScene);
12077
+ const readySceneRef = useRef23(null);
12078
+ const waitRef = useRef23(null);
12079
+ const waitTimerRef = useRef23(null);
12080
+ const detailGenerationRef = useRef23(0);
11833
12081
  const [detailGeneration, setDetailGeneration] = useState12(0);
11834
- const mountedRef = useRef22(true);
11835
- const waitForDetailMsRef = useRef22(0);
12082
+ const mountedRef = useRef23(true);
12083
+ const waitForDetailMsRef = useRef23(0);
11836
12084
  waitForDetailMsRef.current = normalizeTransitionWaitMs(transitionWaitForDetailMs);
11837
12085
  slotsRef.current = slots;
11838
12086
  phaseRef.current = phase;
@@ -11847,14 +12095,14 @@ function ScenesController({
11847
12095
  useEffect23(() => () => {
11848
12096
  controlsRef?.current?.setSceneViewLimits(void 0);
11849
12097
  }, [controlsRef]);
11850
- const releaseInteraction = useCallback8(() => {
12098
+ const releaseInteraction = useCallback9(() => {
11851
12099
  interactionReleaseRef.current?.();
11852
12100
  interactionReleaseRef.current = null;
11853
12101
  }, []);
11854
- const reportError = useCallback8((sceneId, error) => {
12102
+ const reportError = useCallback9((sceneId, error) => {
11855
12103
  onTransitionError?.({ sceneId, error });
11856
12104
  }, [onTransitionError]);
11857
- const clearDetailWait = useCallback8((bumpGeneration) => {
12105
+ const clearDetailWait = useCallback9((bumpGeneration) => {
11858
12106
  if (waitTimerRef.current !== null) {
11859
12107
  clearTimeout(waitTimerRef.current);
11860
12108
  waitTimerRef.current = null;
@@ -11882,7 +12130,7 @@ function ScenesController({
11882
12130
  clearDetailWait(false);
11883
12131
  };
11884
12132
  }, [clearDetailWait]);
11885
- const startPreload = useCallback8((scene) => {
12133
+ const startPreload = useCallback9((scene) => {
11886
12134
  clearDetailWait(true);
11887
12135
  const nextSlot = 1 - liveSlotRef.current;
11888
12136
  requestedSceneIdRef.current = scene.id;
@@ -11920,13 +12168,13 @@ function ScenesController({
11920
12168
  }
11921
12169
  startPreload(requested);
11922
12170
  }, [activeSceneId, clearDetailWait, pendingSlot, reportError, sceneMap, startPreload]);
11923
- const notifySceneReady = useCallback8((scene) => {
12171
+ const notifySceneReady = useCallback9((scene) => {
11924
12172
  if (!scene || readySceneRef.current === scene) return;
11925
12173
  readySceneRef.current = scene;
11926
12174
  controlsRef?.current?.setSceneViewLimits(sceneMap.get(scene.id)?.viewLimits);
11927
12175
  onSceneReady?.(scene.id);
11928
12176
  }, [controlsRef, onSceneReady, sceneMap]);
11929
- const handleReady = useCallback8((slot) => {
12177
+ const handleReady = useCallback9((slot) => {
11930
12178
  if (slot === liveSlotRef.current && pendingSlot === null && phaseRef.current === "idle") {
11931
12179
  notifySceneReady(slotsRef.current[slot]);
11932
12180
  return;
@@ -11962,7 +12210,7 @@ function ScenesController({
11962
12210
  setPhase("capturing");
11963
12211
  setCaptureRequestId((current) => current + 1);
11964
12212
  }, [controlsRef, notifySceneReady, onTransitionEnd, pendingSlot, transition.preset]);
11965
- const finishTransition = useCallback8(() => {
12213
+ const finishTransition = useCallback9(() => {
11966
12214
  const nextSlot = pendingSlot;
11967
12215
  const previousScene = transitionFromRef.current;
11968
12216
  const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
@@ -11987,11 +12235,11 @@ function ScenesController({
11987
12235
  });
11988
12236
  }
11989
12237
  }, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
11990
- const finishTransitionRef = useRef22(finishTransition);
12238
+ const finishTransitionRef = useRef23(finishTransition);
11991
12239
  finishTransitionRef.current = finishTransition;
11992
- const transitionRef = useRef22(transition);
12240
+ const transitionRef = useRef23(transition);
11993
12241
  transitionRef.current = transition;
11994
- const consumeDetailSignal = useCallback8((signal) => {
12242
+ const consumeDetailSignal = useCallback9((signal) => {
11995
12243
  const decision = acceptDetailWait(waitRef.current, signal);
11996
12244
  waitRef.current = decision.wait;
11997
12245
  if (!decision.start) {
@@ -12011,7 +12259,7 @@ function ScenesController({
12011
12259
  setPhase("transitioning");
12012
12260
  setTransitionRunId((current) => current + 1);
12013
12261
  }, []);
12014
- const handleCaptured = useCallback8((nextSnapshot) => {
12262
+ const handleCaptured = useCallback9((nextSnapshot) => {
12015
12263
  if (captureReasonRef.current === "interrupt") {
12016
12264
  setSnapshot((current) => {
12017
12265
  current?.dispose();
@@ -12109,7 +12357,7 @@ function ScenesController({
12109
12357
  startPreload,
12110
12358
  transition.preset
12111
12359
  ]);
12112
- const snapshotRef = useRef22(snapshot);
12360
+ const snapshotRef = useRef23(snapshot);
12113
12361
  snapshotRef.current = snapshot;
12114
12362
  useEffect23(() => () => {
12115
12363
  releaseInteraction();
@@ -12162,7 +12410,7 @@ ${slots[1]?.id ?? ""}`;
12162
12410
  }
12163
12411
  }));
12164
12412
  }, [consumeDetailSignal, detailGeneration, slotSceneKey]);
12165
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
12413
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
12166
12414
  /* @__PURE__ */ jsx23(
12167
12415
  SnapshotCapture,
12168
12416
  {
@@ -12225,7 +12473,7 @@ function Scenes({
12225
12473
  () => controlledId ?? defaultActiveSceneId ?? scenes[0]?.id ?? ""
12226
12474
  );
12227
12475
  const resolvedId = isControlled ? controlledId : internalId;
12228
- const requestSceneChange = useCallback8(
12476
+ const requestSceneChange = useCallback9(
12229
12477
  (id) => {
12230
12478
  if (isControlled) {
12231
12479
  onActiveSceneIdChange?.(id);
@@ -12235,12 +12483,12 @@ function Scenes({
12235
12483
  },
12236
12484
  [isControlled, onActiveSceneIdChange]
12237
12485
  );
12238
- const scenesRef = useRef22(scenes);
12486
+ const scenesRef = useRef23(scenes);
12239
12487
  scenesRef.current = scenes;
12240
- const resolvedIdRef = useRef22(resolvedId);
12488
+ const resolvedIdRef = useRef23(resolvedId);
12241
12489
  resolvedIdRef.current = resolvedId;
12242
12490
  const host = useContext19(ScenesHostContext);
12243
- const phaseRef = useRef22("idle");
12491
+ const phaseRef = useRef23("idle");
12244
12492
  useEffect23(() => {
12245
12493
  if (!host) {
12246
12494
  return;
@@ -12311,12 +12559,12 @@ function Scenes({
12311
12559
 
12312
12560
  // src/hotspot/image-hotspot.tsx
12313
12561
  import { useThree as useThree20 } from "@react-three/fiber";
12314
- import { useEffect as useEffect26, useRef as useRef26, useState as useState15 } from "react";
12562
+ import { useEffect as useEffect26, useRef as useRef27, useState as useState15 } from "react";
12315
12563
  import {
12316
- ClampToEdgeWrapping as ClampToEdgeWrapping3,
12317
- LinearFilter as LinearFilter5,
12318
- LinearMipmapLinearFilter as LinearMipmapLinearFilter2,
12319
- SRGBColorSpace as SRGBColorSpace4,
12564
+ ClampToEdgeWrapping as ClampToEdgeWrapping2,
12565
+ LinearFilter as LinearFilter4,
12566
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
12567
+ SRGBColorSpace as SRGBColorSpace3,
12320
12568
  TextureLoader as TextureLoader4
12321
12569
  } from "three";
12322
12570
 
@@ -12327,13 +12575,13 @@ import {
12327
12575
  useEffect as useEffect25,
12328
12576
  useLayoutEffect as useLayoutEffect7,
12329
12577
  useMemo as useMemo19,
12330
- useRef as useRef25,
12578
+ useRef as useRef26,
12331
12579
  useState as useState14
12332
12580
  } from "react";
12333
12581
  import {
12334
12582
  Box3 as Box34,
12335
12583
  EdgesGeometry,
12336
- MathUtils as MathUtils9,
12584
+ MathUtils as MathUtils10,
12337
12585
  Matrix4 as Matrix44,
12338
12586
  PerspectiveCamera as PerspectiveCamera8,
12339
12587
  Plane,
@@ -12441,12 +12689,12 @@ import { useFrame as useFrame14, useThree as useThree17 } from "@react-three/fib
12441
12689
  import {
12442
12690
  useContext as useContext20,
12443
12691
  useLayoutEffect as useLayoutEffect5,
12444
- useRef as useRef23,
12692
+ useRef as useRef24,
12445
12693
  useSyncExternalStore as useSyncExternalStore9
12446
12694
  } from "react";
12447
12695
  import { createRoot } from "react-dom/client";
12448
12696
  import { Vector3 as Vector314 } from "three";
12449
- import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
12697
+ import { Fragment as Fragment7, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
12450
12698
  function useFallbackStereoView() {
12451
12699
  const runtime = useContext20(WebVRRuntimeContext);
12452
12700
  const host = runtime?.host ?? null;
@@ -12466,9 +12714,9 @@ function ProjectedHtml({
12466
12714
  eye
12467
12715
  }) {
12468
12716
  const { camera, gl, events, size } = useThree17();
12469
- const group = useRef23(null);
12470
- const overlay = useRef23(null);
12471
- const scratch = useRef23(new Vector314());
12717
+ const group = useRef24(null);
12718
+ const overlay = useRef24(null);
12719
+ const scratch = useRef24(new Vector314());
12472
12720
  const target = events.connected || gl.domElement.parentElement;
12473
12721
  const updatePosition = () => {
12474
12722
  const anchor = group.current;
@@ -12543,7 +12791,7 @@ function PanoHtml(props) {
12543
12791
  if (mode === "mobilevr" && portrait) {
12544
12792
  return null;
12545
12793
  }
12546
- return stereoView ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
12794
+ return stereoView ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
12547
12795
  /* @__PURE__ */ jsx24(ProjectedHtml, { ...props, stereoView, eye: "left" }),
12548
12796
  /* @__PURE__ */ jsx24(ProjectedHtml, { ...props, stereoView, eye: "right" })
12549
12797
  ] }) : /* @__PURE__ */ jsx24(ProjectedHtml, { ...props });
@@ -12680,7 +12928,7 @@ function HotspotTooltip({
12680
12928
 
12681
12929
  // src/hotspot/transform.ts
12682
12930
  import {
12683
- MathUtils as MathUtils7,
12931
+ MathUtils as MathUtils8,
12684
12932
  PerspectiveCamera as PerspectiveCamera6,
12685
12933
  Vector3 as Vector315
12686
12934
  } from "three";
@@ -12692,7 +12940,7 @@ function applyHotspotTransform(group, camera, orientation2, surfaceQuaternion3,
12692
12940
  } else {
12693
12941
  group.quaternion.copy(surfaceQuaternion3);
12694
12942
  }
12695
- group.rotateOnAxis(LOCAL_FORWARD, MathUtils7.degToRad(-rotation));
12943
+ group.rotateOnAxis(LOCAL_FORWARD, MathUtils8.degToRad(-rotation));
12696
12944
  let scaleFactor = scaleMode === "fixed" && camera instanceof PerspectiveCamera6 ? fixedScaleFactor(camera.fov, referenceFov) : 1;
12697
12945
  if (scaleMode === "fixed" && orientation2 === "billboard" && useAngularScale && camera instanceof PerspectiveCamera6) {
12698
12946
  group.updateWorldMatrix(true, false);
@@ -12709,15 +12957,15 @@ function applyHotspotTransform(group, camera, orientation2, surfaceQuaternion3,
12709
12957
  group.updateMatrixWorld();
12710
12958
  }
12711
12959
  function fixedScaleFactor(cameraFov, referenceFov) {
12712
- const safeReferenceFov = MathUtils7.clamp(referenceFov, 1, 179);
12713
- return Math.tan(MathUtils7.degToRad(cameraFov) / 2) / Math.tan(MathUtils7.degToRad(safeReferenceFov) / 2);
12960
+ const safeReferenceFov = MathUtils8.clamp(referenceFov, 1, 179);
12961
+ return Math.tan(MathUtils8.degToRad(cameraFov) / 2) / Math.tan(MathUtils8.degToRad(safeReferenceFov) / 2);
12714
12962
  }
12715
12963
  function selectionFrameScale(worldScaleX, worldScaleY, cameraSpaceDepth, fovDegrees, viewportHeight, outsetPixels) {
12716
12964
  const pixels = Number.isFinite(outsetPixels) && outsetPixels > 0 ? outsetPixels : 0;
12717
12965
  const depth = Number.isFinite(cameraSpaceDepth) ? Math.max(cameraSpaceDepth, 1e-4) : 1e-4;
12718
12966
  const height = Number.isFinite(viewportHeight) ? Math.max(viewportHeight, 1) : 1;
12719
- const fov = Number.isFinite(fovDegrees) ? MathUtils7.clamp(fovDegrees, 1, 179) : 75;
12720
- const pixelsPerWorldUnit = height / (2 * Math.tan(MathUtils7.degToRad(fov) * 0.5) * depth);
12967
+ const fov = Number.isFinite(fovDegrees) ? MathUtils8.clamp(fovDegrees, 1, 179) : 75;
12968
+ const pixelsPerWorldUnit = height / (2 * Math.tan(MathUtils8.degToRad(fov) * 0.5) * depth);
12721
12969
  const axis = (worldScale) => {
12722
12970
  const pixelsPerLocalUnit = pixelsPerWorldUnit * Math.abs(worldScale);
12723
12971
  if (pixelsPerLocalUnit < 1e-4) {
@@ -12731,9 +12979,9 @@ function selectionFrameScale(worldScaleX, worldScaleY, cameraSpaceDepth, fovDegr
12731
12979
  // src/hotspot/hotspot-stroke-line.tsx
12732
12980
  import { Line } from "@react-three/drei";
12733
12981
  import { useFrame as useFrame15, useThree as useThree18 } from "@react-three/fiber";
12734
- import { useLayoutEffect as useLayoutEffect6, useRef as useRef24 } from "react";
12982
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef25 } from "react";
12735
12983
  import {
12736
- MathUtils as MathUtils8,
12984
+ MathUtils as MathUtils9,
12737
12985
  PerspectiveCamera as PerspectiveCamera7,
12738
12986
  Vector3 as Vector316
12739
12987
  } from "three";
@@ -12747,7 +12995,7 @@ function applyScreenSpaceDashScale(object, camera, viewportHeight) {
12747
12995
  object.getWorldPosition(LINE_WORLD_POSITION);
12748
12996
  const depth = Math.max(camera.position.distanceTo(LINE_WORLD_POSITION), 1);
12749
12997
  const fov = camera instanceof PerspectiveCamera7 ? camera.fov : 75;
12750
- material.dashScale = viewportHeight / (2 * Math.tan(MathUtils8.degToRad(fov) * 0.5) * depth);
12998
+ material.dashScale = viewportHeight / (2 * Math.tan(MathUtils9.degToRad(fov) * 0.5) * depth);
12751
12999
  }
12752
13000
  function resolveDash(dashSize, gapSize) {
12753
13001
  if (dashSize === void 0 || !Number.isFinite(dashSize) || dashSize <= 0) {
@@ -12768,7 +13016,7 @@ function HotspotStrokeLine({
12768
13016
  dashSize,
12769
13017
  gapSize
12770
13018
  }) {
12771
- const lineRef = useRef24(null);
13019
+ const lineRef = useRef25(null);
12772
13020
  const { camera, size } = useThree18();
12773
13021
  const dash = resolveDash(dashSize, gapSize);
12774
13022
  useLayoutEffect6(() => {
@@ -12844,8 +13092,8 @@ var HOTSPOT_MODE_RENDERING = {
12844
13092
  billboard: { orientation: "billboard", placement: "floating" }
12845
13093
  };
12846
13094
  function angularSizeToWorldSize(size, radius) {
12847
- const safeSize = Number.isFinite(size) ? MathUtils9.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
12848
- return 2 * radius * Math.tan(MathUtils9.degToRad(safeSize) / 2);
13095
+ const safeSize = Number.isFinite(size) ? MathUtils10.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
13096
+ return 2 * radius * Math.tan(MathUtils10.degToRad(safeSize) / 2);
12849
13097
  }
12850
13098
  function resolveScale(scale) {
12851
13099
  return Number.isFinite(scale) && scale > 0 ? scale : 1;
@@ -12858,31 +13106,31 @@ function resolveDistance(placement, distance, width, height) {
12858
13106
  if (!Number.isFinite(distance)) {
12859
13107
  return DEFAULT_FLOATING_DISTANCE;
12860
13108
  }
12861
- return MathUtils9.clamp(Math.abs(distance), 0.1, maximumDistance);
13109
+ return MathUtils10.clamp(Math.abs(distance), 0.1, maximumDistance);
12862
13110
  }
12863
13111
  function maximumHotspotDistance(width, height) {
12864
- const safeWidth = MathUtils9.clamp(
13112
+ const safeWidth = MathUtils10.clamp(
12865
13113
  Math.abs(Number.isFinite(width) ? width : MIN_ANGULAR_SIZE),
12866
13114
  MIN_ANGULAR_SIZE,
12867
13115
  MAX_ANGULAR_SIZE
12868
13116
  );
12869
- const safeHeight = MathUtils9.clamp(
13117
+ const safeHeight = MathUtils10.clamp(
12870
13118
  Math.abs(Number.isFinite(height) ? height : MIN_ANGULAR_SIZE),
12871
13119
  MIN_ANGULAR_SIZE,
12872
13120
  MAX_ANGULAR_SIZE
12873
13121
  );
12874
13122
  const cornerFactor = Math.hypot(
12875
13123
  1,
12876
- Math.tan(MathUtils9.degToRad(safeWidth) / 2),
12877
- Math.tan(MathUtils9.degToRad(safeHeight) / 2)
13124
+ Math.tan(MathUtils10.degToRad(safeWidth) / 2),
13125
+ Math.tan(MathUtils10.degToRad(safeHeight) / 2)
12878
13126
  );
12879
13127
  return (PANORAMA_RADIUS - PANORAMA_SURFACE_INSET) / cornerFactor;
12880
13128
  }
12881
13129
  function createSurfaceQuaternion(position) {
12882
13130
  const outward = panoPositionToVector3(position).normalize();
12883
13131
  const inward = outward.clone().negate();
12884
- const pitch = MathUtils9.degToRad(position.pitch);
12885
- const yaw = MathUtils9.degToRad(position.yaw);
13132
+ const pitch = MathUtils10.degToRad(position.pitch);
13133
+ const yaw = MathUtils10.degToRad(position.yaw);
12886
13134
  const localUp = new Vector317(
12887
13135
  -Math.sin(pitch) * Math.sin(yaw),
12888
13136
  Math.cos(pitch),
@@ -12903,7 +13151,7 @@ function makeInteractionEvent(id, position, event) {
12903
13151
  function angularDistance(a, b) {
12904
13152
  const aVector = panoPositionToVector3(a);
12905
13153
  const bVector = panoPositionToVector3(b);
12906
- return MathUtils9.radToDeg(aVector.angleTo(bVector));
13154
+ return MathUtils10.radToDeg(aVector.angleTo(bVector));
12907
13155
  }
12908
13156
  var DRAG_POINTER = new Vector24();
12909
13157
  var DRAG_RAYCASTER = new Raycaster2();
@@ -13186,13 +13434,13 @@ function HotspotAnchor({
13186
13434
  const eventBus = useContext21(PanoEventBusContext);
13187
13435
  const cursorApi = usePanoCursor();
13188
13436
  const { camera, gl, size } = useThree19();
13189
- const groupRef = useRef25(null);
13190
- const selectionFrameRef = useRef25(null);
13191
- const tooltipAnchorRef = useRef25(null);
13192
- const dragStateRef = useRef25(null);
13193
- const updateDragRef = useRef25(() => void 0);
13194
- const endDragRef = useRef25(() => void 0);
13195
- const suppressNextClickRef = useRef25(false);
13437
+ const groupRef = useRef26(null);
13438
+ const selectionFrameRef = useRef26(null);
13439
+ const tooltipAnchorRef = useRef26(null);
13440
+ const dragStateRef = useRef26(null);
13441
+ const updateDragRef = useRef26(() => void 0);
13442
+ const endDragRef = useRef26(() => void 0);
13443
+ const suppressNextClickRef = useRef26(false);
13196
13444
  const [hovered, setHovered] = useState14(false);
13197
13445
  const [dragging, setDragging] = useState14(false);
13198
13446
  const [pinned, setPinned] = useState14(false);
@@ -13234,7 +13482,7 @@ function HotspotAnchor({
13234
13482
  planeGeometry.dispose();
13235
13483
  return geometry;
13236
13484
  }, []);
13237
- const eventHovered = useRef25(false);
13485
+ const eventHovered = useRef26(false);
13238
13486
  useEffect25(() => {
13239
13487
  if (!visible || !interactive || pointerEvents === "none") eventHovered.current = false;
13240
13488
  }, [visible, interactive, pointerEvents]);
@@ -13752,8 +14000,8 @@ import { jsx as jsx29 } from "react/jsx-runtime";
13752
14000
  function useHotspotImageTexture(src, onLoad, onError) {
13753
14001
  const gl = useThree20((state) => state.gl);
13754
14002
  const [texture, setTexture] = useState15(null);
13755
- const onLoadRef = useRef26(onLoad);
13756
- const onErrorRef = useRef26(onError);
14003
+ const onLoadRef = useRef27(onLoad);
14004
+ const onErrorRef = useRef27(onError);
13757
14005
  onLoadRef.current = onLoad;
13758
14006
  onErrorRef.current = onError;
13759
14007
  useEffect26(() => {
@@ -13772,11 +14020,11 @@ function useHotspotImageTexture(src, onLoad, onError) {
13772
14020
  loadedTexture.dispose();
13773
14021
  return;
13774
14022
  }
13775
- loadedTexture.colorSpace = SRGBColorSpace4;
13776
- loadedTexture.wrapS = ClampToEdgeWrapping3;
13777
- loadedTexture.wrapT = ClampToEdgeWrapping3;
13778
- loadedTexture.magFilter = LinearFilter5;
13779
- loadedTexture.minFilter = LinearMipmapLinearFilter2;
14023
+ loadedTexture.colorSpace = SRGBColorSpace3;
14024
+ loadedTexture.wrapS = ClampToEdgeWrapping2;
14025
+ loadedTexture.wrapT = ClampToEdgeWrapping2;
14026
+ loadedTexture.magFilter = LinearFilter4;
14027
+ loadedTexture.minFilter = LinearMipmapLinearFilter3;
13780
14028
  loadedTexture.generateMipmaps = true;
13781
14029
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
13782
14030
  loadedTexture.needsUpdate = true;
@@ -13816,13 +14064,13 @@ function ImageHotspot({
13816
14064
  }
13817
14065
 
13818
14066
  // src/hotspot/graphic-hotspot.tsx
13819
- import { useEffect as useEffect27, useRef as useRef27, useState as useState16 } from "react";
14067
+ import { useEffect as useEffect27, useRef as useRef28, useState as useState16 } from "react";
13820
14068
  import {
13821
14069
  CanvasTexture,
13822
- ClampToEdgeWrapping as ClampToEdgeWrapping4,
13823
- LinearFilter as LinearFilter6,
13824
- LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
13825
- SRGBColorSpace as SRGBColorSpace5
14070
+ ClampToEdgeWrapping as ClampToEdgeWrapping3,
14071
+ LinearFilter as LinearFilter5,
14072
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter4,
14073
+ SRGBColorSpace as SRGBColorSpace4
13826
14074
  } from "three";
13827
14075
  import { jsx as jsx30 } from "react/jsx-runtime";
13828
14076
  var GRAPHIC_TEXTURE_SIZE = 512;
@@ -14003,19 +14251,19 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
14003
14251
  }
14004
14252
  drawGraphic(context, graphic, aspectWidth, aspectHeight);
14005
14253
  const texture = new CanvasTexture(canvas);
14006
- texture.colorSpace = SRGBColorSpace5;
14007
- texture.wrapS = ClampToEdgeWrapping4;
14008
- texture.wrapT = ClampToEdgeWrapping4;
14009
- texture.magFilter = LinearFilter6;
14010
- texture.minFilter = LinearMipmapLinearFilter3;
14254
+ texture.colorSpace = SRGBColorSpace4;
14255
+ texture.wrapS = ClampToEdgeWrapping3;
14256
+ texture.wrapT = ClampToEdgeWrapping3;
14257
+ texture.magFilter = LinearFilter5;
14258
+ texture.minFilter = LinearMipmapLinearFilter4;
14011
14259
  texture.generateMipmaps = true;
14012
14260
  texture.needsUpdate = true;
14013
14261
  return texture;
14014
14262
  }
14015
14263
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
14016
14264
  const [texture, setTexture] = useState16(null);
14017
- const onLoadRef = useRef27(onLoad);
14018
- const onErrorRef = useRef27(onError);
14265
+ const onLoadRef = useRef28(onLoad);
14266
+ const onErrorRef = useRef28(onError);
14019
14267
  onLoadRef.current = onLoad;
14020
14268
  onErrorRef.current = onError;
14021
14269
  useEffect27(() => {
@@ -14057,12 +14305,12 @@ function GraphicHotspot(props) {
14057
14305
 
14058
14306
  // src/hotspot/sequence-hotspot.tsx
14059
14307
  import { useFrame as useFrame17, useThree as useThree21 } from "@react-three/fiber";
14060
- import { useEffect as useEffect28, useRef as useRef28, useState as useState17 } from "react";
14308
+ import { useEffect as useEffect28, useRef as useRef29, useState as useState17 } from "react";
14061
14309
  import {
14062
- ClampToEdgeWrapping as ClampToEdgeWrapping5,
14063
- LinearFilter as LinearFilter7,
14064
- LinearMipmapLinearFilter as LinearMipmapLinearFilter4,
14065
- SRGBColorSpace as SRGBColorSpace6,
14310
+ ClampToEdgeWrapping as ClampToEdgeWrapping4,
14311
+ LinearFilter as LinearFilter6,
14312
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter5,
14313
+ SRGBColorSpace as SRGBColorSpace5,
14066
14314
  TextureLoader as TextureLoader5
14067
14315
  } from "three";
14068
14316
  import { jsx as jsx31 } from "react/jsx-runtime";
@@ -14081,8 +14329,8 @@ function useSpriteTexture({
14081
14329
  }) {
14082
14330
  const gl = useThree21((state) => state.gl);
14083
14331
  const [texture, setTexture] = useState17(null);
14084
- const onErrorRef = useRef28(onError);
14085
- const onLoadProgressRef = useRef28(onLoadProgress);
14332
+ const onErrorRef = useRef29(onError);
14333
+ const onLoadProgressRef = useRef29(onLoadProgress);
14086
14334
  onErrorRef.current = onError;
14087
14335
  onLoadProgressRef.current = onLoadProgress;
14088
14336
  useEffect28(() => {
@@ -14102,11 +14350,11 @@ function useSpriteTexture({
14102
14350
  loadedTexture.dispose();
14103
14351
  return;
14104
14352
  }
14105
- loadedTexture.colorSpace = SRGBColorSpace6;
14106
- loadedTexture.wrapS = ClampToEdgeWrapping5;
14107
- loadedTexture.wrapT = ClampToEdgeWrapping5;
14108
- loadedTexture.magFilter = LinearFilter7;
14109
- loadedTexture.minFilter = LinearMipmapLinearFilter4;
14353
+ loadedTexture.colorSpace = SRGBColorSpace5;
14354
+ loadedTexture.wrapS = ClampToEdgeWrapping4;
14355
+ loadedTexture.wrapT = ClampToEdgeWrapping4;
14356
+ loadedTexture.magFilter = LinearFilter6;
14357
+ loadedTexture.minFilter = LinearMipmapLinearFilter5;
14110
14358
  loadedTexture.generateMipmaps = true;
14111
14359
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14112
14360
  loadedTexture.needsUpdate = true;
@@ -14156,13 +14404,13 @@ function SequenceHotspot({
14156
14404
  const frameCount = resolveFrameCount(requestedFrameCount);
14157
14405
  const safeFps = resolveFps(fps);
14158
14406
  const [frameIndex, setFrameIndex] = useState17(0);
14159
- const elapsedRef = useRef28(0);
14160
- const frameIndexRef = useRef28(0);
14161
- const endedRef = useRef28(false);
14162
- const lastPlaybackStateRef = useRef28(null);
14163
- const onFrameChangeRef = useRef28(onFrameChange);
14164
- const onPlaybackStateChangeRef = useRef28(onPlaybackStateChange);
14165
- const onEndedRef = useRef28(onEnded);
14407
+ const elapsedRef = useRef29(0);
14408
+ const frameIndexRef = useRef29(0);
14409
+ const endedRef = useRef29(false);
14410
+ const lastPlaybackStateRef = useRef29(null);
14411
+ const onFrameChangeRef = useRef29(onFrameChange);
14412
+ const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
14413
+ const onEndedRef = useRef29(onEnded);
14166
14414
  onFrameChangeRef.current = onFrameChange;
14167
14415
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
14168
14416
  onEndedRef.current = onEnded;
@@ -14246,12 +14494,12 @@ function SequenceHotspot({
14246
14494
 
14247
14495
  // src/hotspot/video-hotspot.tsx
14248
14496
  import { useThree as useThree22 } from "@react-three/fiber";
14249
- import { useEffect as useEffect29, useLayoutEffect as useLayoutEffect8, useRef as useRef29, useState as useState18 } from "react";
14497
+ import { useEffect as useEffect29, useLayoutEffect as useLayoutEffect8, useRef as useRef30, useState as useState18 } from "react";
14250
14498
  import {
14251
- ClampToEdgeWrapping as ClampToEdgeWrapping6,
14252
- LinearFilter as LinearFilter8,
14253
- LinearMipmapLinearFilter as LinearMipmapLinearFilter5,
14254
- SRGBColorSpace as SRGBColorSpace7,
14499
+ ClampToEdgeWrapping as ClampToEdgeWrapping5,
14500
+ LinearFilter as LinearFilter7,
14501
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter6,
14502
+ SRGBColorSpace as SRGBColorSpace6,
14255
14503
  TextureLoader as TextureLoader6,
14256
14504
  VideoTexture
14257
14505
  } from "three";
@@ -14317,7 +14565,7 @@ function clampVolume2(volume) {
14317
14565
  function usePosterTexture(id, poster, onError) {
14318
14566
  const gl = useThree22((state) => state.gl);
14319
14567
  const [texture, setTexture] = useState18(null);
14320
- const onErrorRef = useRef29(onError);
14568
+ const onErrorRef = useRef30(onError);
14321
14569
  onErrorRef.current = onError;
14322
14570
  useEffect29(() => {
14323
14571
  let active = true;
@@ -14335,11 +14583,11 @@ function usePosterTexture(id, poster, onError) {
14335
14583
  loadedTexture.dispose();
14336
14584
  return;
14337
14585
  }
14338
- loadedTexture.colorSpace = SRGBColorSpace7;
14339
- loadedTexture.wrapS = ClampToEdgeWrapping6;
14340
- loadedTexture.wrapT = ClampToEdgeWrapping6;
14341
- loadedTexture.magFilter = LinearFilter8;
14342
- loadedTexture.minFilter = LinearMipmapLinearFilter5;
14586
+ loadedTexture.colorSpace = SRGBColorSpace6;
14587
+ loadedTexture.wrapS = ClampToEdgeWrapping5;
14588
+ loadedTexture.wrapT = ClampToEdgeWrapping5;
14589
+ loadedTexture.magFilter = LinearFilter7;
14590
+ loadedTexture.minFilter = LinearMipmapLinearFilter6;
14343
14591
  loadedTexture.generateMipmaps = true;
14344
14592
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14345
14593
  loadedTexture.needsUpdate = true;
@@ -14376,9 +14624,9 @@ function useVideoResource({
14376
14624
  }) {
14377
14625
  const gl = useThree22((state) => state.gl);
14378
14626
  const [resource, setResource] = useState18(null);
14379
- const onEndedRef = useRef29(onEnded);
14380
- const onErrorRef = useRef29(onError);
14381
- const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
14627
+ const onEndedRef = useRef30(onEnded);
14628
+ const onErrorRef = useRef30(onError);
14629
+ const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
14382
14630
  onEndedRef.current = onEnded;
14383
14631
  onErrorRef.current = onError;
14384
14632
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
@@ -14395,11 +14643,11 @@ function useVideoResource({
14395
14643
  applyInlineVideoPlayback(video, playsInline ?? true);
14396
14644
  video.poster = poster ?? "";
14397
14645
  const texture = new VideoTexture(video);
14398
- texture.colorSpace = SRGBColorSpace7;
14399
- texture.wrapS = ClampToEdgeWrapping6;
14400
- texture.wrapT = ClampToEdgeWrapping6;
14401
- texture.magFilter = LinearFilter8;
14402
- texture.minFilter = LinearFilter8;
14646
+ texture.colorSpace = SRGBColorSpace6;
14647
+ texture.wrapS = ClampToEdgeWrapping5;
14648
+ texture.wrapT = ClampToEdgeWrapping5;
14649
+ texture.magFilter = LinearFilter7;
14650
+ texture.minFilter = LinearFilter7;
14403
14651
  texture.generateMipmaps = false;
14404
14652
  const notifyState = (state) => {
14405
14653
  if (active) {
@@ -14495,8 +14743,8 @@ function VideoHotspot({
14495
14743
  src
14496
14744
  });
14497
14745
  const posterTexture = usePosterTexture(id, poster, onError);
14498
- const onPlaybackErrorRef = useRef29(onPlaybackError);
14499
- const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
14746
+ const onPlaybackErrorRef = useRef30(onPlaybackError);
14747
+ const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
14500
14748
  onPlaybackErrorRef.current = onPlaybackError;
14501
14749
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
14502
14750
  useLayoutEffect8(() => {
@@ -14537,18 +14785,18 @@ function VideoHotspot({
14537
14785
 
14538
14786
  // src/hotspot/audio-hotspot.tsx
14539
14787
  import { useFrame as useFrame18, useThree as useThree23 } from "@react-three/fiber";
14540
- import { useEffect as useEffect30, useRef as useRef30, useState as useState19 } from "react";
14788
+ import { useEffect as useEffect30, useRef as useRef31, useState as useState19 } from "react";
14541
14789
  import {
14542
14790
  CanvasTexture as CanvasTexture2,
14543
- ClampToEdgeWrapping as ClampToEdgeWrapping7,
14544
- LinearFilter as LinearFilter9,
14545
- LinearMipmapLinearFilter as LinearMipmapLinearFilter6,
14546
- SRGBColorSpace as SRGBColorSpace8,
14791
+ ClampToEdgeWrapping as ClampToEdgeWrapping6,
14792
+ LinearFilter as LinearFilter8,
14793
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter7,
14794
+ SRGBColorSpace as SRGBColorSpace7,
14547
14795
  TextureLoader as TextureLoader7
14548
14796
  } from "three";
14549
14797
 
14550
14798
  // src/hotspot/audio-spatial.ts
14551
- import { MathUtils as MathUtils10, Vector3 as Vector318 } from "three";
14799
+ import { MathUtils as MathUtils11, Vector3 as Vector318 } from "three";
14552
14800
  var LOOK_DIR = new Vector318();
14553
14801
  var SOURCE_DIR = new Vector318();
14554
14802
  var RIGHT_DIR = new Vector318();
@@ -14576,11 +14824,11 @@ function lookRangeGain(angleDegrees, range) {
14576
14824
  function audioSpatialFromView(camera, position, range) {
14577
14825
  camera.getWorldDirection(LOOK_DIR);
14578
14826
  SOURCE_DIR.copy(panoPositionToVector3(position)).normalize();
14579
- const angleDegrees = MathUtils10.radToDeg(
14580
- Math.acos(MathUtils10.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
14827
+ const angleDegrees = MathUtils11.radToDeg(
14828
+ Math.acos(MathUtils11.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
14581
14829
  );
14582
14830
  RIGHT_DIR.set(1, 0, 0).applyQuaternion(camera.quaternion);
14583
- const pan = MathUtils10.clamp(
14831
+ const pan = MathUtils11.clamp(
14584
14832
  Math.atan2(SOURCE_DIR.dot(RIGHT_DIR), SOURCE_DIR.dot(LOOK_DIR)) / (Math.PI / 2),
14585
14833
  -1,
14586
14834
  1
@@ -14646,11 +14894,11 @@ function createSpeakerTexture(state) {
14646
14894
  }
14647
14895
  }
14648
14896
  const texture = new CanvasTexture2(canvas);
14649
- texture.colorSpace = SRGBColorSpace8;
14650
- texture.wrapS = ClampToEdgeWrapping7;
14651
- texture.wrapT = ClampToEdgeWrapping7;
14652
- texture.magFilter = LinearFilter9;
14653
- texture.minFilter = LinearMipmapLinearFilter6;
14897
+ texture.colorSpace = SRGBColorSpace7;
14898
+ texture.wrapS = ClampToEdgeWrapping6;
14899
+ texture.wrapT = ClampToEdgeWrapping6;
14900
+ texture.magFilter = LinearFilter8;
14901
+ texture.minFilter = LinearMipmapLinearFilter7;
14654
14902
  texture.generateMipmaps = true;
14655
14903
  texture.needsUpdate = true;
14656
14904
  return texture;
@@ -14675,7 +14923,7 @@ function useSpeakerTextures(enabled) {
14675
14923
  function useIconTexture(icon, enabled, onError, id) {
14676
14924
  const gl = useThree23((state) => state.gl);
14677
14925
  const [texture, setTexture] = useState19(null);
14678
- const onErrorRef = useRef30(onError);
14926
+ const onErrorRef = useRef31(onError);
14679
14927
  onErrorRef.current = onError;
14680
14928
  useEffect30(() => {
14681
14929
  let active = true;
@@ -14693,11 +14941,11 @@ function useIconTexture(icon, enabled, onError, id) {
14693
14941
  loadedTexture.dispose();
14694
14942
  return;
14695
14943
  }
14696
- loadedTexture.colorSpace = SRGBColorSpace8;
14697
- loadedTexture.wrapS = ClampToEdgeWrapping7;
14698
- loadedTexture.wrapT = ClampToEdgeWrapping7;
14699
- loadedTexture.magFilter = LinearFilter9;
14700
- loadedTexture.minFilter = LinearMipmapLinearFilter6;
14944
+ loadedTexture.colorSpace = SRGBColorSpace7;
14945
+ loadedTexture.wrapS = ClampToEdgeWrapping6;
14946
+ loadedTexture.wrapT = ClampToEdgeWrapping6;
14947
+ loadedTexture.magFilter = LinearFilter8;
14948
+ loadedTexture.minFilter = LinearMipmapLinearFilter7;
14701
14949
  loadedTexture.generateMipmaps = true;
14702
14950
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14703
14951
  loadedTexture.needsUpdate = true;
@@ -14735,12 +14983,12 @@ function useAudioHowl({
14735
14983
  format
14736
14984
  }) {
14737
14985
  const [howl, setHowl] = useState19(null);
14738
- const playingRef = useRef30(playing);
14739
- const loopRef = useRef30(loop ?? false);
14740
- const onEndedRef = useRef30(onEnded);
14741
- const onErrorRef = useRef30(onError);
14742
- const onPlaybackErrorRef = useRef30(onPlaybackError);
14743
- const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
14986
+ const playingRef = useRef31(playing);
14987
+ const loopRef = useRef31(loop ?? false);
14988
+ const onEndedRef = useRef31(onEnded);
14989
+ const onErrorRef = useRef31(onError);
14990
+ const onPlaybackErrorRef = useRef31(onPlaybackError);
14991
+ const onPlaybackStateChangeRef = useRef31(onPlaybackStateChange);
14744
14992
  playingRef.current = playing;
14745
14993
  loopRef.current = loop ?? false;
14746
14994
  onEndedRef.current = onEnded;
@@ -14833,11 +15081,11 @@ function AudioHotspot({
14833
15081
  const stoppedIconTexture = useIconTexture(icon, marker, onError, id);
14834
15082
  const playingIconTexture = useIconTexture(playingIcon, marker, onError, id);
14835
15083
  const markerTexture = playing ? playingIconTexture ?? stoppedIconTexture ?? speakerTextures.playing : stoppedIconTexture ?? speakerTextures.stopped;
14836
- const playingRef = useRef30(playing);
15084
+ const playingRef = useRef31(playing);
14837
15085
  playingRef.current = playing;
14838
- const mutedRef = useRef30(muted);
14839
- const volumeRef = useRef30(volume);
14840
- const rangeRef = useRef30(range);
15086
+ const mutedRef = useRef31(muted);
15087
+ const volumeRef = useRef31(volume);
15088
+ const rangeRef = useRef31(range);
14841
15089
  mutedRef.current = muted;
14842
15090
  volumeRef.current = volume;
14843
15091
  rangeRef.current = range;
@@ -14886,15 +15134,15 @@ function AudioHotspot({
14886
15134
  }
14887
15135
 
14888
15136
  // src/hotspot/text-hotspot.tsx
14889
- import { useEffect as useEffect31, useRef as useRef31, useState as useState20 } from "react";
15137
+ import { useEffect as useEffect31, useRef as useRef32, useState as useState20 } from "react";
14890
15138
  import { useFrame as useFrame19, useThree as useThree24 } from "@react-three/fiber";
14891
15139
  import {
14892
15140
  CanvasTexture as CanvasTexture3,
14893
- ClampToEdgeWrapping as ClampToEdgeWrapping8,
14894
- LinearFilter as LinearFilter10,
14895
- MathUtils as MathUtils11,
15141
+ ClampToEdgeWrapping as ClampToEdgeWrapping7,
15142
+ LinearFilter as LinearFilter9,
15143
+ MathUtils as MathUtils12,
14896
15144
  PerspectiveCamera as PerspectiveCamera9,
14897
- SRGBColorSpace as SRGBColorSpace9
15145
+ SRGBColorSpace as SRGBColorSpace8
14898
15146
  } from "three";
14899
15147
  import { jsx as jsx34 } from "react/jsx-runtime";
14900
15148
  var TEXT_TEXTURE_LONG_SIDE = 1024;
@@ -14967,7 +15215,7 @@ function projectedDevicePixels(angleDeg, fovDeg, viewportHeight) {
14967
15215
  }
14968
15216
  const fov = Math.min(179, Math.max(1, resolvePositive(fovDeg, DEFAULT_REFERENCE_FOV2)));
14969
15217
  const angle = Math.min(179, Math.max(0.01, Math.abs(angleDeg)));
14970
- return Math.tan(MathUtils11.degToRad(angle) / 2) / Math.tan(MathUtils11.degToRad(fov) / 2) * viewportHeight;
15218
+ return Math.tan(MathUtils12.degToRad(angle) / 2) / Math.tan(MathUtils12.degToRad(fov) / 2) * viewportHeight;
14971
15219
  }
14972
15220
  function snapRasterLongSide(screenLong, layoutLong) {
14973
15221
  if (!(screenLong > 0)) {
@@ -15157,11 +15405,11 @@ function createTextTexture(text, width, height, style, rasterLong) {
15157
15405
  );
15158
15406
  drawTextHotspot(context, layout.canvasWidth, layout.canvasHeight, text, style);
15159
15407
  const texture = new CanvasTexture3(canvas);
15160
- texture.colorSpace = SRGBColorSpace9;
15161
- texture.wrapS = ClampToEdgeWrapping8;
15162
- texture.wrapT = ClampToEdgeWrapping8;
15163
- texture.magFilter = LinearFilter10;
15164
- texture.minFilter = LinearFilter10;
15408
+ texture.colorSpace = SRGBColorSpace8;
15409
+ texture.wrapS = ClampToEdgeWrapping7;
15410
+ texture.wrapT = ClampToEdgeWrapping7;
15411
+ texture.magFilter = LinearFilter9;
15412
+ texture.minFilter = LinearFilter9;
15165
15413
  texture.generateMipmaps = false;
15166
15414
  texture.needsUpdate = true;
15167
15415
  return texture;
@@ -15206,8 +15454,8 @@ function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
15206
15454
  function useTextTexture(text, width, height, style, scale, scaleMode, referenceFov, onLoad, onError) {
15207
15455
  const rasterLong = useRasterLongSide(width, height, scale, scaleMode, referenceFov);
15208
15456
  const [texture, setTexture] = useState20(null);
15209
- const onLoadRef = useRef31(onLoad);
15210
- const onErrorRef = useRef31(onError);
15457
+ const onLoadRef = useRef32(onLoad);
15458
+ const onErrorRef = useRef32(onError);
15211
15459
  onLoadRef.current = onLoad;
15212
15460
  onErrorRef.current = onError;
15213
15461
  useEffect31(() => {
@@ -15436,7 +15684,7 @@ function IframeHotspot({
15436
15684
  }
15437
15685
 
15438
15686
  // src/hotspot/polygon-hotspot.tsx
15439
- import { useEffect as useEffect32, useMemo as useMemo20, useRef as useRef32 } from "react";
15687
+ import { useEffect as useEffect32, useMemo as useMemo20, useRef as useRef33 } from "react";
15440
15688
  import {
15441
15689
  BufferGeometry as BufferGeometry2,
15442
15690
  DoubleSide as DoubleSide5,
@@ -15715,7 +15963,7 @@ function PolygonHotspot({
15715
15963
  [center, valid, vertices]
15716
15964
  );
15717
15965
  const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
15718
- const startVerticesRef = useRef32(null);
15966
+ const startVerticesRef = useRef33(null);
15719
15967
  const issuesKey = issues.map((issue) => issue.code).join(",");
15720
15968
  useEffect32(() => {
15721
15969
  if (!valid) onInvalid?.(issues);
@@ -15800,9 +16048,9 @@ function PolygonHotspot({
15800
16048
  }
15801
16049
 
15802
16050
  // src/hotspot/polyline-hotspot.tsx
15803
- import { useEffect as useEffect33, useMemo as useMemo21, useRef as useRef33 } from "react";
16051
+ import { useEffect as useEffect33, useMemo as useMemo21, useRef as useRef34 } from "react";
15804
16052
  import {
15805
- MathUtils as MathUtils12,
16053
+ MathUtils as MathUtils13,
15806
16054
  Matrix4 as Matrix46,
15807
16055
  Quaternion as Quaternion6,
15808
16056
  Vector3 as Vector320
@@ -15839,8 +16087,8 @@ function polylineCenter(vertices) {
15839
16087
  function surfaceQuaternion2(position) {
15840
16088
  const outward = panoPositionToVector3(position).normalize();
15841
16089
  const inward = outward.clone().negate();
15842
- const pitch = MathUtils12.degToRad(position.pitch);
15843
- const yaw = MathUtils12.degToRad(position.yaw);
16090
+ const pitch = MathUtils13.degToRad(position.pitch);
16091
+ const yaw = MathUtils13.degToRad(position.yaw);
15844
16092
  const localUp = new Vector320(
15845
16093
  -Math.sin(pitch) * Math.sin(yaw),
15846
16094
  Math.cos(pitch),
@@ -15869,7 +16117,7 @@ function subdivisionSteps(vertices) {
15869
16117
  }
15870
16118
  return Math.min(
15871
16119
  8,
15872
- Math.max(1, Math.ceil(MathUtils12.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
16120
+ Math.max(1, Math.ceil(MathUtils13.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
15873
16121
  );
15874
16122
  }
15875
16123
  function makeLinePositions(vertices, center) {
@@ -15932,7 +16180,7 @@ function PolylineHotspot({
15932
16180
  [center, valid, vertices]
15933
16181
  );
15934
16182
  const opacity = Math.max(0, Math.min(strokeOpacity, 1));
15935
- const startVerticesRef = useRef33(null);
16183
+ const startVerticesRef = useRef34(null);
15936
16184
  useEffect33(() => {
15937
16185
  if (!valid) onInvalid?.(issues);
15938
16186
  }, [issues, onInvalid, valid]);
@@ -16249,7 +16497,7 @@ function ProjectScenes({
16249
16497
  }
16250
16498
 
16251
16499
  // src/background-audio.tsx
16252
- import { useContext as useContext23, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef34, useState as useState22 } from "react";
16500
+ import { useContext as useContext23, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef35, useState as useState22 } from "react";
16253
16501
  var DEFAULT_FADE_MS = 400;
16254
16502
  function resolveFadeMs(value) {
16255
16503
  if (!Number.isFinite(value)) {
@@ -16301,21 +16549,21 @@ function BackgroundAudio({
16301
16549
  const urls = resolveBackgroundAudioUrls(src, sources, sceneId);
16302
16550
  usePanoMediaActivationIntent(playing && urls.length > 0);
16303
16551
  const trackKey = urls.join("\0");
16304
- const currentRef = useRef34(null);
16305
- const outgoingRef = useRef34([]);
16306
- const fadingInRef = useRef34(false);
16307
- const isFirstTrackRef = useRef34(true);
16308
- const playingRef = useRef34(playing);
16309
- const loopRef = useRef34(loop);
16310
- const mutedRef = useRef34(muted);
16311
- const volumeRef = useRef34(volume);
16312
- const mutedPropRef = useRef34(muted);
16313
- const volumePropRef = useRef34(volume);
16314
- const fadeMsRef = useRef34(resolveFadeMs(fadeMs));
16315
- const onPlaybackStateChangeRef = useRef34(onPlaybackStateChange);
16316
- const onPlaybackErrorRef = useRef34(onPlaybackError);
16317
- const onEndedRef = useRef34(onEnded);
16318
- const onErrorRef = useRef34(onError);
16552
+ const currentRef = useRef35(null);
16553
+ const outgoingRef = useRef35([]);
16554
+ const fadingInRef = useRef35(false);
16555
+ const isFirstTrackRef = useRef35(true);
16556
+ const playingRef = useRef35(playing);
16557
+ const loopRef = useRef35(loop);
16558
+ const mutedRef = useRef35(muted);
16559
+ const volumeRef = useRef35(volume);
16560
+ const mutedPropRef = useRef35(muted);
16561
+ const volumePropRef = useRef35(volume);
16562
+ const fadeMsRef = useRef35(resolveFadeMs(fadeMs));
16563
+ const onPlaybackStateChangeRef = useRef35(onPlaybackStateChange);
16564
+ const onPlaybackErrorRef = useRef35(onPlaybackError);
16565
+ const onEndedRef = useRef35(onEnded);
16566
+ const onErrorRef = useRef35(onError);
16319
16567
  playingRef.current = playing;
16320
16568
  loopRef.current = loop;
16321
16569
  fadeMsRef.current = resolveFadeMs(fadeMs);
@@ -16331,7 +16579,7 @@ function BackgroundAudio({
16331
16579
  onPlaybackErrorRef.current = onPlaybackError;
16332
16580
  onEndedRef.current = onEnded;
16333
16581
  onErrorRef.current = onError;
16334
- const snapshotRef = useRef34({
16582
+ const snapshotRef = useRef35({
16335
16583
  ready: false,
16336
16584
  playing: false,
16337
16585
  blocked: false,
@@ -16339,7 +16587,7 @@ function BackgroundAudio({
16339
16587
  muted,
16340
16588
  volume
16341
16589
  });
16342
- const snapshotListenersRef = useRef34(/* @__PURE__ */ new Set());
16590
+ const snapshotListenersRef = useRef35(/* @__PURE__ */ new Set());
16343
16591
  const notifySnapshot = (patch) => {
16344
16592
  const prev = snapshotRef.current;
16345
16593
  snapshotRef.current = { ...prev, ...patch };
@@ -16347,7 +16595,7 @@ function BackgroundAudio({
16347
16595
  listener();
16348
16596
  }
16349
16597
  };
16350
- const isControlledRef = useRef34(isControlled);
16598
+ const isControlledRef = useRef35(isControlled);
16351
16599
  isControlledRef.current = isControlled;
16352
16600
  const controller = useMemo23(
16353
16601
  () => ({
@@ -16635,11 +16883,11 @@ function PanoFilter({
16635
16883
  import { useFrame as useFrame20, useThree as useThree25 } from "@react-three/fiber";
16636
16884
  import {
16637
16885
  forwardRef as forwardRef3,
16638
- useCallback as useCallback9,
16886
+ useCallback as useCallback10,
16639
16887
  useContext as useContext25,
16640
16888
  useEffect as useEffect36,
16641
16889
  useImperativeHandle as useImperativeHandle3,
16642
- useRef as useRef35,
16890
+ useRef as useRef36,
16643
16891
  useState as useState23
16644
16892
  } from "react";
16645
16893
  import { Quaternion as Quaternion7 } from "three";
@@ -16679,30 +16927,30 @@ var Gyro = forwardRef3(function Gyro2({
16679
16927
  const { gl } = useThree25();
16680
16928
  const [internallyDisabled, setInternallyDisabled] = useState23(false);
16681
16929
  const [available, setAvailableState] = useState23(false);
16682
- const availableRef = useRef35(false);
16683
- const availabilityReportedRef = useRef35(null);
16684
- const activeRef = useRef35(false);
16685
- const permissionRef = useRef35(deviceOrientationPermissionState);
16686
- const absoluteEventSourceRef = useRef35(null);
16687
- const rawQuaternionRef = useRef35(new Quaternion7());
16688
- const hasRawQuaternionRef = useRef35(false);
16689
- const calibrationRef = useRef35(new Quaternion7());
16690
- const hasCalibrationRef = useRef35(false);
16691
- const calibratedQuaternionRef = useRef35(new Quaternion7());
16692
- const absoluteOffsetRef = useRef35({ yaw: 0, pitch: 0 });
16693
- const screenAngleRef = useRef35(getScreenOrientationAngle());
16694
- const startPoseRef = useRef35(null);
16695
- const enabledAtRef = useRef35(0);
16696
- const filteredPoseRef = useRef35(null);
16697
- const lastAppliedPoseRef = useRef35(null);
16698
- const lastSamplePoseRef = useRef35(null);
16699
- const lastSampleAtRef = useRef35(null);
16700
- const previousYawRef = useRef35(void 0);
16701
- const absoluteRef = useRef35(absolute);
16702
- const northRef = useRef35(north);
16930
+ const availableRef = useRef36(false);
16931
+ const availabilityReportedRef = useRef36(null);
16932
+ const activeRef = useRef36(false);
16933
+ const permissionRef = useRef36(deviceOrientationPermissionState);
16934
+ const absoluteEventSourceRef = useRef36(null);
16935
+ const rawQuaternionRef = useRef36(new Quaternion7());
16936
+ const hasRawQuaternionRef = useRef36(false);
16937
+ const calibrationRef = useRef36(new Quaternion7());
16938
+ const hasCalibrationRef = useRef36(false);
16939
+ const calibratedQuaternionRef = useRef36(new Quaternion7());
16940
+ const absoluteOffsetRef = useRef36({ yaw: 0, pitch: 0 });
16941
+ const screenAngleRef = useRef36(getScreenOrientationAngle());
16942
+ const startPoseRef = useRef36(null);
16943
+ const enabledAtRef = useRef36(0);
16944
+ const filteredPoseRef = useRef36(null);
16945
+ const lastAppliedPoseRef = useRef36(null);
16946
+ const lastSamplePoseRef = useRef36(null);
16947
+ const lastSampleAtRef = useRef36(null);
16948
+ const previousYawRef = useRef36(void 0);
16949
+ const absoluteRef = useRef36(absolute);
16950
+ const northRef = useRef36(north);
16703
16951
  absoluteRef.current = absolute;
16704
16952
  northRef.current = north;
16705
- const callbacksRef = useRef35({
16953
+ const callbacksRef = useRef36({
16706
16954
  onAvailable,
16707
16955
  onUnavailable,
16708
16956
  onEnable,
@@ -16719,7 +16967,7 @@ var Gyro = forwardRef3(function Gyro2({
16719
16967
  if (!controlsRef) {
16720
16968
  throw new Error("<Gyro> must be rendered inside <PanoViewer>.");
16721
16969
  }
16722
- const emit = useCallback9(
16970
+ const emit = useCallback10(
16723
16971
  (type) => {
16724
16972
  eventBus?.emit(type, void 0);
16725
16973
  switch (type) {
@@ -16746,7 +16994,7 @@ var Gyro = forwardRef3(function Gyro2({
16746
16994
  },
16747
16995
  [eventBus]
16748
16996
  );
16749
- const setAvailable = useCallback9(
16997
+ const setAvailable = useCallback10(
16750
16998
  (nextAvailable) => {
16751
16999
  availableRef.current = nextAvailable;
16752
17000
  setAvailableState(nextAvailable);
@@ -16758,7 +17006,7 @@ var Gyro = forwardRef3(function Gyro2({
16758
17006
  },
16759
17007
  [emit]
16760
17008
  );
16761
- const requestPermission = useCallback9(async () => {
17009
+ const requestPermission = useCallback10(async () => {
16762
17010
  if (!availableRef.current) {
16763
17011
  return false;
16764
17012
  }
@@ -16791,7 +17039,7 @@ var Gyro = forwardRef3(function Gyro2({
16791
17039
  emit("gyrodenied");
16792
17040
  return false;
16793
17041
  }, [emit, setAvailable]);
16794
- const deactivate = useCallback9(() => {
17042
+ const deactivate = useCallback10(() => {
16795
17043
  if (activeRef.current) {
16796
17044
  activeRef.current = false;
16797
17045
  controlsRef.current?.setGyroActive(false);
@@ -16806,7 +17054,7 @@ var Gyro = forwardRef3(function Gyro2({
16806
17054
  lastSampleAtRef.current = null;
16807
17055
  previousYawRef.current = void 0;
16808
17056
  }, [controlsRef, emit]);
16809
- const anchorToView = useCallback9(
17057
+ const anchorToView = useCallback10(
16810
17058
  (yaw, pitch) => {
16811
17059
  if (!hasRawQuaternionRef.current) {
16812
17060
  return;
@@ -16836,7 +17084,7 @@ var Gyro = forwardRef3(function Gyro2({
16836
17084
  },
16837
17085
  []
16838
17086
  );
16839
- const resetSensor = useCallback9(
17087
+ const resetSensor = useCallback10(
16840
17088
  (yaw, pitch) => {
16841
17089
  if (!hasRawQuaternionRef.current) {
16842
17090
  return;
@@ -17118,12 +17366,12 @@ var Gyro = forwardRef3(function Gyro2({
17118
17366
  import { useThree as useThree28 } from "@react-three/fiber";
17119
17367
  import {
17120
17368
  forwardRef as forwardRef4,
17121
- useCallback as useCallback10,
17369
+ useCallback as useCallback11,
17122
17370
  useContext as useContext27,
17123
17371
  useEffect as useEffect40,
17124
17372
  useImperativeHandle as useImperativeHandle4,
17125
17373
  useMemo as useMemo26,
17126
- useRef as useRef39,
17374
+ useRef as useRef40,
17127
17375
  useState as useState24
17128
17376
  } from "react";
17129
17377
 
@@ -17146,6 +17394,184 @@ async function detectWebVRCapabilities() {
17146
17394
  return { webxr, mobile, sensor };
17147
17395
  }
17148
17396
 
17397
+ // src/webvr/dpdb.ts
17398
+ var DEFAULT_WEBVR_DATABASE_URL = "https://storage.googleapis.com/cardboard-dpdb/dpdb.json";
17399
+ var SCREENSIZE_MIN = 3;
17400
+ var SCREENSIZE_MAX = 20;
17401
+ var cache = /* @__PURE__ */ new Map();
17402
+ function normalizeDatabaseUrl(value) {
17403
+ if (value === null || value === "") {
17404
+ return null;
17405
+ }
17406
+ if (value === void 0) {
17407
+ return DEFAULT_WEBVR_DATABASE_URL;
17408
+ }
17409
+ const trimmed = value.trim();
17410
+ return trimmed.length > 0 ? trimmed : null;
17411
+ }
17412
+ function physicalResolution(screenWidth, screenHeight, devicePixelRatio) {
17413
+ const ratio = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
17414
+ return [
17415
+ Math.round(Math.max(0, screenWidth) * ratio),
17416
+ Math.round(Math.max(0, screenHeight) * ratio)
17417
+ ];
17418
+ }
17419
+ function resolutionsMatch(rule, physical) {
17420
+ const [rw, rh] = rule;
17421
+ const [pw, ph] = physical;
17422
+ return rw === pw && rh === ph || rw === ph && rh === pw;
17423
+ }
17424
+ function ruleMatches(rule, input, physical) {
17425
+ if (typeof rule.ua === "string") {
17426
+ return input.userAgent.includes(rule.ua);
17427
+ }
17428
+ if (Array.isArray(rule.res) && rule.res.length === 2 && typeof rule.res[0] === "number" && typeof rule.res[1] === "number") {
17429
+ return resolutionsMatch(
17430
+ [rule.res[0], rule.res[1]],
17431
+ physical
17432
+ );
17433
+ }
17434
+ return false;
17435
+ }
17436
+ function deviceMatches(device, input) {
17437
+ const rules = device.rules;
17438
+ if (!Array.isArray(rules) || rules.length === 0) {
17439
+ return false;
17440
+ }
17441
+ const physical = physicalResolution(
17442
+ input.screenWidth,
17443
+ input.screenHeight,
17444
+ input.devicePixelRatio
17445
+ );
17446
+ return rules.some(
17447
+ (rule) => rule !== null && typeof rule === "object" && ruleMatches(rule, input, physical)
17448
+ );
17449
+ }
17450
+ function diagonalInchesFromDpi(dpi, physicalWidth, physicalHeight) {
17451
+ const shortPx = Math.min(physicalWidth, physicalHeight);
17452
+ const longPx = Math.max(physicalWidth, physicalHeight);
17453
+ if (!(shortPx > 0 && longPx > 0)) {
17454
+ return null;
17455
+ }
17456
+ let dpiX;
17457
+ let dpiY;
17458
+ if (typeof dpi === "number") {
17459
+ if (!(Number.isFinite(dpi) && dpi > 0)) {
17460
+ return null;
17461
+ }
17462
+ dpiX = dpi;
17463
+ dpiY = dpi;
17464
+ } else if (Array.isArray(dpi) && dpi.length >= 1) {
17465
+ const first = dpi[0];
17466
+ const second = dpi.length >= 2 ? dpi[1] : first;
17467
+ if (typeof first !== "number" || typeof second !== "number" || !(Number.isFinite(first) && first > 0) || !(Number.isFinite(second) && second > 0)) {
17468
+ return null;
17469
+ }
17470
+ dpiX = first;
17471
+ dpiY = second;
17472
+ } else {
17473
+ return null;
17474
+ }
17475
+ const widthIn = shortPx / dpiX;
17476
+ const heightIn = longPx / dpiY;
17477
+ const diagonal = Math.sqrt(widthIn * widthIn + heightIn * heightIn);
17478
+ if (!Number.isFinite(diagonal) || diagonal < SCREENSIZE_MIN || diagonal > SCREENSIZE_MAX) {
17479
+ return null;
17480
+ }
17481
+ return Math.round(diagonal * 10) / 10;
17482
+ }
17483
+ function matchDpdbDevice(document2, input) {
17484
+ const devices = document2.devices;
17485
+ if (!Array.isArray(devices)) {
17486
+ return null;
17487
+ }
17488
+ const physical = physicalResolution(
17489
+ input.screenWidth,
17490
+ input.screenHeight,
17491
+ input.devicePixelRatio
17492
+ );
17493
+ for (const device of devices) {
17494
+ if (!deviceMatches(device, input)) {
17495
+ continue;
17496
+ }
17497
+ if (device.dpi === void 0) {
17498
+ continue;
17499
+ }
17500
+ const screensizeInches = diagonalInchesFromDpi(
17501
+ device.dpi,
17502
+ physical[0],
17503
+ physical[1]
17504
+ );
17505
+ if (screensizeInches === null) {
17506
+ continue;
17507
+ }
17508
+ return { device, screensizeInches };
17509
+ }
17510
+ return null;
17511
+ }
17512
+ function readDpdbMatchInput(globalObject = globalThis) {
17513
+ const navigatorObject = globalObject.navigator;
17514
+ const screenObject = globalObject.screen;
17515
+ if (!navigatorObject || !screenObject) {
17516
+ return null;
17517
+ }
17518
+ const width = screenObject.width;
17519
+ const height = screenObject.height;
17520
+ if (typeof width !== "number" || typeof height !== "number" || !(width > 0 && height > 0)) {
17521
+ return null;
17522
+ }
17523
+ const dpr = typeof globalObject.devicePixelRatio === "number" ? globalObject.devicePixelRatio : 1;
17524
+ return {
17525
+ userAgent: navigatorObject.userAgent ?? "",
17526
+ screenWidth: width,
17527
+ screenHeight: height,
17528
+ devicePixelRatio: dpr
17529
+ };
17530
+ }
17531
+ async function fetchDpdbDocument(url) {
17532
+ try {
17533
+ const response = await fetch(url, {
17534
+ credentials: "omit",
17535
+ mode: "cors"
17536
+ });
17537
+ if (!response.ok) {
17538
+ return null;
17539
+ }
17540
+ const data = await response.json();
17541
+ if (data === null || typeof data !== "object" || !("devices" in data) || !Array.isArray(data.devices)) {
17542
+ return null;
17543
+ }
17544
+ return data;
17545
+ } catch {
17546
+ return null;
17547
+ }
17548
+ }
17549
+ function loadDpdbDocument(url) {
17550
+ const existing = cache.get(url);
17551
+ if (existing) {
17552
+ return existing;
17553
+ }
17554
+ const pending = fetchDpdbDocument(url).then((document2) => {
17555
+ if (!document2) {
17556
+ cache.delete(url);
17557
+ }
17558
+ return document2;
17559
+ });
17560
+ cache.set(url, pending);
17561
+ return pending;
17562
+ }
17563
+ async function resolveScreensizeFromDpdb(databaseUrl, input = readDpdbMatchInput()) {
17564
+ const url = normalizeDatabaseUrl(databaseUrl);
17565
+ if (!url || !input) {
17566
+ return null;
17567
+ }
17568
+ const document2 = await loadDpdbDocument(url);
17569
+ if (!document2) {
17570
+ return null;
17571
+ }
17572
+ return matchDpdbDevice(document2, input)?.screensizeInches ?? null;
17573
+ }
17574
+
17149
17575
  // src/webvr/presentation.ts
17150
17576
  var PRESENTATION_PROPERTIES = [
17151
17577
  "background",
@@ -17303,7 +17729,7 @@ function subscribeWebVRPresentationExit(element, onExit) {
17303
17729
 
17304
17730
  // src/webvr/reticle.tsx
17305
17731
  import { useFrame as useFrame21, useThree as useThree26 } from "@react-three/fiber";
17306
- import { useRef as useRef36 } from "react";
17732
+ import { useRef as useRef37 } from "react";
17307
17733
  import {
17308
17734
  DoubleSide as DoubleSide6,
17309
17735
  Raycaster as Raycaster4,
@@ -17396,13 +17822,13 @@ function WebVRReticle({
17396
17822
  scale = RETICLE_SCALE,
17397
17823
  visible
17398
17824
  }) {
17399
- const groupRef = useRef36(null);
17400
- const ringMeshRef = useRef36(null);
17401
- const directionRef = useRef36(new Vector321());
17402
- const raycasterRef = useRef36(new Raycaster4());
17403
- const hoveredRef = useRef36(null);
17404
- const dwellRef = useRef36(0);
17405
- const clickedRef = useRef36(false);
17825
+ const groupRef = useRef37(null);
17826
+ const ringMeshRef = useRef37(null);
17827
+ const directionRef = useRef37(new Vector321());
17828
+ const raycasterRef = useRef37(new Raycaster4());
17829
+ const hoveredRef = useRef37(null);
17830
+ const dwellRef = useRef37(0);
17831
+ const clickedRef = useRef37(false);
17406
17832
  const { camera, gl, scene } = useThree26();
17407
17833
  useFrame21((_, deltaSeconds) => {
17408
17834
  const group = groupRef.current;
@@ -17565,7 +17991,7 @@ function WebVRReticle({
17565
17991
 
17566
17992
  // src/webvr/stereo.tsx
17567
17993
  import { useFrame as useFrame22, useThree as useThree27 } from "@react-three/fiber";
17568
- import { useContext as useContext26, useEffect as useEffect37, useMemo as useMemo25, useRef as useRef37 } from "react";
17994
+ import { useContext as useContext26, useEffect as useEffect37, useMemo as useMemo25, useRef as useRef38 } from "react";
17569
17995
  import {
17570
17996
  Matrix4 as Matrix47,
17571
17997
  Mesh as Mesh6,
@@ -17580,6 +18006,40 @@ import {
17580
18006
  WebGLRenderTarget as WebGLRenderTarget2
17581
18007
  } from "three";
17582
18008
 
18009
+ // src/webvr/lens-center.ts
18010
+ function physicalScreenFromDiagonal(screensizeInches, displayAspect) {
18011
+ if (!(Number.isFinite(screensizeInches) && screensizeInches > 0 && Number.isFinite(displayAspect) && displayAspect > 0)) {
18012
+ return null;
18013
+ }
18014
+ const diagonalMm = screensizeInches * 25.4;
18015
+ const heightMm = diagonalMm / Math.sqrt(displayAspect * displayAspect + 1);
18016
+ const widthMm = heightMm * displayAspect;
18017
+ if (!(heightMm > 0 && widthMm > 0)) {
18018
+ return null;
18019
+ }
18020
+ return { widthMm, heightMm };
18021
+ }
18022
+ function computeStereoLensCenters(profile, screensize, displayAspect) {
18023
+ const zero = { left: { x: 0, y: 0 }, right: { x: 0, y: 0 } };
18024
+ if (screensize === "auto") {
18025
+ return zero;
18026
+ }
18027
+ const screen = physicalScreenFromDiagonal(screensize, displayAspect);
18028
+ if (!screen) {
18029
+ return zero;
18030
+ }
18031
+ const halfEyeWidthMm = screen.widthMm / 4;
18032
+ const ipdMm = Number.isFinite(profile.ipdMm) ? profile.ipdMm : 60;
18033
+ const x = halfEyeWidthMm > 0 ? (halfEyeWidthMm - ipdMm / 2) / halfEyeWidthMm : 0;
18034
+ const trayDistanceMm = typeof profile.trayDistanceMm === "number" && Number.isFinite(profile.trayDistanceMm) && profile.trayDistanceMm > 0 ? profile.trayDistanceMm : 0;
18035
+ const y = trayDistanceMm > 0 && screen.heightMm > 0 ? 2 * trayDistanceMm / screen.heightMm - 1 : 0;
18036
+ const clampUnit = (value) => Math.max(-1, Math.min(1, Number.isFinite(value) ? value : 0));
18037
+ return {
18038
+ left: { x: clampUnit(x), y: clampUnit(y) },
18039
+ right: { x: clampUnit(-x), y: clampUnit(y) }
18040
+ };
18041
+ }
18042
+
17583
18043
  // src/webvr/stereo-disparity.ts
17584
18044
  var WEBVR_PANORAMA_VIRTUAL_DISTANCE_M = 2;
17585
18045
  function panoramaStereoHalfAngle(ipdMm, virtualDistanceM = WEBVR_PANORAMA_VIRTUAL_DISTANCE_M) {
@@ -17595,19 +18055,32 @@ function panoramaStereoToeInYaw(eye, halfAngleRad) {
17595
18055
  }
17596
18056
 
17597
18057
  // src/webvr/stereo-layout.ts
17598
- function computeStereoLayout(logicalWidth, logicalHeight, pixelRatio) {
18058
+ var DEFAULT_WEBVR_OVERSAMPLING = 1;
18059
+ var MIN_WEBVR_OVERSAMPLING = 0.5;
18060
+ var MAX_WEBVR_OVERSAMPLING = 2;
18061
+ function clampWebVROversampling(value) {
18062
+ if (typeof value !== "number" || !Number.isFinite(value)) {
18063
+ return DEFAULT_WEBVR_OVERSAMPLING;
18064
+ }
18065
+ return Math.max(
18066
+ MIN_WEBVR_OVERSAMPLING,
18067
+ Math.min(MAX_WEBVR_OVERSAMPLING, value)
18068
+ );
18069
+ }
18070
+ function computeStereoLayout(logicalWidth, logicalHeight, pixelRatio, oversampling = DEFAULT_WEBVR_OVERSAMPLING) {
17599
18071
  const fullWidth = Math.max(2, Math.floor(logicalWidth));
17600
18072
  const fullHeight = Math.max(1, Math.floor(logicalHeight));
17601
18073
  const leftWidth = Math.max(1, Math.floor(fullWidth / 2));
17602
18074
  const rightWidth = Math.max(1, fullWidth - leftWidth);
17603
18075
  const ratio = Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1;
18076
+ const scale = clampWebVROversampling(oversampling);
17604
18077
  return {
17605
18078
  fullWidth,
17606
18079
  fullHeight,
17607
18080
  left: { width: leftWidth, height: fullHeight, offsetX: 0 },
17608
18081
  right: { width: rightWidth, height: fullHeight, offsetX: leftWidth },
17609
- renderTargetWidth: Math.max(1, Math.floor(leftWidth * ratio)),
17610
- renderTargetHeight: Math.max(1, Math.floor(fullHeight * ratio)),
18082
+ renderTargetWidth: Math.max(1, Math.floor(leftWidth * ratio * scale)),
18083
+ renderTargetHeight: Math.max(1, Math.floor(fullHeight * ratio * scale)),
17611
18084
  stereoAspect: leftWidth / fullWidth
17612
18085
  };
17613
18086
  }
@@ -17624,12 +18097,13 @@ void main() {
17624
18097
  var fragmentShader = `
17625
18098
  uniform sampler2D sourceTexture;
17626
18099
  uniform vec2 distortion;
18100
+ uniform vec2 lensCenter;
17627
18101
  varying vec2 vUv;
17628
18102
  void main() {
17629
- vec2 centered = vUv * 2.0 - 1.0;
18103
+ vec2 centered = vUv * 2.0 - 1.0 - lensCenter;
17630
18104
  float radius2 = dot(centered, centered);
17631
18105
  float scale = 1.0 + distortion.x * radius2 + distortion.y * radius2 * radius2;
17632
- vec2 sourceUv = centered * scale * 0.5 + 0.5;
18106
+ vec2 sourceUv = (centered * scale + lensCenter) * 0.5 + 0.5;
17633
18107
  if (sourceUv.x < 0.0 || sourceUv.x > 1.0 || sourceUv.y < 0.0 || sourceUv.y > 1.0) {
17634
18108
  gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
17635
18109
  } else {
@@ -17649,6 +18123,7 @@ function createDistortionResources() {
17649
18123
  fragmentShader,
17650
18124
  uniforms: {
17651
18125
  distortion: { value: new Vector28() },
18126
+ lensCenter: { value: new Vector28() },
17652
18127
  sourceTexture: { value: target.texture }
17653
18128
  },
17654
18129
  vertexShader
@@ -17665,12 +18140,27 @@ function createDistortionResources() {
17665
18140
  target
17666
18141
  };
17667
18142
  }
17668
- function syncStereoCameras(stereo, camera, profile, screensize, size, gl) {
18143
+ function applyEyeViewOffset(eyeCamera, eye, center) {
18144
+ const fullWidth = eye.width;
18145
+ const fullHeight = eye.height;
18146
+ const offsetX = ((center.x + 1) / 2 - 0.5) * fullWidth;
18147
+ const offsetY = (0.5 - (center.y + 1) / 2) * fullHeight;
18148
+ eyeCamera.setViewOffset(
18149
+ fullWidth,
18150
+ fullHeight,
18151
+ offsetX,
18152
+ offsetY,
18153
+ fullWidth,
18154
+ fullHeight
18155
+ );
18156
+ }
18157
+ function syncStereoCameras(stereo, camera, profile, screensize, oversampling, size, gl) {
17669
18158
  gl.getSize(size);
17670
18159
  const layout = computeStereoLayout(
17671
18160
  size.x,
17672
18161
  size.y,
17673
- gl.getPixelRatio()
18162
+ gl.getPixelRatio(),
18163
+ oversampling
17674
18164
  );
17675
18165
  const screenToLensMm = profile.screenToLensMm ?? 42;
17676
18166
  const displayAspect = layout.fullWidth / layout.fullHeight;
@@ -17689,34 +18179,40 @@ function syncStereoCameras(stereo, camera, profile, screensize, size, gl) {
17689
18179
  TOE_IN.makeRotationY(panoramaStereoToeInYaw("right", halfAngle));
17690
18180
  stereo.cameraR.matrix.multiply(TOE_IN);
17691
18181
  stereo.cameraR.matrixWorldNeedsUpdate = true;
18182
+ const centers = computeStereoLensCenters(profile, screensize, displayAspect);
18183
+ applyEyeViewOffset(stereo.cameraL, layout.left, centers.left);
18184
+ applyEyeViewOffset(stereo.cameraR, layout.right, centers.right);
17692
18185
  stereo.cameraL.updateMatrixWorld(true);
17693
18186
  stereo.cameraR.updateMatrixWorld(true);
17694
- return layout;
18187
+ return { layout, centers };
17695
18188
  }
17696
18189
  function WebVRStereoRenderer({
17697
18190
  mode,
18191
+ oversampling = 1,
17698
18192
  profile,
17699
18193
  screensize
17700
18194
  }) {
17701
18195
  const { camera, gl } = useThree27();
17702
18196
  const runtime = useContext26(WebVRRuntimeContext);
17703
- const fallbackStereoRef = useRef37(null);
18197
+ const fallbackStereoRef = useRef38(null);
17704
18198
  if (!fallbackStereoRef.current) {
17705
18199
  fallbackStereoRef.current = new StereoCamera2();
17706
18200
  }
17707
18201
  const stereo = runtime?.stereoView.stereo ?? fallbackStereoRef.current;
17708
- const stereoRef = useRef37(stereo);
18202
+ const stereoRef = useRef38(stereo);
17709
18203
  stereoRef.current = stereo;
17710
18204
  const resources = useMemo25(createDistortionResources, []);
17711
- const sizeRef = useRef37(new Vector28());
17712
- const viewportRef = useRef37(new Vector42());
17713
- const scissorRef = useRef37(new Vector42());
17714
- const originalFovRef = useRef37(null);
17715
- const stereoActiveRef = useRef37(false);
17716
- const profileRef = useRef37(profile);
17717
- const screensizeRef = useRef37(screensize);
18205
+ const sizeRef = useRef38(new Vector28());
18206
+ const viewportRef = useRef38(new Vector42());
18207
+ const scissorRef = useRef38(new Vector42());
18208
+ const originalFovRef = useRef38(null);
18209
+ const stereoActiveRef = useRef38(false);
18210
+ const profileRef = useRef38(profile);
18211
+ const screensizeRef = useRef38(screensize);
18212
+ const oversamplingRef = useRef38(clampWebVROversampling(oversampling));
17718
18213
  profileRef.current = profile;
17719
18214
  screensizeRef.current = screensize;
18215
+ oversamplingRef.current = clampWebVROversampling(oversampling);
17720
18216
  useEffect37(
17721
18217
  () => () => {
17722
18218
  resources.geometry.dispose();
@@ -17737,11 +18233,12 @@ function WebVRStereoRenderer({
17737
18233
  if (originalFovRef.current === null) {
17738
18234
  originalFovRef.current = cameraToRender.fov;
17739
18235
  }
17740
- const layout = syncStereoCameras(
18236
+ const { layout, centers } = syncStereoCameras(
17741
18237
  stereoRef.current,
17742
18238
  cameraToRender,
17743
18239
  currentProfile,
17744
18240
  currentScreensize,
18241
+ oversamplingRef.current,
17745
18242
  sizeRef.current,
17746
18243
  gl
17747
18244
  );
@@ -17753,7 +18250,7 @@ function WebVRStereoRenderer({
17753
18250
  gl.getScissor(scissorRef.current);
17754
18251
  gl.autoClear = true;
17755
18252
  gl.setScissorTest(true);
17756
- const renderEye = (eyeCamera, eye) => {
18253
+ const renderEye = (eyeCamera, eye, center) => {
17757
18254
  if (hasDistortion) {
17758
18255
  if (resources.target.width !== layout.renderTargetWidth || resources.target.height !== layout.renderTargetHeight) {
17759
18256
  resources.target.setSize(
@@ -17770,6 +18267,7 @@ function WebVRStereoRenderer({
17770
18267
  currentProfile.k1,
17771
18268
  currentProfile.k2
17772
18269
  );
18270
+ resources.material.uniforms.lensCenter?.value.set(center.x, center.y);
17773
18271
  originalRender(resources.scene, resources.camera);
17774
18272
  return;
17775
18273
  }
@@ -17778,8 +18276,8 @@ function WebVRStereoRenderer({
17778
18276
  gl.setScissor(eye.offsetX, 0, eye.width, eye.height);
17779
18277
  originalRender(sceneToRender, eyeCamera);
17780
18278
  };
17781
- renderEye(stereoRef.current.cameraL, layout.left);
17782
- renderEye(stereoRef.current.cameraR, layout.right);
18279
+ renderEye(stereoRef.current.cameraL, layout.left, centers.left);
18280
+ renderEye(stereoRef.current.cameraR, layout.right, centers.right);
17783
18281
  gl.setRenderTarget(previousTarget);
17784
18282
  gl.setViewport(viewportRef.current);
17785
18283
  gl.setScissor(scissorRef.current);
@@ -17810,6 +18308,7 @@ function WebVRStereoRenderer({
17810
18308
  camera,
17811
18309
  profileRef.current,
17812
18310
  screensizeRef.current,
18311
+ oversamplingRef.current,
17813
18312
  sizeRef.current,
17814
18313
  gl
17815
18314
  );
@@ -17825,7 +18324,7 @@ function WebVRStereoRenderer({
17825
18324
  }
17826
18325
 
17827
18326
  // src/webvr/pointerlock.ts
17828
- import { useEffect as useEffect38, useRef as useRef38 } from "react";
18327
+ import { useEffect as useEffect38, useRef as useRef39 } from "react";
17829
18328
  var POINTER_LOCK_CURSOR_ID = "webvr:pointerlock";
17830
18329
  async function requestWebVRPointerLock(element) {
17831
18330
  if (typeof document === "undefined") {
@@ -17858,8 +18357,8 @@ function exitWebVRPointerLock(element) {
17858
18357
  }
17859
18358
  function useWebVRPointerLock(enabled, element) {
17860
18359
  const cursor = usePanoCursor();
17861
- const cursorRef = useRef38(cursor);
17862
- const enabledRef = useRef38(enabled);
18360
+ const cursorRef = useRef39(cursor);
18361
+ const enabledRef = useRef39(enabled);
17863
18362
  cursorRef.current = cursor;
17864
18363
  enabledRef.current = enabled;
17865
18364
  useEffect38(() => {
@@ -18039,8 +18538,44 @@ function useWebVRWakeLock(enabled) {
18039
18538
  }, [enabled]);
18040
18539
  }
18041
18540
 
18541
+ // src/webvr/xr-quality.ts
18542
+ function clampWebVRFoveation(value) {
18543
+ if (typeof value !== "number" || !Number.isFinite(value)) {
18544
+ return void 0;
18545
+ }
18546
+ return Math.max(0, Math.min(1, value));
18547
+ }
18548
+ function resolveWebVRFrameRateOption(frameRate) {
18549
+ if (frameRate === void 0) {
18550
+ return void 0;
18551
+ }
18552
+ if (frameRate === "low" || frameRate === "mid" || frameRate === "high") {
18553
+ return frameRate;
18554
+ }
18555
+ if (typeof frameRate !== "number" || !Number.isFinite(frameRate)) {
18556
+ return void 0;
18557
+ }
18558
+ const target = frameRate;
18559
+ return (rates) => {
18560
+ if (rates.length === 0) {
18561
+ return false;
18562
+ }
18563
+ let best = rates[0];
18564
+ let bestDelta = Math.abs(best - target);
18565
+ for (let i = 1; i < rates.length; i += 1) {
18566
+ const candidate = rates[i];
18567
+ const delta = Math.abs(candidate - target);
18568
+ if (delta < bestDelta) {
18569
+ best = candidate;
18570
+ bestDelta = delta;
18571
+ }
18572
+ }
18573
+ return best;
18574
+ };
18575
+ }
18576
+
18042
18577
  // src/webvr/webvr.tsx
18043
- import { Fragment as Fragment6, jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
18578
+ import { Fragment as Fragment8, jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
18044
18579
  function initializeSettings(screensize, profile) {
18045
18580
  const stored = loadWebVRSettings();
18046
18581
  return {
@@ -18057,7 +18592,15 @@ var WebVR = forwardRef4(function WebVR2({
18057
18592
  desktopSupport = false,
18058
18593
  fakeSupport = true,
18059
18594
  screensize,
18595
+ databaseUrl,
18060
18596
  profile,
18597
+ absolute = false,
18598
+ north = 0,
18599
+ friction = 0,
18600
+ touchSupport = false,
18601
+ oversampling = 1,
18602
+ foveation,
18603
+ frameRate,
18061
18604
  cursor = true,
18062
18605
  cursorDwellMs = DEFAULT_WEBVR_CURSOR_DWELL_MS,
18063
18606
  wakelock = true,
@@ -18073,20 +18616,36 @@ var WebVR = forwardRef4(function WebVR2({
18073
18616
  const controlsRef = useContext27(PanoramaViewContext);
18074
18617
  const eventBus = useContext27(PanoEventBusContext);
18075
18618
  const gl = useThree28((state) => state.gl);
18076
- const gyroRef = useRef39(null);
18077
- const capabilitiesRef = useRef39({
18619
+ const gyroRef = useRef40(null);
18620
+ const capabilitiesRef = useRef40({
18078
18621
  mobile: false,
18079
18622
  sensor: false,
18080
18623
  webxr: false
18081
18624
  });
18082
- const availableRef = useRef39(false);
18083
- const modeRef = useRef39(null);
18084
- const webXRSessionRef = useRef39(null);
18625
+ const availableRef = useRef40(false);
18626
+ const modeRef = useRef40(null);
18627
+ const webXRSessionRef = useRef40(null);
18628
+ const dpdbRequestIdRef = useRef40(0);
18629
+ const databaseUrlRef = useRef40(databaseUrl);
18630
+ databaseUrlRef.current = databaseUrl;
18631
+ const oversamplingRef = useRef40(clampWebVROversampling(oversampling));
18632
+ oversamplingRef.current = clampWebVROversampling(oversampling);
18633
+ const foveationRef = useRef40(clampWebVRFoveation(foveation));
18634
+ foveationRef.current = clampWebVRFoveation(foveation);
18635
+ const frameRateRef = useRef40(frameRate);
18636
+ frameRateRef.current = frameRate;
18637
+ const screensizePropRef = useRef40(screensize);
18638
+ screensizePropRef.current = screensize;
18639
+ const profilePropRef = useRef40(profile);
18640
+ profilePropRef.current = profile;
18085
18641
  const [mode, setMode] = useState24(null);
18086
18642
  const [settings, setSettings] = useState24(
18087
18643
  () => initializeSettings(screensize, profile)
18088
18644
  );
18089
- const callbacksRef = useRef39({
18645
+ const [detectedScreensize, setDetectedScreensize] = useState24(
18646
+ null
18647
+ );
18648
+ const callbacksRef = useRef40({
18090
18649
  onAvailable,
18091
18650
  onDenied,
18092
18651
  onEnterVR,
@@ -18110,18 +18669,44 @@ var WebVR = forwardRef4(function WebVR2({
18110
18669
  () => resolveWebVRProfile(profile, settings.profileId),
18111
18670
  [profile, settings.profileId]
18112
18671
  );
18113
- const reportDenied = useCallback10(
18672
+ const effectiveScreensize = settings.screensize === "auto" ? detectedScreensize ?? "auto" : settings.screensize;
18673
+ const resolvedOversampling = clampWebVROversampling(oversampling);
18674
+ const reportDenied = useCallback11(
18114
18675
  (error) => {
18115
18676
  eventBus.emit("vrdenied", { error });
18116
18677
  callbacksRef.current.onDenied?.(error);
18117
18678
  },
18118
18679
  [eventBus]
18119
18680
  );
18120
- const reportUnknownDevice = useCallback10(() => {
18681
+ const reportUnknownDevice = useCallback11(() => {
18121
18682
  eventBus.emit("vrunknowndevice", void 0);
18122
18683
  callbacksRef.current.onUnknownDevice?.();
18123
18684
  }, [eventBus]);
18124
- const activate = useCallback10(
18685
+ const clearDetectedScreensize = useCallback11(() => {
18686
+ dpdbRequestIdRef.current += 1;
18687
+ setDetectedScreensize(null);
18688
+ updateWebVRHost(host, { detectedScreensize: null });
18689
+ }, [host]);
18690
+ const resolveAutoScreensize = useCallback11(() => {
18691
+ const requestId = ++dpdbRequestIdRef.current;
18692
+ const url = normalizeDatabaseUrl(databaseUrlRef.current);
18693
+ if (!url) {
18694
+ reportUnknownDevice();
18695
+ return;
18696
+ }
18697
+ void resolveScreensizeFromDpdb(url).then((inches) => {
18698
+ if (requestId !== dpdbRequestIdRef.current) {
18699
+ return;
18700
+ }
18701
+ if (inches === null) {
18702
+ reportUnknownDevice();
18703
+ return;
18704
+ }
18705
+ setDetectedScreensize(inches);
18706
+ updateWebVRHost(host, { detectedScreensize: inches });
18707
+ });
18708
+ }, [host, reportUnknownDevice]);
18709
+ const activate = useCallback11(
18125
18710
  (nextMode) => {
18126
18711
  if (modeRef.current === nextMode) {
18127
18712
  return;
@@ -18136,7 +18721,7 @@ var WebVR = forwardRef4(function WebVR2({
18136
18721
  },
18137
18722
  [controlsRef, eventBus]
18138
18723
  );
18139
- const deactivate = useCallback10(
18724
+ const deactivate = useCallback11(
18140
18725
  (expectedMode) => {
18141
18726
  const previousMode = modeRef.current;
18142
18727
  if (!previousMode || expectedMode && previousMode !== expectedMode) {
@@ -18144,6 +18729,7 @@ var WebVR = forwardRef4(function WebVR2({
18144
18729
  }
18145
18730
  modeRef.current = null;
18146
18731
  setMode(null);
18732
+ clearDetectedScreensize();
18147
18733
  if (previousMode === "fake") {
18148
18734
  controlsRef.current?.setGyroActive(false);
18149
18735
  }
@@ -18151,24 +18737,42 @@ var WebVR = forwardRef4(function WebVR2({
18151
18737
  eventBus.emit("vrexit", { mode: previousMode });
18152
18738
  callbacksRef.current.onExitVR?.(previousMode);
18153
18739
  },
18154
- [controlsRef, eventBus, gl.domElement]
18740
+ [clearDetectedScreensize, controlsRef, eventBus, gl.domElement]
18155
18741
  );
18156
- const requestPermission = useCallback10(async () => {
18742
+ const requestPermission = useCallback11(async () => {
18157
18743
  return await gyroRef.current?.requestPermission() ?? false;
18158
18744
  }, []);
18159
- const enterVR = useCallback10(async () => {
18745
+ const applyWebXRQuality = useCallback11(() => {
18746
+ const scale = oversamplingRef.current;
18747
+ if (typeof gl.xr.setFramebufferScaleFactor === "function") {
18748
+ gl.xr.setFramebufferScaleFactor(scale);
18749
+ }
18750
+ const nextFoveation = foveationRef.current;
18751
+ if (nextFoveation !== void 0 && typeof gl.xr.setFoveation === "function") {
18752
+ gl.xr.setFoveation(nextFoveation);
18753
+ }
18754
+ const frameRateOption = resolveWebVRFrameRateOption(frameRateRef.current);
18755
+ if (frameRateOption !== void 0) {
18756
+ xrStore.setFrameRate(frameRateOption);
18757
+ }
18758
+ }, [gl.xr, xrStore]);
18759
+ const enterVR = useCallback11(async () => {
18160
18760
  if (modeRef.current) {
18161
18761
  return true;
18162
18762
  }
18163
18763
  const capabilities = capabilitiesRef.current;
18164
18764
  if (capabilities.webxr) {
18165
18765
  try {
18766
+ if (typeof gl.xr.setFramebufferScaleFactor === "function") {
18767
+ gl.xr.setFramebufferScaleFactor(oversamplingRef.current);
18768
+ }
18166
18769
  await xrStore.enterVR();
18167
18770
  const session2 = xrStore.getState().session;
18168
18771
  if (!session2) {
18169
18772
  throw new Error("The WebXR session did not start.");
18170
18773
  }
18171
18774
  webXRSessionRef.current = session2;
18775
+ applyWebXRQuality();
18172
18776
  session2.addEventListener(
18173
18777
  "end",
18174
18778
  () => {
@@ -18198,10 +18802,10 @@ var WebVR = forwardRef4(function WebVR2({
18198
18802
  reportDenied();
18199
18803
  return false;
18200
18804
  }
18805
+ activate("mobilevr");
18201
18806
  if (settings.screensize === "auto") {
18202
- reportUnknownDevice();
18807
+ resolveAutoScreensize();
18203
18808
  }
18204
- activate("mobilevr");
18205
18809
  return true;
18206
18810
  }
18207
18811
  if (fakeSupport) {
@@ -18220,21 +18824,23 @@ var WebVR = forwardRef4(function WebVR2({
18220
18824
  return false;
18221
18825
  }, [
18222
18826
  activate,
18827
+ applyWebXRQuality,
18223
18828
  deactivate,
18224
18829
  desktopSupport,
18225
18830
  eventBus,
18226
18831
  fakeSupport,
18227
18832
  gl.domElement,
18833
+ gl.xr,
18228
18834
  mobileVr,
18229
18835
  mousePointerLock,
18230
18836
  reportDenied,
18231
- reportUnknownDevice,
18232
18837
  requestPermission,
18838
+ resolveAutoScreensize,
18233
18839
  settings.screensize,
18234
18840
  wakelock,
18235
18841
  xrStore
18236
18842
  ]);
18237
- const exitVR = useCallback10(async () => {
18843
+ const exitVR = useCallback11(async () => {
18238
18844
  if (modeRef.current === "webxr") {
18239
18845
  const session2 = webXRSessionRef.current ?? xrStore.getState().session ?? null;
18240
18846
  if (session2) {
@@ -18247,21 +18853,52 @@ var WebVR = forwardRef4(function WebVR2({
18247
18853
  deactivate();
18248
18854
  leaveWebVRPresentation();
18249
18855
  }, [deactivate, xrStore]);
18250
- const toggleVR = useCallback10(async () => {
18856
+ const toggleVR = useCallback11(async () => {
18251
18857
  if (modeRef.current) {
18252
18858
  await exitVR();
18253
18859
  return false;
18254
18860
  }
18255
18861
  return enterVR();
18256
18862
  }, [enterVR, exitVR]);
18257
- const updateSettings = useCallback10((next) => {
18258
- const normalized = {
18259
- profileId: next.profileId,
18260
- screensize: next.screensize === "auto" ? "auto" : Math.round(Math.max(3, Math.min(20, next.screensize)) * 10) / 10
18261
- };
18262
- setSettings(normalized);
18263
- saveWebVRSettings(normalized);
18264
- }, []);
18863
+ const updateSettings = useCallback11(
18864
+ (next) => {
18865
+ const normalized = {
18866
+ profileId: next.profileId,
18867
+ screensize: next.screensize === "auto" ? "auto" : Math.round(Math.max(3, Math.min(20, next.screensize)) * 10) / 10
18868
+ };
18869
+ setSettings(normalized);
18870
+ saveWebVRSettings(normalized);
18871
+ if (normalized.screensize === "auto") {
18872
+ if (modeRef.current === "mobilevr") {
18873
+ setDetectedScreensize(null);
18874
+ updateWebVRHost(host, { detectedScreensize: null });
18875
+ resolveAutoScreensize();
18876
+ }
18877
+ } else {
18878
+ clearDetectedScreensize();
18879
+ }
18880
+ },
18881
+ [clearDetectedScreensize, host, resolveAutoScreensize]
18882
+ );
18883
+ const resetSettings = useCallback11(() => {
18884
+ clearWebVRSettings();
18885
+ const next = resolveInitialWebVRSettings(
18886
+ screensizePropRef.current,
18887
+ profilePropRef.current
18888
+ );
18889
+ setSettings(next);
18890
+ if (next.screensize === "auto") {
18891
+ if (modeRef.current === "mobilevr") {
18892
+ setDetectedScreensize(null);
18893
+ updateWebVRHost(host, { detectedScreensize: null });
18894
+ resolveAutoScreensize();
18895
+ } else {
18896
+ clearDetectedScreensize();
18897
+ }
18898
+ } else {
18899
+ clearDetectedScreensize();
18900
+ }
18901
+ }, [clearDetectedScreensize, host, resolveAutoScreensize]);
18265
18902
  const controller = useMemo26(
18266
18903
  () => ({
18267
18904
  closeSetup: () => updateWebVRHost(host, { setupOpen: false }),
@@ -18269,10 +18906,19 @@ var WebVR = forwardRef4(function WebVR2({
18269
18906
  exitVR,
18270
18907
  openSetup: () => updateWebVRHost(host, { setupOpen: true }),
18271
18908
  requestPermission,
18909
+ resetSettings,
18272
18910
  toggleVR,
18273
18911
  updateSettings
18274
18912
  }),
18275
- [enterVR, exitVR, host, requestPermission, toggleVR, updateSettings]
18913
+ [
18914
+ enterVR,
18915
+ exitVR,
18916
+ host,
18917
+ requestPermission,
18918
+ resetSettings,
18919
+ toggleVR,
18920
+ updateSettings
18921
+ ]
18276
18922
  );
18277
18923
  useImperativeHandle4(
18278
18924
  ref,
@@ -18280,12 +18926,15 @@ var WebVR = forwardRef4(function WebVR2({
18280
18926
  enterVR,
18281
18927
  exitVR,
18282
18928
  getMode: () => modeRef.current,
18929
+ getState: () => getWebVRState(host),
18283
18930
  isAvailable: () => availableRef.current,
18284
18931
  isEnabled: () => modeRef.current !== null,
18285
18932
  requestPermission,
18933
+ resetSettings,
18934
+ subscribe: (onStoreChange) => subscribeWebVRHost(host, onStoreChange),
18286
18935
  toggleVR
18287
18936
  }),
18288
- [enterVR, exitVR, requestPermission, toggleVR]
18937
+ [enterVR, exitVR, host, requestPermission, resetSettings, toggleVR]
18289
18938
  );
18290
18939
  useEffect40(() => {
18291
18940
  let active = true;
@@ -18314,13 +18963,24 @@ var WebVR = forwardRef4(function WebVR2({
18314
18963
  return () => {
18315
18964
  if (host.controller === controller) {
18316
18965
  host.controller = null;
18317
- updateWebVRHost(host, { mode: null, setupOpen: false });
18966
+ dpdbRequestIdRef.current += 1;
18967
+ setDetectedScreensize(null);
18968
+ updateWebVRHost(host, {
18969
+ detectedScreensize: null,
18970
+ mode: null,
18971
+ setupOpen: false
18972
+ });
18318
18973
  }
18319
18974
  };
18320
18975
  }, [controller, host]);
18321
18976
  useEffect40(() => {
18322
- updateWebVRHost(host, { chrome, mode, settings });
18323
- }, [chrome, host, mode, settings]);
18977
+ updateWebVRHost(host, {
18978
+ chrome,
18979
+ detectedScreensize,
18980
+ mode,
18981
+ settings
18982
+ });
18983
+ }, [chrome, detectedScreensize, host, mode, settings]);
18324
18984
  useEffect40(() => {
18325
18985
  if (mode !== "fake") {
18326
18986
  return;
@@ -18369,6 +19029,23 @@ var WebVR = forwardRef4(function WebVR2({
18369
19029
  void exitVR();
18370
19030
  });
18371
19031
  }, [exitVR, gl.domElement, mode]);
19032
+ useEffect40(() => {
19033
+ if (mode !== "webxr") {
19034
+ return;
19035
+ }
19036
+ const session2 = webXRSessionRef.current ?? xrStore.getState().session ?? null;
19037
+ if (!session2) {
19038
+ return;
19039
+ }
19040
+ const nextFoveation = foveationRef.current;
19041
+ if (nextFoveation !== void 0 && typeof gl.xr.setFoveation === "function") {
19042
+ gl.xr.setFoveation(nextFoveation);
19043
+ }
19044
+ const frameRateOption = resolveWebVRFrameRateOption(frameRateRef.current);
19045
+ if (frameRateOption !== void 0) {
19046
+ xrStore.setFrameRate(frameRateOption);
19047
+ }
19048
+ }, [foveation, frameRate, gl.xr, mode, xrStore]);
18372
19049
  useWebVRPointerLock(
18373
19050
  mousePointerLock && mode === "fake",
18374
19051
  gl.domElement
@@ -18376,27 +19053,31 @@ var WebVR = forwardRef4(function WebVR2({
18376
19053
  useWebVRWakeLock(
18377
19054
  wakelock && (mode === "mobilevr" || mode === "fake")
18378
19055
  );
18379
- return /* @__PURE__ */ jsxs21(Fragment6, { children: [
19056
+ return /* @__PURE__ */ jsxs21(Fragment8, { children: [
18380
19057
  /* @__PURE__ */ jsx40(
18381
19058
  Gyro,
18382
19059
  {
18383
19060
  ref: gyroRef,
19061
+ absolute,
18384
19062
  desktopSupport,
18385
19063
  enabled: mode === "mobilevr",
19064
+ friction,
19065
+ north,
18386
19066
  onDenied: () => {
18387
19067
  reportDenied();
18388
19068
  deactivate("mobilevr");
18389
19069
  },
18390
19070
  softstart: 0,
18391
- touchMode: "off"
19071
+ touchMode: touchSupport ? "horizontaloffset" : "off"
18392
19072
  }
18393
19073
  ),
18394
19074
  /* @__PURE__ */ jsx40(
18395
19075
  WebVRStereoRenderer,
18396
19076
  {
18397
19077
  mode,
19078
+ oversampling: resolvedOversampling,
18398
19079
  profile: resolvedProfile,
18399
- screensize: settings.screensize
19080
+ screensize: effectiveScreensize
18400
19081
  }
18401
19082
  ),
18402
19083
  /* @__PURE__ */ jsx40(
@@ -18412,12 +19093,12 @@ var WebVR = forwardRef4(function WebVR2({
18412
19093
 
18413
19094
  // src/pano-scene-menu.tsx
18414
19095
  import {
18415
- useCallback as useCallback11,
19096
+ useCallback as useCallback12,
18416
19097
  useEffect as useEffect41,
18417
19098
  useId as useId3,
18418
19099
  useLayoutEffect as useLayoutEffect9,
18419
19100
  useMemo as useMemo27,
18420
- useRef as useRef40,
19101
+ useRef as useRef41,
18421
19102
  useState as useState25
18422
19103
  } from "react";
18423
19104
 
@@ -18804,9 +19485,9 @@ function PanoSceneMenu({
18804
19485
  collapseLabel = "Hide scenes"
18805
19486
  }) {
18806
19487
  const listId = useId3();
18807
- const listRef = useRef40(null);
18808
- const trackRef = useRef40(null);
18809
- const itemRefs = useRef40(/* @__PURE__ */ new Map());
19488
+ const listRef = useRef41(null);
19489
+ const trackRef = useRef41(null);
19490
+ const itemRefs = useRef41(/* @__PURE__ */ new Map());
18810
19491
  const resolved = resolveAppearance2(appearance);
18811
19492
  const resolvedInset = resolveInset(inset);
18812
19493
  const horizontal = isHorizontal(placement);
@@ -18820,9 +19501,9 @@ function PanoSceneMenu({
18820
19501
  const [offset, setOffset] = useState25(0);
18821
19502
  const [viewportSize, setViewportSize] = useState25(0);
18822
19503
  const [contentSize, setContentSize] = useState25(0);
18823
- const offsetRef = useRef40(0);
18824
- const dragRef = useRef40(null);
18825
- const animRef = useRef40(null);
19504
+ const offsetRef = useRef41(0);
19505
+ const dragRef = useRef41(null);
19506
+ const animRef = useRef41(null);
18826
19507
  const maxOffset = Math.max(0, contentSize - viewportSize);
18827
19508
  offsetRef.current = offset;
18828
19509
  const activeIndex = useMemo27(() => {
@@ -18831,18 +19512,18 @@ function PanoSceneMenu({
18831
19512
  }
18832
19513
  return items.findIndex((item) => item.id === activeId);
18833
19514
  }, [activeId, items]);
18834
- const cancelScrollAnimation = useCallback11(() => {
19515
+ const cancelScrollAnimation = useCallback12(() => {
18835
19516
  const anim = animRef.current;
18836
19517
  if (anim?.raf != null) {
18837
19518
  cancelAnimationFrame(anim.raf);
18838
19519
  }
18839
19520
  animRef.current = null;
18840
19521
  }, []);
18841
- const clampOffset = useCallback11(
19522
+ const clampOffset = useCallback12(
18842
19523
  (value) => Math.min(maxOffset, Math.max(0, value)),
18843
19524
  [maxOffset]
18844
19525
  );
18845
- const startScrollAnimation = useCallback11(
19526
+ const startScrollAnimation = useCallback12(
18846
19527
  (velocity) => {
18847
19528
  cancelScrollAnimation();
18848
19529
  const current = offsetRef.current;
@@ -19109,7 +19790,7 @@ function PanoSceneMenu({
19109
19790
  setOffset((previous) => clampOffset(previous + event.deltaX));
19110
19791
  };
19111
19792
  const axisPoint = (event) => horizontal ? event.clientX : event.clientY;
19112
- const endSuppressClickRef = useRef40(false);
19793
+ const endSuppressClickRef = useRef41(false);
19113
19794
  const beginListDrag = (event, captureTarget) => {
19114
19795
  if (!customDrag || event.button !== 0) return;
19115
19796
  cancelScrollAnimation();
@@ -19319,7 +20000,7 @@ function PanoSceneMenu({
19319
20000
  }
19320
20001
 
19321
20002
  // src/use-pano-viewer.ts
19322
- import { useMemo as useMemo28, useRef as useRef41 } from "react";
20003
+ import { useMemo as useMemo28, useRef as useRef42 } from "react";
19323
20004
  function createPanoViewerHandle(ref) {
19324
20005
  return {
19325
20006
  getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
@@ -19350,6 +20031,36 @@ function createPanoViewerHandle(ref) {
19350
20031
  isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
19351
20032
  isVREnabled: () => ref.current?.isVREnabled() ?? false,
19352
20033
  getVRMode: () => ref.current?.getVRMode() ?? null,
20034
+ getVRState: () => ref.current?.getVRState() ?? EMPTY_WEBVR_STATE,
20035
+ subscribeVR: (onStoreChange) => {
20036
+ let disposed = false;
20037
+ let unsubscribe;
20038
+ const timers = [];
20039
+ const attach = () => {
20040
+ if (disposed || unsubscribe) {
20041
+ return;
20042
+ }
20043
+ const handle = ref.current;
20044
+ if (!handle) {
20045
+ return;
20046
+ }
20047
+ unsubscribe = handle.subscribeVR(onStoreChange);
20048
+ onStoreChange();
20049
+ };
20050
+ attach();
20051
+ if (typeof window !== "undefined") {
20052
+ for (const delay of [0, 16, 50, 100, 250]) {
20053
+ timers.push(window.setTimeout(attach, delay));
20054
+ }
20055
+ }
20056
+ return () => {
20057
+ disposed = true;
20058
+ for (const id of timers) {
20059
+ window.clearTimeout(id);
20060
+ }
20061
+ unsubscribe?.();
20062
+ };
20063
+ },
19353
20064
  requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
19354
20065
  getVideo: () => ref.current?.getVideo() ?? null,
19355
20066
  subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
@@ -19401,7 +20112,7 @@ function createPanoViewerHandle(ref) {
19401
20112
  };
19402
20113
  }
19403
20114
  function usePanoViewer() {
19404
- const ref = useRef41(null);
20115
+ const ref = useRef42(null);
19405
20116
  const handle = useMemo28(() => createPanoViewerHandle(ref), []);
19406
20117
  return useMemo28(() => ({ ref, ...handle }), [handle]);
19407
20118
  }
@@ -19409,25 +20120,25 @@ function usePanoViewer() {
19409
20120
  // src/video/pano-video.tsx
19410
20121
  import { useThree as useThree29 } from "@react-three/fiber";
19411
20122
  import {
19412
- useCallback as useCallback12,
20123
+ useCallback as useCallback13,
19413
20124
  useContext as useContext28,
19414
20125
  useEffect as useEffect42,
19415
20126
  useLayoutEffect as useLayoutEffect10,
19416
20127
  useMemo as useMemo29,
19417
- useRef as useRef42,
20128
+ useRef as useRef43,
19418
20129
  useState as useState26
19419
20130
  } from "react";
19420
20131
  import {
19421
20132
  BackSide as BackSide4,
19422
- LinearFilter as LinearFilter11,
19423
- LinearMipmapLinearFilter as LinearMipmapLinearFilter7,
19424
- MathUtils as MathUtils13,
20133
+ LinearFilter as LinearFilter10,
20134
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter8,
20135
+ MathUtils as MathUtils14,
19425
20136
  RepeatWrapping as RepeatWrapping2,
19426
- SRGBColorSpace as SRGBColorSpace10,
20137
+ SRGBColorSpace as SRGBColorSpace9,
19427
20138
  TextureLoader as TextureLoader8,
19428
20139
  VideoTexture as VideoTexture2
19429
20140
  } from "three";
19430
- import { Fragment as Fragment7, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
20141
+ import { Fragment as Fragment9, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
19431
20142
  var EMPTY_TRACKS = [];
19432
20143
  var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
19433
20144
  function createEmptySnapshot() {
@@ -19503,7 +20214,7 @@ function findTextTrack(video, tracks, trackId) {
19503
20214
  return list.length > 0 ? list[0] : null;
19504
20215
  }
19505
20216
  function applyEquirectangularLayout(texture, anisotropy) {
19506
- texture.colorSpace = SRGBColorSpace10;
20217
+ texture.colorSpace = SRGBColorSpace9;
19507
20218
  texture.wrapS = RepeatWrapping2;
19508
20219
  texture.repeat.x = -1;
19509
20220
  texture.offset.x = 1;
@@ -19513,7 +20224,7 @@ function applyEquirectangularLayout(texture, anisotropy) {
19513
20224
  function usePosterTexture2(poster, onError) {
19514
20225
  const gl = useThree29((state) => state.gl);
19515
20226
  const [texture, setTexture] = useState26(null);
19516
- const onErrorRef = useRef42(onError);
20227
+ const onErrorRef = useRef43(onError);
19517
20228
  onErrorRef.current = onError;
19518
20229
  useEffect42(() => {
19519
20230
  let active = true;
@@ -19536,8 +20247,8 @@ function usePosterTexture2(poster, onError) {
19536
20247
  gl.capabilities.getMaxAnisotropy()
19537
20248
  );
19538
20249
  loadedTexture.wrapT = RepeatWrapping2;
19539
- loadedTexture.magFilter = LinearFilter11;
19540
- loadedTexture.minFilter = LinearMipmapLinearFilter7;
20250
+ loadedTexture.magFilter = LinearFilter10;
20251
+ loadedTexture.minFilter = LinearMipmapLinearFilter8;
19541
20252
  loadedTexture.generateMipmaps = true;
19542
20253
  setTexture(loadedTexture);
19543
20254
  },
@@ -19631,17 +20342,17 @@ function PanoVideo({
19631
20342
  () => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
19632
20343
  );
19633
20344
  const [ready, setReady] = useState26(false);
19634
- const autoPlayRef = useRef42(autoPlay);
20345
+ const autoPlayRef = useRef43(autoPlay);
19635
20346
  autoPlayRef.current = autoPlay;
19636
- const blockedRef = useRef42(false);
19637
- const captionAppearanceOverrideRef = useRef42(
20347
+ const blockedRef = useRef43(false);
20348
+ const captionAppearanceOverrideRef = useRef43(
19638
20349
  {}
19639
20350
  );
19640
- const mediaCapabilitiesRef = useRef42({
20351
+ const mediaCapabilitiesRef = useRef43({
19641
20352
  volumeAdjustable: true,
19642
20353
  playbackRates: [...DEFAULT_PANO_VIDEO_PLAYBACK_RATES]
19643
20354
  });
19644
- const storeRef = useRef42(null);
20355
+ const storeRef = useRef43(null);
19645
20356
  if (!storeRef.current) {
19646
20357
  storeRef.current = {
19647
20358
  snapshot: {
@@ -19651,7 +20362,7 @@ function PanoVideo({
19651
20362
  listeners: /* @__PURE__ */ new Set()
19652
20363
  };
19653
20364
  }
19654
- const actionsRef = useRef42({
20365
+ const actionsRef = useRef43({
19655
20366
  play: async () => {
19656
20367
  },
19657
20368
  pause: () => {
@@ -19671,12 +20382,12 @@ function PanoVideo({
19671
20382
  setCaptionAppearance: (_appearance) => {
19672
20383
  }
19673
20384
  });
19674
- const onLoadRef = useRef42(onLoad);
19675
- const onErrorRef = useRef42(onError);
19676
- const onPlaybackStateChangeRef = useRef42(onPlaybackStateChange);
19677
- const onEndedRef = useRef42(onEnded);
19678
- const onVariantChangeRef = useRef42(onVariantChange);
19679
- const onTrackChangeRef = useRef42(onTrackChange);
20385
+ const onLoadRef = useRef43(onLoad);
20386
+ const onErrorRef = useRef43(onError);
20387
+ const onPlaybackStateChangeRef = useRef43(onPlaybackStateChange);
20388
+ const onEndedRef = useRef43(onEnded);
20389
+ const onVariantChangeRef = useRef43(onVariantChange);
20390
+ const onTrackChangeRef = useRef43(onTrackChange);
19680
20391
  onLoadRef.current = onLoad;
19681
20392
  onErrorRef.current = onError;
19682
20393
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
@@ -19693,8 +20404,8 @@ function PanoVideo({
19693
20404
  video.preload = "metadata";
19694
20405
  const texture = new VideoTexture2(video);
19695
20406
  applyEquirectangularLayout(texture, 1);
19696
- texture.magFilter = LinearFilter11;
19697
- texture.minFilter = LinearFilter11;
20407
+ texture.magFilter = LinearFilter10;
20408
+ texture.minFilter = LinearFilter10;
19698
20409
  texture.generateMipmaps = false;
19699
20410
  return { video, texture };
19700
20411
  }, []);
@@ -19735,7 +20446,7 @@ function PanoVideo({
19735
20446
  activeVariant?.poster ?? poster,
19736
20447
  onError
19737
20448
  );
19738
- const emitSnapshot = useCallback12(
20449
+ const emitSnapshot = useCallback13(
19739
20450
  (patch) => {
19740
20451
  const store = storeRef.current;
19741
20452
  const video = resource?.video;
@@ -20126,11 +20837,11 @@ function PanoVideo({
20126
20837
  if (variants.length === 0) {
20127
20838
  return null;
20128
20839
  }
20129
- return /* @__PURE__ */ jsxs23(Fragment7, { children: [
20840
+ return /* @__PURE__ */ jsxs23(Fragment9, { children: [
20130
20841
  activeTexture ? /* @__PURE__ */ jsxs23(
20131
20842
  "mesh",
20132
20843
  {
20133
- rotation: [0, MathUtils13.degToRad(90 + yawOffset2), 0],
20844
+ rotation: [0, MathUtils14.degToRad(90 + yawOffset2), 0],
20134
20845
  visible,
20135
20846
  children: [
20136
20847
  /* @__PURE__ */ jsx42("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
@@ -20151,12 +20862,12 @@ function PanoVideo({
20151
20862
 
20152
20863
  // src/map/pano-map.tsx
20153
20864
  import {
20154
- useCallback as useCallback13,
20865
+ useCallback as useCallback14,
20155
20866
  useEffect as useEffect44,
20156
20867
  useId as useId4,
20157
20868
  useLayoutEffect as useLayoutEffect11,
20158
20869
  useMemo as useMemo30,
20159
- useRef as useRef44,
20870
+ useRef as useRef45,
20160
20871
  useState as useState28
20161
20872
  } from "react";
20162
20873
 
@@ -20218,12 +20929,12 @@ function normalizeDegrees(value) {
20218
20929
  }
20219
20930
 
20220
20931
  // src/map/use-map-view.ts
20221
- import { useContext as useContext29, useEffect as useEffect43, useRef as useRef43, useState as useState27 } from "react";
20932
+ import { useContext as useContext29, useEffect as useEffect43, useRef as useRef44, useState as useState27 } from "react";
20222
20933
  var DEFAULT_VIEW2 = { yaw: 0, pitch: 0, fov: 75 };
20223
20934
  function useMapView(controlled) {
20224
20935
  const eventBus = useContext29(PanoEventBusContext);
20225
20936
  const [view, setView] = useState27(controlled ?? DEFAULT_VIEW2);
20226
- const controlledRef = useRef43(controlled);
20937
+ const controlledRef = useRef44(controlled);
20227
20938
  controlledRef.current = controlled;
20228
20939
  useEffect43(() => {
20229
20940
  if (controlled !== void 0) {
@@ -20240,7 +20951,7 @@ function useMapView(controlled) {
20240
20951
  }
20241
20952
 
20242
20953
  // src/map/pano-map.tsx
20243
- import { Fragment as Fragment8, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
20954
+ import { Fragment as Fragment10, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
20244
20955
  var DEFAULT_ICON_SIZE2 = 28;
20245
20956
  var DEFAULT_SECTOR_COLOR = "#ffffff";
20246
20957
  var DEFAULT_SECTOR_OPACITY = 0.55;
@@ -20377,20 +21088,20 @@ function PanoMap({
20377
21088
  }) {
20378
21089
  const reactId = useId4().replace(/:/g, "");
20379
21090
  const view = useMapView(controlledView);
20380
- const rootRef = useRef44(null);
20381
- const containerRef = useRef44(null);
20382
- const sectorNodeRef = useRef44(null);
20383
- const yawRef = useRef44(view.yaw);
21091
+ const rootRef = useRef45(null);
21092
+ const containerRef = useRef45(null);
21093
+ const sectorNodeRef = useRef45(null);
21094
+ const yawRef = useRef45(view.yaw);
20384
21095
  const [size, setSize] = useState28({ width: 0, height: 0 });
20385
21096
  const [imageSize2, setImageSize] = useState28({ width: 0, height: 0 });
20386
21097
  const [scale, setScale] = useState28(1);
20387
21098
  const [pan, setPan] = useState28({ x: 0, y: 0 });
20388
21099
  const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState28(false);
20389
- const dragRef = useRef44(null);
20390
- const panAnimRef = useRef44(null);
20391
- const panRef = useRef44(pan);
20392
- const scaleRef = useRef44(scale);
20393
- const initialViewKeyRef = useRef44(null);
21100
+ const dragRef = useRef45(null);
21101
+ const panAnimRef = useRef45(null);
21102
+ const panRef = useRef45(pan);
21103
+ const scaleRef = useRef45(scale);
21104
+ const initialViewKeyRef = useRef45(null);
20394
21105
  const spots = map.spots;
20395
21106
  const iconSize = map.iconSize ?? DEFAULT_ICON_SIZE2;
20396
21107
  const interaction = map.interaction ?? "fixed";
@@ -20450,7 +21161,7 @@ function PanoMap({
20450
21161
  };
20451
21162
  panRef.current = pan;
20452
21163
  scaleRef.current = scale;
20453
- const panBoundsFor = useCallback13(
21164
+ const panBoundsFor = useCallback14(
20454
21165
  (nextScale) => {
20455
21166
  const width = fit.width * nextScale;
20456
21167
  const height = fit.height * nextScale;
@@ -20466,7 +21177,7 @@ function PanoMap({
20466
21177
  },
20467
21178
  [fit, size]
20468
21179
  );
20469
- const clampPan = useCallback13(
21180
+ const clampPan = useCallback14(
20470
21181
  (nextScale, nextPan) => {
20471
21182
  const bounds = panBoundsFor(nextScale);
20472
21183
  return {
@@ -20476,14 +21187,14 @@ function PanoMap({
20476
21187
  },
20477
21188
  [panBoundsFor]
20478
21189
  );
20479
- const cancelPanAnimation = useCallback13(() => {
21190
+ const cancelPanAnimation = useCallback14(() => {
20480
21191
  const anim = panAnimRef.current;
20481
21192
  if (anim?.raf != null) {
20482
21193
  cancelAnimationFrame(anim.raf);
20483
21194
  }
20484
21195
  panAnimRef.current = null;
20485
21196
  }, []);
20486
- const startPanAnimation = useCallback13(
21197
+ const startPanAnimation = useCallback14(
20487
21198
  (velocity) => {
20488
21199
  cancelPanAnimation();
20489
21200
  const current = panRef.current;
@@ -20589,7 +21300,7 @@ function PanoMap({
20589
21300
  size.height,
20590
21301
  size.width
20591
21302
  ]);
20592
- const applyZoom = useCallback13(
21303
+ const applyZoom = useCallback14(
20593
21304
  (nextScale, origin) => {
20594
21305
  if (!gestureZoom) return;
20595
21306
  cancelPanAnimation();
@@ -20801,7 +21512,7 @@ function PanoMap({
20801
21512
  children: collapsed ? "\u25A3" : "\u25A2"
20802
21513
  }
20803
21514
  ) : null,
20804
- zoomControls && !collapsed ? /* @__PURE__ */ jsxs24(Fragment8, { children: [
21515
+ zoomControls && !collapsed ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
20805
21516
  /* @__PURE__ */ jsx43(
20806
21517
  "button",
20807
21518
  {
@@ -21043,10 +21754,10 @@ import {
21043
21754
  useEffect as useEffect45,
21044
21755
  useId as useId5,
21045
21756
  useMemo as useMemo31,
21046
- useRef as useRef45,
21757
+ useRef as useRef46,
21047
21758
  useState as useState29
21048
21759
  } from "react";
21049
- import { Fragment as Fragment9, jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
21760
+ import { Fragment as Fragment11, jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
21050
21761
  var DEFAULT_DIALOG_WIDTH = 720;
21051
21762
  var DEFAULT_DIALOG_HEIGHT = 520;
21052
21763
  function dockedBox(corner, offsetX, offsetY, width, height) {
@@ -21066,8 +21777,8 @@ function PanoMapDialogShell({
21066
21777
  usePanoModal(open);
21067
21778
  const { size } = usePanoOverlay();
21068
21779
  const titleId = useId5();
21069
- const panelRef = useRef45(null);
21070
- const previouslyFocused = useRef45(null);
21780
+ const panelRef = useRef46(null);
21781
+ const previouslyFocused = useRef46(null);
21071
21782
  useEffect45(() => {
21072
21783
  if (!open) return;
21073
21784
  const active = panelRef.current?.ownerDocument.activeElement;
@@ -21096,7 +21807,7 @@ function PanoMapDialogShell({
21096
21807
  const height = Math.min(preferredHeight, Math.max(0, size.height - 32));
21097
21808
  const left = Math.max(0, (size.width - width) / 2);
21098
21809
  const top = Math.max(0, (size.height - height) / 2);
21099
- return /* @__PURE__ */ jsxs25(Fragment9, { children: [
21810
+ return /* @__PURE__ */ jsxs25(Fragment11, { children: [
21100
21811
  /* @__PURE__ */ jsx44(
21101
21812
  "div",
21102
21813
  {
@@ -21330,6 +22041,7 @@ export {
21330
22041
  DEFAULT_PANO_VIDEO_PLAYBACK_RATES,
21331
22042
  DEFAULT_WEBVR_CHROME_APPEARANCE,
21332
22043
  DEFAULT_WEBVR_CURSOR_DWELL_MS,
22044
+ EMPTY_WEBVR_STATE,
21333
22045
  GraphicHotspot,
21334
22046
  Gyro,
21335
22047
  IframeHotspot,
@@ -21344,6 +22056,7 @@ export {
21344
22056
  PanoFilter,
21345
22057
  PanoMap,
21346
22058
  PanoMapOverlay,
22059
+ PanoOverlay,
21347
22060
  PanoOverlayPortalProvider,
21348
22061
  PanoSceneMenu,
21349
22062
  PanoVideo,