|
@@ -1,95 +1,8 @@
|
|
import { Nullable } from "../../types";
|
|
import { Nullable } from "../../types";
|
|
import { Observer, Observable } from "../../Misc/observable";
|
|
import { Observer, Observable } from "../../Misc/observable";
|
|
-import { IDisposable, Scene } from "../../scene";
|
|
|
|
-import { AbstractMesh } from "../../Meshes/abstractMesh";
|
|
|
|
-import { WebXRExperienceHelper } from "./webXRExperienceHelper";
|
|
|
|
-import { Matrix, Quaternion } from '../../Maths/math';
|
|
|
|
-/**
|
|
|
|
- * Represents an XR input
|
|
|
|
- */
|
|
|
|
-export class WebXRController {
|
|
|
|
- /**
|
|
|
|
- * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
|
|
|
|
- */
|
|
|
|
- public grip?: AbstractMesh;
|
|
|
|
- /**
|
|
|
|
- * Pointer which can be used to select objects or attach a visible laser to
|
|
|
|
- */
|
|
|
|
- public pointer: AbstractMesh;
|
|
|
|
-
|
|
|
|
- private _tmpMatrix = new Matrix();
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Creates the controller
|
|
|
|
- * @see https://doc.babylonjs.com/how_to/webxr
|
|
|
|
- * @param scene the scene which the controller should be associated to
|
|
|
|
- * @param inputSource the underlying input source for the controller
|
|
|
|
- * @param parentContainer parent that the controller meshes should be children of
|
|
|
|
- */
|
|
|
|
- constructor(
|
|
|
|
- private scene: Scene,
|
|
|
|
- /** The underlying input source for the controller */
|
|
|
|
- public inputSource: XRInputSource,
|
|
|
|
- private parentContainer: Nullable<AbstractMesh> = null)
|
|
|
|
- {
|
|
|
|
- this.pointer = new AbstractMesh("controllerPointer", scene);
|
|
|
|
- if (parentContainer) {
|
|
|
|
- parentContainer.addChild(this.pointer);
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Updates the controller pose based on the given XRFrame
|
|
|
|
- * @param xrFrame xr frame to update the pose with
|
|
|
|
- * @param referenceSpace reference space to use
|
|
|
|
- */
|
|
|
|
- public updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace) {
|
|
|
|
- var pose = xrFrame.getPose(this.inputSource.targetRaySpace, referenceSpace);
|
|
|
|
- if (pose) {
|
|
|
|
- Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
|
|
|
|
- if (!this.pointer.getScene().useRightHandedSystem) {
|
|
|
|
- this._tmpMatrix.toggleModelMatrixHandInPlace();
|
|
|
|
- }
|
|
|
|
- if (!this.pointer.rotationQuaternion) {
|
|
|
|
- this.pointer.rotationQuaternion = new Quaternion();
|
|
|
|
- }
|
|
|
|
- this._tmpMatrix.decompose(this.pointer.scaling, this.pointer.rotationQuaternion!, this.pointer.position);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (this.inputSource.gripSpace) {
|
|
|
|
- if (!this.grip) {
|
|
|
|
- this.grip = new AbstractMesh("controllerGrip", this.scene);
|
|
|
|
- if (this.parentContainer) {
|
|
|
|
- this.parentContainer.addChild(this.grip);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
|
|
|
|
- if (pose) {
|
|
|
|
- Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
|
|
|
|
- if (!this.grip.getScene().useRightHandedSystem) {
|
|
|
|
- this._tmpMatrix.toggleModelMatrixHandInPlace();
|
|
|
|
- }
|
|
|
|
- if (!this.grip.rotationQuaternion) {
|
|
|
|
- this.grip.rotationQuaternion = new Quaternion();
|
|
|
|
- }
|
|
|
|
- this._tmpMatrix.decompose(this.grip.scaling, this.grip.rotationQuaternion!, this.grip.position);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Disposes of the object
|
|
|
|
- */
|
|
|
|
- dispose() {
|
|
|
|
- if (this.grip) {
|
|
|
|
- this.grip.dispose();
|
|
|
|
- }
|
|
|
|
- this.pointer.dispose();
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
+import { IDisposable } from "../../scene";
|
|
|
|
+import { WebXRExperienceHelper, WebXRState } from "./webXRExperienceHelper";
|
|
|
|
+import { WebXRController } from './webXRController';
|
|
|
|
|
|
/**
|
|
/**
|
|
* XR input used to track XR inputs such as controllers/rays
|
|
* XR input used to track XR inputs such as controllers/rays
|
|
@@ -100,6 +13,7 @@ export class WebXRInput implements IDisposable {
|
|
*/
|
|
*/
|
|
public controllers: Array<WebXRController> = [];
|
|
public controllers: Array<WebXRController> = [];
|
|
private _frameObserver: Nullable<Observer<any>>;
|
|
private _frameObserver: Nullable<Observer<any>>;
|
|
|
|
+ private _stateObserver: Nullable<Observer<any>>;
|
|
/**
|
|
/**
|
|
* Event when a controller has been connected/added
|
|
* Event when a controller has been connected/added
|
|
*/
|
|
*/
|
|
@@ -111,23 +25,35 @@ export class WebXRInput implements IDisposable {
|
|
|
|
|
|
/**
|
|
/**
|
|
* Initializes the WebXRInput
|
|
* Initializes the WebXRInput
|
|
- * @param helper experience helper which the input should be created for
|
|
|
|
|
|
+ * @param baseExperience experience helper which the input should be created for
|
|
*/
|
|
*/
|
|
- public constructor(private helper: WebXRExperienceHelper) {
|
|
|
|
- this._frameObserver = helper.sessionManager.onXRFrameObservable.add(() => {
|
|
|
|
- if (!helper.sessionManager.currentFrame) {
|
|
|
|
|
|
+ public constructor(
|
|
|
|
+ /**
|
|
|
|
+ * Base experience the input listens to
|
|
|
|
+ */
|
|
|
|
+ public baseExperience: WebXRExperienceHelper
|
|
|
|
+ ) {
|
|
|
|
+ // Remove controllers when exiting XR
|
|
|
|
+ this._stateObserver = baseExperience.onStateChangedObservable.add((s) => {
|
|
|
|
+ if (s === WebXRState.NOT_IN_XR) {
|
|
|
|
+ this._addAndRemoveControllers([], this.controllers.map((c) => {return c.inputSource; }));
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this._frameObserver = baseExperience.sessionManager.onXRFrameObservable.add(() => {
|
|
|
|
+ if (!baseExperience.sessionManager.currentFrame) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
// Start listing to input add/remove event
|
|
// Start listing to input add/remove event
|
|
- if (this.controllers.length == 0 && helper.sessionManager.session.inputSources) {
|
|
|
|
- this._addAndRemoveControllers(helper.sessionManager.session.inputSources, []);
|
|
|
|
- helper.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
|
|
|
|
|
|
+ if (this.controllers.length == 0 && baseExperience.sessionManager.session.inputSources) {
|
|
|
|
+ this._addAndRemoveControllers(baseExperience.sessionManager.session.inputSources, []);
|
|
|
|
+ baseExperience.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
|
|
}
|
|
}
|
|
|
|
|
|
// Update controller pose info
|
|
// Update controller pose info
|
|
this.controllers.forEach((controller) => {
|
|
this.controllers.forEach((controller) => {
|
|
- controller.updateFromXRFrame(helper.sessionManager.currentFrame!, helper.sessionManager.referenceSpace);
|
|
|
|
|
|
+ controller.updateFromXRFrame(baseExperience.sessionManager.currentFrame!, baseExperience.sessionManager.referenceSpace);
|
|
});
|
|
});
|
|
|
|
|
|
});
|
|
});
|
|
@@ -139,18 +65,18 @@ export class WebXRInput implements IDisposable {
|
|
|
|
|
|
private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
|
|
private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
|
|
// Add controllers if they don't already exist
|
|
// Add controllers if they don't already exist
|
|
- var sources = this.controllers.map((c) => {return c.inputSource; });
|
|
|
|
- addInputs.forEach((input) => {
|
|
|
|
|
|
+ let sources = this.controllers.map((c) => {return c.inputSource; });
|
|
|
|
+ for (let input of addInputs) {
|
|
if (sources.indexOf(input) === -1) {
|
|
if (sources.indexOf(input) === -1) {
|
|
- var controller = new WebXRController(this.helper.camera._scene, input, this.helper.container);
|
|
|
|
|
|
+ let controller = new WebXRController(this.baseExperience.camera._scene, input, this.baseExperience.container);
|
|
this.controllers.push(controller);
|
|
this.controllers.push(controller);
|
|
this.onControllerAddedObservable.notifyObservers(controller);
|
|
this.onControllerAddedObservable.notifyObservers(controller);
|
|
}
|
|
}
|
|
- });
|
|
|
|
|
|
+ }
|
|
|
|
|
|
// Remove and dispose of controllers to be disposed
|
|
// Remove and dispose of controllers to be disposed
|
|
- var keepControllers: Array<WebXRController> = [];
|
|
|
|
- var removedControllers: Array<WebXRController> = [];
|
|
|
|
|
|
+ let keepControllers: Array<WebXRController> = [];
|
|
|
|
+ let removedControllers: Array<WebXRController> = [];
|
|
this.controllers.forEach((c) => {
|
|
this.controllers.forEach((c) => {
|
|
if (removeInputs.indexOf(c.inputSource) === -1) {
|
|
if (removeInputs.indexOf(c.inputSource) === -1) {
|
|
keepControllers.push(c);
|
|
keepControllers.push(c);
|
|
@@ -173,6 +99,7 @@ export class WebXRInput implements IDisposable {
|
|
this.controllers.forEach((c) => {
|
|
this.controllers.forEach((c) => {
|
|
c.dispose();
|
|
c.dispose();
|
|
});
|
|
});
|
|
- this.helper.sessionManager.onXRFrameObservable.remove(this._frameObserver);
|
|
|
|
|
|
+ this.baseExperience.sessionManager.onXRFrameObservable.remove(this._frameObserver);
|
|
|
|
+ this.baseExperience.onStateChangedObservable.remove(this._stateObserver);
|
|
}
|
|
}
|
|
}
|
|
}
|