@terraware/web-components 5.0.5 → 5.0.7-rc.2
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 +6 -5
- package/components/VirtualWalkthrough/AutoRotator.d.ts +10 -0
- package/components/VirtualWalkthrough/AutoRotator.d.ts.map +1 -1
- package/components/VirtualWalkthrough/AutoRotator.js +21 -5
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts +14 -0
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts.map +1 -1
- package/components/VirtualWalkthrough/TfXrNavigation.js +46 -0
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts +19 -1
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts.map +1 -1
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +38 -20
- package/components/VirtualWalkthrough/XrExitButton.d.ts +5 -1
- package/components/VirtualWalkthrough/XrExitButton.d.ts.map +1 -1
- package/components/VirtualWalkthrough/XrExitButton.js +5 -2
- package/components/VirtualWalkthrough/boundary-wall.d.ts.map +1 -1
- package/components/VirtualWalkthrough/boundary-wall.js +1 -1
- package/components/VirtualWalkthrough/camera-adoption.d.ts +39 -0
- package/components/VirtualWalkthrough/camera-adoption.d.ts.map +1 -0
- package/components/VirtualWalkthrough/camera-adoption.js +66 -0
- package/components/VirtualWalkthrough/useAdoptedCamera.d.ts +14 -0
- package/components/VirtualWalkthrough/useAdoptedCamera.d.ts.map +1 -0
- package/components/VirtualWalkthrough/useAdoptedCamera.js +45 -0
- package/components/VirtualWalkthrough/useXrRenderTuning.d.ts +2 -1
- package/components/VirtualWalkthrough/useXrRenderTuning.d.ts.map +1 -1
- package/components/VirtualWalkthrough/useXrRenderTuning.js +10 -1
- package/components/VirtualWalkthrough/walkthrough-camera.d.ts +8 -1
- package/components/VirtualWalkthrough/walkthrough-camera.d.ts.map +1 -1
- package/components/VirtualWalkthrough/walkthrough-camera.js +38 -10
- package/components/VirtualWalkthrough/xr-annotation-interaction.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-annotation-interaction.js +2 -2
- package/components/VirtualWalkthrough/xr-exit-button.d.ts +7 -0
- package/components/VirtualWalkthrough/xr-exit-button.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-exit-button.js +18 -3
- package/components/VirtualWalkthrough/xr-gaze-dwell.d.ts.map +1 -1
- package/components/VirtualWalkthrough/xr-gaze-dwell.js +2 -2
- package/hooks/cameraEntityContext.d.ts +12 -0
- package/hooks/cameraEntityContext.d.ts.map +1 -0
- package/hooks/cameraEntityContext.js +14 -0
- package/hooks/useCameraPosition.d.ts +7 -1
- package/hooks/useCameraPosition.d.ts.map +1 -1
- package/hooks/useCameraPosition.js +32 -10
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Annotation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/Annotation.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Annotation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/Annotation.tsx"],"names":[],"mappings":"AAWA,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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
|
1
|
+
import { useContext, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import { Entity } from '@playcanvas/react';
|
|
3
3
|
import { Script } from '@playcanvas/react/components';
|
|
4
4
|
import { useApp } from '@playcanvas/react/hooks';
|
|
5
5
|
import { Gizmo, TranslateGizmo } from 'playcanvas';
|
|
6
6
|
import { Annotation as Annotation$1 } from 'playcanvas/scripts/esm/annotations.mjs';
|
|
7
7
|
import { CameraControls } from 'playcanvas/scripts/esm/camera-controls.mjs';
|
|
8
|
+
import { CameraEntityContext } from '../../hooks/cameraEntityContext.js';
|
|
8
9
|
import { useCameraPosition } from '../../hooks/useCameraPosition.js';
|
|
9
10
|
import './annotation-styles.css';
|
|
10
11
|
import { jsx } from 'react/jsx-runtime';
|
|
@@ -52,6 +53,7 @@ const Annotation = props => {
|
|
|
52
53
|
index
|
|
53
54
|
} = props;
|
|
54
55
|
const app = useApp();
|
|
56
|
+
const walkthroughCamera = useContext(CameraEntityContext);
|
|
55
57
|
const {
|
|
56
58
|
setCamera
|
|
57
59
|
} = useCameraPosition();
|
|
@@ -146,9 +148,8 @@ const Annotation = props => {
|
|
|
146
148
|
}
|
|
147
149
|
const setupGizmo = () => {
|
|
148
150
|
const annotationEntity = app.root.findByName(entityName);
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const cameraControls = cameraEntity?.script?.get(CameraControls.scriptName);
|
|
151
|
+
const cameraComponent = walkthroughCamera?.camera;
|
|
152
|
+
const cameraControls = walkthroughCamera?.script?.get(CameraControls.scriptName);
|
|
152
153
|
if (!annotationEntity || !cameraComponent) {
|
|
153
154
|
return false;
|
|
154
155
|
}
|
|
@@ -188,7 +189,7 @@ const Annotation = props => {
|
|
|
188
189
|
clearInterval(intervalId);
|
|
189
190
|
cleanupGizmo();
|
|
190
191
|
};
|
|
191
|
-
}, [app, isEdit, isSelected, visible, onPositionChange, entityName]);
|
|
192
|
+
}, [app, isEdit, isSelected, visible, onPositionChange, entityName, walkthroughCamera]);
|
|
192
193
|
return /*#__PURE__*/jsx(Entity, {
|
|
193
194
|
name: entityName,
|
|
194
195
|
position: position,
|
|
@@ -88,6 +88,16 @@ export declare class AutoRotator extends Script {
|
|
|
88
88
|
* Initialize the script.
|
|
89
89
|
*/
|
|
90
90
|
initialize(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Finds the camera and the script posing it. WalkthroughCamera is mounted on this entity, the rig,
|
|
93
|
+
* so that it poses a camera the walkthrough adopted from a host scene as readily as one of its own;
|
|
94
|
+
* CameraControls sits on the camera entity itself.
|
|
95
|
+
*
|
|
96
|
+
* Called again while nothing has been found, because an adopted camera is reparented into the rig
|
|
97
|
+
* by an effect that runs after the scripts are mounted.
|
|
98
|
+
* @private
|
|
99
|
+
*/
|
|
100
|
+
private resolveCamera;
|
|
91
101
|
/**
|
|
92
102
|
* Update loop that handles auto-rotation logic.
|
|
93
103
|
* Runs after CameraControls updates.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutoRotator.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/AutoRotator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAQ,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC;;;;;OAKG;IACH,KAAK,SAAK;IAEV;;;;;OAKG;IACH,UAAU,SAAK;IAEf;;;;;OAKG;IACH,YAAY,SAAK;IAEjB;;;;;OAKG;IACH,eAAe,SAAK;IAEpB;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAK;IAElB;;;OAGG;IACH,OAAO,CAAC,kBAAkB,CAAS;IAEnC;;;OAGG;IACH,OAAO,CAAC,mBAAmB,CAAS;IAEpC;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAuB;IAEpC;;;OAGG;IACH,OAAO,CAAC,GAAG,CAAuB;IAElC;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAa;IAEjC;;;OAGG;IACH,OAAO,CAAC,cAAc,CAAa;IAEnC;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAa;IAEtC;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,SAAS,CAAC,CAAa;IAE/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,UAAU;
|
|
1
|
+
{"version":3,"file":"AutoRotator.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/AutoRotator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAQ,MAAM,YAAY,CAAC;AAEhD;;;GAGG;AACH,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC;;;;;OAKG;IACH,KAAK,SAAK;IAEV;;;;;OAKG;IACH,UAAU,SAAK;IAEf;;;;;OAKG;IACH,YAAY,SAAK;IAEjB;;;;;OAKG;IACH,eAAe,SAAK;IAEpB;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAK;IAElB;;;OAGG;IACH,OAAO,CAAC,kBAAkB,CAAS;IAEnC;;;OAGG;IACH,OAAO,CAAC,mBAAmB,CAAS;IAEpC;;;OAGG;IACH,OAAO,CAAC,KAAK,CAAuB;IAEpC;;;OAGG;IACH,OAAO,CAAC,GAAG,CAAuB;IAElC;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAa;IAEjC;;;OAGG;IACH,OAAO,CAAC,cAAc,CAAa;IAEnC;;;OAGG;IACH,OAAO,CAAC,iBAAiB,CAAa;IAEtC;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,SAAS,CAAC,CAAa;IAE/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;IACH,UAAU;IAYV;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAQrB;;;OAGG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM;IA0ErB;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAWlB;;OAEG;IACH,OAAO;CAQR"}
|
|
@@ -112,11 +112,24 @@ class AutoRotator extends Script {
|
|
|
112
112
|
this.app.graphicsDevice.canvas.addEventListener('pointerdown', this.onPointerDown);
|
|
113
113
|
}
|
|
114
114
|
window.addEventListener('keydown', this.onKeyDown);
|
|
115
|
-
this.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
this.resolveCamera();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Finds the camera and the script posing it. WalkthroughCamera is mounted on this entity, the rig,
|
|
120
|
+
* so that it poses a camera the walkthrough adopted from a host scene as readily as one of its own;
|
|
121
|
+
* CameraControls sits on the camera entity itself.
|
|
122
|
+
*
|
|
123
|
+
* Called again while nothing has been found, because an adopted camera is reparented into the rig
|
|
124
|
+
* by an effect that runs after the scripts are mounted.
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
resolveCamera() {
|
|
128
|
+
this.cameraEntity = this.entity.findComponent('camera')?.entity ?? null;
|
|
129
|
+
this.cameraControls = this.cameraEntity?.script?.cameraControls ?? null;
|
|
130
|
+
// @ts-expect-error - scripts are added dynamically to the entity
|
|
131
|
+
const rigWalkthroughCamera = this.entity.script?.walkthroughCamera;
|
|
132
|
+
this.walkthroughCamera = rigWalkthroughCamera ?? this.cameraEntity?.script?.walkthroughCamera ?? null;
|
|
120
133
|
}
|
|
121
134
|
|
|
122
135
|
/**
|
|
@@ -124,6 +137,9 @@ class AutoRotator extends Script {
|
|
|
124
137
|
* Runs after CameraControls updates.
|
|
125
138
|
*/
|
|
126
139
|
postUpdate(dt) {
|
|
140
|
+
if (!this.cameraControls && !this.walkthroughCamera) {
|
|
141
|
+
this.resolveCamera();
|
|
142
|
+
}
|
|
127
143
|
const hasControls = this.cameraControls && this.cameraControls._pose;
|
|
128
144
|
const hasWalkthrough = !!this.walkthroughCamera;
|
|
129
145
|
if (!hasControls && !hasWalkthrough) {
|
|
@@ -21,6 +21,20 @@ export declare class TfXrNavigation extends PcXrNavigation {
|
|
|
21
21
|
clampDistance: number;
|
|
22
22
|
private _gestures;
|
|
23
23
|
private _isBlocked;
|
|
24
|
+
/**
|
|
25
|
+
* Tracks controllers the base script never heard about.
|
|
26
|
+
*
|
|
27
|
+
* Movement, turning and teleport all iterate `_inputSources`, which the base fills from the XR
|
|
28
|
+
* input `add` event it subscribes to in initialize. A walkthrough that adopts the host's camera is
|
|
29
|
+
* mounted into a session that is already running, so the controllers present at session start
|
|
30
|
+
* fired `add` before this script existed and every control is inert for the whole session.
|
|
31
|
+
*
|
|
32
|
+
* The sources are picked up by hand rather than by re-firing `add` on the input manager, which
|
|
33
|
+
* would also reach the host's own listeners and have them handle controllers they already know
|
|
34
|
+
* about. The handlers mirror the base's and are stored in its map, so its teardown detaches them.
|
|
35
|
+
*/
|
|
36
|
+
private _syncInputSources;
|
|
37
|
+
private _resolveCamera;
|
|
24
38
|
tryTeleport(inputSource: XrInputSource): void;
|
|
25
39
|
update(dt: number): void;
|
|
26
40
|
/**
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAoB7F;;;;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;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,iBAAiB;IA2BzB,OAAO,CAAC,cAAc;IAOtB,WAAW,CAAC,WAAW,EAAE,aAAa;IAYtC,MAAM,CAAC,EAAE,EAAE,MAAM;IAqCjB;;;;;;;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"}
|
|
@@ -30,6 +30,50 @@ class TfXrNavigation extends XrNavigation {
|
|
|
30
30
|
_isBlocked(inputSource) {
|
|
31
31
|
return !this.enableTeleport || this.isTeleportBlocked?.(inputSource) === true;
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tracks controllers the base script never heard about.
|
|
36
|
+
*
|
|
37
|
+
* Movement, turning and teleport all iterate `_inputSources`, which the base fills from the XR
|
|
38
|
+
* input `add` event it subscribes to in initialize. A walkthrough that adopts the host's camera is
|
|
39
|
+
* mounted into a session that is already running, so the controllers present at session start
|
|
40
|
+
* fired `add` before this script existed and every control is inert for the whole session.
|
|
41
|
+
*
|
|
42
|
+
* The sources are picked up by hand rather than by re-firing `add` on the input manager, which
|
|
43
|
+
* would also reach the host's own listeners and have them handle controllers they already know
|
|
44
|
+
* about. The handlers mirror the base's and are stored in its map, so its teardown detaches them.
|
|
45
|
+
*/
|
|
46
|
+
_syncInputSources() {
|
|
47
|
+
const internals = this;
|
|
48
|
+
for (const inputSource of this.app.xr?.input?.inputSources ?? []) {
|
|
49
|
+
if (internals._inputSources.has(inputSource)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const handleSelectStart = () => {
|
|
53
|
+
internals._activePointers.set(inputSource, true);
|
|
54
|
+
internals._arcHits.delete(inputSource);
|
|
55
|
+
};
|
|
56
|
+
const handleSelectEnd = () => {
|
|
57
|
+
internals._activePointers.set(inputSource, false);
|
|
58
|
+
if (this.enableTeleport) {
|
|
59
|
+
this.tryTeleport(inputSource);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
inputSource.on('selectstart', handleSelectStart);
|
|
63
|
+
inputSource.on('selectend', handleSelectEnd);
|
|
64
|
+
internals._inputHandlers.set(inputSource, {
|
|
65
|
+
handleSelectStart,
|
|
66
|
+
handleSelectEnd
|
|
67
|
+
});
|
|
68
|
+
internals._inputSources.add(inputSource);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
_resolveCamera() {
|
|
72
|
+
const internals = this;
|
|
73
|
+
if (!internals._cameraEntity) {
|
|
74
|
+
internals._cameraEntity = this.entity.findComponent('camera')?.entity ?? null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
33
77
|
tryTeleport(inputSource) {
|
|
34
78
|
// The latch decides on the frames the press actually aimed through, because super.tryTeleport
|
|
35
79
|
// commits the arc hit cached back then rather than re-tracing the release-time ray. The live
|
|
@@ -41,6 +85,8 @@ class TfXrNavigation extends XrNavigation {
|
|
|
41
85
|
super.tryTeleport(inputSource);
|
|
42
86
|
}
|
|
43
87
|
update(dt) {
|
|
88
|
+
this._resolveCamera();
|
|
89
|
+
this._syncInputSources();
|
|
44
90
|
super.update(dt);
|
|
45
91
|
const internals = this;
|
|
46
92
|
for (const inputSource of internals._inputSources) {
|
|
@@ -13,6 +13,18 @@ export interface VirtualWalkthroughViewerProps {
|
|
|
13
13
|
/**
|
|
14
14
|
* Camera component mounted on the camera entity. Defaults to a 60 degree camera clearing to the
|
|
15
15
|
* horizon color.
|
|
16
|
+
*
|
|
17
|
+
* Pass null to mount no camera of the walkthrough's own, which is what a caller sharing a scene
|
|
18
|
+
* it does not own wants: an XR session is welded to the camera it was started on, so a
|
|
19
|
+
* walkthrough that brought its own could only take over by ending the session and dropping the
|
|
20
|
+
* user out of the headset. The walkthrough adopts the scene's existing camera into its rig
|
|
21
|
+
* instead, for as long as it is mounted.
|
|
22
|
+
*
|
|
23
|
+
* A scene shared this way is expected to hold exactly one camera. The adoption takes the first
|
|
24
|
+
* camera in graph traversal order, which is only certain to be the one the headset renders
|
|
25
|
+
* through while it is the only one there: a scene that also carries a spectator or overlay camera
|
|
26
|
+
* can hand the rig the wrong entity, and locomotion then goes inert with nothing reported. Prefer
|
|
27
|
+
* the session's own camera, `app.xr.camera`, over the traversal if that ever needs to hold.
|
|
16
28
|
*/
|
|
17
29
|
camera?: ReactNode;
|
|
18
30
|
/** World-space point the camera looks at. */
|
|
@@ -54,6 +66,12 @@ export interface VirtualWalkthroughViewerProps {
|
|
|
54
66
|
* but couldn't be started, in which case the error says why.
|
|
55
67
|
*/
|
|
56
68
|
onXrExit?: (error?: Error) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Runs instead of ending the XR session when the user activates the in-headset exit button.
|
|
71
|
+
* Without it the button ends the session, which in turn reports `onXrExit`; with it the session
|
|
72
|
+
* stays running and neither happens, leaving the handler to decide what closing means.
|
|
73
|
+
*/
|
|
74
|
+
onXrClose?: () => void;
|
|
57
75
|
annotations: AnnotationProps[];
|
|
58
76
|
onSaveAnnotations: (annotations: AnnotationProps[]) => void | Promise<void>;
|
|
59
77
|
editable?: boolean;
|
|
@@ -62,6 +80,6 @@ export interface VirtualWalkthroughViewerProps {
|
|
|
62
80
|
onToggleFullScreen?: () => void;
|
|
63
81
|
maxImagesPerAnnotation?: number;
|
|
64
82
|
}
|
|
65
|
-
declare const VirtualWalkthroughViewer: ({ splatSrc, splatFormat, camera, origin, cameraPosition, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight, scaleFactor, splatModelProps, autoStartVr, onXrExit, annotations, onSaveAnnotations, editable, showFreeFly, isFullScreen, onToggleFullScreen, maxImagesPerAnnotation, }: VirtualWalkthroughViewerProps) => import("react/jsx-runtime").JSX.Element;
|
|
83
|
+
declare const VirtualWalkthroughViewer: ({ splatSrc, splatFormat, camera, origin, cameraPosition, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight, scaleFactor, splatModelProps, autoStartVr, onXrExit, onXrClose, annotations, onSaveAnnotations, editable, showFreeFly, isFullScreen, onToggleFullScreen, maxImagesPerAnnotation, }: VirtualWalkthroughViewerProps) => import("react/jsx-runtime").JSX.Element;
|
|
66
84
|
export default VirtualWalkthroughViewer;
|
|
67
85
|
//# sourceMappingURL=VirtualWalkthroughViewer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualWalkthroughViewer.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VirtualWalkthroughViewer.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAAqD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"VirtualWalkthroughViewer.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/VirtualWalkthroughViewer.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,SAAS,EAAqD,MAAM,OAAO,CAAC;AAa5F,OAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAO3D,OAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAS3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAoB5C;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,8CAA8C;IAC9C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,2FAA2F;IAC3F,WAAW,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,uEAAuE;IACvE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC3C;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IACnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,iBAAiB,EAAE,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,QAAA,MAAM,wBAAwB,GAAI,oUAuB/B,6BAA6B,4CAkb/B,CAAC;AAEF,eAAe,wBAAwB,CAAC"}
|
|
@@ -5,6 +5,7 @@ import { Script, Camera } from '@playcanvas/react/components';
|
|
|
5
5
|
import { useApp } from '@playcanvas/react/hooks';
|
|
6
6
|
import { Vec3, Color } from 'playcanvas';
|
|
7
7
|
import { XrControllers } from 'playcanvas/scripts/esm/xr/xr-controllers.mjs';
|
|
8
|
+
import { CameraEntityContext } from '../../hooks/cameraEntityContext.js';
|
|
8
9
|
import { useCameraPosition } from '../../hooks/useCameraPosition.js';
|
|
9
10
|
import { useDevicePerformance } from '../../hooks/useDevicePerformance.js';
|
|
10
11
|
import useXr from '../../hooks/useXr.js';
|
|
@@ -24,10 +25,11 @@ import XrExitButton from './XrExitButton.js';
|
|
|
24
25
|
import XrGazeDwell from './XrGazeDwell.js';
|
|
25
26
|
import XrPointerRay from './XrPointerRay.js';
|
|
26
27
|
import { XrStartPose } from './XrStartPose.js';
|
|
28
|
+
import useAdoptedCamera from './useAdoptedCamera.js';
|
|
27
29
|
import useXrRenderTuning from './useXrRenderTuning.js';
|
|
28
30
|
import { WalkthroughCamera } from './walkthrough-camera.js';
|
|
29
31
|
import { rayHitsInteractiveUi } from './xr-interactive-ui.js';
|
|
30
|
-
import { jsx, jsxs
|
|
32
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
31
33
|
|
|
32
34
|
const DEFAULT_FOCUS_POINT = [0, 0.1, 0];
|
|
33
35
|
const DEFAULT_POSITION = [1, 0.1, 0];
|
|
@@ -67,6 +69,7 @@ const VirtualWalkthroughViewer = ({
|
|
|
67
69
|
splatModelProps,
|
|
68
70
|
autoStartVr = false,
|
|
69
71
|
onXrExit,
|
|
72
|
+
onXrClose,
|
|
70
73
|
annotations,
|
|
71
74
|
onSaveAnnotations,
|
|
72
75
|
editable = false,
|
|
@@ -76,9 +79,19 @@ const VirtualWalkthroughViewer = ({
|
|
|
76
79
|
maxImagesPerAnnotation
|
|
77
80
|
}) => {
|
|
78
81
|
const theme = useTheme();
|
|
82
|
+
// Held as state so the effects and hooks that address these entities re-run once they
|
|
83
|
+
// exist. Addressed directly: a viewer mounted into a host's scene shares the graph
|
|
84
|
+
// with the host's entities, so the camera can be found by component type.
|
|
85
|
+
const [cameraRoot, setCameraRoot] = useState(null);
|
|
86
|
+
const [cameraEntity, setCameraEntity] = useState(null);
|
|
87
|
+
const adoptedCamera = useAdoptedCamera(cameraRoot, cameraEntity);
|
|
88
|
+
// The entity carrying the live camera component: the viewer's own, or the host's while adopted.
|
|
89
|
+
// Everything that poses the camera or projects through it addresses this rather than the entity
|
|
90
|
+
// the viewer mounts a camera of its own on, which holds nothing while a camera is adopted.
|
|
91
|
+
const activeCamera = adoptedCamera ?? cameraEntity;
|
|
79
92
|
const {
|
|
80
93
|
setCamera
|
|
81
|
-
} = useCameraPosition();
|
|
94
|
+
} = useCameraPosition(activeCamera);
|
|
82
95
|
const {
|
|
83
96
|
isHighPerformance
|
|
84
97
|
} = useDevicePerformance();
|
|
@@ -162,27 +175,27 @@ const VirtualWalkthroughViewer = ({
|
|
|
162
175
|
if (!groundPlane.length || isCurrentlyInXr) {
|
|
163
176
|
return;
|
|
164
177
|
}
|
|
165
|
-
// @ts-expect-error - scripts are added dynamically to the
|
|
166
|
-
const walkthroughCam =
|
|
178
|
+
// @ts-expect-error - scripts are added dynamically to the entity
|
|
179
|
+
const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
|
|
167
180
|
if (walkthroughCam) {
|
|
168
181
|
// Should be changed to a react prop if shallowEquals in playcanvas/react is fixed (see https://github.com/playcanvas/react/pull/298)
|
|
169
182
|
walkthroughCam.groundPlane = groundPlane;
|
|
170
183
|
}
|
|
171
|
-
}, [groundPlane,
|
|
184
|
+
}, [groundPlane, cameraRoot, isCurrentlyInXr]);
|
|
172
185
|
useEffect(() => {
|
|
173
186
|
// Set imperatively for the same reason as BoundaryRing: boundsCenter is a Vec3, and the
|
|
174
187
|
// @playcanvas/react memo() comparator stops at the first prop with an .equals() method.
|
|
175
188
|
// @ts-expect-error - scripts are added dynamically to the entity
|
|
176
|
-
const navigation =
|
|
189
|
+
const navigation = cameraRoot?.script?.tfXrNavigation;
|
|
177
190
|
if (navigation) {
|
|
178
191
|
navigation.boundsCenter = sceneBoundsCenter;
|
|
179
192
|
navigation.boundsRadius = sceneBoundsRadius;
|
|
180
193
|
}
|
|
181
|
-
}, [
|
|
194
|
+
}, [cameraRoot, sceneBoundsCenter, sceneBoundsRadius]);
|
|
182
195
|
const handleToggleFreeFly = useCallback(() => {
|
|
183
196
|
const newFreeFly = !isFreeFly;
|
|
184
|
-
// @ts-expect-error - scripts are added dynamically to the
|
|
185
|
-
const walkthroughCam =
|
|
197
|
+
// @ts-expect-error - scripts are added dynamically to the entity
|
|
198
|
+
const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
|
|
186
199
|
if (walkthroughCam) {
|
|
187
200
|
walkthroughCam.freeFly = newFreeFly;
|
|
188
201
|
}
|
|
@@ -190,7 +203,7 @@ const VirtualWalkthroughViewer = ({
|
|
|
190
203
|
setCamera(origin, cameraPosition);
|
|
191
204
|
}
|
|
192
205
|
setIsFreeFly(newFreeFly);
|
|
193
|
-
}, [isFreeFly,
|
|
206
|
+
}, [isFreeFly, cameraRoot, setCamera, origin, cameraPosition]);
|
|
194
207
|
useEffect(() => {
|
|
195
208
|
setLocalAnnotations(annotations);
|
|
196
209
|
}, [annotations]);
|
|
@@ -300,23 +313,28 @@ const VirtualWalkthroughViewer = ({
|
|
|
300
313
|
scaleFactor: scaleFactor,
|
|
301
314
|
revealRain: isHighPerformance
|
|
302
315
|
}, 'splat'), [isHighPerformance, splatSrc, splatFormat, scaleFactor, splatModelProps]);
|
|
303
|
-
return /*#__PURE__*/jsxs(
|
|
316
|
+
return /*#__PURE__*/jsxs(CameraEntityContext.Provider, {
|
|
317
|
+
value: activeCamera,
|
|
304
318
|
children: [/*#__PURE__*/jsx(GradientSky, {
|
|
305
319
|
topColor: skyColor || '#FFFFFF',
|
|
306
320
|
horizonColor: skyColor || '#EAF8FF',
|
|
307
321
|
groundColor: groundColor || '#C3BDB7'
|
|
308
322
|
}), /*#__PURE__*/jsxs(Entity, {
|
|
309
323
|
name: "camera-root",
|
|
310
|
-
|
|
324
|
+
ref: setCameraRoot,
|
|
325
|
+
children: [/*#__PURE__*/jsx(Entity, {
|
|
311
326
|
name: "camera",
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
327
|
+
ref: setCameraEntity,
|
|
328
|
+
children: camera
|
|
329
|
+
}), !isCurrentlyInXr && /*#__PURE__*/jsx(Script, {
|
|
330
|
+
script: WalkthroughCamera,
|
|
331
|
+
boundsCenter: sceneBoundsCenter,
|
|
332
|
+
boundsRadius: sceneBoundsRadius,
|
|
333
|
+
enableFly: !isTextFieldFocused,
|
|
334
|
+
averageCameraHeight: averageCameraHeight
|
|
335
|
+
}), /*#__PURE__*/jsx(XrExitButton, {
|
|
336
|
+
onClose: onXrClose
|
|
337
|
+
}), /*#__PURE__*/jsx(Script, {
|
|
320
338
|
script: XrControllers,
|
|
321
339
|
enabled: !isEdit
|
|
322
340
|
}), /*#__PURE__*/jsx(XrAnnotationInteraction, {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
interface XrExitButtonProps {
|
|
2
|
+
/** Runs instead of ending the XR session when the button is activated. */
|
|
3
|
+
onClose?: () => void;
|
|
4
|
+
}
|
|
5
|
+
declare const XrExitButton: ({ onClose }: XrExitButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
2
6
|
export default XrExitButton;
|
|
3
7
|
//# sourceMappingURL=XrExitButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"XrExitButton.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/XrExitButton.tsx"],"names":[],"mappings":"AAOA,QAAA,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"XrExitButton.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/XrExitButton.tsx"],"names":[],"mappings":"AAOA,UAAU,iBAAiB;IACzB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,YAAY,GAAI,aAAa,iBAAiB,4CAInD,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -4,10 +4,13 @@ import { Script } from '@playcanvas/react/components';
|
|
|
4
4
|
import { XrExitButton as XrExitButton$1 } from './xr-exit-button.js';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
|
-
const XrExitButton = (
|
|
7
|
+
const XrExitButton = ({
|
|
8
|
+
onClose
|
|
9
|
+
}) => /*#__PURE__*/jsx(Entity, {
|
|
8
10
|
name: "xr-exit-button",
|
|
9
11
|
children: /*#__PURE__*/jsx(Script, {
|
|
10
|
-
script: XrExitButton$1
|
|
12
|
+
script: XrExitButton$1,
|
|
13
|
+
onClose: onClose
|
|
11
14
|
})
|
|
12
15
|
});
|
|
13
16
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundary-wall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-wall.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"boundary-wall.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-wall.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,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"}
|
|
@@ -237,7 +237,7 @@ class BoundaryWallScript extends Script {
|
|
|
237
237
|
*/
|
|
238
238
|
_resolveDependencies() {
|
|
239
239
|
if (!this._camera) {
|
|
240
|
-
this._camera = this.app.root.
|
|
240
|
+
this._camera = this.app.root.findComponent('camera')?.entity ?? null;
|
|
241
241
|
}
|
|
242
242
|
if (!this._navigation) {
|
|
243
243
|
const rig = this.app.root.findByName('camera-root');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Entity } from 'playcanvas';
|
|
2
|
+
/**
|
|
3
|
+
* A camera the walkthrough took over, and the call that hands it back.
|
|
4
|
+
*/
|
|
5
|
+
export interface AdoptedCamera {
|
|
6
|
+
/** The entity now carrying the live camera component, which the rig poses and projects from. */
|
|
7
|
+
camera: Entity;
|
|
8
|
+
release: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hands the scene's existing camera to a walkthrough rig that hasn't been given one.
|
|
12
|
+
*
|
|
13
|
+
* A caller mounting the viewer into a scene it doesn't own passes `camera={null}`, because an XR
|
|
14
|
+
* session is welded to the camera component it was started on — `XrManager.camera` is read-only and
|
|
15
|
+
* `start` refuses while a session is running — so a walkthrough that brought a camera of its own
|
|
16
|
+
* could only become the session's camera by ending it, which drops the user out of the headset.
|
|
17
|
+
*
|
|
18
|
+
* That leaves the rig with no camera under it, and everything that poses or solves against the head
|
|
19
|
+
* resolves it with `findComponent('camera')` on the rig: `WalkthroughCamera`'s pointer and keyboard
|
|
20
|
+
* walk, `XrStartPose`, `XrNavigation`'s thumbstick movement and turning, and the bounds clamp all go
|
|
21
|
+
* inert, so the head is never placed at the walkthrough's start point and can never be moved from
|
|
22
|
+
* wherever the host scene left it. Splat LOD is chosen from
|
|
23
|
+
* the camera's distance to each octree node, so the far side of the model stays coarse for the whole
|
|
24
|
+
* session no matter which way the headset turns.
|
|
25
|
+
*
|
|
26
|
+
* Reparenting carries the live camera component with it, so the session, its views, and the host's
|
|
27
|
+
* own clear color and clip planes are all untouched — only which entity the camera hangs off
|
|
28
|
+
* changes. The camera becomes a direct child of the rig, which is exactly where a camera the viewer
|
|
29
|
+
* owns sits, so the rig means the same thing either way.
|
|
30
|
+
*
|
|
31
|
+
* @param root - The scene root to look for a camera in, expected to hold exactly one camera.
|
|
32
|
+
* @param rig - The entity the walkthrough's XR scripts move, `camera-root`.
|
|
33
|
+
* @param ownCamera - The entity the viewer mounts its own camera on. Nothing is adopted when it
|
|
34
|
+
* already carries one.
|
|
35
|
+
* @returns The adopted camera and the call that puts it back, or null when there was nothing to
|
|
36
|
+
* adopt.
|
|
37
|
+
*/
|
|
38
|
+
export declare const adoptSceneCamera: (root: Entity, rig: Entity, ownCamera: Entity) => AdoptedCamera | null;
|
|
39
|
+
//# sourceMappingURL=camera-adoption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"camera-adoption.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/camera-adoption.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAc,MAAM,YAAY,CAAC;AAWjE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gGAAgG;IAChG,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,EAAE,WAAW,MAAM,KAAG,aAAa,GAAG,IA8B/F,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pose an adopted camera is put back to when the walkthrough releases it.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A camera the walkthrough took over, and the call that hands it back.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hands the scene's existing camera to a walkthrough rig that hasn't been given one.
|
|
11
|
+
*
|
|
12
|
+
* A caller mounting the viewer into a scene it doesn't own passes `camera={null}`, because an XR
|
|
13
|
+
* session is welded to the camera component it was started on — `XrManager.camera` is read-only and
|
|
14
|
+
* `start` refuses while a session is running — so a walkthrough that brought a camera of its own
|
|
15
|
+
* could only become the session's camera by ending it, which drops the user out of the headset.
|
|
16
|
+
*
|
|
17
|
+
* That leaves the rig with no camera under it, and everything that poses or solves against the head
|
|
18
|
+
* resolves it with `findComponent('camera')` on the rig: `WalkthroughCamera`'s pointer and keyboard
|
|
19
|
+
* walk, `XrStartPose`, `XrNavigation`'s thumbstick movement and turning, and the bounds clamp all go
|
|
20
|
+
* inert, so the head is never placed at the walkthrough's start point and can never be moved from
|
|
21
|
+
* wherever the host scene left it. Splat LOD is chosen from
|
|
22
|
+
* the camera's distance to each octree node, so the far side of the model stays coarse for the whole
|
|
23
|
+
* session no matter which way the headset turns.
|
|
24
|
+
*
|
|
25
|
+
* Reparenting carries the live camera component with it, so the session, its views, and the host's
|
|
26
|
+
* own clear color and clip planes are all untouched — only which entity the camera hangs off
|
|
27
|
+
* changes. The camera becomes a direct child of the rig, which is exactly where a camera the viewer
|
|
28
|
+
* owns sits, so the rig means the same thing either way.
|
|
29
|
+
*
|
|
30
|
+
* @param root - The scene root to look for a camera in, expected to hold exactly one camera.
|
|
31
|
+
* @param rig - The entity the walkthrough's XR scripts move, `camera-root`.
|
|
32
|
+
* @param ownCamera - The entity the viewer mounts its own camera on. Nothing is adopted when it
|
|
33
|
+
* already carries one.
|
|
34
|
+
* @returns The adopted camera and the call that puts it back, or null when there was nothing to
|
|
35
|
+
* adopt.
|
|
36
|
+
*/
|
|
37
|
+
const adoptSceneCamera = (root, rig, ownCamera) => {
|
|
38
|
+
if (ownCamera.camera) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const camera = root.findComponent('camera')?.entity;
|
|
42
|
+
// isDescendantOf covers a host that mounted the walkthrough inside its own camera, where
|
|
43
|
+
// reparenting would make a cycle.
|
|
44
|
+
if (!camera || camera === rig || !camera.parent || rig.isDescendantOf(camera)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const previous = {
|
|
48
|
+
parent: camera.parent,
|
|
49
|
+
position: camera.getLocalPosition().clone(),
|
|
50
|
+
rotation: camera.getLocalRotation().clone()
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// addChild keeps the local transform, so the host's authored camera pose survives the move and
|
|
54
|
+
// only has to be restored because a session overwrites it with the head pose every frame.
|
|
55
|
+
rig.addChild(camera);
|
|
56
|
+
return {
|
|
57
|
+
camera,
|
|
58
|
+
release: () => {
|
|
59
|
+
previous.parent.addChild(camera);
|
|
60
|
+
camera.setLocalPosition(previous.position);
|
|
61
|
+
camera.setLocalRotation(previous.rotation);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { adoptSceneCamera };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Entity } from 'playcanvas';
|
|
2
|
+
/**
|
|
3
|
+
* Keeps the walkthrough's rig holding a camera for as long as the viewer is mounted, adopting the
|
|
4
|
+
* scene's own when the viewer wasn't given one. See {@link adoptSceneCamera} for why.
|
|
5
|
+
*
|
|
6
|
+
* @param rig - The `camera-root` entity, or null before it exists.
|
|
7
|
+
* @param ownCamera - The entity the viewer mounts its own camera on, or null before it exists.
|
|
8
|
+
* @returns The adopted camera, or null when the viewer brought one of its own. Held as state so the
|
|
9
|
+
* viewer re-renders and re-points everything that addresses the camera at the entity now carrying
|
|
10
|
+
* it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const useAdoptedCamera: (rig: Entity | null, ownCamera: Entity | null) => Entity | null;
|
|
13
|
+
export default useAdoptedCamera;
|
|
14
|
+
//# sourceMappingURL=useAdoptedCamera.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdoptedCamera.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/useAdoptedCamera.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAIpC;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,GAAG,IAAI,EAAE,WAAW,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IA8BxF,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useState, useLayoutEffect } from 'react';
|
|
2
|
+
import { useApp } from '@playcanvas/react/hooks';
|
|
3
|
+
import { adoptSceneCamera } from './camera-adoption.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Keeps the walkthrough's rig holding a camera for as long as the viewer is mounted, adopting the
|
|
7
|
+
* scene's own when the viewer wasn't given one. See {@link adoptSceneCamera} for why.
|
|
8
|
+
*
|
|
9
|
+
* @param rig - The `camera-root` entity, or null before it exists.
|
|
10
|
+
* @param ownCamera - The entity the viewer mounts its own camera on, or null before it exists.
|
|
11
|
+
* @returns The adopted camera, or null when the viewer brought one of its own. Held as state so the
|
|
12
|
+
* viewer re-renders and re-points everything that addresses the camera at the entity now carrying
|
|
13
|
+
* it.
|
|
14
|
+
*/
|
|
15
|
+
const useAdoptedCamera = (rig, ownCamera) => {
|
|
16
|
+
const app = useApp();
|
|
17
|
+
const [adopted, setAdopted] = useState(null);
|
|
18
|
+
|
|
19
|
+
// A layout effect, so that the camera is handed back before the rig it was parented into is
|
|
20
|
+
// destroyed. `@playcanvas/react`'s Entity destroys its entity in a layout effect's teardown, and
|
|
21
|
+
// destroying an entity destroys its children — so a release deferred to a passive effect runs
|
|
22
|
+
// after the host's camera has already been destroyed along with the walkthrough, leaving the host
|
|
23
|
+
// scene with no camera to render through at all.
|
|
24
|
+
//
|
|
25
|
+
// The rig is a child of the viewer, and React tears an unmounting tree's layout effects down from
|
|
26
|
+
// the top, so this runs first.
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!rig || !ownCamera) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Both entities are state set from refs, so this runs a commit after the child that mounts the
|
|
33
|
+
// camera component: a viewer that was given a camera is recognised as owning one rather than
|
|
34
|
+
// adopted on top of.
|
|
35
|
+
const adoption = adoptSceneCamera(app.root, rig, ownCamera);
|
|
36
|
+
setAdopted(adoption?.camera ?? null);
|
|
37
|
+
return () => {
|
|
38
|
+
adoption?.release();
|
|
39
|
+
setAdopted(null);
|
|
40
|
+
};
|
|
41
|
+
}, [app, rig, ownCamera]);
|
|
42
|
+
return adopted;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { useAdoptedCamera as default, useAdoptedCamera };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Holds the XR render settings in place for the duration of a session
|
|
2
|
+
* Holds the XR render settings in place for the duration of a session, including one that is
|
|
3
|
+
* already running when this mounts.
|
|
3
4
|
*
|
|
4
5
|
* Call this once per application. The gsplat settings it writes are scene-global, so a second
|
|
5
6
|
* caller would capture the first caller's tuned values as the state to restore, and the scene would
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useXrRenderTuning.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/useXrRenderTuning.ts"],"names":[],"mappings":"AAMA
|
|
1
|
+
{"version":3,"file":"useXrRenderTuning.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/useXrRenderTuning.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,YA8C7B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -3,7 +3,8 @@ import { useApp } from '@playcanvas/react/hooks';
|
|
|
3
3
|
import { applyGsplatTuning, XR_FIXED_FOVEATION, restoreGsplatTuning } from './xr-render-tuning.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Holds the XR render settings in place for the duration of a session
|
|
6
|
+
* Holds the XR render settings in place for the duration of a session, including one that is
|
|
7
|
+
* already running when this mounts.
|
|
7
8
|
*
|
|
8
9
|
* Call this once per application. The gsplat settings it writes are scene-global, so a second
|
|
9
10
|
* caller would capture the first caller's tuned values as the state to restore, and the scene would
|
|
@@ -35,6 +36,14 @@ const useXrRenderTuning = () => {
|
|
|
35
36
|
};
|
|
36
37
|
xr.on('start', handleStart);
|
|
37
38
|
xr.on('end', restore);
|
|
39
|
+
|
|
40
|
+
// A viewer can be mounted into a scene that is already in a headset, in which case `start`
|
|
41
|
+
// fired before this hook existed and waiting for it would leave the whole session rendering
|
|
42
|
+
// with the desktop settings. `handleStart` is idempotent, so a session that goes on to fire
|
|
43
|
+
// `start` anyway does not capture the tuned values as the ones to restore.
|
|
44
|
+
if (xr.active) {
|
|
45
|
+
handleStart();
|
|
46
|
+
}
|
|
38
47
|
return () => {
|
|
39
48
|
xr.off('start', handleStart);
|
|
40
49
|
xr.off('end', restore);
|
|
@@ -34,7 +34,7 @@ export declare class WalkthroughCamera extends Script {
|
|
|
34
34
|
* Script component is wrapped in memo() whose shallowEquals comparator returns early on the first prop
|
|
35
35
|
* with a .equals() method (Vec3), so any prop listed after boundsCenter is never compared and the
|
|
36
36
|
* component won't re-render when it changes. Set via:
|
|
37
|
-
*
|
|
37
|
+
* cameraRoot.script.walkthroughCamera.groundPlane = points
|
|
38
38
|
* Should be updated if shallowEquals is fixed (see https://github.com/playcanvas/react/pull/298)
|
|
39
39
|
*/
|
|
40
40
|
groundPlane: Vec3[];
|
|
@@ -48,9 +48,16 @@ export declare class WalkthroughCamera extends Script {
|
|
|
48
48
|
private _scrollDelta;
|
|
49
49
|
private _keys;
|
|
50
50
|
private _removeListeners;
|
|
51
|
+
private _camera;
|
|
51
52
|
private _planeNormal;
|
|
52
53
|
private _planePoint;
|
|
53
54
|
private _hasGroundPlane;
|
|
55
|
+
/**
|
|
56
|
+
* The camera to pose, or null while there is none to pose. The entity this script is mounted on is
|
|
57
|
+
* never posed in its place: on the walkthrough that is the rig, which is the frame an XR session
|
|
58
|
+
* is measured from and whose height the session's floor is taken from.
|
|
59
|
+
*/
|
|
60
|
+
private _posedCamera;
|
|
54
61
|
private _syncGroundPlane;
|
|
55
62
|
initialize(): void;
|
|
56
63
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walkthrough-camera.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/walkthrough-camera.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"walkthrough-camera.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/walkthrough-camera.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,MAAM,EAAE,IAAI,EAAQ,MAAM,YAAY,CAAC;AAmC/E,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;IAI9C,OAAO,CAAC,OAAO,CAAuB;IAGtC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,eAAe,CAAS;IAEhC;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,gBAAgB;IAaxB,UAAU;IAkFV;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI;IAuBjC,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM;IA0B1B;;;OAGG;IACH,IAAI,UAAU,IAAI,IAAI,CAMrB;IAED,MAAM,CAAC,EAAE,EAAE,MAAM;IAgGjB,OAAO;CAIR"}
|
|
@@ -12,6 +12,11 @@ import { computeGroundPlane, yOnPlane } from './groundPlane.js';
|
|
|
12
12
|
* - Y position is clamped between boundsMin.y and boundsMax.y; set them equal
|
|
13
13
|
* to lock vertical position entirely.
|
|
14
14
|
* - Exposes reset() and focusPoint to stay compatible with useCameraPosition.
|
|
15
|
+
*
|
|
16
|
+
* The pose is written to the camera under this script's entity rather than to the entity itself, so
|
|
17
|
+
* mounting it on the walkthrough's rig drives whichever camera the rig is holding — one the viewer
|
|
18
|
+
* brought, or one it adopted from the host scene. Mounted straight onto a camera entity it still
|
|
19
|
+
* drives that entity, which `findComponent` resolves to itself.
|
|
15
20
|
*/
|
|
16
21
|
|
|
17
22
|
const _quat = new Quat();
|
|
@@ -74,7 +79,7 @@ class WalkthroughCamera extends Script {
|
|
|
74
79
|
* Script component is wrapped in memo() whose shallowEquals comparator returns early on the first prop
|
|
75
80
|
* with a .equals() method (Vec3), so any prop listed after boundsCenter is never compared and the
|
|
76
81
|
* component won't re-render when it changes. Set via:
|
|
77
|
-
*
|
|
82
|
+
* cameraRoot.script.walkthroughCamera.groundPlane = points
|
|
78
83
|
* Should be updated if shallowEquals is fixed (see https://github.com/playcanvas/react/pull/298)
|
|
79
84
|
*/
|
|
80
85
|
groundPlane = [];
|
|
@@ -93,10 +98,24 @@ class WalkthroughCamera extends Script {
|
|
|
93
98
|
_keys = {};
|
|
94
99
|
_removeListeners = [];
|
|
95
100
|
|
|
101
|
+
// The camera being posed, resolved lazily. Held until it exists: a camera adopted from the host
|
|
102
|
+
// scene is reparented into the rig by an effect that runs after the scripts are mounted.
|
|
103
|
+
_camera = null;
|
|
104
|
+
|
|
96
105
|
// Cached plane derived from groundPlane attribute. Computed once when 3 points become available.
|
|
97
106
|
_planeNormal = new Vec3(0, 1, 0);
|
|
98
107
|
_planePoint = new Vec3(0, 0, 0);
|
|
99
108
|
_hasGroundPlane = false;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The camera to pose, or null while there is none to pose. The entity this script is mounted on is
|
|
112
|
+
* never posed in its place: on the walkthrough that is the rig, which is the frame an XR session
|
|
113
|
+
* is measured from and whose height the session's floor is taken from.
|
|
114
|
+
*/
|
|
115
|
+
_posedCamera() {
|
|
116
|
+
this._camera ??= this.entity.findComponent('camera')?.entity ?? null;
|
|
117
|
+
return this._camera;
|
|
118
|
+
}
|
|
100
119
|
_syncGroundPlane() {
|
|
101
120
|
if (this._hasGroundPlane || this.groundPlane.length < 3) {
|
|
102
121
|
return;
|
|
@@ -179,7 +198,8 @@ class WalkthroughCamera extends Script {
|
|
|
179
198
|
* Compatible with the useCameraPosition hook's reset() call.
|
|
180
199
|
*/
|
|
181
200
|
reset(focus, position) {
|
|
182
|
-
this.
|
|
201
|
+
const camera = this._posedCamera();
|
|
202
|
+
camera?.setPosition(position);
|
|
183
203
|
const dx = focus.x - position.x;
|
|
184
204
|
const dy = focus.y - position.y;
|
|
185
205
|
const dz = focus.z - position.z;
|
|
@@ -195,7 +215,7 @@ class WalkthroughCamera extends Script {
|
|
|
195
215
|
// Snap targets so there's no damped drift from the previous pose.
|
|
196
216
|
this._targetPitch = this._pitch;
|
|
197
217
|
this._targetYaw = this._yaw;
|
|
198
|
-
|
|
218
|
+
camera?.setEulerAngles(this._pitch, this._yaw, 0);
|
|
199
219
|
}
|
|
200
220
|
get currentYaw() {
|
|
201
221
|
return this._targetYaw;
|
|
@@ -210,7 +230,11 @@ class WalkthroughCamera extends Script {
|
|
|
210
230
|
*/
|
|
211
231
|
orbitStep(yawDelta) {
|
|
212
232
|
this._syncGroundPlane();
|
|
213
|
-
const
|
|
233
|
+
const camera = this._posedCamera();
|
|
234
|
+
if (!camera) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const pos = camera.getPosition();
|
|
214
238
|
const dx = pos.x - this.boundsCenter.x;
|
|
215
239
|
const dz = pos.z - this.boundsCenter.z;
|
|
216
240
|
const radius = Math.sqrt(dx * dx + dz * dz) || this.boundsRadius * 0.5;
|
|
@@ -219,12 +243,12 @@ class WalkthroughCamera extends Script {
|
|
|
219
243
|
const nx = this.boundsCenter.x + radius * Math.cos(newAngle);
|
|
220
244
|
const nz = this.boundsCenter.z + radius * Math.sin(newAngle);
|
|
221
245
|
const ny = this._hasGroundPlane ? yOnPlane(nx, nz, this._planeNormal, this._planePoint, this.boundsCenter.y) + this.averageCameraHeight : this.boundsCenter.y;
|
|
222
|
-
|
|
246
|
+
camera.setPosition(nx, ny, nz);
|
|
223
247
|
const faceDx = this.boundsCenter.x - nx;
|
|
224
248
|
const faceDz = this.boundsCenter.z - nz;
|
|
225
249
|
this._yaw = Math.atan2(-faceDx, -faceDz) * (180 / Math.PI);
|
|
226
250
|
this._targetYaw = this._yaw;
|
|
227
|
-
|
|
251
|
+
camera.setEulerAngles(this._pitch, this._yaw, 0);
|
|
228
252
|
}
|
|
229
253
|
|
|
230
254
|
/**
|
|
@@ -232,13 +256,17 @@ class WalkthroughCamera extends Script {
|
|
|
232
256
|
* Compatible with the useCameraPosition hook's focusPoint read.
|
|
233
257
|
*/
|
|
234
258
|
get focusPoint() {
|
|
235
|
-
const pos = this.entity.getPosition();
|
|
259
|
+
const pos = (this._posedCamera() ?? this.entity).getPosition();
|
|
236
260
|
_quat.setFromEulerAngles(this._pitch, this._yaw, 0);
|
|
237
261
|
_quat.transformVector(Vec3.FORWARD, _flatForward);
|
|
238
262
|
return new Vec3(pos.x + _flatForward.x, pos.y + _flatForward.y, pos.z + _flatForward.z);
|
|
239
263
|
}
|
|
240
264
|
update(dt) {
|
|
241
265
|
this._syncGroundPlane();
|
|
266
|
+
const camera = this._posedCamera();
|
|
267
|
+
if (!camera) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
242
270
|
|
|
243
271
|
// Lerp current angles toward targets using the same damping as CameraControls.
|
|
244
272
|
// Snap to the target once within ANGLE_EPSILON so the asymptotic lerp actually
|
|
@@ -262,9 +290,9 @@ class WalkthroughCamera extends Script {
|
|
|
262
290
|
this._pitch = nextPitch;
|
|
263
291
|
this._yaw = nextYaw;
|
|
264
292
|
if (anglesChanged) {
|
|
265
|
-
|
|
293
|
+
camera.setEulerAngles(this._pitch, this._yaw, 0);
|
|
266
294
|
}
|
|
267
|
-
const pos =
|
|
295
|
+
const pos = camera.getPosition();
|
|
268
296
|
let nx = pos.x;
|
|
269
297
|
let ny = pos.y;
|
|
270
298
|
let nz = pos.z;
|
|
@@ -308,7 +336,7 @@ class WalkthroughCamera extends Script {
|
|
|
308
336
|
// idle frames; note it does not stop per-frame rasterization — while autoRender is on
|
|
309
337
|
// the app renders every frame regardless of whether the transform changed.
|
|
310
338
|
if (Math.abs(nx - pos.x) > POSITION_EPSILON || Math.abs(targetY - pos.y) > POSITION_EPSILON || Math.abs(nz - pos.z) > POSITION_EPSILON) {
|
|
311
|
-
|
|
339
|
+
camera.setPosition(nx, targetY, nz);
|
|
312
340
|
}
|
|
313
341
|
}
|
|
314
342
|
destroy() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-annotation-interaction.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-interaction.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"xr-annotation-interaction.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-annotation-interaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAkC,MAAM,YAAY,CAAC;AAOrF,qBAAa,uBAAwB,SAAQ,MAAM;IACjD,MAAM,CAAC,UAAU,SAA6B;IAE9C,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE/B,OAAO,CAAC,YAAY,CAAgC;IAEpD,OAAO,CAAC,WAAW,CAAyE;IAE5F,OAAO,CAAC,QAAQ,CAId;IAEF,OAAO,CAAC,SAAS,CAKf;IAEF,OAAO,CAAC,uBAAuB;IAqB/B,UAAU;IAKV,MAAM;CAeP"}
|
|
@@ -35,8 +35,8 @@ class XrAnnotationInteraction extends Script {
|
|
|
35
35
|
entity,
|
|
36
36
|
script
|
|
37
37
|
} = candidates[index];
|
|
38
|
-
const camera = this.app.root.
|
|
39
|
-
const screen = camera?.
|
|
38
|
+
const camera = this.app.root.findComponent('camera');
|
|
39
|
+
const screen = camera?.worldToScreen(entity.getPosition());
|
|
40
40
|
script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
|
|
41
41
|
}
|
|
42
42
|
initialize() {
|
|
@@ -12,6 +12,11 @@ export declare class XrExitButton extends Script {
|
|
|
12
12
|
halfSize: number;
|
|
13
13
|
/** World-space radius of the hover/hit sphere. Slightly larger than halfSize for easier targeting. */
|
|
14
14
|
hitRadius: number;
|
|
15
|
+
/**
|
|
16
|
+
* Called in place of ending the XR session when the user activates the button, leaving the
|
|
17
|
+
* session running so the handler decides what happens next.
|
|
18
|
+
*/
|
|
19
|
+
onClose?: () => void;
|
|
15
20
|
private _material?;
|
|
16
21
|
private _texture?;
|
|
17
22
|
private _mesh?;
|
|
@@ -28,6 +33,8 @@ export declare class XrExitButton extends Script {
|
|
|
28
33
|
private _onSelect;
|
|
29
34
|
/** True when the ray points at the button's hit sphere. False while the button entity is disabled. */
|
|
30
35
|
rayHitsButton(origin: Vec3, direction: Vec3): boolean;
|
|
36
|
+
/** The button's exit action: the `onClose` handler when one is set, otherwise ending the session. */
|
|
37
|
+
close(): void;
|
|
31
38
|
/** The button's static artwork, painted once. Only the sweep changes per frame, and that lives in
|
|
32
39
|
* the shader. */
|
|
33
40
|
private _createMaskTexture;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-exit-button.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-exit-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,MAAM,EAGN,IAAI,EAGL,MAAM,YAAY,CAAC;AASpB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,IAAI,EAAE,WAAW,IAAI,EAAE,QAAQ,IAAI,EAAE,QAAQ,MAAM,KAAG,OAUhG,CAAC;AAUF,qBAAa,YAAa,SAAQ,MAAM;IACtC,MAAM,CAAC,UAAU,SAAkB;IAEnC,yGAAyG;IACzG,MAAM,OAA8B;IAEpC,oFAAoF;IACpF,QAAQ,SAAQ;IAEhB,sGAAsG;IACtG,SAAS,SAAQ;IAEjB,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAgC;IAEpD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,OAAO,CAA2B;IAE1C,OAAO,CAAC,WAAW,CAAyE;IAE5F,OAAO,CAAC,UAAU,CAGhB;IAEF,OAAO,CAAC,QAAQ,CAMd;IAEF,OAAO,CAAC,SAAS,CAOf;IAEF,sGAAsG;IACtG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO;IAQrD;qBACiB;IACjB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,cAAc;IAKtB,UAAU;IAwBV,MAAM,CAAC,EAAE,EAAE,MAAM;IAiDjB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAuBlB,OAAO;CAcR"}
|
|
1
|
+
{"version":3,"file":"xr-exit-button.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-exit-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,MAAM,EAGN,IAAI,EAGL,MAAM,YAAY,CAAC;AASpB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,IAAI,EAAE,WAAW,IAAI,EAAE,QAAQ,IAAI,EAAE,QAAQ,MAAM,KAAG,OAUhG,CAAC;AAUF,qBAAa,YAAa,SAAQ,MAAM;IACtC,MAAM,CAAC,UAAU,SAAkB;IAEnC,yGAAyG;IACzG,MAAM,OAA8B;IAEpC,oFAAoF;IACpF,QAAQ,SAAQ;IAEhB,sGAAsG;IACtG,SAAS,SAAQ;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,QAAQ,CAAC,CAAU;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAO;IACrB,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,YAAY,CAAgC;IAEpD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,OAAO,CAA2B;IAE1C,OAAO,CAAC,WAAW,CAAyE;IAE5F,OAAO,CAAC,UAAU,CAGhB;IAEF,OAAO,CAAC,QAAQ,CAMd;IAEF,OAAO,CAAC,SAAS,CAOf;IAEF,sGAAsG;IACtG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,OAAO;IAQrD,qGAAqG;IACrG,KAAK;IASL;qBACiB;IACjB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,cAAc;IAKtB,UAAU;IAwBV,MAAM,CAAC,EAAE,EAAE,MAAM;IAiDjB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAuBlB,OAAO;CAcR"}
|
|
@@ -38,6 +38,12 @@ class XrExitButton extends Script {
|
|
|
38
38
|
|
|
39
39
|
/** World-space radius of the hover/hit sphere. Slightly larger than halfSize for easier targeting. */
|
|
40
40
|
hitRadius = 0.15;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Called in place of ending the XR session when the user activates the button, leaving the
|
|
44
|
+
* session running so the handler decides what happens next.
|
|
45
|
+
*/
|
|
46
|
+
|
|
41
47
|
_hovered = false;
|
|
42
48
|
_headPosition = new Vec3();
|
|
43
49
|
_headRotation = new Quat();
|
|
@@ -62,7 +68,7 @@ class XrExitButton extends Script {
|
|
|
62
68
|
return;
|
|
63
69
|
}
|
|
64
70
|
if (this.rayHitsButton(inputSource.getOrigin(), inputSource.getDirection())) {
|
|
65
|
-
this.
|
|
71
|
+
this.close();
|
|
66
72
|
}
|
|
67
73
|
};
|
|
68
74
|
|
|
@@ -74,6 +80,15 @@ class XrExitButton extends Script {
|
|
|
74
80
|
return raySphereIntersect(origin, direction, this.entity.getPosition(), this.hitRadius);
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
/** The button's exit action: the `onClose` handler when one is set, otherwise ending the session. */
|
|
84
|
+
close() {
|
|
85
|
+
if (this.onClose) {
|
|
86
|
+
this.onClose();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.app.xr?.end();
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
/** The button's static artwork, painted once. Only the sweep changes per frame, and that lives in
|
|
78
93
|
* the shader. */
|
|
79
94
|
_createMaskTexture() {
|
|
@@ -141,7 +156,7 @@ class XrExitButton extends Script {
|
|
|
141
156
|
// trigger. Edge-detected so holding a button and then aiming onto it doesn't fire.
|
|
142
157
|
const facePressed = this._faceButtons.justPressed(source);
|
|
143
158
|
if (sourceHovers && facePressed) {
|
|
144
|
-
this.
|
|
159
|
+
this.close();
|
|
145
160
|
return;
|
|
146
161
|
}
|
|
147
162
|
|
|
@@ -155,7 +170,7 @@ class XrExitButton extends Script {
|
|
|
155
170
|
this._hold = hold.state;
|
|
156
171
|
this._material?.setParameter('uProgress', pieShaderProgress(hold.progress));
|
|
157
172
|
if (hold.justFired) {
|
|
158
|
-
this.
|
|
173
|
+
this.close();
|
|
159
174
|
return;
|
|
160
175
|
}
|
|
161
176
|
if (hovered !== this._hovered) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xr-gaze-dwell.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-gaze-dwell.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"xr-gaze-dwell.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/xr-gaze-dwell.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,MAAM,EAKP,MAAM,YAAY,CAAC;AAmBpB,qBAAa,WAAY,SAAQ,MAAM;IACrC,MAAM,CAAC,UAAU,SAAiB;IAElC;6FACyF;IACzF,WAAW,SAAM;IAEjB,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,WAAW,CAAqC;IAExD,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,CAAU;IAE3B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,aAAa,CAAc;IAEnC,OAAO,CAAC,WAAW,CAAyE;IAE5F;;;;;OAKG;IACH,OAAO,CAAC,UAAU,CAGhB;IAEF,OAAO,CAAC,QAAQ,CAId;IAEF,OAAO,CAAC,cAAc;IAMtB,UAAU;IA4BV,MAAM,CAAC,EAAE,EAAE,MAAM;IAgCjB,OAAO,CAAC,UAAU;IAgBlB,uDAAuD;IACvD,OAAO,CAAC,SAAS;IAcjB,OAAO;CAiBR"}
|
|
@@ -102,8 +102,8 @@ class XrGazeDwell extends Script {
|
|
|
102
102
|
entity,
|
|
103
103
|
script
|
|
104
104
|
} = candidates[target];
|
|
105
|
-
const camera = this.app.root.
|
|
106
|
-
const screen = camera?.
|
|
105
|
+
const camera = this.app.root.findComponent('camera');
|
|
106
|
+
const screen = camera?.worldToScreen(entity.getPosition());
|
|
107
107
|
script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Entity } from 'playcanvas';
|
|
2
|
+
/**
|
|
3
|
+
* The camera entity the walkthrough owns, published by `VirtualWalkthroughViewer` for the hooks
|
|
4
|
+
* beneath it.
|
|
5
|
+
*
|
|
6
|
+
* Looking the camera up by name is not safe once the viewer shares a scene graph with a host that
|
|
7
|
+
* has entities of its own: `findByName('camera')` walks the whole graph and returns whichever
|
|
8
|
+
* entity was added first, so a host that names its own camera entity `camera` shadows the viewer's.
|
|
9
|
+
* Null outside a viewer, where the name lookup is still the only thing to go on.
|
|
10
|
+
*/
|
|
11
|
+
export declare const CameraEntityContext: import("react").Context<Entity | null>;
|
|
12
|
+
//# sourceMappingURL=cameraEntityContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cameraEntityContext.d.ts","sourceRoot":"","sources":["../../src/hooks/cameraEntityContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,wCAAqC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The camera entity the walkthrough owns, published by `VirtualWalkthroughViewer` for the hooks
|
|
5
|
+
* beneath it.
|
|
6
|
+
*
|
|
7
|
+
* Looking the camera up by name is not safe once the viewer shares a scene graph with a host that
|
|
8
|
+
* has entities of its own: `findByName('camera')` walks the whole graph and returns whichever
|
|
9
|
+
* entity was added first, so a host that names its own camera entity `camera` shadows the viewer's.
|
|
10
|
+
* Null outside a viewer, where the name lookup is still the only thing to go on.
|
|
11
|
+
*/
|
|
12
|
+
const CameraEntityContext = /*#__PURE__*/createContext(null);
|
|
13
|
+
|
|
14
|
+
export { CameraEntityContext };
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { Entity } from 'playcanvas';
|
|
2
|
+
/**
|
|
3
|
+
* @param cameraEntity - The camera to drive. Callers above {@link CameraEntityContext} pass it
|
|
4
|
+
* directly; the ones beneath take it from the context, and anything with neither falls back to
|
|
5
|
+
* looking an entity named `camera` in the scene graph.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useCameraPosition: (cameraEntity?: Entity | null) => {
|
|
2
8
|
setCamera: (focus: [number, number, number], position?: [number, number, number], horizontalNdcBias?: number) => void;
|
|
3
9
|
getCameraState: () => {
|
|
4
10
|
position: [number, number, number];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCameraPosition.d.ts","sourceRoot":"","sources":["../../src/hooks/useCameraPosition.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useCameraPosition.d.ts","sourceRoot":"","sources":["../../src/hooks/useCameraPosition.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAmB,MAAM,YAAY,CAAC;AAcrD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,eAAe,MAAM,GAAG,IAAI;uBAkBlD,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;kBAmBf,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;eACrB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;CAQpF,CAAC"}
|
|
@@ -1,22 +1,44 @@
|
|
|
1
|
-
import { useCallback, useMemo } from 'react';
|
|
1
|
+
import { useContext, useCallback, useMemo } from 'react';
|
|
2
2
|
import { useApp } from '@playcanvas/react/hooks';
|
|
3
3
|
import { Vec3 } from 'playcanvas';
|
|
4
|
+
import { CameraEntityContext } from './cameraEntityContext.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
/** The camera-driving scripts this hook talks to. */
|
|
7
|
+
|
|
8
|
+
/** Scripts are added to entities dynamically, so they aren't on the engine's types. */
|
|
9
|
+
const scriptsOn = node => node?.script;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param cameraEntity - The camera to drive. Callers above {@link CameraEntityContext} pass it
|
|
13
|
+
* directly; the ones beneath take it from the context, and anything with neither falls back to
|
|
14
|
+
* looking an entity named `camera` in the scene graph.
|
|
15
|
+
*/
|
|
16
|
+
const useCameraPosition = cameraEntity => {
|
|
6
17
|
const app = useApp();
|
|
18
|
+
const contextCamera = useContext(CameraEntityContext);
|
|
19
|
+
const resolvedCamera = cameraEntity ?? contextCamera;
|
|
20
|
+
const findCamera = useCallback(() => resolvedCamera ?? app.root.findByName('camera'), [app, resolvedCamera]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The script driving the camera. WalkthroughCamera is mounted on the rig above the camera rather
|
|
24
|
+
* than on the camera entity, so that it poses a camera the walkthrough adopted from a host scene
|
|
25
|
+
* as readily as one of its own; CameraControls sits on the camera entity itself.
|
|
26
|
+
*/
|
|
27
|
+
const findControls = useCallback(camera => {
|
|
28
|
+
const own = scriptsOn(camera);
|
|
29
|
+
return own?.cameraControls ?? own?.walkthroughCamera ?? scriptsOn(camera?.parent)?.walkthroughCamera;
|
|
30
|
+
}, []);
|
|
7
31
|
const setCamera = useCallback((focus, position, horizontalNdcBias = 0) => {
|
|
8
|
-
const camera =
|
|
9
|
-
|
|
10
|
-
const controls = camera?.script?.cameraControls ?? camera?.script?.walkthroughCamera;
|
|
32
|
+
const camera = findCamera();
|
|
33
|
+
const controls = findControls(camera);
|
|
11
34
|
if (controls && camera) {
|
|
12
35
|
// The extra bias arg is only honored by WalkthroughCamera; CameraControls ignores it.
|
|
13
36
|
controls.reset(new Vec3(focus), position ? new Vec3(position) : camera.getPosition(), horizontalNdcBias);
|
|
14
37
|
}
|
|
15
|
-
}, [
|
|
38
|
+
}, [findCamera, findControls]);
|
|
16
39
|
const getCameraState = useCallback(() => {
|
|
17
|
-
const camera =
|
|
18
|
-
|
|
19
|
-
const controls = camera?.script?.cameraControls ?? camera?.script?.walkthroughCamera;
|
|
40
|
+
const camera = findCamera();
|
|
41
|
+
const controls = findControls(camera);
|
|
20
42
|
if (camera && controls) {
|
|
21
43
|
const position = camera.getPosition();
|
|
22
44
|
const focusPoint = controls?.focusPoint;
|
|
@@ -26,7 +48,7 @@ const useCameraPosition = () => {
|
|
|
26
48
|
};
|
|
27
49
|
}
|
|
28
50
|
return null;
|
|
29
|
-
}, [
|
|
51
|
+
}, [findCamera, findControls]);
|
|
30
52
|
return useMemo(() => ({
|
|
31
53
|
setCamera,
|
|
32
54
|
getCameraState
|