瀏覽代碼

pointer drag behavior on axis or plane with 6dof support

Trevor Baron 7 年之前
父節點
當前提交
09e90b7f19
共有 4 個文件被更改,包括 211 次插入3 次删除
  1. 9 0
      Tools/Gulp/config.json
  2. 1 0
      dist/preview release/what's new.md
  3. 177 0
      src/Behaviors/Mesh/babylon.pointerDragBehavior.ts
  4. 24 3
      src/babylon.scene.ts

+ 9 - 0
Tools/Gulp/config.json

@@ -107,6 +107,7 @@
             "layer",
             "textureTools",
             "cameraBehaviors",
+            "meshBehaviors",
             "nullEngine",
             "instrumentation",
             "backgroundMaterial",
@@ -289,6 +290,14 @@
                 "core"
             ]
         },
+        "meshBehaviors": {
+            "files": [
+                "../../src/Behaviors/Mesh/babylon.pointerDragBehavior.js"
+            ],
+            "dependUpon": [
+                "core"
+            ]
+        },
         "textureTools": {
             "files": [
                 "../../src/Tools/babylon.textureTools.js"

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

@@ -19,6 +19,7 @@
 - Added webVR constructor options: disable laser pointer toggle, teleportation floor meshes ([TrevorDev](https://github.com/TrevorDev))
 - 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))
+- Pointer drag behavior to enable drag and drop with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 

+ 177 - 0
src/Behaviors/Mesh/babylon.pointerDragBehavior.ts

@@ -0,0 +1,177 @@
+module BABYLON {
+    /**
+     * A behavior that when attached to a mesh will allow the mesh to be dragged around the screen based on pointer events
+     */
+    export class PointerDragBehavior implements Behavior<Mesh> {
+        private _attachedNode: Node; 
+        private _dragPlane: Mesh;
+        private _scene:Scene;
+        private _pointerObserver:Nullable<Observer<PointerInfo>>;
+        private static planeScene:Scene;
+        private _draggingID = -1;
+        
+        /**
+         *  Fires each time the attached mesh is dragged with the pointer
+         */
+        public onDragObservable = new Observable<{delta:Vector3, dragPlanePoint:Vector3}>()
+        /**
+         *  Fires each time a drag begins (eg. mouse down on mesh)
+         */
+        public onDragStartObservable = new Observable<{dragPlanePoint:Vector3}>()
+        /**
+         *  Fires each time a drag ends (eg. mouse release after drag)
+         */
+        public onDragEndObservable = new Observable<{dragPlanePoint:Vector3}>()
+        /**
+         *  If the attached mesh should be moved when dragged
+         */
+        public moveAttached = true;
+        /**
+         *  Mesh with the position where the drag plane should be placed
+         */
+        public _dragPlaneParent:Nullable<Mesh>=null;
+        
+        /**
+         * Creates a pointer drag behavior that can be attached to a mesh
+         * @param options The drag axis or normal of the plane that will be dragged accross
+         */
+        constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
+            var optionCount = 0;
+            if(options.dragAxis){
+                optionCount++;
+            }
+            if(options.dragPlaneNormal){
+                optionCount++;
+            }
+            if(optionCount > 1){
+                throw "Multiple drag modes specified in dragBehavior options. Only one expected";
+            }
+            if(optionCount < 1){
+                throw "At least one drag mode option must be specified";
+            }
+        }
+
+        /**
+         *  The name of the behavior
+         */
+        public get name(): string {
+            return "PointerDrag";
+        }
+
+        /**
+         *  Initializes the behavior
+         */
+        public init() {}
+
+        /**
+         * Attaches the drag behavior the passed in mesh
+         * @param ownerNode The mesh that will be dragged around once attached
+         */
+        public attach(ownerNode: Mesh): void {
+            this._scene = ownerNode.getScene();
+            this._attachedNode = ownerNode;
+
+            // Initialize drag plane to not interfere with existing scene
+            if(!PointerDragBehavior.planeScene){
+                PointerDragBehavior.planeScene = new BABYLON.Scene(this._scene.getEngine())
+                this._scene.getEngine().scenes.pop();
+            }
+            this._dragPlane = BABYLON.Mesh.CreatePlane("pointerDragPlane", 1000, PointerDragBehavior.planeScene, false, BABYLON.Mesh.DOUBLESIDE);
+
+            // State of the drag
+            var dragging = false
+            var lastPosition = new BABYLON.Vector3(0,0,0);
+            var delta = new BABYLON.Vector3(0,0,0);
+
+            this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo)=>{
+                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if(!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray){
+                        if(this._attachedNode == 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});
+                            }
+                        }
+                    }
+                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
+                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId){
+                        dragging = false;
+                        this._draggingID = -1;
+                        this.onDragEndObservable.notifyObservers({dragPlanePoint: lastPosition});
+                    }
+                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
+                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
+                        var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
+                        this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                        if (pickedPoint) {
+                            // depending on the drag mode option drag accordingly
+                            if(this.options.dragAxis){
+                                //get the closest point on the dragaxis from the selected mesh to the picked point location
+                                // https://www.opengl.org/discussion_boards/showthread.php/159717-Closest-point-on-a-Vector-to-a-point
+                                this.options.dragAxis.scaleToRef(BABYLON.Vector3.Dot(pickedPoint.subtract(lastPosition), this.options.dragAxis), delta);
+                            }else{
+                                pickedPoint.subtractToRef(lastPosition, delta);
+                            }
+                            if(this.moveAttached){
+                                (<Mesh>this._attachedNode).position.addInPlace(delta);
+                                
+                            }
+                            this.onDragObservable.notifyObservers({delta: delta, dragPlanePoint: pickedPoint});
+                            lastPosition.copyFrom(pickedPoint);
+                        }
+                    }
+                }
+            });
+        }
+
+        private _pickWithRayOnDragPlane(ray:Nullable<Ray>){
+            if(!ray){
+                return null;
+            }
+            var pickResult = PointerDragBehavior.planeScene.pickWithRay(ray, (m)=>{return m == this._dragPlane})
+            if (pickResult && pickResult.hit && pickResult.pickedMesh && pickResult.pickedPoint) {
+                return pickResult.pickedPoint;
+            }else{
+                return null;
+            }
+        }
+
+        // Position the drag plane based on the attached mesh position, for single axis rotate the plane along the axis to face the camera
+        private _updateDragPlanePosition(ray:Ray){
+            var pointA = this._dragPlaneParent ? this._dragPlaneParent.position : (<Mesh>this._attachedNode).position // center
+            if(this.options.dragAxis){
+                var camPos = ray.origin;
+
+                // Calculate plane normal in direction of camera but perpendicular to drag axis
+                var pointB = pointA.add(this.options.dragAxis); // towards drag axis
+                var pointC = pointA.add(camPos.subtract(pointA).normalize()); // towards camera
+                // Get perpendicular line from direction to camera and drag axis
+                var lineA = pointB.subtract(pointA);
+                var lineB = pointC.subtract(pointA);
+                var perpLine = BABYLON.Vector3.Cross(lineA, lineB);
+                // Get perpendicular line from previous result and drag axis to adjust lineB to be perpendiculat to camera
+                var norm = BABYLON.Vector3.Cross(lineA, perpLine).normalize();
+
+                this._dragPlane.position.copyFrom(pointA);
+                this._dragPlane.lookAt(pointA.add(norm));
+            }else if(this.options.dragPlaneNormal){
+                this._dragPlane.position.copyFrom(pointA);
+                this._dragPlane.lookAt(pointA.add(this.options.dragPlaneNormal));
+            }
+            this._dragPlane.computeWorldMatrix(true);
+        }
+
+        /**
+         *  Detaches the behavior from the mesh
+         */
+        public detach(): void {
+            if(this._pointerObserver){
+                this._scene.onPointerObservable.remove(this._pointerObserver);
+            }
+        }
+    }
+}

+ 24 - 3
src/babylon.scene.ts

@@ -1596,6 +1596,21 @@
         }
 
         // Pointers handling
+        private _pickSpriteButKeepRay(originalPointerInfo:Nullable<PickingInfo>, x: number, y: number, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean, camera?: Camera): Nullable<PickingInfo>{
+            var result = this.pickSprite(x,y,predicate,fastCheck,camera);
+            if(result){
+                result.ray = originalPointerInfo ? originalPointerInfo.ray : null;
+            }
+            return result;
+        }
+
+        private _setRayOnPointerInfo(pointerInfo:PointerInfo){
+            if(pointerInfo.pickInfo){
+                if(!pointerInfo.pickInfo.ray){
+                    pointerInfo.pickInfo.ray = this.createPickingRay(pointerInfo.event.offsetX, pointerInfo.event.offsetY, Matrix.Identity(), this.activeCamera);
+                }
+            }
+        }
 
         /**
          * Use this method to simulate a pointer move on a mesh
@@ -1634,8 +1649,8 @@
             } else {
                 this.setPointerOverMesh(null);
                 // Sprites
-                pickResult = this.pickSprite(this._unTranslatedPointerX, this._unTranslatedPointerY, this._spritePredicate, false, this.cameraToUseForPointers || undefined);
-
+                pickResult = this._pickSpriteButKeepRay(pickResult, this._unTranslatedPointerX, this._unTranslatedPointerY, this._spritePredicate, false, this.cameraToUseForPointers || undefined);
+                
                 if (pickResult && pickResult.hit && pickResult.pickedSprite) {
                     this.setPointerOverSprite(pickResult.pickedSprite);
                     if (this._pointerOverSprite && this._pointerOverSprite.actionManager && this._pointerOverSprite.actionManager.hoverCursor) {
@@ -1659,6 +1674,7 @@
 
                 if (this.onPointerObservable.hasObservers()) {
                     let pi = new PointerInfo(type, evt, pickResult);
+                    this._setRayOnPointerInfo(pi);
                     this.onPointerObservable.notifyObservers(pi, type);
                 }
             }
@@ -1728,6 +1744,7 @@
 
                 if (this.onPointerObservable.hasObservers()) {
                     let pi = new PointerInfo(type, evt, pickResult);
+                    this._setRayOnPointerInfo(pi);
                     this.onPointerObservable.notifyObservers(pi, type);
                 }
             }
@@ -1761,6 +1778,7 @@
                     if (clickInfo.singleClick && !clickInfo.ignore && this.onPointerObservable.hasObservers()) {
                         let type = PointerEventTypes.POINTERPICK;
                         let pi = new PointerInfo(type, evt, pickResult);
+                        this._setRayOnPointerInfo(pi);
                         this.onPointerObservable.notifyObservers(pi, type);
                     }
                 }
@@ -1790,17 +1808,20 @@
                         if (clickInfo.singleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
                             let type = PointerEventTypes.POINTERTAP;
                             let pi = new PointerInfo(type, evt, pickResult);
+                            this._setRayOnPointerInfo(pi);
                             this.onPointerObservable.notifyObservers(pi, type);
                         }
                         if (clickInfo.doubleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
                             let type = PointerEventTypes.POINTERDOUBLETAP;
                             let pi = new PointerInfo(type, evt, pickResult);
+                            this._setRayOnPointerInfo(pi);
                             this.onPointerObservable.notifyObservers(pi, type);
                         }
                     }
                 }
                 else {
                     let pi = new PointerInfo(type, evt, pickResult);
+                    this._setRayOnPointerInfo(pi);
                     this.onPointerObservable.notifyObservers(pi, type);
                 }
             }
@@ -5730,7 +5751,7 @@
                 return this._cachedRayForTransform;
             }, predicate, fastCheck);
             if(result){
-                result.ray = ray;
+                result.ray = new Ray(ray.origin, ray.direction);
             }
             return result;
         }