Prechádzať zdrojové kódy

Merge remote-tracking branch 'BabylonJS/master' into arc-wheel-hotfix

Raanan Weber 9 rokov pred
rodič
commit
0420a3d62c
31 zmenil súbory, kde vykonal 1983 pridanie a 1513 odobranie
  1. 27 17
      Exporters/3ds Max/BabylonExport.Entities/BabylonPBRMaterial.cs
  2. BIN
      Exporters/Unity 5/Unity3D2Babylon/BabylonExport.Entities.dll
  3. 48 0
      Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Cameras.cs
  4. 292 24
      Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Materials.cs
  5. 2 16
      Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Meshes.cs
  6. 3 0
      Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.cs
  7. 19 19
      dist/preview release/babylon.core.js
  8. 725 687
      dist/preview release/babylon.d.ts
  9. 27 27
      dist/preview release/babylon.js
  10. 230 203
      dist/preview release/babylon.max.js
  11. 26 26
      dist/preview release/babylon.noworker.js
  12. 7 0
      dist/preview release/what's new.md
  13. 11 3
      loaders/OBJ/babylon.objFileLoader.js
  14. 8 2
      loaders/OBJ/babylon.objFileLoader.ts
  15. 6 6
      src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.js
  16. 14 12
      src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.ts
  17. 74 77
      src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js
  18. 97 102
      src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.ts
  19. 45 55
      src/Cameras/Inputs/babylon.freecamera.input.mouse.js
  20. 66 80
      src/Cameras/Inputs/babylon.freecamera.input.mouse.ts
  21. 57 61
      src/Cameras/Inputs/babylon.freecamera.input.touch.js
  22. 71 79
      src/Cameras/Inputs/babylon.freecamera.input.touch.ts
  23. 1 0
      src/Culling/babylon.ray.js
  24. 3 1
      src/Culling/babylon.ray.ts
  25. 3 1
      src/LensFlare/babylon.lensFlareSystem.js
  26. 3 3
      src/LensFlare/babylon.lensFlareSystem.ts
  27. 1 1
      src/Mesh/babylon.meshSimplification.ts
  28. 6 0
      src/Tools/babylon.observable.js
  29. 8 0
      src/Tools/babylon.observable.ts
  30. 37 2
      src/babylon.scene.js
  31. 66 9
      src/babylon.scene.ts

+ 27 - 17
Exporters/3ds Max/BabylonExport.Entities/BabylonPBRMaterial.cs

@@ -6,7 +6,7 @@ namespace BabylonExport.Entities
     {
         [DataMember]
         public string customType { get; private set; }
-        
+
         [DataMember]
         public float directIntensity { get; set; }
 
@@ -47,19 +47,19 @@ namespace BabylonExport.Entities
         public float overloadedEmissiveIntensity { get; set; }
 
         [DataMember]
-        public float overloadedAmbient { get; set; }
+        public float[] overloadedAmbient { get; set; }
 
         [DataMember]
-        public float overloadedAlbedo { get; set; }
+        public float[] overloadedAlbedo { get; set; }
 
         [DataMember]
-        public float overloadedReflectivity { get; set; }
+        public float[] overloadedReflectivity { get; set; }
 
         [DataMember]
-        public float overloadedEmissive { get; set; }
+        public float[] overloadedEmissive { get; set; }
 
         [DataMember]
-        public float overloadedReflection { get; set; }
+        public float[] overloadedReflection { get; set; }
 
         [DataMember]
         public float overloadedMicroSurface { get; set; }
@@ -101,19 +101,19 @@ namespace BabylonExport.Entities
         public BabylonTexture refractionTexture { get; set; }
 
         [DataMember]
-        public float[] ambientColor { get; set; }
+        public float[] ambient { get; set; }
 
         [DataMember]
-        public float[] albedoColor { get; set; }
+        public float[] albedo { get; set; }
 
         [DataMember]
-        public float[] reflectivityColor { get; set; }
+        public float[] reflectivity { get; set; }
 
         [DataMember]
-        public float[] reflectionColor { get; set; }
+        public float[] reflection { get; set; }
 
         [DataMember]
-        public float[] emissiveColor { get; set; }
+        public float[] emissive { get; set; }
 
         [DataMember]
         public bool useAlphaFromAlbedoTexture { get; set; }
@@ -128,6 +128,9 @@ namespace BabylonExport.Entities
         public bool useSpecularOverAlpha { get; set; }
 
         [DataMember]
+        public bool useRadianceOverAlpha { get; set; }
+
+        [DataMember]
         public float indexOfRefraction { get; set; }
 
         [DataMember]
@@ -139,7 +142,7 @@ namespace BabylonExport.Entities
         [DataMember]
         public BabylonFresnelParameters opacityFresnelParameters { get; set; }
 
-        public BabylonPBRMaterial(): base()
+        public BabylonPBRMaterial() : base()
         {
             customType = "BABYLON.PBRMaterial";
 
@@ -153,10 +156,17 @@ namespace BabylonExport.Entities
             overloadedShadowIntensity = 1.0f;
             overloadedShadeIntensity = 1.0f;
 
-            ambientColor = new[] { 0f, 0f, 0f };
-            reflectionColor = new[] { 0f, 0f, 0f };
-            emissiveColor = new[] { 0f, 0f, 0f };
+            ambient = new[] { 0f, 0f, 0f };
+            albedo = new[] { 0f, 0f, 0f };
+            reflectivity = new[] { 0f, 0f, 0f };
+            reflection = new[] { 0f, 0f, 0f };
+            emissive = new[] { 0f, 0f, 0f };
+
+            overloadedAmbient = new[] { 0f, 0f, 0f };
+            overloadedAlbedo = new[] { 0f, 0f, 0f };
+            overloadedReflectivity = new[] { 0f, 0f, 0f };
+            overloadedEmissive = new[] { 0f, 0f, 0f };
+            overloadedReflection = new[] { 0f, 0f, 0f };
         }
     }
-}
-;
+}

BIN
Exporters/Unity 5/Unity3D2Babylon/BabylonExport.Entities.dll


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 48 - 0
Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Cameras.cs


+ 292 - 24
Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Materials.cs

@@ -41,13 +41,18 @@ namespace Unity3D2Babylon
                         case CubemapFace.NegativeZ:
                             faceTexturePath += "_nz.jpg";
                             break;
+                        default:
+                            continue;
                     }
 
-                    var tempTexture = new Texture2D(cubemap.width, cubemap.height, cubemap.format, false);
+                    var tempTexture = new Texture2D(cubemap.width, cubemap.height, TextureFormat.RGB24, false);
 
                     tempTexture.SetPixels(cubemap.GetPixels(face));
                     tempTexture.Apply();
 
+                    // Flip faces in cube texture.
+                    tempTexture = FlipTexture(tempTexture);
+
                     File.WriteAllBytes(faceTexturePath, tempTexture.EncodeToJPG());
                 }
 
@@ -64,6 +69,22 @@ namespace Unity3D2Babylon
             babylonTexture.coordinatesMode = 3;
         }
 
+        private Texture2D FlipTexture(Texture2D original)
+        {
+            Texture2D flipped = new Texture2D(original.width, original.height);
+
+            for (int i = 0; i < original.width; i++)
+            {
+                for (int j = 0; j < original.height; j++)
+                {
+                    flipped.SetPixel(i, original.height - j - 1, original.GetPixel(i, j));
+                }
+            }
+            flipped.Apply();
+
+            return flipped;
+        }
+
         private void CopyTexture(string texturePath, Texture2D texture2D, BabylonTexture babylonTexture, bool isLightmap = false)
         {
             bool needToDelete = false;
@@ -125,7 +146,7 @@ namespace Unity3D2Babylon
                     textureImporter.convertToNormalmap = previousConvertToNormalmap;
                     textureImporter.textureType = previousTextureType;
                     textureImporter.grayscaleToAlpha = previousGrayscaleToAlpha;
-                   
+
                     AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceUpdate);
                 }
                 catch (Exception ex)
@@ -146,7 +167,21 @@ namespace Unity3D2Babylon
 
         private BabylonMaterial DumpMaterial(Material material, Renderer renderer)
         {
-            var materialNotSupported = false; 
+            if (renderer.sharedMaterial.shader.name == "Standard")
+            {
+                return DumpPBRMaterial(renderer.sharedMaterial, renderer, true);
+            }
+            else if (renderer.sharedMaterial.shader.name == "Standard (Specular setup)")
+            {
+                return DumpPBRMaterial(renderer.sharedMaterial, renderer, false);
+            }
+
+            return DumpStandardMaterial(renderer.sharedMaterial, renderer);
+        }
+
+        private BabylonMaterial DumpStandardMaterial(Material material, Renderer renderer)
+        {
+            var materialNotSupported = false;
 
             if (!materialsDictionary.ContainsKey(material.name))
             {
@@ -252,43 +287,276 @@ namespace Unity3D2Babylon
             return materialsDictionary[material.name];
         }
 
+        private BabylonMaterial DumpPBRMaterial(Material material, Renderer renderer, bool metallic)
+        {
+            if (materialsDictionary.ContainsKey(material.name))
+            {
+                return materialsDictionary[material.name];
+            }
 
-        private BabylonMaterial DumpPBRMaterial(Material material, Renderer renderer)
+            var babylonPbrMaterial = new BabylonPBRMaterial
+            {
+                name = material.name,
+                id = Guid.NewGuid().ToString(),
+                albedo = new float[4],
+                useEmissiveAsIllumination = true,
+                useSpecularOverAlpha = true,
+                useRadianceOverAlpha = true,
+            };
+
+            babylonPbrMaterial.environmentIntensity = RenderSettings.ambientIntensity;
+
+            // Albedo
+            if (material.HasProperty("_Color"))
+            {
+                babylonPbrMaterial.albedo = material.color.ToFloat();
+            }
+            babylonPbrMaterial.albedoTexture = DumpTextureFromMaterial(material, "_MainTex");
+
+            // Transparency
+            DumpTransparency(material, babylonPbrMaterial);
+
+            // Glossiess/Reflectivity
+            DumpGlossinessReflectivity(material, metallic, babylonPbrMaterial);
+
+            // Occlusion
+            babylonPbrMaterial.ambientTexture = DumpTextureFromMaterial(material, "_OcclusionMap");
+            if (babylonPbrMaterial.ambientTexture != null && material.HasProperty("_OcclusionStrength"))
+            {
+                babylonPbrMaterial.ambientTexture.level = material.GetFloat("_OcclusionStrength");
+            }
+
+            // Emissive
+            if (material.HasProperty("_EmissionColor"))
+            {
+                babylonPbrMaterial.emissive = material.GetColor("_EmissionColor").ToFloat();
+            }
+            babylonPbrMaterial.emissiveTexture = DumpTextureFromMaterial(material, "_EmissionMap");
+
+            // Normal
+            babylonPbrMaterial.bumpTexture = DumpTextureFromMaterial(material, "_BumpMap");
+            if (babylonPbrMaterial.bumpTexture != null && material.HasProperty("_BumpMapScale"))
+            {
+                babylonPbrMaterial.bumpTexture.level = material.GetFloat("_BumpMapScale");
+            }
+
+            // Reflection
+            babylonPbrMaterial.reflectionTexture = DumpReflectionTexture();
+
+            materialsDictionary.Add(babylonPbrMaterial.name, babylonPbrMaterial);
+            return babylonPbrMaterial;
+        }
+
+        private void DumpGlossinessReflectivity(Material material, bool metallic, BabylonPBRMaterial babylonPbrMaterial)
         {
-            if (!materialsDictionary.ContainsKey(material.name))
+            if (material.HasProperty("_Glossiness"))
+            {
+                babylonPbrMaterial.microSurface = material.GetFloat("_Glossiness");
+            }
+
+            if (metallic)
             {
-                var bMat = new BabylonPBRMaterial
+                if (material.HasProperty("_Metallic"))
                 {
-                    name = material.name,
-                    id = Guid.NewGuid().ToString(),
-                    albedoColor = new float[4]
-                };
+                    var metalness = material.GetFloat("_Metallic");
+                    babylonPbrMaterial.reflectivity = new float[] { metalness * babylonPbrMaterial.albedo[0],
+                        metalness * babylonPbrMaterial.albedo[1],
+                        metalness * babylonPbrMaterial.albedo[2] };
 
-                if (material.HasProperty("_Color"))
+                    if (babylonPbrMaterial.albedoTexture != null)
+                    {
+                        var albedoTexture = material.GetTexture("_MainTex") as Texture2D;
+                        if (albedoTexture != null)
+                        {
+                            var albedoPixels = GetPixels(albedoTexture);
+                            var reflectivityTexture = new Texture2D(albedoTexture.width, albedoTexture.height, TextureFormat.RGBA32, false);
+                            reflectivityTexture.alphaIsTransparency = true;
+                            babylonPbrMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
+
+                            var metallicTexture = material.GetTexture("_MetallicGlossMap") as Texture2D;
+                            if (metallicTexture == null)
+                            {
+                                for (var i = 0; i < albedoTexture.width; i++)
+                                {
+                                    for (var j = 0; j < albedoTexture.height; j++)
+                                    {
+                                        albedoPixels[j * albedoTexture.width + i].r *= metalness;
+                                        albedoPixels[j * albedoTexture.width + i].g *= metalness;
+                                        albedoPixels[j * albedoTexture.width + i].b *= metalness;
+                                        albedoPixels[j * albedoTexture.width + i].a = babylonPbrMaterial.microSurface;
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                var metallicPixels = GetPixels(metallicTexture);
+                                for (var i = 0; i < albedoTexture.width; i++)
+                                {
+                                    for (var j = 0; j < albedoTexture.height; j++)
+                                    {
+                                        var pixel = albedoPixels[j * albedoTexture.width + i];
+                                        var metallicPixel = metallicPixels[j * albedoTexture.width + i];
+                                        albedoPixels[j * albedoTexture.width + i].r *= metallicPixel.r;
+                                        albedoPixels[j * albedoTexture.width + i].g *= metallicPixel.r;
+                                        albedoPixels[j * albedoTexture.width + i].b *= metallicPixel.r;
+                                        albedoPixels[j * albedoTexture.width + i].a = metallicPixel.a;
+                                    }
+                                }
+                            }
+
+                            reflectivityTexture.SetPixels(albedoPixels);
+                            reflectivityTexture.Apply();
+
+                            var textureName = albedoTexture.name + "_MetallicGlossMap.png";
+                            var babylonTexture = new BabylonTexture { name = textureName };
+                            var textureScale = material.GetTextureScale("_MainTex");
+                            babylonTexture.uScale = textureScale.x;
+                            babylonTexture.vScale = textureScale.y;
+
+                            var textureOffset = material.GetTextureOffset("_MainTex");
+                            babylonTexture.uOffset = textureOffset.x;
+                            babylonTexture.vOffset = textureOffset.y;
+
+                            var reflectivityTexturePath = Path.Combine(Path.GetTempPath(), textureName);
+                            File.WriteAllBytes(reflectivityTexturePath, reflectivityTexture.EncodeToPNG());
+                            babylonScene.AddTexture(reflectivityTexturePath);
+                            if (File.Exists(reflectivityTexturePath))
+                            {
+                                File.Delete(reflectivityTexturePath);
+                            }
+
+                            babylonPbrMaterial.reflectivityTexture = babylonTexture;
+                        }
+                    }
+                    //else
+                    //{
+                    //      TODO. Manage Albedo Cube Texture.
+                    //}
+                }
+            }
+            else
+            {
+
+                if (material.HasProperty("_SpecColor"))
+                {
+                    babylonPbrMaterial.reflectivity = material.GetColor("_SpecColor").ToFloat();
+                }
+                babylonPbrMaterial.reflectivityTexture = DumpTextureFromMaterial(material, "_SpecGlossMap");
+                if (babylonPbrMaterial.reflectivityTexture != null && babylonPbrMaterial.reflectivityTexture.hasAlpha)
                 {
-                    bMat.albedoColor = material.color.ToFloat();
+                    babylonPbrMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                 }
+            }
+        }
 
-                bMat.albedoTexture = DumpTextureFromMaterial(material, "_MainTex");
+        private static Color[] GetPixels(Texture2D texture)
+        {
+            string texturePath = AssetDatabase.GetAssetPath(texture);
+
+            // Change texture import settings to be able to read texture data
+            var textureImporter = AssetImporter.GetAtPath(texturePath) as TextureImporter;
+            var previousIsReadable = textureImporter.isReadable;
+            var previousNormalMap = textureImporter.normalmap;
+            var previousLightmap = textureImporter.lightmap;
+            var previousConvertToNormalmap = textureImporter.convertToNormalmap;
+            var previousTextureType = textureImporter.textureType;
+            var previousGrayscaleToAlpha = textureImporter.grayscaleToAlpha;
+
+            textureImporter.textureType = TextureImporterType.Advanced;
+            textureImporter.isReadable = true;
+            textureImporter.lightmap = false;
+            textureImporter.normalmap = false;
+            textureImporter.convertToNormalmap = false;
+            textureImporter.grayscaleToAlpha = false;
+
+            AssetDatabase.ImportAsset(texturePath);
+
+            var pixels = texture.GetPixels();
+
+            // Restore
+            textureImporter.isReadable = previousIsReadable;
+            textureImporter.normalmap = previousNormalMap;
+            textureImporter.lightmap = previousLightmap;
+            textureImporter.convertToNormalmap = previousConvertToNormalmap;
+            textureImporter.textureType = previousTextureType;
+            textureImporter.grayscaleToAlpha = previousGrayscaleToAlpha;
+
+            return pixels;
+        }
 
-                if (material.HasProperty("_Glossiness"))
+        private static void DumpTransparency(Material material, BabylonPBRMaterial babylonPbrMaterial)
+        {
+            if (material.HasProperty("_Mode"))
+            {
+                var mode = material.GetFloat("_Mode");
+                if (mode == 2.0f)
                 {
-                    bMat.microSurface = material.GetFloat("_Glossiness");
+                    // Transparent Albedo
+                    if (babylonPbrMaterial.albedoTexture != null && babylonPbrMaterial.albedoTexture.hasAlpha)
+                    {
+                        babylonPbrMaterial.useAlphaFromAlbedoTexture = true;
+                    }
+                    // Material Alpha
+                    else
+                    {
+                        babylonPbrMaterial.alpha = babylonPbrMaterial.albedo[3];
+                    }
                 }
-
-                if (material.HasProperty("_Metallic"))
+                else if (mode == 1.0f)
                 {
-                    var metallic = material.GetFloat("_Metallic");
-                    bMat.reflectivityColor = new float[] { metallic, metallic, metallic };
+                    // Cutout
+                    // Follow the texture hasAlpha property.
                 }
+                else
+                {
+                    // Opaque
+                    if (babylonPbrMaterial.albedoTexture != null)
+                    {
+                        babylonPbrMaterial.albedoTexture.hasAlpha = false;
+                    }
+                    babylonPbrMaterial.alpha = 1.0f;
+                }
+            }
+        }
 
-                bMat.bumpTexture = DumpTextureFromMaterial(material, "_BumpMap");
+        private BabylonTexture DumpReflectionTexture()
+        {
+            if (sceneReflectionTexture != null)
+            {
+                return sceneReflectionTexture;
+            }
 
-                materialsDictionary.Add(bMat.name, bMat);
-                return bMat;
+            // Take only reflection source currently and not the RenderSettings.ambientMode
+            if (RenderSettings.defaultReflectionMode == UnityEngine.Rendering.DefaultReflectionMode.Skybox)
+            {
+                var skybox = RenderSettings.skybox;
+                if (skybox != null)
+                {
+                    if (skybox.shader.name == "Skybox/Cubemap")
+                    {
+                        var cubeMap = skybox.GetTexture("_Tex") as Cubemap;
+                        if (cubeMap != null)
+                        {
+                            sceneReflectionTexture = new BabylonTexture();
+                            CopyTextureCube("sceneReflectionTexture.hdr", cubeMap, sceneReflectionTexture);
+                            sceneReflectionTexture.level = RenderSettings.reflectionIntensity;
+                        }
+                    }
+                    //else if (skybox.shader.name == "Skybox/6 Sided")
+                    //{
+                    //    // TODO. HDR faces.
+                    //}
+                }
+            }
+            else if (RenderSettings.customReflection != null)
+            {
+                var cubeMap = RenderSettings.customReflection;
+                sceneReflectionTexture = new BabylonTexture();
+                CopyTextureCube("sceneReflectionTexture.hdr", cubeMap, sceneReflectionTexture);
+                sceneReflectionTexture.level = RenderSettings.reflectionIntensity;
             }
 
-            return materialsDictionary[material.name];
+            return sceneReflectionTexture;
         }
 
         private BabylonTexture DumpTextureFromMaterial(Material material, string name)
@@ -308,6 +576,7 @@ namespace Unity3D2Babylon
             {
                 return null;
             }
+
             var texturePath = AssetDatabase.GetAssetPath(texture);
             var textureName = Path.GetFileName(texturePath);
             var babylonTexture = new BabylonTexture { name = textureName };
@@ -324,7 +593,6 @@ namespace Unity3D2Babylon
             }
 
             var texture2D = texture as Texture2D;
-
             if (texture2D)
             {
                 babylonTexture.hasAlpha = texture2D.alphaIsTransparency;

+ 2 - 16
Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.Meshes.cs

@@ -164,14 +164,7 @@ namespace Unity3D2Babylon
                                 var sharedMaterial = renderer.sharedMaterials[i];
                                 BabylonMaterial babylonMaterial;
 
-                                if (sharedMaterial.HasProperty("_Metallic"))
-                                {
-                                    babylonMaterial = DumpPBRMaterial(sharedMaterial, renderer);
-                                }
-                                else
-                                {
-                                    babylonMaterial = DumpMaterial(sharedMaterial, renderer);
-                                }
+                                babylonMaterial = DumpMaterial(sharedMaterial, renderer);
 
                                 bMultiMat.materials[i] = babylonMaterial.id;
                             }
@@ -207,14 +200,7 @@ namespace Unity3D2Babylon
                     }
                     else
                     {
-                        if (renderer.sharedMaterial.HasProperty("_Metallic"))
-                        {
-                            babylonMesh.materialId = DumpPBRMaterial(renderer.sharedMaterial, renderer).id;
-                        }
-                        else
-                        {
-                            babylonMesh.materialId = DumpMaterial(renderer.sharedMaterial, renderer).id;
-                        }
+                        babylonMesh.materialId = DumpMaterial(renderer.sharedMaterial, renderer).id;
                     }
                 }
 

+ 3 - 0
Exporters/Unity 5/Unity3D2Babylon/SceneBuilder.cs

@@ -25,6 +25,8 @@ namespace Unity3D2Babylon
 
         readonly ExportationOptions exportationOptions;
 
+        BabylonTexture sceneReflectionTexture;
+
         public SceneBuilder(string outputPath, string sceneName, ExportationOptions exportationOptions)
         {
             OutputPath = outputPath;
@@ -143,6 +145,7 @@ namespace Unity3D2Babylon
                 if (camera != null)
                 {
                     ConvertUnityCameraToBabylon(camera, progress);
+                    ConvertUnitySkyboxToBabylon(camera, progress);
                     continue;
                 }
 

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 19 - 19
dist/preview release/babylon.core.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 725 - 687
dist/preview release/babylon.d.ts


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 27 - 27
dist/preview release/babylon.js


+ 230 - 203
dist/preview release/babylon.max.js

@@ -3160,6 +3160,12 @@ var BABYLON;
             }
         };
         /**
+         * return true is the Observable has at least one Observer registered
+         */
+        Observable.prototype.hasObservers = function () {
+            return this._observers.length > 0;
+        };
+        /**
         * Clear the list of observers
         */
         Observable.prototype.clear = function () {
@@ -8314,6 +8320,7 @@ var BABYLON;
         Ray.Transform = function (ray, matrix) {
             var newOrigin = BABYLON.Vector3.TransformCoordinates(ray.origin, matrix);
             var newDirection = BABYLON.Vector3.TransformNormal(ray.direction, matrix);
+            newDirection.normalize();
             return new Ray(newOrigin, newDirection, ray.length);
         };
         Ray.smallnum = 0.00000001;
@@ -11625,71 +11632,61 @@ var BABYLON;
         }
         FreeCameraMouseInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
-            if (!this._onMouseDown) {
+            if (!this._pointerInput) {
                 var camera = this.camera;
                 var engine = this.camera.getEngine();
-                this._onMouseDown = function (evt) {
-                    _this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseUp = function (evt) {
-                    _this.previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseOut = function (evt) {
-                    _this.previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseMove = function (evt) {
-                    if (!_this.previousPosition && !engine.isPointerLock) {
-                        return;
+                this._pointerInput = function (p, s) {
+                    var evt = p.event;
+                    if (p.type === 1 /* PointerDown */) {
+                        //   evt.srcElement.setPointerCapture(evt.pointerId);
+                        _this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                    var offsetX;
-                    var offsetY;
-                    if (!engine.isPointerLock) {
-                        offsetX = evt.clientX - _this.previousPosition.x;
-                        offsetY = evt.clientY - _this.previousPosition.y;
+                    else if (p.type === 2 /* PointerUp */) {
+                        //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        _this.previousPosition = null;
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                    else {
-                        offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                        offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-                    }
-                    camera.cameraRotation.y += offsetX / _this.angularSensibility;
-                    camera.cameraRotation.x += offsetY / _this.angularSensibility;
-                    _this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                    else if (p.type === 3 /* PointerMove */) {
+                        if (!_this.previousPosition && !engine.isPointerLock) {
+                            return;
+                        }
+                        var offsetX;
+                        var offsetY;
+                        if (!engine.isPointerLock) {
+                            offsetX = evt.clientX - _this.previousPosition.x;
+                            offsetY = evt.clientY - _this.previousPosition.y;
+                        }
+                        else {
+                            offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
+                            offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+                        }
+                        camera.cameraRotation.y += offsetX / _this.angularSensibility;
+                        camera.cameraRotation.x += offsetY / _this.angularSensibility;
+                        _this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
                 };
             }
-            element.addEventListener("mousedown", this._onMouseDown, false);
-            element.addEventListener("mouseup", this._onMouseUp, false);
-            element.addEventListener("mouseout", this._onMouseOut, false);
-            element.addEventListener("mousemove", this._onMouseMove, false);
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
         };
         FreeCameraMouseInput.prototype.detachControl = function (element) {
-            if (this._onMouseDown && element) {
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 this.previousPosition = null;
-                element.removeEventListener("mousedown", this._onMouseDown);
-                element.removeEventListener("mouseup", this._onMouseUp);
-                element.removeEventListener("mouseout", this._onMouseOut);
-                element.removeEventListener("mousemove", this._onMouseMove);
-                this._onMouseDown = null;
-                this._onMouseUp = null;
-                this._onMouseOut = null;
-                this._onMouseMove = null;
             }
         };
         FreeCameraMouseInput.prototype.getTypeName = function () {
@@ -11835,80 +11832,76 @@ var BABYLON;
         FreeCameraTouchInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
             var previousPosition;
-            if (this._onPointerDown === undefined) {
+            if (this._pointerInput === undefined) {
                 this._onLostFocus = function (evt) {
                     _this._offsetX = null;
                     _this._offsetY = null;
                 };
-                this._onPointerDown = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                    _this._pointerPressed.push(evt.pointerId);
-                    if (_this._pointerPressed.length !== 1) {
-                        return;
-                    }
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                };
-                this._onPointerUp = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                    var index = _this._pointerPressed.indexOf(evt.pointerId);
-                    if (index === -1) {
-                        return;
-                    }
-                    _this._pointerPressed.splice(index, 1);
-                    if (index != 0) {
-                        return;
-                    }
-                    previousPosition = null;
-                    _this._offsetX = null;
-                    _this._offsetY = null;
-                };
-                this._onPointerMove = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                this._pointerInput = function (p, s) {
+                    var evt = p.event;
+                    if (p.type === 1 /* PointerDown */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        //  evt.srcElement.setPointerCapture(evt.pointerId);
+                        _this._pointerPressed.push(evt.pointerId);
+                        if (_this._pointerPressed.length !== 1) {
+                            return;
+                        }
+                        previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
                     }
-                    if (!previousPosition) {
-                        return;
+                    else if (p.type === 2 /* PointerUp */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        var index = _this._pointerPressed.indexOf(evt.pointerId);
+                        if (index === -1) {
+                            return;
+                        }
+                        _this._pointerPressed.splice(index, 1);
+                        if (index != 0) {
+                            return;
+                        }
+                        previousPosition = null;
+                        _this._offsetX = null;
+                        _this._offsetY = null;
                     }
-                    var index = _this._pointerPressed.indexOf(evt.pointerId);
-                    if (index != 0) {
-                        return;
+                    else if (p.type === 3 /* PointerMove */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        if (!previousPosition) {
+                            return;
+                        }
+                        var index = _this._pointerPressed.indexOf(evt.pointerId);
+                        if (index != 0) {
+                            return;
+                        }
+                        _this._offsetX = evt.clientX - previousPosition.x;
+                        _this._offsetY = -(evt.clientY - previousPosition.y);
                     }
-                    _this._offsetX = evt.clientX - previousPosition.x;
-                    _this._offsetY = -(evt.clientY - previousPosition.y);
                 };
             }
             element.addEventListener("blur", this._onLostFocus);
-            element.addEventListener("pointerdown", this._onPointerDown);
-            element.addEventListener("pointerup", this._onPointerUp);
-            element.addEventListener("pointerout", this._onPointerUp);
-            element.addEventListener("pointermove", this._onPointerMove);
         };
         FreeCameraTouchInput.prototype.detachControl = function (element) {
-            if (this._onPointerDown && element) {
+            if (this._pointerInput && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 element.removeEventListener("blur", this._onLostFocus);
-                element.removeEventListener("pointerdown", this._onPointerDown);
-                element.removeEventListener("pointerup", this._onPointerUp);
-                element.removeEventListener("pointerout", this._onPointerUp);
-                element.removeEventListener("pointermove", this._onPointerMove);
-                this._onPointerDown = null;
-                this._onPointerUp = null;
-                this._onPointerMove = null;
                 this._onLostFocus = null;
                 this._pointerPressed = [];
                 this._offsetX = null;
@@ -12310,7 +12303,8 @@ var BABYLON;
         }
         ArcRotateCameraMouseWheelInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
-            this._wheel = function (event) {
+            this._wheel = function (p, s) {
+                var event = p.event;
                 var delta = 0;
                 if (event.wheelDelta) {
                     delta = event.wheelDelta / (_this.wheelPrecision * 40);
@@ -12326,13 +12320,12 @@ var BABYLON;
                     }
                 }
             };
-            element.addEventListener('mousewheel', this._wheel, false);
-            element.addEventListener('DOMMouseScroll', this._wheel, false);
+            this._observer = this.camera.getScene().onPointerObservable.add(this._wheel);
         };
         ArcRotateCameraMouseWheelInput.prototype.detachControl = function (element) {
-            if (this._wheel && element) {
-                element.removeEventListener('mousewheel', this._wheel);
-                element.removeEventListener('DOMMouseScroll', this._wheel);
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 this._wheel = null;
             }
         };
@@ -12371,6 +12364,77 @@ var BABYLON;
             var cacheSoloPointer; // cache pointer object for better perf on camera rotation
             var pointers = new BABYLON.SmartCollection();
             var previousPinchDistance = 0;
+            this._pointerInput = function (p, s) {
+                var evt = p.event;
+                if (p.type === 1 /* PointerDown */) {
+                    evt.srcElement.setPointerCapture(evt.pointerId);
+                    // Manage panning with right click
+                    _this._isRightClick = evt.button === 2;
+                    // manage pointers
+                    pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
+                    cacheSoloPointer = pointers.item(evt.pointerId);
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                }
+                else if (p.type === 2 /* PointerUp */) {
+                    evt.srcElement.releasePointerCapture(evt.pointerId);
+                    cacheSoloPointer = null;
+                    previousPinchDistance = 0;
+                    //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
+                    //but emptying completly pointers collection is required to fix a bug on iPhone : 
+                    //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
+                    //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
+                    pointers.empty();
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                }
+                else if (p.type === 3 /* PointerMove */) {
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                    switch (pointers.count) {
+                        case 1:
+                            if (_this.panningSensibility !== 0 && ((_this._isCtrlPushed && _this.camera._useCtrlForPanning) || (!_this.camera._useCtrlForPanning && _this._isRightClick))) {
+                                _this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / _this.panningSensibility;
+                                _this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / _this.panningSensibility;
+                            }
+                            else {
+                                var offsetX = evt.clientX - cacheSoloPointer.x;
+                                var offsetY = evt.clientY - cacheSoloPointer.y;
+                                _this.camera.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
+                                _this.camera.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
+                            }
+                            cacheSoloPointer.x = evt.clientX;
+                            cacheSoloPointer.y = evt.clientY;
+                            break;
+                        case 2:
+                            //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
+                            pointers.item(evt.pointerId).x = evt.clientX;
+                            pointers.item(evt.pointerId).y = evt.clientY;
+                            var direction = _this.pinchInwards ? 1 : -1;
+                            var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
+                            var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
+                            var pinchSquaredDistance = (distX * distX) + (distY * distY);
+                            if (previousPinchDistance === 0) {
+                                previousPinchDistance = pinchSquaredDistance;
+                                return;
+                            }
+                            if (pinchSquaredDistance !== previousPinchDistance) {
+                                _this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (_this.pinchPrecision * ((_this.angularSensibilityX + _this.angularSensibilityY) / 2) * direction);
+                                previousPinchDistance = pinchSquaredDistance;
+                            }
+                            break;
+                        default:
+                            if (pointers.item(evt.pointerId)) {
+                                pointers.item(evt.pointerId).x = evt.clientX;
+                                pointers.item(evt.pointerId).y = evt.clientY;
+                            }
+                    }
+                }
+            };
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
             this._onContextMenu = function (evt) {
                 evt.preventDefault();
             };
@@ -12389,71 +12453,6 @@ var BABYLON;
             this._onKeyUp = function (evt) {
                 _this._isCtrlPushed = evt.ctrlKey;
             };
-            this._onPointerDown = function (evt) {
-                // Manage panning with right click
-                _this._isRightClick = evt.button === 2;
-                // manage pointers
-                pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
-                cacheSoloPointer = pointers.item(evt.pointerId);
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-            this._onPointerUp = function (evt) {
-                cacheSoloPointer = null;
-                previousPinchDistance = 0;
-                //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
-                //but emptying completly pointers collection is required to fix a bug on iPhone : 
-                //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
-                //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
-                pointers.empty();
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-            this._onPointerMove = function (evt) {
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-                switch (pointers.count) {
-                    case 1:
-                        if (_this.panningSensibility !== 0 && ((_this._isCtrlPushed && _this.camera._useCtrlForPanning) || (!_this.camera._useCtrlForPanning && _this._isRightClick))) {
-                            _this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / _this.panningSensibility;
-                            _this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / _this.panningSensibility;
-                        }
-                        else {
-                            var offsetX = evt.clientX - cacheSoloPointer.x;
-                            var offsetY = evt.clientY - cacheSoloPointer.y;
-                            _this.camera.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
-                            _this.camera.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
-                        }
-                        cacheSoloPointer.x = evt.clientX;
-                        cacheSoloPointer.y = evt.clientY;
-                        break;
-                    case 2:
-                        //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
-                        pointers.item(evt.pointerId).x = evt.clientX;
-                        pointers.item(evt.pointerId).y = evt.clientY;
-                        var direction = _this.pinchInwards ? 1 : -1;
-                        var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
-                        var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
-                        var pinchSquaredDistance = (distX * distX) + (distY * distY);
-                        if (previousPinchDistance === 0) {
-                            previousPinchDistance = pinchSquaredDistance;
-                            return;
-                        }
-                        if (pinchSquaredDistance !== previousPinchDistance) {
-                            _this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (_this.pinchPrecision * ((_this.angularSensibilityX + _this.angularSensibilityY) / 2) * direction);
-                            previousPinchDistance = pinchSquaredDistance;
-                        }
-                        break;
-                    default:
-                        if (pointers.item(evt.pointerId)) {
-                            pointers.item(evt.pointerId).x = evt.clientX;
-                            pointers.item(evt.pointerId).y = evt.clientY;
-                        }
-                }
-            };
             this._onMouseMove = function (evt) {
                 if (!engine.isPointerLock) {
                     return;
@@ -12485,10 +12484,6 @@ var BABYLON;
                     }
                 }
             };
-            element.addEventListener(eventPrefix + "down", this._onPointerDown, false);
-            element.addEventListener(eventPrefix + "up", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "out", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "move", this._onPointerMove, false);
             element.addEventListener("mousemove", this._onMouseMove, false);
             element.addEventListener("MSPointerDown", this._onGestureStart, false);
             element.addEventListener("MSGestureChange", this._onGesture, false);
@@ -12499,12 +12494,10 @@ var BABYLON;
             ]);
         };
         ArcRotateCameraPointersInput.prototype.detachControl = function (element) {
-            if (element && this._onPointerDown) {
+            if (element && this._observer) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 element.removeEventListener("contextmenu", this._onContextMenu);
-                element.removeEventListener(eventPrefix + "down", this._onPointerDown);
-                element.removeEventListener(eventPrefix + "up", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "out", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "move", this._onPointerMove);
                 element.removeEventListener("mousemove", this._onMouseMove);
                 element.removeEventListener("MSPointerDown", this._onGestureStart);
                 element.removeEventListener("MSGestureChange", this._onGesture);
@@ -12513,9 +12506,6 @@ var BABYLON;
                 this.pinchInwards = true;
                 this._onKeyDown = null;
                 this._onKeyUp = null;
-                this._onPointerDown = null;
-                this._onPointerUp = null;
-                this._onPointerMove = null;
                 this._onMouseMove = null;
                 this._onGestureStart = null;
                 this._onGesture = null;
@@ -14103,6 +14093,19 @@ var BABYLON;
 var BABYLON;
 (function (BABYLON) {
     /**
+     * This type contains all the data related to a pointer event in Babylon.js.
+     * The event member is an instnce of PointerEvent for all types except PointerWheel and is of type MouseWheelEvent when type equals PointerWheel
+     */
+    var PointerInfo = (function () {
+        function PointerInfo(type, event, pickInfo) {
+            this.type = type;
+            this.event = event;
+            this.pickInfo = pickInfo;
+        }
+        return PointerInfo;
+    }());
+    BABYLON.PointerInfo = PointerInfo;
+    /**
      * Represents a scene to be rendered by the engine.
      * @see http://doc.babylonjs.com/page.php?p=21911
      */
@@ -14194,6 +14197,10 @@ var BABYLON;
             this.onMeshRemovedObservable = new BABYLON.Observable();
             // Animations
             this.animations = [];
+            /**
+             * Observable event triggered each time an input event is received from the rendering canvas
+             */
+            this.onPointerObservable = new BABYLON.Observable();
             this.cameraToUseForPointers = null; // Define this parameter if you are using multiple cameras and you want to specify which one should be used for pointer position
             this._startingPointerPosition = new BABYLON.Vector2(0, 0);
             this._startingPointerTime = 0;
@@ -14592,6 +14599,10 @@ var BABYLON;
                 if (_this.onPointerMove) {
                     _this.onPointerMove(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(evt.type === "mousewheel" ? 4 /* PointerWheel */ : 3 /* PointerMove */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
             };
             this._onPointerDown = function (evt) {
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
@@ -14645,6 +14656,10 @@ var BABYLON;
                 if (_this.onPointerDown) {
                     _this.onPointerDown(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(1 /* PointerDown */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
                 // Sprites
                 _this._pickedDownSprite = null;
                 if (_this.spriteManagers.length > 0) {
@@ -14681,8 +14696,14 @@ var BABYLON;
                 // Meshes
                 var pickResult = _this.pick(_this._unTranslatedPointerX, _this._unTranslatedPointerY, _this.pointerUpPredicate, false, _this.cameraToUseForPointers);
                 if (pickResult.hit && pickResult.pickedMesh) {
-                    if (_this.onPointerPick && _this._pickedDownMesh != null && pickResult.pickedMesh == _this._pickedDownMesh) {
-                        _this.onPointerPick(evt, pickResult);
+                    if (_this._pickedDownMesh != null && pickResult.pickedMesh == _this._pickedDownMesh) {
+                        if (_this.onPointerPick) {
+                            _this.onPointerPick(evt, pickResult);
+                        }
+                        if (_this.onPointerObservable.hasObservers()) {
+                            var pi = new PointerInfo(5 /* PointerPick */, evt, pickResult);
+                            _this.onPointerObservable.notifyObservers(pi);
+                        }
                     }
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNew(pickResult.pickedMesh, evt));
@@ -14697,6 +14718,10 @@ var BABYLON;
                 if (_this.onPointerUp) {
                     _this.onPointerUp(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(2 /* PointerUp */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
                 _this._startingPointerTime = 0;
                 // Sprites
                 if (_this.spriteManagers.length > 0) {
@@ -35763,7 +35788,7 @@ var BABYLON;
                                 var deleted1 = [];
                                 var v0 = t.vertices[j];
                                 var v1 = t.vertices[(j + 1) % 3];
-                                if (v0.isBorder !== v1.isBorder)
+                                if (v0.isBorder || v1.isBorder)
                                     continue;
                                 var p = BABYLON.Vector3.Zero();
                                 var n = BABYLON.Vector3.Zero();
@@ -39149,7 +39174,8 @@ var BABYLON;
         };
         LensFlareSystem.Parse = function (parsedLensFlareSystem, scene, rootUrl) {
             var emitter = scene.getLastEntryByID(parsedLensFlareSystem.emitterId);
-            var lensFlareSystem = new LensFlareSystem("lensFlareSystem#" + parsedLensFlareSystem.emitterId, emitter, scene);
+            var name = parsedLensFlareSystem.name || "lensFlareSystem#" + parsedLensFlareSystem.emitterId;
+            var lensFlareSystem = new LensFlareSystem(name, emitter, scene);
             lensFlareSystem.borderLimit = parsedLensFlareSystem.borderLimit;
             for (var index = 0; index < parsedLensFlareSystem.flares.length; index++) {
                 var parsedFlare = parsedLensFlareSystem.flares[index];
@@ -39159,6 +39185,7 @@ var BABYLON;
         };
         LensFlareSystem.prototype.serialize = function () {
             var serializationObject = {};
+            serializationObject.name = this.name;
             serializationObject.emitterId = this.getEmitter().id;
             serializationObject.borderLimit = this.borderLimit;
             serializationObject.flares = [];

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 26 - 26
dist/preview release/babylon.noworker.js


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

@@ -25,12 +25,19 @@
     - New SPS property `computeBoundingBox` ([jerome](https://github.com/jbousquie))  
     - Added a new OnPickOut trigger fired when you release the pointer button outside of a mesh or sprite. ([deltakosh](https://github.com/deltakosh))
     - Added support for OnPointerOver and OnPointerOut for sprites. ([deltakosh](https://github.com/deltakosh))
+    - Added an optional predicate on Node.getDescendants, Node.getChildren to filter out Nodes based on a callback execution. ([nockawa](https://github.com/nockawa))
+    - Added Ray.intersectionPlane & intersectionSegment. ([nockawa](https://github.com/nockawa))
+    - LinesMesh class now supports Intersection. Added the intersectionThreshold property to set a tolerance margin during intersection with wire lines. ([nockawa](https://github.com/nockawa))
+    - Geometry.boundingBias property to enlarge the boundingInfo objects ([nockawa](https://github.com/nockawa))
+    - Tools.ExtractMinAndMax & ExtractMinAndMaxIndexed now supports an optional Bias for Extent computation.
+
   - **API doc**
     - class `SolidParticleSystem` documented
     - class `MeshBuilder` documented
   - **Bug fixes**
     - Fixed bug with billboards and parenting ([deltakosh](https://github.com/deltakosh))
     - Fixed bug with ArcRotateCamera.setTarget ([deltakosh](https://github.com/deltakosh))
+    - Fixed bug with OBJ Loader - All meshes were concatenated with the previous one ([Temechon](https://github.com/Temechon))
   - **Breaking changes**
     - `VertexData.CreateLines()` removed as `MeshBuilder.CreateLines()` now calls `MeshBuilder.CreateLineSystem()`
     - `scene.onNewXXXAdded` and `scene.onXXXRemoved' callbacks were removed and replaced by `scene.onNewXXXAddedObservable` and `scene.onXXXRemovedObservable`

+ 11 - 3
loaders/OBJ/babylon.objFileLoader.js

@@ -1,4 +1,4 @@
-/// <reference path="../../dist/preview release/babylon.d.ts"/>
+
 var BABYLON;
 (function (BABYLON) {
     /**
@@ -354,6 +354,13 @@ var BABYLON;
                     unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                     unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
                 }
+				// Reset arrays for the next new meshes
+                wrappedPositionForBabylon = [];
+                wrappedNormalsForBabylon = [];
+                wrappedUvsForBabylon = [];
+                tuplePosNorm = [];
+                curPositionInIndices = 0;
+                
             };
             /**
              * Create triangles from polygons by recursion
@@ -498,7 +505,7 @@ var BABYLON;
                     indicesForBabylon = [];
                     unwrappedPositionsForBabylon = [];
                     unwrappedNormalsForBabylon = [];
-                    unwrappedUVForBabylon = [];
+                    unwrappedUVForBabylon = [];  
                 }
             };
             //Main function
@@ -653,7 +660,6 @@ var BABYLON;
                 });
             }
             //Create a BABYLON.Mesh list
-            var vertexData = new BABYLON.VertexData(); //The container for the values
             var babylonMeshesArray = []; //The mesh for babylon
             var materialToUse = [];
             //Set data for each mesh
@@ -680,6 +686,8 @@ var BABYLON;
                 //This is indispensable for the importMesh function
                 materialToUse.push(meshesFromObj[j].materialName);
                 //Set the data for the babylonMesh
+                
+                var vertexData = new BABYLON.VertexData(); //The container for the values
                 vertexData.positions = handledMesh.positions;
                 vertexData.normals = handledMesh.normals;
                 vertexData.uvs = handledMesh.uvs;

+ 8 - 2
loaders/OBJ/babylon.objFileLoader.ts

@@ -390,7 +390,13 @@ module BABYLON {
                     unwrappedPositionsForBabylon.push(wrappedPositionForBabylon[l].x, wrappedPositionForBabylon[l].y, wrappedPositionForBabylon[l].z);
                     unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                     unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
-                }
+                }				
+				// Reset arrays for the next new meshes
+                wrappedPositionForBabylon = [];
+                wrappedNormalsForBabylon = [];
+                wrappedUvsForBabylon = [];
+                tuplePosNorm = [];
+                curPositionInIndices = 0;
             };
 
             /**
@@ -782,7 +788,6 @@ module BABYLON {
             }
 
             //Create a BABYLON.Mesh list
-            var vertexData: VertexData = new BABYLON.VertexData(); //The container for the values
             var babylonMeshesArray: Array<BABYLON.Mesh> = []; //The mesh for babylon
             var materialToUse = [];
 
@@ -812,6 +817,7 @@ module BABYLON {
                 //This is indispensable for the importMesh function
                 materialToUse.push(meshesFromObj[j].materialName);
 
+				var vertexData: VertexData = new BABYLON.VertexData(); //The container for the values
                 //Set the data for the babylonMesh
                 vertexData.positions = handledMesh.positions;
                 vertexData.normals = handledMesh.normals;

+ 6 - 6
src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.js

@@ -12,7 +12,8 @@ var BABYLON;
         }
         ArcRotateCameraMouseWheelInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
-            this._wheel = function (event) {
+            this._wheel = function (p, s) {
+                var event = p.event;
                 var delta = 0;
                 if (event.wheelDelta) {
                     delta = event.wheelDelta / (_this.wheelPrecision * 40);
@@ -28,13 +29,12 @@ var BABYLON;
                     }
                 }
             };
-            element.addEventListener('mousewheel', this._wheel, false);
-            element.addEventListener('DOMMouseScroll', this._wheel, false);
+            this._observer = this.camera.getScene().onPointerObservable.add(this._wheel);
         };
         ArcRotateCameraMouseWheelInput.prototype.detachControl = function (element) {
-            if (this._wheel && element) {
-                element.removeEventListener('mousewheel', this._wheel);
-                element.removeEventListener('DOMMouseScroll', this._wheel);
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 this._wheel = null;
             }
         };

+ 14 - 12
src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.ts

@@ -1,14 +1,16 @@
 module BABYLON {
     export class ArcRotateCameraMouseWheelInput implements ICameraInput<ArcRotateCamera> {
         camera: ArcRotateCamera;
-        
-        private _wheel: (e: MouseWheelEvent) => void;
+
+        private _wheel: (p: PointerInfo, s: EventState) => void;
+        private _observer: Observer<PointerInfo>;
 
         @serialize()
         public wheelPrecision = 3.0;
 
         public attachControl(element: HTMLElement, noPreventDefault?: boolean) {
-            this._wheel = event => {
+            this._wheel = (p, s) => {
+                var event = <MouseWheelEvent>p.event;
                 var delta = 0;
                 if (event.wheelDelta) {
                     delta = event.wheelDelta / (this.wheelPrecision * 40);
@@ -25,14 +27,14 @@ module BABYLON {
                     }
                 }
             };
-            element.addEventListener('mousewheel', this._wheel, false);
-            element.addEventListener('DOMMouseScroll', this._wheel, false);
+
+            this._observer = this.camera.getScene().onPointerObservable.add(this._wheel);
         }
 
         public detachControl(element: HTMLElement) {
-            if (this._wheel && element){
-                element.removeEventListener('mousewheel', this._wheel);
-                element.removeEventListener('DOMMouseScroll', this._wheel);
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 this._wheel = null;
             }
         }
@@ -40,11 +42,11 @@ module BABYLON {
         getTypeName(): string {
             return "ArcRotateCameraMouseWheelInput";
         }
-        
-        getSimpleName(){
+
+        getSimpleName() {
             return "mousewheel";
         }
     }
-    
+
     CameraInputTypes["ArcRotateCameraMouseWheelInput"] = ArcRotateCameraMouseWheelInput;
-}
+}

+ 74 - 77
src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js

@@ -23,6 +23,77 @@ var BABYLON;
             var cacheSoloPointer; // cache pointer object for better perf on camera rotation
             var pointers = new BABYLON.SmartCollection();
             var previousPinchDistance = 0;
+            this._pointerInput = function (p, s) {
+                var evt = p.event;
+                if (p.type === 1 /* PointerDown */) {
+                    evt.srcElement.setPointerCapture(evt.pointerId);
+                    // Manage panning with right click
+                    _this._isRightClick = evt.button === 2;
+                    // manage pointers
+                    pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
+                    cacheSoloPointer = pointers.item(evt.pointerId);
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                }
+                else if (p.type === 2 /* PointerUp */) {
+                    evt.srcElement.releasePointerCapture(evt.pointerId);
+                    cacheSoloPointer = null;
+                    previousPinchDistance = 0;
+                    //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
+                    //but emptying completly pointers collection is required to fix a bug on iPhone : 
+                    //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
+                    //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
+                    pointers.empty();
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                }
+                else if (p.type === 3 /* PointerMove */) {
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                    switch (pointers.count) {
+                        case 1:
+                            if (_this.panningSensibility !== 0 && ((_this._isCtrlPushed && _this.camera._useCtrlForPanning) || (!_this.camera._useCtrlForPanning && _this._isRightClick))) {
+                                _this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / _this.panningSensibility;
+                                _this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / _this.panningSensibility;
+                            }
+                            else {
+                                var offsetX = evt.clientX - cacheSoloPointer.x;
+                                var offsetY = evt.clientY - cacheSoloPointer.y;
+                                _this.camera.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
+                                _this.camera.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
+                            }
+                            cacheSoloPointer.x = evt.clientX;
+                            cacheSoloPointer.y = evt.clientY;
+                            break;
+                        case 2:
+                            //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
+                            pointers.item(evt.pointerId).x = evt.clientX;
+                            pointers.item(evt.pointerId).y = evt.clientY;
+                            var direction = _this.pinchInwards ? 1 : -1;
+                            var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
+                            var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
+                            var pinchSquaredDistance = (distX * distX) + (distY * distY);
+                            if (previousPinchDistance === 0) {
+                                previousPinchDistance = pinchSquaredDistance;
+                                return;
+                            }
+                            if (pinchSquaredDistance !== previousPinchDistance) {
+                                _this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (_this.pinchPrecision * ((_this.angularSensibilityX + _this.angularSensibilityY) / 2) * direction);
+                                previousPinchDistance = pinchSquaredDistance;
+                            }
+                            break;
+                        default:
+                            if (pointers.item(evt.pointerId)) {
+                                pointers.item(evt.pointerId).x = evt.clientX;
+                                pointers.item(evt.pointerId).y = evt.clientY;
+                            }
+                    }
+                }
+            };
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
             this._onContextMenu = function (evt) {
                 evt.preventDefault();
             };
@@ -41,71 +112,6 @@ var BABYLON;
             this._onKeyUp = function (evt) {
                 _this._isCtrlPushed = evt.ctrlKey;
             };
-            this._onPointerDown = function (evt) {
-                // Manage panning with right click
-                _this._isRightClick = evt.button === 2;
-                // manage pointers
-                pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
-                cacheSoloPointer = pointers.item(evt.pointerId);
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-            this._onPointerUp = function (evt) {
-                cacheSoloPointer = null;
-                previousPinchDistance = 0;
-                //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
-                //but emptying completly pointers collection is required to fix a bug on iPhone : 
-                //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
-                //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
-                pointers.empty();
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-            this._onPointerMove = function (evt) {
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-                switch (pointers.count) {
-                    case 1:
-                        if (_this.panningSensibility !== 0 && ((_this._isCtrlPushed && _this.camera._useCtrlForPanning) || (!_this.camera._useCtrlForPanning && _this._isRightClick))) {
-                            _this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / _this.panningSensibility;
-                            _this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / _this.panningSensibility;
-                        }
-                        else {
-                            var offsetX = evt.clientX - cacheSoloPointer.x;
-                            var offsetY = evt.clientY - cacheSoloPointer.y;
-                            _this.camera.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
-                            _this.camera.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
-                        }
-                        cacheSoloPointer.x = evt.clientX;
-                        cacheSoloPointer.y = evt.clientY;
-                        break;
-                    case 2:
-                        //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
-                        pointers.item(evt.pointerId).x = evt.clientX;
-                        pointers.item(evt.pointerId).y = evt.clientY;
-                        var direction = _this.pinchInwards ? 1 : -1;
-                        var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
-                        var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
-                        var pinchSquaredDistance = (distX * distX) + (distY * distY);
-                        if (previousPinchDistance === 0) {
-                            previousPinchDistance = pinchSquaredDistance;
-                            return;
-                        }
-                        if (pinchSquaredDistance !== previousPinchDistance) {
-                            _this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (_this.pinchPrecision * ((_this.angularSensibilityX + _this.angularSensibilityY) / 2) * direction);
-                            previousPinchDistance = pinchSquaredDistance;
-                        }
-                        break;
-                    default:
-                        if (pointers.item(evt.pointerId)) {
-                            pointers.item(evt.pointerId).x = evt.clientX;
-                            pointers.item(evt.pointerId).y = evt.clientY;
-                        }
-                }
-            };
             this._onMouseMove = function (evt) {
                 if (!engine.isPointerLock) {
                     return;
@@ -137,10 +143,6 @@ var BABYLON;
                     }
                 }
             };
-            element.addEventListener(eventPrefix + "down", this._onPointerDown, false);
-            element.addEventListener(eventPrefix + "up", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "out", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "move", this._onPointerMove, false);
             element.addEventListener("mousemove", this._onMouseMove, false);
             element.addEventListener("MSPointerDown", this._onGestureStart, false);
             element.addEventListener("MSGestureChange", this._onGesture, false);
@@ -151,12 +153,10 @@ var BABYLON;
             ]);
         };
         ArcRotateCameraPointersInput.prototype.detachControl = function (element) {
-            if (element && this._onPointerDown) {
+            if (element && this._observer) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 element.removeEventListener("contextmenu", this._onContextMenu);
-                element.removeEventListener(eventPrefix + "down", this._onPointerDown);
-                element.removeEventListener(eventPrefix + "up", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "out", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "move", this._onPointerMove);
                 element.removeEventListener("mousemove", this._onMouseMove);
                 element.removeEventListener("MSPointerDown", this._onGestureStart);
                 element.removeEventListener("MSGestureChange", this._onGesture);
@@ -165,9 +165,6 @@ var BABYLON;
                 this.pinchInwards = true;
                 this._onKeyDown = null;
                 this._onKeyUp = null;
-                this._onPointerDown = null;
-                this._onPointerUp = null;
-                this._onPointerMove = null;
                 this._onMouseMove = null;
                 this._onGestureStart = null;
                 this._onGesture = null;

+ 97 - 102
src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.ts

@@ -3,7 +3,7 @@ module BABYLON {
 
     export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamera> {
         camera: ArcRotateCamera;
-        
+
         @serialize()
         public angularSensibilityX = 1000.0;
 
@@ -20,11 +20,10 @@ module BABYLON {
         private _isCtrlPushed: boolean = false;
         public pinchInwards = true;
 
+        private _pointerInput: (p: PointerInfo, s: EventState) => void;
+        private _observer: Observer<PointerInfo>;
         private _onKeyDown: (e: KeyboardEvent) => any;
         private _onKeyUp: (e: KeyboardEvent) => any;
-        private _onPointerDown: (e: PointerEvent) => void;
-        private _onPointerUp: (e: PointerEvent) => void;
-        private _onPointerMove: (e: PointerEvent) => void;
         private _onMouseMove: (e: MouseEvent) => any;
         private _onGestureStart: (e: PointerEvent) => void;
         private _onGesture: (e: MSGestureEvent) => void;
@@ -38,6 +37,85 @@ module BABYLON {
             var pointers = new SmartCollection();
             var previousPinchDistance = 0;
 
+            this._pointerInput = (p, s) => {
+                var evt = <PointerEvent>p.event;
+                if (p.type === PointerEventType.PointerDown) {
+                   // evt.srcElement.setPointerCapture(evt.pointerId);
+
+                    // Manage panning with right click
+                    this._isRightClick = evt.button === 2;
+
+                    // manage pointers
+                    pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
+                    cacheSoloPointer = pointers.item(evt.pointerId);
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                } else if (p.type === PointerEventType.PointerUp) {
+                   // evt.srcElement.releasePointerCapture(evt.pointerId);
+
+                    cacheSoloPointer = null;
+                    previousPinchDistance = 0;
+
+                    //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
+                    //but emptying completly pointers collection is required to fix a bug on iPhone : 
+                    //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
+                    //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
+                    pointers.empty();
+
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                } else if (p.type === PointerEventType.PointerMove) {
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+
+                    switch (pointers.count) {
+                        case 1: //normal camera rotation
+                            if (this.panningSensibility !== 0 && ((this._isCtrlPushed && this.camera._useCtrlForPanning) || (!this.camera._useCtrlForPanning && this._isRightClick))) {
+                                this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / this.panningSensibility;
+                                this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / this.panningSensibility;
+                            } else {
+                                var offsetX = evt.clientX - cacheSoloPointer.x;
+                                var offsetY = evt.clientY - cacheSoloPointer.y;
+                                this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
+                                this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
+                            }
+                            cacheSoloPointer.x = evt.clientX;
+                            cacheSoloPointer.y = evt.clientY;
+                            break;
+
+                        case 2: //pinch
+                            //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
+                            pointers.item(evt.pointerId).x = evt.clientX;
+                            pointers.item(evt.pointerId).y = evt.clientY;
+                            var direction = this.pinchInwards ? 1 : -1;
+                            var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
+                            var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
+                            var pinchSquaredDistance = (distX * distX) + (distY * distY);
+                            if (previousPinchDistance === 0) {
+                                previousPinchDistance = pinchSquaredDistance;
+                                return;
+                            }
+
+                            if (pinchSquaredDistance !== previousPinchDistance) {
+                                this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (this.pinchPrecision * ((this.angularSensibilityX + this.angularSensibilityY) / 2) * direction);
+                                previousPinchDistance = pinchSquaredDistance;
+                            }
+                            break;
+
+                        default:
+                            if (pointers.item(evt.pointerId)) {
+                                pointers.item(evt.pointerId).x = evt.clientX;
+                                pointers.item(evt.pointerId).y = evt.clientY;
+                            }
+                    }
+                }
+            }
+
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
+
             this._onContextMenu = evt => {
                 evt.preventDefault();
             };
@@ -61,81 +139,6 @@ module BABYLON {
                 this._isCtrlPushed = evt.ctrlKey;
             };
 
-            this._onPointerDown = evt => {
-                // Manage panning with right click
-                this._isRightClick = evt.button === 2;
-
-                // manage pointers
-                pointers.add(evt.pointerId, { x: evt.clientX, y: evt.clientY, type: evt.pointerType });
-                cacheSoloPointer = pointers.item(evt.pointerId);
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-
-            this._onPointerUp = evt => {
-                cacheSoloPointer = null;
-                previousPinchDistance = 0;
-
-                //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
-                //but emptying completly pointers collection is required to fix a bug on iPhone : 
-                //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
-                //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
-                pointers.empty();
-
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-            };
-
-            this._onPointerMove = evt => {
-                if (!noPreventDefault) {
-                    evt.preventDefault();
-                }
-
-                switch (pointers.count) {
-
-                    case 1: //normal camera rotation
-                        if (this.panningSensibility !== 0 && ((this._isCtrlPushed && this.camera._useCtrlForPanning) || (!this.camera._useCtrlForPanning && this._isRightClick))) {
-                            this.camera.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / this.panningSensibility;
-                            this.camera.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / this.panningSensibility;
-                        } else {
-                            var offsetX = evt.clientX - cacheSoloPointer.x;
-                            var offsetY = evt.clientY - cacheSoloPointer.y;
-                            this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
-                            this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
-                        }
-                        cacheSoloPointer.x = evt.clientX;
-                        cacheSoloPointer.y = evt.clientY;
-                        break;
-
-                    case 2: //pinch
-                        //if (noPreventDefault) { evt.preventDefault(); } //if pinch gesture, could be usefull to force preventDefault to avoid html page scroll/zoom in some mobile browsers
-                        pointers.item(evt.pointerId).x = evt.clientX;
-                        pointers.item(evt.pointerId).y = evt.clientY;
-                        var direction = this.pinchInwards ? 1 : -1;
-                        var distX = pointers.getItemByIndex(0).x - pointers.getItemByIndex(1).x;
-                        var distY = pointers.getItemByIndex(0).y - pointers.getItemByIndex(1).y;
-                        var pinchSquaredDistance = (distX * distX) + (distY * distY);
-                        if (previousPinchDistance === 0) {
-                            previousPinchDistance = pinchSquaredDistance;
-                            return;
-                        }
-
-                        if (pinchSquaredDistance !== previousPinchDistance) {
-                            this.camera.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (this.pinchPrecision * ((this.angularSensibilityX + this.angularSensibilityY) / 2) * direction);
-                            previousPinchDistance = pinchSquaredDistance;
-                        }
-                        break;
-
-                    default:
-                        if (pointers.item(evt.pointerId)) {
-                            pointers.item(evt.pointerId).x = evt.clientX;
-                            pointers.item(evt.pointerId).y = evt.clientY;
-                        }
-                }
-            };
-
             this._onMouseMove = evt => {
                 if (!engine.isPointerLock) {
                     return;
@@ -176,11 +179,7 @@ module BABYLON {
                     }
                 }
             };
-        
-            element.addEventListener(eventPrefix + "down", this._onPointerDown, false);
-            element.addEventListener(eventPrefix + "up", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "out", this._onPointerUp, false);
-            element.addEventListener(eventPrefix + "move", this._onPointerMove, false);
+
             element.addEventListener("mousemove", this._onMouseMove, false);
             element.addEventListener("MSPointerDown", this._onGestureStart, false);
             element.addEventListener("MSGestureChange", this._onGesture, false);
@@ -193,34 +192,30 @@ module BABYLON {
         }
 
         public detachControl(element: HTMLElement) {
-            if (element && this._onPointerDown) {
+            if (element && this._observer) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
+
                 element.removeEventListener("contextmenu", this._onContextMenu);
-                element.removeEventListener(eventPrefix + "down", this._onPointerDown);
-                element.removeEventListener(eventPrefix + "up", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "out", this._onPointerUp);
-                element.removeEventListener(eventPrefix + "move", this._onPointerMove);
                 element.removeEventListener("mousemove", this._onMouseMove);
                 element.removeEventListener("MSPointerDown", this._onGestureStart);
                 element.removeEventListener("MSGestureChange", this._onGesture);
-                
+
                 this._isRightClick = false;
                 this._isCtrlPushed = false;
                 this.pinchInwards = true;
 
-                this._onKeyDown= null;
-                this._onKeyUp= null;
-                this._onPointerDown= null;
-                this._onPointerUp= null;
-                this._onPointerMove= null;
-                this._onMouseMove= null;
-                this._onGestureStart= null;
-                this._onGesture= null;
-                this._MSGestureHandler= null;
-                this._onLostFocus= null;
+                this._onKeyDown = null;
+                this._onKeyUp = null;
+                this._onMouseMove = null;
+                this._onGestureStart = null;
+                this._onGesture = null;
+                this._MSGestureHandler = null;
+                this._onLostFocus = null;
                 this._onContextMenu = null;
 
             }
-            
+
             Tools.UnregisterTopRootEvents([
                 { name: "keydown", handler: this._onKeyDown },
                 { name: "keyup", handler: this._onKeyUp },
@@ -238,4 +233,4 @@ module BABYLON {
     }
 
     CameraInputTypes["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;
-}
+}

+ 45 - 55
src/Cameras/Inputs/babylon.freecamera.input.mouse.js

@@ -12,71 +12,61 @@ var BABYLON;
         }
         FreeCameraMouseInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
-            if (!this._onMouseDown) {
+            if (!this._pointerInput) {
                 var camera = this.camera;
                 var engine = this.camera.getEngine();
-                this._onMouseDown = function (evt) {
-                    _this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                this._pointerInput = function (p, s) {
+                    var evt = p.event;
+                    if (p.type === 1 /* PointerDown */) {
+                        //   evt.srcElement.setPointerCapture(evt.pointerId);
+                        _this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                };
-                this._onMouseUp = function (evt) {
-                    _this.previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseOut = function (evt) {
-                    _this.previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseMove = function (evt) {
-                    if (!_this.previousPosition && !engine.isPointerLock) {
-                        return;
-                    }
-                    var offsetX;
-                    var offsetY;
-                    if (!engine.isPointerLock) {
-                        offsetX = evt.clientX - _this.previousPosition.x;
-                        offsetY = evt.clientY - _this.previousPosition.y;
-                    }
-                    else {
-                        offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                        offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+                    else if (p.type === 2 /* PointerUp */) {
+                        //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        _this.previousPosition = null;
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                    camera.cameraRotation.y += offsetX / _this.angularSensibility;
-                    camera.cameraRotation.x += offsetY / _this.angularSensibility;
-                    _this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                    else if (p.type === 3 /* PointerMove */) {
+                        if (!_this.previousPosition && !engine.isPointerLock) {
+                            return;
+                        }
+                        var offsetX;
+                        var offsetY;
+                        if (!engine.isPointerLock) {
+                            offsetX = evt.clientX - _this.previousPosition.x;
+                            offsetY = evt.clientY - _this.previousPosition.y;
+                        }
+                        else {
+                            offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
+                            offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+                        }
+                        camera.cameraRotation.y += offsetX / _this.angularSensibility;
+                        camera.cameraRotation.x += offsetY / _this.angularSensibility;
+                        _this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
                 };
             }
-            element.addEventListener("mousedown", this._onMouseDown, false);
-            element.addEventListener("mouseup", this._onMouseUp, false);
-            element.addEventListener("mouseout", this._onMouseOut, false);
-            element.addEventListener("mousemove", this._onMouseMove, false);
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
         };
         FreeCameraMouseInput.prototype.detachControl = function (element) {
-            if (this._onMouseDown && element) {
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 this.previousPosition = null;
-                element.removeEventListener("mousedown", this._onMouseDown);
-                element.removeEventListener("mouseup", this._onMouseUp);
-                element.removeEventListener("mouseout", this._onMouseOut);
-                element.removeEventListener("mousemove", this._onMouseMove);
-                this._onMouseDown = null;
-                this._onMouseUp = null;
-                this._onMouseOut = null;
-                this._onMouseMove = null;
             }
         };
         FreeCameraMouseInput.prototype.getTypeName = function () {

+ 66 - 80
src/Cameras/Inputs/babylon.freecamera.input.mouse.ts

@@ -1,107 +1,93 @@
-module BABYLON {       
+module BABYLON {
     export class FreeCameraMouseInput implements ICameraInput<FreeCamera> {
-        camera : FreeCamera;
-        
+        camera: FreeCamera;
+
         @serialize()
         public angularSensibility = 2000.0;
-        
-        private _onMouseDown: (e: MouseEvent) => any;
-        private _onMouseUp: (e: MouseEvent) => any;
-        private _onMouseOut: (e: MouseEvent) => any;
-        private _onMouseMove: (e: MouseEvent) => any;
+
+        private _pointerInput: (p: PointerInfo, s: EventState) => void;
+        private _observer: Observer<PointerInfo>;
+
         private previousPosition: { x: number, y: number };
 
-        attachControl(element: HTMLElement, noPreventDefault?: boolean){                     
-            if (!this._onMouseDown) {
+        attachControl(element: HTMLElement, noPreventDefault?: boolean) {
+
+            if (!this._pointerInput) {
                 var camera = this.camera;
                 var engine = this.camera.getEngine();
-                
-                this._onMouseDown = evt => {
-                    this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
+                this._pointerInput = (p, s) => {
+                    var evt = <PointerEvent>p.event;
+                    if (p.type === PointerEventType.PointerDown) {
+                     //   evt.srcElement.setPointerCapture(evt.pointerId);
 
-                this._onMouseUp = evt => {
-                    this.previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
+                        this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
 
-                this._onMouseOut = evt => {
-                    this.previousPosition = null;
-                    
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                };
-
-                this._onMouseMove = evt => {
-                    if (!this.previousPosition && !engine.isPointerLock) {
-                        return;
+                    else if (p.type === PointerEventType.PointerUp) {
+                      //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        this.previousPosition = null;
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
 
-                    var offsetX;
-                    var offsetY;
+                    else if (p.type === PointerEventType.PointerMove) {
+                        if (!this.previousPosition && !engine.isPointerLock) {
+                            return;
+                        }
 
-                    if (!engine.isPointerLock) {
-                        offsetX = evt.clientX - this.previousPosition.x;
-                        offsetY = evt.clientY - this.previousPosition.y;
-                    } else {
-                        offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                        offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-                    }
+                        var offsetX;
+                        var offsetY;
 
-                    camera.cameraRotation.y += offsetX / this.angularSensibility;
-                    camera.cameraRotation.x += offsetY / this.angularSensibility;
+                        if (!engine.isPointerLock) {
+                            offsetX = evt.clientX - this.previousPosition.x;
+                            offsetY = evt.clientY - this.previousPosition.y;
+                        } else {
+                            offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
+                            offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+                        }
 
-                    this.previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                        camera.cameraRotation.y += offsetX / this.angularSensibility;
+                        camera.cameraRotation.x += offsetY / this.angularSensibility;
+
+                        this.previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
+
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
                     }
-                };
+                }
             }
 
-            element.addEventListener("mousedown", this._onMouseDown, false);
-            element.addEventListener("mouseup", this._onMouseUp, false);
-            element.addEventListener("mouseout", this._onMouseOut, false);
-            element.addEventListener("mousemove", this._onMouseMove, false);
-   
+            this._observer = this.camera.getScene().onPointerObservable.add(this._pointerInput);
         }
-        
-        detachControl(element : HTMLElement){   
-            if (this._onMouseDown && element) {
+
+        detachControl(element: HTMLElement) {
+            if (this._observer && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
+
                 this.previousPosition = null;
-                element.removeEventListener("mousedown", this._onMouseDown);
-                element.removeEventListener("mouseup", this._onMouseUp);
-                element.removeEventListener("mouseout", this._onMouseOut);
-                element.removeEventListener("mousemove", this._onMouseMove);
-                
-                this._onMouseDown = null;
-                this._onMouseUp = null;
-                this._onMouseOut = null;
-                this._onMouseMove = null;
-            }        
+            }
         }
-        
-        getTypeName(): string{
+
+        getTypeName(): string {
             return "FreeCameraMouseInput";
         }
-        
-        getSimpleName(){
+
+        getSimpleName() {
             return "mouse";
         }
     }
-    
+
     CameraInputTypes["FreeCameraMouseInput"] = FreeCameraMouseInput;
-}
+}

+ 57 - 61
src/Cameras/Inputs/babylon.freecamera.input.touch.js

@@ -18,80 +18,76 @@ var BABYLON;
         FreeCameraTouchInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
             var previousPosition;
-            if (this._onPointerDown === undefined) {
+            if (this._pointerInput === undefined) {
                 this._onLostFocus = function (evt) {
                     _this._offsetX = null;
                     _this._offsetY = null;
                 };
-                this._onPointerDown = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
+                this._pointerInput = function (p, s) {
+                    var evt = p.event;
+                    if (p.type === 1 /* PointerDown */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        //  evt.srcElement.setPointerCapture(evt.pointerId);
+                        _this._pointerPressed.push(evt.pointerId);
+                        if (_this._pointerPressed.length !== 1) {
+                            return;
+                        }
+                        previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
                     }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
+                    else if (p.type === 2 /* PointerUp */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        var index = _this._pointerPressed.indexOf(evt.pointerId);
+                        if (index === -1) {
+                            return;
+                        }
+                        _this._pointerPressed.splice(index, 1);
+                        if (index != 0) {
+                            return;
+                        }
+                        previousPosition = null;
+                        _this._offsetX = null;
+                        _this._offsetY = null;
                     }
-                    _this._pointerPressed.push(evt.pointerId);
-                    if (_this._pointerPressed.length !== 1) {
-                        return;
+                    else if (p.type === 3 /* PointerMove */) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                        if (!previousPosition) {
+                            return;
+                        }
+                        var index = _this._pointerPressed.indexOf(evt.pointerId);
+                        if (index != 0) {
+                            return;
+                        }
+                        _this._offsetX = evt.clientX - previousPosition.x;
+                        _this._offsetY = -(evt.clientY - previousPosition.y);
                     }
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                };
-                this._onPointerUp = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                    var index = _this._pointerPressed.indexOf(evt.pointerId);
-                    if (index === -1) {
-                        return;
-                    }
-                    _this._pointerPressed.splice(index, 1);
-                    if (index != 0) {
-                        return;
-                    }
-                    previousPosition = null;
-                    _this._offsetX = null;
-                    _this._offsetY = null;
-                };
-                this._onPointerMove = function (evt) {
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                    if (!previousPosition) {
-                        return;
-                    }
-                    var index = _this._pointerPressed.indexOf(evt.pointerId);
-                    if (index != 0) {
-                        return;
-                    }
-                    _this._offsetX = evt.clientX - previousPosition.x;
-                    _this._offsetY = -(evt.clientY - previousPosition.y);
                 };
             }
             element.addEventListener("blur", this._onLostFocus);
-            element.addEventListener("pointerdown", this._onPointerDown);
-            element.addEventListener("pointerup", this._onPointerUp);
-            element.addEventListener("pointerout", this._onPointerUp);
-            element.addEventListener("pointermove", this._onPointerMove);
         };
         FreeCameraTouchInput.prototype.detachControl = function (element) {
-            if (this._onPointerDown && element) {
+            if (this._pointerInput && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
                 element.removeEventListener("blur", this._onLostFocus);
-                element.removeEventListener("pointerdown", this._onPointerDown);
-                element.removeEventListener("pointerup", this._onPointerUp);
-                element.removeEventListener("pointerout", this._onPointerUp);
-                element.removeEventListener("pointermove", this._onPointerMove);
-                this._onPointerDown = null;
-                this._onPointerUp = null;
-                this._onPointerMove = null;
                 this._onLostFocus = null;
                 this._pointerPressed = [];
                 this._offsetX = null;

+ 71 - 79
src/Cameras/Inputs/babylon.freecamera.input.touch.ts

@@ -6,9 +6,8 @@ module BABYLON {
         private _offsetY: number = null;
         private _pointerCount: number = 0;
         private _pointerPressed = [];
-        private _onPointerDown: (e: PointerEvent) => any;
-        private _onPointerUp: (e: PointerEvent) => any;
-        private _onPointerMove: (e: PointerEvent) => any;
+        private _pointerInput: (p: PointerInfo, s: EventState) => void;
+        private _observer: Observer<PointerInfo>;
         private _onLostFocus: (e: FocusEvent) => any;
 
         @serialize()
@@ -20,103 +19,96 @@ module BABYLON {
         attachControl(element: HTMLElement, noPreventDefault?: boolean) {
             var previousPosition;
 
-            if (this._onPointerDown === undefined) {
+            if (this._pointerInput === undefined) {
                 this._onLostFocus = (evt) => {
                     this._offsetX = null;
                     this._offsetY = null;
                 }
-                
-                this._onPointerDown = (evt) => {
 
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-
-                    this._pointerPressed.push(evt.pointerId);
-
-                    if (this._pointerPressed.length !== 1) {
-                        return;
-                    }
+                this._pointerInput = (p, s) => {
+                    var evt = <PointerEvent>p.event;
+                    if (p.type === PointerEventType.PointerDown) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
 
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                };
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
 
-                this._onPointerUp = (evt) => {
+                      //  evt.srcElement.setPointerCapture(evt.pointerId);
+                        this._pointerPressed.push(evt.pointerId);
 
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
+                        if (this._pointerPressed.length !== 1) {
+                            return;
+                        }
 
-                    var index: number = this._pointerPressed.indexOf(evt.pointerId);
-
-                    if (index === -1) {
-                        return;
+                        previousPosition = {
+                            x: evt.clientX,
+                            y: evt.clientY
+                        };
                     }
-                    this._pointerPressed.splice(index, 1);
 
-                    if (index != 0) {
-                        return;
+                    else if (p.type === PointerEventType.PointerUp) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
+
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
+
+                      //  evt.srcElement.releasePointerCapture(evt.pointerId);
+                        var index: number = this._pointerPressed.indexOf(evt.pointerId);
+
+                        if (index === -1) {
+                            return;
+                        }
+                        this._pointerPressed.splice(index, 1);
+
+                        if (index != 0) {
+                            return;
+                        }
+                        previousPosition = null;
+                        this._offsetX = null;
+                        this._offsetY = null;
                     }
-                    previousPosition = null;
-                    this._offsetX = null;
-                    this._offsetY = null;
-                };
 
-                this._onPointerMove = (evt) => {
+                    else if (p.type === PointerEventType.PointerMove) {
+                        if (evt.pointerType === "mouse") {
+                            return;
+                        }
 
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
+                        if (!noPreventDefault) {
+                            evt.preventDefault();
+                        }
 
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
+                        if (!previousPosition) {
+                            return;
+                        }
 
-                    if (!previousPosition) {
-                        return;
-                    }
+                        var index: number = this._pointerPressed.indexOf(evt.pointerId);
 
-                    var index: number = this._pointerPressed.indexOf(evt.pointerId);
+                        if (index != 0) {
+                            return;
+                        }
 
-                    if (index != 0) {
-                        return;
+                        this._offsetX = evt.clientX - previousPosition.x;
+                        this._offsetY = -(evt.clientY - previousPosition.y);
                     }
-
-                    this._offsetX = evt.clientX - previousPosition.x;
-                    this._offsetY = -(evt.clientY - previousPosition.y);
-                };
-
-
+                }
             }
+
             element.addEventListener("blur", this._onLostFocus);
-            element.addEventListener("pointerdown", this._onPointerDown);
-            element.addEventListener("pointerup", this._onPointerUp);
-            element.addEventListener("pointerout", this._onPointerUp);
-            element.addEventListener("pointermove", this._onPointerMove);
         }
 
         detachControl(element: HTMLElement) {
-            if (this._onPointerDown && element){
+            if (this._pointerInput && element) {
+                this.camera.getScene().onPointerObservable.remove(this._observer);
+                this._observer = null;
+
                 element.removeEventListener("blur", this._onLostFocus);
-                element.removeEventListener("pointerdown", this._onPointerDown);
-                element.removeEventListener("pointerup", this._onPointerUp);
-                element.removeEventListener("pointerout", this._onPointerUp);
-                element.removeEventListener("pointermove", this._onPointerMove);
-                
-                this._onPointerDown = null;
-                this._onPointerUp = null;
-                this._onPointerMove = null;
+
                 this._onLostFocus = null;
                 this._pointerPressed = [];
                 this._offsetX = null;
@@ -145,11 +137,11 @@ module BABYLON {
         getTypeName(): string {
             return "FreeCameraTouchInput";
         }
-        
-        getSimpleName(){
+
+        getSimpleName() {
             return "touch";
         }
     }
-    
+
     CameraInputTypes["FreeCameraTouchInput"] = FreeCameraTouchInput;
-}
+}

+ 1 - 0
src/Culling/babylon.ray.js

@@ -262,6 +262,7 @@ var BABYLON;
         Ray.Transform = function (ray, matrix) {
             var newOrigin = BABYLON.Vector3.TransformCoordinates(ray.origin, matrix);
             var newDirection = BABYLON.Vector3.TransformNormal(ray.direction, matrix);
+            newDirection.normalize();
             return new Ray(newOrigin, newDirection, ray.length);
         };
         Ray.smallnum = 0.00000001;

+ 3 - 1
src/Culling/babylon.ray.ts

@@ -106,7 +106,7 @@
             return this.intersectsBoxMinMax(box.minimum, box.maximum);
         }
 
-        public intersectsSphere(sphere): boolean {
+        public intersectsSphere(sphere: BoundingSphere): boolean {
             var x = sphere.center.x - this.origin.x;
             var y = sphere.center.y - this.origin.y;
             var z = sphere.center.z - this.origin.z;
@@ -307,6 +307,8 @@
             var newOrigin = Vector3.TransformCoordinates(ray.origin, matrix);
             var newDirection = Vector3.TransformNormal(ray.direction, matrix);
 
+            newDirection.normalize();
+
             return new Ray(newOrigin, newDirection, ray.length);
         }
     }

+ 3 - 1
src/LensFlare/babylon.lensFlareSystem.js

@@ -178,7 +178,8 @@ var BABYLON;
         };
         LensFlareSystem.Parse = function (parsedLensFlareSystem, scene, rootUrl) {
             var emitter = scene.getLastEntryByID(parsedLensFlareSystem.emitterId);
-            var lensFlareSystem = new LensFlareSystem("lensFlareSystem#" + parsedLensFlareSystem.emitterId, emitter, scene);
+            var name = parsedLensFlareSystem.name || "lensFlareSystem#" + parsedLensFlareSystem.emitterId;
+            var lensFlareSystem = new LensFlareSystem(name, emitter, scene);
             lensFlareSystem.borderLimit = parsedLensFlareSystem.borderLimit;
             for (var index = 0; index < parsedLensFlareSystem.flares.length; index++) {
                 var parsedFlare = parsedLensFlareSystem.flares[index];
@@ -188,6 +189,7 @@ var BABYLON;
         };
         LensFlareSystem.prototype.serialize = function () {
             var serializationObject = {};
+            serializationObject.name = this.name;
             serializationObject.emitterId = this.getEmitter().id;
             serializationObject.borderLimit = this.borderLimit;
             serializationObject.flares = [];

+ 3 - 3
src/LensFlare/babylon.lensFlareSystem.ts

@@ -233,10 +233,10 @@
             var index = this._scene.lensFlareSystems.indexOf(this);
             this._scene.lensFlareSystems.splice(index, 1);
         }
-        
+
         public static Parse(parsedLensFlareSystem: any, scene: Scene, rootUrl: string): LensFlareSystem {
             var emitter = scene.getLastEntryByID(parsedLensFlareSystem.emitterId);
-            
+
             var name = parsedLensFlareSystem.name || "lensFlareSystem#" + parsedLensFlareSystem.emitterId;
 
             var lensFlareSystem = new LensFlareSystem(name, emitter, scene);
@@ -252,7 +252,7 @@
 
         public serialize(): any {
             var serializationObject: any = {};
-            
+
             serializationObject.name = this.name;
 
             serializationObject.emitterId = this.getEmitter().id;

+ 1 - 1
src/Mesh/babylon.meshSimplification.ts

@@ -763,4 +763,4 @@
             return error;
         }
     }
-} 
+} 

+ 6 - 0
src/Tools/babylon.observable.js

@@ -84,6 +84,12 @@ var BABYLON;
             }
         };
         /**
+         * return true is the Observable has at least one Observer registered
+         */
+        Observable.prototype.hasObservers = function () {
+            return this._observers.length > 0;
+        };
+        /**
         * Clear the list of observers
         */
         Observable.prototype.clear = function () {

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

@@ -90,6 +90,13 @@
         }
 
         /**
+         * return true is the Observable has at least one Observer registered
+         */
+        public hasObservers(): boolean {
+            return this._observers.length > 0;
+        }
+
+        /**
         * Clear the list of observers
         */
         public clear(): void {
@@ -108,3 +115,4 @@
         }
     }
 }
+

+ 37 - 2
src/babylon.scene.js

@@ -1,6 +1,19 @@
 var BABYLON;
 (function (BABYLON) {
     /**
+     * This type contains all the data related to a pointer event in Babylon.js.
+     * The event member is an instnce of PointerEvent for all types except PointerWheel and is of type MouseWheelEvent when type equals PointerWheel
+     */
+    var PointerInfo = (function () {
+        function PointerInfo(type, event, pickInfo) {
+            this.type = type;
+            this.event = event;
+            this.pickInfo = pickInfo;
+        }
+        return PointerInfo;
+    }());
+    BABYLON.PointerInfo = PointerInfo;
+    /**
      * Represents a scene to be rendered by the engine.
      * @see http://doc.babylonjs.com/page.php?p=21911
      */
@@ -92,6 +105,10 @@ var BABYLON;
             this.onMeshRemovedObservable = new BABYLON.Observable();
             // Animations
             this.animations = [];
+            /**
+             * Observable event triggered each time an input event is received from the rendering canvas
+             */
+            this.onPointerObservable = new BABYLON.Observable();
             this.cameraToUseForPointers = null; // Define this parameter if you are using multiple cameras and you want to specify which one should be used for pointer position
             this._startingPointerPosition = new BABYLON.Vector2(0, 0);
             this._startingPointerTime = 0;
@@ -490,6 +507,10 @@ var BABYLON;
                 if (_this.onPointerMove) {
                     _this.onPointerMove(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(evt.type === "mousewheel" ? 4 /* PointerWheel */ : 3 /* PointerMove */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
             };
             this._onPointerDown = function (evt) {
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
@@ -543,6 +564,10 @@ var BABYLON;
                 if (_this.onPointerDown) {
                     _this.onPointerDown(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(1 /* PointerDown */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
                 // Sprites
                 _this._pickedDownSprite = null;
                 if (_this.spriteManagers.length > 0) {
@@ -579,8 +604,14 @@ var BABYLON;
                 // Meshes
                 var pickResult = _this.pick(_this._unTranslatedPointerX, _this._unTranslatedPointerY, _this.pointerUpPredicate, false, _this.cameraToUseForPointers);
                 if (pickResult.hit && pickResult.pickedMesh) {
-                    if (_this.onPointerPick && _this._pickedDownMesh != null && pickResult.pickedMesh == _this._pickedDownMesh) {
-                        _this.onPointerPick(evt, pickResult);
+                    if (_this._pickedDownMesh != null && pickResult.pickedMesh == _this._pickedDownMesh) {
+                        if (_this.onPointerPick) {
+                            _this.onPointerPick(evt, pickResult);
+                        }
+                        if (_this.onPointerObservable.hasObservers()) {
+                            var pi = new PointerInfo(5 /* PointerPick */, evt, pickResult);
+                            _this.onPointerObservable.notifyObservers(pi);
+                        }
                     }
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(BABYLON.ActionManager.OnPickUpTrigger, BABYLON.ActionEvent.CreateNew(pickResult.pickedMesh, evt));
@@ -595,6 +626,10 @@ var BABYLON;
                 if (_this.onPointerUp) {
                     _this.onPointerUp(evt, pickResult);
                 }
+                if (_this.onPointerObservable.hasObservers()) {
+                    var pi = new PointerInfo(2 /* PointerUp */, evt, pickResult);
+                    _this.onPointerObservable.notifyObservers(pi);
+                }
                 _this._startingPointerTime = 0;
                 // Sprites
                 if (_this.spriteManagers.length > 0) {

+ 66 - 9
src/babylon.scene.ts

@@ -4,6 +4,22 @@
     }
 
     /**
+     * Different type of pointer event, enumerations are self explanatory
+     */
+    export const enum PointerEventType {
+        None, PointerDown, PointerUp, PointerMove, PointerWheel, PointerPick
+    }
+
+    /**
+     * This type contains all the data related to a pointer event in Babylon.js.
+     * The event member is an instnce of PointerEvent for all types except PointerWheel and is of type MouseWheelEvent when type equals PointerWheel
+     */
+    export class PointerInfo {
+        constructor(public type: PointerEventType, public event: PointerEvent | MouseWheelEvent, public pickInfo: PickingInfo) {
+        }
+    }
+
+    /**
      * Represents a scene to be rendered by the engine.
      * @see http://doc.babylonjs.com/page.php?p=21911
      */
@@ -182,10 +198,29 @@
         private _onPointerMove: (evt: PointerEvent) => void;
         private _onPointerDown: (evt: PointerEvent) => void;
         private _onPointerUp: (evt: PointerEvent) => void;
+
+        /**
+         * @deprecated Use onPointerObservable instead
+         */
         public onPointerMove: (evt: PointerEvent, pickInfo: PickingInfo) => void;
+        /**
+         * @deprecated Use onPointerObservable instead
+         */
         public onPointerDown: (evt: PointerEvent, pickInfo: PickingInfo) => void;
+        /**
+         * @deprecated Use onPointerObservable instead
+         */
         public onPointerUp: (evt: PointerEvent, pickInfo: PickingInfo) => void;
+        /**
+         * @deprecated Use onPointerObservable instead
+         */
         public onPointerPick: (evt: PointerEvent, pickInfo: PickingInfo) => void;
+
+        /**
+         * Observable event triggered each time an input event is received from the rendering canvas
+         */
+        public onPointerObservable = new Observable<PointerInfo>();
+
         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;
@@ -594,7 +629,7 @@
 
                 // Meshes
                 var pickResult = this.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, this.pointerMovePredicate, false, this.cameraToUseForPointers);
-                    
+
                 if (pickResult.hit && pickResult.pickedMesh) {
                     this.setPointerOverSprite(null);
 
@@ -623,6 +658,11 @@
                 if (this.onPointerMove) {
                     this.onPointerMove(evt, pickResult);
                 }
+
+                if (this.onPointerObservable.hasObservers()) {
+                    let pi = new PointerInfo(evt.type === "mousewheel" ? PointerEventType.PointerWheel : PointerEventType.PointerMove, evt, pickResult);
+                    this.onPointerObservable.notifyObservers(pi);
+                }
             };
 
             this._onPointerDown = (evt: PointerEvent) => {
@@ -665,7 +705,7 @@
 
                         if (pickResult.pickedMesh.actionManager.hasSpecificTrigger(ActionManager.OnLongPressTrigger)) {
                             var that = this;
-                            window.setTimeout(function() {
+                            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);
@@ -687,6 +727,11 @@
                     this.onPointerDown(evt, pickResult);
                 }
 
+                if (this.onPointerObservable.hasObservers()) {
+                    let pi = new PointerInfo(PointerEventType.PointerDown, evt, pickResult);
+                    this.onPointerObservable.notifyObservers(pi);
+                }
+
                 // Sprites
                 this._pickedDownSprite = null;
                 if (this.spriteManagers.length > 0) {
@@ -716,7 +761,7 @@
                 if (!this.cameraToUseForPointers && !this.activeCamera) {
                     return;
                 }
-                
+
                 this._updatePointerPosition(evt);
 
                 if (!this.pointerUpPredicate) {
@@ -729,8 +774,14 @@
                 var pickResult = this.pick(this._unTranslatedPointerX, this._unTranslatedPointerY, this.pointerUpPredicate, false, this.cameraToUseForPointers);
 
                 if (pickResult.hit && pickResult.pickedMesh) {
-                    if (this.onPointerPick && this._pickedDownMesh != null && pickResult.pickedMesh == this._pickedDownMesh) {
-                        this.onPointerPick(evt, pickResult);
+                    if (this._pickedDownMesh != null && pickResult.pickedMesh == this._pickedDownMesh) {
+                        if (this.onPointerPick) {
+                            this.onPointerPick(evt, pickResult);
+                        }
+                        if (this.onPointerObservable.hasObservers()) {
+                            let pi = new PointerInfo(PointerEventType.PointerPick, evt, pickResult);
+                            this.onPointerObservable.notifyObservers(pi);
+                        }
                     }
                     if (pickResult.pickedMesh.actionManager) {
                         pickResult.pickedMesh.actionManager.processTrigger(ActionManager.OnPickUpTrigger, ActionEvent.CreateNew(pickResult.pickedMesh, evt));
@@ -748,8 +799,13 @@
                     this.onPointerUp(evt, pickResult);
                 }
 
+                if (this.onPointerObservable.hasObservers()) {
+                    let pi = new PointerInfo(PointerEventType.PointerUp, evt, pickResult);
+                    this.onPointerObservable.notifyObservers(pi);
+                }
+
                 this._startingPointerTime = 0;
-                
+
                 // Sprites
                 if (this.spriteManagers.length > 0) {
                     pickResult = this.pickSprite(this._unTranslatedPointerX, this._unTranslatedPointerY, spritePredicate, false, this.cameraToUseForPointers);
@@ -1119,7 +1175,7 @@
             var position = this.cameras.push(newCamera);
             this.onNewCameraAddedObservable.notifyObservers(newCamera);
         }
-        
+
         /**
          * Switch active camera
          * @param {Camera} newCamera - new active camera
@@ -1767,7 +1823,7 @@
             this.updateTransformMatrix();
 
             this.onBeforeCameraRenderObservable.notifyObservers(this.activeCamera);
-            
+
             // Meshes
             var beforeEvaluateActiveMeshesDate = Tools.Now;
             Tools.StartPerformanceCounter("Active meshes evaluation");
@@ -2418,7 +2474,7 @@
             }
 
             var cameraViewport = camera.viewport;
-            var viewport = cameraViewport.toGlobal(engine. getRenderWidth(), engine.getRenderHeight());
+            var viewport = cameraViewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight());
             var identity = Matrix.Identity();
 
             // Moving coordinates to local viewport world
@@ -2708,3 +2764,4 @@
     }
 }
 
+