Procházet zdrojové kódy

Merge pull request #4493 from TrevorDev/sixDofAndMultiBehavior

Six dof and multi ray scaling behavior
David Catuhe před 7 roky
rodič
revize
1c3c25a72e

+ 3 - 1
Tools/Gulp/config.json

@@ -313,7 +313,9 @@
         },
         "meshBehaviors": {
             "files": [
-                "../../src/Behaviors/Mesh/babylon.pointerDragBehavior.js"
+                "../../src/Behaviors/Mesh/babylon.pointerDragBehavior.js",
+                "../../src/Behaviors/Mesh/babylon.multiPointerScaleBehavior.js",
+                "../../src/Behaviors/Mesh/babylon.sixDofDragBehavior.js"
             ],
             "dependUpon": [
                 "behaviors"

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

@@ -27,7 +27,7 @@
 - Get a root mesh from an asset container, load a mesh from a file with a single string url ([TrevorDev](https://github.com/TrevorDev))
 - UtilityLayer class to render another scene as a layer on top of an existing scene ([TrevorDev](https://github.com/TrevorDev))
 - AnimationGroup has now onAnimationGroupEnd observable ([RaananW](https://github.com/RaananW))
-- Pointer drag behavior to enable drag and drop with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
+- PointerDragBehavior, SixDofDragBehavior, MultiPointerScaleBehavior to enable drag and drop/scaling with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
 - Gizmo and GizmoManager classes used to manipulate meshes in a scene. Position, rotation, scale, and bounding box gizmos ([TrevorDev](https://github.com/TrevorDev))
 - Added a new `mesh.ignoreNonUniformScaling` to turn off non uniform scaling compensation ([Deltakosh](https://github.com/deltakosh))
 - AssetsManager tasks will only run when their state is INIT. It is now possible to remove a task from the assets manager ([RaananW](https://github.com/RaananW))

+ 101 - 0
src/Behaviors/Mesh/babylon.multiPointerScaleBehavior.ts

@@ -0,0 +1,101 @@
+module BABYLON {
+    /**
+     * A behavior that when attached to a mesh will allow the mesh to be scaled
+     */
+    export class MultiPointerScaleBehavior implements Behavior<Mesh> {
+        private _dragBehaviorA:PointerDragBehavior;
+        private _dragBehaviorB:PointerDragBehavior;
+        private _startDistance = 0;
+        private _initialScale = new Vector3(0,0,0);
+        private _targetScale = new Vector3(0,0,0);
+        private _ownerNode:Mesh;
+        private _sceneRenderObserver:Nullable<Observer<Scene>> = null;
+
+        constructor(){
+            this._dragBehaviorA = new BABYLON.PointerDragBehavior({});
+            this._dragBehaviorA.moveAttached = false;
+            this._dragBehaviorB = new BABYLON.PointerDragBehavior({});
+            this._dragBehaviorB.moveAttached = false;
+        }
+        
+        /**
+         *  The name of the behavior
+         */
+        public get name(): string {
+            return "MultiPointerScale";
+        }
+
+        /**
+         *  Initializes the behavior
+         */
+        public init() {}
+
+        private _getCurrentDistance(){
+            return this._dragBehaviorA.lastDragPosition.subtract(this._dragBehaviorB.lastDragPosition).length();
+        }
+
+        /**
+         * Attaches the scale behavior the passed in mesh
+         * @param ownerNode The mesh that will be scaled around once attached
+         */
+        public attach(ownerNode: Mesh): void {
+            this._ownerNode = ownerNode;
+
+            // Create 2 drag behaviors such that each will only be triggered by a separate pointer
+            this._dragBehaviorA.onDragStartObservable.add((e)=>{
+                if(this._dragBehaviorA.dragging && this._dragBehaviorB.dragging){
+                    if(this._dragBehaviorA.currentDraggingPointerID == this._dragBehaviorB.currentDraggingPointerID){
+                        this._dragBehaviorA.releaseDrag();
+                    }else{
+                        this._initialScale.copyFrom(ownerNode.scaling)
+                        this._startDistance = this._getCurrentDistance();
+                    }
+                }
+            });
+            this._dragBehaviorB.onDragStartObservable.add((e)=>{
+                if(this._dragBehaviorA.dragging && this._dragBehaviorB.dragging){
+                    if(this._dragBehaviorA.currentDraggingPointerID == this._dragBehaviorB.currentDraggingPointerID){
+                        this._dragBehaviorB.releaseDrag();
+                    }else{
+                        this._initialScale.copyFrom(ownerNode.scaling)
+                        this._startDistance = this._getCurrentDistance();
+                    }
+                }
+            });
+
+            // Once both drag behaviors are active scale based on the distance between the two pointers
+            [this._dragBehaviorA, this._dragBehaviorB].forEach((behavior)=>{
+                behavior.onDragObservable.add(()=>{
+                    if(this._dragBehaviorA.dragging && this._dragBehaviorB.dragging){
+                        var ratio = this._getCurrentDistance()/this._startDistance;
+                        this._initialScale.scaleToRef(ratio, this._targetScale);
+                    }
+                });
+            })
+
+            ownerNode.addBehavior(this._dragBehaviorA);
+            ownerNode.addBehavior(this._dragBehaviorB);
+
+            // On every frame move towards target scaling to avoid jitter caused by vr controllers
+            this._sceneRenderObserver = ownerNode.getScene().onBeforeRenderObservable.add(()=>{
+                if(this._dragBehaviorA.dragging && this._dragBehaviorB.dragging){
+                    var change = this._targetScale.subtract(ownerNode.scaling).scaleInPlace(0.1);
+                    if(change.length()>0.01){
+                        ownerNode.scaling.addInPlace(change);
+                    }
+                }
+            });
+        }
+        /**
+         *  Detaches the behavior from the mesh
+         */
+        public detach(): void {
+            this._ownerNode.getScene().onBeforeRenderObservable.remove(this._sceneRenderObserver);
+            [this._dragBehaviorA, this._dragBehaviorB].forEach((behavior)=>{
+                behavior.onDragStartObservable.clear();
+                behavior.onDragObservable.clear();
+                this._ownerNode.removeBehavior(behavior);
+            });
+        }
+    }
+}

+ 35 - 20
src/Behaviors/Mesh/babylon.pointerDragBehavior.ts

@@ -8,7 +8,18 @@ module BABYLON {
         private _scene:Scene;
         private _pointerObserver:Nullable<Observer<PointerInfo>>;
         private static _planeScene:Scene;
-        private _draggingID = -1;
+        /**
+         * The id of the pointer that is currently interacting with the behavior (-1 when no pointer is active)
+         */
+        public currentDraggingPointerID = -1;
+        /**
+         * The last position where the pointer hit the drag plane in world space
+         */
+        public lastDragPosition:Vector3;
+        /**
+         * If the behavior is currently in a dragging state
+         */
+        public dragging = false;
         // Debug mode will display drag planes to help visualize behavior
         private _debugMode = false;
         private _maxDragAngle = Math.PI/5;
@@ -20,15 +31,15 @@ module BABYLON {
          *  * dragPlaneNormal normal of the current drag plane used during the drag
          *  * dragPlanePoint in world space where the drag intersects the drag plane
          */
-        public onDragObservable = new Observable<{delta:Vector3, dragPlanePoint:Vector3, dragPlaneNormal:Vector3, dragDistance:number}>()
+        public onDragObservable = new Observable<{delta:Vector3, dragPlanePoint:Vector3, dragPlaneNormal:Vector3, dragDistance:number, pointerId:number}>()
         /**
          *  Fires each time a drag begins (eg. mouse down on mesh)
          */
-        public onDragStartObservable = new Observable<{dragPlanePoint:Vector3}>()
+        public onDragStartObservable = new Observable<{dragPlanePoint:Vector3, pointerId:number}>()
         /**
          *  Fires each time a drag ends (eg. mouse release after drag)
          */
-        public onDragEndObservable = new Observable<{dragPlanePoint:Vector3}>()
+        public onDragEndObservable = new Observable<{dragPlanePoint:Vector3, pointerId:number}>()
         /**
          *  If the attached mesh should be moved when dragged
          */
@@ -95,8 +106,7 @@ module BABYLON {
             this._dragPlane = BABYLON.Mesh.CreatePlane("pointerDragPlane", this._debugMode ? 1 : 10000, PointerDragBehavior._planeScene, false, BABYLON.Mesh.DOUBLESIDE);
 
             // State of the drag
-            var dragging = false;
-            var lastPosition = new BABYLON.Vector3(0,0,0);
+            this.lastDragPosition = new BABYLON.Vector3(0,0,0);
             var delta = new BABYLON.Vector3(0,0,0);
             var dragLength = 0;
 
@@ -110,24 +120,23 @@ module BABYLON {
                 }
                 
                 if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
-                    if(!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
+                    
+                    if(!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
                         this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
                         var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
                         if(pickedPoint){
-                            dragging = true;
-                            this._draggingID = (<PointerEvent>pointerInfo.event).pointerId;
-                            lastPosition.copyFrom(pickedPoint);
-                            this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint});
+                            this.dragging = true;
+                            this.currentDraggingPointerID = (<PointerEvent>pointerInfo.event).pointerId;
+                            this.lastDragPosition.copyFrom(pickedPoint);
+                            this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint, pointerId: this.currentDraggingPointerID});
                         }
                     }
                 }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
-                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId){
-                        dragging = false;
-                        this._draggingID = -1;
-                        this.onDragEndObservable.notifyObservers({dragPlanePoint: lastPosition});
+                    if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId){
+                        this.releaseDrag();
                     }
                 }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
-                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
+                    if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId && this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
                         var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
                         
                          // Get angle between drag plane and ray. Only update the drag plane at non steep angles to avoid jumps in delta position
@@ -143,23 +152,29 @@ module BABYLON {
                                 var worldDragAxis = Vector3.TransformCoordinates(this.options.dragAxis, this._attachedNode.getWorldMatrix().getRotationMatrix());
 
                                 // Project delta drag from the drag plane onto the drag axis
-                                dragLength = BABYLON.Vector3.Dot(pickedPoint.subtract(lastPosition), worldDragAxis)
+                                dragLength = BABYLON.Vector3.Dot(pickedPoint.subtract(this.lastDragPosition), worldDragAxis)
                                 worldDragAxis.scaleToRef(dragLength, delta);
                             }else{
                                 dragLength = delta.length();
-                                pickedPoint.subtractToRef(lastPosition, delta);
+                                pickedPoint.subtractToRef(this.lastDragPosition, delta);
                             }
                             if(this.moveAttached){
                                 (<Mesh>this._attachedNode).position.addInPlace(delta);
                             }
-                            this.onDragObservable.notifyObservers({dragDistance: dragLength, delta: delta, dragPlanePoint: pickedPoint, dragPlaneNormal: this._dragPlane.forward});
-                            lastPosition.copyFrom(pickedPoint);
+                            this.onDragObservable.notifyObservers({dragDistance: dragLength, delta: delta, dragPlanePoint: pickedPoint, dragPlaneNormal: this._dragPlane.forward, pointerId: this.currentDraggingPointerID});
+                            this.lastDragPosition.copyFrom(pickedPoint);
                         }
                     }
                 }
             });
         }
 
+        public releaseDrag(){
+            this.dragging = false;
+            this.onDragEndObservable.notifyObservers({dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID});
+            this.currentDraggingPointerID = -1;
+        }
+
         private _pickWithRayOnDragPlane(ray:Nullable<Ray>){
             if(!ray){
                 return null;

+ 143 - 0
src/Behaviors/Mesh/babylon.sixDofDragBehavior.ts

@@ -0,0 +1,143 @@
+module BABYLON {
+    /**
+     * A behavior that when attached to a mesh will allow the mesh to be dragged around based on directions and origin of the pointer's ray
+     */
+    export class SixDofDragBehavior implements Behavior<Mesh> {
+        private static _virtualScene:Scene;
+        private _ownerNode:Mesh;
+        private _sceneRenderObserver:Nullable<Observer<Scene>> = null;
+        private _scene:Scene;
+        private _targetPosition = new Vector3(0,0,0);
+        private _virtualOriginMesh:AbstractMesh;
+        private _virtualDragMesh:AbstractMesh;
+        private _pointerObserver:Nullable<Observer<PointerInfo>>;
+        // How much faster the object should move when its further away
+        private _sixDofZDragFactor = 5;
+        /**
+         * If the behavior is currently in a dragging state
+         */
+        public dragging = false;
+        /**
+         * The id of the pointer that is currently interacting with the behavior (-1 when no pointer is active)
+         */
+        public currentDraggingPointerID = -1;
+
+
+        constructor(){
+        }
+        
+        /**
+         *  The name of the behavior
+         */
+        public get name(): string {
+            return "SixDofDrag";
+        }
+
+        /**
+         *  Initializes the behavior
+         */
+        public init() {}
+
+        /**
+         * Attaches the scale behavior the passed in mesh
+         * @param ownerNode The mesh that will be scaled around once attached
+         */
+        public attach(ownerNode: Mesh): void {
+            this._ownerNode = ownerNode;
+            this._scene = this._ownerNode.getScene();
+            if(!SixDofDragBehavior._virtualScene){
+                SixDofDragBehavior._virtualScene = new BABYLON.Scene(this._scene.getEngine());
+                this._scene.getEngine().scenes.pop();
+            }
+        
+            var pickedMesh:Nullable<AbstractMesh> = null;
+            var lastSixDofOriginPosition = new BABYLON.Vector3(0,0,0);
+
+            // Setup virtual meshes to be used for dragging without dirtying the existing scene
+            this._virtualOriginMesh = new BABYLON.AbstractMesh("", SixDofDragBehavior._virtualScene);
+            this._virtualOriginMesh.rotationQuaternion = new Quaternion();
+            this._virtualDragMesh = new BABYLON.AbstractMesh("", SixDofDragBehavior._virtualScene);
+            this._virtualDragMesh.rotationQuaternion = new Quaternion();
+
+            var pickPredicate = (m:AbstractMesh)=>{
+                return this._ownerNode == m || m.isDescendantOf(this._ownerNode);
+            }
+            
+            this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo, eventState)=>{                
+                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if(!this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray && pickPredicate(pointerInfo.pickInfo.pickedMesh)){
+                        pickedMesh = pointerInfo.pickInfo.pickedMesh;
+                        lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
+
+                        // Set position and orientation of the controller
+                        this._virtualOriginMesh.position.copyFrom(pointerInfo.pickInfo.ray.origin);
+                        this._virtualOriginMesh.lookAt(pointerInfo.pickInfo.ray.origin.subtract(pointerInfo.pickInfo.ray.direction));
+
+                        // Attach the virtual drag mesh to the virtual origin mesh so it can be dragged
+                        this._virtualOriginMesh.removeChild(this._virtualDragMesh);
+                        this._virtualDragMesh.position.copyFrom(pickedMesh.absolutePosition);
+                        if(!pickedMesh.rotationQuaternion){
+                            pickedMesh.rotationQuaternion = new Quaternion();
+                        }
+                        this._virtualDragMesh.rotationQuaternion!.copyFrom(pickedMesh.rotationQuaternion);
+                        this._virtualOriginMesh.addChild(this._virtualDragMesh);
+
+                        // Update state
+                        this.dragging = true;
+                        this.currentDraggingPointerID = (<PointerEvent>pointerInfo.event).pointerId;
+                    }
+                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
+                    if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId){
+                        this.dragging = false;
+                        this.currentDraggingPointerID = -1;
+                        pickedMesh = null;
+                        this._virtualOriginMesh.removeChild(this._virtualDragMesh);
+                    }
+                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
+                    if(this.currentDraggingPointerID == (<PointerEvent>pointerInfo.event).pointerId && this.dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray && pickedMesh){
+                        // Calculate controller drag distance in controller space
+                        var originDragDifference = pointerInfo.pickInfo.ray.origin.subtract(lastSixDofOriginPosition);
+                        lastSixDofOriginPosition.copyFrom(pointerInfo.pickInfo.ray.origin);
+                        var localOriginDragDifference = Vector3.TransformCoordinates(originDragDifference, Matrix.Invert(this._virtualOriginMesh.getWorldMatrix().getRotationMatrix()));
+                        
+                        this._virtualOriginMesh.addChild(this._virtualDragMesh);
+                        // Determine how much the controller moved to/away towards the dragged object and use this to move the object further when its further away
+                        var zDragDistance = Vector3.Dot(localOriginDragDifference, this._virtualOriginMesh.position.normalizeToNew());
+                        this._virtualDragMesh.position.z -= this._virtualDragMesh.position.z < 1 ? zDragDistance*this._sixDofZDragFactor : zDragDistance*this._sixDofZDragFactor*this._virtualDragMesh.position.z;
+                        if(this._virtualDragMesh.position.z < 0){
+                            this._virtualDragMesh.position.z = 0;
+                        }
+                        
+                        // Update the controller position
+                        this._virtualOriginMesh.position.copyFrom(pointerInfo.pickInfo.ray.origin);
+                        this._virtualOriginMesh.lookAt(pointerInfo.pickInfo.ray.origin.subtract(pointerInfo.pickInfo.ray.direction));
+                        this._virtualOriginMesh.removeChild(this._virtualDragMesh)
+                    
+                        // Move the virtualObjectsPosition into the picked mesh's space if needed
+                        this._targetPosition.copyFrom(this._virtualDragMesh.absolutePosition);
+                        if(pickedMesh.parent){
+                            Vector3.TransformCoordinatesToRef(this._targetPosition, Matrix.Invert(pickedMesh.parent.getWorldMatrix()), this._targetPosition);
+                        }
+                    }
+                }
+            });
+
+            // On every frame move towards target scaling to avoid jitter caused by vr controllers
+            this._sceneRenderObserver = ownerNode.getScene().onBeforeRenderObservable.add(()=>{
+                if(this.dragging && pickedMesh){
+                    // Slowly move mesh to avoid jitter
+                    pickedMesh.position.addInPlace(this._targetPosition.subtract(pickedMesh.position).scale(0.2));
+                }
+            });
+        }
+        /**
+         *  Detaches the behavior from the mesh
+         */
+        public detach(): void {
+            this._scene.onPointerObservable.remove(this._pointerObserver);
+            this._ownerNode.getScene().onBeforeRenderObservable.remove(this._sceneRenderObserver);
+            this._virtualOriginMesh.dispose()
+            this._virtualDragMesh.dispose();
+        }
+    }
+}

+ 13 - 0
src/Gamepad/Controllers/babylon.poseEnabledController.ts

@@ -258,6 +258,19 @@ module BABYLON {
             if (!this._mesh.rotationQuaternion) {
                 this._mesh.rotationQuaternion = new Quaternion();
             }
+
+            // Sync controller mesh and pointing pose node's state with controller
+            this.update();
+            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)});
+            }
+
             this._meshAttachedObservable.notifyObservers(mesh);
         }
 

+ 4 - 2
src/Gamepad/Controllers/babylon.windowsMotionController.ts

@@ -348,14 +348,14 @@ module BABYLON {
 
                 if (meshLoaded) {
                     meshLoaded(this._defaultModel);
-                }
+                }                    
             }, null, (scene: Scene, message: string) => {
                 Tools.Log(message);
                 Tools.Warn('Failed to retrieve controller model from the remote server: ' + path + filename);
                 if (!forceDefault) {
                     this.initControllerMesh(scene, meshLoaded, true);
                 }
-            });
+            });            
         }
 
         /**
@@ -476,6 +476,8 @@ module BABYLON {
             loadedMeshInfo.pointingPoseNode = getChildByName(rootNode, this._mapping.pointingPoseMeshName);
             if (!loadedMeshInfo.pointingPoseNode) {
                 Tools.Warn('Missing pointing pose mesh with name: ' + this._mapping.pointingPoseMeshName);
+            }else{
+                this._pointingPoseNode = loadedMeshInfo.pointingPoseNode;
             }
 
             return loadedMeshInfo;