|
@@ -36,6 +36,10 @@ import "../../Meshes/Builders/cylinderBuilder";
|
|
|
import "../../Gamepads/gamepadSceneComponent";
|
|
|
import "../../Animations/animatable";
|
|
|
import { Axis } from '../../Maths/math.axis';
|
|
|
+import { WebXRSessionManager } from '../XR/webXRSessionManager';
|
|
|
+import { WebXRDefaultExperience } from '../XR/webXRDefaultExperience';
|
|
|
+import { WebXRState } from '../XR/webXRTypes';
|
|
|
+import { WebXRControllerTeleportation } from '../XR/webXRControllerTeleportation';
|
|
|
|
|
|
/**
|
|
|
* Options to modify the vr teleportation behavior.
|
|
@@ -87,6 +91,8 @@ export interface VRExperienceHelperOptions extends WebVROptions {
|
|
|
* Distortion metrics for the fallback vrDeviceOrientationCamera (default: VRCameraMetrics.Default)
|
|
|
*/
|
|
|
vrDeviceOrientationCameraMetrics?: VRCameraMetrics;
|
|
|
+
|
|
|
+ useXR?: boolean;
|
|
|
}
|
|
|
|
|
|
class VRExperienceHelperGazer implements IDisposable {
|
|
@@ -672,6 +678,19 @@ export class VRExperienceHelper {
|
|
|
*/
|
|
|
public requestPointerLockOnFullScreen = true;
|
|
|
|
|
|
+ // XR
|
|
|
+
|
|
|
+ /**
|
|
|
+ * If asking to force XR, this will be populated with the default xr experience
|
|
|
+ */
|
|
|
+ public xr: WebXRDefaultExperience;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Was the XR test done already. If this is true AND this.xr exists, xr is initialized.
|
|
|
+ * If this is true and no this.xr, xr exists but is not supported, using WebVR.
|
|
|
+ */
|
|
|
+ public xrTestDone: boolean = false;
|
|
|
+
|
|
|
/**
|
|
|
* Instantiates a VRExperienceHelper.
|
|
|
* Helps to quickly add VR support to an existing scene.
|
|
@@ -750,6 +769,98 @@ export class VRExperienceHelper {
|
|
|
this._existingCamera = this._scene.activeCamera;
|
|
|
}
|
|
|
|
|
|
+ if (this.webVROptions.useXR && (navigator as any).xr) {
|
|
|
+ // force-check XR session support
|
|
|
+ WebXRSessionManager.IsSessionSupported("immersive-vr").then((supported) => {
|
|
|
+ if (supported) {
|
|
|
+ Logger.Log("Using WebXR. It is recommended to use the WebXRDefaultExperience directly");
|
|
|
+ // it is possible to use XR, let's do it!
|
|
|
+ scene.createDefaultXRExperienceAsync({
|
|
|
+ floorMeshes: webVROptions.floorMeshes || []
|
|
|
+ }).then((xr) => {
|
|
|
+ this.xr = xr;
|
|
|
+ // connect observables
|
|
|
+ this.xrTestDone = true;
|
|
|
+
|
|
|
+ this._cameraGazer = new VRExperienceHelperCameraGazer(() => { return this.xr.baseExperience.camera; }, scene);
|
|
|
+
|
|
|
+ this.xr.baseExperience.onStateChangedObservable.add((state) => {
|
|
|
+ // support for entering / exiting
|
|
|
+ switch (state) {
|
|
|
+ case WebXRState.ENTERING_XR:
|
|
|
+ this.onEnteringVRObservable.notifyObservers(this);
|
|
|
+ if (this._interactionsEnabled) {
|
|
|
+ this._scene.registerBeforeRender(this.beforeRender);
|
|
|
+ }
|
|
|
+ if (this._displayLaserPointer) {
|
|
|
+ [this._leftController, this._rightController].forEach((controller) => {
|
|
|
+ if (controller) {
|
|
|
+ controller._activatePointer();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case WebXRState.EXITING_XR:
|
|
|
+ this.onExitingVRObservable.notifyObservers(this);
|
|
|
+ if (this._interactionsEnabled) {
|
|
|
+ this._scene.unregisterBeforeRender(this.beforeRender);
|
|
|
+ this._cameraGazer._gazeTracker.isVisible = false;
|
|
|
+ if (this._leftController) {
|
|
|
+ this._leftController._gazeTracker.isVisible = false;
|
|
|
+ }
|
|
|
+ if (this._rightController) {
|
|
|
+ this._rightController._gazeTracker.isVisible = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // resize to update width and height when exiting vr exits fullscreen
|
|
|
+ this._scene.getEngine().resize();
|
|
|
+
|
|
|
+ [this._leftController, this._rightController].forEach((controller) => {
|
|
|
+ if (controller) {
|
|
|
+ controller._deactivatePointer();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ break;
|
|
|
+ case WebXRState.IN_XR:
|
|
|
+ this._hasEnteredVR = true;
|
|
|
+ break;
|
|
|
+ case WebXRState.NOT_IN_XR:
|
|
|
+ this._hasEnteredVR = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.xr.input.onControllerAddedObservable.add((controller) => {
|
|
|
+ var webVRController = controller.gamepadController;
|
|
|
+ if (webVRController) {
|
|
|
+ var localController = new VRExperienceHelperControllerGazer(webVRController, this._scene, this._cameraGazer._gazeTracker);
|
|
|
+
|
|
|
+ if (controller.inputSource.handedness === "right" || (this._leftController && this._leftController.webVRController != webVRController)) {
|
|
|
+ this._rightController = localController;
|
|
|
+ } else {
|
|
|
+ this._leftController = localController;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._tryEnableInteractionOnController(localController);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // XR not supported (thou exists), continue WebVR init
|
|
|
+ this.completeVRInit(scene, webVROptions);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // no XR, continue init synchronous
|
|
|
+ this.completeVRInit(scene, webVROptions);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private completeVRInit(scene: Scene,
|
|
|
+ webVROptions: VRExperienceHelperOptions): void {
|
|
|
+ this.xrTestDone = true;
|
|
|
// Create VR cameras
|
|
|
if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
|
|
|
if (webVROptions.useMultiview) {
|
|
@@ -945,7 +1056,7 @@ export class VRExperienceHelper {
|
|
|
* Gets a value indicating if we are currently in VR mode.
|
|
|
*/
|
|
|
public get isInVRMode(): boolean {
|
|
|
- return this._webVRpresenting || this._fullscreenVRpresenting;
|
|
|
+ return (this.xr && this.webVROptions.useXR && this.xr.baseExperience.state === WebXRState.IN_XR) || (this._webVRpresenting || this._fullscreenVRpresenting);
|
|
|
}
|
|
|
|
|
|
private onVrDisplayPresentChange() {
|
|
@@ -1007,6 +1118,10 @@ export class VRExperienceHelper {
|
|
|
* Otherwise, will use the fullscreen API.
|
|
|
*/
|
|
|
public enterVR() {
|
|
|
+ if (this.xr) {
|
|
|
+ this.xr.baseExperience.enterXRAsync("immersive-vr", "local-floor", this.xr.renderTarget);
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (this.onEnteringVRObservable) {
|
|
|
try {
|
|
|
this.onEnteringVRObservable.notifyObservers(this);
|
|
@@ -1099,6 +1214,10 @@ export class VRExperienceHelper {
|
|
|
* Attempt to exit VR, or fullscreen.
|
|
|
*/
|
|
|
public exitVR() {
|
|
|
+ if (this.xr) {
|
|
|
+ this.xr.baseExperience.exitXRAsync();
|
|
|
+ return;
|
|
|
+ }
|
|
|
if (this._hasEnteredVR) {
|
|
|
if (this.onExitingVRObservable) {
|
|
|
try {
|
|
@@ -1319,6 +1438,42 @@ export class VRExperienceHelper {
|
|
|
|
|
|
this.enableInteractions();
|
|
|
|
|
|
+ if (this.webVROptions.useXR && (vrTeleportationOptions.floorMeshes || vrTeleportationOptions.floorMeshName)) {
|
|
|
+ const floorMeshes: AbstractMesh[] = vrTeleportationOptions.floorMeshes || [];
|
|
|
+ if (!floorMeshes.length) {
|
|
|
+ const floorMesh = this._scene.getMeshByName(vrTeleportationOptions.floorMeshName!);
|
|
|
+ if (floorMesh) {
|
|
|
+ floorMeshes.push(floorMesh);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (this.xr) {
|
|
|
+ this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, floorMeshes);
|
|
|
+ return;
|
|
|
+ } else if (!this.xrTestDone) {
|
|
|
+ const waitForXr = () => {
|
|
|
+ if (this.xrTestDone) {
|
|
|
+ this._scene.unregisterBeforeRender(waitForXr);
|
|
|
+ if (this.xr) {
|
|
|
+ this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, floorMeshes);
|
|
|
+ } else {
|
|
|
+ this.enableTeleportation(vrTeleportationOptions);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ this._scene.registerBeforeRender(waitForXr);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.xr && vrTeleportationOptions.floorMeshes) {
|
|
|
+ this.xr.teleportation = new WebXRControllerTeleportation(this.xr.input, vrTeleportationOptions.floorMeshes);
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ if (this.webVROptions.useXR && !this.xrTestDone) {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (vrTeleportationOptions.floorMeshName) {
|
|
|
this._floorMeshName = vrTeleportationOptions.floorMeshName;
|
|
|
}
|
|
@@ -2148,6 +2303,10 @@ export class VRExperienceHelper {
|
|
|
this._teleportationTarget.dispose();
|
|
|
}
|
|
|
|
|
|
+ if (this.xr) {
|
|
|
+ this.xr.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
this._floorMeshesCollection = [];
|
|
|
|
|
|
document.removeEventListener("keydown", this._onKeyDown);
|