浏览代码

Refactoring and adding events into Observable and ActionManager classes

-add two masks for onPrePointerObservable and onPointerObservable which are POINTERTAP and POINTERDOUBLETAP
-add a new trigger for ActionManager which is onDoublePickTrigger
-add a static array  for ActionManager class which classified all types of triggers registered into all actionManager instances in a unique way, it's useful in order to prevent mesh picking if it's not requiered
-equivalent methods in ActionManager such as hasPickTriggers or hasSpecificTrigger are now implemented in Observable (Mask instead of Tigger) for the same reason
-move LongPressDelay and add DoubleClickDelay into static fields of Scene class, they are in common between Obervable and ActionManager
-add a flag to Scene to enable exclusive double click mode (double click raises only double click instead of raising a simple click followed by a double click)
yuccai 8 年之前
父节点
当前提交
e772f8bc73
共有 3 个文件被更改,包括 436 次插入90 次删除
  1. 65 13
      src/Actions/babylon.actionManager.ts
  2. 21 0
      src/Tools/babylon.observable.ts
  3. 350 77
      src/babylon.scene.ts

+ 65 - 13
src/Actions/babylon.actionManager.ts

@@ -62,16 +62,17 @@
         private static _OnRightPickTrigger = 3;
         private static _OnCenterPickTrigger = 4;
         private static _OnPickDownTrigger = 5;
-        private static _OnPickUpTrigger = 6;
-        private static _OnLongPressTrigger = 7;
-        private static _OnPointerOverTrigger = 8;
-        private static _OnPointerOutTrigger = 9;
-        private static _OnEveryFrameTrigger = 10;
-        private static _OnIntersectionEnterTrigger = 11;
-        private static _OnIntersectionExitTrigger = 12;
-        private static _OnKeyDownTrigger = 13;
-        private static _OnKeyUpTrigger = 14;
-        private static _OnPickOutTrigger = 15;
+        private static _OnDoublePickTrigger = 6;
+        private static _OnPickUpTrigger = 7;
+        private static _OnLongPressTrigger = 8;
+        private static _OnPointerOverTrigger = 9;
+        private static _OnPointerOutTrigger = 10;
+        private static _OnEveryFrameTrigger = 11;
+        private static _OnIntersectionEnterTrigger = 12;
+        private static _OnIntersectionExitTrigger = 13;
+        private static _OnKeyDownTrigger = 14;
+        private static _OnKeyUpTrigger = 15;
+        private static _OnPickOutTrigger = 16;
 
         public static get NothingTrigger(): number {
             return ActionManager._NothingTrigger;
@@ -97,6 +98,10 @@
             return ActionManager._OnPickDownTrigger;
         }
 
+        public static get OnDoublePickTrigger(): number {
+            return ActionManager._OnDoublePickTrigger;
+        }
+
         public static get OnPickUpTrigger(): number {
             return ActionManager._OnPickUpTrigger;
         }
@@ -138,9 +143,8 @@
             return ActionManager._OnKeyUpTrigger;
         }
 
-        public static DragMovementThreshold = 10; // in pixels
-        public static LongPressDelay = 500; // in milliseconds
-        
+        public static Triggers = {};
+
         // Members
         public actions = new Array<Action>();
 
@@ -158,6 +162,14 @@
         public dispose(): void {
             var index = this._scene._actionManagers.indexOf(this);
 
+            for (var i = 0; i < this.actions.length; i++) {
+                var action = this.actions[i];
+                ActionManager.Triggers[action.trigger]--;
+                if (ActionManager.Triggers[action.trigger] === 0) {
+                    delete ActionManager.Triggers[action.trigger]
+                }
+            }
+
             if (index > -1) {
                 this._scene._actionManagers.splice(index, 1);
             }
@@ -233,6 +245,39 @@
             return false;
         }
 
+        public static get HasTriggers(): boolean {
+            for (var t in ActionManager.Triggers) {
+                if (ActionManager.Triggers.hasOwnProperty(t)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public static get HasPickTriggers(): boolean {
+            for (var t in ActionManager.Triggers) {
+                if (ActionManager.Triggers.hasOwnProperty(t)) {
+                    let t_int = parseInt(t);
+                    if (t_int >= ActionManager._OnPickTrigger && t_int <= ActionManager._OnPickUpTrigger) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        public static HasSpecificTrigger(trigger: number): boolean {
+            for (var t in ActionManager.Triggers) {
+                if (ActionManager.Triggers.hasOwnProperty(t)) {
+                    let t_int = parseInt(t);
+                    if (t_int === trigger) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
         /**
          * Registers an action to this action manager
          * @param {BABYLON.Action} action - the action to be registered
@@ -248,6 +293,13 @@
 
             this.actions.push(action);
 
+          if(ActionManager.Triggers[action.trigger]) {
+              ActionManager.Triggers[action.trigger]++;
+            }
+            else{
+              ActionManager.Triggers[action.trigger] = 1;
+            }
+
             action._actionManager = this;
             action._prepare();
 

+ 21 - 0
src/Tools/babylon.observable.ts

@@ -154,6 +154,27 @@
 
             return result;
         }
+
+        public hasPickMasks(): boolean {
+            for (var obs of this._observers) {
+                if (obs.mask && (obs.mask === PointerEventTypes.POINTERPICK// ||
+                                // obs.mask === PointerEventTypes.POINTERTAP  ||
+                                // obs.mask === PointerEventTypes.POINTERDOUBLETAP
+                                )) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public hasSpecificMask(mask: number = -1): boolean {
+            for (var obs of this._observers) {
+                if (obs.mask & mask && obs.mask === mask) {
+                    return true;
+                }
+            }
+            return false;
+        }
     }
 
 }

+ 350 - 77
src/babylon.scene.ts

@@ -3,12 +3,47 @@
         dispose(): void;
     }
 
+    class ClickInfo {
+        private _singleClick = false;
+        private _doubleClick = false;
+        private _hasSwiped = false;
+        private _ignore = false;
+
+        public get singleClick(): boolean {
+            return this._singleClick;
+        }
+        public get doubleClick(): boolean {
+            return this._doubleClick;
+        }
+        public get hasSwiped(): boolean{
+            return this._hasSwiped;
+        }
+        public get ignore(): boolean{
+            return this._ignore;
+        }
+
+        public set singleClick(b: boolean) {
+            this._singleClick = b;
+        }
+        public set doubleClick(b: boolean) {
+            this._doubleClick = b;
+        }
+        public set hasSwiped(b: boolean) {
+            this._hasSwiped = b;
+        }
+        public set ignore(b: boolean) {
+            this._ignore = b;
+        }
+    }
+
     export class PointerEventTypes {
         static _POINTERDOWN = 0x01;
         static _POINTERUP = 0x02;
         static _POINTERMOVE = 0x04;
         static _POINTERWHEEL = 0x08;
         static _POINTERPICK = 0x10;
+        static _POINTERTAP = 0x20;
+        static _POINTERDOUBLETAP = 0x40;
 
         public static get POINTERDOWN(): number {
             return PointerEventTypes._POINTERDOWN;
@@ -29,6 +64,14 @@
         public static get POINTERPICK(): number {
             return PointerEventTypes._POINTERPICK;
         }
+        
+        public static get POINTERTAP(): number {
+            return PointerEventTypes._POINTERTAP;
+        }
+
+        public static get POINTERDOUBLETAP(): number {
+            return PointerEventTypes._POINTERDOUBLETAP;
+        }
     }
 
     export class PointerInfoBase {
@@ -324,13 +367,34 @@
             return new Vector2(this._unTranslatedPointerX, this._unTranslatedPointerY);
         }
 
+        public static DragMovementThreshold = 10; // in pixels
+        public static LongPressDelay = 500; // in milliseconds
+        public static DoubleClickDelay = 300; // in milliseconds
+
+        private _initClickEvent: (obs1: Observable<PointerInfoPre>, obs2: Observable<PointerInfo>, evt: PointerEvent, cb: (clickInfo: ClickInfo, pickResult: PointerInfo) => void) => void;
+        private _initActionManager: (act: ActionManager, clickInfo: ClickInfo) => ActionManager;
+        private _delayedSimpleClick: (btn: number, clickInfo: ClickInfo, cb: (clickInfo: ClickInfo, pickResult: PointerInfo) => void) => void;
+        private _delayedSimpleClickTimeout;
+        private _previousDelayedSimpleClickTimeout;
+        private _meshPickProceed = false;
+
+        private _previousButtonPressed;
+        private _previousHasSwiped = false;
+        private _currentPickResult = null;
+        private _previousPickResult = null;
+        private _isButtonPressed = false;
+        private _doubleClickOccured = false;
+        public exclusiveDoubleClickMode = false;
+
         public cameraToUseForPointers: Camera = null; // Define this parameter if you are using multiple cameras and you want to specify which one should be used for pointer position
         private _pointerX: number;
         private _pointerY: number;
         private _unTranslatedPointerX: number;
         private _unTranslatedPointerY: number;
         private _startingPointerPosition = new Vector2(0, 0);
+        private _previousStartingPointerPosition = new Vector2(0, 0);
         private _startingPointerTime = 0;
+        private _previousStartingPointerTime = 0;
         // Mirror
         public _mirroredCameraPosition: Vector3;
 
@@ -549,6 +613,7 @@
         private _uniqueIdCounter = 0;
 
         private _pickedDownMesh: AbstractMesh;
+        private _pickedUpMesh: AbstractMesh;
         private _pickedDownSprite: Sprite;
         private _externalData: StringDictionary<Object>;
         private _uid: string;
@@ -779,6 +844,159 @@
         * @param attachMove defines if you want to attach events to pointermove
         */
         public attachControl(attachUp = true, attachDown = true, attachMove = true) {
+            this._initActionManager = (act: ActionManager, clickInfo: ClickInfo): ActionManager => {
+                if (!this._meshPickProceed) {
+                    let pickResult = this.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, this.pointerDownPredicate, false, this.cameraToUseForPointers);
+                    this._currentPickResult = pickResult;
+                    act = (pickResult.hit && pickResult.pickedMesh) ? pickResult.pickedMesh.actionManager : null;
+                    this._meshPickProceed = true;
+                }
+                return act;
+            };
+
+            this._delayedSimpleClick = (btn: number, clickInfo: ClickInfo, cb: (clickInfo: ClickInfo, pickResult: PointerInfo) => void) => {
+                // double click delay is over and that no double click has been raised since, or the 2 consecutive keys pressed are different
+                if ((new Date().getTime() - this._previousStartingPointerTime > Scene.DoubleClickDelay && !this._doubleClickOccured) ||
+                btn !== this._previousButtonPressed ) {
+                    this._doubleClickOccured = false;
+                    clickInfo.singleClick = true;
+                    clickInfo.ignore = false;
+                    cb(clickInfo, this._currentPickResult);
+                }
+            }
+
+            this._initClickEvent = (obs1: Observable<PointerInfoPre>, obs2: Observable<PointerInfo>, evt: PointerEvent, cb: (clickInfo: ClickInfo, pickResult: PointerInfo) => void): void => {
+                    let clickInfo = new ClickInfo();
+                    this._currentPickResult = null;
+                    let act;
+
+                    let checkPicking = obs1.hasPickMasks() || obs2.hasPickMasks();
+                    if (!checkPicking && ActionManager.HasPickTriggers) {
+                        act = this._initActionManager(act, clickInfo);
+                        if (act)
+                            checkPicking = act.hasPickTriggers;
+                    }
+                    if (checkPicking) {
+                        let btn = evt.button;
+                        clickInfo.hasSwiped = Math.abs(this._startingPointerPosition.x - this._pointerX) > Scene.DragMovementThreshold ||
+                                              Math.abs(this._startingPointerPosition.y - this._pointerY) > Scene.DragMovementThreshold;
+
+                        if (!clickInfo.hasSwiped) {
+                            let checkSingleClickImmediately = !this.exclusiveDoubleClickMode;
+
+                            if (!checkSingleClickImmediately) {
+                                checkSingleClickImmediately = !obs1.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP) &&
+                                                              !obs2.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP);
+
+                                if (checkSingleClickImmediately && !ActionManager.HasSpecificTrigger(ActionManager.OnDoublePickTrigger)) {
+                                    act = this._initActionManager(act, clickInfo);
+                                    if (act)
+                                        checkSingleClickImmediately = !act.hasSpecificTrigger(ActionManager.OnDoublePickTrigger);
+                                }
+                            }
+
+                            if (checkSingleClickImmediately) {
+                                // single click detected if double click delay is over or two different successive keys pressed without exclusive double click or no double click required
+                                if (new Date().getTime() - this._previousStartingPointerTime > Scene.DoubleClickDelay ||
+                                    btn !== this._previousButtonPressed ) {
+                                        clickInfo.singleClick = true;
+                                        cb(clickInfo, this._currentPickResult);
+                                }
+                            }
+                            // at least one double click is required to be check and exclusive double click is enabled
+                            else {
+                                // wait that no double click has been raised during the double click delay
+                                this._previousDelayedSimpleClickTimeout = this._delayedSimpleClickTimeout;
+                                this._delayedSimpleClickTimeout = window.setTimeout(this._delayedSimpleClick.bind(this, btn, clickInfo, cb), Scene.DoubleClickDelay);
+                            }
+
+                            let checkDoubleClick = obs1.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP) ||
+                                                   obs2.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP);
+                            if (!checkDoubleClick && ActionManager.HasSpecificTrigger(ActionManager.OnDoublePickTrigger)){
+                                act = this._initActionManager(act, clickInfo);
+                                if (act)
+                                    checkDoubleClick = act.hasSpecificTrigger(ActionManager.OnDoublePickTrigger);
+                            }
+                            if (checkDoubleClick) {
+                                // two successive keys pressed are equal, double click delay is not over and double click has not just occurred
+                                if (btn === this._previousButtonPressed &&
+                                    new Date().getTime() - this._previousStartingPointerTime < Scene.DoubleClickDelay &&
+                                    !this._doubleClickOccured
+                                ) {
+                                    // pointer has not moved for 2 clicks, it's a double click
+                                    if (!clickInfo.hasSwiped &&
+                                        Math.abs(this._previousStartingPointerPosition.x - this._startingPointerPosition.x) < Scene.DragMovementThreshold &&
+                                        Math.abs(this._previousStartingPointerPosition.y - this._startingPointerPosition.y) < Scene.DragMovementThreshold) {
+                                        this._previousStartingPointerTime = 0;
+                                        this._doubleClickOccured = true;
+                                        clickInfo.doubleClick = true;
+                                        clickInfo.ignore = false;
+                                        if (this.exclusiveDoubleClickMode && this._previousDelayedSimpleClickTimeout && this._previousDelayedSimpleClickTimeout.clearTimeout)
+                                            this._previousDelayedSimpleClickTimeout.clearTimeout();
+                                        this._previousDelayedSimpleClickTimeout = this._delayedSimpleClickTimeout;
+                                        cb(clickInfo, this._currentPickResult);
+                                    }
+                                    // if the two successive clicks are too far, it's just two simple clicks
+                                    else {
+                                        this._doubleClickOccured = false;
+                                        this._previousStartingPointerTime = this._startingPointerTime;
+                                        this._previousStartingPointerPosition.x = this._startingPointerPosition.x;
+                                        this._previousStartingPointerPosition.y = this._startingPointerPosition.y;
+                                        this._previousButtonPressed = btn;
+                                        this._previousHasSwiped = clickInfo.hasSwiped;
+                                        if (this.exclusiveDoubleClickMode){
+                                            if (this._previousDelayedSimpleClickTimeout && this._previousDelayedSimpleClickTimeout.clearTimeout) {
+                                                this._previousDelayedSimpleClickTimeout.clearTimeout();
+                                            }
+                                            this._previousDelayedSimpleClickTimeout = this._delayedSimpleClickTimeout;
+                                            cb(clickInfo, this._previousPickResult);
+                                        }
+                                        else {
+                                            cb(clickInfo, this._currentPickResult);
+                                        }
+                                    }
+                                }
+                                // just the first click of the double has been raised
+                                else {
+                                    this._doubleClickOccured = false;
+                                    this._previousStartingPointerTime = this._startingPointerTime;
+                                    this._previousStartingPointerPosition.x = this._startingPointerPosition.x;
+                                    this._previousStartingPointerPosition.y = this._startingPointerPosition.y;
+                                    this._previousButtonPressed = btn;
+                                    this._previousHasSwiped = clickInfo.hasSwiped;
+                                }
+                            }
+                        }
+                    }
+                    clickInfo.ignore = true;
+                    cb(clickInfo, this._currentPickResult);
+
+                    /*
+                    let checkLongPicking = obs1.hasSpecificMask(PointerEventTypes.POINTERLONGPRESS) ||
+                    obs2.hasSpecificMask(PointerEventTypes.POINTERLONGPRESS);
+                    if (!checkLongPicking){
+                        act = this._initActionManager(act);
+                        if (act)
+                        checkLongPicking = act.hasSpecificTrigger(ActionManager.OnLongPressTrigger);
+                    }
+                    if (checkLongPicking) {
+                        window.setTimeout((function () {
+                            if (this._startingPointerTime !== 0 &&
+                                new Date().getTime() - this._startingPointerTime           > Scene.LongPressDelay &&
+                                Math.abs(this._startingPointerPosition.x - this._pointerX) < Scene.DragMovementThreshold &&
+                                Math.abs(this._startingPointerPosition.y - this._pointerY) < Scene.DragMovementThreshold
+                            ) {
+                                //this._startingPointerTime = 0;
+                                //let type = PointerEventTypes.POINTERLONGPRESS;
+                                //let pi = new PointerInfo(type, evt, pickResult);
+                                //this.onPointerObservable.notifyObservers(pi, type);
+                                this.getEngine().getRenderingCanvas().dispatchEvent(new Event("longpress"));
+                            }
+                        }).bind(this), Scene.LongPressDelay);
+                    }
+                    */
+            };
+
             var spritePredicate = (sprite: Sprite): boolean => {
                 return sprite.isPickable && sprite.actionManager && sprite.actionManager.hasPointerTriggers;
             };
@@ -855,6 +1073,10 @@
             };
 
             this._onPointerDown = (evt: PointerEvent) => {
+                this._isButtonPressed = true;
+                this._pickedDownMesh = null;
+                this._meshPickProceed = false;
+
                 this._updatePointerPosition(evt);
 
                 // PreObservable support
@@ -887,40 +1109,39 @@
 
                 if (pickResult.hit && pickResult.pickedMesh) {
                     this._pickedDownMesh = pickResult.pickedMesh;
-                    if (pickResult.pickedMesh.actionManager) {
-                        if (pickResult.pickedMesh.actionManager.hasPickTriggers) {
+                    var actionManager = pickResult.pickedMesh.actionManager;
+                    if (actionManager) {
+                        if (actionManager.hasPickTriggers) {
+                            actionManager.processTrigger(ActionManager.OnPickDownTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                             switch (evt.button) {
                                 case 0:
-                                    pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                                    actionManager.processTrigger(ActionManager.OnLeftPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                                     break;
                                 case 1:
-                                    pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                                    actionManager.processTrigger(ActionManager.OnCenterPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                                     break;
                                 case 2:
-                                    pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnRightPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                                    actionManager.processTrigger(ActionManager.OnRightPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                                     break;
                             }
-                            if (pickResult.pickedMesh.actionManager) {
-                                pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickDownTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
-                            }
                         }
 
-                        if (pickResult.pickedMesh.actionManager && pickResult.pickedMesh.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
-                            var that = this;
-                            window.setTimeout(function () {
-                                var pickResult = that.pick(that._unTranslatedPointerX, that._unTranslatedPointerY,
-                                    (mesh: AbstractMesh): boolean => mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger),
-                                    false, that.cameraToUseForPointers);
+                        if (actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
+                            window.setTimeout((function () {
+                                var pickResult = this.pick(this._unTranslatedPointerX, this._unTranslatedPointerY,
+                                    (mesh: AbstractMesh): boolean => mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger) && mesh == this._pickedDownMesh,
+                                    false, this.cameraToUseForPointers);
 
                                 if (pickResult.hit && pickResult.pickedMesh) {
-                                    if (pickResult.pickedMesh.actionManager) {
-                                        if (that._startingPointerTime !== 0 && ((new Date().getTime() - that._startingPointerTime) > ActionManager.LongPressDelay) && (Math.abs(that._startingPointerPosition.x - that._pointerX) < ActionManager.DragMovementThreshold && Math.abs(that._startingPointerPosition.y - that._pointerY) < ActionManager.DragMovementThreshold)) {
-                                            that._startingPointerTime = 0;
-                                            pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
-                                        }
+                                    if (this._isButtonPressed &&
+                                        ((new Date().getTime() - this._startingPointerTime) > Scene.LongPressDelay) &&
+                                        (Math.abs(this._startingPointerPosition.x - this._pointerX) < Scene.DragMovementThreshold &&
+                                         Math.abs(this._startingPointerPosition.y - this._pointerY) < Scene.DragMovementThreshold)) {
+                                            this._startingPointerTime = 0;
+                                            actionManager.processTrigger(ActionManager.OnLongPressTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                                     }
                                 }
-                            }, ActionManager.LongPressDelay);
+                            }).bind(this), Scene.LongPressDelay);
                         }
                     }
                 }
@@ -963,85 +1184,137 @@
             };
 
             this._onPointerUp = (evt: PointerEvent) => {
+                this._isButtonPressed = false;
+                this._pickedUpMesh = null;
+                this._meshPickProceed = false;
+
                 this._updatePointerPosition(evt);
 
-                // PreObservable support
-                if (this.onPrePointerObservable.hasObservers()) {
-                    let type = PointerEventTypes.POINTERUP;
-                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                    this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
+                this._initClickEvent(this.onPrePointerObservable, this.onPointerObservable,  evt, (function(clickInfo, pickResult){
+                    // PreObservable support
+                    if (this.onPrePointerObservable.hasObservers()) {
+                        if (!clickInfo.ignore) {
+                            if (!clickInfo.hasSwiped) {
+                                if (clickInfo.singleClick && this.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
+                                    let type = PointerEventTypes.POINTERTAP;
+                                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+                                    this.onPrePointerObservable.notifyObservers(pi, type);
+                                    if (pi.skipOnPointerObservable) {
+                                        return;
+                                    }
+                                }
+                                if (clickInfo.doubleClick && this.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
+                                    let type = PointerEventTypes.POINTERDOUBLETAP;
+                                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+                                    this.onPrePointerObservable.notifyObservers(pi, type);
+                                    if (pi.skipOnPointerObservable) {
+                                        return;
+                                    }
+                                }
+                            }
+                        }
+                        else {
+                            let type = PointerEventTypes.POINTERUP;
+                            let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+                            this.onPrePointerObservable.notifyObservers(pi, type);
+                            if (pi.skipOnPointerObservable) {
+                                return;
+                            }
+                        }
                     }
-                }
 
-                if (!this.cameraToUseForPointers && !this.activeCamera) {
-                    return;
-                }
+                    if (!this.cameraToUseForPointers && !this.activeCamera) {
+                        return;
+                    }
 
-                if (!this.pointerUpPredicate) {
-                    this.pointerUpPredicate = (mesh: AbstractMesh): boolean => {
-                        return mesh.isPickable && mesh.isVisible && mesh.isReady();
-                    };
-                }
+                    if (!this.pointerUpPredicate) {
+                        this.pointerUpPredicate = (mesh: AbstractMesh): boolean => {
+                            return mesh.isPickable && mesh.isVisible && mesh.isReady();
+                        };
+                    }
 
-                // Meshes
-                var pickResult = this.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, this.pointerUpPredicate, false, this.cameraToUseForPointers);
+                    // Meshes
+                    if (!this._meshPickProceed && ActionManager.HasTriggers) {
+                        this._initActionManager(null, clickInfo);
+                    }
+                    if (!pickResult) {
+                        pickResult = this._currentPickResult;
+                    }
 
-                if (pickResult.hit && pickResult.pickedMesh) {
-                    if (this._pickedDownMesh != null && pickResult.pickedMesh == this._pickedDownMesh) {
-                        if (this.onPointerPick) {
-                            this.onPointerPick(evt, pickResult);
-                        }
-                        if (this.onPointerObservable.hasObservers()) {
-                            let type = PointerEventTypes.POINTERPICK;
-                            let pi = new PointerInfo(type, evt, pickResult);
-                            this.onPointerObservable.notifyObservers(pi, type);
+                    if (pickResult.pickedMesh) {
+                        this._pickedUpMesh = pickResult.pickedMesh;
+                        if (this._pickedDownMesh === this._pickedUpMesh) {
+                            if (this.onPointerPick) {
+                                this.onPointerPick(evt, pickResult);
+                            }
+                            if (clickInfo.singleClick && !clickInfo.ignore && this.onPointerObservable.hasObservers()) {
+                                let type = PointerEventTypes.POINTERPICK;
+                                let pi = new PointerInfo(type, evt, pickResult);
+                                this.onPointerObservable.notifyObservers(pi, type);
+                            }
                         }
-                    }
-                    if (pickResult.pickedMesh.actionManager) {
-                        pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                         if (pickResult.pickedMesh.actionManager) {
-                            if (Math.abs(this._startingPointerPosition.x - this._pointerX) < ActionManager.DragMovementThreshold && Math.abs(this._startingPointerPosition.y - this._pointerY) < ActionManager.DragMovementThreshold) {
+                            if (clickInfo.ignore) {
+                                pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                            }
+                            if (!clickInfo.hasSwiped && !clickInfo.ignore && clickInfo.singleClick) {
                                 pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                             }
+                            if (this._pickedDownMesh !== this._pickedUpMesh) {
+                                pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickOutTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                            }
+                            if (clickInfo.doubleClick && !clickInfo.ignore && pickResult.pickedMesh.actionManager.hasSpecificTrigger(ActionManager.OnDoublePickTrigger)) {
+                                pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnDoublePickTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
+                            }
                         }
                     }
-                }
-                if (this._pickedDownMesh && this._pickedDownMesh.actionManager && this._pickedDownMesh !== pickResult.pickedMesh) {
-                    this._pickedDownMesh.actionManager.processTrigger(ActionManager.OnPickOutTrigger, ActionEvent.CreateNew(this._pickedDownMesh, evt));
-                }
 
-                if (this.onPointerUp) {
-                    this.onPointerUp(evt, pickResult);
-                }
-
-                if (this.onPointerObservable.hasObservers()) {
-                    let type = PointerEventTypes.POINTERUP;
-                    let pi = new PointerInfo(type, evt, pickResult);
-                    this.onPointerObservable.notifyObservers(pi, type);
-                }
+                    if (this.onPointerUp) {
+                        this.onPointerUp(evt, pickResult);
+                    }
 
-                this._startingPointerTime = 0;
+                    if (this.onPointerObservable.hasObservers()) {
+                        if (!clickInfo.ignore) {
+                            if (!clickInfo.hasSwiped) {
+                                if (clickInfo.singleClick && this.onPointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
+                                    let type = PointerEventTypes.POINTERTAP;
+                                    let pi = new PointerInfo(type, evt, pickResult);
+                                    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.onPointerObservable.notifyObservers(pi, type);
+                                }
+                            }
+                        }
+                        else {
+                            let type = PointerEventTypes.POINTERUP;
+                            let pi = new PointerInfo(type, evt, pickResult);
+                            this.onPointerObservable.notifyObservers(pi, type);
+                        }
+                    }
 
-                // Sprites
-                if (this.spriteManagers.length > 0) {
-                    pickResult = this.pickSprite(this._unTranslatedPointerX, this._unTranslatedPointerY, spritePredicate, false, this.cameraToUseForPointers);
+                    // Sprites
+                    if (this.spriteManagers.length > 0) {
+                        pickResult = this.pickSprite(this._unTranslatedPointerX, this._unTranslatedPointerY, spritePredicate, false, this.cameraToUseForPointers);
 
-                    if (pickResult.hit && pickResult.pickedSprite) {
-                        if (pickResult.pickedSprite.actionManager) {
-                            pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                        if (pickResult.hit && pickResult.pickedSprite) {
                             if (pickResult.pickedSprite.actionManager) {
-                                if (Math.abs(this._startingPointerPosition.x - this._pointerX) < ActionManager.DragMovementThreshold && Math.abs(this._startingPointerPosition.y - this._pointerY) < ActionManager.DragMovementThreshold) {
-                                    pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                if (pickResult.pickedSprite.actionManager) {
+                                    if (Math.abs(this._startingPointerPosition.x - this._pointerX) < Scene.DragMovementThreshold && Math.abs(this._startingPointerPosition.y - this._pointerY) < Scene.DragMovementThreshold) {
+                                        pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                    }
                                 }
                             }
                         }
+                        if (this._pickedDownSprite && this._pickedDownSprite.actionManager && this._pickedDownSprite !== pickResult.pickedSprite) {
+                            this._pickedDownSprite.actionManager.processTrigger(ActionManager.OnPickOutTrigger, ActionEvent.CreateNewFromSprite(this._pickedDownSprite, this, evt));
+                        }
                     }
-                    if (this._pickedDownSprite && this._pickedDownSprite.actionManager && this._pickedDownSprite !== pickResult.pickedSprite) {
-                        this._pickedDownSprite.actionManager.processTrigger(ActionManager.OnPickOutTrigger, ActionEvent.CreateNewFromSprite(this._pickedDownSprite, this, evt));
-                    }
-                }
+                    this._previousPickResult = this._currentPickResult;
+                }).bind(this));
             };
 
             this._onKeyDown = (evt: Event) => {