@terraware/web-components 4.2.22-rc.0 → 4.2.22
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/components/VirtualWalkthrough/Annotation.d.ts.map +1 -1
- package/components/VirtualWalkthrough/Annotation.js +10 -0
- package/components/VirtualWalkthrough/BoundaryWall.d.ts +11 -0
- package/components/VirtualWalkthrough/BoundaryWall.d.ts.map +1 -0
- package/components/VirtualWalkthrough/BoundaryWall.js +35 -0
- package/components/VirtualWalkthrough/SplatModel.d.ts +1 -8
- package/components/VirtualWalkthrough/SplatModel.d.ts.map +1 -1
- package/components/VirtualWalkthrough/SplatModel.js +2 -6
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts +42 -6
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts.map +1 -1
- package/components/VirtualWalkthrough/TfXrNavigation.js +109 -9
- package/components/VirtualWalkthrough/TfXrNavigation.test.js +110 -0
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts +1 -3
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts.map +1 -1
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +51 -6
- package/components/VirtualWalkthrough/VrAnnotationPanel.d.ts +9 -0
- package/components/VirtualWalkthrough/VrAnnotationPanel.d.ts.map +1 -0
- package/components/VirtualWalkthrough/VrAnnotationPanel.js +22 -0
- package/components/VirtualWalkthrough/XrAnnotationInteraction.d.ts +6 -0
- package/components/VirtualWalkthrough/XrAnnotationInteraction.d.ts.map +1 -0
- package/components/VirtualWalkthrough/XrAnnotationInteraction.js +15 -0
- package/components/VirtualWalkthrough/XrGazeDwell.d.ts +6 -0
- package/components/VirtualWalkthrough/XrGazeDwell.d.ts.map +1 -0
- package/components/VirtualWalkthrough/XrGazeDwell.js +15 -0
- package/components/VirtualWalkthrough/XrPointerRay.d.ts +7 -0
- package/components/VirtualWalkthrough/XrPointerRay.d.ts.map +1 -0
- package/components/VirtualWalkthrough/XrPointerRay.js +15 -0
- package/components/VirtualWalkthrough/boundary-wall.d.ts +86 -0
- package/components/VirtualWalkthrough/boundary-wall.d.ts.map +1 -0
- package/components/VirtualWalkthrough/boundary-wall.js +331 -0
- package/components/VirtualWalkthrough/boundary-wall.test.js +203 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.d.ts +6 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.d.ts.map +1 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +22 -0
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.test.js +26 -0
- package/components/VirtualWalkthrough/vr-annotation-panel.d.ts +40 -0
- package/components/VirtualWalkthrough/vr-annotation-panel.d.ts.map +1 -0
- package/components/VirtualWalkthrough/vr-annotation-panel.js +312 -0
- package/components/VirtualWalkthrough/xr-annotation-candidates.d.ts +18 -0
- package/components/VirtualWalkthrough/xr-annotation-candidates.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-annotation-candidates.js +38 -0
- package/components/VirtualWalkthrough/xr-annotation-interaction.d.ts +13 -0
- package/components/VirtualWalkthrough/xr-annotation-interaction.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-annotation-interaction.js +59 -0
- package/components/VirtualWalkthrough/xr-annotation-targeting.d.ts +10 -0
- package/components/VirtualWalkthrough/xr-annotation-targeting.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-annotation-targeting.js +36 -0
- package/components/VirtualWalkthrough/xr-annotation-targeting.test.js +66 -0
- package/components/VirtualWalkthrough/xr-button-arming.d.ts +14 -0
- package/components/VirtualWalkthrough/xr-button-arming.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-button-arming.js +19 -0
- package/components/VirtualWalkthrough/xr-button-arming.test.js +35 -0
- package/components/VirtualWalkthrough/xr-button-hold.d.ts +20 -0
- package/components/VirtualWalkthrough/xr-button-hold.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-button-hold.js +43 -0
- package/components/VirtualWalkthrough/xr-button-hold.test.js +57 -0
- package/components/VirtualWalkthrough/xr-exit-button.d.ts +21 -4
- package/components/VirtualWalkthrough/xr-exit-button.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-exit-button.js +98 -37
- package/components/VirtualWalkthrough/xr-face-buttons.d.ts +15 -0
- package/components/VirtualWalkthrough/xr-face-buttons.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-face-buttons.js +31 -0
- package/components/VirtualWalkthrough/xr-face-buttons.test.js +82 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell-state.d.ts +17 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell-state.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell-state.js +39 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell-state.test.js +61 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell.d.ts +30 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-gaze-dwell.js +173 -0
- package/components/VirtualWalkthrough/xr-interactive-ui.d.ts +13 -0
- package/components/VirtualWalkthrough/xr-interactive-ui.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-interactive-ui.js +25 -0
- package/components/VirtualWalkthrough/xr-interactive-ui.test.js +165 -0
- package/components/VirtualWalkthrough/xr-pointer-ray.d.ts +10 -0
- package/components/VirtualWalkthrough/xr-pointer-ray.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-pointer-ray.js +24 -0
- package/components/VirtualWalkthrough/xr-progress-pie.d.ts +10 -0
- package/components/VirtualWalkthrough/xr-progress-pie.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-progress-pie.js +30 -0
- package/components/VirtualWalkthrough/xr-progress-pie.test.js +79 -0
- package/components/VirtualWalkthrough/xr-ray-quad.d.ts +13 -0
- package/components/VirtualWalkthrough/xr-ray-quad.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-ray-quad.js +29 -0
- package/components/VirtualWalkthrough/xr-ray-quad.test.js +37 -0
- package/components/VirtualWalkthrough/xr-scene-bounds.d.ts +17 -0
- package/components/VirtualWalkthrough/xr-scene-bounds.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-scene-bounds.js +41 -0
- package/components/VirtualWalkthrough/xr-scene-bounds.test.js +70 -0
- package/components/VirtualWalkthrough/xr-teleport-gesture.d.ts +30 -0
- package/components/VirtualWalkthrough/xr-teleport-gesture.d.ts.map +1 -0
- package/components/VirtualWalkthrough/xr-teleport-gesture.js +45 -0
- package/components/VirtualWalkthrough/xr-teleport-gesture.test.js +90 -0
- package/hooks/useXr.d.ts +2 -0
- package/hooks/useXr.d.ts.map +1 -1
- package/hooks/useXr.js +10 -6
- package/package.json +1 -1
- package/virtualWalkthrough.d.ts +1 -3
- package/virtualWalkthrough.d.ts.map +1 -1
- package/virtualWalkthrough.js +1 -2
- package/components/VirtualWalkthrough/splatFormat.d.ts +0 -17
- package/components/VirtualWalkthrough/splatFormat.d.ts.map +0 -1
- package/components/VirtualWalkthrough/splatFormat.js +0 -50
- package/components/VirtualWalkthrough/splatFormat.test.js +0 -51
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Annotation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/Annotation.tsx"],"names":[],"mappings":"AAUA,OAAO,yBAAyB,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAS5D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAChE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjF,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnG;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,UAAU,GAAI,OAAO,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"Annotation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/Annotation.tsx"],"names":[],"mappings":"AAUA,OAAO,yBAAyB,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAS5D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IAChE,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjF,sBAAsB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnG;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,UAAU,GAAI,OAAO,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,4CA2L7D,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -101,6 +101,15 @@ const Annotation = props => {
|
|
|
101
101
|
}
|
|
102
102
|
}, [setCamera]);
|
|
103
103
|
|
|
104
|
+
// VR open path: shows the annotation without moving the camera (roomscale head pose
|
|
105
|
+
// comes from the XR views, so a camera move would be disorienting or a no-op).
|
|
106
|
+
const handleVrOpen = useCallback((screenX, screenY) => {
|
|
107
|
+
if (isEditRef.current) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
onViewRef.current?.(annotationForViewRef.current, screenX, screenY);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
104
113
|
// Stable callback so its identity doesn't churn the underlying script prop each frame.
|
|
105
114
|
const handleScreenPositionUpdate = useCallback((screenX, screenY, size) => {
|
|
106
115
|
onScreenPositionUpdateRef.current?.(index, screenX, screenY, size);
|
|
@@ -190,6 +199,7 @@ const Annotation = props => {
|
|
|
190
199
|
text: bodyText,
|
|
191
200
|
enabled: visible,
|
|
192
201
|
onClickCallback: handleClick,
|
|
202
|
+
onVrOpenCallback: handleVrOpen,
|
|
193
203
|
onScreenPositionUpdateCallback: isViewed ? handleScreenPositionUpdate : undefined
|
|
194
204
|
})
|
|
195
205
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Vec3 } from 'playcanvas';
|
|
2
|
+
export type BoundaryWallProps = {
|
|
3
|
+
/** World-space, i.e. already multiplied by the scene scaleFactor. */
|
|
4
|
+
center: Vec3;
|
|
5
|
+
radius: number;
|
|
6
|
+
groundPlane: Vec3[];
|
|
7
|
+
baseY: number;
|
|
8
|
+
};
|
|
9
|
+
declare const BoundaryWall: ({ center, radius, groundPlane, baseY }: BoundaryWallProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export default BoundaryWall;
|
|
11
|
+
//# sourceMappingURL=BoundaryWall.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import { Entity } from '@playcanvas/react';
|
|
3
|
+
import { Script } from '@playcanvas/react/components';
|
|
4
|
+
import { useApp } from '@playcanvas/react/hooks';
|
|
5
|
+
import { BoundaryWallScript } from './boundary-wall';
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
const BoundaryWall = ({
|
|
8
|
+
center,
|
|
9
|
+
radius,
|
|
10
|
+
groundPlane,
|
|
11
|
+
baseY
|
|
12
|
+
}) => {
|
|
13
|
+
const app = useApp();
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
// Set imperatively rather than via reactive Script props. @playcanvas/react wraps Script
|
|
16
|
+
// in memo() whose shallowEquals returns early on the first prop with a .equals() method
|
|
17
|
+
// (Vec3), so any prop after a Vec3 is never compared and would not propagate.
|
|
18
|
+
// @ts-expect-error - scripts are added dynamically to the entity
|
|
19
|
+
const script = app.root.findByName('boundary-wall')?.script?.boundaryWall;
|
|
20
|
+
if (script) {
|
|
21
|
+
script.center = center;
|
|
22
|
+
script.radius = radius;
|
|
23
|
+
script.groundPlane = groundPlane;
|
|
24
|
+
script.baseY = baseY;
|
|
25
|
+
script.rebuild();
|
|
26
|
+
}
|
|
27
|
+
}, [app, center, radius, groundPlane, baseY]);
|
|
28
|
+
return /*#__PURE__*/_jsx(Entity, {
|
|
29
|
+
name: "boundary-wall",
|
|
30
|
+
children: /*#__PURE__*/_jsx(Script, {
|
|
31
|
+
script: BoundaryWallScript
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
export default BoundaryWall;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { SplatFormat } from './splatFormat';
|
|
3
2
|
export interface SplatModelProps {
|
|
4
3
|
splatSrc: string;
|
|
5
|
-
/**
|
|
6
|
-
* Splat format of `splatSrc`. Inferred from the URL when omitted, which covers bundled `.sog`,
|
|
7
|
-
* unbundled sog (`meta.json`), streamed LOD sog (`lod-meta.json`) and `.ply` sources. Set this
|
|
8
|
-
* explicitly when the URL hides the filename.
|
|
9
|
-
*/
|
|
10
|
-
splatFormat?: SplatFormat;
|
|
11
4
|
rotation?: [number, number, number];
|
|
12
5
|
cropAabbMin?: [number, number, number];
|
|
13
6
|
cropAabbMax?: [number, number, number];
|
|
@@ -17,6 +10,6 @@ export interface SplatModelProps {
|
|
|
17
10
|
revealRain?: boolean;
|
|
18
11
|
onError?: (error: Error) => void;
|
|
19
12
|
}
|
|
20
|
-
declare const _default: React.MemoExoticComponent<({ splatSrc,
|
|
13
|
+
declare const _default: React.MemoExoticComponent<({ splatSrc, rotation, cropAabbMin, cropAabbMax, cropEdgeScaleFactor, cropFade, cropFadeDistance, revealRain, onError, }: SplatModelProps) => import("react/jsx-runtime").JSX.Element | null>;
|
|
21
14
|
export default _default;
|
|
22
15
|
//# 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;
|
|
1
|
+
{"version":3,"file":"SplatModel.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/SplatModel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA0B,MAAM,OAAO,CAAC;AAW/C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,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;4KAYE,eAAe;AAgClB,wBAAgC"}
|
|
@@ -6,11 +6,9 @@ import BlockingSpinner from './BlockingSpinner';
|
|
|
6
6
|
import SplatCrop from './SplatCrop';
|
|
7
7
|
import SplatFadeCrop from './SplatFadeCrop';
|
|
8
8
|
import SplatRevealRain from './SplatRevealRain';
|
|
9
|
-
import { detectSplatFormat, splatLoaderFilename } from './splatFormat';
|
|
10
9
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
10
|
const SplatModel = ({
|
|
12
11
|
splatSrc,
|
|
13
|
-
splatFormat,
|
|
14
12
|
rotation,
|
|
15
13
|
cropAabbMin,
|
|
16
14
|
cropAabbMax,
|
|
@@ -20,16 +18,14 @@ const SplatModel = ({
|
|
|
20
18
|
revealRain = false,
|
|
21
19
|
onError
|
|
22
20
|
}) => {
|
|
23
|
-
//
|
|
24
|
-
// format we are actually loading. See splatFormat.ts.
|
|
25
|
-
const filename = splatLoaderFilename(splatFormat ?? detectSplatFormat(splatSrc));
|
|
21
|
+
// A filename is required for the file props to assist with the asset loading. Otherwise it assumes that the splatSrc is a ply file.
|
|
26
22
|
const {
|
|
27
23
|
asset,
|
|
28
24
|
loading,
|
|
29
25
|
error
|
|
30
26
|
} = useSplat(splatSrc, {
|
|
31
27
|
file: {
|
|
32
|
-
filename
|
|
28
|
+
filename: 'model.sog'
|
|
33
29
|
}
|
|
34
30
|
});
|
|
35
31
|
useEffect(() => {
|
|
@@ -1,15 +1,51 @@
|
|
|
1
|
+
import { Vec3, XrInputSource } from 'playcanvas';
|
|
1
2
|
import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr/xr-navigation.mjs';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* XrNavigation that never teleports on a select meant for something else: it honours the
|
|
5
|
+
* enableTeleport flag (which the base ignores in tryTeleport) and skips sources whose ray is over
|
|
6
|
+
* interactive UI.
|
|
5
7
|
*/
|
|
6
8
|
export declare class TfXrNavigation extends PcXrNavigation {
|
|
7
9
|
static scriptName: string;
|
|
10
|
+
/** Assigned by the React wrapper. True when this source's ray is over interactive UI. */
|
|
11
|
+
isTeleportBlocked?: (inputSource: XrInputSource) => boolean;
|
|
8
12
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* World-space bounds circle. Assigned by the React wrapper rather than passed as a Script prop:
|
|
14
|
+
* boundsCenter is a Vec3, and @playcanvas/react's memo() comparator stops at the first prop with
|
|
15
|
+
* an .equals() method, so any prop after it would never propagate.
|
|
16
|
+
* A boundsRadius of 0 disables clamping entirely.
|
|
12
17
|
*/
|
|
13
|
-
|
|
18
|
+
boundsCenter: Vec3;
|
|
19
|
+
boundsRadius: number;
|
|
20
|
+
/** How far the rig was pulled back in the last postUpdate to keep the head inside the bounds. */
|
|
21
|
+
clampDistance: number;
|
|
22
|
+
private _gestures;
|
|
23
|
+
private _isBlocked;
|
|
24
|
+
tryTeleport(inputSource: XrInputSource): void;
|
|
25
|
+
update(dt: number): void;
|
|
26
|
+
/**
|
|
27
|
+
* Holds the head inside the bounds circle. Runs in postUpdate, after PlayCanvas has written this
|
|
28
|
+
* frame's head pose into the camera's local transform (xr.update runs before app.update) and after
|
|
29
|
+
* the base script's locomotion, so one clamp covers every way the head can move: thumbstick
|
|
30
|
+
* translation of the rig, room-scale walking within the rig, and snap turns pivoting the rig.
|
|
31
|
+
*
|
|
32
|
+
* This script must stay a no-op outside XR regardless of how it's mounted.
|
|
33
|
+
*/
|
|
34
|
+
postUpdate(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Clamps the teleport landing point to the bounds circle. Overriding here rather than in
|
|
37
|
+
* tryTeleport covers both paths that produce a landing point — the per-frame aim in
|
|
38
|
+
* _handleTeleportation and tryTeleport's own recompute when no hit is cached — and because
|
|
39
|
+
* _handleTeleportation positions the landing ring from this same record immediately afterwards,
|
|
40
|
+
* the ring previews the clamped destination without any extra work.
|
|
41
|
+
*
|
|
42
|
+
* Only valid hits are clamped: when the base finds no hit it leaves rec.point holding a stale
|
|
43
|
+
* value from an earlier frame, which must not be projected onto the circle and shown as a target.
|
|
44
|
+
* Clamping only ever shortens the throw, so a hit that passed the base's distance check still does.
|
|
45
|
+
*/
|
|
46
|
+
_computeArcHit(origin: Vec3, direction: Vec3, rec: {
|
|
47
|
+
point: Vec3;
|
|
48
|
+
valid: boolean;
|
|
49
|
+
}): void;
|
|
14
50
|
}
|
|
15
51
|
//# sourceMappingURL=TfXrNavigation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TfXrNavigation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfXrNavigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,6CAA6C,CAAC;
|
|
1
|
+
{"version":3,"file":"TfXrNavigation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfXrNavigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,IAAI,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,6CAA6C,CAAC;AAkB7F;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,cAAc;IAChD,MAAM,CAAC,UAAU,SAAoB;IAErC,yFAAyF;IACzF,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,KAAK,OAAO,CAAC;IAE5D;;;;;OAKG;IACH,YAAY,OAAc;IAC1B,YAAY,SAAK;IAEjB,iGAAiG;IACjG,aAAa,SAAK;IAElB,OAAO,CAAC,SAAS,CAA8B;IAE/C,OAAO,CAAC,UAAU;IAIlB,WAAW,CAAC,WAAW,EAAE,aAAa;IAYtC,MAAM,CAAC,EAAE,EAAE,MAAM;IAmCjB;;;;;;;OAOG;IACH,UAAU;IAoBV;;;;;;;;;;OAUG;IACH,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE;CAkBnF"}
|
|
@@ -1,24 +1,124 @@
|
|
|
1
|
+
import { Vec3 } from 'playcanvas';
|
|
1
2
|
import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr/xr-navigation.mjs';
|
|
3
|
+
import { clampToCircle } from './xr-scene-bounds';
|
|
4
|
+
import { TeleportGestureLatch } from './xr-teleport-gesture';
|
|
5
|
+
|
|
6
|
+
/** The base script's per-source aim state, which it exposes only as underscore-private fields. */
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
9
|
+
* XrNavigation that never teleports on a select meant for something else: it honours the
|
|
10
|
+
* enableTeleport flag (which the base ignores in tryTeleport) and skips sources whose ray is over
|
|
11
|
+
* interactive UI.
|
|
6
12
|
*/
|
|
7
13
|
export class TfXrNavigation extends PcXrNavigation {
|
|
8
14
|
static scriptName = 'tfXrNavigation';
|
|
9
15
|
|
|
16
|
+
/** Assigned by the React wrapper. True when this source's ray is over interactive UI. */
|
|
17
|
+
|
|
10
18
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
19
|
+
* World-space bounds circle. Assigned by the React wrapper rather than passed as a Script prop:
|
|
20
|
+
* boundsCenter is a Vec3, and @playcanvas/react's memo() comparator stops at the first prop with
|
|
21
|
+
* an .equals() method, so any prop after it would never propagate.
|
|
22
|
+
* A boundsRadius of 0 disables clamping entirely.
|
|
14
23
|
*/
|
|
24
|
+
boundsCenter = new Vec3();
|
|
25
|
+
boundsRadius = 0;
|
|
26
|
+
|
|
27
|
+
/** How far the rig was pulled back in the last postUpdate to keep the head inside the bounds. */
|
|
28
|
+
clampDistance = 0;
|
|
29
|
+
_gestures = new TeleportGestureLatch();
|
|
30
|
+
_isBlocked(inputSource) {
|
|
31
|
+
return !this.enableTeleport || this.isTeleportBlocked?.(inputSource) === true;
|
|
32
|
+
}
|
|
15
33
|
tryTeleport(inputSource) {
|
|
16
|
-
//
|
|
17
|
-
|
|
34
|
+
// The latch decides on the frames the press actually aimed through, because super.tryTeleport
|
|
35
|
+
// commits the arc hit cached back then rather than re-tracing the release-time ray. The live
|
|
36
|
+
// check stays too: the same release also fires 'select', so a ray that lands on UI at release
|
|
37
|
+
// is operating that UI and must not move the rig as well.
|
|
38
|
+
if (this._gestures.consumeBlocked(inputSource) || this._isBlocked(inputSource)) {
|
|
18
39
|
return;
|
|
19
40
|
}
|
|
20
|
-
|
|
21
|
-
// Call the parent implementation
|
|
22
41
|
super.tryTeleport(inputSource);
|
|
23
42
|
}
|
|
43
|
+
update(dt) {
|
|
44
|
+
super.update(dt);
|
|
45
|
+
const internals = this;
|
|
46
|
+
for (const inputSource of internals._inputSources) {
|
|
47
|
+
// Sampled once per source per frame: isTeleportBlocked ray-tests every hotspot.
|
|
48
|
+
const uiBlocked = this.isTeleportBlocked?.(inputSource) === true;
|
|
49
|
+
|
|
50
|
+
// Tracked unconditionally, and before the arc-hiding work below. super.update has just cached
|
|
51
|
+
// this frame's arc hit, which is what a release would commit, so the latch has to see the same
|
|
52
|
+
// frame. A source that isn't pressed carries no gesture, so any stale entry from a press that
|
|
53
|
+
// ended while teleport was disabled (skipping tryTeleport entirely) is dropped here rather
|
|
54
|
+
// than leaking into the source's next press.
|
|
55
|
+
this._gestures.track(inputSource, {
|
|
56
|
+
pressed: internals._activePointers.get(inputSource) === true,
|
|
57
|
+
teleportDisabled: !this.enableTeleport,
|
|
58
|
+
uiBlocked
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The base hides the arc only from inside its own teleport handling, which it skips entirely
|
|
62
|
+
// once teleport is off - so an arc raised before the block (gaze dwell can open a panel while
|
|
63
|
+
// the trigger is held) would otherwise stay frozen in the world. These writes are idempotent,
|
|
64
|
+
// and the base re-enables the visuals itself on the first frame a source is unblocked.
|
|
65
|
+
const visual = internals._arcVisuals.get(inputSource);
|
|
66
|
+
if (!visual || !visual.entity.enabled && !visual.ringEntity.enabled) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!this.enableTeleport || uiBlocked) {
|
|
70
|
+
visual.entity.enabled = false;
|
|
71
|
+
visual.ringEntity.enabled = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Holds the head inside the bounds circle. Runs in postUpdate, after PlayCanvas has written this
|
|
78
|
+
* frame's head pose into the camera's local transform (xr.update runs before app.update) and after
|
|
79
|
+
* the base script's locomotion, so one clamp covers every way the head can move: thumbstick
|
|
80
|
+
* translation of the rig, room-scale walking within the rig, and snap turns pivoting the rig.
|
|
81
|
+
*
|
|
82
|
+
* This script must stay a no-op outside XR regardless of how it's mounted.
|
|
83
|
+
*/
|
|
84
|
+
postUpdate() {
|
|
85
|
+
this.clampDistance = 0;
|
|
86
|
+
const camera = this._cameraEntity;
|
|
87
|
+
if (!this.app.xr?.active || !camera || this.boundsRadius <= 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const head = camera.getPosition();
|
|
91
|
+
const clamped = clampToCircle(head.x, head.z, this.boundsCenter.x, this.boundsCenter.z, this.boundsRadius);
|
|
92
|
+
if (clamped.distance === 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// World-space translate, not translateLocal: a snap turn has yawed the rig, so a local-space
|
|
97
|
+
// correction would be rotated away from the direction the head actually overshot.
|
|
98
|
+
this.entity.translate(clamped.x - head.x, 0, clamped.z - head.z);
|
|
99
|
+
this.clampDistance = clamped.distance;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Clamps the teleport landing point to the bounds circle. Overriding here rather than in
|
|
104
|
+
* tryTeleport covers both paths that produce a landing point — the per-frame aim in
|
|
105
|
+
* _handleTeleportation and tryTeleport's own recompute when no hit is cached — and because
|
|
106
|
+
* _handleTeleportation positions the landing ring from this same record immediately afterwards,
|
|
107
|
+
* the ring previews the clamped destination without any extra work.
|
|
108
|
+
*
|
|
109
|
+
* Only valid hits are clamped: when the base finds no hit it leaves rec.point holding a stale
|
|
110
|
+
* value from an earlier frame, which must not be projected onto the circle and shown as a target.
|
|
111
|
+
* Clamping only ever shortens the throw, so a hit that passed the base's distance check still does.
|
|
112
|
+
*/
|
|
113
|
+
_computeArcHit(origin, direction, rec) {
|
|
114
|
+
super._computeArcHit(origin, direction, rec);
|
|
115
|
+
if (!rec.valid || this.boundsRadius <= 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Y is left alone: it is the navigation plane height, and bounds are XZ-only.
|
|
120
|
+
const clamped = clampToCircle(rec.point.x, rec.point.z, this.boundsCenter.x, this.boundsCenter.z, this.boundsRadius);
|
|
121
|
+
rec.point.x = clamped.x;
|
|
122
|
+
rec.point.z = clamped.z;
|
|
123
|
+
}
|
|
24
124
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Vec3 } from 'playcanvas';
|
|
2
|
+
|
|
3
|
+
// The real base script is an .mjs file that Jest's default CRA transform config never transforms
|
|
4
|
+
// (node_modules is excluded), so importing it directly fails to parse. TfXrNavigation.postUpdate
|
|
5
|
+
// never calls into the base class, so a stub with no behaviour is enough to satisfy `extends`.
|
|
6
|
+
jest.mock('playcanvas/scripts/esm/xr/xr-navigation.mjs', () => ({
|
|
7
|
+
XrNavigation: class {}
|
|
8
|
+
}));
|
|
9
|
+
import { TfXrNavigation } from './TfXrNavigation';
|
|
10
|
+
const BOUNDS_CENTER = new Vec3(2, 0, 3);
|
|
11
|
+
const BOUNDS_RADIUS = 5;
|
|
12
|
+
const makeNavigation = options => {
|
|
13
|
+
const translate = jest.fn();
|
|
14
|
+
const navigation = Object.create(TfXrNavigation.prototype);
|
|
15
|
+
navigation.boundsCenter = BOUNDS_CENTER.clone();
|
|
16
|
+
navigation.boundsRadius = options.boundsRadius;
|
|
17
|
+
navigation.clampDistance = 0;
|
|
18
|
+
navigation.app = {
|
|
19
|
+
xr: {
|
|
20
|
+
active: options.xrActive
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
navigation.entity = {
|
|
24
|
+
translate
|
|
25
|
+
};
|
|
26
|
+
Object.assign(navigation, {
|
|
27
|
+
_cameraEntity: {
|
|
28
|
+
getPosition: () => options.head
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
navigation,
|
|
33
|
+
translate
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
describe('TfXrNavigation.postUpdate', () => {
|
|
37
|
+
it('does not clamp when there is no active XR session, even with the head far outside the circle', () => {
|
|
38
|
+
const {
|
|
39
|
+
navigation,
|
|
40
|
+
translate
|
|
41
|
+
} = makeNavigation({
|
|
42
|
+
xrActive: false,
|
|
43
|
+
boundsRadius: BOUNDS_RADIUS,
|
|
44
|
+
head: new Vec3(1000, 1.5, 1000)
|
|
45
|
+
});
|
|
46
|
+
navigation.postUpdate();
|
|
47
|
+
expect(translate).not.toHaveBeenCalled();
|
|
48
|
+
expect(navigation.clampDistance).toBe(0);
|
|
49
|
+
});
|
|
50
|
+
it('does not clamp when boundsRadius is 0', () => {
|
|
51
|
+
const {
|
|
52
|
+
navigation,
|
|
53
|
+
translate
|
|
54
|
+
} = makeNavigation({
|
|
55
|
+
xrActive: true,
|
|
56
|
+
boundsRadius: 0,
|
|
57
|
+
head: new Vec3(1000, 1.5, 1000)
|
|
58
|
+
});
|
|
59
|
+
navigation.postUpdate();
|
|
60
|
+
expect(translate).not.toHaveBeenCalled();
|
|
61
|
+
expect(navigation.clampDistance).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
it('does not clamp when the head is inside the circle', () => {
|
|
64
|
+
const {
|
|
65
|
+
navigation,
|
|
66
|
+
translate
|
|
67
|
+
} = makeNavigation({
|
|
68
|
+
xrActive: true,
|
|
69
|
+
boundsRadius: BOUNDS_RADIUS,
|
|
70
|
+
head: new Vec3(3, 1.5, 4)
|
|
71
|
+
});
|
|
72
|
+
navigation.postUpdate();
|
|
73
|
+
expect(translate).not.toHaveBeenCalled();
|
|
74
|
+
expect(navigation.clampDistance).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
it('pulls the rig back and records the overshoot when the head is outside the circle in an active session', () => {
|
|
77
|
+
const {
|
|
78
|
+
navigation,
|
|
79
|
+
translate
|
|
80
|
+
} = makeNavigation({
|
|
81
|
+
xrActive: true,
|
|
82
|
+
boundsRadius: BOUNDS_RADIUS,
|
|
83
|
+
head: new Vec3(10, 1.5, 3)
|
|
84
|
+
});
|
|
85
|
+
navigation.postUpdate();
|
|
86
|
+
expect(translate).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(translate).toHaveBeenCalledWith(-3, 0, 0);
|
|
88
|
+
expect(navigation.clampDistance).toBe(3);
|
|
89
|
+
});
|
|
90
|
+
it('does not latch clampDistance across frames', () => {
|
|
91
|
+
const {
|
|
92
|
+
navigation,
|
|
93
|
+
translate
|
|
94
|
+
} = makeNavigation({
|
|
95
|
+
xrActive: true,
|
|
96
|
+
boundsRadius: BOUNDS_RADIUS,
|
|
97
|
+
head: new Vec3(10, 1.5, 3)
|
|
98
|
+
});
|
|
99
|
+
navigation.postUpdate();
|
|
100
|
+
expect(navigation.clampDistance).toBe(3);
|
|
101
|
+
Object.assign(navigation, {
|
|
102
|
+
_cameraEntity: {
|
|
103
|
+
getPosition: () => new Vec3(3, 1.5, 4)
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
navigation.postUpdate();
|
|
107
|
+
expect(translate).toHaveBeenCalledTimes(1);
|
|
108
|
+
expect(navigation.clampDistance).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { AnnotationProps } from './Annotation';
|
|
2
2
|
import { SplatControlsStrings } from './SplatControls';
|
|
3
|
-
import { SplatFormat } from './splatFormat';
|
|
4
3
|
export interface VirtualWalkthroughViewerProps {
|
|
5
4
|
splatSrc: string;
|
|
6
|
-
splatFormat?: SplatFormat;
|
|
7
5
|
origin?: [number, number, number];
|
|
8
6
|
cameraPosition?: [number, number, number];
|
|
9
7
|
sceneBounds?: {
|
|
@@ -26,6 +24,6 @@ export interface VirtualWalkthroughViewerProps {
|
|
|
26
24
|
onToggleFullScreen?: () => void;
|
|
27
25
|
maxImagesPerAnnotation?: number;
|
|
28
26
|
}
|
|
29
|
-
declare const VirtualWalkthroughViewer: ({ splatSrc,
|
|
27
|
+
declare const VirtualWalkthroughViewer: ({ splatSrc, origin, cameraPosition, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight, scaleFactor, annotations, onSaveAnnotations, strings, editable, showFreeFly, isFullScreen, onToggleFullScreen, maxImagesPerAnnotation, }: VirtualWalkthroughViewerProps) => import("react/jsx-runtime").JSX.Element;
|
|
30
28
|
export default VirtualWalkthroughViewer;
|
|
31
29
|
//# 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;
|
|
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;AAqBtE,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,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,qQAkB/B,6BAA6B,4CA+W/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
|
|
@@ -12,19 +12,30 @@ import Annotation from './Annotation';
|
|
|
12
12
|
import AnnotationPanel from './AnnotationPanel';
|
|
13
13
|
import { AutoRotator } from './AutoRotator';
|
|
14
14
|
import BoundaryRing from './BoundaryRing';
|
|
15
|
+
import BoundaryWall from './BoundaryWall';
|
|
15
16
|
import GradientSky from './GradientSky';
|
|
16
17
|
import SplatControls from './SplatControls';
|
|
17
18
|
import SplatModel from './SplatModel';
|
|
18
19
|
import { TfAnnotationManager } from './TfAnnotationManager';
|
|
19
20
|
import { TfXrNavigation } from './TfXrNavigation';
|
|
21
|
+
import VrAnnotationPanel from './VrAnnotationPanel';
|
|
22
|
+
import XrAnnotationInteraction from './XrAnnotationInteraction';
|
|
20
23
|
import XrExitButton from './XrExitButton';
|
|
24
|
+
import XrGazeDwell from './XrGazeDwell';
|
|
25
|
+
import XrPointerRay from './XrPointerRay';
|
|
21
26
|
import { WalkthroughCamera } from './walkthrough-camera';
|
|
27
|
+
import { rayHitsInteractiveUi } from './xr-interactive-ui';
|
|
22
28
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
23
29
|
const DEFAULT_FOCUS_POINT = [0, 0.1, 0];
|
|
24
30
|
const DEFAULT_POSITION = [1, 0.1, 0];
|
|
31
|
+
|
|
32
|
+
// How far outside the clamp radius the boundary wall stands. Without it the wall would be coplanar
|
|
33
|
+
// with the surface the head is clamped to, so a pinned user would have it in their face and through
|
|
34
|
+
// their near clip plane. Deliberately an absolute world-space distance rather than one scaled by
|
|
35
|
+
// scaleFactor: it is about the size of the user's body, not about the size of the scene.
|
|
36
|
+
const WALL_INSET = 0.25;
|
|
25
37
|
const VirtualWalkthroughViewer = ({
|
|
26
38
|
splatSrc,
|
|
27
|
-
splatFormat,
|
|
28
39
|
origin = DEFAULT_FOCUS_POINT,
|
|
29
40
|
cameraPosition = DEFAULT_POSITION,
|
|
30
41
|
sceneBounds,
|
|
@@ -61,7 +72,9 @@ const VirtualWalkthroughViewer = ({
|
|
|
61
72
|
const [viewingAnnotationIndex, setViewingAnnotationIndex] = useState(-1);
|
|
62
73
|
const [viewedScreenPos, setViewedScreenPos] = useState(null);
|
|
63
74
|
const {
|
|
64
|
-
isCurrentlyInXr
|
|
75
|
+
isCurrentlyInXr,
|
|
76
|
+
isCurrentlyInVr,
|
|
77
|
+
isCurrentlyInAr
|
|
65
78
|
} = useXr();
|
|
66
79
|
const sceneBoundsRadius = useMemo(() => {
|
|
67
80
|
if (sceneBounds?.m !== undefined) {
|
|
@@ -90,6 +103,16 @@ const VirtualWalkthroughViewer = ({
|
|
|
90
103
|
walkthroughCam.groundPlane = cameraGroundPlane;
|
|
91
104
|
}
|
|
92
105
|
}, [cameraGroundPlane, app]);
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
// Set imperatively for the same reason as BoundaryRing: boundsCenter is a Vec3, and the
|
|
108
|
+
// @playcanvas/react memo() comparator stops at the first prop with an .equals() method.
|
|
109
|
+
// @ts-expect-error - scripts are added dynamically to the entity
|
|
110
|
+
const navigation = app.root.findByName('camera-root')?.script?.tfXrNavigation;
|
|
111
|
+
if (navigation) {
|
|
112
|
+
navigation.boundsCenter = cameraBoundsCenter;
|
|
113
|
+
navigation.boundsRadius = sceneBoundsRadius * scaleFactor;
|
|
114
|
+
}
|
|
115
|
+
}, [app, cameraBoundsCenter, sceneBoundsRadius, scaleFactor]);
|
|
93
116
|
const handleToggleFreeFly = useCallback(() => {
|
|
94
117
|
const newFreeFly = !isFreeFly;
|
|
95
118
|
// @ts-expect-error - scripts are added dynamically to the camera entity
|
|
@@ -164,6 +187,9 @@ const VirtualWalkthroughViewer = ({
|
|
|
164
187
|
setViewedScreenPos(null);
|
|
165
188
|
}, []);
|
|
166
189
|
const handleAnnotationScreenPositionUpdate = useCallback((_index, screenX, screenY, size) => {
|
|
190
|
+
if (isCurrentlyInXr) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
167
193
|
// The scene is frozen while the panel is open, so guard against redundant
|
|
168
194
|
// updates from the per-frame callback to avoid needless re-renders.
|
|
169
195
|
setViewedScreenPos(prev => prev && prev.x === screenX && prev.y === screenY && prev.size === size ? prev : {
|
|
@@ -171,12 +197,18 @@ const VirtualWalkthroughViewer = ({
|
|
|
171
197
|
y: screenY,
|
|
172
198
|
size
|
|
173
199
|
});
|
|
174
|
-
}, []);
|
|
200
|
+
}, [isCurrentlyInXr]);
|
|
175
201
|
const handleCloseAnnotation = useCallback(() => {
|
|
176
202
|
setViewingAnnotation(null);
|
|
177
203
|
setViewingAnnotationIndex(-1);
|
|
178
204
|
setViewedScreenPos(null);
|
|
179
205
|
}, []);
|
|
206
|
+
const isTeleportBlocked = useCallback(inputSource => rayHitsInteractiveUi(app, inputSource.getOrigin(), inputSource.getDirection()), [app]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!isCurrentlyInXr) {
|
|
209
|
+
handleCloseAnnotation();
|
|
210
|
+
}
|
|
211
|
+
}, [isCurrentlyInXr, handleCloseAnnotation]);
|
|
180
212
|
const handleAnnotationUpdate = useCallback(updates => {
|
|
181
213
|
if (selectedAnnotationIndex === -1) {
|
|
182
214
|
return;
|
|
@@ -196,10 +228,9 @@ const VirtualWalkthroughViewer = ({
|
|
|
196
228
|
const canSave = useMemo(() => localAnnotations.every(annotation => annotation.title && annotation.title.trim() !== ''), [localAnnotations]);
|
|
197
229
|
const splatModel = useMemo(() => /*#__PURE__*/_jsx(SplatModel, {
|
|
198
230
|
splatSrc: splatSrc,
|
|
199
|
-
splatFormat: splatFormat,
|
|
200
231
|
rotation: [-180, 0, 0],
|
|
201
232
|
revealRain: isHighPerformance
|
|
202
|
-
}, 'splat'), [isHighPerformance, splatSrc
|
|
233
|
+
}, 'splat'), [isHighPerformance, splatSrc]);
|
|
203
234
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
204
235
|
children: [/*#__PURE__*/_jsx(GradientSky, {
|
|
205
236
|
topColor: skyColor || '#FFFFFF',
|
|
@@ -225,10 +256,19 @@ const VirtualWalkthroughViewer = ({
|
|
|
225
256
|
}), /*#__PURE__*/_jsx(XrExitButton, {}), /*#__PURE__*/_jsx(Script, {
|
|
226
257
|
script: XrControllers,
|
|
227
258
|
enabled: !isEdit
|
|
259
|
+
}), /*#__PURE__*/_jsx(XrAnnotationInteraction, {
|
|
260
|
+
onDismiss: handleCloseAnnotation
|
|
261
|
+
}), /*#__PURE__*/_jsx(XrPointerRay, {}), isCurrentlyInVr && viewingAnnotation && /*#__PURE__*/_jsx(VrAnnotationPanel, {
|
|
262
|
+
annotation: viewingAnnotation,
|
|
263
|
+
annotationIndex: viewingAnnotationIndex,
|
|
264
|
+
scaleFactor: scaleFactor
|
|
265
|
+
}, viewingAnnotationIndex), isCurrentlyInXr && /*#__PURE__*/_jsx(XrGazeDwell, {
|
|
266
|
+
activeIndex: viewingAnnotationIndex
|
|
228
267
|
}), /*#__PURE__*/_jsx(Script, {
|
|
229
268
|
script: TfXrNavigation,
|
|
230
269
|
enabled: !isEdit,
|
|
231
|
-
enableTeleport:
|
|
270
|
+
enableTeleport: !isCurrentlyInAr && !viewingAnnotation,
|
|
271
|
+
isTeleportBlocked: isTeleportBlocked
|
|
232
272
|
}), !isCurrentlyInXr && /*#__PURE__*/_jsx(Script, {
|
|
233
273
|
script: AutoRotator,
|
|
234
274
|
enabled: !isEdit && autoRotate && !viewingAnnotation,
|
|
@@ -267,6 +307,11 @@ const VirtualWalkthroughViewer = ({
|
|
|
267
307
|
onScreenPositionUpdate: handleAnnotationScreenPositionUpdate
|
|
268
308
|
}, `annotation-${index}`))]
|
|
269
309
|
})]
|
|
310
|
+
}), isCurrentlyInXr && sceneBoundsRadius > 0 && /*#__PURE__*/_jsx(BoundaryWall, {
|
|
311
|
+
center: cameraBoundsCenter,
|
|
312
|
+
radius: sceneBoundsRadius * scaleFactor + WALL_INSET,
|
|
313
|
+
groundPlane: cameraGroundPlane,
|
|
314
|
+
baseY: 0
|
|
270
315
|
}), /*#__PURE__*/_jsx(SplatControls, {
|
|
271
316
|
defaultCameraFocus: origin,
|
|
272
317
|
defaultCameraPosition: cameraPosition,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnnotationProps } from './Annotation';
|
|
2
|
+
interface VrAnnotationPanelProps {
|
|
3
|
+
annotation: AnnotationProps;
|
|
4
|
+
annotationIndex: number;
|
|
5
|
+
scaleFactor: number;
|
|
6
|
+
}
|
|
7
|
+
declare const VrAnnotationPanel: ({ annotation, annotationIndex, scaleFactor }: VrAnnotationPanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default VrAnnotationPanel;
|
|
9
|
+
//# sourceMappingURL=VrAnnotationPanel.d.ts.map
|
|
@@ -0,0 +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;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,QAAA,MAAM,iBAAiB,GAAI,8CAA8C,sBAAsB,4CAY9F,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Entity } from '@playcanvas/react';
|
|
3
|
+
import { Script } from '@playcanvas/react/components';
|
|
4
|
+
import { VrAnnotationPanel as VrAnnotationPanelScript } from './vr-annotation-panel';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
const VrAnnotationPanel = ({
|
|
7
|
+
annotation,
|
|
8
|
+
annotationIndex,
|
|
9
|
+
scaleFactor
|
|
10
|
+
}) => /*#__PURE__*/_jsx(Entity, {
|
|
11
|
+
name: "vr-annotation-panel",
|
|
12
|
+
children: /*#__PURE__*/_jsx(Script, {
|
|
13
|
+
script: VrAnnotationPanelScript,
|
|
14
|
+
title: annotation.title,
|
|
15
|
+
label: annotation.label,
|
|
16
|
+
bodyText: annotation.bodyText,
|
|
17
|
+
imageUrls: annotation.imageUrls,
|
|
18
|
+
annotationIndex: annotationIndex,
|
|
19
|
+
scaleFactor: scaleFactor
|
|
20
|
+
})
|
|
21
|
+
});
|
|
22
|
+
export default VrAnnotationPanel;
|