Browse Source

Merge pull request #7535 from RaananW/physics-controller-feature

New XR Feature - physics impostors for controllers
David Catuhe 5 years ago
parent
commit
992f9c08c9

+ 1 - 0
dist/preview release/what's new.md

@@ -204,6 +204,7 @@
 - Selection has gaze mode (which can be forced) and touch-screen support ([#7395](https://github.com/BabylonJS/Babylon.js/issues/7395)) ([RaananW](https://github.com/RaananW/))
 - Selection has gaze mode (which can be forced) and touch-screen support ([#7395](https://github.com/BabylonJS/Babylon.js/issues/7395)) ([RaananW](https://github.com/RaananW/))
 - Laser pointers can be excluded from lighting influence so that they are always visible in both WebXR and WebVR ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - Laser pointers can be excluded from lighting influence so that they are always visible in both WebXR and WebVR ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - Full support for the online motion controller repository ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
 - Full support for the online motion controller repository ([#7323](https://github.com/BabylonJS/Babylon.js/issues/7323)) ([RaananW](https://github.com/RaananW/))
+- New WebXR feature - Controller physics impostor ([RaananW](https://github.com/RaananW/))
 
 
 ### Ray
 ### Ray
 
 

+ 283 - 0
src/Cameras/XR/features/WebXRControllerPhysics.ts

@@ -0,0 +1,283 @@
+import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
+import { Vector3, Quaternion } from "../../../Maths/math.vector";
+import { WebXRController } from "../webXRController";
+import { PhysicsImpostor } from "../../../Physics/physicsImpostor";
+import { WebXRInput } from "../webXRInput";
+import { WebXRSessionManager } from "../webXRSessionManager";
+import { AbstractMesh } from "../../../Meshes/abstractMesh";
+import { SphereBuilder } from "../../../Meshes/Builders/sphereBuilder";
+import { WebXRFeatureName, WebXRFeaturesManager } from "../webXRFeaturesManager";
+import { Logger } from '../../../Misc/logger';
+
+/**
+ * Options for the controller physics feature
+ */
+export class IWebXRControllerPhysicsOptions {
+    /**
+     * the xr input to use with this pointer selection
+     */
+    xrInput: WebXRInput;
+    /**
+     * The physics properties of the future impostors
+     */
+    physicsProperties?: {
+        /**
+         * If set to true, a mesh impostor will be created when the controller mesh was loaded
+         * Note that this requires a physics engine that supports mesh impostors!
+         */
+        useControllerMesh?: boolean;
+        /**
+         * The type of impostor to create. Default is sphere
+         */
+        impostorType?: number;
+        /**
+         * the size of the impostor
+         */
+        impostorSize?: number | { width: number, height: number, depth: number };
+        /**
+         * Friction definitions
+         */
+        friction?: number;
+        /**
+         * Restitution
+         */
+        restitution?: number;
+    };
+}
+
+/**
+ * Add physics impostor to your webxr controllers,
+ * including naive calculation of their linear and angular velocity
+ */
+export class WebXRControllerPhysics extends WebXRAbstractFeature {
+
+    /**
+     * The module's name
+     */
+    public static readonly Name = WebXRFeatureName.PHYSICS_CONTROLLERS;
+    /**
+     * The (Babylon) version of this module.
+     * This is an integer representing the implementation version.
+     * This number does not correspond to the webxr specs version
+     */
+    public static readonly Version = 1;
+
+    private _lastTimestamp: number = 0;
+    private _delta: number = 0;
+
+    private _controllers: {
+        [id: string]: {
+            xrController: WebXRController;
+            impostorMesh?: AbstractMesh,
+            impostor: PhysicsImpostor
+            oldPos?: Vector3;
+            oldRotation?: Quaternion;
+        }
+    } = {};
+
+    private _tmpVector: Vector3 = new Vector3();
+    private _tmpQuaternion: Quaternion = new Quaternion();
+
+    /**
+     * Construct a new Controller Physics Feature
+     * @param _xrSessionManager the corresponding xr session manager
+     * @param _options options to create this feature with
+     */
+    constructor(_xrSessionManager: WebXRSessionManager, private readonly _options: IWebXRControllerPhysicsOptions) {
+        super(_xrSessionManager);
+        if (!this._options.physicsProperties) {
+            this._options.physicsProperties = {
+            };
+        }
+    }
+
+    /**
+     * Update the physics properties provided in the constructor
+     * @param newProperties the new properties object
+     */
+    public setPhysicsProperties(newProperties: {
+        impostorType?: number,
+        impostorSize?: number | { width: number, height: number, depth: number },
+        friction?: number,
+        restitution?: number
+    }) {
+        this._options.physicsProperties = {
+            ...this._options.physicsProperties,
+            ...newProperties
+        };
+    }
+
+    /**
+     * attach this feature
+     * Will usually be called by the features manager
+     *
+     * @returns true if successful.
+     */
+    attach(): boolean {
+        if (!super.attach()) {
+            return false;
+        }
+
+        if (!this._options.xrInput) {
+            return true;
+        }
+
+        this._options.xrInput.controllers.forEach(this._attachController);
+        this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
+        this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
+            // REMOVE the controller
+            this._detachController(controller.uniqueId);
+        });
+
+        return true;
+    }
+
+    /**
+     * detach this feature.
+     * Will usually be called by the features manager
+     *
+     * @returns true if successful.
+     */
+    detach(): boolean {
+        if (!super.detach()) {
+            return false;
+        }
+
+        Object.keys(this._controllers).forEach((controllerId) => {
+            this._detachController(controllerId);
+        });
+
+        return true;
+    }
+
+    /**
+     * Manually add a controller (if no xrInput was provided or physics engine was not enabled)
+     * @param xrController the controller to add
+     */
+    public addController(xrController: WebXRController) {
+        this._attachController(xrController);
+    }
+
+    private _debugMode = false;
+
+    /**
+     * @hidden
+     * enable debugging - will show console outputs and the impostor mesh
+     */
+    public _enablePhysicsDebug() {
+        this._debugMode = true;
+        Object.keys(this._controllers).forEach((controllerId) => {
+            const controllerData = this._controllers[controllerId];
+            if (controllerData.impostorMesh) {
+                controllerData.impostorMesh.isVisible = true;
+            }
+        });
+    }
+
+    private _attachController = (xrController: WebXRController
+    ) => {
+        if (this._controllers[xrController.uniqueId]) {
+            // already attached
+            return;
+        }
+        if (!this._xrSessionManager.scene.isPhysicsEnabled()) {
+            Logger.Warn("physics engine not enabled, skipped. Please add this controller manually.");
+        }
+        if (this._options.physicsProperties!.useControllerMesh) {
+            xrController.onMotionControllerProfileLoaded.addOnce((motionController) => {
+                motionController.onModelLoadedObservable.addOnce(() => {
+                    const impostor = new PhysicsImpostor(motionController.rootMesh!, PhysicsImpostor.MeshImpostor, {
+                        mass: 0,
+                        ...this._options.physicsProperties
+                    });
+                    const controllerMesh = xrController.grip || xrController.pointer;
+                    this._controllers[xrController.uniqueId] = {
+                        xrController,
+                        impostor,
+                        oldPos: controllerMesh.position.clone(),
+                        oldRotation: controllerMesh.rotationQuaternion!.clone()
+                    };
+                });
+            });
+        } else {
+            const impostorType: number = this._options.physicsProperties!.impostorType || PhysicsImpostor.SphereImpostor;
+            const impostorSize: number | { width: number, height: number, depth: number } = this._options.physicsProperties!.impostorSize || 0.08;
+            const impostorMesh = SphereBuilder.CreateSphere('impostor-mesh-' + xrController.uniqueId, {
+                diameterX: typeof impostorSize === 'number' ? impostorSize : impostorSize.width,
+                diameterY: typeof impostorSize === 'number' ? impostorSize : impostorSize.height,
+                diameterZ: typeof impostorSize === 'number' ? impostorSize : impostorSize.depth
+            });
+            impostorMesh.isVisible = this._debugMode;
+            impostorMesh.isPickable = false;
+            impostorMesh.rotationQuaternion = new Quaternion();
+            const controllerMesh = xrController.grip || xrController.pointer;
+            impostorMesh.position.copyFrom(controllerMesh.position);
+            impostorMesh.rotationQuaternion!.copyFrom(controllerMesh.rotationQuaternion!);
+            const impostor = new PhysicsImpostor(impostorMesh, impostorType, {
+                mass: 0,
+                ...this._options.physicsProperties
+            });
+            this._controllers[xrController.uniqueId] = {
+                xrController,
+                impostor,
+                impostorMesh
+            };
+        }
+    }
+
+    private _detachController(xrControllerUniqueId: string) {
+        const controllerData = this._controllers[xrControllerUniqueId];
+        if (!controllerData) { return; }
+        if (controllerData.impostorMesh) {
+            controllerData.impostorMesh.dispose();
+        }
+        // remove from the map
+        delete this._controllers[xrControllerUniqueId];
+    }
+
+    protected _onXRFrame(_xrFrame: any): void {
+        this._delta = (this._xrSessionManager.currentTimestamp - this._lastTimestamp);
+        this._lastTimestamp = this._xrSessionManager.currentTimestamp;
+        Object.keys(this._controllers).forEach((controllerId) => {
+            const controllerData = this._controllers[controllerId];
+            const controllerMesh = controllerData.xrController.grip || controllerData.xrController.pointer;
+
+            const comparedPosition = controllerData.oldPos || controllerData.impostorMesh!.position;
+            const comparedQuaternion = controllerData.oldRotation || controllerData.impostorMesh!.rotationQuaternion!;
+
+            if (!controllerMesh.position.equalsWithEpsilon(comparedPosition)) {
+                controllerMesh.position.subtractToRef(comparedPosition, this._tmpVector);
+                this._tmpVector.scaleInPlace(this._delta);
+                controllerData.impostor.setLinearVelocity(this._tmpVector);
+                if (this._debugMode) {
+                    console.log(this._tmpVector, 'linear');
+                }
+            }
+            if (!comparedQuaternion.equalsWithEpsilon(controllerMesh.rotationQuaternion!)) {
+                // roughly based on this - https://www.gamedev.net/forums/topic/347752-quaternion-and-angular-velocity/
+                comparedQuaternion.conjugateInPlace().multiplyToRef(controllerMesh.rotationQuaternion!, this._tmpQuaternion);
+                const len = Math.sqrt(this._tmpQuaternion.x * this._tmpQuaternion.x + this._tmpQuaternion.y * this._tmpQuaternion.y + this._tmpQuaternion.z * this._tmpQuaternion.z);
+                this._tmpVector.set(this._tmpQuaternion.x, this._tmpQuaternion.y, this._tmpQuaternion.z);
+                // define a better epsilon
+                if (len < 0.001) {
+                    this._tmpVector.scaleInPlace(2);
+                } else {
+                    const angle = 2 * Math.atan2(len, this._tmpQuaternion.w);
+                    this._tmpVector.scaleInPlace((angle / (len * this._delta)));
+                }
+                controllerData.impostor.setAngularVelocity(this._tmpVector);
+                if (this._debugMode) {
+                    console.log(this._tmpVector, this._tmpQuaternion, 'angular');
+                }
+            }
+            comparedPosition.copyFrom(controllerMesh.position);
+            comparedQuaternion.copyFrom(controllerMesh.rotationQuaternion!);
+        });
+    }
+
+}
+
+//register the plugin
+WebXRFeaturesManager.AddWebXRFeature(WebXRControllerPhysics.Name, (xrSessionManager, options) => {
+    return () => new WebXRControllerPhysics(xrSessionManager, options);
+}, WebXRControllerPhysics.Version, true);

+ 2 - 1
src/Cameras/XR/features/WebXRControllerPointerSelection.ts

@@ -471,7 +471,8 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
 
 
     private _updatePointerDistance(_laserPointer: AbstractMesh, distance: number = 100) {
     private _updatePointerDistance(_laserPointer: AbstractMesh, distance: number = 100) {
         _laserPointer.scaling.y = distance;
         _laserPointer.scaling.y = distance;
-        _laserPointer.position.z = distance / 2;
+        // a bit of distance from the controller
+        _laserPointer.position.z = (distance / 2) + 0.05;
     }
     }
 }
 }
 
 

+ 2 - 1
src/Cameras/XR/features/index.ts

@@ -3,4 +3,5 @@ export * from "./WebXRAnchorSystem";
 export * from "./WebXRPlaneDetector";
 export * from "./WebXRPlaneDetector";
 export * from "./WebXRBackgroundRemover";
 export * from "./WebXRBackgroundRemover";
 export * from "./WebXRControllerTeleportation";
 export * from "./WebXRControllerTeleportation";
-export * from "./WebXRControllerPointerSelection";
+export * from "./WebXRControllerPointerSelection";
+export * from './WebXRControllerPhysics';

+ 5 - 0
src/Cameras/XR/webXRFeaturesManager.ts

@@ -58,6 +58,11 @@ export class WebXRFeatureName {
      * The name of the plane detection feature
      * The name of the plane detection feature
      */
      */
     public static PLANE_DETECTION = "xr-plane-detection";
     public static PLANE_DETECTION = "xr-plane-detection";
+
+    /**
+     * physics impostors for xr controllers feature
+     */
+    public static PHYSICS_CONTROLLERS = "xr-physics-controller";
 }
 }
 
 
 /**
 /**