@terraware/web-components 4.2.19 → 4.2.20
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/SplatControls.d.ts.map +1 -1
- package/components/VirtualWalkthrough/SplatControls.js +12 -45
- package/components/VirtualWalkthrough/TfAnnotationManager.d.ts +7 -0
- package/components/VirtualWalkthrough/TfAnnotationManager.d.ts.map +1 -1
- package/components/VirtualWalkthrough/TfAnnotationManager.js +27 -0
- package/hooks/useXr.d.ts +12 -0
- package/hooks/useXr.d.ts.map +1 -0
- package/hooks/useXr.js +74 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SplatControls.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/SplatControls.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SplatControls.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/SplatControls.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAA2B,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACrF,OAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAyB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAE/E,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,UAAU,EAAE,iBAAiB,CAAC;IAC9B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAC5C,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IAChE,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,QAAA,MAAM,aAAa,GAAI,2dA8BpB,kBAAkB,4CAwKpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef
|
|
1
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { Fullscreen, FullscreenExit } from '@mui/icons-material';
|
|
3
3
|
import { Box, IconButton, useTheme } from '@mui/material';
|
|
4
|
-
import { useApp } from '@playcanvas/react/hooks';
|
|
5
|
-
import { XRSPACE_LOCAL, XRTYPE_AR, XRTYPE_VR } from 'playcanvas';
|
|
6
4
|
import useBoolean from '../../hooks/useBoolean';
|
|
7
5
|
import { useCameraPosition } from '../../hooks/useCameraPosition';
|
|
6
|
+
import { useXr } from '../../hooks/useXr';
|
|
8
7
|
import { getRgbaFromHex } from '../../utils/color';
|
|
9
8
|
import useDeviceInfo from '../../utils/useDeviceInfo';
|
|
10
9
|
import Button from '../Button/Button';
|
|
@@ -48,51 +47,19 @@ const SplatControls = ({
|
|
|
48
47
|
const {
|
|
49
48
|
isDesktop
|
|
50
49
|
} = useDeviceInfo();
|
|
51
|
-
const app = useApp();
|
|
52
50
|
const {
|
|
53
51
|
setCamera,
|
|
54
52
|
getCameraState
|
|
55
53
|
} = useCameraPosition();
|
|
56
|
-
const
|
|
57
|
-
|
|
54
|
+
const {
|
|
55
|
+
isXrAvailable,
|
|
56
|
+
startXr
|
|
57
|
+
} = useXr({
|
|
58
|
+
onError
|
|
59
|
+
});
|
|
58
60
|
const [isInfoVisible, setIsInfoVisible] = useBoolean(true);
|
|
59
61
|
const paneRef = useRef(null);
|
|
60
62
|
const infoButtonRef = useRef(null);
|
|
61
|
-
const errorCallback = useCallback(err => {
|
|
62
|
-
if (err) {
|
|
63
|
-
onError?.(err);
|
|
64
|
-
app.xr?.end();
|
|
65
|
-
}
|
|
66
|
-
}, [app, onError]);
|
|
67
|
-
const handleAr = useCallback(() => app.xr?.start(app.root.findComponent('camera'), XRTYPE_AR, XRSPACE_LOCAL, {
|
|
68
|
-
callback: errorCallback
|
|
69
|
-
}), [app, errorCallback]);
|
|
70
|
-
const handleVr = useCallback(() => app.xr?.start(app.root.findComponent('camera'), XRTYPE_VR, XRSPACE_LOCAL, {
|
|
71
|
-
callback: errorCallback
|
|
72
|
-
}), [app, errorCallback]);
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
// this can't be changed to `useMemo(() => app.xr?.isAvailable(XRTYPE_AR), [app])` because `app` doesn't update when
|
|
75
|
-
// XR's availability is updated
|
|
76
|
-
const handleAvailable = (type, available) => {
|
|
77
|
-
if (type === XRTYPE_VR) {
|
|
78
|
-
setIsVrAvailable(available);
|
|
79
|
-
} else if (type === XRTYPE_AR) {
|
|
80
|
-
setIsArAvailable(available);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Check current availability state on mount
|
|
85
|
-
if (app.xr?.isAvailable(XRTYPE_VR)) {
|
|
86
|
-
setIsVrAvailable(true);
|
|
87
|
-
}
|
|
88
|
-
if (app.xr?.isAvailable(XRTYPE_AR)) {
|
|
89
|
-
setIsArAvailable(true);
|
|
90
|
-
}
|
|
91
|
-
app.xr?.on('available', handleAvailable);
|
|
92
|
-
return () => {
|
|
93
|
-
app.xr?.off('available', handleAvailable);
|
|
94
|
-
};
|
|
95
|
-
}, [app]);
|
|
96
63
|
useEffect(() => {
|
|
97
64
|
const handleKeyPress = event => {
|
|
98
65
|
if (event.key === 'r' || event.key === 'R') {
|
|
@@ -171,12 +138,12 @@ const SplatControls = ({
|
|
|
171
138
|
gap: 1,
|
|
172
139
|
pointerEvents: 'auto'
|
|
173
140
|
},
|
|
174
|
-
children: [
|
|
141
|
+
children: [isXrAvailable('AR') && !isEdit && /*#__PURE__*/_jsx(Button, {
|
|
175
142
|
label: strings.ar,
|
|
176
|
-
onClick:
|
|
177
|
-
}),
|
|
143
|
+
onClick: () => startXr('AR')
|
|
144
|
+
}), isXrAvailable('VR') && !isEdit && /*#__PURE__*/_jsx(Button, {
|
|
178
145
|
label: strings.vr,
|
|
179
|
-
onClick:
|
|
146
|
+
onClick: () => startXr('VR')
|
|
180
147
|
}), isDesktop && editable && !isEdit && onToggleEdit && /*#__PURE__*/_jsx(Button, {
|
|
181
148
|
label: strings.edit,
|
|
182
149
|
onClick: handleEdit
|
|
@@ -96,6 +96,13 @@ export declare class TfAnnotationManager extends PcAnnotationManager {
|
|
|
96
96
|
* @private
|
|
97
97
|
*/
|
|
98
98
|
_updateAnnotationPositions(annotation: any, resources: any, screenPos: any): void;
|
|
99
|
+
/**
|
|
100
|
+
* Override to also disable the hotspot mesh (not just the DOM) when the annotation is behind the
|
|
101
|
+
* camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane under a
|
|
102
|
+
* scaled content-root would stay huge in view until the camera moves the anchor in front.
|
|
103
|
+
* @private
|
|
104
|
+
*/
|
|
105
|
+
_hideAnnotationElements(annotation: any, resources: any): void;
|
|
99
106
|
/**
|
|
100
107
|
* Override the scale update to clamp the world size to a maximum value.
|
|
101
108
|
* Also scales the hotspot DOM element to match.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TfAnnotationManager.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfAnnotationManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,OAAO,EAAQ,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAIlG,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAelD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC1D,MAAM,CAAC,UAAU,SAAyB;IAE1C,OAAO,CAAC,aAAa,CAAO;IAC5B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,sBAAsB,CAAkB;IAChD,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;;;;;OAQG;IACH,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,YAAY,IAJQ,MAAM,CAM7B;IAED;;;;;;;;;;OAUG;IACH,IAAI,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAEvC;IAED,IAAI,sBAAsB,IAJQ,MAAM,CAMvC;IAED;;OAEG;IACH,UAAU;IAgBV;;;;OAIG;IACH,eAAe;IAiBf;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,kBAAkB;IAazC;;OAEG;IACH,MAAM;IAmCN;;;;OAIG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"TfAnnotationManager.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfAnnotationManager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,OAAO,EAAQ,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,IAAI,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAIlG,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAelD;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC1D,MAAM,CAAC,UAAU,SAAyB;IAE1C,OAAO,CAAC,aAAa,CAAO;IAC5B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,gBAAgB,CAA4B;IACpD,OAAO,CAAC,sBAAsB,CAAkB;IAChD,OAAO,CAAC,uBAAuB,CAAa;IAC5C,OAAO,CAAC,WAAW,CAAmD;IAEtE;;;;;;;;OAQG;IACH,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED,IAAI,YAAY,IAJQ,MAAM,CAM7B;IAED;;;;;;;;;;OAUG;IACH,IAAI,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAEvC;IAED,IAAI,sBAAsB,IAJQ,MAAM,CAMvC;IAED;;OAEG;IACH,UAAU;IAgBV;;;;OAIG;IACH,eAAe;IAiBf;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,kBAAkB;IAazC;;OAEG;IACH,MAAM;IAmCN;;;;OAIG;IACH,mBAAmB,CAAC,UAAU,EAAE,GAAG;IAcnC;;;OAGG;IACH,oBAAoB,CAAC,UAAU,EAAE,GAAG;IAiBpC;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,wBAAwB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM;IAW/E;;;;OAIG;IACH,cAAc,CAAC,UAAU,EAAE,GAAG;IAI9B;;;;OAIG;IACH,sBAAsB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG;IASlD,mBAAmB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO;IAKrD,0BAA0B;IAK1B,YAAY,CAAC,WAAW,EAAE,GAAG;IAK7B,aAAa,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM;IAI7C;;;OAGG;IACH,0BAA0B,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IA6B1E;;;;;OAKG;IACH,uBAAuB,CAAC,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAMvD;;;;OAIG;IACH,iCAAiC,CAAC,UAAU,EAAE,GAAG;IAiDjD;;;;OAIG;IACH,oBAAoB,CAAC,MAAM,EAAE,GAAG;IAUhC;;;OAGG;IACH,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAuB,EAAE,WAAW,SAAI,EAAE,IAAI,CAAC,EAAE,kBAAkB;CAyD7G"}
|
|
@@ -161,6 +161,15 @@ export class TfAnnotationManager extends PcAnnotationManager {
|
|
|
161
161
|
_registerAnnotation(annotation) {
|
|
162
162
|
super._registerAnnotation(annotation);
|
|
163
163
|
this._applyAnnotationIcon(annotation);
|
|
164
|
+
|
|
165
|
+
// Keep the hotspot mesh hidden until the first scale pass clamps it. The annotation entity
|
|
166
|
+
// starts at unit local scale, so under a scaled content-root the plane would render many world
|
|
167
|
+
// units across for the frames before _updateAnnotationRotationAndScale runs.
|
|
168
|
+
const resources = this._annotationResources.get(annotation);
|
|
169
|
+
if (resources) {
|
|
170
|
+
resources.baseEntity.enabled = false;
|
|
171
|
+
resources.overlayEntity.enabled = false;
|
|
172
|
+
}
|
|
164
173
|
}
|
|
165
174
|
|
|
166
175
|
/**
|
|
@@ -269,6 +278,18 @@ export class TfAnnotationManager extends PcAnnotationManager {
|
|
|
269
278
|
}
|
|
270
279
|
}
|
|
271
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Override to also disable the hotspot mesh (not just the DOM) when the annotation is behind the
|
|
283
|
+
* camera. The base implementation leaves the mesh enabled, so a not-yet-scaled plane under a
|
|
284
|
+
* scaled content-root would stay huge in view until the camera moves the anchor in front.
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
_hideAnnotationElements(annotation, resources) {
|
|
288
|
+
super._hideAnnotationElements(annotation, resources);
|
|
289
|
+
resources.baseEntity.enabled = false;
|
|
290
|
+
resources.overlayEntity.enabled = false;
|
|
291
|
+
}
|
|
292
|
+
|
|
272
293
|
/**
|
|
273
294
|
* Override the scale update to clamp the world size to a maximum value.
|
|
274
295
|
* Also scales the hotspot DOM element to match.
|
|
@@ -305,6 +326,12 @@ export class TfAnnotationManager extends PcAnnotationManager {
|
|
|
305
326
|
// size, so it uses the true _hotspotSize and the clamp ratio only (never the
|
|
306
327
|
// parent scale, which the DOM overlay does not inherit).
|
|
307
328
|
const resources = this._annotationResources.get(annotation);
|
|
329
|
+
if (resources) {
|
|
330
|
+
// This branch only runs for annotations in front of the camera, and the scale above is now
|
|
331
|
+
// clamped, so it is safe to reveal the mesh (hidden on register / when behind the camera).
|
|
332
|
+
resources.baseEntity.enabled = true;
|
|
333
|
+
resources.overlayEntity.enabled = true;
|
|
334
|
+
}
|
|
308
335
|
if (resources && resources.hotspotDom) {
|
|
309
336
|
const clampRatio = clampedScale / unclampedScale;
|
|
310
337
|
const baseSize = this._hotspotSize + 5; // Match the +5 from the stylesheet
|
package/hooks/useXr.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type XrType = 'VR' | 'AR';
|
|
2
|
+
interface UseXrOptions {
|
|
3
|
+
onError?: (error: Error) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare const useXr: ({ onError }?: UseXrOptions) => {
|
|
6
|
+
isXrAvailable: (type: XrType) => boolean;
|
|
7
|
+
startXr: (type: XrType) => void;
|
|
8
|
+
endXr: () => void;
|
|
9
|
+
isCurrentlyInXr: boolean;
|
|
10
|
+
};
|
|
11
|
+
export default useXr;
|
|
12
|
+
//# sourceMappingURL=useXr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useXr.d.ts","sourceRoot":"","sources":["../../src/hooks/useXr.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjC,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAOD,eAAO,MAAM,KAAK,GAAI,cAAa,YAAiB;0BA0CT,MAAM;oBAGtC,MAAM;;;CAsBhB,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/hooks/useXr.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useApp } from '@playcanvas/react/hooks';
|
|
3
|
+
import { XRSPACE_LOCALFLOOR, XRTYPE_AR, XRTYPE_VR } from 'playcanvas';
|
|
4
|
+
const XR_TYPES = {
|
|
5
|
+
VR: XRTYPE_VR,
|
|
6
|
+
AR: XRTYPE_AR
|
|
7
|
+
};
|
|
8
|
+
export const useXr = ({
|
|
9
|
+
onError
|
|
10
|
+
} = {}) => {
|
|
11
|
+
const app = useApp();
|
|
12
|
+
const [available, setAvailable] = useState({
|
|
13
|
+
VR: false,
|
|
14
|
+
AR: false
|
|
15
|
+
});
|
|
16
|
+
const [isCurrentlyInXr, setIsCurrentlyInXr] = useState(app.xr?.active ?? false);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
// We subscribe to the manager directly because `app` doesn't update when XR's availability changes.
|
|
19
|
+
const handleAvailable = (type, isAvailable) => {
|
|
20
|
+
if (type === XRTYPE_VR) {
|
|
21
|
+
setAvailable(prev => ({
|
|
22
|
+
...prev,
|
|
23
|
+
VR: isAvailable
|
|
24
|
+
}));
|
|
25
|
+
} else if (type === XRTYPE_AR) {
|
|
26
|
+
setAvailable(prev => ({
|
|
27
|
+
...prev,
|
|
28
|
+
AR: isAvailable
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
setAvailable({
|
|
33
|
+
VR: app.xr?.isAvailable(XRTYPE_VR) ?? false,
|
|
34
|
+
AR: app.xr?.isAvailable(XRTYPE_AR) ?? false
|
|
35
|
+
});
|
|
36
|
+
app.xr?.on('available', handleAvailable);
|
|
37
|
+
return () => {
|
|
38
|
+
app.xr?.off('available', handleAvailable);
|
|
39
|
+
};
|
|
40
|
+
}, [app]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const handleStart = () => setIsCurrentlyInXr(true);
|
|
43
|
+
const handleEnd = () => setIsCurrentlyInXr(false);
|
|
44
|
+
setIsCurrentlyInXr(app.xr?.active ?? false);
|
|
45
|
+
app.xr?.on('start', handleStart);
|
|
46
|
+
app.xr?.on('end', handleEnd);
|
|
47
|
+
return () => {
|
|
48
|
+
app.xr?.off('start', handleStart);
|
|
49
|
+
app.xr?.off('end', handleEnd);
|
|
50
|
+
};
|
|
51
|
+
}, [app]);
|
|
52
|
+
const isXrAvailable = useCallback(type => available[type], [available]);
|
|
53
|
+
const startXr = useCallback(type => {
|
|
54
|
+
const camera = app.root.findComponent('camera');
|
|
55
|
+
app.xr?.start(camera, XR_TYPES[type], XRSPACE_LOCALFLOOR, {
|
|
56
|
+
callback: err => {
|
|
57
|
+
if (err) {
|
|
58
|
+
onError?.(err);
|
|
59
|
+
app.xr?.end();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}, [app, onError]);
|
|
64
|
+
const endXr = useCallback(() => {
|
|
65
|
+
app.xr?.end();
|
|
66
|
+
}, [app]);
|
|
67
|
+
return useMemo(() => ({
|
|
68
|
+
isXrAvailable,
|
|
69
|
+
startXr,
|
|
70
|
+
endXr,
|
|
71
|
+
isCurrentlyInXr
|
|
72
|
+
}), [isXrAvailable, startXr, endXr, isCurrentlyInXr]);
|
|
73
|
+
};
|
|
74
|
+
export default useXr;
|