@terraware/web-components 4.3.1-rc.0 → 4.3.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.
Files changed (30) hide show
  1. package/components/VirtualWalkthrough/BoundaryWall.d.ts +1 -0
  2. package/components/VirtualWalkthrough/BoundaryWall.d.ts.map +1 -1
  3. package/components/VirtualWalkthrough/SplatModel.d.ts +1 -6
  4. package/components/VirtualWalkthrough/SplatModel.d.ts.map +1 -1
  5. package/components/VirtualWalkthrough/SplatModel.js +0 -2
  6. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts +9 -2
  7. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts.map +1 -1
  8. package/components/VirtualWalkthrough/TfAnnotationManager.js +32 -11
  9. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts +0 -14
  10. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts.map +1 -1
  11. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +25 -38
  12. package/components/VirtualWalkthrough/VrAnnotationPanel.d.ts +2 -1
  13. package/components/VirtualWalkthrough/VrAnnotationPanel.d.ts.map +1 -1
  14. package/components/VirtualWalkthrough/VrAnnotationPanel.js +4 -2
  15. package/components/VirtualWalkthrough/boundary-wall.d.ts +2 -0
  16. package/components/VirtualWalkthrough/boundary-wall.d.ts.map +1 -1
  17. package/components/VirtualWalkthrough/boundary-wall.js +2 -0
  18. package/components/VirtualWalkthrough/vr-annotation-panel.d.ts +1 -0
  19. package/components/VirtualWalkthrough/vr-annotation-panel.d.ts.map +1 -1
  20. package/components/VirtualWalkthrough/vr-annotation-panel.js +9 -6
  21. package/components/VirtualWalkthrough/walkthrough-camera.d.ts +1 -0
  22. package/components/VirtualWalkthrough/walkthrough-camera.d.ts.map +1 -1
  23. package/components/VirtualWalkthrough/walkthrough-camera.js +5 -3
  24. package/package.json +1 -1
  25. package/components/VirtualWalkthrough/XrStartPose.d.ts +0 -52
  26. package/components/VirtualWalkthrough/XrStartPose.d.ts.map +0 -1
  27. package/components/VirtualWalkthrough/XrStartPose.js +0 -128
  28. package/components/VirtualWalkthrough/xr-start-pose.d.ts +0 -57
  29. package/components/VirtualWalkthrough/xr-start-pose.d.ts.map +0 -1
  30. package/components/VirtualWalkthrough/xr-start-pose.js +0 -80
@@ -1,5 +1,6 @@
1
1
  import { Vec3 } from 'playcanvas';
2
2
  export type BoundaryWallProps = {
3
+ /** World-space, i.e. already multiplied by the scene scaleFactor. */
3
4
  center: Vec3;
4
5
  radius: number;
5
6
  groundPlane: Vec3[];
@@ -1 +1 @@
1
- {"version":3,"file":"BoundaryWall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/BoundaryWall.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIlC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,QAAA,MAAM,YAAY,GAAI,wCAAwC,iBAAiB,4CAuB9E,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"BoundaryWall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/BoundaryWall.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIlC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,qEAAqE;IACrE,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,QAAA,MAAM,YAAY,GAAI,wCAAwC,iBAAiB,4CAuB9E,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -9,11 +9,6 @@ export interface SplatModelProps {
9
9
  */
10
10
  splatFormat?: SplatFormat;
11
11
  rotation?: [number, number, number];
12
- /**
13
- * Scale applied to the splat entity. Splat models come out of reconstruction in their own
14
- * arbitrary units, so this is what converts them world units.
15
- */
16
- scaleFactor?: number;
17
12
  cropAabbMin?: [number, number, number];
18
13
  cropAabbMax?: [number, number, number];
19
14
  cropEdgeScaleFactor?: number;
@@ -22,6 +17,6 @@ export interface SplatModelProps {
22
17
  revealRain?: boolean;
23
18
  onError?: (error: Error) => void;
24
19
  }
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>;
20
+ declare const _default: React.MemoExoticComponent<({ splatSrc, splatFormat, rotation, cropAabbMin, cropAabbMax, cropEdgeScaleFactor, cropFade, cropFadeDistance, revealRain, onError, }: SplatModelProps) => import("react/jsx-runtime").JSX.Element | null>;
26
21
  export default _default;
27
22
  //# 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,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;yLAaE,eAAe;AAkClB,wBAAgC"}
@@ -13,7 +13,6 @@ const SplatModel = ({
13
13
  splatSrc,
14
14
  splatFormat,
15
15
  rotation,
16
- scaleFactor = 1,
17
16
  cropAabbMin,
18
17
  cropAabbMax,
19
18
  cropEdgeScaleFactor,
@@ -48,7 +47,6 @@ const SplatModel = ({
48
47
  return /*#__PURE__*/jsxs(Entity, {
49
48
  name: "splat",
50
49
  rotation: rotation,
51
- scale: [scaleFactor, scaleFactor, scaleFactor],
52
50
  children: [/*#__PURE__*/jsx(GSplat, {
53
51
  asset: asset,
54
52
  unified: true
@@ -8,6 +8,7 @@ import { AnnotationIconType } from './Annotation';
8
8
  export declare class TfAnnotationManager extends PcAnnotationManager {
9
9
  static scriptName: string;
10
10
  private _maxWorldSize;
11
+ private _scratchScale;
11
12
  private _customParentDom;
12
13
  private _clickHandlersAttached;
13
14
  private _hotspotBackgroundColor;
@@ -97,8 +98,8 @@ export declare class TfAnnotationManager extends PcAnnotationManager {
97
98
  _updateAnnotationPositions(annotation: any, resources: any, screenPos: any): void;
98
99
  /**
99
100
  * Override to also disable the hotspot mesh (not just the DOM) when the annotation is behind the
100
- * camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane would stay
101
- * in view at its full unit size until the camera moves the anchor in front.
101
+ * camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane under a
102
+ * scaled content-root would stay huge in view until the camera moves the anchor in front.
102
103
  * @private
103
104
  */
104
105
  _hideAnnotationElements(annotation: any, resources: any): void;
@@ -108,6 +109,12 @@ export declare class TfAnnotationManager extends PcAnnotationManager {
108
109
  * @private
109
110
  */
110
111
  _updateAnnotationRotationAndScale(annotation: any): void;
112
+ /**
113
+ * World-space uniform scale of the annotation entity's parent (e.g. a scaled
114
+ * content-root). Returns 1 when there is no parent or it has no scale.
115
+ * @private
116
+ */
117
+ _getParentWorldScale(entity: any): number;
111
118
  /**
112
119
  * Override to create hotspot texture with custom background color.
113
120
  * @private
@@ -1 +1 @@
1
- {"version":3,"file":"TfAnnotationManager.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfAnnotationManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,OAAO,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAIlG,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAelD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC1D,MAAM,CAAC,UAAU,SAAyB;IAE1C,OAAO,CAAC,aAAa,CAAO;IAC5B,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,sBAAsB,CAAkB;IAChD,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;;;;;OAQG;IACH,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,YAAY,IAJQ,MAAM,CAM7B;IAED;;;;;;;;;;OAUG;IACH,IAAI,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAEvC;IAED,IAAI,sBAAsB,IAJQ,MAAM,CAMvC;IAED;;OAEG;IACH,UAAU;IAgBV;;;;OAIG;IACH,eAAe;IAiBf;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,kBAAkB;IAazC;;OAEG;IACH,MAAM;IAmCN;;;;OAIG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAcnC;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG;IAiBpC;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,wBAAwB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM;IAW/E;;;;OAIG;IACH,cAAc,CAAC,UAAU,EAAE,GAAG;IAI9B;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG;IASlD,mBAAmB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO;IAKrD,0BAA0B;IAK1B,YAAY,CAAC,WAAW,EAAE,GAAG;IAK7B,aAAa,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM;IAI7C;;;OAGG;IACH,0BAA0B,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IA6B1E;;;;;OAKG;IACH,uBAAuB,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAMvD;;;;OAIG;IACH,iCAAiC,CAAC,UAAU,EAAE,GAAG;IA2CjD;;;OAGG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAuB,EAAE,WAAW,SAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB;CAyD7G"}
1
+ {"version":3,"file":"TfAnnotationManager.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfAnnotationManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,OAAO,EAAQ,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAIlG,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAelD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC1D,MAAM,CAAC,UAAU,SAAyB;IAE1C,OAAO,CAAC,aAAa,CAAO;IAC5B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,sBAAsB,CAAkB;IAChD,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;;;;;OAQG;IACH,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,YAAY,IAJQ,MAAM,CAM7B;IAED;;;;;;;;;;OAUG;IACH,IAAI,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAEvC;IAED,IAAI,sBAAsB,IAJQ,MAAM,CAMvC;IAED;;OAEG;IACH,UAAU;IAgBV;;;;OAIG;IACH,eAAe;IAiBf;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,kBAAkB;IAazC;;OAEG;IACH,MAAM;IAmCN;;;;OAIG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAcnC;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG;IAiBpC;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,wBAAwB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM;IAW/E;;;;OAIG;IACH,cAAc,CAAC,UAAU,EAAE,GAAG;IAI9B;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG;IASlD,mBAAmB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO;IAKrD,0BAA0B;IAK1B,YAAY,CAAC,WAAW,EAAE,GAAG;IAK7B,aAAa,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM;IAI7C;;;OAGG;IACH,0BAA0B,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IA6B1E;;;;;OAKG;IACH,uBAAuB,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAMvD;;;;OAIG;IACH,iCAAiC,CAAC,UAAU,EAAE,GAAG;IAiDjD;;;;OAIG;IACH,oBAAoB,CAAC,MAAM,EAAE,GAAG;IAUhC;;;OAGG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAuB,EAAE,WAAW,SAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB;CAyD7G"}
@@ -1,6 +1,6 @@
1
1
  import { createElement } from 'react';
2
2
  import { renderToStaticMarkup } from 'react-dom/server';
3
- import { Texture, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from 'playcanvas';
3
+ import { Vec3, Texture, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from 'playcanvas';
4
4
  import { AnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
5
5
  import Icon from '../Icon/Icon.js';
6
6
 
@@ -24,6 +24,7 @@ const HOTSPOT_HOVER_SCALE = 1.3;
24
24
  class TfAnnotationManager extends AnnotationManager {
25
25
  static scriptName = 'tfAnnotationManager';
26
26
  _maxWorldSize = 1.0;
27
+ _scratchScale = new Vec3();
27
28
  _customParentDom = null;
28
29
  _clickHandlersAttached = new Set();
29
30
  _hotspotBackgroundColor = '#2C8658';
@@ -162,9 +163,9 @@ class TfAnnotationManager extends AnnotationManager {
162
163
  super._registerAnnotation(annotation);
163
164
  this._applyAnnotationIcon(annotation);
164
165
 
165
- // Keep the hotspot mesh hidden until the first scale pass sizes it. The annotation entity
166
- // starts at unit scale, so the plane would render a metre across for the frames before
167
- // _updateAnnotationRotationAndScale runs.
166
+ // Keep the hotspot mesh hidden until the first scale pass clamps it. The annotation entity
167
+ // starts at unit local scale, so under a scaled content-root the plane would render many world
168
+ // units across for the frames before _updateAnnotationRotationAndScale runs.
168
169
  const resources = this._annotationResources.get(annotation);
169
170
  if (resources) {
170
171
  resources.baseEntity.enabled = false;
@@ -280,8 +281,8 @@ class TfAnnotationManager extends AnnotationManager {
280
281
 
281
282
  /**
282
283
  * Override to also disable the hotspot mesh (not just the DOM) when the annotation is behind the
283
- * camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane would stay
284
- * in view at its full unit size until the camera moves the anchor in front.
284
+ * camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane under a
285
+ * scaled content-root would stay huge in view until the camera moves the anchor in front.
285
286
  * @private
286
287
  */
287
288
  _hideAnnotationElements(annotation, resources) {
@@ -308,9 +309,14 @@ class TfAnnotationManager extends AnnotationManager {
308
309
  // The world size that projects to _hotspotSize screen pixels at this distance.
309
310
  const targetWorldSize = this._hotspotSize / screenHeight * (2 * distance / projMatrix.data[5]);
310
311
 
311
- // Annotations hang off an unscaled parent, so local scale is world scale and maxWorldSize
312
- // clamps the hotspot at a size in metres.
313
- const clampedScale = Math.min(targetWorldSize, this._maxWorldSize);
312
+ // setLocalScale is applied on top of the parent's world scale (e.g. a scaled
313
+ // content-root), so divide it out to keep the on-screen size independent of it.
314
+ const parentScale = this._getParentWorldScale(annotation.entity);
315
+ const unclampedScale = targetWorldSize / parentScale;
316
+
317
+ // Clamp in the annotation's own (parent-local) space so maxWorldSize keeps the
318
+ // same size relative to the model regardless of the content-root scale.
319
+ const clampedScale = Math.min(unclampedScale, this._maxWorldSize);
314
320
  const hovered = this._hoverAnnotation === annotation;
315
321
  const hoverScale = hovered ? HOTSPOT_HOVER_SCALE : 1;
316
322
  const entityScale = clampedScale * hoverScale;
@@ -318,7 +324,8 @@ class TfAnnotationManager extends AnnotationManager {
318
324
 
319
325
  // Scale the hotspot DOM element proportionally when clamped, including the
320
326
  // hover growth so it tracks the visible circle. This is a pure screen-space
321
- // size, so it uses the true _hotspotSize and the clamp ratio only.
327
+ // size, so it uses the true _hotspotSize and the clamp ratio only (never the
328
+ // parent scale, which the DOM overlay does not inherit).
322
329
  const resources = this._annotationResources.get(annotation);
323
330
  if (resources) {
324
331
  // This branch only runs for annotations in front of the camera, and the scale above is now
@@ -327,7 +334,7 @@ class TfAnnotationManager extends AnnotationManager {
327
334
  resources.overlayEntity.enabled = true;
328
335
  }
329
336
  if (resources && resources.hotspotDom) {
330
- const clampRatio = clampedScale / targetWorldSize;
337
+ const clampRatio = clampedScale / unclampedScale;
331
338
  const baseSize = this._hotspotSize + 5; // Match the +5 from the stylesheet
332
339
  const scaledSize = baseSize * clampRatio * hoverScale;
333
340
  resources.hotspotDom.style.width = `${scaledSize}px`;
@@ -335,6 +342,20 @@ class TfAnnotationManager extends AnnotationManager {
335
342
  }
336
343
  }
337
344
 
345
+ /**
346
+ * World-space uniform scale of the annotation entity's parent (e.g. a scaled
347
+ * content-root). Returns 1 when there is no parent or it has no scale.
348
+ * @private
349
+ */
350
+ _getParentWorldScale(entity) {
351
+ const parent = entity.parent;
352
+ if (!parent) {
353
+ return 1;
354
+ }
355
+ parent.getWorldTransform().getScale(this._scratchScale);
356
+ return this._scratchScale.x || 1;
357
+ }
358
+
338
359
  /**
339
360
  * Override to create hotspot texture with custom background color.
340
361
  * @private
@@ -1,35 +1,21 @@
1
1
  import { AnnotationProps } from './Annotation';
2
2
  import { SplatControlsStrings } from './SplatControls';
3
3
  import { SplatFormat } from './splatFormat';
4
- /**
5
- * Every coordinate, distance and size in this component is world-space, in the metres an XR
6
- * headset imposes on the scene: `scaleFactor` converts the splat's own arbitrary units to them,
7
- * and is applied to nothing but the splat itself.
8
- */
9
4
  export interface VirtualWalkthroughViewerProps {
10
5
  splatSrc: string;
11
6
  splatFormat?: SplatFormat;
12
- /** World-space point the camera looks at. */
13
7
  origin?: [number, number, number];
14
- /** World-space point the camera starts at. */
15
8
  cameraPosition?: [number, number, number];
16
- /** World-space centre (x, y, z) and radius (m) of the circle the camera is held inside. */
17
9
  sceneBounds?: {
18
10
  x: number;
19
11
  y: number;
20
12
  z: number;
21
13
  m: number;
22
14
  };
23
- /** Three world-space points defining the plane the camera walks on. */
24
15
  groundPlane?: [number, number, number][];
25
16
  skyColor?: string;
26
17
  groundColor?: string;
27
- /** Eye height (m) above the ground plane. */
28
18
  averageCameraHeight?: number;
29
- /**
30
- * Scale that converts the splat model's units to the world's metres. Applied to the splat
31
- * entity alone — every other coordinate this component takes is already world-space.
32
- */
33
19
  scaleFactor?: number;
34
20
  annotations: AnnotationProps[];
35
21
  onSaveAnnotations: (annotations: AnnotationProps[]) => void | Promise<void>;
@@ -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;AAM3D,OAAsB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAUtE,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,OAAO,EAAE,oBAAoB,CAAC;IAC9B,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,kRAmB/B,6BAA6B,4CAqX/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;AAM3D,OAAsB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAStE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAa5C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,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,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,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,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,OAAO,EAAE,oBAAoB,CAAC;IAC9B,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,kRAmB/B,6BAA6B,4CAuX/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
@@ -23,7 +23,6 @@ import XrAnnotationInteraction from './XrAnnotationInteraction.js';
23
23
  import XrExitButton from './XrExitButton.js';
24
24
  import XrGazeDwell from './XrGazeDwell.js';
25
25
  import XrPointerRay from './XrPointerRay.js';
26
- import { XrStartPose } from './XrStartPose.js';
27
26
  import { WalkthroughCamera } from './walkthrough-camera.js';
28
27
  import { rayHitsInteractiveUi } from './xr-interactive-ui.js';
29
28
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -33,21 +32,9 @@ const DEFAULT_POSITION = [1, 0.1, 0];
33
32
 
34
33
  // How far outside the clamp radius the boundary wall stands. Without it the wall would be coplanar
35
34
  // with the surface the head is clamped to, so a pinned user would have it in their face and through
36
- // their near clip plane.
35
+ // their near clip plane. Deliberately an absolute world-space distance rather than one scaled by
36
+ // scaleFactor: it is about the size of the user's body, not about the size of the scene.
37
37
  const WALL_INSET = 0.25;
38
-
39
- /**
40
- * Largest world size (metres) a hotspot is allowed to reach as the camera approaches it, so it
41
- * stops growing rather than filling the view from close up.
42
- */
43
- const MAX_HOTSPOT_WORLD_SIZE = 0.75;
44
-
45
- /**
46
- * Every coordinate, distance and size in this component is world-space, in the metres an XR
47
- * headset imposes on the scene: `scaleFactor` converts the splat's own arbitrary units to them,
48
- * and is applied to nothing but the splat itself.
49
- */
50
-
51
38
  const VirtualWalkthroughViewer = ({
52
39
  splatSrc,
53
40
  splatFormat,
@@ -98,34 +85,36 @@ const VirtualWalkthroughViewer = ({
98
85
  const dx = cameraPosition[0] - origin[0];
99
86
  const dy = cameraPosition[1] - origin[1];
100
87
  const dz = cameraPosition[2] - origin[2];
101
- return Math.sqrt(dx * dx + dy * dy + dz * dz);
88
+ return Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.5;
102
89
  }, [cameraPosition, sceneBounds, origin]);
103
90
  const sceneBoundsCenter = useMemo(() => sceneBounds ? new Vec3(sceneBounds.x, sceneBounds.y, sceneBounds.z) : new Vec3(origin[0], cameraPosition[1], origin[2]), [sceneBounds, origin, cameraPosition]);
91
+ const cameraBoundsCenter = useMemo(() => sceneBoundsCenter.clone().mulScalar(scaleFactor), [sceneBoundsCenter, scaleFactor]);
104
92
  const groundPlane = useMemo(() => groundPlaneProp?.length === 3 ? groundPlaneProp.map(p => new Vec3(p[0], p[1], p[2])) : [], [groundPlaneProp]);
93
+ const cameraGroundPlane = useMemo(() => groundPlane.map(p => p.clone().mulScalar(scaleFactor)), [groundPlane, scaleFactor]);
105
94
  useEffect(() => {
106
95
  setCamera(origin, cameraPosition);
107
96
  }, [origin, cameraPosition, setCamera]);
108
97
  useEffect(() => {
109
- if (!groundPlane.length) {
98
+ if (!cameraGroundPlane.length) {
110
99
  return;
111
100
  }
112
101
  // @ts-expect-error - scripts are added dynamically to the camera entity
113
102
  const walkthroughCam = app.root.findByName('camera')?.script?.walkthroughCamera;
114
103
  if (walkthroughCam) {
115
104
  // Should be changed to a react prop if shallowEquals in playcanvas/react is fixed (see https://github.com/playcanvas/react/pull/298)
116
- walkthroughCam.groundPlane = groundPlane;
105
+ walkthroughCam.groundPlane = cameraGroundPlane;
117
106
  }
118
- }, [groundPlane, app]);
107
+ }, [cameraGroundPlane, app]);
119
108
  useEffect(() => {
120
109
  // Set imperatively for the same reason as BoundaryRing: boundsCenter is a Vec3, and the
121
110
  // @playcanvas/react memo() comparator stops at the first prop with an .equals() method.
122
111
  // @ts-expect-error - scripts are added dynamically to the entity
123
112
  const navigation = app.root.findByName('camera-root')?.script?.tfXrNavigation;
124
113
  if (navigation) {
125
- navigation.boundsCenter = sceneBoundsCenter;
126
- navigation.boundsRadius = sceneBoundsRadius;
114
+ navigation.boundsCenter = cameraBoundsCenter;
115
+ navigation.boundsRadius = sceneBoundsRadius * scaleFactor;
127
116
  }
128
- }, [app, sceneBoundsCenter, sceneBoundsRadius]);
117
+ }, [app, cameraBoundsCenter, sceneBoundsRadius, scaleFactor]);
129
118
  const handleToggleFreeFly = useCallback(() => {
130
119
  const newFreeFly = !isFreeFly;
131
120
  // @ts-expect-error - scripts are added dynamically to the camera entity
@@ -243,9 +232,8 @@ const VirtualWalkthroughViewer = ({
243
232
  splatSrc: splatSrc,
244
233
  splatFormat: splatFormat,
245
234
  rotation: [-180, 0, 0],
246
- scaleFactor: scaleFactor,
247
235
  revealRain: isHighPerformance
248
- }, 'splat'), [isHighPerformance, splatSrc, splatFormat, scaleFactor]);
236
+ }, 'splat'), [isHighPerformance, splatSrc, splatFormat]);
249
237
  return /*#__PURE__*/jsxs(Fragment, {
250
238
  children: [/*#__PURE__*/jsx(GradientSky, {
251
239
  topColor: skyColor || '#FFFFFF',
@@ -260,10 +248,13 @@ const VirtualWalkthroughViewer = ({
260
248
  fov: 60
261
249
  }), !isCurrentlyInXr && /*#__PURE__*/jsx(Script, {
262
250
  script: WalkthroughCamera,
263
- boundsCenter: sceneBoundsCenter,
264
- boundsRadius: sceneBoundsRadius,
251
+ boundsCenter: cameraBoundsCenter,
252
+ boundsRadius: sceneBoundsRadius * scaleFactor,
265
253
  enableFly: !isTextFieldFocused,
266
- averageCameraHeight: averageCameraHeight
254
+ averageCameraHeight: scaleFactor * averageCameraHeight,
255
+ moveSpeed: 0.3 * scaleFactor,
256
+ moveFastSpeed: 0.5 * scaleFactor,
257
+ moveSlowSpeed: 0.15 * scaleFactor
267
258
  })]
268
259
  }), /*#__PURE__*/jsx(XrExitButton, {}), /*#__PURE__*/jsx(Script, {
269
260
  script: XrControllers,
@@ -272,15 +263,10 @@ const VirtualWalkthroughViewer = ({
272
263
  onDismiss: handleCloseAnnotation
273
264
  }), /*#__PURE__*/jsx(XrPointerRay, {}), isCurrentlyInVr && viewingAnnotation && /*#__PURE__*/jsx(VrAnnotationPanel, {
274
265
  annotation: viewingAnnotation,
275
- annotationIndex: viewingAnnotationIndex
266
+ annotationIndex: viewingAnnotationIndex,
267
+ scaleFactor: scaleFactor
276
268
  }, viewingAnnotationIndex), isCurrentlyInXr && /*#__PURE__*/jsx(XrGazeDwell, {
277
269
  activeIndex: viewingAnnotationIndex
278
- }), /*#__PURE__*/jsx(Script, {
279
- script: XrStartPose,
280
- targetX: cameraPosition[0],
281
- targetZ: cameraPosition[2],
282
- focusX: origin[0],
283
- focusZ: origin[2]
284
270
  }), /*#__PURE__*/jsx(Script, {
285
271
  script: TfXrNavigation,
286
272
  enabled: !isEdit,
@@ -295,6 +281,7 @@ const VirtualWalkthroughViewer = ({
295
281
  })]
296
282
  }), /*#__PURE__*/jsxs(Entity, {
297
283
  name: "content-root",
284
+ scale: [scaleFactor, scaleFactor, scaleFactor],
298
285
  children: [splatModel, sceneBounds?.m !== undefined && groundPlane.length === 3 && /*#__PURE__*/jsx(BoundaryRing, {
299
286
  center: sceneBoundsCenter,
300
287
  radius: sceneBoundsRadius,
@@ -305,7 +292,7 @@ const VirtualWalkthroughViewer = ({
305
292
  script: TfAnnotationManager,
306
293
  enabled: true,
307
294
  hotspotSize: 30,
308
- maxWorldSize: MAX_HOTSPOT_WORLD_SIZE,
295
+ maxWorldSize: 0.05,
309
296
  opacity: 1,
310
297
  hotspotColor: new Color().fromString(theme.palette.TwClrIcnBrand),
311
298
  hoverColor: new Color().fromString('#ffffff'),
@@ -324,9 +311,9 @@ const VirtualWalkthroughViewer = ({
324
311
  }, `annotation-${index}`))]
325
312
  })]
326
313
  }), isCurrentlyInXr && sceneBoundsRadius > 0 && /*#__PURE__*/jsx(BoundaryWall, {
327
- center: sceneBoundsCenter,
328
- radius: sceneBoundsRadius + WALL_INSET,
329
- groundPlane: groundPlane,
314
+ center: cameraBoundsCenter,
315
+ radius: sceneBoundsRadius * scaleFactor + WALL_INSET,
316
+ groundPlane: cameraGroundPlane,
330
317
  baseY: 0
331
318
  }), /*#__PURE__*/jsx(SplatControls, {
332
319
  defaultCameraFocus: origin,
@@ -2,7 +2,8 @@ import type { AnnotationProps } from './Annotation';
2
2
  interface VrAnnotationPanelProps {
3
3
  annotation: AnnotationProps;
4
4
  annotationIndex: number;
5
+ scaleFactor: number;
5
6
  }
6
- declare const VrAnnotationPanel: ({ annotation, annotationIndex }: VrAnnotationPanelProps) => import("react/jsx-runtime").JSX.Element;
7
+ declare const VrAnnotationPanel: ({ annotation, annotationIndex, scaleFactor }: VrAnnotationPanelProps) => import("react/jsx-runtime").JSX.Element;
7
8
  export default VrAnnotationPanel;
8
9
  //# sourceMappingURL=VrAnnotationPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VrAnnotationPanel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VrAnnotationPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,QAAA,MAAM,iBAAiB,GAAI,iCAAiC,sBAAsB,4CAWjF,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"VrAnnotationPanel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VrAnnotationPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,UAAU,sBAAsB;IAC9B,UAAU,EAAE,eAAe,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,QAAA,MAAM,iBAAiB,GAAI,8CAA8C,sBAAsB,4CAY9F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -6,7 +6,8 @@ import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  const VrAnnotationPanel = ({
8
8
  annotation,
9
- annotationIndex
9
+ annotationIndex,
10
+ scaleFactor
10
11
  }) => /*#__PURE__*/jsx(Entity, {
11
12
  name: "vr-annotation-panel",
12
13
  children: /*#__PURE__*/jsx(Script, {
@@ -15,7 +16,8 @@ const VrAnnotationPanel = ({
15
16
  label: annotation.label,
16
17
  bodyText: annotation.bodyText,
17
18
  imageUrls: annotation.imageUrls,
18
- annotationIndex: annotationIndex
19
+ annotationIndex: annotationIndex,
20
+ scaleFactor: scaleFactor
19
21
  })
20
22
  });
21
23
 
@@ -42,6 +42,8 @@ export declare const boundaryWallMesh: ({ center, radius, groundPlane, baseY, to
42
42
  *
43
43
  * center / radius / groundPlane / baseY are set imperatively by the BoundaryWall component (see
44
44
  * BoundaryWall.tsx for why they are not reactive Script props), which calls `rebuild()` afterwards.
45
+ * All of them are world-space: this entity is deliberately mounted outside `content-root`, which
46
+ * carries the scene's scaleFactor.
45
47
  */
46
48
  export declare class BoundaryWallScript extends Script {
47
49
  static scriptName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"boundary-wall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-wall.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,EAOL,MAAM,EAEN,IAAI,EACL,MAAM,YAAY,CAAC;AAKpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,gHAAgH;IAChH,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,sEAQ9B,0BAA0B,KAAG,oBAqD/B,CAAC;AA2EF;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,MAAM;IAC5C,MAAM,CAAC,UAAU,SAAkB;IAEnC,MAAM,OAAc;IACpB,MAAM,SAAK;IACX,WAAW,EAAE,IAAI,EAAE,CAAM;IACzB,uEAAuE;IACvE,KAAK,SAAK;IACV;;;OAGG;IACH,IAAI,SAAO;IACX,QAAQ,SAAM;IACd,WAAW,SAAO;IAClB,2CAA2C;IAC3C,SAAS,SAAS;IAClB,0EAA0E;IAC1E,YAAY,SAAO;IACnB,0EAA0E;IAC1E,gBAAgB,SAAO;IACvB,SAAS,QAA2B;IACpC,SAAS,QAA2B;IAEpC,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,UAAU,CAAuB;IAEzC,UAAU;IAwBV;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO;IA6CP,MAAM;IAgCN,OAAO,CAAC,UAAU;CASnB"}
1
+ {"version":3,"file":"boundary-wall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-wall.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,EAOL,MAAM,EAEN,IAAI,EACL,MAAM,YAAY,CAAC;AAKpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,gHAAgH;IAChH,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,GAAI,sEAQ9B,0BAA0B,KAAG,oBAqD/B,CAAC;AA2EF;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,SAAQ,MAAM;IAC5C,MAAM,CAAC,UAAU,SAAkB;IAEnC,MAAM,OAAc;IACpB,MAAM,SAAK;IACX,WAAW,EAAE,IAAI,EAAE,CAAM;IACzB,uEAAuE;IACvE,KAAK,SAAK;IACV;;;OAGG;IACH,IAAI,SAAO;IACX,QAAQ,SAAM;IACd,WAAW,SAAO;IAClB,2CAA2C;IAC3C,SAAS,SAAS;IAClB,0EAA0E;IAC1E,YAAY,SAAO;IACnB,0EAA0E;IAC1E,gBAAgB,SAAO;IACvB,SAAS,QAA2B;IACpC,SAAS,QAA2B;IAEpC,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,WAAW,CAAuC;IAC1D,OAAO,CAAC,UAAU,CAAuB;IAEzC,UAAU;IAwBV;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAW5B,OAAO;IA6CP,MAAM;IAgCN,OAAO,CAAC,UAAU;CASnB"}
@@ -179,6 +179,8 @@ const wallFragmentGLSL = /* glsl */`
179
179
  *
180
180
  * center / radius / groundPlane / baseY are set imperatively by the BoundaryWall component (see
181
181
  * BoundaryWall.tsx for why they are not reactive Script props), which calls `rebuild()` afterwards.
182
+ * All of them are world-space: this entity is deliberately mounted outside `content-root`, which
183
+ * carries the scene's scaleFactor.
182
184
  */
183
185
  class BoundaryWallScript extends Script {
184
186
  static scriptName = 'boundaryWall';
@@ -6,6 +6,7 @@ export declare class VrAnnotationPanel extends Script {
6
6
  bodyText?: string;
7
7
  imageUrls?: string[];
8
8
  annotationIndex: number;
9
+ scaleFactor: number;
9
10
  private _material?;
10
11
  private _texture?;
11
12
  private _mesh?;
@@ -1 +1 @@
1
- {"version":3,"file":"vr-annotation-panel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/vr-annotation-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,MAAM,EAGN,IAAI,EAEL,MAAM,YAAY,CAAC;AAmCpB,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,MAAM,CAAC,UAAU,SAAuB;IAGxC,KAAK,SAAM;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,SAAM;IAErB,OAAO,CAAC,SAAS,CAAC,CAAmB;IACrC,OAAO,CAAC,QAAQ,CAAC,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,OAAO,CAAC,CAAoB;IACpC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,aAAa,CAAc;IAEnC,OAAO,CAAC,SAAS,CAsBf;IAEF,UAAU;IAwCV,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IAqFpB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,WAAW;IAmBnB,8FAA8F;IAC9F,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO;IAIpD,MAAM;IAmCN,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;CAYlB"}
1
+ {"version":3,"file":"vr-annotation-panel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/vr-annotation-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,MAAM,EAGN,IAAI,EAEL,MAAM,YAAY,CAAC;AAiCpB,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,MAAM,CAAC,UAAU,SAAuB;IAGxC,KAAK,SAAM;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,eAAe,SAAM;IACrB,WAAW,SAAK;IAEhB,OAAO,CAAC,SAAS,CAAC,CAAmB;IACrC,OAAO,CAAC,QAAQ,CAAC,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,OAAO,CAAC,CAAoB;IACpC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,aAAa,CAAc;IAEnC,OAAO,CAAC,SAAS,CAsBf;IAEF,UAAU;IAwCV,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,UAAU;IAkBlB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IAqFpB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,WAAW;IAmBnB,8FAA8F;IAC9F,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO;IAIpD,MAAM;IAsCN,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,SAAS;CAYlB"}
@@ -4,15 +4,14 @@ import { rayQuadHit } from './xr-ray-quad.js';
4
4
 
5
5
  const CANVAS_WIDTH = 1024;
6
6
  const CANVAS_HEIGHT = 768;
7
- const PANEL_WIDTH = 7.5;
8
- const PANEL_HEIGHT = PANEL_WIDTH * CANVAS_HEIGHT / CANVAS_WIDTH;
9
7
 
10
- /** Gap (m) between the top of the hotspot and the bottom edge of the panel. */
11
- const PANEL_GAP = 0.75;
8
+ /** Panel width in world meters; height derived from the canvas aspect ratio. */
9
+ const PANEL_WIDTH = 0.5;
10
+ const PANEL_HEIGHT = PANEL_WIDTH * CANVAS_HEIGHT / CANVAS_WIDTH;
12
11
 
13
12
  /** World-space offset from the anchored hotspot: raise the panel above it so the
14
13
  * (tall) panel clears the hotspot rather than covering it. */
15
- const PANEL_OFFSET = new Vec3(0, PANEL_HEIGHT / 2 + PANEL_GAP, 0);
14
+ const PANEL_OFFSET = new Vec3(0, PANEL_HEIGHT / 2 + 0.05, 0);
16
15
  const PAD_X = 48;
17
16
 
18
17
  /** Local +z (the quad's front face) — used to derive the panel's yaw toward the viewer. */
@@ -34,6 +33,7 @@ class VrAnnotationPanel extends Script {
34
33
  // Props assigned by the React wrapper.
35
34
  title = '';
36
35
  annotationIndex = -1;
36
+ scaleFactor = 1;
37
37
  _drawnSignature = null;
38
38
  _currentImage = 0;
39
39
  _loadToken = 0;
@@ -278,7 +278,10 @@ class VrAnnotationPanel extends Script {
278
278
  return;
279
279
  }
280
280
  this._anchor.copy(anchor.getPosition());
281
- this.entity.setPosition(this._anchor.x + PANEL_OFFSET.x, this._anchor.y + PANEL_OFFSET.y, this._anchor.z + PANEL_OFFSET.z);
281
+ // Panel size and its offset from the hotspot scale with the scene so the panel keeps its
282
+ // proportions relative to the (content-root-scaled) annotations across scenes.
283
+ this.entity.setLocalScale(this.scaleFactor, this.scaleFactor, this.scaleFactor);
284
+ this.entity.setPosition(this._anchor.x + PANEL_OFFSET.x * this.scaleFactor, this._anchor.y + PANEL_OFFSET.y * this.scaleFactor, this._anchor.z + PANEL_OFFSET.z * this.scaleFactor);
282
285
 
283
286
  // Billboard on yaw only: face the viewer horizontally but stay upright and level, so
284
287
  // tilting/rolling the headset does not tilt the panel. Derived from the head's forward
@@ -1,6 +1,7 @@
1
1
  import { Script, Vec3 } from 'playcanvas';
2
2
  export declare class WalkthroughCamera extends Script {
3
3
  static scriptName: string;
4
+ /** @attribute */
4
5
  moveSpeed: number;
5
6
  /** @attribute */
6
7
  moveFastSpeed: number;
@@ -1 +1 @@
1
- {"version":3,"file":"walkthrough-camera.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/walkthrough-camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,IAAI,EAAQ,MAAM,YAAY,CAAC;AA8BtD,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,MAAM,CAAC,UAAU,SAAuB;IAExC,SAAS,SAAO;IAEhB,iBAAiB;IACjB,aAAa,SAAO;IAEpB,iBAAiB;IACjB,aAAa,SAAQ;IAErB,iBAAiB;IACjB,eAAe,SAAO;IAEtB,iBAAiB;IACjB,aAAa,SAAQ;IAErB,iBAAiB;IACjB,QAAQ,SAAO;IAEf,iBAAiB;IACjB,QAAQ,SAAM;IAEd,iBAAiB;IACjB,WAAW,SAAS;IAEpB,iBAAiB;IACjB,YAAY,OAAqB;IAEjC,iBAAiB;IACjB,YAAY,SAAM;IAElB,iBAAiB;IACjB,OAAO,UAAS;IAEhB,iBAAiB;IACjB,SAAS,UAAQ;IAEjB,iBAAiB;IACjB,mBAAmB,SAAK;IAExB;;;;;;;;;;OAUG;IACH,WAAW,EAAE,IAAI,EAAE,CAAM;IAGzB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,IAAI,CAAK;IAGjB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,gBAAgB,CAAsB;IAG9C,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,gBAAgB;IAaxB,UAAU;IAkFV;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI;IAsBjC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM;IAsB1B;;;OAGG;IACH,IAAI,UAAU,IAAI,IAAI,CAMrB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM;IA4FjB,OAAO;CAIR"}
1
+ {"version":3,"file":"walkthrough-camera.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/walkthrough-camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,IAAI,EAAQ,MAAM,YAAY,CAAC;AA8BtD,qBAAa,iBAAkB,SAAQ,MAAM;IAC3C,MAAM,CAAC,UAAU,SAAuB;IAExC,iBAAiB;IACjB,SAAS,SAAO;IAEhB,iBAAiB;IACjB,aAAa,SAAO;IAEpB,iBAAiB;IACjB,aAAa,SAAQ;IAErB,iBAAiB;IACjB,eAAe,SAAO;IAEtB,iBAAiB;IACjB,aAAa,SAAQ;IAErB,iBAAiB;IACjB,QAAQ,SAAO;IAEf,iBAAiB;IACjB,QAAQ,SAAM;IAEd,iBAAiB;IACjB,WAAW,SAAS;IAEpB,iBAAiB;IACjB,YAAY,OAAqB;IAEjC,iBAAiB;IACjB,YAAY,SAAM;IAElB,iBAAiB;IACjB,OAAO,UAAS;IAEhB,iBAAiB;IACjB,SAAS,UAAQ;IAEjB,iBAAiB;IACjB,mBAAmB,SAAK;IAExB;;;;;;;;;;OAUG;IACH,WAAW,EAAE,IAAI,EAAE,CAAM;IAGzB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,IAAI,CAAK;IAGjB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,gBAAgB,CAAsB;IAG9C,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,gBAAgB;IAaxB,UAAU;IAkFV;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI;IAsBjC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM;IAsB1B;;;OAGG;IACH,IAAI,UAAU,IAAI,IAAI,CAMrB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM;IA4FjB,OAAO;CAIR"}
@@ -28,13 +28,15 @@ const ANGLE_EPSILON = 1e-3;
28
28
  const POSITION_EPSILON = 1e-5;
29
29
  class WalkthroughCamera extends Script {
30
30
  static scriptName = 'walkthroughCamera';
31
- moveSpeed = 4.5;
32
31
 
33
32
  /** @attribute */
34
- moveFastSpeed = 7.5;
33
+ moveSpeed = 0.3;
35
34
 
36
35
  /** @attribute */
37
- moveSlowSpeed = 2.25;
36
+ moveFastSpeed = 0.5;
37
+
38
+ /** @attribute */
39
+ moveSlowSpeed = 0.15;
38
40
 
39
41
  /** @attribute */
40
42
  lookSensitivity = 0.1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraware/web-components",
3
- "version": "4.3.1-rc.0",
3
+ "version": "4.3.1",
4
4
  "author": "Terraformation Inc.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1,52 +0,0 @@
1
- import { Script } from 'playcanvas';
2
- /**
3
- * Starts an immersive session at the scene's camera position rather than at the world origin.
4
- *
5
- * PlayCanvas overwrites the camera entity's local transform with the head pose every frame, so the
6
- * head lands wherever the headset's reference space puts it — near (0, 0) for a rig that has never
7
- * been moved. This script moves the rig on the first frame of a session so the head instead starts
8
- * at `targetX`/`targetZ` facing `focusX`/`focusZ`.
9
- *
10
- * Only XZ is touched: the user stands on the rig floor, which the boundary wall is also built
11
- * against, so lifting the rig onto the splat's ground plane would put them through it.
12
- *
13
- * Attach to the rig entity — the camera's parent, alongside TfXrNavigation, whose bounds clamp
14
- * pulls the start point back inside the scene bounds when the camera position sits outside them.
15
- */
16
- export declare class XrStartPose extends Script {
17
- static scriptName: string;
18
- /** World-space X the head should start at, i.e. the viewer's `cameraPosition`. */
19
- targetX: number;
20
- /** World-space Z the head should start at. */
21
- targetZ: number;
22
- /** World-space X the head should face from there, i.e. the viewer's `origin`. */
23
- focusX: number;
24
- /** World-space Z the head should face from there. */
25
- focusZ: number;
26
- /** Set when a session starts, cleared once the rig has been placed. */
27
- private _pending;
28
- /**
29
- * Whether the engine has written a head pose for this session yet.
30
- */
31
- private _posed;
32
- private _onXrStart;
33
- /** Fired from XrManager.update only after the head pose has been written to the camera. */
34
- private _onXrUpdate;
35
- private _onXrEnd;
36
- initialize(): void;
37
- private _isXrActive;
38
- /**
39
- * The bounds circle to place the start point in, or undefined when nothing is clamping the head.
40
- * Resolved per call rather than cached: the React wrapper assigns the bounds to the navigation
41
- * script imperatively, so they can land after this script initializes.
42
- */
43
- private _bounds;
44
- /**
45
- * Runs in update rather than on the 'start' event: the engine writes the head pose during
46
- * xr.update, which precedes the script update in the same tick, so a frame that has posed is the
47
- * first point at which there is a real head to solve the rig pose from.
48
- */
49
- update(): void;
50
- private _teardown;
51
- }
52
- //# sourceMappingURL=XrStartPose.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"XrStartPose.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/XrStartPose.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwB,MAAM,YAAY,CAAC;AAM1D;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC,kFAAkF;IAClF,OAAO,SAAK;IAEZ,8CAA8C;IAC9C,OAAO,SAAK;IAEZ,iFAAiF;IACjF,MAAM,SAAK;IAEX,qDAAqD;IACrD,MAAM,SAAK;IAEX,uEAAuE;IACvE,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,UAAU,CAEhB;IAEF,2FAA2F;IAC3F,OAAO,CAAC,WAAW,CAEjB;IAEF,OAAO,CAAC,QAAQ,CAEd;IAEF,UAAU;IAWV,OAAO,CAAC,WAAW,CACoF;IAEvG;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAQb;IAEF;;;;OAIG;IACH,MAAM;IA2BN,OAAO,CAAC,SAAS;CAKlB"}
@@ -1,128 +0,0 @@
1
- import { Script, XRTYPE_VR, XRTYPE_AR } from 'playcanvas';
2
- import { xrStartRigPose, yawFromBasis } from './xr-start-pose.js';
3
-
4
- /**
5
- * Starts an immersive session at the scene's camera position rather than at the world origin.
6
- *
7
- * PlayCanvas overwrites the camera entity's local transform with the head pose every frame, so the
8
- * head lands wherever the headset's reference space puts it — near (0, 0) for a rig that has never
9
- * been moved. This script moves the rig on the first frame of a session so the head instead starts
10
- * at `targetX`/`targetZ` facing `focusX`/`focusZ`.
11
- *
12
- * Only XZ is touched: the user stands on the rig floor, which the boundary wall is also built
13
- * against, so lifting the rig onto the splat's ground plane would put them through it.
14
- *
15
- * Attach to the rig entity — the camera's parent, alongside TfXrNavigation, whose bounds clamp
16
- * pulls the start point back inside the scene bounds when the camera position sits outside them.
17
- */
18
- class XrStartPose extends Script {
19
- static scriptName = 'xrStartPose';
20
-
21
- /** World-space X the head should start at, i.e. the viewer's `cameraPosition`. */
22
- targetX = 0;
23
-
24
- /** World-space Z the head should start at. */
25
- targetZ = 0;
26
-
27
- /** World-space X the head should face from there, i.e. the viewer's `origin`. */
28
- focusX = 0;
29
-
30
- /** World-space Z the head should face from there. */
31
- focusZ = 0;
32
-
33
- /** Set when a session starts, cleared once the rig has been placed. */
34
- _pending = false;
35
-
36
- /**
37
- * Whether the engine has written a head pose for this session yet.
38
- */
39
- _posed = false;
40
- _onXrStart = () => {
41
- this._pending = true;
42
- };
43
-
44
- /** Fired from XrManager.update only after the head pose has been written to the camera. */
45
- _onXrUpdate = () => {
46
- this._posed = true;
47
- };
48
- _onXrEnd = () => {
49
- this._posed = false;
50
- };
51
- initialize() {
52
- // Covers mounting into a session that is already running as well as the usual mount before one.
53
- this._pending = this._isXrActive();
54
- this.app.xr?.on('start', this._onXrStart);
55
- this.app.xr?.on('update', this._onXrUpdate);
56
- this.app.xr?.on('end', this._onXrEnd);
57
- // Script removal fires a 'destroy' event rather than calling a destroy() method, so the
58
- // teardown has to be registered as a listener or these handlers outlive the script.
59
- this.once('destroy', () => this._teardown());
60
- }
61
- _isXrActive = () => this.app.xr?.active === true && (this.app.xr?.type === XRTYPE_VR || this.app.xr?.type === XRTYPE_AR);
62
-
63
- /**
64
- * The bounds circle to place the start point in, or undefined when nothing is clamping the head.
65
- * Resolved per call rather than cached: the React wrapper assigns the bounds to the navigation
66
- * script imperatively, so they can land after this script initializes.
67
- */
68
- _bounds = () => {
69
- // @ts-expect-error - scripts are added dynamically to the entity
70
- const navigation = this.entity.script?.tfXrNavigation;
71
- if (!navigation || navigation.boundsRadius <= 0) {
72
- return undefined;
73
- }
74
- return {
75
- x: navigation.boundsCenter.x,
76
- z: navigation.boundsCenter.z,
77
- radius: navigation.boundsRadius
78
- };
79
- };
80
-
81
- /**
82
- * Runs in update rather than on the 'start' event: the engine writes the head pose during
83
- * xr.update, which precedes the script update in the same tick, so a frame that has posed is the
84
- * first point at which there is a real head to solve the rig pose from.
85
- */
86
- update() {
87
- if (!this._pending || !this._posed || !this._isXrActive()) {
88
- return;
89
- }
90
- const camera = this.entity.findComponent('camera')?.entity;
91
- if (!camera) {
92
- return;
93
- }
94
- const head = camera.getPosition();
95
- const rig = this.entity.getPosition();
96
- const rigY = rig.y;
97
- const pose = xrStartRigPose({
98
- head: {
99
- x: head.x,
100
- z: head.z
101
- },
102
- headYaw: yawFromBasis(camera.forward, camera.right),
103
- rig: {
104
- x: rig.x,
105
- z: rig.z
106
- },
107
- target: {
108
- x: this.targetX,
109
- z: this.targetZ
110
- },
111
- focus: {
112
- x: this.focusX,
113
- z: this.focusZ
114
- },
115
- bounds: this._bounds()
116
- });
117
- this.entity.rotate(0, pose.yawDelta, 0);
118
- this.entity.setPosition(pose.x, rigY, pose.z);
119
- this._pending = false;
120
- }
121
- _teardown() {
122
- this.app.xr?.off('start', this._onXrStart);
123
- this.app.xr?.off('update', this._onXrUpdate);
124
- this.app.xr?.off('end', this._onXrEnd);
125
- }
126
- }
127
-
128
- export { XrStartPose };
@@ -1,57 +0,0 @@
1
- export type Point2 = {
2
- x: number;
3
- z: number;
4
- };
5
- export type XrStartBounds = Point2 & {
6
- radius: number;
7
- };
8
- export type XrStartPoseInput = {
9
- /** Where the head is now. */
10
- head: Point2;
11
- /** Yaw of the head's forward direction now. */
12
- headYaw: number;
13
- /** Where the rig is now. */
14
- rig: Point2;
15
- /** Where the head should end up. */
16
- target: Point2;
17
- /** What the head should face from there. */
18
- focus: Point2;
19
- /**
20
- * Circle to keep the target inside. Omit (or pass a non-positive radius) to place the head on the
21
- * target as given.
22
- */
23
- bounds?: XrStartBounds;
24
- };
25
- export type XrStartPose = {
26
- /** Yaw to turn the rig by, about its own origin. */
27
- yawDelta: number;
28
- /** Where the rig ends up once turned. */
29
- x: number;
30
- z: number;
31
- };
32
- /**
33
- * Yaw of a heading on the XZ plane, matching PlayCanvas' right-handed CCW Y rotation:
34
- * forward = (-sin(yaw), 0, -cos(yaw)).
35
- */
36
- export declare const yawFromDirection: (dx: number, dz: number) => number | null;
37
- /**
38
- * Yaw an entity is facing, from its world basis vectors. Falls back to the right vector when the
39
- * forward vector is vertical — a head tipped fully up or down still has a heading, but its forward
40
- * vector has no horizontal component to read it from.
41
- */
42
- export declare const yawFromBasis: (forward: {
43
- x: number;
44
- z: number;
45
- }, right: {
46
- x: number;
47
- z: number;
48
- }) => number;
49
- /**
50
- * Solves for the rig pose that lands the head on `target` facing `focus`.
51
- *
52
- * Turning the rig swings the head around the rig's origin, so the turn is applied to the head's
53
- * offset within the rig first and the rig is then placed so that swung offset lands on the target.
54
- * A focus point on top of the target gives no heading, so the rig is only moved, not turned.
55
- */
56
- export declare const xrStartRigPose: ({ head, headYaw, rig, target, focus, bounds }: XrStartPoseInput) => XrStartPose;
57
- //# sourceMappingURL=xr-start-pose.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"xr-start-pose.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-start-pose.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,MAAM,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAeF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,IAAI,MAAM,EAAE,IAAI,MAAM,KAAG,MAAM,GAAG,IAC8C,CAAC;AAElH;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,KAAG,MACZ,CAAC;AAWvF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,+CAA+C,gBAAgB,KAAG,WAOhG,CAAC"}
@@ -1,80 +0,0 @@
1
- import { clampToCircle } from './xr-scene-bounds.js';
2
-
3
- /**
4
- * Where to put the XR rig so the head starts at a chosen spot facing a chosen point.
5
- *
6
- * PlayCanvas writes the head pose into the camera entity's *local* transform every frame, so the
7
- * only thing a scene can move is the rig the camera hangs off. The head sits at an arbitrary offset
8
- * within the rig — the reference space is pinned to wherever the user physically stood when the
9
- * session began — so the rig pose has to be solved backwards from the head pose rather than set to
10
- * the target outright.
11
- *
12
- * All angles are degrees and all coordinates world-space XZ; Y is left to the caller, since the
13
- * user stands on the rig floor rather than on the scene's ground plane.
14
- */
15
- const RAD_TO_DEG = 180 / Math.PI;
16
- const DEG_TO_RAD = Math.PI / 180;
17
-
18
- /** Below this length a direction vector carries no usable heading. */
19
- const DIRECTION_EPSILON = 1e-6;
20
-
21
- /** Wraps to (-180, 180] so the rig always turns the short way round. */
22
- const normalizeAngle = degrees => {
23
- const wrapped = ((degrees + 180) % 360 + 360) % 360 - 180;
24
- return wrapped === -180 ? 180 : wrapped;
25
- };
26
-
27
- /**
28
- * Yaw of a heading on the XZ plane, matching PlayCanvas' right-handed CCW Y rotation:
29
- * forward = (-sin(yaw), 0, -cos(yaw)).
30
- */
31
- const yawFromDirection = (dx, dz) => Math.abs(dx) < DIRECTION_EPSILON && Math.abs(dz) < DIRECTION_EPSILON ? null : Math.atan2(-dx, -dz) * RAD_TO_DEG;
32
-
33
- /**
34
- * Yaw an entity is facing, from its world basis vectors. Falls back to the right vector when the
35
- * forward vector is vertical — a head tipped fully up or down still has a heading, but its forward
36
- * vector has no horizontal component to read it from.
37
- */
38
- const yawFromBasis = (forward, right) => yawFromDirection(forward.x, forward.z) ?? Math.atan2(-right.z, right.x) * RAD_TO_DEG;
39
-
40
- /** Rotates a point about the origin by `degrees` of PlayCanvas yaw. */
41
- const rotateXz = (point, degrees) => {
42
- const radians = degrees * DEG_TO_RAD;
43
- const cos = Math.cos(radians);
44
- const sin = Math.sin(radians);
45
- return {
46
- x: point.x * cos + point.z * sin,
47
- z: -point.x * sin + point.z * cos
48
- };
49
- };
50
-
51
- /**
52
- * Solves for the rig pose that lands the head on `target` facing `focus`.
53
- *
54
- * Turning the rig swings the head around the rig's origin, so the turn is applied to the head's
55
- * offset within the rig first and the rig is then placed so that swung offset lands on the target.
56
- * A focus point on top of the target gives no heading, so the rig is only moved, not turned.
57
- */
58
- const xrStartRigPose = ({
59
- head,
60
- headYaw,
61
- rig,
62
- target,
63
- focus,
64
- bounds
65
- }) => {
66
- const landing = bounds ? clampToCircle(target.x, target.z, bounds.x, bounds.z, bounds.radius) : target;
67
- const facingYaw = yawFromDirection(focus.x - landing.x, focus.z - landing.z);
68
- const yawDelta = facingYaw === null ? 0 : normalizeAngle(facingYaw - headYaw);
69
- const offset = rotateXz({
70
- x: head.x - rig.x,
71
- z: head.z - rig.z
72
- }, yawDelta);
73
- return {
74
- yawDelta,
75
- x: landing.x - offset.x,
76
- z: landing.z - offset.z
77
- };
78
- };
79
-
80
- export { xrStartRigPose, yawFromBasis, yawFromDirection };