@terraware/web-components 5.0.1-rc.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,8 +20,18 @@ export interface SplatModelProps {
20
20
  cropFade?: boolean;
21
21
  cropFadeDistance?: number;
22
22
  revealRain?: boolean;
23
+ /**
24
+ * Maximum allowable budget for splat rendering operations. Only applies to streamed sog models.
25
+ * A value of 0 means no limit. Left at the engine's default when omitted.
26
+ */
27
+ splatBudget?: number;
28
+ /**
29
+ * Caps the device pixel ratio the scene renders at, trading sharpness for fill rate on high-DPI
30
+ * displays. Left at the engine's default when omitted.
31
+ */
32
+ maxPixelRatio?: number;
23
33
  onError?: (error: Error) => void;
24
34
  }
25
- declare const _default: React.MemoExoticComponent<({ splatSrc, splatFormat, rotation, scaleFactor, cropAabbMin, cropAabbMax, cropEdgeScaleFactor, cropFade, cropFadeDistance, revealRain, onError, }: SplatModelProps) => import("react/jsx-runtime").JSX.Element | null>;
35
+ declare const _default: React.MemoExoticComponent<({ splatSrc, splatFormat, rotation, scaleFactor, cropAabbMin, cropAabbMax, cropEdgeScaleFactor, cropFade, cropFadeDistance, revealRain, splatBudget, maxPixelRatio, onError, }: SplatModelProps) => import("react/jsx-runtime").JSX.Element | null>;
26
36
  export default _default;
27
37
  //# sourceMappingURL=SplatModel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SplatModel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/SplatModel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAU/C,OAAO,EAAE,WAAW,EAA0C,MAAM,eAAe,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;sMAcE,eAAe;AAkClB,wBAAgC"}
1
+ {"version":3,"file":"SplatModel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/SplatModel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAU/C,OAAO,EAAE,WAAW,EAA0C,MAAM,eAAe,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;kOAgBE,eAAe;AAgDlB,wBAAgC"}
@@ -1,7 +1,7 @@
1
1
  import { memo, useEffect } from 'react';
2
2
  import { Entity } from '@playcanvas/react';
3
3
  import { GSplat } from '@playcanvas/react/components';
4
- import { useSplat } from '@playcanvas/react/hooks';
4
+ import { useApp, useSplat } from '@playcanvas/react/hooks';
5
5
  import BlockingSpinner from './BlockingSpinner.js';
6
6
  import SplatCrop from './SplatCrop.js';
7
7
  import SplatFadeCrop from './SplatFadeCrop.js';
@@ -20,8 +20,12 @@ const SplatModel = ({
20
20
  cropFade = false,
21
21
  cropFadeDistance = 0.5,
22
22
  revealRain = false,
23
+ splatBudget,
24
+ maxPixelRatio,
23
25
  onError
24
26
  }) => {
27
+ const app = useApp();
28
+
25
29
  // The loader selects its parser from this filename rather than from splatSrc, so it has to name the
26
30
  // format we are actually loading. See splatFormat.ts.
27
31
  const filename = splatLoaderFilename(splatFormat ?? detectSplatFormat(splatSrc));
@@ -34,6 +38,17 @@ const SplatModel = ({
34
38
  filename
35
39
  }
36
40
  });
41
+ useEffect(() => {
42
+ if (!app) {
43
+ return;
44
+ }
45
+ if (splatBudget !== undefined) {
46
+ app.scene.gsplat.splatBudget = splatBudget;
47
+ }
48
+ if (maxPixelRatio !== undefined) {
49
+ app.graphicsDevice.maxPixelRatio = maxPixelRatio;
50
+ }
51
+ }, [app, splatBudget, maxPixelRatio]);
37
52
  useEffect(() => {
38
53
  if (error) {
39
54
  onError?.(new Error(error));
@@ -1,4 +1,5 @@
1
1
  import { AnnotationProps } from './Annotation';
2
+ import { SplatModelProps } from './SplatModel';
2
3
  import { SplatFormat } from './splatFormat';
3
4
  /**
4
5
  * Every coordinate, distance and size in this component is world-space, in the metres an XR
@@ -30,6 +31,12 @@ export interface VirtualWalkthroughViewerProps {
30
31
  * entity alone — every other coordinate this component takes is already world-space.
31
32
  */
32
33
  scaleFactor?: number;
34
+ /**
35
+ * Overrides passed through to the splat model, for tuning things like `splatBudget` and
36
+ * `maxPixelRatio`. The props this component drives itself — `splatSrc`, `splatFormat`,
37
+ * `rotation`, `scaleFactor` and `revealRain` — take precedence over anything set here.
38
+ */
39
+ splatModelProps?: Partial<SplatModelProps>;
33
40
  annotations: AnnotationProps[];
34
41
  onSaveAnnotations: (annotations: AnnotationProps[]) => void | Promise<void>;
35
42
  editable?: boolean;
@@ -38,6 +45,6 @@ export interface VirtualWalkthroughViewerProps {
38
45
  onToggleFullScreen?: () => void;
39
46
  maxImagesPerAnnotation?: number;
40
47
  }
41
- declare const VirtualWalkthroughViewer: ({ splatSrc, splatFormat, origin, cameraPosition, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight, scaleFactor, annotations, onSaveAnnotations, editable, showFreeFly, isFullScreen, onToggleFullScreen, maxImagesPerAnnotation, }: VirtualWalkthroughViewerProps) => import("react/jsx-runtime").JSX.Element;
48
+ declare const VirtualWalkthroughViewer: ({ splatSrc, splatFormat, origin, cameraPosition, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight, scaleFactor, splatModelProps, annotations, onSaveAnnotations, editable, showFreeFly, isFullScreen, onToggleFullScreen, maxImagesPerAnnotation, }: VirtualWalkthroughViewerProps) => import("react/jsx-runtime").JSX.Element;
42
49
  export default VirtualWalkthroughViewer;
43
50
  //# sourceMappingURL=VirtualWalkthroughViewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualWalkthroughViewer.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VirtualWalkthroughViewer.tsx"],"names":[],"mappings":"AAYA,OAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAgB3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAkB5C;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,8CAA8C;IAC9C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,2FAA2F;IAC3F,WAAW,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,iBAAiB,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,QAAA,MAAM,wBAAwB,GAAI,yQAkB/B,6BAA6B,4CA6X/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"VirtualWalkthroughViewer.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VirtualWalkthroughViewer.tsx"],"names":[],"mappings":"AAYA,OAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAO3D,OAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAS3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAmB5C;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,8CAA8C;IAC9C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,2FAA2F;IAC3F,WAAW,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3C,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,iBAAiB,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,QAAA,MAAM,wBAAwB,GAAI,0RAmB/B,6BAA6B,4CAgY/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
@@ -24,6 +24,7 @@ import XrExitButton from './XrExitButton.js';
24
24
  import XrGazeDwell from './XrGazeDwell.js';
25
25
  import XrPointerRay from './XrPointerRay.js';
26
26
  import { XrStartPose } from './XrStartPose.js';
27
+ import useXrRenderTuning from './useXrRenderTuning.js';
27
28
  import { WalkthroughCamera } from './walkthrough-camera.js';
28
29
  import { rayHitsInteractiveUi } from './xr-interactive-ui.js';
29
30
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -59,6 +60,7 @@ const VirtualWalkthroughViewer = ({
59
60
  groundColor,
60
61
  averageCameraHeight = 0,
61
62
  scaleFactor = 1,
63
+ splatModelProps,
62
64
  annotations,
63
65
  onSaveAnnotations,
64
66
  editable = false,
@@ -90,6 +92,7 @@ const VirtualWalkthroughViewer = ({
90
92
  isCurrentlyInVr,
91
93
  isCurrentlyInAr
92
94
  } = useXr();
95
+ useXrRenderTuning();
93
96
  const sceneBoundsRadius = useMemo(() => {
94
97
  if (sceneBounds?.m !== undefined) {
95
98
  return sceneBounds.m;
@@ -250,12 +253,13 @@ const VirtualWalkthroughViewer = ({
250
253
  }), [handleAnnotationUpdate]);
251
254
  const canSave = useMemo(() => localAnnotations.every(annotation => annotation.title && annotation.title.trim() !== ''), [localAnnotations]);
252
255
  const splatModel = useMemo(() => /*#__PURE__*/jsx(SplatModel, {
256
+ ...splatModelProps,
253
257
  splatSrc: splatSrc,
254
258
  splatFormat: splatFormat,
255
259
  rotation: [-180, 0, 0],
256
260
  scaleFactor: scaleFactor,
257
261
  revealRain: isHighPerformance
258
- }, 'splat'), [isHighPerformance, splatSrc, splatFormat, scaleFactor]);
262
+ }, 'splat'), [isHighPerformance, splatSrc, splatFormat, scaleFactor, splatModelProps]);
259
263
  return /*#__PURE__*/jsxs(Fragment, {
260
264
  children: [/*#__PURE__*/jsx(GradientSky, {
261
265
  topColor: skyColor || '#FFFFFF',
@@ -295,6 +299,7 @@ const VirtualWalkthroughViewer = ({
295
299
  script: TfXrNavigation,
296
300
  enabled: !isEdit,
297
301
  enableTeleport: !isCurrentlyInAr && !viewingAnnotation,
302
+ enableSnapVertical: false,
298
303
  isTeleportBlocked: isTeleportBlocked
299
304
  }), !isCurrentlyInXr && /*#__PURE__*/jsx(Script, {
300
305
  script: AutoRotator,
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Holds the XR render settings in place for the duration of a session.
3
+ *
4
+ * Call this once per application. The gsplat settings it writes are scene-global, so a second
5
+ * caller would capture the first caller's tuned values as the state to restore, and the scene would
6
+ * come out of the session still tuned.
7
+ */
8
+ export declare const useXrRenderTuning: () => void;
9
+ export default useXrRenderTuning;
10
+ //# sourceMappingURL=useXrRenderTuning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useXrRenderTuning.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/useXrRenderTuning.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,YAsC7B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+ import { useApp } from '@playcanvas/react/hooks';
3
+ import { applyGsplatTuning, XR_FIXED_FOVEATION, restoreGsplatTuning } from './xr-render-tuning.js';
4
+
5
+ /**
6
+ * Holds the XR render settings in place for the duration of a session.
7
+ *
8
+ * Call this once per application. The gsplat settings it writes are scene-global, so a second
9
+ * caller would capture the first caller's tuned values as the state to restore, and the scene would
10
+ * come out of the session still tuned.
11
+ */
12
+ const useXrRenderTuning = () => {
13
+ const app = useApp();
14
+ useEffect(() => {
15
+ const xr = app.xr;
16
+ if (!xr) {
17
+ return;
18
+ }
19
+ let previous = null;
20
+ const restore = () => {
21
+ if (previous) {
22
+ restoreGsplatTuning(app.scene.gsplat, previous);
23
+ previous = null;
24
+ }
25
+ };
26
+ const handleStart = () => {
27
+ // Guards against capturing already-tuned values as the restore target if `start` ever
28
+ // fires again before a matching `end`.
29
+ if (!previous) {
30
+ previous = applyGsplatTuning(app.scene.gsplat);
31
+ }
32
+ // Only settable on an active session, so it cannot go in the start options alongside
33
+ // framebufferScaleFactor. A no-op when the presentation layer doesn't support it.
34
+ xr.fixedFoveation = XR_FIXED_FOVEATION;
35
+ };
36
+ xr.on('start', handleStart);
37
+ xr.on('end', restore);
38
+ return () => {
39
+ xr.off('start', handleStart);
40
+ xr.off('end', restore);
41
+ restore();
42
+ };
43
+ }, [app]);
44
+ };
45
+
46
+ export { useXrRenderTuning as default, useXrRenderTuning };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Render settings applied for the duration of an XR session. Splats rasterize as alpha-blended
3
+ * quads with no early-z rejection, which makes a headset's stereo pixel throughput the binding
4
+ * constraint on framerate long before splat count is.
5
+ */
6
+ /**
7
+ * Peripheral back-buffer resolution reduction, 0 (off) to 1 (strongest). Ineffective while MSAA is
8
+ * on, which is one reason the application requests `antialias: false`.
9
+ */
10
+ export declare const XR_FIXED_FOVEATION = 1;
11
+ /**
12
+ * The subset of `GSplatParams` this module writes. Structural rather than the PlayCanvas type so
13
+ * the logic can be exercised against a plain object.
14
+ */
15
+ export interface GsplatTuningParams {
16
+ radialSorting: boolean;
17
+ alphaClipForward: number;
18
+ minPixelSize: number;
19
+ }
20
+ /**
21
+ * `radialSorting` orders splats by distance from the camera rather than by depth along its forward
22
+ * axis, which is the more accurate ordering when the camera rotates rather than translates. Sorting
23
+ * runs on a worker and so lags the view by at least a frame; head rotation dominates in a headset,
24
+ * where that lag is what reads as swimming.
25
+ *
26
+ * The other two trade fidelity for fill rate: `alphaClipForward` drops near-transparent splats from
27
+ * the forward pass, and `minPixelSize` drops splats that project to less than a few pixels.
28
+ */
29
+ export declare const XR_GSPLAT_TUNING: GsplatTuningParams;
30
+ /**
31
+ * Applies the XR settings, returning the values that were replaced so they can be restored when the
32
+ * session ends. These live on the scene, so leaving them tuned would degrade desktop rendering.
33
+ */
34
+ export declare const applyGsplatTuning: (params: GsplatTuningParams) => GsplatTuningParams;
35
+ export declare const restoreGsplatTuning: (params: GsplatTuningParams, previous: GsplatTuningParams) => void;
36
+ //# sourceMappingURL=xr-render-tuning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xr-render-tuning.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-render-tuning.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,kBAI9B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,kBAAkB,KAAG,kBAY9D,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,QAAQ,kBAAkB,EAAE,UAAU,kBAAkB,KAAG,IAI9F,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Render settings applied for the duration of an XR session. Splats rasterize as alpha-blended
3
+ * quads with no early-z rejection, which makes a headset's stereo pixel throughput the binding
4
+ * constraint on framerate long before splat count is.
5
+ */
6
+
7
+ /**
8
+ * Peripheral back-buffer resolution reduction, 0 (off) to 1 (strongest). Ineffective while MSAA is
9
+ * on, which is one reason the application requests `antialias: false`.
10
+ */
11
+ const XR_FIXED_FOVEATION = 1;
12
+
13
+ /**
14
+ * The subset of `GSplatParams` this module writes. Structural rather than the PlayCanvas type so
15
+ * the logic can be exercised against a plain object.
16
+ */
17
+
18
+ /**
19
+ * `radialSorting` orders splats by distance from the camera rather than by depth along its forward
20
+ * axis, which is the more accurate ordering when the camera rotates rather than translates. Sorting
21
+ * runs on a worker and so lags the view by at least a frame; head rotation dominates in a headset,
22
+ * where that lag is what reads as swimming.
23
+ *
24
+ * The other two trade fidelity for fill rate: `alphaClipForward` drops near-transparent splats from
25
+ * the forward pass, and `minPixelSize` drops splats that project to less than a few pixels.
26
+ */
27
+ const XR_GSPLAT_TUNING = {
28
+ radialSorting: true,
29
+ alphaClipForward: 0.1,
30
+ minPixelSize: 1
31
+ };
32
+
33
+ /**
34
+ * Applies the XR settings, returning the values that were replaced so they can be restored when the
35
+ * session ends. These live on the scene, so leaving them tuned would degrade desktop rendering.
36
+ */
37
+ const applyGsplatTuning = params => {
38
+ const previous = {
39
+ radialSorting: params.radialSorting,
40
+ alphaClipForward: params.alphaClipForward,
41
+ minPixelSize: params.minPixelSize
42
+ };
43
+ params.radialSorting = XR_GSPLAT_TUNING.radialSorting;
44
+ params.alphaClipForward = XR_GSPLAT_TUNING.alphaClipForward;
45
+ params.minPixelSize = XR_GSPLAT_TUNING.minPixelSize;
46
+ return previous;
47
+ };
48
+ const restoreGsplatTuning = (params, previous) => {
49
+ params.radialSorting = previous.radialSorting;
50
+ params.alphaClipForward = previous.alphaClipForward;
51
+ params.minPixelSize = previous.minPixelSize;
52
+ };
53
+
54
+ export { XR_FIXED_FOVEATION, XR_GSPLAT_TUNING, applyGsplatTuning, restoreGsplatTuning };
@@ -1 +1 @@
1
- {"version":3,"file":"useXr.d.ts","sourceRoot":"","sources":["../../src/hooks/useXr.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjC,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAOD,eAAO,MAAM,KAAK,GAAI,cAAa,YAAiB;0BA4CT,MAAM;oBAGtC,MAAM;;;;;CA6BhB,CAAC;AAEF,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"useXr.d.ts","sourceRoot":"","sources":["../../src/hooks/useXr.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjC,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAgBD,eAAO,MAAM,KAAK,GAAI,cAAa,YAAiB;0BA4CT,MAAM;oBAGtC,MAAM;;;;;CA8BhB,CAAC;AAEF,eAAe,KAAK,CAAC"}
package/hooks/useXr.js CHANGED
@@ -6,6 +6,15 @@ const XR_TYPES = {
6
6
  VR: XRTYPE_VR,
7
7
  AR: XRTYPE_AR
8
8
  };
9
+
10
+ /**
11
+ * Factor PlayCanvas applies on top of the device pixel ratio to compute the headset's
12
+ * framebuffer resolution: `app.graphicsDevice.maxPixelRatio / window.devicePixelRatio *
13
+ * XR_FRAMEBUFFER_SCALE_FACTOR`. Splat rendering in stereo is fill-rate bound, so this (and
14
+ * `maxPixelRatio`, which also multiplies XR resolution) is the largest lever on XR framerate.
15
+ * Read once when the session starts and fixed for its lifetime.
16
+ */
17
+ const XR_FRAMEBUFFER_SCALE_FACTOR = 1;
9
18
  const useXr = ({
10
19
  onError
11
20
  } = {}) => {
@@ -56,6 +65,7 @@ const useXr = ({
56
65
  const startXr = useCallback(type => {
57
66
  const camera = app.root.findComponent('camera');
58
67
  app.xr?.start(camera, XR_TYPES[type], XRSPACE_LOCALFLOOR, {
68
+ framebufferScaleFactor: XR_FRAMEBUFFER_SCALE_FACTOR,
59
69
  callback: err => {
60
70
  if (err) {
61
71
  onError?.(err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraware/web-components",
3
- "version": "5.0.1-rc.0",
3
+ "version": "5.0.1",
4
4
  "author": "Terraformation Inc.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {