Bladeren bron

controller progress

Trevor Baron 6 jaren geleden
bovenliggende
commit
300cc351de

+ 1 - 1
src/Cameras/XR/webXRExperienceHelper.ts

@@ -89,7 +89,7 @@ export class WebXRExperienceHelper implements IDisposable {
     private constructor(private scene: Scene) {
         this.camera = new WebXRCamera("", scene);
         this.sessionManager = new WebXRSessionManager(scene);
-        this.container = new AbstractMesh("", scene);
+        this.container = new AbstractMesh("WebXR Container", scene);
         this.camera.parent = this.container;
     }
 

+ 301 - 21
src/Cameras/XR/webXRInput.ts

@@ -3,7 +3,15 @@ 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';
+import { Matrix, Quaternion, Vector3, Color3, Axis } from '../../Maths/math';
+import { WindowsMotionController } from '../../Gamepads/Controllers/windowsMotionController';
+import { OculusTouchController } from '../../Gamepads/Controllers/oculusTouchController';
+import { Mesh } from '../../Meshes/mesh';
+import { Ray } from '../../Culling/ray';
+import { StandardMaterial } from '../../Materials/standardMaterial';
+import { DynamicTexture } from '../../Materials/Textures/dynamicTexture';
+import { EasingFunction, SineEase } from '../../Animations/easing';
+import { Animation } from '../../Animations/animation';
 /**
  * Represents an XR input
  */
@@ -18,6 +26,8 @@ export class WebXRController {
     public pointer: AbstractMesh;
 
     private _tmpMatrix = new Matrix();
+    private _tmpQuaternion = new Quaternion();
+    private _tmpVector = new Vector3();
 
     /**
      * Creates the controller
@@ -35,7 +45,13 @@ export class WebXRController {
         this.pointer = new AbstractMesh("controllerPointer", scene);
         if (parentContainer) {
             parentContainer.addChild(this.pointer);
+        }
 
+        if (this.inputSource.gripSpace) {
+            this.grip = new AbstractMesh("controllerGrip", this.scene);
+            if (this.parentContainer) {
+                this.parentContainer.addChild(this.grip);
+            }
         }
     }
 
@@ -57,14 +73,7 @@ export class WebXRController {
             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);
-                }
-            }
-
+        if (this.inputSource.gripSpace && this.grip) {
             var pose = xrFrame.getPose(this.inputSource.gripSpace, referenceSpace);
             if (pose) {
                 Matrix.FromFloat32ArrayToRefScaled(pose.transform.matrix, 0, 1, this._tmpMatrix);
@@ -77,7 +86,18 @@ export class WebXRController {
                 this._tmpMatrix.decompose(this.grip.scaling, this.grip.rotationQuaternion!, this.grip.position);
             }
         }
+    }
 
+    public getWorldPointerRayToRef(result:Ray){
+        // Force update to ensure picked point is synced with ray
+        var worldMatrix = this.pointer.computeWorldMatrix(true)
+        worldMatrix.decompose(undefined, this._tmpQuaternion, undefined)
+        this._tmpVector.set(0,0,1)
+        this._tmpVector.rotateByQuaternionToRef(this._tmpQuaternion, this._tmpVector)
+        result.origin = this.pointer.absolutePosition
+        result.direction.copyFrom(this._tmpVector)
+        result.length = 1000;
+        return result;
     }
 
     /**
@@ -111,23 +131,23 @@ export class WebXRInput implements IDisposable {
 
     /**
      * Initializes the WebXRInput
-     * @param helper experience helper which the input should be created for
+     * @param xrExperienceHelper 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(public xrExperienceHelper: WebXRExperienceHelper) {
+        this._frameObserver = xrExperienceHelper.sessionManager.onXRFrameObservable.add(() => {
+            if (!xrExperienceHelper.sessionManager.currentFrame) {
                 return;
             }
 
             // 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 && xrExperienceHelper.sessionManager.session.inputSources) {
+                this._addAndRemoveControllers(xrExperienceHelper.sessionManager.session.inputSources, []);
+                xrExperienceHelper.sessionManager.session.addEventListener("inputsourceschange", this._onInputSourcesChange);
             }
 
             // Update controller pose info
             this.controllers.forEach((controller) => {
-                controller.updateFromXRFrame(helper.sessionManager.currentFrame!, helper.sessionManager.referenceSpace);
+                controller.updateFromXRFrame(xrExperienceHelper.sessionManager.currentFrame!, xrExperienceHelper.sessionManager.referenceSpace);
             });
 
         });
@@ -140,13 +160,13 @@ export class WebXRInput implements IDisposable {
     private _addAndRemoveControllers(addInputs: Array<XRInputSource>, removeInputs: Array<XRInputSource>) {
         // Add controllers if they don't already exist
         var sources = this.controllers.map((c) => {return c.inputSource; });
-        addInputs.forEach((input) => {
+        for(var input of addInputs){
             if (sources.indexOf(input) === -1) {
-                var controller = new WebXRController(this.helper.camera._scene, input, this.helper.container);
+                var controller = new WebXRController(this.xrExperienceHelper.camera._scene, input, this.xrExperienceHelper.container);
                 this.controllers.push(controller);
                 this.onControllerAddedObservable.notifyObservers(controller);
             }
-        });
+        }
 
         // Remove and dispose of controllers to be disposed
         var keepControllers: Array<WebXRController> = [];
@@ -173,6 +193,266 @@ export class WebXRInput implements IDisposable {
         this.controllers.forEach((c) => {
             c.dispose();
         });
-        this.helper.sessionManager.onXRFrameObservable.remove(this._frameObserver);
+        this.xrExperienceHelper.sessionManager.onXRFrameObservable.remove(this._frameObserver);
+    }
+}
+
+export class WebXRControllerModelLoader {
+    constructor(input: WebXRInput){
+        input.onControllerAddedObservable.add((c)=>{
+            if(c.inputSource.gamepad && c.inputSource.gamepad.id === "oculus-touch"){
+                let controllerModel = new OculusTouchController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness
+                controllerModel.isXR = true
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m)=>{
+                    controllerModel.mesh!.parent = c.grip!
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(0,Math.PI,0);
+                })
+            }else if(c.inputSource.gamepad && c.inputSource.gamepad.id === "oculus-quest"){
+                OculusTouchController._IsQuest = true;
+                let controllerModel = new OculusTouchController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness
+                controllerModel.isXR = true
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m)=>{
+                    controllerModel.mesh!.parent = c.grip!
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(Math.PI/-4,Math.PI,0);
+                })
+            }else{
+                let controllerModel = new WindowsMotionController(c.inputSource.gamepad);
+                controllerModel.hand = c.inputSource.handedness
+                controllerModel.isXR = true
+                controllerModel.initControllerMesh(c.grip!.getScene(), (m)=>{
+                    controllerModel.mesh!.parent = c.grip!
+                    controllerModel.mesh!.rotationQuaternion = Quaternion.FromEulerAngles(0,Math.PI,0);
+                })
+            }
+            
+        })
     }
+}
+
+export class WebXRControllerPointerSelection {
+    private _laserPointer: Mesh;
+    private _gazeTracker: Mesh;
+    private triggerDown = false;
+    public static _idCounter = 0;
+    private _id:number;
+    private _tmpRay = new Ray(new Vector3(), new Vector3());
+    constructor(input: WebXRInput){
+        this._id = WebXRControllerPointerSelection._idCounter++;
+        input.onControllerAddedObservable.add((c)=>{
+            var scene = c.pointer.getScene();
+            this._laserPointer = Mesh.CreateCylinder("laserPointer", 1, 0.0002, 0.004, 20, 1, scene, false);
+            this._laserPointer.parent = c.pointer
+
+            var laserPointerMaterial = new StandardMaterial("laserPointerMat", scene);
+            laserPointerMaterial.emissiveColor = new Color3(0.7, 0.7, 0.7);
+            laserPointerMaterial.alpha = 0.6;
+            this._laserPointer.material = laserPointerMaterial;
+            
+            this._laserPointer.rotation.x = Math.PI / 2;
+            this._updatePointerDistance(1)
+            this._laserPointer.isPickable = false;
+
+
+            this._gazeTracker = Mesh.CreateTorus("gazeTracker", 0.0035*3, 0.0025*3, 20, scene, false);
+            this._gazeTracker.bakeCurrentTransformIntoVertices();
+            this._gazeTracker.isPickable = false;
+            this._gazeTracker.isVisible = false;
+            var targetMat = new StandardMaterial("targetMat", scene);
+            targetMat.specularColor = Color3.Black();
+            targetMat.emissiveColor = new Color3(0.7, 0.7, 0.7);
+            targetMat.backFaceCulling = false;
+            this._gazeTracker.material = targetMat;
+
+
+            
+
+            scene.onBeforeRenderObservable.add(()=>{                
+                c.getWorldPointerRayToRef(this._tmpRay)
+                var pick = scene.pickWithRay(this._tmpRay)
+
+                if(pick){
+                    if(c.inputSource.gamepad && c.inputSource.gamepad.buttons[0] && c.inputSource.gamepad.buttons[0].value > 0.7){
+                        if(!this.triggerDown){
+                            scene.simulatePointerDown(pick, { pointerId: this._id });
+                        }
+                        this.triggerDown = true;
+                    }else{
+                        if(this.triggerDown){
+                            scene.simulatePointerUp(pick, { pointerId: this._id });
+                        }
+                        this.triggerDown = false;
+                    }
+                    scene.simulatePointerMove(pick, { pointerId: this._id });
+                }
+                
+
+                
+                if(pick && pick.pickedPoint && pick.hit){
+                    this._updatePointerDistance(pick.distance)
+
+                    // Scale based on distance
+                    this._gazeTracker.position.copyFrom(pick.pickedPoint)
+                    this._gazeTracker.scaling.x = Math.sqrt(pick.distance);
+                    this._gazeTracker.scaling.y = Math.sqrt(pick.distance);
+                    this._gazeTracker.scaling.z = Math.sqrt(pick.distance);
+
+                    // To avoid z-fighting
+                    var pickNormal = this._convertNormalToDirectionOfRay(pick.getNormal(), this._tmpRay);
+                    let deltaFighting = 0.002;
+                    this._gazeTracker.position.copyFrom(pick.pickedPoint);
+                    if (pickNormal) {
+                        var axis1 = Vector3.Cross(Axis.Y, pickNormal);
+                        var axis2 = Vector3.Cross(pickNormal, axis1);
+                        Vector3.RotationFromAxisToRef(axis2, pickNormal, axis1, this._gazeTracker.rotation);
+                        this._gazeTracker.position.addInPlace(pickNormal.scale(deltaFighting));
+                    }
+
+                    this._gazeTracker.isVisible = true;
+                }else{
+                    this._gazeTracker.isVisible = false;
+                }
+            })
+        })
+    }
+
+    private _convertNormalToDirectionOfRay(normal: Nullable<Vector3>, ray: Ray) {
+        if (normal) {
+            var angle = Math.acos(Vector3.Dot(normal, ray.direction));
+            if (angle < Math.PI / 2) {
+                normal.scaleInPlace(-1);
+            }
+        }
+        return normal;
+    }
+
+    public _updatePointerDistance(distance: number = 100) {
+        this._laserPointer.scaling.y = distance;
+        this._laserPointer.position.z = distance / 2;
+    }
+}
+
+export class WebXRControllerTeleportation {
+    private _teleportationFillColor: string = "#444444";
+    private _teleportationBorderColor: string = "#FFFFFF";
+    private _forwardReadyToTeleport = false;
+    private _backwardReadyToTeleport = false;
+    private _tmpRay = new Ray(new Vector3(), new Vector3());
+    private _tmpVector = new Vector3();
+    constructor(input: WebXRInput, public floorMeshes = []){
+        input.onControllerAddedObservable.add((c)=>{
+            var scene = c.pointer.getScene();
+
+            // Teleport animation
+            var teleportationTarget = Mesh.CreateGround("teleportationTarget", 2, 2, 2, scene);
+            teleportationTarget.isPickable = false;
+            var length = 512;
+            var dynamicTexture = new DynamicTexture("DynamicTexture", length, scene, true);
+            dynamicTexture.hasAlpha = true;
+            var context = dynamicTexture.getContext();
+            var centerX = length / 2;
+            var centerY = length / 2;
+            var radius = 200;
+            context.beginPath();
+            context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
+            context.fillStyle = this._teleportationFillColor;
+            context.fill();
+            context.lineWidth = 10;
+            context.strokeStyle = this._teleportationBorderColor;
+            context.stroke();
+            context.closePath();
+            dynamicTexture.update();
+            var teleportationCircleMaterial = new StandardMaterial("TextPlaneMaterial", scene);
+            teleportationCircleMaterial.diffuseTexture = dynamicTexture;
+            teleportationTarget.material = teleportationCircleMaterial;
+            var torus = Mesh.CreateTorus("torusTeleportation", 0.75, 0.1, 25, scene, false);
+            torus.isPickable = false;
+            torus.parent = teleportationTarget;
+            var animationInnerCircle = new Animation("animationInnerCircle", "position.y", 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
+            var keys = [];
+            keys.push({
+                frame: 0,
+                value: 0
+            });
+            keys.push({
+                frame: 30,
+                value: 0.4
+            });
+            keys.push({
+                frame: 60,
+                value: 0
+            });
+            animationInnerCircle.setKeys(keys);
+            var easingFunction = new SineEase();
+            easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
+            animationInnerCircle.setEasingFunction(easingFunction);
+            torus.animations = [];
+            torus.animations.push(animationInnerCircle);
+            scene.beginAnimation(torus, 0, 60, true);
+
+
+
+            scene.onBeforeRenderObservable.add(()=>{
+                if(this._forwardReadyToTeleport){
+                    c.getWorldPointerRayToRef(this._tmpRay)
+                    var pick = scene.pickWithRay(this._tmpRay)
+                    if(pick && pick.pickedPoint){
+
+                        // To avoid z-fighting
+                        teleportationTarget.position.copyFrom(pick.pickedPoint);
+                        teleportationTarget.position.y += 0.002;
+                    }
+                    teleportationTarget.isVisible = true;
+                    (<Mesh>teleportationTarget.getChildren()[0]).isVisible = true;
+                }else{
+                    teleportationTarget.isVisible = false;
+                    (<Mesh>teleportationTarget.getChildren()[0]).isVisible = false;
+                }
+
+                if(c.inputSource.gamepad){
+                    if(c.inputSource.gamepad.axes[1]){
+                        if(c.inputSource.gamepad.axes[1] < -0.7){
+                            this._forwardReadyToTeleport = true
+                        }else{
+                            if(this._forwardReadyToTeleport){
+                                this._tmpVector.copyFrom(teleportationTarget.position)
+                                this._tmpVector.y += input.xrExperienceHelper.camera.position.y;
+                                input.xrExperienceHelper.setPositionOfCameraUsingContainer(this._tmpVector)
+                            }
+                            this._forwardReadyToTeleport = false
+                        }
+
+                        if(c.inputSource.gamepad.axes[1] > 0.7){
+                            this._backwardReadyToTeleport = true
+                        }else{
+                            if(this._backwardReadyToTeleport){
+                                var camMat = input.xrExperienceHelper.camera.computeWorldMatrix();
+                                var q = new Quaternion()
+                                camMat.decompose(undefined, q, this._tmpRay.origin)
+                                this._tmpVector.set(0,0,-1);
+                                this._tmpVector.rotateByQuaternionToRef(q, this._tmpVector)
+                                this._tmpVector.y = 0;
+                                this._tmpVector.normalize()
+                                this._tmpVector.y = -1.5;
+                                this._tmpVector.normalize()
+                                this._tmpRay.direction.copyFrom(this._tmpVector)
+                                var pick = scene.pickWithRay(this._tmpRay)
+
+                                if(pick && pick.pickedPoint){
+                                    this._tmpVector.copyFrom(pick.pickedPoint)
+                                    this._tmpVector.y += input.xrExperienceHelper.camera.position.y;
+                                    input.xrExperienceHelper.setPositionOfCameraUsingContainer(this._tmpVector)
+                                }
+                            }
+                            this._backwardReadyToTeleport = false
+                        }
+                    }
+                    
+                }
+            })
+        })
+    }
+
+    
 }

+ 1 - 1
src/Cameras/camera.ts

@@ -902,7 +902,7 @@ export class Camera extends Node {
     }
 
     /** @hidden */
-    public _isRightCamera = true;
+    public _isRightCamera = false;
     /**
      * Gets the right camera of a rig setup in case of Rigged Camera
      */

+ 23 - 8
src/Gamepads/Controllers/poseEnabledController.ts

@@ -131,6 +131,10 @@ export class PoseEnabledControllerHelper {
  * Defines the PoseEnabledController object that contains state of a vr capable controller
  */
 export class PoseEnabledController extends Gamepad implements PoseControlled {
+    /**
+     * If the controller is used in a webXR session
+     */
+    public isXR = false;
     // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
     private _deviceRoomPosition = Vector3.Zero();
     private _deviceRoomRotationQuaternion = new Quaternion();
@@ -228,6 +232,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * Updates the state of the pose enbaled controller and mesh based on the current position and rotation of the controller
      */
     public update() {
+        if(this.isXR){
+            return
+        }
         super.update();
         this._updatePoseAndMesh();
     }
@@ -236,6 +243,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * Updates only the pose device and mesh without doing any button event checking
      */
     protected _updatePoseAndMesh() {
+        if(this.isXR){
+            return
+        }
         var pose: GamepadPose = this.browserGamepad.pose;
         this.updateFromDevice(pose);
 
@@ -283,6 +293,9 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
      * @param poseData raw pose fromthe device
      */
     updateFromDevice(poseData: DevicePose) {
+        if(this.isXR){
+            return
+        }
         if (poseData) {
             this.rawPose = poseData;
             if (poseData.position) {
@@ -335,15 +348,17 @@ export class PoseEnabledController extends Gamepad implements PoseControlled {
         }
 
         // Sync controller mesh and pointing pose node's state with controller, this is done to avoid a frame where position is 0,0,0 when attaching mesh
-        this._updatePoseAndMesh();
-        if (this._pointingPoseNode) {
-            var parents = [];
-            var obj: Node = this._pointingPoseNode;
-            while (obj.parent) {
-                parents.push(obj.parent);
-                obj = obj.parent;
+        if(!this.isXR){
+            this._updatePoseAndMesh();
+            if (this._pointingPoseNode) {
+                var parents = [];
+                var obj: Node = this._pointingPoseNode;
+                while (obj.parent) {
+                    parents.push(obj.parent);
+                    obj = obj.parent;
+                }
+                parents.reverse().forEach((p) => { p.computeWorldMatrix(true); });
             }
-            parents.reverse().forEach((p) => { p.computeWorldMatrix(true); });
         }
 
         this._meshAttachedObservable.notifyObservers(mesh);