浏览代码

Adding picking and actions for sprites

David Catuhe 10 年之前
父节点
当前提交
df358d1e07

文件差异内容过多而无法显示
+ 1400 - 1386
dist/preview release/babylon.d.ts


文件差异内容过多而无法显示
+ 16 - 17
dist/preview release/babylon.js


+ 140 - 10
dist/preview release/babylon.max.js

@@ -7461,6 +7461,7 @@ var BABYLON;
             this.bv = 0;
             this.faceId = -1;
             this.subMeshId = 0;
+            this.pickedSprite = null;
         }
         // Methods
         PickingInfo.prototype.getNormal = function (useWorldCoordinates, useVerticesNormals) {
@@ -12846,11 +12847,15 @@ var BABYLON;
         // Pointers handling
         Scene.prototype.attachControl = function () {
             var _this = this;
+            var spritePredicate = function (sprite) {
+                return sprite.isPickable && sprite.actionManager && sprite.actionManager.hasPickTriggers;
+            };
             this._onPointerMove = function (evt) {
                 var canvas = _this._engine.getRenderingCanvas();
                 _this._updatePointerPosition(evt);
+                // Meshes
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, function (mesh) { return mesh.isPickable && mesh.isVisible && mesh.isReady(); }, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     _this._meshUnderPointer = pickResult.pickedMesh;
                     _this.setPointerOverMesh(pickResult.pickedMesh);
                     if (_this._meshUnderPointer.actionManager && _this._meshUnderPointer.actionManager.hasPointerTriggers) {
@@ -12861,21 +12866,29 @@ var BABYLON;
                     }
                 }
                 else {
+                    // Sprites
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        canvas.style.cursor = "pointer";
+                        return;
+                    }
+                    // Restore pointer
                     _this.setPointerOverMesh(null);
                     canvas.style.cursor = "";
                     _this._meshUnderPointer = null;
                 }
             };
             this._onPointerDown = function (evt) {
+                _this._updatePointerPosition(evt);
                 var predicate = null;
+                // Meshes
                 if (!_this.onPointerDown) {
                     predicate = function (mesh) {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasPickTriggers;
                     };
                 }
-                _this._updatePointerPosition(evt);
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, predicate, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         switch (evt.button) {
                             case 0:
@@ -12894,17 +12907,38 @@ var BABYLON;
                 if (_this.onPointerDown) {
                     _this.onPointerDown(evt, pickResult);
                 }
+                // Sprites
+                if (_this.spriteManagers.length > 0) {
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        if (pickResult.pickedSprite.actionManager) {
+                            switch (evt.button) {
+                                case 0:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnLeftPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                                case 1:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnCenterPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                                case 2:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnRightPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                            }
+                            pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                        }
+                    }
+                }
             };
             this._onPointerUp = function (evt) {
                 var predicate = null;
+                _this._updatePointerPosition(evt);
                 if (!_this.onPointerUp) {
                     predicate = function (mesh) {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasSpecificTrigger(BABYLON.ActionManager.OnPickUpTrigger);
                     };
                 }
-                _this._updatePointerPosition(evt);
+                // Meshes
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, predicate, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                     }
@@ -12912,6 +12946,15 @@ var BABYLON;
                 if (_this.onPointerUp) {
                     _this.onPointerUp(evt, pickResult);
                 }
+                // Sprites
+                if (_this.spriteManagers.length > 0) {
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        if (pickResult.pickedSprite.actionManager) {
+                            pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                        }
+                    }
+                }
             };
             this._onKeyDown = function (evt) {
                 if (_this.actionManager) {
@@ -14203,6 +14246,28 @@ var BABYLON;
             }
             return pickingInfo || new BABYLON.PickingInfo();
         };
+        Scene.prototype._internalPickSprites = function (rayFunction, predicate, fastCheck) {
+            var pickingInfo = null;
+            if (this.spriteManagers.length > 0) {
+                var ray = rayFunction(BABYLON.Matrix.Identity());
+                for (var spriteIndex = 0; spriteIndex < this.spriteManagers.length; spriteIndex++) {
+                    var spriteManager = this.spriteManagers[spriteIndex];
+                    if (!spriteManager.isPickable) {
+                        continue;
+                    }
+                    var result = spriteManager.intersects(ray, predicate, fastCheck);
+                    if (!result || !result.hit)
+                        continue;
+                    if (!fastCheck && pickingInfo != null && result.distance >= pickingInfo.distance)
+                        continue;
+                    pickingInfo = result;
+                    if (fastCheck) {
+                        break;
+                    }
+                }
+            }
+            return pickingInfo || new BABYLON.PickingInfo();
+        };
         Scene.prototype.pick = function (x, y, predicate, fastCheck, camera) {
             var _this = this;
             /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
@@ -14213,6 +14278,16 @@ var BABYLON;
             /// <param name="camera">camera to use for computing the picking ray. Can be set to null. In this case, the scene.activeCamera will be used</param>
             return this._internalPick(function (world) { return _this.createPickingRay(x, y, world, camera); }, predicate, fastCheck);
         };
+        Scene.prototype.pickSprite = function (x, y, predicate, fastCheck, camera) {
+            var _this = this;
+            /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
+            /// <param name="x">X position on screen</param>
+            /// <param name="y">Y position on screen</param>
+            /// <param name="predicate">Predicate function used to determine eligible sprites. Can be set to null. In this case, a sprite must have isPickable set to true</param>
+            /// <param name="fastCheck">Launch a fast check only using the bounding boxes. Can be set to null.</param>
+            /// <param name="camera">camera to use for computing the picking ray. Can be set to null. In this case, the scene.activeCamera will be used</param>
+            return this._internalPickSprites(function (world) { return _this.createPickingRay(x, y, world, camera); }, predicate, fastCheck);
+        };
         Scene.prototype.pickWithRay = function (ray, predicate, fastCheck) {
             var _this = this;
             return this._internalPick(function (world) {
@@ -21701,6 +21776,7 @@ var BABYLON;
             this.renderingGroupId = 0;
             this.layerMask = 0x0FFFFFFF;
             this.fogEnabled = true;
+            this.isPickable = false;
             this._vertexDeclaration = [4, 4, 4, 4];
             this._vertexStrideSize = 16 * 4; // 15 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellIndexX, cellIndexY, color)
             this._capacity = capacity;
@@ -21758,6 +21834,47 @@ var BABYLON;
             this._vertices[arrayOffset + 14] = sprite.color.b;
             this._vertices[arrayOffset + 15] = sprite.color.a;
         };
+        SpriteManager.prototype.intersects = function (ray, predicate, fastCheck) {
+            var count = Math.min(this._capacity, this.sprites.length);
+            var min = BABYLON.Vector3.Zero();
+            var max = BABYLON.Vector3.Zero();
+            var distance = Number.MAX_VALUE;
+            var currentSprite;
+            for (var index = 0; index < count; index++) {
+                var sprite = this.sprites[index];
+                if (!sprite) {
+                    continue;
+                }
+                if (predicate) {
+                    if (!predicate(sprite)) {
+                        continue;
+                    }
+                }
+                else if (!sprite.isPickable) {
+                    continue;
+                }
+                min.copyFromFloats(sprite.position.x - sprite.width / 2, sprite.position.y - sprite.height / 2, sprite.position.z);
+                max.copyFromFloats(sprite.position.x + sprite.width / 2, sprite.position.y + sprite.height / 2, sprite.position.z);
+                if (ray.intersectsBoxMinMax(min, max)) {
+                    var currentDistance = BABYLON.Vector3.Distance(sprite.position, ray.origin);
+                    if (distance > currentDistance) {
+                        distance = currentDistance;
+                        currentSprite = sprite;
+                        if (fastCheck) {
+                            break;
+                        }
+                    }
+                }
+            }
+            if (currentSprite) {
+                var result = new BABYLON.PickingInfo();
+                result.hit = true;
+                result.pickedSprite = currentSprite;
+                result.distance = distance;
+                return result;
+            }
+            return null;
+        };
         SpriteManager.prototype.render = function () {
             // Check
             if (!this._effectBase.isReady() || !this._effectFog.isReady() || !this._spriteTexture || !this._spriteTexture.isReady())
@@ -21849,6 +21966,7 @@ var BABYLON;
             this.invertU = 0;
             this.invertV = 0;
             this.animations = new Array();
+            this.isPickable = true;
             this._animationStarted = false;
             this._loopAnimation = false;
             this._fromIndex = 0;
@@ -28839,9 +28957,9 @@ var BABYLON;
     var ActionEvent = (function () {
         /**
          * @constructor
-         * @param source The mesh that triggered the action.
-         * @param pointerX the X mouse cursor position at the time of the event
-         * @param pointerY the Y mouse cursor position at the time of the event
+         * @param source The mesh or sprite that triggered the action.
+         * @param pointerX The X mouse cursor position at the time of the event
+         * @param pointerY The Y mouse cursor position at the time of the event
          * @param meshUnderPointer The mesh that is currently pointed at (can be null)
          * @param sourceEvent the original (browser) event that triggered the ActionEvent
          */
@@ -28855,7 +28973,7 @@ var BABYLON;
         }
         /**
          * Helper function to auto-create an ActionEvent from a source mesh.
-         * @param source the source mesh that triggered the event
+         * @param source The source mesh that triggered the event
          * @param evt {Event} The original (browser) event
          */
         ActionEvent.CreateNew = function (source, evt, additionalData) {
@@ -28863,6 +28981,15 @@ var BABYLON;
             return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
         };
         /**
+         * Helper function to auto-create an ActionEvent from a source mesh.
+         * @param source The source sprite that triggered the event
+         * @param scene Scene associated with the sprite
+         * @param evt {Event} The original (browser) event
+         */
+        ActionEvent.CreateNewFromSprite = function (source, scene, evt, additionalData) {
+            return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
+        };
+        /**
          * Helper function to auto-create an ActionEvent from a scene. If triggered by a mesh use ActionEvent.CreateNew
          * @param scene the scene where the event occurred
          * @param evt {Event} The original (browser) event
@@ -29024,7 +29151,7 @@ var BABYLON;
                     if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnPointerOutTrigger) {
                         return true;
                     }
-                    if (action.trigger == ActionManager._OnPickUpTrigger) {
+                    if (action.trigger === ActionManager._OnPickUpTrigger) {
                         return true;
                     }
                 }
@@ -29044,6 +29171,9 @@ var BABYLON;
                     if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnCenterPickTrigger) {
                         return true;
                     }
+                    if (action.trigger === ActionManager._OnPickUpTrigger) {
+                        return true;
+                    }
                 }
                 return false;
             },

文件差异内容过多而无法显示
+ 16 - 17
dist/preview release/babylon.noworker.js


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

@@ -2,7 +2,9 @@
   - **Major updates**
     - New `StandardMaterial.lightmapTexture` which can be controlled with `StandardMaterial.lightmapThreshold`. [Demo here](#NEEDDEMO) ([deltakosh](https://github.com/deltakosh))
     - Support for reflection probes. [See documentation here](http://doc.babylonjs.com/tutorials/How_to_use_Reflection_probes) ([deltakosh](https://github.com/deltakosh))
-    - New serializers [folder](https://github.com/BabylonJS/Babylon.js/serializers) to host .babylon converter (like .obj) ([deltakosh](https://github.com/deltakosh))
+    - New serializers [folder](https://github.com/BabylonJS/Babylon.js/serializers) to host .babylon serializers ([deltakosh](https://github.com/deltakosh))
+      - New .obj serializer ([BitOfGold](https://github.com/BitOfGold))
+    - Sprites now can be [picked](http://www.babylonjs-playground.com/#1XMVZW#3) and can use [actions](http://www.babylonjs-playground.com/#9RUHH#3) ([deltakosh](https://github.com/deltakosh))
   - **Updates**
     - New `Material.sideOrientation` property to define clockwise or counter-clockwise faces selection. [Demo here](http://www.babylonjs-playground.com/#1TZJQY) ([deltakosh](https://github.com/deltakosh))
     - It is now possible to create a custom loading screen [PR](https://github.com/BabylonJS/Babylon.js/pull/700) ([RaananW](https://github.com/RaananW))

+ 17 - 5
src/Actions/babylon.actionManager.js

@@ -6,9 +6,9 @@ var BABYLON;
     var ActionEvent = (function () {
         /**
          * @constructor
-         * @param source The mesh that triggered the action.
-         * @param pointerX the X mouse cursor position at the time of the event
-         * @param pointerY the Y mouse cursor position at the time of the event
+         * @param source The mesh or sprite that triggered the action.
+         * @param pointerX The X mouse cursor position at the time of the event
+         * @param pointerY The Y mouse cursor position at the time of the event
          * @param meshUnderPointer The mesh that is currently pointed at (can be null)
          * @param sourceEvent the original (browser) event that triggered the ActionEvent
          */
@@ -22,7 +22,7 @@ var BABYLON;
         }
         /**
          * Helper function to auto-create an ActionEvent from a source mesh.
-         * @param source the source mesh that triggered the event
+         * @param source The source mesh that triggered the event
          * @param evt {Event} The original (browser) event
          */
         ActionEvent.CreateNew = function (source, evt, additionalData) {
@@ -30,6 +30,15 @@ var BABYLON;
             return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
         };
         /**
+         * Helper function to auto-create an ActionEvent from a source mesh.
+         * @param source The source sprite that triggered the event
+         * @param scene Scene associated with the sprite
+         * @param evt {Event} The original (browser) event
+         */
+        ActionEvent.CreateNewFromSprite = function (source, scene, evt, additionalData) {
+            return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
+        };
+        /**
          * Helper function to auto-create an ActionEvent from a scene. If triggered by a mesh use ActionEvent.CreateNew
          * @param scene the scene where the event occurred
          * @param evt {Event} The original (browser) event
@@ -191,7 +200,7 @@ var BABYLON;
                     if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnPointerOutTrigger) {
                         return true;
                     }
-                    if (action.trigger == ActionManager._OnPickUpTrigger) {
+                    if (action.trigger === ActionManager._OnPickUpTrigger) {
                         return true;
                     }
                 }
@@ -211,6 +220,9 @@ var BABYLON;
                     if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnCenterPickTrigger) {
                         return true;
                     }
+                    if (action.trigger === ActionManager._OnPickUpTrigger) {
+                        return true;
+                    }
                 }
                 return false;
             },

+ 19 - 6
src/Actions/babylon.actionManager.ts

@@ -6,19 +6,19 @@
     export class ActionEvent {
         /**
          * @constructor
-         * @param source The mesh that triggered the action.
-         * @param pointerX the X mouse cursor position at the time of the event
-         * @param pointerY the Y mouse cursor position at the time of the event
+         * @param source The mesh or sprite that triggered the action.
+         * @param pointerX The X mouse cursor position at the time of the event
+         * @param pointerY The Y mouse cursor position at the time of the event
          * @param meshUnderPointer The mesh that is currently pointed at (can be null)
          * @param sourceEvent the original (browser) event that triggered the ActionEvent
          */
-        constructor(public source: AbstractMesh, public pointerX: number, public pointerY: number, public meshUnderPointer: AbstractMesh, public sourceEvent?: any, public additionalData?: any) {
+        constructor(public source: any, public pointerX: number, public pointerY: number, public meshUnderPointer: AbstractMesh, public sourceEvent?: any, public additionalData?: any) {
 
         }
 
         /**
          * Helper function to auto-create an ActionEvent from a source mesh.
-         * @param source the source mesh that triggered the event
+         * @param source The source mesh that triggered the event
          * @param evt {Event} The original (browser) event
          */
         public static CreateNew(source: AbstractMesh, evt?: Event, additionalData?: any): ActionEvent {
@@ -27,6 +27,16 @@
         }
 
         /**
+         * Helper function to auto-create an ActionEvent from a source mesh.
+         * @param source The source sprite that triggered the event
+         * @param scene Scene associated with the sprite
+         * @param evt {Event} The original (browser) event
+         */
+        public static CreateNewFromSprite(source: Sprite, scene: Scene, evt?: Event, additionalData?: any): ActionEvent {
+            return new ActionEvent(source, scene.pointerX, scene.pointerY, scene.meshUnderPointer, evt, additionalData);
+        }
+
+        /**
          * Helper function to auto-create an ActionEvent from a scene. If triggered by a mesh use ActionEvent.CreateNew
          * @param scene the scene where the event occurred
          * @param evt {Event} The original (browser) event
@@ -175,7 +185,7 @@
                 if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnPointerOutTrigger) {
                     return true;
                 }
-                if (action.trigger == ActionManager._OnPickUpTrigger) {
+                if (action.trigger === ActionManager._OnPickUpTrigger) {
                     return true;
                 }
             }
@@ -194,6 +204,9 @@
                 if (action.trigger >= ActionManager._OnPickTrigger && action.trigger <= ActionManager._OnCenterPickTrigger) {
                     return true;
                 }
+                if (action.trigger === ActionManager._OnPickUpTrigger) {
+                    return true;
+                }
             }
 
             return false;

+ 1 - 0
src/Collisions/babylon.pickingInfo.js

@@ -21,6 +21,7 @@ var BABYLON;
             this.bv = 0;
             this.faceId = -1;
             this.subMeshId = 0;
+            this.pickedSprite = null;
         }
         // Methods
         PickingInfo.prototype.getNormal = function (useWorldCoordinates, useVerticesNormals) {

+ 1 - 0
src/Collisions/babylon.pickingInfo.ts

@@ -16,6 +16,7 @@
         public bv = 0;
         public faceId = -1;
         public subMeshId = 0;
+        public pickedSprite: Sprite = null;
 
         // Methods
         public getNormal(useWorldCoordinates = false, useVerticesNormals = true): Vector3 {

+ 1 - 0
src/Sprites/babylon.sprite.js

@@ -11,6 +11,7 @@ var BABYLON;
             this.invertU = 0;
             this.invertV = 0;
             this.animations = new Array();
+            this.isPickable = true;
             this._animationStarted = false;
             this._loopAnimation = false;
             this._fromIndex = 0;

+ 2 - 0
src/Sprites/babylon.sprite.ts

@@ -10,6 +10,8 @@
         public invertV = 0;
         public disposeWhenFinishedAnimating: boolean;
         public animations = new Array<Animation>();
+        public isPickable = true;
+        public actionManager: ActionManager;
 
         private _animationStarted = false;
         private _loopAnimation = false;

+ 42 - 0
src/Sprites/babylon.spriteManager.js

@@ -9,6 +9,7 @@ var BABYLON;
             this.renderingGroupId = 0;
             this.layerMask = 0x0FFFFFFF;
             this.fogEnabled = true;
+            this.isPickable = false;
             this._vertexDeclaration = [4, 4, 4, 4];
             this._vertexStrideSize = 16 * 4; // 15 floats per sprite (x, y, z, angle, sizeX, sizeY, offsetX, offsetY, invertU, invertV, cellIndexX, cellIndexY, color)
             this._capacity = capacity;
@@ -66,6 +67,47 @@ var BABYLON;
             this._vertices[arrayOffset + 14] = sprite.color.b;
             this._vertices[arrayOffset + 15] = sprite.color.a;
         };
+        SpriteManager.prototype.intersects = function (ray, predicate, fastCheck) {
+            var count = Math.min(this._capacity, this.sprites.length);
+            var min = BABYLON.Vector3.Zero();
+            var max = BABYLON.Vector3.Zero();
+            var distance = Number.MAX_VALUE;
+            var currentSprite;
+            for (var index = 0; index < count; index++) {
+                var sprite = this.sprites[index];
+                if (!sprite) {
+                    continue;
+                }
+                if (predicate) {
+                    if (!predicate(sprite)) {
+                        continue;
+                    }
+                }
+                else if (!sprite.isPickable) {
+                    continue;
+                }
+                min.copyFromFloats(sprite.position.x - sprite.width / 2, sprite.position.y - sprite.height / 2, sprite.position.z);
+                max.copyFromFloats(sprite.position.x + sprite.width / 2, sprite.position.y + sprite.height / 2, sprite.position.z);
+                if (ray.intersectsBoxMinMax(min, max)) {
+                    var currentDistance = BABYLON.Vector3.Distance(sprite.position, ray.origin);
+                    if (distance > currentDistance) {
+                        distance = currentDistance;
+                        currentSprite = sprite;
+                        if (fastCheck) {
+                            break;
+                        }
+                    }
+                }
+            }
+            if (currentSprite) {
+                var result = new BABYLON.PickingInfo();
+                result.hit = true;
+                result.pickedSprite = currentSprite;
+                result.distance = distance;
+                return result;
+            }
+            return null;
+        };
         SpriteManager.prototype.render = function () {
             // Check
             if (!this._effectBase.isReady() || !this._effectFog.isReady() || !this._spriteTexture || !this._spriteTexture.isReady())

+ 52 - 0
src/Sprites/babylon.spriteManager.ts

@@ -5,6 +5,7 @@
         public layerMask: number = 0x0FFFFFFF;
         public onDispose: () => void;
         public fogEnabled = true;
+        public isPickable = false;
 
         private _capacity: number;
         private _spriteTexture: Texture;
@@ -94,6 +95,57 @@
             this._vertices[arrayOffset + 15] = sprite.color.a;
         }
 
+        public intersects(ray: Ray, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean): PickingInfo {
+            var count = Math.min(this._capacity, this.sprites.length);
+            var min = Vector3.Zero();
+            var max = Vector3.Zero();
+            var distance = Number.MAX_VALUE;
+            var currentSprite: Sprite;
+
+            for (var index = 0; index < count; index++) {
+                var sprite = this.sprites[index];
+                if (!sprite) {
+                    continue;
+                }
+
+                if (predicate) {
+                    if (!predicate(sprite)) {
+                        continue;
+                    }
+                } else if (!sprite.isPickable) {
+                    continue;
+                }
+
+                min.copyFromFloats(sprite.position.x - sprite.width / 2, sprite.position.y - sprite.height / 2, sprite.position.z);
+                max.copyFromFloats(sprite.position.x + sprite.width / 2, sprite.position.y + sprite.height / 2, sprite.position.z);
+
+                if (ray.intersectsBoxMinMax(min, max)) {
+                    var currentDistance = Vector3.Distance(sprite.position, ray.origin);
+
+                    if (distance > currentDistance) {
+                        distance = currentDistance;
+                        currentSprite = sprite;
+
+                        if (fastCheck) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (currentSprite) {
+                var result = new PickingInfo();
+
+                result.hit = true;
+                result.pickedSprite = currentSprite;
+                result.distance = distance
+
+                return result;
+            }
+
+            return null;
+        } 
+
         public render(): void {
             // Check
             if (!this._effectBase.isReady() || !this._effectFog.isReady() || !this._spriteTexture || !this._spriteTexture.isReady())

+ 79 - 5
src/babylon.scene.js

@@ -308,11 +308,15 @@ var BABYLON;
         // Pointers handling
         Scene.prototype.attachControl = function () {
             var _this = this;
+            var spritePredicate = function (sprite) {
+                return sprite.isPickable && sprite.actionManager && sprite.actionManager.hasPickTriggers;
+            };
             this._onPointerMove = function (evt) {
                 var canvas = _this._engine.getRenderingCanvas();
                 _this._updatePointerPosition(evt);
+                // Meshes
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, function (mesh) { return mesh.isPickable && mesh.isVisible && mesh.isReady(); }, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     _this._meshUnderPointer = pickResult.pickedMesh;
                     _this.setPointerOverMesh(pickResult.pickedMesh);
                     if (_this._meshUnderPointer.actionManager && _this._meshUnderPointer.actionManager.hasPointerTriggers) {
@@ -323,21 +327,29 @@ var BABYLON;
                     }
                 }
                 else {
+                    // Sprites
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        canvas.style.cursor = "pointer";
+                        return;
+                    }
+                    // Restore pointer
                     _this.setPointerOverMesh(null);
                     canvas.style.cursor = "";
                     _this._meshUnderPointer = null;
                 }
             };
             this._onPointerDown = function (evt) {
+                _this._updatePointerPosition(evt);
                 var predicate = null;
+                // Meshes
                 if (!_this.onPointerDown) {
                     predicate = function (mesh) {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasPickTriggers;
                     };
                 }
-                _this._updatePointerPosition(evt);
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, predicate, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         switch (evt.button) {
                             case 0:
@@ -356,17 +368,38 @@ var BABYLON;
                 if (_this.onPointerDown) {
                     _this.onPointerDown(evt, pickResult);
                 }
+                // Sprites
+                if (_this.spriteManagers.length > 0) {
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        if (pickResult.pickedSprite.actionManager) {
+                            switch (evt.button) {
+                                case 0:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnLeftPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                                case 1:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnCenterPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                                case 2:
+                                    pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnRightPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                                    break;
+                            }
+                            pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnPickTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                        }
+                    }
+                }
             };
             this._onPointerUp = function (evt) {
                 var predicate = null;
+                _this._updatePointerPosition(evt);
                 if (!_this.onPointerUp) {
                     predicate = function (mesh) {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasSpecificTrigger(BABYLON.ActionManager.OnPickUpTrigger);
                     };
                 }
-                _this._updatePointerPosition(evt);
+                // Meshes
                 var pickResult = _this.pick(_this._pointerX, _this._pointerY, predicate, false, _this.cameraToUseForPointers);
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                     }
@@ -374,6 +407,15 @@ var BABYLON;
                 if (_this.onPointerUp) {
                     _this.onPointerUp(evt, pickResult);
                 }
+                // Sprites
+                if (_this.spriteManagers.length > 0) {
+                    pickResult = _this.pickSprite(_this._pointerX, _this._pointerY, spritePredicate, false, _this.cameraToUseForPointers);
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        if (pickResult.pickedSprite.actionManager) {
+                            pickResult.pickedSprite.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, _this, evt));
+                        }
+                    }
+                }
             };
             this._onKeyDown = function (evt) {
                 if (_this.actionManager) {
@@ -1665,6 +1707,28 @@ var BABYLON;
             }
             return pickingInfo || new BABYLON.PickingInfo();
         };
+        Scene.prototype._internalPickSprites = function (rayFunction, predicate, fastCheck) {
+            var pickingInfo = null;
+            if (this.spriteManagers.length > 0) {
+                var ray = rayFunction(BABYLON.Matrix.Identity());
+                for (var spriteIndex = 0; spriteIndex < this.spriteManagers.length; spriteIndex++) {
+                    var spriteManager = this.spriteManagers[spriteIndex];
+                    if (!spriteManager.isPickable) {
+                        continue;
+                    }
+                    var result = spriteManager.intersects(ray, predicate, fastCheck);
+                    if (!result || !result.hit)
+                        continue;
+                    if (!fastCheck && pickingInfo != null && result.distance >= pickingInfo.distance)
+                        continue;
+                    pickingInfo = result;
+                    if (fastCheck) {
+                        break;
+                    }
+                }
+            }
+            return pickingInfo || new BABYLON.PickingInfo();
+        };
         Scene.prototype.pick = function (x, y, predicate, fastCheck, camera) {
             var _this = this;
             /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
@@ -1675,6 +1739,16 @@ var BABYLON;
             /// <param name="camera">camera to use for computing the picking ray. Can be set to null. In this case, the scene.activeCamera will be used</param>
             return this._internalPick(function (world) { return _this.createPickingRay(x, y, world, camera); }, predicate, fastCheck);
         };
+        Scene.prototype.pickSprite = function (x, y, predicate, fastCheck, camera) {
+            var _this = this;
+            /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
+            /// <param name="x">X position on screen</param>
+            /// <param name="y">Y position on screen</param>
+            /// <param name="predicate">Predicate function used to determine eligible sprites. Can be set to null. In this case, a sprite must have isPickable set to true</param>
+            /// <param name="fastCheck">Launch a fast check only using the bounding boxes. Can be set to null.</param>
+            /// <param name="camera">camera to use for computing the picking ray. Can be set to null. In this case, the scene.activeCamera will be used</param>
+            return this._internalPickSprites(function (world) { return _this.createPickingRay(x, y, world, camera); }, predicate, fastCheck);
+        };
         Scene.prototype.pickWithRay = function (ray, predicate, fastCheck) {
             var _this = this;
             return this._internalPick(function (world) {

+ 98 - 8
src/babylon.scene.ts

@@ -443,17 +443,22 @@
 
         // Pointers handling
         public attachControl() {
+            var spritePredicate = (sprite: Sprite): boolean => {
+                return sprite.isPickable && sprite.actionManager && sprite.actionManager.hasPickTriggers;
+            };
+
             this._onPointerMove = (evt: PointerEvent) => {
                 var canvas = this._engine.getRenderingCanvas();
 
                 this._updatePointerPosition(evt);
 
+                // Meshes
                 var pickResult = this.pick(this._pointerX, this._pointerY,
                     (mesh: AbstractMesh): boolean => mesh.isPickable && mesh.isVisible && mesh.isReady(),
                     false,
                     this.cameraToUseForPointers);
 
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     this._meshUnderPointer = pickResult.pickedMesh;
 
                     this.setPointerOverMesh(pickResult.pickedMesh);
@@ -464,6 +469,15 @@
                         canvas.style.cursor = "";
                     }
                 } else {
+                    // Sprites
+                    pickResult = this.pickSprite(this._pointerX, this._pointerY, spritePredicate, false, this.cameraToUseForPointers);
+
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        canvas.style.cursor = "pointer";
+                        return;
+                    }
+
+                    // Restore pointer
                     this.setPointerOverMesh(null);
                     canvas.style.cursor = "";
                     this._meshUnderPointer = null;
@@ -472,19 +486,19 @@
 
             this._onPointerDown = (evt: PointerEvent) => {
 
+                this._updatePointerPosition(evt);
+
                 var predicate = null;
 
+                // Meshes
                 if (!this.onPointerDown) {
                     predicate = (mesh: AbstractMesh): boolean => {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasPickTriggers;
                     };
                 }
-
-                this._updatePointerPosition(evt);
-
                 var pickResult = this.pick(this._pointerX, this._pointerY, predicate, false, this.cameraToUseForPointers);
 
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         switch (evt.button) {
                             case 0:
@@ -504,22 +518,45 @@
                 if (this.onPointerDown) {
                     this.onPointerDown(evt, pickResult);
                 }
+
+                // Sprites
+                if (this.spriteManagers.length > 0) {
+                    pickResult = this.pickSprite(this._pointerX, this._pointerY, spritePredicate, false, this.cameraToUseForPointers);
+
+                    if (pickResult.hit && pickResult.pickedSprite) {
+                        if (pickResult.pickedSprite.actionManager) {
+                            switch (evt.button) {
+                                case 0:
+                                    pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnLeftPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                    break;
+                                case 1:
+                                    pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnCenterPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                    break;
+                                case 2:
+                                    pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnRightPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                                    break;
+                            }
+                            pickResult.pickedSprite.actionManager.processTrigger(ActionManager.OnPickTrigger, ActionEvent.CreateNewFromSprite(pickResult.pickedSprite, this, evt));
+                        }
+                    }
+                }
             };
             this._onPointerUp = (evt: PointerEvent) => {
 
                 var predicate = null;
 
+                this._updatePointerPosition(evt);
+
                 if (!this.onPointerUp) {
                     predicate = (mesh: AbstractMesh): boolean => {
                         return mesh.isPickable && mesh.isVisible && mesh.isReady() && mesh.actionManager && mesh.actionManager.hasSpecificTrigger(ActionManager.OnPickUpTrigger);
                     };
                 }
 
-                this._updatePointerPosition(evt);
-
+                // Meshes
                 var pickResult = this.pick(this._pointerX, this._pointerY, predicate, false, this.cameraToUseForPointers);
 
-                if (pickResult.hit) {
+                if (pickResult.hit && pickResult.pickedMesh) {
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
                     }
@@ -528,6 +565,17 @@
                 if (this.onPointerUp) {
                     this.onPointerUp(evt, pickResult);
                 }
+
+                // Sprites
+                if (this.spriteManagers.length > 0) {
+                    pickResult = this.pickSprite(this._pointerX, this._pointerY, 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));
+                        }
+                    }
+                }
             };
 
             this._onKeyDown = (evt: Event) => {
@@ -2099,6 +2147,38 @@
             return pickingInfo || new PickingInfo();
         }
 
+
+        private _internalPickSprites(rayFunction: (world: Matrix) => Ray, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean): PickingInfo {
+            var pickingInfo = null;
+
+            if (this.spriteManagers.length > 0) {
+                var ray = rayFunction(Matrix.Identity());
+
+                for (var spriteIndex = 0; spriteIndex < this.spriteManagers.length; spriteIndex++) {
+                    var spriteManager = this.spriteManagers[spriteIndex];
+
+                    if (!spriteManager.isPickable) {
+                        continue;
+                    }
+
+                    var result = spriteManager.intersects(ray, predicate, fastCheck);
+                    if (!result || !result.hit)
+                        continue;
+
+                    if (!fastCheck && pickingInfo != null && result.distance >= pickingInfo.distance)
+                        continue;
+
+                    pickingInfo = result;
+
+                    if (fastCheck) {
+                        break;
+                    }
+                }
+            }
+
+            return pickingInfo || new PickingInfo();
+        }
+
         public pick(x: number, y: number, predicate?: (mesh: AbstractMesh) => boolean, fastCheck?: boolean, camera?: Camera): PickingInfo {
             /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
             /// <param name="x">X position on screen</param>
@@ -2109,6 +2189,16 @@
             return this._internalPick(world => this.createPickingRay(x, y, world, camera), predicate, fastCheck);
         }
 
+        public pickSprite(x: number, y: number, predicate?: (sprite: Sprite) => boolean, fastCheck?: boolean, camera?: Camera): PickingInfo {
+            /// <summary>Launch a ray to try to pick a mesh in the scene</summary>
+            /// <param name="x">X position on screen</param>
+            /// <param name="y">Y position on screen</param>
+            /// <param name="predicate">Predicate function used to determine eligible sprites. Can be set to null. In this case, a sprite must have isPickable set to true</param>
+            /// <param name="fastCheck">Launch a fast check only using the bounding boxes. Can be set to null.</param>
+            /// <param name="camera">camera to use for computing the picking ray. Can be set to null. In this case, the scene.activeCamera will be used</param>
+            return this._internalPickSprites(world => this.createPickingRay(x, y, world, camera), predicate, fastCheck);
+        }
+
         public pickWithRay(ray: Ray, predicate: (mesh: Mesh) => boolean, fastCheck?: boolean) {
             return this._internalPick(world => {
                 if (!this._pickWithRayInverseMatrix) {