Просмотр исходного кода

Merge remote-tracking branch 'refs/remotes/BabylonJS/master'

Raanan Weber 10 лет назад
Родитель
Сommit
41b0a00cc9
100 измененных файлов с 22886 добавлено и 17642 удалено
  1. 2 1
      Exporters/Blender/io_export_babylon.py
  2. 15 1
      Tools/Gulp/config.json
  3. 48 39
      Tools/Gulp/gulp-addModuleExports.js
  4. 4 0
      Tools/Gulp/gulpfile.js
  5. 23 22
      dist/preview release/babylon.core.js
  6. 1885 1298
      dist/preview release/babylon.d.ts
  7. 30 29
      dist/preview release/babylon.js
  8. 9540 8138
      dist/preview release/babylon.max.js
  9. 29 29
      dist/preview release/babylon.noworker.js
  10. 2 0
      dist/preview release/what's new.md
  11. 18 0
      materialsLibrary/config.json
  12. 178 0
      materialsLibrary/dist/babylon.gridMaterial.js
  13. 1 0
      materialsLibrary/dist/babylon.gridMaterial.min.js
  14. 188 29
      materialsLibrary/dist/babylon.pbrMaterial.js
  15. 3 3
      materialsLibrary/dist/babylon.pbrMaterial.min.js
  16. 55 0
      materialsLibrary/dist/dts/babylon.gridMaterial.d.ts
  17. 168 6
      materialsLibrary/dist/dts/babylon.pbrMaterial.d.ts
  18. 13 1
      materialsLibrary/gulpfile.js
  19. 192 0
      materialsLibrary/materials/grid/babylon.gridmaterial.ts
  20. 94 0
      materialsLibrary/materials/grid/grid.fragment.fx
  21. 19 0
      materialsLibrary/materials/grid/grid.vertex.fx
  22. 3 0
      materialsLibrary/materials/grid/legacygrid.fragment.fx
  23. 11 0
      materialsLibrary/materials/grid/legacygrid.vertex.fx
  24. 181 19
      materialsLibrary/materials/pbr/babylon.pbrMaterial.ts
  25. 31 0
      materialsLibrary/materials/pbr/includes/harmonicsFunctions.fx
  26. 197 0
      materialsLibrary/materials/pbr/includes/pbrFunctions.fx
  27. 130 0
      materialsLibrary/materials/pbr/includes/pbrLightFunctions.fx
  28. 50 0
      materialsLibrary/materials/pbr/includes/pbrLightFunctionsCall.fx
  29. 171 0
      materialsLibrary/materials/pbr/includes/pbrShadowFunctions.fx
  30. 119 451
      materialsLibrary/materials/pbr/legacypbr.fragment.fx
  31. 0 4
      materialsLibrary/materials/pbr/legacypbr.vertex.fx
  32. 15 780
      materialsLibrary/materials/pbr/pbr.fragment.fx
  33. 1 1
      materialsLibrary/materials/pbr/pbr.vertex.fx
  34. 65 0
      materialsLibrary/test/add/addgrid.js
  35. 8 1
      materialsLibrary/test/index.html
  36. 4908 4546
      materialsLibrary/test/refs/babylon.max.js
  37. 31 0
      src/Animations/babylon.animation.js
  38. 34 0
      src/Animations/babylon.animation.ts
  39. 1 1
      src/Bones/babylon.bone.js
  40. 1 1
      src/Bones/babylon.bone.ts
  41. 45 1
      src/Bones/babylon.skeleton.js
  42. 51 1
      src/Bones/babylon.skeleton.ts
  43. 69 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.js
  44. 73 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.ts
  45. 108 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.js
  46. 108 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.ts
  47. 55 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.js
  48. 53 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.ts
  49. 193 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js
  50. 229 0
      src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.ts
  51. 75 0
      src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.js
  52. 84 0
      src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.ts
  53. 64 0
      src/Cameras/Inputs/babylon.freecamera.input.gamepad.js
  54. 67 0
      src/Cameras/Inputs/babylon.freecamera.input.gamepad.ts
  55. 111 0
      src/Cameras/Inputs/babylon.freecamera.input.keyboard.js
  56. 110 0
      src/Cameras/Inputs/babylon.freecamera.input.keyboard.ts
  57. 102 0
      src/Cameras/Inputs/babylon.freecamera.input.mouse.js
  58. 117 0
      src/Cameras/Inputs/babylon.freecamera.input.mouse.ts
  59. 139 0
      src/Cameras/Inputs/babylon.freecamera.input.touch.js
  60. 166 0
      src/Cameras/Inputs/babylon.freecamera.input.touch.ts
  61. 52 0
      src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.js
  62. 61 0
      src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.ts
  63. 43 0
      src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.js
  64. 55 0
      src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.ts
  65. 1 27
      src/Cameras/VR/babylon.vrDeviceOrientationCamera.js
  66. 2 37
      src/Cameras/VR/babylon.vrDeviceOrientationCamera.ts
  67. 1 0
      src/Cameras/VR/babylon.webVRCamera.js
  68. 2 0
      src/Cameras/VR/babylon.webVRCamera.ts
  69. 141 268
      src/Cameras/babylon.arcRotateCamera.js
  70. 136 310
      src/Cameras/babylon.arcRotateCamera.ts
  71. 38 0
      src/Cameras/babylon.arcRotateCameraInputsManager.js
  72. 34 0
      src/Cameras/babylon.arcRotateCameraInputsManager.ts
  73. 23 0
      src/Cameras/babylon.camera.js
  74. 33 3
      src/Cameras/babylon.camera.ts
  75. 115 0
      src/Cameras/babylon.cameraInputsManager.js
  76. 150 0
      src/Cameras/babylon.cameraInputsManager.ts
  77. 31 66
      src/Cameras/babylon.deviceOrientationCamera.js
  78. 26 72
      src/Cameras/babylon.deviceOrientationCamera.ts
  79. 84 150
      src/Cameras/babylon.freeCamera.js
  80. 90 176
      src/Cameras/babylon.freeCamera.ts
  81. 50 0
      src/Cameras/babylon.freeCameraInputsManager.js
  82. 49 0
      src/Cameras/babylon.freeCameraInputsManager.ts
  83. 30 0
      src/Cameras/babylon.gamepadCamera.js
  84. 27 0
      src/Cameras/babylon.gamepadCamera.ts
  85. 31 114
      src/Cameras/babylon.touchCamera.js
  86. 25 142
      src/Cameras/babylon.touchCamera.ts
  87. 31 52
      src/Cameras/babylon.universalCamera.js
  88. 25 52
      src/Cameras/babylon.universalCamera.ts
  89. 1 37
      src/Cameras/babylon.virtualJoysticksCamera.js
  90. 2 46
      src/Cameras/babylon.virtualJoysticksCamera.ts
  91. 15 0
      src/Lights/babylon.light.js
  92. 16 0
      src/Lights/babylon.light.ts
  93. 399 321
      src/Loading/Plugins/babylon.babylonFileLoader.js
  94. 426 356
      src/Loading/Plugins/babylon.babylonFileLoader.ts
  95. 43 0
      src/Loading/babylon.sceneLoader.js
  96. 31 0
      src/Loading/babylon.sceneLoader.ts
  97. 29 1
      src/Materials/Textures/babylon.hdrCubeTexture.js
  98. 45 10
      src/Materials/Textures/babylon.hdrcubetexture.ts
  99. 10 0
      src/Materials/babylon.material.js
  100. 0 0
      src/Materials/babylon.material.ts

+ 2 - 1
Exporters/Blender/io_export_babylon.py

@@ -1,7 +1,7 @@
 bl_info = {
     'name': 'Babylon.js',
     'author': 'David Catuhe, Jeff Palmer',
-    'version': (4, 4, 1),
+    'version': (4, 4, 2),
     'blender': (2, 75, 0),
     'location': 'File > Export > Babylon.js (.babylon)',
     'description': 'Export Babylon.js scenes (.babylon)',
@@ -399,6 +399,7 @@ class Main(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
         # Open file
         file_handler = io.open(self.filepathMinusExtension + '.babylon', 'w', encoding='utf8')
         file_handler.write('{')
+        file_handler.write('"producer":{"name":"Blender","version":"' + bpy.app.version_string + '","exporter_version":"' + format_version() + '","file":"' + Main.nameSpace + '.babylon"},\n')
         self.world.to_scene_file(file_handler)
 
         # Materials

+ 15 - 1
Tools/Gulp/config.json

@@ -42,11 +42,25 @@
       "../../src/Collisions/babylon.collider.js",
       "../../src/Collisions/babylon.collisionCoordinator.js",
       "../../src/Cameras/babylon.camera.js",
+      "../../src/cameras/babylon.camerainputsmanager.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.mouse.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.keyboard.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.touch.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.deviceorientation.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.vrdeviceorientation.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.gamepad.js",
+      "../../src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js",
+      "../../src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js",
+      "../../src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js",
+      "../../src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js",
+      "../../src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js",
       "../../src/Cameras/babylon.targetCamera.js",
       "../../src/Cameras/babylon.freeCamera.js",
+      "../../src/Cameras/babylon.freeCameraInputsManager.js",
       "../../src/Cameras/babylon.followCamera.js",
       "../../src/Cameras/babylon.touchCamera.js",
       "../../src/Cameras/babylon.arcRotateCamera.js",
+      "../../src/Cameras/babylon.arcRotateCameraInputsManager.js",
       "../../src/Rendering/babylon.renderingManager.js",
       "../../src/Rendering/babylon.renderingGroup.js",
       "../../src/babylon.scene.js",
@@ -193,7 +207,7 @@
       "../../src/tools/hdr/babylon.tools.hdr.js",
       "../../src/tools/hdr/babylon.tools.pmremGenerator.js",
       "../../src/materials/textures/babylon.hdrcubetexture.js",
-      "../../src/debug/babylon.skeletonViewer.js"
+      "../../src/debug/babylon.skeletonViewer.js"      
     ]
   }
 }

+ 48 - 39
Tools/Gulp/gulp-addModuleExports.js

@@ -1,39 +1,48 @@
-var gutil = require('gulp-util');
-var through = require('through2');
-
-module.exports = function (varName) {
-  return through.obj(function (file, enc, cb) {
-
-    var moduleExportsAddition = 
-      '\nif (((typeof window != "undefined" && window.module) || (typeof module != "undefined")) && typeof module.exports != "undefined") {\n' +
-      '    module.exports = ' + varName + ';\n' +
-      '};\n';
-      
-      var extendsAddition = 
-      'var __extends = (this && this.__extends) || function (d, b) {\n' +
-        'for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];\n' +
-        'function __() { this.constructor = d; }\n' +
-        '__.prototype = b.prototype;\n' +
-        'd.prototype = new __();\n' +
-      '};\n';
-
-    if (file.isNull()) {
-      cb(null, file);
-      return;
-    }
-
-    if (file.isStream()) {
-      //streams not supported, no need for now.
-      return;
-    }
-
-    try {
-      file.contents = new Buffer(extendsAddition.concat(String(file.contents)).concat(moduleExportsAddition));
-      this.push(file);
-
-    } catch (err) {
-      this.emit('error', new gutil.PluginError('gulp-add-module-exports', err, {fileName: file.path}));
-    }
-    cb();
-  });
-};
+var gutil = require('gulp-util');
+var through = require('through2');
+
+module.exports = function (varName) {
+    return through.obj(function (file, enc, cb) {
+
+        var moduleExportsAddition =
+          '\nif (((typeof window != "undefined" && window.module) || (typeof module != "undefined")) && typeof module.exports != "undefined") {\n' +
+          '    module.exports = ' + varName + ';\n' +
+          '};\n';
+
+        var extendsAddition =
+        'var __extends = (this && this.__extends) || function (d, b) {\n' +
+          'for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];\n' +
+          'function __() { this.constructor = d; }\n' +
+          '__.prototype = b.prototype;\n' +
+          'd.prototype = new __();\n' +
+        '};\n';
+
+        var decorateAddition =
+        'var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n' +
+            'var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n' +
+            'if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);\n' +
+            'else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n' +
+            'return c > 3 && r && Object.defineProperty(target, key, r), r;\n' +
+        '};\n';
+
+        if (file.isNull()) {
+            cb(null, file);
+            return;
+        }
+
+        if (file.isStream()) {
+            //streams not supported, no need for now.
+            return;
+        }
+
+        try {
+            file.contents = new Buffer(decorateAddition.concat(new Buffer(extendsAddition.concat(String(file.contents)).concat(moduleExportsAddition))));
+            this.push(file);
+
+        } catch (err) {
+            this.emit('error', new gutil.PluginError('gulp-add-module-exports', err, { fileName: file.path }));
+        }
+        cb();
+    });
+};
+

+ 4 - 0
Tools/Gulp/gulpfile.js

@@ -20,6 +20,7 @@ var shadersStream;
 var workersStream;
 
 var extendsSearchRegex = /var\s__extends[\s\S]+?\};/g;
+var decorateSearchRegex = /var\s__decorate[\s\S]+?\};/g;
 
 //function to convert the shaders' filenames to variable names.
 function shadersName(filename) {
@@ -120,6 +121,7 @@ gulp.task("buildCore", ["shaders"], function () {
         .pipe(concat(config.build.minCoreFilename))
         .pipe(cleants())
         .pipe(replace(extendsSearchRegex, ""))
+        .pipe(replace(decorateSearchRegex, ""))
         .pipe(addModuleExports("BABYLON"))
         .pipe(uglify())
         .pipe(gulp.dest(config.build.outputDirectory));
@@ -135,6 +137,7 @@ gulp.task("buildNoWorker", ["shaders"], function () {
         .pipe(concat(config.build.minNoWorkerFilename))
         .pipe(cleants())
         .pipe(replace(extendsSearchRegex, ""))
+        .pipe(replace(decorateSearchRegex, ""))
         .pipe(addModuleExports("BABYLON"))
         .pipe(uglify())
         .pipe(gulp.dest(config.build.outputDirectory));
@@ -151,6 +154,7 @@ gulp.task("build", ["workers", "shaders"], function () {
         .pipe(concat(config.build.filename))
         .pipe(cleants())
         .pipe(replace(extendsSearchRegex, ""))
+        .pipe(replace(decorateSearchRegex, ""))
         .pipe(addModuleExports("BABYLON"))
         .pipe(gulp.dest(config.build.outputDirectory))
         .pipe(rename(config.build.minFilename))

Разница между файлами не показана из-за своего большого размера
+ 23 - 22
dist/preview release/babylon.core.js


Разница между файлами не показана из-за своего большого размера
+ 1885 - 1298
dist/preview release/babylon.d.ts


Разница между файлами не показана из-за своего большого размера
+ 30 - 29
dist/preview release/babylon.js


Разница между файлами не показана из-за своего большого размера
+ 9540 - 8138
dist/preview release/babylon.max.js


Разница между файлами не показана из-за своего большого размера
+ 29 - 29
dist/preview release/babylon.noworker.js


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

@@ -8,7 +8,9 @@
     - StandardMaterial now supports Parallax and Parallax Occlusion Mapping ([nockawa](https://github.com/nockawa))
     - Animations blending. See [demo here](http://www.babylonjs-playground.com/#2BLI9T#3). More [info here](NEED DOC!) ([deltakosh](https://github.com/deltakosh))
     - New debuger tool: SkeletonViewer. See [demo here](Demo available here: http://www.babylonjs-playground.com/#1BZJVJ#8) (Adam & [deltakosh](https://github.com/deltakosh))
+    - Added Camera Inputs Manager to manage camera inputs (mouse, touch, keyboard, gamepad, ...) in a composable way, without relying on class inheritance [gleborgne](https://github.com/gleborgne)
   - **Updates**
+    - Added Camera.ForceAttachControlToAlwaysPreventDefault to help embedding Babylon.js in iFrames ([deltakosh](https://github.com/deltakosh))
     - Support for Layer.alphaTest ([deltakosh](https://github.com/deltakosh))
     - New scene.pointerDownPredicate, scene.pointerMovePredicate, scene.pointerUpPredicate to define your own predicates for meshes picking selection ([deltakosh](https://github.com/deltakosh))
     - New OnPickTrigger support for spritesManager ([deltakosh](https://github.com/deltakosh))

+ 18 - 0
materialsLibrary/config.json

@@ -11,6 +11,13 @@
     },
     {
       "file": "materials/pbr/babylon.pbrMaterial.ts",
+      "referenceFiles": [
+        "materials/pbr/includes/pbrFunctions.fx",
+        "materials/pbr/includes/harmonicsFunctions.fx",
+        "materials/pbr/includes/pbrLightFunctions.fx",
+        "materials/pbr/includes/pbrLightFunctionsCall.fx",
+        "materials/pbr/includes/pbrShadowFunctions.fx"  
+      ],
       "shaderFiles": [
         "materials/pbr/pbr.vertex.fx",
         "materials/pbr/pbr.fragment.fx",
@@ -91,6 +98,17 @@
         "materials/sky/sky.fragment.fx"
       ],
       "output": "babylon.skyMaterial.js"
+    },
+    {
+      "file": "materials/grid/babylon.gridMaterial.ts",
+      "shaderFiles": [
+        "materials/grid/grid.vertex.fx",
+        "materials/grid/grid.fragment.fx",
+        "materials/grid/legacygrid.vertex.fx",
+        "materials/grid/legacygrid.fragment.fx"
+      ],
+      "output": "babylon.gridMaterial.js",
+      "declarationFilename": "babylon.gridMaterial.d.ts"
     }
   ],
   "build": {

Разница между файлами не показана из-за своего большого размера
+ 178 - 0
materialsLibrary/dist/babylon.gridMaterial.js


Разница между файлами не показана из-за своего большого размера
+ 1 - 0
materialsLibrary/dist/babylon.gridMaterial.min.js


Разница между файлами не показана из-за своего большого размера
+ 188 - 29
materialsLibrary/dist/babylon.pbrMaterial.js


Разница между файлами не показана из-за своего большого размера
+ 3 - 3
materialsLibrary/dist/babylon.pbrMaterial.min.js


+ 55 - 0
materialsLibrary/dist/dts/babylon.gridMaterial.d.ts

@@ -0,0 +1,55 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts" />
+declare module BABYLON {
+    /**
+     * The grid materials allows you to wrap any shape with a grid.
+     * Colors are customizable.
+     */
+    class GridMaterial extends BABYLON.Material {
+        /**
+         * Main color of the grid (e.g. between lines)
+         */
+        mainColor: Color3;
+        /**
+         * Color of the grid lines.
+         */
+        lineColor: Color3;
+        /**
+         * The scale of the grid compared to unit.
+         */
+        gridRatio: number;
+        /**
+         * The frequency of thicker lines.
+         */
+        majorUnitFrequency: number;
+        /**
+         * The visibility of minor units in the grid.
+         */
+        minorUnitVisibility: number;
+        /**
+         * The grid opacity outside of the lines.
+         */
+        opacity: number;
+        private _gridControl;
+        private _renderId;
+        private _defines;
+        private _cachedDefines;
+        /**
+         * constructor
+         * @param name The name given to the material in order to identify it afterwards.
+         * @param scene The scene the material is used in.
+         */
+        constructor(name: string, scene: Scene);
+        /**
+         * Returns wehter or not the grid requires alpha blending.
+         */
+        needAlphaBlending(): boolean;
+        private _checkCache(scene, mesh?, useInstances?);
+        isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean;
+        bindOnlyWorldMatrix(world: Matrix): void;
+        bind(world: Matrix, mesh?: Mesh): void;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): GridMaterial;
+        serialize(): any;
+        static Parse(source: any, scene: Scene, rootUrl: string): GridMaterial;
+    }
+}

+ 168 - 6
materialsLibrary/dist/dts/babylon.pbrMaterial.d.ts

@@ -1,65 +1,221 @@
 /// <reference path="../../../dist/preview release/babylon.d.ts" />
 declare module BABYLON {
+    /**
+     * The Physically based material of BJS.
+     *
+     * This offers the main features of a standard PBR material.
+     * For more information, please refer to the documentation :
+     * http://doc.babylonjs.com/extensions/Physically_Based_Rendering
+     */
     class PBRMaterial extends BABYLON.Material {
+        /**
+         * Intensity of the direct lights e.g. the four lights available in your scene.
+         * This impacts both the direct diffuse and specular highlights.
+         */
         directIntensity: number;
+        /**
+         * Intensity of the emissive part of the material.
+         * This helps controlling the emissive effect without modifying the emissive color.
+         */
         emissiveIntensity: number;
+        /**
+         * Intensity of the environment e.g. how much the environment will light the object
+         * either through harmonics for rough material or through the refelction for shiny ones.
+         */
         environmentIntensity: number;
+        /**
+         * This is a special control allowing the reduction of the specular highlights coming from the
+         * four lights of the scene. Those highlights may not be needed in full environment lighting.
+         */
         specularIntensity: number;
         private _lightingInfos;
+        /**
+         * Debug Control allowing disabling the bump map on this material.
+         */
+        disableBumpMap: boolean;
+        /**
+         * Debug Control helping enforcing or dropping the darkness of shadows.
+         * 1.0 means the shadows have their normal darkness, 0.0 means the shadows are not visible.
+         */
         overloadedShadowIntensity: number;
+        /**
+         * Debug Control helping dropping the shading effect coming from the diffuse lighting.
+         * 1.0 means the shade have their normal impact, 0.0 means no shading at all.
+         */
         overloadedShadeIntensity: number;
         private _overloadedShadowInfos;
+        /**
+         * The camera exposure used on this material.
+         * This property is here and not in the camera to allow controlling exposure without full screen post process.
+         * This corresponds to a photographic exposure.
+         */
         cameraExposure: number;
+        /**
+         * The camera contrast used on this material.
+         * This property is here and not in the camera to allow controlling contrast without full screen post process.
+         */
         cameraContrast: number;
         private _cameraInfos;
         private _microsurfaceTextureLods;
+        /**
+         * Debug Control allowing to overload the ambient color.
+         * This as to be use with the overloadedAmbientIntensity parameter.
+         */
+        overloadedAmbient: Color3;
+        /**
+         * Debug Control indicating how much the overloaded ambient color is used against the default one.
+         */
         overloadedAmbientIntensity: number;
+        /**
+         * Debug Control allowing to overload the albedo color.
+         * This as to be use with the overloadedAlbedoIntensity parameter.
+         */
+        overloadedAlbedo: Color3;
+        /**
+         * Debug Control indicating how much the overloaded albedo color is used against the default one.
+         */
         overloadedAlbedoIntensity: number;
+        /**
+         * Debug Control allowing to overload the reflectivity color.
+         * This as to be use with the overloadedReflectivityIntensity parameter.
+         */
+        overloadedReflectivity: Color3;
+        /**
+         * Debug Control indicating how much the overloaded reflectivity color is used against the default one.
+         */
         overloadedReflectivityIntensity: number;
+        /**
+         * Debug Control allowing to overload the emissive color.
+         * This as to be use with the overloadedEmissiveIntensity parameter.
+         */
+        overloadedEmissive: Color3;
+        /**
+         * Debug Control indicating how much the overloaded emissive color is used against the default one.
+         */
         overloadedEmissiveIntensity: number;
         private _overloadedIntensity;
-        overloadedAmbient: Color3;
-        overloadedAlbedo: Color3;
-        overloadedReflectivity: Color3;
-        overloadedEmissive: Color3;
+        /**
+         * Debug Control allowing to overload the reflection color.
+         * This as to be use with the overloadedReflectionIntensity parameter.
+         */
         overloadedReflection: Color3;
+        /**
+         * Debug Control indicating how much the overloaded reflection color is used against the default one.
+         */
+        overloadedReflectionIntensity: number;
+        /**
+         * Debug Control allowing to overload the microsurface.
+         * This as to be use with the overloadedMicroSurfaceIntensity parameter.
+         */
         overloadedMicroSurface: number;
+        /**
+         * Debug Control indicating how much the overloaded microsurface is used against the default one.
+         */
         overloadedMicroSurfaceIntensity: number;
-        overloadedReflectionIntensity: number;
         private _overloadedMicroSurface;
-        disableBumpMap: boolean;
+        /**
+         * AKA Diffuse Texture in standard nomenclature.
+         */
         albedoTexture: BaseTexture;
+        /**
+         * AKA Occlusion Texture in other nomenclature.
+         */
         ambientTexture: BaseTexture;
         opacityTexture: BaseTexture;
         reflectionTexture: BaseTexture;
         emissiveTexture: BaseTexture;
+        /**
+         * AKA Specular texture in other nomenclature.
+         */
         reflectivityTexture: BaseTexture;
         bumpTexture: BaseTexture;
         lightmapTexture: BaseTexture;
         refractionTexture: BaseTexture;
         ambientColor: Color3;
+        /**
+         * AKA Diffuse Color in other nomenclature.
+         */
         albedoColor: Color3;
+        /**
+         * AKA Specular Color in other nomenclature.
+         */
         reflectivityColor: Color3;
         reflectionColor: Color3;
         emissiveColor: Color3;
+        /**
+         * AKA Glossiness in other nomenclature.
+         */
         microSurface: number;
+        /**
+         * source material index of refraction (IOR)' / 'destination material IOR.
+         */
         indexOfRefraction: number;
+        /**
+         * Controls if refraction needs to be inverted on Y. This could be usefull for procedural texture.
+         */
         invertRefractionY: boolean;
         opacityFresnelParameters: FresnelParameters;
         emissiveFresnelParameters: FresnelParameters;
+        /**
+         * This parameters will make the material used its opacity to control how much it is refracting aginst not.
+         * Materials half opaque for instance using refraction could benefit from this control.
+         */
         linkRefractionWithTransparency: boolean;
+        /**
+         * The emissive and albedo are linked to never be more than one (Energy conservation).
+         */
         linkEmissiveWithAlbedo: boolean;
         useLightmapAsShadowmap: boolean;
+        /**
+         * In this mode, the emissive informtaion will always be added to the lighting once.
+         * A light for instance can be thought as emissive.
+         */
         useEmissiveAsIllumination: boolean;
+        /**
+         * Secifies that the alpha is coming form the albedo channel alpha channel.
+         */
         useAlphaFromAlbedoTexture: boolean;
+        /**
+         * Specifies that the material will keeps the specular highlights over a transparent surface (only the most limunous ones).
+         * A car glass is a good exemple of that. When sun reflects on it you can not see what is behind.
+         */
         useSpecularOverAlpha: boolean;
+        /**
+         * Specifies if the reflectivity texture contains the glossiness information in its alpha channel.
+         */
         useMicroSurfaceFromReflectivityMapAlpha: boolean;
+        /**
+         * In case the reflectivity map does not contain the microsurface information in its alpha channel,
+         * The material will try to infer what glossiness each pixel should be.
+         */
         useAutoMicroSurfaceFromReflectivityMap: boolean;
+        /**
+         * Allows to work with scalar in linear mode. This is definitely a matter of preferences and tools used during
+         * the creation of the material.
+         */
         useScalarInLinearSpace: boolean;
+        /**
+         * BJS is using an harcoded light falloff based on a manually sets up range.
+         * In PBR, one way to represents the fallof is to use the inverse squared root algorythm.
+         * This parameter can help you switch back to the BJS mode in order to create scenes using both materials.
+         */
         usePhysicalLightFalloff: boolean;
+        /**
+         * Specifies that the material will keeps the reflection highlights over a transparent surface (only the most limunous ones).
+         * A car glass is a good exemple of that. When the street lights reflects on it you can not see what is behind.
+         */
         useRadianceOverAlpha: boolean;
+        /**
+         * Allows using the bump map in parallax mode.
+         */
         useParallax: boolean;
+        /**
+         * Allows using the bump map in parallax occlusion mode.
+         */
         useParallaxOcclusion: boolean;
+        /**
+         * Controls the scale bias of the parallax mode.
+         */
         parallaxScaleBias: number;
         disableLighting: boolean;
         private _renderTargets;
@@ -70,6 +226,12 @@ declare module BABYLON {
         private _defines;
         private _cachedDefines;
         private _useLogarithmicDepth;
+        /**
+         * Instantiates a new PBRMaterial instance.
+         *
+         * @param name The material name
+         * @param scene The scene the material will be use in.
+         */
         constructor(name: string, scene: Scene);
         useLogarithmicDepth: boolean;
         needAlphaBlending(): boolean;

+ 13 - 1
materialsLibrary/gulpfile.js

@@ -20,6 +20,10 @@ function shadersName(filename) {
       .replace('.fx', 'Shader');
 }
 
+function includeShadersName(filename) {
+    return filename.replace('.fx', '');
+}
+
 gulp.task('copyReference', function () {
     return gulp.src("../dist/preview release/babylon.max.js").pipe(gulp.dest("test/refs"));
 });
@@ -57,7 +61,15 @@ gulp.task('default', ["copyReference"], function () {
                 .pipe(uncommentShader())
                 .pipe(srcToVariable("BABYLON.Effect.ShadersStore", true, shadersName));
 
-        return merge2(js, shader)
+        if (!material.referenceFiles) {
+            material.referenceFiles = [];
+        }
+        
+        var includeShader = gulp.src(material.referenceFiles)
+            .pipe(uncommentShader())
+            .pipe(srcToVariable("BABYLON.Effect.IncludesShadersStore", true, includeShadersName));
+        
+        return merge2(js, shader, includeShader)
             .pipe(cleants())
             .pipe(replace(extendsSearchRegex, ""))
             .pipe(concat(material.output))

+ 192 - 0
materialsLibrary/materials/grid/babylon.gridmaterial.ts

@@ -0,0 +1,192 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON {
+    class GRIDMaterialDefines extends MaterialDefines {
+        public TRANSPARENT = false;
+
+        constructor() {
+            super();
+            this._keys = Object.keys(this);
+        }
+    }
+    
+    /**
+     * The grid materials allows you to wrap any shape with a grid.
+     * Colors are customizable.
+     */
+    export class GridMaterial extends BABYLON.Material {
+        
+        /**
+         * Main color of the grid (e.g. between lines)
+         */
+        @serializeAsColor3()
+        public mainColor = Color3.White();
+        
+        /**
+         * Color of the grid lines.
+         */
+        @serializeAsColor3()
+        public lineColor = Color3.Black();
+        
+        /**
+         * The scale of the grid compared to unit.
+         */
+        @serialize()
+        public gridRatio = 1.0;
+        
+        /**
+         * The frequency of thicker lines.
+         */
+        @serialize()
+        public majorUnitFrequency = 10;
+        
+        /**
+         * The visibility of minor units in the grid.
+         */
+        @serialize()
+        public minorUnitVisibility = 0.33;
+        
+        /**
+         * The grid opacity outside of the lines.
+         */
+        @serialize()
+        public opacity = 1.0;
+        
+        private _gridControl: Vector4 = new Vector4(this.gridRatio, this.majorUnitFrequency, this.minorUnitVisibility, this.opacity);
+        
+        private _renderId: number;
+        private _defines = new GRIDMaterialDefines();
+        private _cachedDefines = new GRIDMaterialDefines();
+        
+        /**
+         * constructor
+         * @param name The name given to the material in order to identify it afterwards.
+         * @param scene The scene the material is used in.
+         */
+        constructor(name: string, scene: Scene) {
+            super(name, scene);
+        }
+        
+        /**
+         * Returns wehter or not the grid requires alpha blending.
+         */
+        public needAlphaBlending(): boolean {
+            return this.opacity < 1.0;
+        }
+          
+        private _checkCache(scene: Scene, mesh?: AbstractMesh, useInstances?: boolean): boolean {
+            if (!mesh) {
+                return true;
+            }
+
+            if (mesh._materialDefines && mesh._materialDefines.isEqual(this._defines)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        public isReady(mesh?: AbstractMesh, useInstances?: boolean): boolean {
+            if (this.checkReadyOnlyOnce) {
+                if (this._wasPreviouslyReady) {
+                    return true;
+                }
+            }
+
+            var scene = this.getScene();
+
+            if (!this.checkReadyOnEveryCall) {
+                if (this._renderId === scene.getRenderId()) {
+                    if (this._checkCache(scene, mesh, useInstances)) {
+                        return true;
+                    }
+                }
+            }
+
+            var engine = scene.getEngine();
+            var needNormals = true;
+
+            this._defines.reset();
+
+            if (this.opacity < 1.0) {
+                this._defines.TRANSPARENT = true;
+            }
+
+            // Get correct effect      
+            if (!this._effect || !this._defines.isEqual(this._cachedDefines)) {
+                this._defines.cloneTo(this._cachedDefines);
+                scene.resetCachedMaterial();
+
+                // Attributes
+                var attribs = [VertexBuffer.PositionKind, VertexBuffer.NormalKind];
+
+                // Effect
+                var shaderName = scene.getEngine().getCaps().standardDerivatives ? "grid" : "legacygrid";
+                
+                // Defines
+                var join = this._defines.toString();
+                this._effect = scene.getEngine().createEffect(shaderName,
+                    attribs,
+                    ["worldViewProjection", "mainColor", "lineColor", "gridControl"],
+                    [],
+                    join, 
+                    null, 
+                    this.onCompiled, 
+                    this.onError);
+            }
+            
+            if (!this._effect.isReady()) {
+                return false;
+            }
+
+            this._renderId = scene.getRenderId();
+            this._wasPreviouslyReady = true;
+
+            return true;
+        }
+        
+        public bindOnlyWorldMatrix(world: Matrix): void {
+            var scene = this.getScene();
+
+            this._effect.setMatrix("worldViewProjection", world.multiply(scene.getTransformMatrix()));
+        }
+
+        public bind(world: Matrix, mesh?: Mesh): void {
+            var scene = this.getScene();
+
+            // Matrices        
+            this.bindOnlyWorldMatrix(world);
+            
+            // Uniforms
+            if (scene.getCachedMaterial() !== (<BABYLON.Material>this)) {
+                this._effect.setColor3("mainColor", this.mainColor);
+                this._effect.setColor3("lineColor", this.lineColor);
+                
+                this._gridControl.x = this.gridRatio;
+                this._gridControl.y = Math.round(this.majorUnitFrequency);
+                this._gridControl.z = this.minorUnitVisibility;
+                this._gridControl.w = this.opacity;
+                this._effect.setVector4("gridControl", this._gridControl);
+            }
+            super.bind(world, mesh);
+        }
+        
+        public dispose(forceDisposeEffect?: boolean): void {
+            super.dispose(forceDisposeEffect);
+        }
+        
+        public clone(name: string): GridMaterial {
+            return SerializationHelper.Clone(() => new GridMaterial(name, this.getScene()), this);
+        }
+
+        public serialize(): any {
+            var serializationObject = SerializationHelper.Serialize(this); 
+            serializationObject.customType = "BABYLON.GridMaterial"; 
+            return serializationObject;
+        }
+
+        public static Parse(source: any, scene: Scene, rootUrl: string): GridMaterial {
+            return SerializationHelper.Parse(() => new GridMaterial(source.name, scene), source, scene, rootUrl);
+        }
+    }
+}

+ 94 - 0
materialsLibrary/materials/grid/grid.fragment.fx

@@ -0,0 +1,94 @@
+#extension GL_OES_standard_derivatives : enable
+
+#define SQRT2 1.41421356
+#define PI 3.14159
+
+precision highp float;
+
+uniform vec3 mainColor;
+uniform vec3 lineColor;
+uniform vec4 gridControl;
+
+// Varying
+varying vec3 vPosition;
+varying vec3 vNormal;
+
+float getVisibility(float position) {
+    // Major grid line every Frequency defined in material.
+    float majorGridFrequency = gridControl.y;
+    if (floor(position + 0.5) == floor(position / majorGridFrequency + 0.5) * majorGridFrequency)
+    {
+        return 1.0;
+    }  
+    
+    return gridControl.z;
+}
+
+float getAnisotropicAttenuation(float differentialLength) {
+    const float maxNumberOfLines = 10.0;
+    return clamp(1.0 / (differentialLength + 1.0) - 1.0 / maxNumberOfLines, 0.0, 1.0);
+}
+
+float isPointOnLine(float position, float differentialLength) {
+    float fractionPartOfPosition = position - floor(position + 0.5); // fract part around unit [-0.5; 0.5]
+    fractionPartOfPosition /= differentialLength; // adapt to the screen space size it takes
+    fractionPartOfPosition = clamp(fractionPartOfPosition, -1., 1.);
+    
+    float result = 0.5 + 0.5 * cos(fractionPartOfPosition * PI); // Convert to 0-1 for antialiasing.
+    return result;    
+}
+
+float contributionOnAxis(float position) {
+    float differentialLength = length(vec2(dFdx(position), dFdy(position)));
+    differentialLength *= SQRT2;  // Multiply by SQRT2 for diagonal length
+    
+    // Is the point on the line.
+    float result = isPointOnLine(position, differentialLength);
+    
+    // Add dynamic visibility.
+    float visibility = getVisibility(position);
+    result *= visibility;
+    
+    // Anisotropic filtering.
+    float anisotropicAttenuation = getAnisotropicAttenuation(differentialLength);
+    result *= anisotropicAttenuation;
+    
+    return result;
+}
+
+float normalImpactOnAxis(float x) {
+    float normalImpact = clamp(1.0 - 2.8 * abs(x * x * x), 0.0, 1.0);
+    return normalImpact;
+}
+
+void main(void) {
+    
+    // Scale position to the requested ratio.
+    float gridRatio = gridControl.x;
+    vec3 gridPos = vPosition / gridRatio;
+    
+    // Find the contribution of each coords.
+    float x = contributionOnAxis(gridPos.x);
+    float y = contributionOnAxis(gridPos.y);
+    float z = contributionOnAxis(gridPos.z); 
+    
+    // Find the normal contribution.
+    vec3 normal = normalize(vNormal);
+    x *= normalImpactOnAxis(normal.x);
+    y *= normalImpactOnAxis(normal.y);
+    z *= normalImpactOnAxis(normal.z);
+    
+    // Create the grid value by combining axis.
+    float grid = clamp(x + y + z, 0., 1.);
+    
+    // Create the color.
+    vec3 gridColor = mix(mainColor, lineColor, grid);
+
+#ifdef TRANSPARENT
+    float opacity = clamp(grid, 0.08, gridControl.w);
+    gl_FragColor = vec4(gridColor.rgb, opacity);
+#else
+    // Apply the color.
+    gl_FragColor = vec4(gridColor.rgb, 1.0);
+#endif
+}

+ 19 - 0
materialsLibrary/materials/grid/grid.vertex.fx

@@ -0,0 +1,19 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+attribute vec3 normal;
+
+// Uniforms
+uniform mat4 worldViewProjection;
+
+// Varying
+varying vec3 vPosition;
+varying vec3 vNormal;
+
+void main(void) {
+    gl_Position = worldViewProjection * vec4(position, 1.0);
+
+    vPosition = position;
+    vNormal = normal;
+}

+ 3 - 0
materialsLibrary/materials/grid/legacygrid.fragment.fx

@@ -0,0 +1,3 @@
+void main(void) {
+    gl_FragColor = vec4(1, 1, 1, 0.1);
+}

+ 11 - 0
materialsLibrary/materials/grid/legacygrid.vertex.fx

@@ -0,0 +1,11 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+
+// Uniforms
+uniform mat4 worldViewProjection;
+
+void main(void) {
+    gl_Position = worldViewProjection * vec4(position, 1.0);
+}

+ 181 - 19
materialsLibrary/materials/pbr/babylon.pbrMaterial.ts

@@ -102,33 +102,79 @@ module BABYLON {
         }
     }
 
+    /**
+     * The Physically based material of BJS.
+     * 
+     * This offers the main features of a standard PBR material.
+     * For more information, please refer to the documentation : 
+     * http://doc.babylonjs.com/extensions/Physically_Based_Rendering
+     */
     export class PBRMaterial extends BABYLON.Material {
 
+        /**
+         * Intensity of the direct lights e.g. the four lights available in your scene.
+         * This impacts both the direct diffuse and specular highlights.
+         */
         @serialize()
         public directIntensity: number = 1.0;
         
+        /**
+         * Intensity of the emissive part of the material.
+         * This helps controlling the emissive effect without modifying the emissive color.
+         */
         @serialize()
         public emissiveIntensity: number = 1.0;
         
+        /**
+         * Intensity of the environment e.g. how much the environment will light the object
+         * either through harmonics for rough material or through the refelction for shiny ones.
+         */
         @serialize()
         public environmentIntensity: number = 1.0;
         
+        /**
+         * This is a special control allowing the reduction of the specular highlights coming from the 
+         * four lights of the scene. Those highlights may not be needed in full environment lighting.
+         */
         @serialize()
         public specularIntensity: number = 1.0;
 
         private _lightingInfos: Vector4 = new Vector4(this.directIntensity, this.emissiveIntensity, this.environmentIntensity, this.specularIntensity);
+        
+        /**
+         * Debug Control allowing disabling the bump map on this material.
+         */
+        @serialize()
+        public disableBumpMap: boolean = false;
 
+        /**
+         * Debug Control helping enforcing or dropping the darkness of shadows.
+         * 1.0 means the shadows have their normal darkness, 0.0 means the shadows are not visible.
+         */
         @serialize()
         public overloadedShadowIntensity: number = 1.0;
         
+        /**
+         * Debug Control helping dropping the shading effect coming from the diffuse lighting.
+         * 1.0 means the shade have their normal impact, 0.0 means no shading at all.
+         */
         @serialize()
         public overloadedShadeIntensity: number = 1.0;
         
         private _overloadedShadowInfos: Vector4 = new Vector4(this.overloadedShadowIntensity, this.overloadedShadeIntensity, 0.0, 0.0);
 
+        /**
+         * The camera exposure used on this material.
+         * This property is here and not in the camera to allow controlling exposure without full screen post process.
+         * This corresponds to a photographic exposure.
+         */
         @serialize()
         public cameraExposure: number = 1.0;
         
+        /**
+         * The camera contrast used on this material.
+         * This property is here and not in the camera to allow controlling contrast without full screen post process.
+         */
         @serialize()
         public cameraContrast: number = 1.0;
         
@@ -136,52 +182,97 @@ module BABYLON {
 
         private _microsurfaceTextureLods: Vector2 = new Vector2(0.0, 0.0);
 
+        /**
+         * Debug Control allowing to overload the ambient color.
+         * This as to be use with the overloadedAmbientIntensity parameter.
+         */
+        @serializeAsColor3()
+        public overloadedAmbient: Color3 = BABYLON.Color3.White();
+
+        /**
+         * Debug Control indicating how much the overloaded ambient color is used against the default one.
+         */
         @serialize()
         public overloadedAmbientIntensity: number = 0.0;
         
+        /**
+         * Debug Control allowing to overload the albedo color.
+         * This as to be use with the overloadedAlbedoIntensity parameter.
+         */
+        @serializeAsColor3()
+        public overloadedAlbedo: Color3 = BABYLON.Color3.White();
+        
+        /**
+         * Debug Control indicating how much the overloaded albedo color is used against the default one.
+         */
         @serialize()
         public overloadedAlbedoIntensity: number = 0.0;
         
+        /**
+         * Debug Control allowing to overload the reflectivity color.
+         * This as to be use with the overloadedReflectivityIntensity parameter.
+         */
+        @serializeAsColor3()
+        public overloadedReflectivity: Color3 = BABYLON.Color3.White();
+        
+        /**
+         * Debug Control indicating how much the overloaded reflectivity color is used against the default one.
+         */
         @serialize()
         public overloadedReflectivityIntensity: number = 0.0;
         
+        /**
+         * Debug Control allowing to overload the emissive color.
+         * This as to be use with the overloadedEmissiveIntensity parameter.
+         */
+        @serializeAsColor3()
+        public overloadedEmissive: Color3 = BABYLON.Color3.White();
+        
+        /**
+         * Debug Control indicating how much the overloaded emissive color is used against the default one.
+         */
         @serialize()
         public overloadedEmissiveIntensity: number = 0.0;
         
         private _overloadedIntensity: Vector4 = new Vector4(this.overloadedAmbientIntensity, this.overloadedAlbedoIntensity, this.overloadedReflectivityIntensity, this.overloadedEmissiveIntensity);
-
-        @serializeAsColor3()
-        public overloadedAmbient: Color3 = BABYLON.Color3.White();
-        
-        @serializeAsColor3()
-        public overloadedAlbedo: Color3 = BABYLON.Color3.White();
-        
-        @serializeAsColor3()
-        public overloadedReflectivity: Color3 = BABYLON.Color3.White();
-        
-        @serializeAsColor3()
-        public overloadedEmissive: Color3 = BABYLON.Color3.White();
         
+        /**
+         * Debug Control allowing to overload the reflection color.
+         * This as to be use with the overloadedReflectionIntensity parameter.
+         */
         @serializeAsColor3()
         public overloadedReflection: Color3 = BABYLON.Color3.White();
+        
+        /**
+         * Debug Control indicating how much the overloaded reflection color is used against the default one.
+         */
+        @serialize()
+        public overloadedReflectionIntensity: number = 0.0;
 
+        /**
+         * Debug Control allowing to overload the microsurface.
+         * This as to be use with the overloadedMicroSurfaceIntensity parameter.
+         */
         @serialize()
         public overloadedMicroSurface: number = 0.0;
         
+        /**
+         * Debug Control indicating how much the overloaded microsurface is used against the default one.
+         */
         @serialize()
         public overloadedMicroSurfaceIntensity: number = 0.0;
         
-        @serialize()
-        public overloadedReflectionIntensity: number = 0.0;
-        
         private _overloadedMicroSurface: Vector3 = new Vector3(this.overloadedMicroSurface, this.overloadedMicroSurfaceIntensity, this.overloadedReflectionIntensity);
 
-        @serialize()
-        public disableBumpMap: boolean = false;
-
+        /**
+         * AKA Diffuse Texture in standard nomenclature.
+         */
         @serializeAsTexture()
         public albedoTexture: BaseTexture;
         
+        /**
+         * AKA Occlusion Texture in other nomenclature.
+         */
         @serializeAsTexture()
         public ambientTexture: BaseTexture;
         
@@ -194,6 +285,9 @@ module BABYLON {
         @serializeAsTexture()
         public emissiveTexture: BaseTexture;
         
+        /**
+         * AKA Specular texture in other nomenclature.
+         */
         @serializeAsTexture()
         public reflectivityTexture: BaseTexture;
         
@@ -209,9 +303,15 @@ module BABYLON {
         @serializeAsColor3("ambient")
         public ambientColor = new Color3(0, 0, 0);
         
+        /**
+         * AKA Diffuse Color in other nomenclature.
+         */
         @serializeAsColor3("albedo")
         public albedoColor = new Color3(1, 1, 1);
         
+        /**
+         * AKA Specular Color in other nomenclature.
+         */
         @serializeAsColor3("reflectivity")
         public reflectivityColor = new Color3(1, 1, 1);
         
@@ -221,12 +321,21 @@ module BABYLON {
         @serializeAsColor3("emissivie")
         public emissiveColor = new Color3(0, 0, 0);
         
+        /**
+         * AKA Glossiness in other nomenclature.
+         */
         @serialize()
-        public microSurface = 0.5;
+        public microSurface = 0.9;
         
+        /**
+         * source material index of refraction (IOR)' / 'destination material IOR.
+         */
         @serialize()
         public indexOfRefraction = 0.66;
         
+        /**
+         * Controls if refraction needs to be inverted on Y. This could be usefull for procedural texture.
+         */
         @serialize()
         public invertRefractionY = false;
         
@@ -236,45 +345,92 @@ module BABYLON {
         @serializeAsFresnelParameters()
         public emissiveFresnelParameters: FresnelParameters;
 
+        /**
+         * This parameters will make the material used its opacity to control how much it is refracting aginst not.
+         * Materials half opaque for instance using refraction could benefit from this control.
+         */
         @serialize()
         public linkRefractionWithTransparency = false;
         
+        /**
+         * The emissive and albedo are linked to never be more than one (Energy conservation).
+         */
         @serialize()
         public linkEmissiveWithAlbedo = false;
         
         @serialize()
         public useLightmapAsShadowmap = false;
         
+        /**
+         * In this mode, the emissive informtaion will always be added to the lighting once.
+         * A light for instance can be thought as emissive.
+         */
         @serialize()
         public useEmissiveAsIllumination = false;
         
+        /**
+         * Secifies that the alpha is coming form the albedo channel alpha channel.
+         */
         @serialize()
         public useAlphaFromAlbedoTexture = false;
         
+        /**
+         * Specifies that the material will keeps the specular highlights over a transparent surface (only the most limunous ones).
+         * A car glass is a good exemple of that. When sun reflects on it you can not see what is behind.
+         */
         @serialize()
         public useSpecularOverAlpha = true;
         
+        /**
+         * Specifies if the reflectivity texture contains the glossiness information in its alpha channel.
+         */
         @serialize()
         public useMicroSurfaceFromReflectivityMapAlpha = false;
         
+        /**
+         * In case the reflectivity map does not contain the microsurface information in its alpha channel,
+         * The material will try to infer what glossiness each pixel should be.
+         */
         @serialize()
         public useAutoMicroSurfaceFromReflectivityMap = false;
         
+        /**
+         * Allows to work with scalar in linear mode. This is definitely a matter of preferences and tools used during
+         * the creation of the material.
+         */
         @serialize()
         public useScalarInLinearSpace = false;
         
+        /**
+         * BJS is using an harcoded light falloff based on a manually sets up range.
+         * In PBR, one way to represents the fallof is to use the inverse squared root algorythm.
+         * This parameter can help you switch back to the BJS mode in order to create scenes using both materials.
+         */
         @serialize()
         public usePhysicalLightFalloff = true;
         
+        /**
+         * Specifies that the material will keeps the reflection highlights over a transparent surface (only the most limunous ones).
+         * A car glass is a good exemple of that. When the street lights reflects on it you can not see what is behind.
+         */
         @serialize()
         public useRadianceOverAlpha = true;
         
+        /**
+         * Allows using the bump map in parallax mode.
+         */
         @serialize()
         public useParallax = false;
 
+        /**
+         * Allows using the bump map in parallax occlusion mode.
+         */
         @serialize()
         public useParallaxOcclusion = false;
 
+        /**
+         * Controls the scale bias of the parallax mode.
+         */
         @serialize()
         public parallaxScaleBias = 0.05;
         
@@ -292,6 +448,12 @@ module BABYLON {
 
         private _useLogarithmicDepth: boolean;
 
+        /**
+         * Instantiates a new PBRMaterial instance.
+         * 
+         * @param name The material name
+         * @param scene The scene the material will be use in.
+         */
         constructor(name: string, scene: Scene) {
             super(name, scene);
 

+ 31 - 0
materialsLibrary/materials/pbr/includes/harmonicsFunctions.fx

@@ -0,0 +1,31 @@
+#ifdef USESPHERICALFROMREFLECTIONMAP
+    uniform vec3 vSphericalX;
+    uniform vec3 vSphericalY;
+    uniform vec3 vSphericalZ;
+    uniform vec3 vSphericalXX;
+    uniform vec3 vSphericalYY;
+    uniform vec3 vSphericalZZ;
+    uniform vec3 vSphericalXY;
+    uniform vec3 vSphericalYZ;
+    uniform vec3 vSphericalZX;
+
+    vec3 EnvironmentIrradiance(vec3 normal)
+    {
+        // Note: 'normal' is assumed to be normalised (or near normalised)
+        // This isn't as critical as it is with other calculations (e.g. specular highlight), but the result will be incorrect nonetheless.
+
+        // TODO: switch to optimal implementation
+        vec3 result =
+            vSphericalX * normal.x +
+            vSphericalY * normal.y +
+            vSphericalZ * normal.z +
+            vSphericalXX * normal.x * normal.x +
+            vSphericalYY * normal.y * normal.y +
+            vSphericalZZ * normal.z * normal.z +
+            vSphericalYZ * normal.y * normal.z +
+            vSphericalZX * normal.z * normal.x +
+            vSphericalXY * normal.x * normal.y;
+
+        return result.rgb;
+    }
+#endif

+ 197 - 0
materialsLibrary/materials/pbr/includes/pbrFunctions.fx

@@ -0,0 +1,197 @@
+// Constants
+#define RECIPROCAL_PI2 0.15915494
+#define FRESNEL_MAXIMUM_ON_ROUGH 0.25
+
+// PBR CUSTOM CONSTANTS
+const float kPi = 3.1415926535897932384626433832795;
+const float kRougnhessToAlphaScale = 0.1;
+const float kRougnhessToAlphaOffset = 0.29248125;
+
+float Square(float value)
+{
+    return value * value;
+}
+
+float getLuminance(vec3 color)
+{
+    return clamp(dot(color, vec3(0.2126, 0.7152, 0.0722)), 0., 1.);
+}
+
+float convertRoughnessToAverageSlope(float roughness)
+{
+    // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
+    const float kMinimumVariance = 0.0005;
+    float alphaG = Square(roughness) + kMinimumVariance;
+    return alphaG;
+}
+
+// Based on Beckamm roughness to Blinn exponent + http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html 
+float getMipMapIndexFromAverageSlope(float maxMipLevel, float alpha)
+{
+    // do not take in account lower mips hence -1... and wait from proper preprocess.
+    // formula comes from approximation of the mathematical solution.
+    //float mip = maxMipLevel + kRougnhessToAlphaOffset + 0.5 * log2(alpha);
+    
+    // In the mean time 
+    // Always [0..1] goes from max mip to min mip in a log2 way.  
+    // Change 5 to nummip below.
+    // http://www.wolframalpha.com/input/?i=x+in+0..1+plot+(+5+%2B+0.3+%2B+0.1+*+5+*+log2(+(1+-+x)+*+(1+-+x)+%2B+0.0005))
+    float mip = kRougnhessToAlphaOffset + maxMipLevel + (maxMipLevel * kRougnhessToAlphaScale * log2(alpha));
+    
+    return clamp(mip, 0., maxMipLevel);
+}
+
+float getMipMapIndexFromAverageSlopeWithPMREM(float maxMipLevel, float alphaG)
+{
+    float specularPower = clamp(2. / alphaG - 2., 0.000001, 2048.);
+    
+    // Based on CubeMapGen for cosine power with 2048 spec default and 0.25 dropoff 
+    return clamp(- 0.5 * log2(specularPower) + 5.5, 0., maxMipLevel);
+}
+
+// From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
+float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
+{
+    float tanSquared = (1.0 - dot * dot) / (dot * dot);
+    return 2.0 / (1.0 + sqrt(1.0 + alphaG * alphaG * tanSquared));
+}
+
+float smithVisibilityG_TrowbridgeReitzGGX_Walter(float NdotL, float NdotV, float alphaG)
+{
+    return smithVisibilityG1_TrowbridgeReitzGGX(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGX(NdotV, alphaG);
+}
+
+// Trowbridge-Reitz (GGX)
+// Generalised Trowbridge-Reitz with gamma power=2.0
+float normalDistributionFunction_TrowbridgeReitzGGX(float NdotH, float alphaG)
+{
+    // Note: alphaG is average slope (gradient) of the normals in slope-space.
+    // It is also the (trigonometric) tangent of the median distribution value, i.e. 50% of normals have
+    // a tangent (gradient) closer to the macrosurface than this slope.
+    float a2 = Square(alphaG);
+    float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
+    return a2 / (kPi * d * d);
+}
+
+vec3 fresnelSchlickGGX(float VdotH, vec3 reflectance0, vec3 reflectance90)
+{
+    return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0., 1.), 5.0);
+}
+
+vec3 FresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectance90, float smoothness)
+{
+    // Schlick fresnel approximation, extended with basic smoothness term so that rough surfaces do not approach reflectance90 at grazing angle
+    float weight = mix(FRESNEL_MAXIMUM_ON_ROUGH, 1.0, smoothness);
+    return reflectance0 + weight * (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotN, 0., 1.), 5.0);
+}
+
+// Cook Torance Specular computation.
+vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 specularColor)
+{
+    float alphaG = convertRoughnessToAverageSlope(roughness);
+    float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
+    float visibility = smithVisibilityG_TrowbridgeReitzGGX_Walter(NdotL, NdotV, alphaG);
+    visibility /= (4.0 * NdotL * NdotV); // Cook Torance Denominator  integated in viibility to avoid issues when visibility function changes.
+
+    vec3 fresnel = fresnelSchlickGGX(VdotH, specularColor, vec3(1., 1., 1.));
+
+    float specTerm = max(0., visibility * distribution) * NdotL;
+    return fresnel * specTerm * kPi; // TODO: audit pi constants
+}
+
+float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
+{
+    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
+    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
+    float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
+    float diffuseFresnelNL = pow(clamp(1.0 - NdotV, 0.000001, 1.), 5.0);
+    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
+    float diffuseFresnelTerm =
+        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
+        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);
+
+
+    return diffuseFresnelTerm * NdotL;
+    // PI Test
+    // diffuseFresnelTerm /= kPi;
+}
+
+float adjustRoughnessFromLightProperties(float roughness, float lightRadius, float lightDistance)
+{
+    // At small angle this approximation works. 
+    float lightRoughness = lightRadius / lightDistance;
+    // Distribution can sum.
+    float totalRoughness = clamp(lightRoughness + roughness, 0., 1.);
+    return totalRoughness;
+}
+
+float computeDefaultMicroSurface(float microSurface, vec3 reflectivityColor)
+{
+    float kReflectivityNoAlphaWorkflow_SmoothnessMax = 0.95;
+
+    float reflectivityLuminance = getLuminance(reflectivityColor);
+    float reflectivityLuma = sqrt(reflectivityLuminance);
+    microSurface = reflectivityLuma * kReflectivityNoAlphaWorkflow_SmoothnessMax;
+
+    return microSurface;
+}
+
+vec3 toLinearSpace(vec3 color)
+{
+    return vec3(pow(color.r, 2.2), pow(color.g, 2.2), pow(color.b, 2.2));
+}
+
+vec3 toGammaSpace(vec3 color)
+{
+    return vec3(pow(color.r, 1.0 / 2.2), pow(color.g, 1.0 / 2.2), pow(color.b, 1.0 / 2.2));
+}
+
+float computeLightFalloff(vec3 lightOffset, float lightDistanceSquared, float range)
+{
+    #ifdef USEPHYSICALLIGHTFALLOFF
+        float lightDistanceFalloff = 1.0 / ((lightDistanceSquared + 0.0001));
+        return lightDistanceFalloff;
+    #else
+        float lightFalloff = max(0., 1.0 - length(lightOffset) / range);
+        return lightFalloff;
+    #endif
+}
+
+#ifdef CAMERATONEMAP
+    vec3 toneMaps(vec3 color)
+    {
+        color = max(color, 0.0);
+
+        // TONE MAPPING / EXPOSURE
+        color.rgb = color.rgb * vCameraInfos.x;
+
+        float tuning = 1.5; // TODO: sync up so e.g. 18% greys are matched to exposure appropriately
+        // PI Test
+        // tuning *=  kPi;
+        vec3 tonemapped = 1.0 - exp2(-color.rgb * tuning); // simple local photographic tonemapper
+        color.rgb = mix(color.rgb, tonemapped, 1.0);
+        return color;
+    }
+#endif
+
+#ifdef CAMERACONTRAST
+    vec4 contrasts(vec4 color)
+    {
+        color = clamp(color, 0.0, 1.0);
+
+        vec3 resultHighContrast = color.rgb * color.rgb * (3.0 - 2.0 * color.rgb);
+        float contrast = vCameraInfos.y;
+        if (contrast < 1.0)
+        {
+            // Decrease contrast: interpolate towards zero-contrast image (flat grey)
+            color.rgb = mix(vec3(0.5, 0.5, 0.5), color.rgb, contrast);
+        }
+        else
+        {
+            // Increase contrast: apply simple shoulder-toe high contrast curve
+            color.rgb = mix(color.rgb, resultHighContrast, contrast - 1.0);
+        }
+
+        return color;
+    }
+#endif

+ 130 - 0
materialsLibrary/materials/pbr/includes/pbrLightFunctions.fx

@@ -0,0 +1,130 @@
+// Light Computing
+struct lightingInfo
+{
+    vec3 diffuse;
+    #ifdef SPECULARTERM
+        vec3 specular;
+    #endif
+};
+
+lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius, out float NdotL) {
+    lightingInfo result;
+
+    vec3 lightDirection;
+    float attenuation = 1.0;
+    float lightDistance;
+    
+    // Point
+    if (lightData.w == 0.)
+    {
+        vec3 lightOffset = lightData.xyz - vPositionW;
+        float lightDistanceSquared = dot(lightOffset, lightOffset);
+        attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
+        
+        lightDistance = sqrt(lightDistanceSquared);
+        lightDirection = normalize(lightOffset);
+    }
+    // Directional
+    else
+    {
+        lightDistance = length(-lightData.xyz);
+        lightDirection = normalize(-lightData.xyz);
+    }
+    
+    // Roughness
+    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
+    
+    // diffuse
+    vec3 H = normalize(viewDirectionW + lightDirection);
+    NdotL = max(0.00000000001, dot(vNormal, lightDirection));
+    float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
+
+    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
+    result.diffuse = diffuseTerm * diffuseColor * attenuation;
+
+    #ifdef SPECULARTERM
+        // Specular
+        float NdotH = max(0.00000000001, dot(vNormal, H));
+
+        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
+        result.specular = specTerm * attenuation;
+    #endif
+
+    return result;
+}
+
+lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius, out float NdotL) {
+    lightingInfo result;
+
+    vec3 lightOffset = lightData.xyz - vPositionW;
+    vec3 lightVectorW = normalize(lightOffset);
+
+    // diffuse
+    float cosAngle = max(0.000000000000001, dot(-lightDirection.xyz, lightVectorW));
+    
+    if (cosAngle >= lightDirection.w)
+    {
+        cosAngle = max(0., pow(cosAngle, lightData.w));
+        
+        // Inverse squared falloff.
+        float lightDistanceSquared = dot(lightOffset, lightOffset);
+        float attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
+        
+        // Directional falloff.
+        attenuation *= cosAngle;
+        
+        // Roughness.
+        float lightDistance = sqrt(lightDistanceSquared);
+        roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
+        
+        // Diffuse
+        vec3 H = normalize(viewDirectionW - lightDirection.xyz);
+        NdotL = max(0.00000000001, dot(vNormal, -lightDirection.xyz));
+        float VdotH = clamp(dot(viewDirectionW, H), 0.00000000001, 1.0);
+
+        float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
+        result.diffuse = diffuseTerm * diffuseColor * attenuation;
+
+        #ifdef SPECULARTERM
+            // Specular
+            float NdotH = max(0.00000000001, dot(vNormal, H));
+
+            vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
+            result.specular = specTerm  * attenuation;
+        #endif
+
+        return result;
+    }
+
+    result.diffuse = vec3(0.);
+    #ifdef SPECULARTERM
+        result.specular = vec3(0.);
+    #endif
+
+    return result;
+}
+
+lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV, float lightRadius, out float NdotL) {
+    lightingInfo result;
+
+    // Roughness
+    // Do not touch roughness on hemispheric.
+
+    // Diffuse
+    NdotL = dot(vNormal, lightData.xyz) * 0.5 + 0.5;
+    result.diffuse = mix(groundColor, diffuseColor, NdotL);
+
+    #ifdef SPECULARTERM
+        // Specular
+        vec3 lightVectorW = normalize(lightData.xyz);
+        vec3 H = normalize(viewDirectionW + lightVectorW);
+        float NdotH = max(0.00000000001, dot(vNormal, H));
+        NdotL = max(0.00000000001, NdotL);
+        float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
+
+        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
+        result.specular = specTerm;
+    #endif
+
+    return result;
+}

+ 50 - 0
materialsLibrary/materials/pbr/includes/pbrLightFunctionsCall.fx

@@ -0,0 +1,50 @@
+#ifdef LIGHT{X}
+    #ifndef SPECULARTERM
+        vec3 vLightSpecular{X} = vec3(0.0);
+    #endif
+    #ifdef SPOTLIGHT{X}
+        info = computeSpotLighting(viewDirectionW, normalW, vLightData{X}, vLightDirection{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+    #endif
+    #ifdef HEMILIGHT{X}
+        info = computeHemisphericLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightGround{X}, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+    #endif
+    #if defined(POINTLIGHT{X}) || defined(DIRLIGHT{X})
+        info = computeLighting(viewDirectionW, normalW, vLightData{X}, vLightDiffuse{X}.rgb, vLightSpecular{X}, vLightDiffuse{X}.a, roughness, NdotV, vLightRadiuses[{X}], NdotL);
+    #endif
+    
+    #ifdef SHADOW{X}
+        #ifdef SHADOWVSM{X}
+            notShadowLevel = computeShadowWithVSM(vPositionFromLight{X}, shadowSampler{X}, shadowsInfo{X}.z, shadowsInfo{X}.x);
+        #else
+            #ifdef SHADOWPCF{X}
+                #if defined(POINTLIGHT{X})
+                    notShadowLevel = computeShadowWithPCFCube(vLightData{X}.xyz, shadowSampler{X}, shadowsInfo{X}.y, shadowsInfo{X}.z, shadowsInfo{X}.x);
+                #else
+                    notShadowLevel = computeShadowWithPCF(vPositionFromLight{X}, shadowSampler{X}, shadowsInfo{X}.y, shadowsInfo{X}.z, shadowsInfo{X}.x);
+                #endif
+            #else
+                #if defined(POINTLIGHT{X})
+                    notShadowLevel = computeShadowCube(vLightData{X}.xyz, shadowSampler{X}, shadowsInfo{X}.x, shadowsInfo{X}.z);
+                #else
+                    notShadowLevel = computeShadow(vPositionFromLight{X}, shadowSampler{X}, shadowsInfo{X}.x, shadowsInfo{X}.z);
+                #endif
+            #endif
+        #endif
+    #else
+        notShadowLevel = 1.;
+    #endif
+    
+    lightDiffuseContribution += info.diffuse * notShadowLevel;
+    
+    #ifdef OVERLOADEDSHADOWVALUES
+        if (NdotL < 0.000000000011)
+        {
+            notShadowLevel = 1.;
+        }
+        shadowedOnlyLightDiffuseContribution *= notShadowLevel;
+    #endif
+
+    #ifdef SPECULARTERM
+        lightSpecularContribution += info.specular * notShadowLevel;
+    #endif
+#endif

+ 171 - 0
materialsLibrary/materials/pbr/includes/pbrShadowFunctions.fx

@@ -0,0 +1,171 @@
+// Shadows
+#ifdef SHADOWS
+
+    float unpack(vec4 color)
+    {
+        const vec4 bit_shift = vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
+        return dot(color, bit_shift);
+    }
+
+    #if defined(POINTLIGHT0) || defined(POINTLIGHT1) || defined(POINTLIGHT2) || defined(POINTLIGHT3)
+        uniform vec2 depthValues;
+
+        float computeShadowCube(vec3 lightPosition, samplerCube shadowSampler, float darkness, float bias)
+        {
+            vec3 directionToLight = vPositionW - lightPosition;
+            float depth = length(directionToLight);
+            depth = clamp(depth, 0., 1.0);
+
+            directionToLight = normalize(directionToLight);
+            directionToLight.y = - directionToLight.y;
+
+            float shadow = unpack(textureCube(shadowSampler, directionToLight)) + bias;
+
+            if (depth > shadow)
+            {
+                #ifdef OVERLOADEDSHADOWVALUES
+                    return mix(1.0, darkness, vOverloadedShadowIntensity.x);
+                #else
+                    return darkness;
+                #endif
+            }
+            return 1.0;
+        }
+
+        float computeShadowWithPCFCube(vec3 lightPosition, samplerCube shadowSampler, float mapSize, float bias, float darkness)
+        {
+            vec3 directionToLight = vPositionW - lightPosition;
+            float depth = length(directionToLight);
+
+            depth = (depth - depthValues.x) / (depthValues.y - depthValues.x);
+            depth = clamp(depth, 0., 1.0);
+
+            directionToLight = normalize(directionToLight);
+            directionToLight.y = -directionToLight.y;
+
+            float visibility = 1.;
+
+            vec3 poissonDisk[4];
+            poissonDisk[0] = vec3(-1.0, 1.0, -1.0);
+            poissonDisk[1] = vec3(1.0, -1.0, -1.0);
+            poissonDisk[2] = vec3(-1.0, -1.0, -1.0);
+            poissonDisk[3] = vec3(1.0, -1.0, 1.0);
+
+            // Poisson Sampling
+            float biasedDepth = depth - bias;
+
+            if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[0] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[1] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[2] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[3] * mapSize)) < biasedDepth) visibility -= 0.25;
+
+            #ifdef OVERLOADEDSHADOWVALUES
+                return  min(1.0, mix(1.0, visibility + darkness, vOverloadedShadowIntensity.x));
+            #else
+                return  min(1.0, visibility + darkness);
+            #endif
+        }
+    #endif
+
+    #if defined(SPOTLIGHT0) || defined(SPOTLIGHT1) || defined(SPOTLIGHT2) || defined(SPOTLIGHT3) ||  defined(DIRLIGHT0) || defined(DIRLIGHT1) || defined(DIRLIGHT2) || defined(DIRLIGHT3)
+        float computeShadow(vec4 vPositionFromLight, sampler2D shadowSampler, float darkness, float bias)
+        {
+            vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+            depth = 0.5 * depth + vec3(0.5);
+            vec2 uv = depth.xy;
+
+            if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
+            {
+                return 1.0;
+            }
+
+            float shadow = unpack(texture2D(shadowSampler, uv)) + bias;
+
+            if (depth.z > shadow)
+            {
+                #ifdef OVERLOADEDSHADOWVALUES
+                    return mix(1.0, darkness, vOverloadedShadowIntensity.x);
+                #else
+                    return darkness;
+                #endif
+            }
+            return 1.;
+        }
+
+        float computeShadowWithPCF(vec4 vPositionFromLight, sampler2D shadowSampler, float mapSize, float bias, float darkness)
+        {
+            vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+            depth = 0.5 * depth + vec3(0.5);
+            vec2 uv = depth.xy;
+
+            if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
+            {
+                return 1.0;
+            }
+
+            float visibility = 1.;
+
+            vec2 poissonDisk[4];
+            poissonDisk[0] = vec2(-0.94201624, -0.39906216);
+            poissonDisk[1] = vec2(0.94558609, -0.76890725);
+            poissonDisk[2] = vec2(-0.094184101, -0.92938870);
+            poissonDisk[3] = vec2(0.34495938, 0.29387760);
+
+            // Poisson Sampling
+            float biasedDepth = depth.z - bias;
+
+            if (unpack(texture2D(shadowSampler, uv + poissonDisk[0] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(texture2D(shadowSampler, uv + poissonDisk[1] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(texture2D(shadowSampler, uv + poissonDisk[2] * mapSize)) < biasedDepth) visibility -= 0.25;
+            if (unpack(texture2D(shadowSampler, uv + poissonDisk[3] * mapSize)) < biasedDepth) visibility -= 0.25;
+
+            #ifdef OVERLOADEDSHADOWVALUES
+                return  min(1.0, mix(1.0, visibility + darkness, vOverloadedShadowIntensity.x));
+            #else
+                return  min(1.0, visibility + darkness);
+            #endif
+        }
+
+        // Thanks to http://devmaster.net/
+        float unpackHalf(vec2 color)
+        {
+            return color.x + (color.y / 255.0);
+        }
+
+        float linstep(float low, float high, float v) {
+            return clamp((v - low) / (high - low), 0.0, 1.0);
+        }
+
+        float ChebychevInequality(vec2 moments, float compare, float bias)
+        {
+            float p = smoothstep(compare - bias, compare, moments.x);
+            float variance = max(moments.y - moments.x * moments.x, 0.02);
+            float d = compare - moments.x;
+            float p_max = linstep(0.2, 1.0, variance / (variance + d * d));
+
+            return clamp(max(p, p_max), 0.0, 1.0);
+        }
+
+        float computeShadowWithVSM(vec4 vPositionFromLight, sampler2D shadowSampler, float bias, float darkness)
+        {
+            vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
+            depth = 0.5 * depth + vec3(0.5);
+            vec2 uv = depth.xy;
+
+            if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0 || depth.z >= 1.0)
+            {
+                return 1.0;
+            }
+
+            vec4 texel = texture2D(shadowSampler, uv);
+
+            vec2 moments = vec2(unpackHalf(texel.xy), unpackHalf(texel.zw));
+            #ifdef OVERLOADEDSHADOWVALUES
+                return min(1.0, mix(1.0, 1.0 - ChebychevInequality(moments, depth.z, bias) + darkness, vOverloadedShadowIntensity.x));
+            #else
+                return min(1.0, 1.0 - ChebychevInequality(moments, depth.z, bias) + darkness);
+            #endif
+        }
+    #endif
+
+#endif

+ 119 - 451
materialsLibrary/materials/pbr/legacypbr.fragment.fx

@@ -8,6 +8,7 @@ uniform vec3 vEyePosition;
 uniform vec3 vAmbientColor;
 uniform vec4 vAlbedoColor;
 uniform vec3 vReflectionColor;
+uniform vec4 vLightRadiuses;
 
 // CUSTOM CONTROLS
 uniform vec4 vLightingIntensity;
@@ -27,153 +28,6 @@ uniform vec3 vOverloadedMicroSurface;
 uniform vec4 vOverloadedShadowIntensity;
 #endif
 
-// PBR CUSTOM CONSTANTS
-const float kPi = 3.1415926535897932384626433832795;
-
-// PBR HELPER METHODS
-float Square(float value)
-{
-    return value * value;
-}
-
-float getLuminance(vec3 color)
-{
-    return clamp(dot(color, vec3(0.2126, 0.7152, 0.0722)), 0., 1.);
-}
-
-float convertRoughnessToAverageSlope(float roughness)
-{
-    // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
-    const float kMinimumVariance = 0.0005;
-    float alphaG = Square(roughness) + kMinimumVariance;
-    return alphaG;
-}
-
-// From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
-float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
-{
-    float tanSquared = (1.0 - dot * dot) / (dot * dot);
-    return 2.0 / (1.0 + sqrt(1.0 + alphaG * alphaG * tanSquared));
-}
-
-float smithVisibilityG_TrowbridgeReitzGGX_Walter(float NdotL, float NdotV, float alphaG)
-{
-    return smithVisibilityG1_TrowbridgeReitzGGX(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGX(NdotV, alphaG);
-}
-
-// Trowbridge-Reitz (GGX)
-// Generalised Trowbridge-Reitz with gamma power=2.0
-float normalDistributionFunction_TrowbridgeReitzGGX(float NdotH, float alphaG)
-{
-    // Note: alphaG is average slope (gradient) of the normals in slope-space.
-    // It is also the (trigonometric) tangent of the median distribution value, i.e. 50% of normals have
-    // a tangent (gradient) closer to the macrosurface than this slope.
-    float a2 = Square(alphaG);
-    float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
-    return a2 / (kPi * d * d);
-}
-
-vec3 fresnelSchlickGGX(float VdotH, vec3 reflectance0, vec3 reflectance90)
-{
-    return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0., 1.), 5.0);
-}
-
-vec3 FresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectance90, float smoothness)
-{
-    // Schlick fresnel approximation, extended with basic smoothness term so that rough surfaces do not approach reflectance90 at grazing angle
-    float weight = mix(FRESNEL_MAXIMUM_ON_ROUGH, 1.0, smoothness);
-    return reflectance0 + weight * (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotN, 0., 1.), 5.0);
-}
-
-// Cook Torance Specular computation.
-vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 specularColor)
-{
-    float alphaG = convertRoughnessToAverageSlope(roughness);
-    float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
-    float visibility = smithVisibilityG_TrowbridgeReitzGGX_Walter(NdotL, NdotV, alphaG);
-    visibility /= (4.0 * NdotL * NdotV); // Cook Torance Denominator  integated in viibility to avoid issues when visibility function changes.
-
-    vec3 fresnel = fresnelSchlickGGX(VdotH, specularColor, vec3(1., 1., 1.));
-
-    float specTerm = max(0., visibility * distribution) * NdotL;
-    return fresnel * specTerm;
-}
-
-float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
-{
-    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
-    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
-    float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
-    float diffuseFresnelNL = pow(clamp(1.0 - NdotV, 0.000001, 1.), 5.0);
-    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
-    float diffuseFresnelTerm =
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);
-
-    return diffuseFresnelTerm * NdotL;
-}
-
-float computeDefaultMicroSurface(float microSurface, vec3 reflectivityColor)
-{
-    if (microSurface == 0.)
-    {
-        float kReflectivityNoAlphaWorkflow_SmoothnessMax = 0.95;
-
-        float reflectivityLuminance = getLuminance(reflectivityColor);
-        float reflectivityLuma = sqrt(reflectivityLuminance);
-        microSurface = reflectivityLuma * kReflectivityNoAlphaWorkflow_SmoothnessMax;
-    }
-    return microSurface;
-}
-
-vec3 toLinearSpace(vec3 color)
-{
-    return vec3(pow(color.r, 2.2), pow(color.g, 2.2), pow(color.b, 2.2));
-}
-
-vec3 toGammaSpace(vec3 color)
-{
-    return vec3(pow(color.r, 1.0 / 2.2), pow(color.g, 1.0 / 2.2), pow(color.b, 1.0 / 2.2));
-}
-
-#ifdef CAMERATONEMAP
-    vec3 toneMaps(vec3 color)
-    {
-        color = max(color, 0.0);
-
-        // TONE MAPPING / EXPOSURE
-        color.rgb = color.rgb * vCameraInfos.x;
-
-        float tuning = 1.5; // TODO: sync up so e.g. 18% greys are matched to exposure appropriately
-        vec3 tonemapped = 1.0 - exp2(-color.rgb * tuning); // simple local photographic tonemapper
-        color.rgb = mix(color.rgb, tonemapped, 1.0);
-        return color;
-    }
-#endif
-
-#ifdef CAMERACONTRAST
-    vec4 contrasts(vec4 color)
-    {
-        color = clamp(color, 0.0, 1.0);
-
-        vec3 resultHighContrast = color.rgb * color.rgb * (3.0 - 2.0 * color.rgb);
-        float contrast = vCameraInfos.y;
-        if (contrast < 1.0)
-        {
-            // Decrease contrast: interpolate towards zero-contrast image (flat grey)
-            color.rgb = mix(vec3(0.5, 0.5, 0.5), color.rgb, contrast);
-        }
-        else
-        {
-            // Increase contrast: apply simple shoulder-toe high contrast curve
-            color.rgb = mix(color.rgb, resultHighContrast, contrast - 1.0);
-        }
-
-        return color;
-    }
-#endif
-// END PBR HELPER METHODS
-
 uniform vec4 vReflectivityColor;
 uniform vec3 vEmissiveColor;
 
@@ -233,116 +87,10 @@ uniform sampler2D reflectivitySampler;
 
 #include<clipPlaneFragmentDeclaration>
 
-// Light Computing
-struct lightingInfo
-{
-    vec3 diffuse;
-#ifdef SPECULARTERM
-    vec3 specular;
-#endif
-};
-
-lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV) {
-    lightingInfo result;
-
-    vec3 lightVectorW;
-    float attenuation = 1.0;
-    if (lightData.w == 0.)
-    {
-        vec3 direction = lightData.xyz - vPositionW;
-
-        attenuation = max(0., 1.0 - length(direction) / range);
-        lightVectorW = normalize(direction);
-    }
-    else
-    {
-        lightVectorW = normalize(-lightData.xyz);
-    }
-
-    // diffuse
-    vec3 H = normalize(viewDirectionW + lightVectorW);
-    float NdotL = max(0.00000000001, dot(vNormal, lightVectorW));
-    float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
-
-    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-    result.diffuse = diffuseTerm * diffuseColor * attenuation;
-
-#ifdef SPECULARTERM
-    // Specular
-    float NdotH = max(0.00000000001, dot(vNormal, H));
-
-    vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-    result.specular = specTerm * specularColor * attenuation;
-#endif
-
-    return result;
-}
-
-lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV) {
-    lightingInfo result;
-
-    vec3 direction = lightData.xyz - vPositionW;
-    vec3 lightVectorW = normalize(direction);
-    float attenuation = max(0., 1.0 - length(direction) / range);
-
-    // diffuse
-    float cosAngle = max(0.0000001, dot(-lightDirection.xyz, lightVectorW));
-    float spotAtten = 0.0;
-
-    if (cosAngle >= lightDirection.w)
-    {
-        cosAngle = max(0., pow(cosAngle, lightData.w));
-        spotAtten = clamp((cosAngle - lightDirection.w) / (1. - cosAngle), 0.0, 1.0);
-
-        // Diffuse
-        vec3 H = normalize(viewDirectionW - lightDirection.xyz);
-        float NdotL = max(0.00000000001, dot(vNormal, -lightDirection.xyz));
-        float VdotH = clamp(dot(viewDirectionW, H), 0.00000000001, 1.0);
-
-        float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-        result.diffuse = diffuseTerm * diffuseColor * attenuation * spotAtten;
-
-#ifdef SPECULARTERM
-        // Specular
-        float NdotH = max(0.00000000001, dot(vNormal, H));
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-        result.specular = specTerm * specularColor * attenuation * spotAtten;
-#endif
-
-        return result;
-    }
-
-    result.diffuse = vec3(0.);
-#ifdef SPECULARTERM
-    result.specular = vec3(0.);
-#endif
-
-    return result;
-}
-
-lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV) {
-    lightingInfo result;
-
-    vec3 lightVectorW = normalize(lightData.xyz);
-
-    // Diffuse
-    float ndl = dot(vNormal, lightData.xyz) * 0.5 + 0.5;
-    result.diffuse = mix(groundColor, diffuseColor, ndl);
-
-#ifdef SPECULARTERM
-    // Specular
-    vec3 H = normalize(viewDirectionW + lightVectorW);
-    float NdotH = max(0.00000000001, dot(vNormal, H));
-    float NdotL = max(0.00000000001, ndl);
-    float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
-
-    vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-    result.specular = specTerm * specularColor;
-#endif
-
-    return result;
-}
+// PBR
+#include<pbrFunctions>
+#include<harmonicsFunctions>
+#include<pbrLightFunctions>
 
 void main(void) {
 #include<clipPlaneFragment>
@@ -350,74 +98,81 @@ void main(void) {
     vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
 
     // Base color
-    vec4 baseColor = vec4(1., 1., 1., 1.);
-    vec3 diffuseColor = vAlbedoColor.rgb;
+    vec4 surfaceAlbedo = vec4(1., 1., 1., 1.);
+    vec3 surfaceAlbedoContribution = vAlbedoColor.rgb;
     
     // Alpha
     float alpha = vAlbedoColor.a;
 
-#ifdef ALBEDO
-    baseColor = texture2D(diffuseSampler, vAlbedoUV);
-    baseColor = vec4(toLinearSpace(baseColor.rgb), baseColor.a);
+    #ifdef ALBEDO
+        surfaceAlbedo = texture2D(albedoSampler, vAlbedoUV);
+        surfaceAlbedo = vec4(toLinearSpace(surfaceAlbedo.rgb), surfaceAlbedo.a);
 
-#ifdef ALPHATEST
-    if (baseColor.a < 0.4)
-        discard;
-#endif
+        #ifdef ALPHATEST
+            if (baseColor.a < 0.4)
+                discard;
+        #endif
 
-#ifdef ALPHAFROMALBEDO
-    alpha *= baseColor.a;
-#endif
+        #ifdef ALPHAFROMALBEDO
+            alpha *= surfaceAlbedo.a;
+        #endif
 
-    baseColor.rgb *= vAlbedoInfos.y;
-#endif
+        surfaceAlbedo.rgb *= vAlbedoInfos.y;
+    #else
+        // No Albedo texture.
+        surfaceAlbedo.rgb = surfaceAlbedoContribution;
+        surfaceAlbedoContribution = vec3(1., 1., 1.);
+    #endif
 
-#ifdef VERTEXCOLOR
-    baseColor.rgb *= vColor.rgb;
-#endif
+    #ifdef VERTEXCOLOR
+        baseColor.rgb *= vColor.rgb;
+    #endif
 
-#ifdef OVERLOADEDVALUES
-    baseColor.rgb = mix(baseColor.rgb, vOverloadedAlbedo, vOverloadedIntensity.y);
-    albedoColor.rgb = mix(albedoColor.rgb, vOverloadedAlbedo, vOverloadedIntensity.y);
-#endif
+    #ifdef OVERLOADEDVALUES
+        surfaceAlbedo.rgb = mix(surfaceAlbedo.rgb, vOverloadedAlbedo, vOverloadedIntensity.y);
+    #endif
 
     // Bump
-#ifdef NORMAL
-    vec3 normalW = normalize(vNormalW);
-#else
-    vec3 normalW = vec3(1.0, 1.0, 1.0);
-#endif
+    #ifdef NORMAL
+        vec3 normalW = normalize(vNormalW);
+    #else
+        vec3 normalW = vec3(1.0, 1.0, 1.0);
+    #endif
 
     // Ambient color
-    vec3 baseAmbientColor = vec3(1., 1., 1.);
+    vec3 ambientColor = vec3(1., 1., 1.);
 
-#ifdef AMBIENT
-    baseAmbientColor = texture2D(ambientSampler, vAmbientUV).rgb * vAmbientInfos.y;
-    #ifdef OVERLOADEDVALUES
-        baseAmbientColor.rgb = mix(baseAmbientColor.rgb, vOverloadedAmbient, vOverloadedIntensity.x);
+    #ifdef AMBIENT
+        ambientColor = texture2D(ambientSampler, vAmbientUV).rgb * vAmbientInfos.y;
+        
+        #ifdef OVERLOADEDVALUES
+            ambientColor.rgb = mix(ambientColor.rgb, vOverloadedAmbient, vOverloadedIntensity.x);
+        #endif
     #endif
-#endif
 
     // Reflectivity map
     float microSurface = vReflectivityColor.a;
-    vec3 reflectivityColor = vReflectivityColor.rgb;
-
+    vec3 surfaceReflectivityColor = vReflectivityColor.rgb;
+    
     #ifdef OVERLOADEDVALUES
-        reflectivityColor.rgb = mix(reflectivityColor.rgb, vOverloadedReflectivity, vOverloadedIntensity.z);
+        surfaceReflectivityColor.rgb = mix(surfaceReflectivityColor.rgb, vOverloadedReflectivity, vOverloadedIntensity.z);
     #endif
 
     #ifdef REFLECTIVITY
-            vec4 reflectivityMapColor = texture2D(reflectivitySampler, vReflectivityUV);
-            reflectivityColor = toLinearSpace(reflectivityMapColor.rgb);
+        vec4 surfaceReflectivityColorMap = texture2D(reflectivitySampler, vReflectivityUV);
+        surfaceReflectivityColor = surfaceReflectivityColorMap.rgb;
+        surfaceReflectivityColor = toLinearSpace(surfaceReflectivityColor);
 
         #ifdef OVERLOADEDVALUES
-                reflectivityColor.rgb = mix(reflectivityColor.rgb, vOverloadedReflectivity, vOverloadedIntensity.z);
+            surfaceReflectivityColor = mix(surfaceReflectivityColor, vOverloadedReflectivity, vOverloadedIntensity.z);
         #endif
 
         #ifdef MICROSURFACEFROMREFLECTIVITYMAP
-            microSurface = reflectivityMapColor.a;
+            microSurface = surfaceReflectivityColorMap.a;
         #else
-            microSurface = computeDefaultMicroSurface(microSurface, reflectivityColor);
+            #ifdef MICROSURFACEAUTOMATIC
+                microSurface = computeDefaultMicroSurface(microSurface, surfaceReflectivityColor);
+            #endif
         #endif
     #endif
 
@@ -425,234 +180,147 @@ void main(void) {
         microSurface = mix(microSurface, vOverloadedMicroSurface.x, vOverloadedMicroSurface.y);
     #endif
 
-    // Apply Energy Conservation taking in account the environment level only if the environment is present.
-    float reflectance = max(max(reflectivityColor.r, reflectivityColor.g), reflectivityColor.b);
-    baseColor.rgb = (1. - reflectance) * baseColor.rgb;
-
-    // Compute Specular Fresnel + Reflectance.
+    // Compute N dot V.
     float NdotV = max(0.00000000001, dot(normalW, viewDirectionW));
 
     // Adapt microSurface.
     microSurface = clamp(microSurface, 0., 1.) * 0.98;
 
-    // Call rough to not conflict with previous one.
-    float rough = clamp(1. - microSurface, 0.000001, 1.0);
-
+    // Compute roughness.
+    float roughness = clamp(1. - microSurface, 0.000001, 1.0);
+    
     // Lighting
-    vec3 diffuseBase = vec3(0., 0., 0.);
+    vec3 lightDiffuseContribution = vec3(0., 0., 0.);
 
 #ifdef OVERLOADEDSHADOWVALUES
-    vec3 shadowedOnlyDiffuseBase = vec3(1., 1., 1.);
+    vec3 shadowedOnlyLightDiffuseContribution = vec3(1., 1., 1.);
 #endif
 
 #ifdef SPECULARTERM
-    vec3 specularBase = vec3(0., 0., 0.);
-#endif
-    float shadow = 1.;
-
-#ifdef LIGHT0
-#ifndef SPECULARTERM
-    vec3 vLightSpecular0 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT0
-    lightingInfo info = computeSpotLighting(viewDirectionW, normalW, vLightData0, vLightDirection0, vLightDiffuse0.rgb, vLightSpecular0, vLightDiffuse0.a, rough, NdotV);
-#endif
-#ifdef HEMILIGHT0
-    lightingInfo info = computeHemisphericLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightSpecular0, vLightGround0, rough, NdotV);
-#endif
-#if defined(POINTLIGHT0) || defined(DIRLIGHT0)
-    lightingInfo info = computeLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightSpecular0, vLightDiffuse0.a, rough, NdotV);
-#endif
-
-    shadow = 1.;
-    diffuseBase += info.diffuse * shadow;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyDiffuseBase *= shadow;
-#endif
-
-#ifdef SPECULARTERM
-    specularBase += info.specular * shadow;
-#endif
-#endif
-
-#ifdef LIGHT1
-#ifndef SPECULARTERM
-    vec3 vLightSpecular1 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT1
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData1, vLightDirection1, vLightDiffuse1.rgb, vLightSpecular1, vLightDiffuse1.a, rough, NdotV);
-#endif
-#ifdef HEMILIGHT1
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightSpecular1, vLightGround1, rough, NdotV);
-#endif
-#if defined(POINTLIGHT1) || defined(DIRLIGHT1)
-    info = computeLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightSpecular1, vLightDiffuse1.a, rough, NdotV);
+    vec3 lightSpecularContribution= vec3(0., 0., 0.);
 #endif
+    float notShadowLevel = 1.; // 1 - shadowLevel
+    float NdotL = -1.;
+    lightingInfo info;
 
-    shadow = 1.;
-    diffuseBase += info.diffuse * shadow;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyDiffuseBase *= shadow;
-#endif
+#include<pbrLightFunctionsCall>[0]
+#include<pbrLightFunctionsCall>[1]
+#include<pbrLightFunctionsCall>[2]
+#include<pbrLightFunctionsCall>[3]
 
 #ifdef SPECULARTERM
-    specularBase += info.specular * shadow;
-#endif
+    lightSpecularContribution *= vLightingIntensity.w;
 #endif
 
-#ifdef LIGHT2
-#ifndef SPECULARTERM
-    vec3 vLightSpecular2 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT2
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData2, vLightDirection2, vLightDiffuse2.rgb, vLightSpecular2, vLightDiffuse2.a, rough, NdotV);
-#endif
-#ifdef HEMILIGHT2
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightSpecular2, vLightGround2, rough, NdotV);
-#endif
-#if defined(POINTLIGHT2) || defined(DIRLIGHT2)
-    info = computeLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightSpecular2, vLightDiffuse2.a, rough, NdotV);
-#endif
-
-    shadow = 1.;
-    diffuseBase += info.diffuse * shadow;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyDiffuseBase *= shadow;
-#endif
-
-#ifdef SPECULARTERM
-    specularBase += info.specular * shadow;
-#endif
-#endif
+#ifdef OPACITY
+    vec4 opacityMap = texture2D(opacitySampler, vOpacityUV);
 
-#ifdef LIGHT3
-#ifndef SPECULARTERM
-    vec3 vLightSpecular3 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT3
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData3, vLightDirection3, vLightDiffuse3.rgb, vLightSpecular3, vLightDiffuse3.a, rough, NdotV);
-#endif
-#ifdef HEMILIGHT3
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightSpecular3, vLightGround3, rough, NdotV);
-#endif
-#if defined(POINTLIGHT3) || defined(DIRLIGHT3)
-    info = computeLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightSpecular3, vLightDiffuse3.a, rough, NdotV);
-#endif
+    #ifdef OPACITYRGB
+        opacityMap.rgb = opacityMap.rgb * vec3(0.3, 0.59, 0.11);
+        alpha *= (opacityMap.x + opacityMap.y + opacityMap.z)* vOpacityInfos.y;
+    #else
+        alpha *= opacityMap.a * vOpacityInfos.y;
+    #endif
 
-    shadow = 1.;
-    diffuseBase += info.diffuse * shadow;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyDiffuseBase *= shadow;
 #endif
 
-#ifdef SPECULARTERM
-    specularBase += info.specular * shadow;
-#endif
+#ifdef VERTEXALPHA
+    alpha *= vColor.a;
 #endif
 
 // Reflection
-vec3 reflectionColor = vReflectionColor.rgb;
-vec3 ambientReflectionColor = vReflectionColor.rgb;
-
-reflectionColor *= vLightingIntensity.z;
-ambientReflectionColor *= vLightingIntensity.z;
-
-// Compute reflection reflectivity fresnel
-vec3 reflectivityEnvironmentR0 = reflectivityColor.rgb;
-vec3 reflectivityEnvironmentR90 = vec3(1.0, 1.0, 1.0);
-vec3 reflectivityEnvironmentReflectanceViewer = FresnelSchlickEnvironmentGGX(clamp(NdotV, 0., 1.), reflectivityEnvironmentR0, reflectivityEnvironmentR90, sqrt(microSurface));
-reflectionColor *= reflectivityEnvironmentReflectanceViewer;
+vec3 environmentRadiance = vReflectionColor.rgb;
+vec3 environmentIrradiance = vReflectionColor.rgb;
 
 #ifdef OVERLOADEDVALUES
-    ambientReflectionColor = mix(ambientReflectionColor, vOverloadedReflection, vOverloadedMicroSurface.z);
-    reflectionColor = mix(reflectionColor, vOverloadedReflection, vOverloadedMicroSurface.z);
+    environmentIrradiance = mix(environmentIrradiance, vOverloadedReflection, vOverloadedMicroSurface.z);
+    environmentRadiance = mix(environmentRadiance, vOverloadedReflection, vOverloadedMicroSurface.z);
 #endif
 
-#ifdef OPACITY
-    vec4 opacityMap = texture2D(opacitySampler, vOpacityUV);
-
-#ifdef OPACITYRGB
-    opacityMap.rgb = opacityMap.rgb * vec3(0.3, 0.59, 0.11);
-    alpha *= (opacityMap.x + opacityMap.y + opacityMap.z)* vOpacityInfos.y;
-#else
-    alpha *= opacityMap.a * vOpacityInfos.y;
-#endif
+environmentRadiance *= vLightingIntensity.z;
+environmentIrradiance *= vLightingIntensity.z;
 
-#endif
+// Compute reflection reflectivity fresnel
+vec3 specularEnvironmentR0 = surfaceReflectivityColor.rgb;
+vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0);
+vec3 specularEnvironmentReflectance = FresnelSchlickEnvironmentGGX(clamp(NdotV, 0., 1.), specularEnvironmentR0, specularEnvironmentR90, sqrt(microSurface));
 
-#ifdef VERTEXALPHA
-    alpha *= vColor.a;
-#endif
+// Apply Energy Conservation taking in account the environment level only if the environment is present.
+float reflectance = max(max(surfaceReflectivityColor.r, surfaceReflectivityColor.g), surfaceReflectivityColor.b);
+surfaceAlbedo.rgb = (1. - reflectance) * surfaceAlbedo.rgb;
+environmentRadiance *= specularEnvironmentReflectance;
 
-    // Emissive
-    vec3 emissiveColor = vEmissiveColor;
+// Emissive
+vec3 surfaceEmissiveColor = vEmissiveColor;
 #ifdef EMISSIVE
     vec3 emissiveColorTex = texture2D(emissiveSampler, vEmissiveUV).rgb;
-    emissiveColor = toLinearSpace(emissiveColorTex.rgb) * emissiveColor * vEmissiveInfos.y;
+    surfaceEmissiveColor = toLinearSpace(emissiveColorTex.rgb) * surfaceEmissiveColor * vEmissiveInfos.y;
 #endif
 
 #ifdef OVERLOADEDVALUES
-    emissiveColor = mix(emissiveColor, vOverloadedEmissive, vOverloadedIntensity.w);
+    surfaceEmissiveColor = mix(surfaceEmissiveColor, vOverloadedEmissive, vOverloadedIntensity.w);
 #endif
 
-    // Composition
+// Composition
 #ifdef EMISSIVEASILLUMINATION
-    vec3 finalDiffuse = max(diffuseBase * albedoColor + vAmbientColor, 0.0) * baseColor.rgb;
-
+    vec3 finalDiffuse = max(lightDiffuseContribution * surfaceAlbedoContribution + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
+    
     #ifdef OVERLOADEDSHADOWVALUES
-        shadowedOnlyDiffuseBase = max(shadowedOnlyDiffuseBase * albedoColor + vAmbientColor, 0.0) * baseColor.rgb;
+        shadowedOnlyLightDiffuseContribution = max(shadowedOnlyLightDiffuseContribution * surfaceAlbedoContribution + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
     #endif
 #else
     #ifdef LINKEMISSIVEWITHALBEDO
-        vec3 finalDiffuse = max((diffuseBase + emissiveColor) * albedoColor + vAmbientColor, 0.0) * baseColor.rgb;
+        vec3 finalDiffuse = max((lightDiffuseContribution + surfaceEmissiveColor) * surfaceAlbedoContribution + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
+
         #ifdef OVERLOADEDSHADOWVALUES
-                shadowedOnlyDiffuseBase = max((shadowedOnlyDiffuseBase + emissiveColor) * albedoColor + vAmbientColor, 0.0) * baseColor.rgb;
+            shadowedOnlyLightDiffuseContribution = max((shadowedOnlyLightDiffuseContribution + surfaceEmissiveColor) * surfaceAlbedoContribution + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
         #endif
     #else
-        vec3 finalDiffuse = max(diffuseBase * albedoColor + emissiveColor + vAmbientColor, 0.0) * baseColor.rgb;
+        vec3 finalDiffuse = max(lightDiffuseContribution * surfaceAlbedoContribution + surfaceEmissiveColor + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
+
         #ifdef OVERLOADEDSHADOWVALUES
-            shadowedOnlyDiffuseBase = max(shadowedOnlyDiffuseBase * albedoColor + emissiveColor + vAmbientColor, 0.0) * baseColor.rgb;
+            shadowedOnlyLightDiffuseContribution = max(shadowedOnlyLightDiffuseContribution * surfaceAlbedoContribution + surfaceEmissiveColor + vAmbientColor, 0.0) * surfaceAlbedo.rgb;
         #endif
     #endif
 #endif
 
 #ifdef OVERLOADEDSHADOWVALUES
-      finalDiffuse = mix(finalDiffuse, shadowedOnlyDiffuseBase, (1.0 - vOverloadedShadowIntensity.y));
+    finalDiffuse = mix(finalDiffuse, shadowedOnlyLightDiffuseContribution, (1.0 - vOverloadedShadowIntensity.y));
 #endif
 
-// diffuse lighting from environment 0.2 replaces Harmonic...
-// Ambient Reflection already includes the environment intensity.
-finalDiffuse += baseColor.rgb * ambientReflectionColor * 0.2;
-
 #ifdef SPECULARTERM
-    vec3 finalSpecular = specularBase * reflectivityColor * vLightingIntensity.w;
+    vec3 finalSpecular = lightSpecularContribution * surfaceReflectivityColor;
 #else
     vec3 finalSpecular = vec3(0.0);
 #endif
 
 #ifdef SPECULAROVERALPHA
-    alpha = clamp(alpha + dot(finalSpecular, vec3(0.3, 0.59, 0.11)), 0., 1.);
+    alpha = clamp(alpha + getLuminance(finalSpecular), 0., 1.);
+#endif
+
+#ifdef RADIANCEOVERALPHA
+    alpha = clamp(alpha + getLuminance(environmentRadiance), 0., 1.);
 #endif
 
 // Composition
 // Reflection already includes the environment intensity.
 #ifdef EMISSIVEASILLUMINATION
-    vec4 color = vec4(finalDiffuse * baseAmbientColor * vLightingIntensity.x + finalSpecular * vLightingIntensity.x + reflectionColor + emissiveColor * vLightingIntensity.y, alpha);
+    vec4 finalColor = vec4(finalDiffuse * ambientColor * vLightingIntensity.x + surfaceAlbedo.rgb * environmentIrradiance + finalSpecular * vLightingIntensity.x + environmentRadiance + surfaceEmissiveColor * vLightingIntensity.y, alpha);
 #else
-    vec4 color = vec4(finalDiffuse * baseAmbientColor * vLightingIntensity.x + finalSpecular * vLightingIntensity.x + reflectionColor, alpha);
+    vec4 finalColor = vec4(finalDiffuse * ambientColor * vLightingIntensity.x + surfaceAlbedo.rgb * environmentIrradiance + finalSpecular * vLightingIntensity.x + environmentRadiance, alpha);
 #endif
 
-    color = max(color, 0.0);
+    finalColor = max(finalColor, 0.0);
 
 #ifdef CAMERATONEMAP
-    color.rgb = toneMaps(color.rgb);
+    finalColor.rgb = toneMaps(finalColor.rgb);
 #endif
 
-    color.rgb = toGammaSpace(color.rgb);
+    finalColor.rgb = toGammaSpace(finalColor.rgb);
 
 #ifdef CAMERACONTRAST
-    color = contrasts(color);
+    finalColor = contrasts(finalColor);
 #endif
 
-    gl_FragColor = color;
+    gl_FragColor = finalColor;
 }

+ 0 - 4
materialsLibrary/materials/pbr/legacypbr.vertex.fx

@@ -12,7 +12,6 @@ attribute vec2 uv2;
 #ifdef VERTEXCOLOR
 attribute vec4 color;
 #endif
-
 #include<bonesDeclaration>
 
 // Uniforms
@@ -65,9 +64,6 @@ void main(void) {
 
 #include<bonesVertex>
 
-    finalWorld = finalWorld * influence;
-#endif
-
 	gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
 
 	vec4 worldPos = finalWorld * vec4(position, 1.0);

+ 15 - 780
materialsLibrary/materials/pbr/pbr.fragment.fx

@@ -12,10 +12,6 @@
 
 precision highp float;
 
-// Constants
-#define RECIPROCAL_PI2 0.15915494
-#define FRESNEL_MAXIMUM_ON_ROUGH 0.25
-
 uniform vec3 vEyePosition;
 uniform vec3 vAmbientColor;
 uniform vec3 vReflectionColor;
@@ -40,296 +36,12 @@ uniform vec4 vCameraInfos;
     uniform vec4 vOverloadedShadowIntensity;
 #endif
 
-#ifdef USESPHERICALFROMREFLECTIONMAP
-    uniform vec3 vSphericalX;
-    uniform vec3 vSphericalY;
-    uniform vec3 vSphericalZ;
-    uniform vec3 vSphericalXX;
-    uniform vec3 vSphericalYY;
-    uniform vec3 vSphericalZZ;
-    uniform vec3 vSphericalXY;
-    uniform vec3 vSphericalYZ;
-    uniform vec3 vSphericalZX;
-
-    vec3 EnvironmentIrradiance(vec3 normal)
-    {
-        // Note: 'normal' is assumed to be normalised (or near normalised)
-        // This isn't as critical as it is with other calculations (e.g. specular highlight), but the result will be incorrect nonetheless.
-
-        // TODO: switch to optimal implementation
-        vec3 result =
-            vSphericalX * normal.x +
-            vSphericalY * normal.y +
-            vSphericalZ * normal.z +
-            vSphericalXX * normal.x * normal.x +
-            vSphericalYY * normal.y * normal.y +
-            vSphericalZZ * normal.z * normal.z +
-            vSphericalYZ * normal.y * normal.z +
-            vSphericalZX * normal.z * normal.x +
-            vSphericalXY * normal.x * normal.y;
-
-        return result.rgb;
-    }
-#endif
-
 #if defined(REFLECTION) || defined(REFRACTION)
     uniform vec2 vMicrosurfaceTextureLods;
 #endif
 
-// PBR CUSTOM CONSTANTS
-const float kPi = 3.1415926535897932384626433832795;
-const float kRougnhessToAlphaScale = 0.1;
-const float kRougnhessToAlphaOffset = 0.29248125;
-
-#ifdef PoissonSamplingEnvironment
-    const int poissonSphereSamplersCount = 32;
-    vec3 poissonSphereSamplers[poissonSphereSamplersCount];
-
-    void initSamplers()
-    {
-        poissonSphereSamplers[0] = vec3( -0.552198926093, 0.801049753814, -0.0322487480415 );
-        poissonSphereSamplers[1] = vec3( 0.344874796559, -0.650989584719, 0.283038477033 ); 
-        poissonSphereSamplers[2] = vec3( -0.0710183703467, 0.163770497767, -0.95022416734 ); 
-        poissonSphereSamplers[3] = vec3( 0.422221832073, 0.576613638193, 0.519157625948 ); 
-        poissonSphereSamplers[4] = vec3( -0.561872200916, -0.665581249881, -0.131630473211 ); 
-        poissonSphereSamplers[5] = vec3( -0.409905973809, 0.0250731510778, 0.674676954809 ); 
-        poissonSphereSamplers[6] = vec3( 0.206829570551, -0.190199352704, 0.919073906156 ); 
-        poissonSphereSamplers[7] = vec3( -0.857514664463, 0.0274425010091, -0.475068738967 ); 
-        poissonSphereSamplers[8] = vec3( -0.816275009951, -0.0432916479141, 0.40394579291 ); 
-        poissonSphereSamplers[9] = vec3( 0.397976181928, -0.633227519667, -0.617794410447 ); 
-        poissonSphereSamplers[10] = vec3( -0.181484199014, 0.0155418272003, -0.34675720703 ); 
-        poissonSphereSamplers[11] = vec3( 0.591734926919, 0.489930882201, -0.51675303188 ); 
-        poissonSphereSamplers[12] = vec3( -0.264514973057, 0.834248662136, 0.464624235985 ); 
-        poissonSphereSamplers[13] = vec3( -0.125845223505, 0.812029586099, -0.46213797731 ); 
-        poissonSphereSamplers[14] = vec3( 0.0345715424639, 0.349983742938, 0.855109899027 ); 
-        poissonSphereSamplers[15] = vec3( 0.694340492749, -0.281052190209, -0.379600605543 ); 
-        poissonSphereSamplers[16] = vec3( -0.241055518078, -0.580199280578, 0.435381168431 );
-        poissonSphereSamplers[17] = vec3( 0.126313722289, 0.715113642744, 0.124385788055 ); 
-        poissonSphereSamplers[18] = vec3( 0.752862552387, 0.277075021888, 0.275059597549 );
-        poissonSphereSamplers[19] = vec3( -0.400896300918, -0.309374534321, -0.74285782627 ); 
-        poissonSphereSamplers[20] = vec3( 0.121843331941, -0.00381197918195, 0.322441835258 ); 
-        poissonSphereSamplers[21] = vec3( 0.741656771351, -0.472083016745, 0.14589173819 ); 
-        poissonSphereSamplers[22] = vec3( -0.120347565985, -0.397252703556, -0.00153836114051 ); 
-        poissonSphereSamplers[23] = vec3( -0.846258835203, -0.433763808754, 0.168732209784 ); 
-        poissonSphereSamplers[24] = vec3( 0.257765618362, -0.546470581239, -0.242234375624 ); 
-        poissonSphereSamplers[25] = vec3( -0.640343473361, 0.51920903395, 0.549310644325 ); 
-        poissonSphereSamplers[26] = vec3( -0.894309984621, 0.297394061018, 0.0884583225292 ); 
-        poissonSphereSamplers[27] = vec3( -0.126241933628, -0.535151016335, -0.440093659672 ); 
-        poissonSphereSamplers[28] = vec3( -0.158176440297, -0.393125021578, 0.890727226039 ); 
-        poissonSphereSamplers[29] = vec3( 0.896024272938, 0.203068725821, -0.11198597748 ); 
-        poissonSphereSamplers[30] = vec3( 0.568671758933, -0.314144243629, 0.509070768816 ); 
-        poissonSphereSamplers[31] = vec3( 0.289665332178, 0.104356977462, -0.348379247171 );
-    }
-
-    vec3 environmentSampler(samplerCube cubeMapSampler, vec3 centralDirection, float microsurfaceAverageSlope)
-    {
-        vec3 result = vec3(0., 0., 0.);
-        for(int i = 0; i < poissonSphereSamplersCount; i++)
-        {
-            vec3 offset = poissonSphereSamplers[i];
-            vec3 direction = centralDirection + microsurfaceAverageSlope * offset;
-            result += textureCube(cubeMapSampler, direction, 0.).rgb;
-        }
-
-        result /= 32.0;
-        return result;
-    }
-
-#endif
-
-// PBR HELPER METHODS
-float Square(float value)
-{
-    return value * value;
-}
-
-float getLuminance(vec3 color)
-{
-    return clamp(dot(color, vec3(0.2126, 0.7152, 0.0722)), 0., 1.);
-}
-
-float convertRoughnessToAverageSlope(float roughness)
-{
-    // Calculate AlphaG as square of roughness; add epsilon to avoid numerical issues
-    const float kMinimumVariance = 0.0005;
-    float alphaG = Square(roughness) + kMinimumVariance;
-    return alphaG;
-}
-
-// Based on Beckamm roughness to Blinn exponent + http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html 
-float getMipMapIndexFromAverageSlope(float maxMipLevel, float alpha)
-{
-    // do not take in account lower mips hence -1... and wait from proper preprocess.
-    // formula comes from approximation of the mathematical solution.
-    //float mip = maxMipLevel + kRougnhessToAlphaOffset + 0.5 * log2(alpha);
-    
-    // In the mean time 
-    // Always [0..1] goes from max mip to min mip in a log2 way.  
-    // Change 5 to nummip below.
-    // http://www.wolframalpha.com/input/?i=x+in+0..1+plot+(+5+%2B+0.3+%2B+0.1+*+5+*+log2(+(1+-+x)+*+(1+-+x)+%2B+0.0005))
-    float mip = kRougnhessToAlphaOffset + maxMipLevel + (maxMipLevel * kRougnhessToAlphaScale * log2(alpha));
-    
-    return clamp(mip, 0., maxMipLevel);
-}
-
-float getMipMapIndexFromAverageSlopeWithPMREM(float maxMipLevel, float alphaG)
-{
-    float specularPower = clamp(2. / alphaG - 2., 0.000001, 2048.);
-    
-    // Based on CubeMapGen for cosine power with 2048 spec default and 0.25 dropoff 
-    return clamp(- 0.5 * log2(specularPower) + 5.5, 0., maxMipLevel);
-}
-
-// From Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007
-float smithVisibilityG1_TrowbridgeReitzGGX(float dot, float alphaG)
-{
-    float tanSquared = (1.0 - dot * dot) / (dot * dot);
-    return 2.0 / (1.0 + sqrt(1.0 + alphaG * alphaG * tanSquared));
-}
-
-float smithVisibilityG_TrowbridgeReitzGGX_Walter(float NdotL, float NdotV, float alphaG)
-{
-    return smithVisibilityG1_TrowbridgeReitzGGX(NdotL, alphaG) * smithVisibilityG1_TrowbridgeReitzGGX(NdotV, alphaG);
-}
-
-// Trowbridge-Reitz (GGX)
-// Generalised Trowbridge-Reitz with gamma power=2.0
-float normalDistributionFunction_TrowbridgeReitzGGX(float NdotH, float alphaG)
-{
-    // Note: alphaG is average slope (gradient) of the normals in slope-space.
-    // It is also the (trigonometric) tangent of the median distribution value, i.e. 50% of normals have
-    // a tangent (gradient) closer to the macrosurface than this slope.
-    float a2 = Square(alphaG);
-    float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
-    return a2 / (kPi * d * d);
-}
-
-vec3 fresnelSchlickGGX(float VdotH, vec3 reflectance0, vec3 reflectance90)
-{
-    return reflectance0 + (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotH, 0., 1.), 5.0);
-}
-
-vec3 FresnelSchlickEnvironmentGGX(float VdotN, vec3 reflectance0, vec3 reflectance90, float smoothness)
-{
-    // Schlick fresnel approximation, extended with basic smoothness term so that rough surfaces do not approach reflectance90 at grazing angle
-    float weight = mix(FRESNEL_MAXIMUM_ON_ROUGH, 1.0, smoothness);
-    return reflectance0 + weight * (reflectance90 - reflectance0) * pow(clamp(1.0 - VdotN, 0., 1.), 5.0);
-}
-
-// Cook Torance Specular computation.
-vec3 computeSpecularTerm(float NdotH, float NdotL, float NdotV, float VdotH, float roughness, vec3 specularColor)
-{
-    float alphaG = convertRoughnessToAverageSlope(roughness);
-    float distribution = normalDistributionFunction_TrowbridgeReitzGGX(NdotH, alphaG);
-    float visibility = smithVisibilityG_TrowbridgeReitzGGX_Walter(NdotL, NdotV, alphaG);
-    visibility /= (4.0 * NdotL * NdotV); // Cook Torance Denominator  integated in viibility to avoid issues when visibility function changes.
-
-    vec3 fresnel = fresnelSchlickGGX(VdotH, specularColor, vec3(1., 1., 1.));
-
-    float specTerm = max(0., visibility * distribution) * NdotL;
-    return fresnel * specTerm * kPi; // TODO: audit pi constants
-}
-
-float computeDiffuseTerm(float NdotL, float NdotV, float VdotH, float roughness)
-{
-    // Diffuse fresnel falloff as per Disney principled BRDF, and in the spirit of
-    // of general coupled diffuse/specular models e.g. Ashikhmin Shirley.
-    float diffuseFresnelNV = pow(clamp(1.0 - NdotL, 0.000001, 1.), 5.0);
-    float diffuseFresnelNL = pow(clamp(1.0 - NdotV, 0.000001, 1.), 5.0);
-    float diffuseFresnel90 = 0.5 + 2.0 * VdotH * VdotH * roughness;
-    float diffuseFresnelTerm =
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNL) *
-        (1.0 + (diffuseFresnel90 - 1.0) * diffuseFresnelNV);
-
-
-    return diffuseFresnelTerm * NdotL;
-    // PI Test
-    // diffuseFresnelTerm /= kPi;
-}
-
-float adjustRoughnessFromLightProperties(float roughness, float lightRadius, float lightDistance)
-{
-    // At small angle this approximation works. 
-    float lightRoughness = lightRadius / lightDistance;
-    // Distribution can sum.
-    float totalRoughness = clamp(lightRoughness + roughness, 0., 1.);
-    return totalRoughness;
-}
-
-float computeDefaultMicroSurface(float microSurface, vec3 reflectivityColor)
-{
-    float kReflectivityNoAlphaWorkflow_SmoothnessMax = 0.95;
-
-    float reflectivityLuminance = getLuminance(reflectivityColor);
-    float reflectivityLuma = sqrt(reflectivityLuminance);
-    microSurface = reflectivityLuma * kReflectivityNoAlphaWorkflow_SmoothnessMax;
-
-    return microSurface;
-}
-
-vec3 toLinearSpace(vec3 color)
-{
-    return vec3(pow(color.r, 2.2), pow(color.g, 2.2), pow(color.b, 2.2));
-}
-
-vec3 toGammaSpace(vec3 color)
-{
-    return vec3(pow(color.r, 1.0 / 2.2), pow(color.g, 1.0 / 2.2), pow(color.b, 1.0 / 2.2));
-}
-
-float computeLightFalloff(vec3 lightOffset, float lightDistanceSquared, float range)
-{
-    #ifdef USEPHYSICALLIGHTFALLOFF
-        float lightDistanceFalloff = 1.0 / ((lightDistanceSquared + 0.0001));
-        return lightDistanceFalloff;
-    #else
-        float lightFalloff = max(0., 1.0 - length(lightOffset) / range);
-        return lightFalloff;
-    #endif
-}
-
-#ifdef CAMERATONEMAP
-    vec3 toneMaps(vec3 color)
-    {
-        color = max(color, 0.0);
-
-        // TONE MAPPING / EXPOSURE
-        color.rgb = color.rgb * vCameraInfos.x;
-
-        float tuning = 1.5; // TODO: sync up so e.g. 18% greys are matched to exposure appropriately
-        // PI Test
-        // tuning *=  kPi;
-        vec3 tonemapped = 1.0 - exp2(-color.rgb * tuning); // simple local photographic tonemapper
-        color.rgb = mix(color.rgb, tonemapped, 1.0);
-        return color;
-    }
-#endif
-
-#ifdef CAMERACONTRAST
-    vec4 contrasts(vec4 color)
-    {
-        color = clamp(color, 0.0, 1.0);
-
-        vec3 resultHighContrast = color.rgb * color.rgb * (3.0 - 2.0 * color.rgb);
-        float contrast = vCameraInfos.y;
-        if (contrast < 1.0)
-        {
-            // Decrease contrast: interpolate towards zero-contrast image (flat grey)
-            color.rgb = mix(vec3(0.5, 0.5, 0.5), color.rgb, contrast);
-        }
-        else
-        {
-            // Increase contrast: apply simple shoulder-toe high contrast curve
-            color.rgb = mix(color.rgb, resultHighContrast, contrast - 1.0);
-        }
-
-        return color;
-    }
-#endif
-// END PBR HELPER METHODS
-
-    uniform vec4 vReflectivityColor;
-    uniform vec3 vEmissiveColor;
+uniform vec4 vReflectivityColor;
+uniform vec3 vEmissiveColor;
 
 // Input
 varying vec3 vPositionW;
@@ -440,177 +152,11 @@ varying vec3 vPositionUVW;
 
 #endif
 
-// Shadows
-#ifdef SHADOWS
-
-float unpack(vec4 color)
-{
-    const vec4 bit_shift = vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
-    return dot(color, bit_shift);
-}
-
-#if defined(POINTLIGHT0) || defined(POINTLIGHT1) || defined(POINTLIGHT2) || defined(POINTLIGHT3)
-uniform vec2 depthValues;
-
-float computeShadowCube(vec3 lightPosition, samplerCube shadowSampler, float darkness, float bias)
-{
-	vec3 directionToLight = vPositionW - lightPosition;
-	float depth = length(directionToLight);
-	depth = clamp(depth, 0., 1.0);
-
-	directionToLight = normalize(directionToLight);
-	directionToLight.y = - directionToLight.y;
-
-	float shadow = unpack(textureCube(shadowSampler, directionToLight)) + bias;
-
-    if (depth > shadow)
-    {
-#ifdef OVERLOADEDSHADOWVALUES
-        return mix(1.0, darkness, vOverloadedShadowIntensity.x);
-#else
-        return darkness;
-#endif
-    }
-    return 1.0;
-}
-
-float computeShadowWithPCFCube(vec3 lightPosition, samplerCube shadowSampler, float mapSize, float bias, float darkness)
-{
-    vec3 directionToLight = vPositionW - lightPosition;
-    float depth = length(directionToLight);
-
-    depth = (depth - depthValues.x) / (depthValues.y - depthValues.x);
-    depth = clamp(depth, 0., 1.0);
-
-    directionToLight = normalize(directionToLight);
-    directionToLight.y = -directionToLight.y;
-
-    float visibility = 1.;
-
-    vec3 poissonDisk[4];
-    poissonDisk[0] = vec3(-1.0, 1.0, -1.0);
-    poissonDisk[1] = vec3(1.0, -1.0, -1.0);
-    poissonDisk[2] = vec3(-1.0, -1.0, -1.0);
-    poissonDisk[3] = vec3(1.0, -1.0, 1.0);
-
-    // Poisson Sampling
-    float biasedDepth = depth - bias;
-
-    if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[0] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[1] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[2] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(textureCube(shadowSampler, directionToLight + poissonDisk[3] * mapSize)) < biasedDepth) visibility -= 0.25;
-
-#ifdef OVERLOADEDSHADOWVALUES
-    return  min(1.0, mix(1.0, visibility + darkness, vOverloadedShadowIntensity.x));
-#else
-    return  min(1.0, visibility + darkness);
-#endif
-}
-#endif
-
-#if defined(SPOTLIGHT0) || defined(SPOTLIGHT1) || defined(SPOTLIGHT2) || defined(SPOTLIGHT3) ||  defined(DIRLIGHT0) || defined(DIRLIGHT1) || defined(DIRLIGHT2) || defined(DIRLIGHT3)
-float computeShadow(vec4 vPositionFromLight, sampler2D shadowSampler, float darkness, float bias)
-{
-    vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
-    depth = 0.5 * depth + vec3(0.5);
-    vec2 uv = depth.xy;
-
-    if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
-    {
-        return 1.0;
-    }
-
-    float shadow = unpack(texture2D(shadowSampler, uv)) + bias;
-
-    if (depth.z > shadow)
-    {
-#ifdef OVERLOADEDSHADOWVALUES
-        return mix(1.0, darkness, vOverloadedShadowIntensity.x);
-#else
-        return darkness;
-#endif
-    }
-    return 1.;
-}
-
-float computeShadowWithPCF(vec4 vPositionFromLight, sampler2D shadowSampler, float mapSize, float bias, float darkness)
-{
-    vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
-    depth = 0.5 * depth + vec3(0.5);
-    vec2 uv = depth.xy;
-
-    if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0)
-    {
-        return 1.0;
-    }
-
-    float visibility = 1.;
-
-    vec2 poissonDisk[4];
-    poissonDisk[0] = vec2(-0.94201624, -0.39906216);
-    poissonDisk[1] = vec2(0.94558609, -0.76890725);
-    poissonDisk[2] = vec2(-0.094184101, -0.92938870);
-    poissonDisk[3] = vec2(0.34495938, 0.29387760);
-
-    // Poisson Sampling
-    float biasedDepth = depth.z - bias;
-
-    if (unpack(texture2D(shadowSampler, uv + poissonDisk[0] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(texture2D(shadowSampler, uv + poissonDisk[1] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(texture2D(shadowSampler, uv + poissonDisk[2] * mapSize)) < biasedDepth) visibility -= 0.25;
-    if (unpack(texture2D(shadowSampler, uv + poissonDisk[3] * mapSize)) < biasedDepth) visibility -= 0.25;
-
-#ifdef OVERLOADEDSHADOWVALUES
-    return  min(1.0, mix(1.0, visibility + darkness, vOverloadedShadowIntensity.x));
-#else
-    return  min(1.0, visibility + darkness);
-#endif
-}
-
-// Thanks to http://devmaster.net/
-float unpackHalf(vec2 color)
-{
-    return color.x + (color.y / 255.0);
-}
-
-float linstep(float low, float high, float v) {
-    return clamp((v - low) / (high - low), 0.0, 1.0);
-}
-
-float ChebychevInequality(vec2 moments, float compare, float bias)
-{
-    float p = smoothstep(compare - bias, compare, moments.x);
-    float variance = max(moments.y - moments.x * moments.x, 0.02);
-    float d = compare - moments.x;
-    float p_max = linstep(0.2, 1.0, variance / (variance + d * d));
-
-    return clamp(max(p, p_max), 0.0, 1.0);
-}
-
-float computeShadowWithVSM(vec4 vPositionFromLight, sampler2D shadowSampler, float bias, float darkness)
-{
-    vec3 depth = vPositionFromLight.xyz / vPositionFromLight.w;
-    depth = 0.5 * depth + vec3(0.5);
-    vec2 uv = depth.xy;
-
-    if (uv.x < 0. || uv.x > 1.0 || uv.y < 0. || uv.y > 1.0 || depth.z >= 1.0)
-    {
-        return 1.0;
-    }
-
-    vec4 texel = texture2D(shadowSampler, uv);
-
-    vec2 moments = vec2(unpackHalf(texel.xy), unpackHalf(texel.zw));
-#ifdef OVERLOADEDSHADOWVALUES
-    return min(1.0, mix(1.0, 1.0 - ChebychevInequality(moments, depth.z, bias) + darkness, vOverloadedShadowIntensity.x));
-#else
-    return min(1.0, 1.0 - ChebychevInequality(moments, depth.z, bias) + darkness);
-#endif
-}
-#endif
-
-#endif
+// PBR
+#include<pbrShadowFunctions>
+#include<pbrFunctions>
+#include<harmonicsFunctions>
+#include<pbrLightFunctions>
 
 #include<bumpFragmentFunctions>
 #include<clipPlaneFragmentDeclaration>
@@ -619,144 +165,9 @@ float computeShadowWithVSM(vec4 vPositionFromLight, sampler2D shadowSampler, flo
 // Fog
 #include<fogFragmentDeclaration>
 
-// Light Computing
-struct lightingInfo
-{
-    vec3 diffuse;
-#ifdef SPECULARTERM
-    vec3 specular;
-#endif
-};
-
-lightingInfo computeLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius) {
-    lightingInfo result;
-
-    vec3 lightDirection;
-    float attenuation = 1.0;
-    float lightDistance;
-    
-    // Point
-    if (lightData.w == 0.)
-    {
-        vec3 lightOffset = lightData.xyz - vPositionW;
-        float lightDistanceSquared = dot(lightOffset, lightOffset);
-        attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
-        
-        lightDistance = sqrt(lightDistanceSquared);
-        lightDirection = normalize(lightOffset);
-    }
-    // Directional
-    else
-    {
-        lightDistance = length(-lightData.xyz);
-        lightDirection = normalize(-lightData.xyz);
-    }
-    
-    // Roughness
-    roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
-    
-    // diffuse
-    vec3 H = normalize(viewDirectionW + lightDirection);
-    float NdotL = max(0.00000000001, dot(vNormal, lightDirection));
-    float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
-
-    float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-    result.diffuse = diffuseTerm * diffuseColor * attenuation;
-
-#ifdef SPECULARTERM
-    // Specular
-    float NdotH = max(0.00000000001, dot(vNormal, H));
-
-    vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-    result.specular = specTerm * attenuation;
-#endif
-
-    return result;
-}
-
-lightingInfo computeSpotLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec4 lightDirection, vec3 diffuseColor, vec3 specularColor, float range, float roughness, float NdotV, float lightRadius) {
-    lightingInfo result;
-
-    vec3 lightOffset = lightData.xyz - vPositionW;
-    vec3 lightVectorW = normalize(lightOffset);
-
-    // diffuse
-    float cosAngle = max(0.000000000000001, dot(-lightDirection.xyz, lightVectorW));
-    
-    if (cosAngle >= lightDirection.w)
-    {
-        cosAngle = max(0., pow(cosAngle, lightData.w));
-        
-        // Inverse squared falloff.
-        float lightDistanceSquared = dot(lightOffset, lightOffset);
-        float attenuation = computeLightFalloff(lightOffset, lightDistanceSquared, range);
-        
-        // Directional falloff.
-        attenuation *= cosAngle;
-        
-        // Roughness.
-        float lightDistance = sqrt(lightDistanceSquared);
-        roughness = adjustRoughnessFromLightProperties(roughness, lightRadius, lightDistance);
-        
-        // Diffuse
-        vec3 H = normalize(viewDirectionW - lightDirection.xyz);
-        float NdotL = max(0.00000000001, dot(vNormal, -lightDirection.xyz));
-        float VdotH = clamp(dot(viewDirectionW, H), 0.00000000001, 1.0);
-
-        float diffuseTerm = computeDiffuseTerm(NdotL, NdotV, VdotH, roughness);
-        result.diffuse = diffuseTerm * diffuseColor * attenuation;
-
-#ifdef SPECULARTERM
-        // Specular
-        float NdotH = max(0.00000000001, dot(vNormal, H));
-
-        vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-        result.specular = specTerm  * attenuation;
-#endif
-
-        return result;
-    }
-
-    result.diffuse = vec3(0.);
-#ifdef SPECULARTERM
-    result.specular = vec3(0.);
-#endif
-
-    return result;
-}
-
-lightingInfo computeHemisphericLighting(vec3 viewDirectionW, vec3 vNormal, vec4 lightData, vec3 diffuseColor, vec3 specularColor, vec3 groundColor, float roughness, float NdotV, float lightRadius) {
-    lightingInfo result;
-
-    // Roughness
-    // Do not touch roughness on hemispheric.
-
-    // Diffuse
-    float ndl = dot(vNormal, lightData.xyz) * 0.5 + 0.5;
-    result.diffuse = mix(groundColor, diffuseColor, ndl);
-
-#ifdef SPECULARTERM
-    // Specular
-    vec3 lightVectorW = normalize(lightData.xyz);
-    vec3 H = normalize(viewDirectionW + lightVectorW);
-    float NdotH = max(0.00000000001, dot(vNormal, H));
-    float NdotL = max(0.00000000001, ndl);
-    float VdotH = clamp(0.00000000001, 1.0, dot(viewDirectionW, H));
-
-    vec3 specTerm = computeSpecularTerm(NdotH, NdotL, NdotV, VdotH, roughness, specularColor);
-    result.specular = specTerm;
-#endif
-
-    return result;
-}
-
 void main(void) {
 #include<clipPlaneFragment>
 
-    #ifdef PoissonSamplingEnvironment
-        initSamplers();
-    #endif
-
     vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
 
     // Albedo
@@ -816,7 +227,7 @@ void main(void) {
         #endif
     #endif
 
-    // Specular map
+    // Reflectivity map
     float microSurface = vReflectivityColor.a;
     vec3 surfaceReflectivityColor = vReflectivityColor.rgb;
     
@@ -830,7 +241,7 @@ void main(void) {
         surfaceReflectivityColor = toLinearSpace(surfaceReflectivityColor);
 
         #ifdef OVERLOADEDVALUES
-                surfaceReflectivityColor = mix(surfaceReflectivityColor, vOverloadedReflectivity, vOverloadedIntensity.z);
+            surfaceReflectivityColor = mix(surfaceReflectivityColor, vOverloadedReflectivity, vOverloadedIntensity.z);
         #endif
 
         #ifdef MICROSURFACEFROMREFLECTIVITYMAP
@@ -866,185 +277,13 @@ void main(void) {
     vec3 lightSpecularContribution= vec3(0., 0., 0.);
 #endif
     float notShadowLevel = 1.; // 1 - shadowLevel
+    float NdotL = -1.;
+    lightingInfo info;
 
-#ifdef LIGHT0
-#ifndef SPECULARTERM
-    vec3 vLightSpecular0 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT0
-    lightingInfo info = computeSpotLighting(viewDirectionW, normalW, vLightData0, vLightDirection0, vLightDiffuse0.rgb, vLightSpecular0, vLightDiffuse0.a, roughness, NdotV, vLightRadiuses[0]);
-#endif
-#ifdef HEMILIGHT0
-    lightingInfo info = computeHemisphericLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightSpecular0, vLightGround0, roughness, NdotV, vLightRadiuses[0]);
-#endif
-#if defined(POINTLIGHT0) || defined(DIRLIGHT0)
-    lightingInfo info = computeLighting(viewDirectionW, normalW, vLightData0, vLightDiffuse0.rgb, vLightSpecular0, vLightDiffuse0.a, roughness, NdotV, vLightRadiuses[0]);
-#endif
-#ifdef SHADOW0
-#ifdef SHADOWVSM0
-    notShadowLevel = computeShadowWithVSM(vPositionFromLight0, shadowSampler0, shadowsInfo0.z, shadowsInfo0.x);
-#else
-#ifdef SHADOWPCF0
-#if defined(POINTLIGHT0)
-    notShadowLevel = computeShadowWithPCFCube(vLightData0.xyz, shadowSampler0, shadowsInfo0.y, shadowsInfo0.z, shadowsInfo0.x);
-#else
-    notShadowLevel = computeShadowWithPCF(vPositionFromLight0, shadowSampler0, shadowsInfo0.y, shadowsInfo0.z, shadowsInfo0.x);
-#endif
-#else
-#if defined(POINTLIGHT0)
-    notShadowLevel = computeShadowCube(vLightData0.xyz, shadowSampler0, shadowsInfo0.x, shadowsInfo0.z);
-#else
-    notShadowLevel = computeShadow(vPositionFromLight0, shadowSampler0, shadowsInfo0.x, shadowsInfo0.z);
-#endif
-#endif
-#endif
-#else
-    notShadowLevel = 1.;
-#endif
-    lightDiffuseContribution += info.diffuse * notShadowLevel;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyLightDiffuseContribution *= notShadowLevel;
-#endif
-
-#ifdef SPECULARTERM
-    lightSpecularContribution += info.specular * notShadowLevel;
-#endif
-#endif
-
-#ifdef LIGHT1
-#ifndef SPECULARTERM
-    vec3 vLightSpecular1 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT1
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData1, vLightDirection1, vLightDiffuse1.rgb, vLightSpecular1, vLightDiffuse1.a, roughness, NdotV, vLightRadiuses[1]);
-#endif
-#ifdef HEMILIGHT1
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightSpecular1, vLightGround1, roughness, NdotV, vLightRadiuses[1]);
-#endif
-#if defined(POINTLIGHT1) || defined(DIRLIGHT1)
-    info = computeLighting(viewDirectionW, normalW, vLightData1, vLightDiffuse1.rgb, vLightSpecular1, vLightDiffuse1.a, roughness, NdotV, vLightRadiuses[1]);
-#endif
-#ifdef SHADOW1
-#ifdef SHADOWVSM1
-    notShadowLevel = computeShadowWithVSM(vPositionFromLight1, shadowSampler1, shadowsInfo1.z, shadowsInfo1.x);
-#else
-#ifdef SHADOWPCF1
-#if defined(POINTLIGHT1)
-    notShadowLevel = computeShadowWithPCFCube(vLightData1.xyz, shadowSampler1, shadowsInfo1.y, shadowsInfo1.z, shadowsInfo1.x);
-#else
-    notShadowLevel = computeShadowWithPCF(vPositionFromLight1, shadowSampler1, shadowsInfo1.y, shadowsInfo1.z, shadowsInfo1.x);
-#endif
-#else
-#if defined(POINTLIGHT1)
-    notShadowLevel = computeShadowCube(vLightData1.xyz, shadowSampler1, shadowsInfo1.x, shadowsInfo1.z);
-#else
-    notShadowLevel = computeShadow(vPositionFromLight1, shadowSampler1, shadowsInfo1.x, shadowsInfo1.z);
-#endif
-#endif
-#endif
-#else
-    notShadowLevel = 1.;
-#endif
-
-    lightDiffuseContribution += info.diffuse * notShadowLevel;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyLightDiffuseContribution *= notShadowLevel;
-#endif
-
-#ifdef SPECULARTERM
-    lightSpecularContribution += info.specular * notShadowLevel;
-#endif
-#endif
-
-#ifdef LIGHT2
-#ifndef SPECULARTERM
-    vec3 vLightSpecular2 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT2
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData2, vLightDirection2, vLightDiffuse2.rgb, vLightSpecular2, vLightDiffuse2.a, roughness, NdotV, vLightRadiuses[2]);
-#endif
-#ifdef HEMILIGHT2
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightSpecular2, vLightGround2, roughness, NdotV, vLightRadiuses[2]);
-#endif
-#if defined(POINTLIGHT2) || defined(DIRLIGHT2)
-    info = computeLighting(viewDirectionW, normalW, vLightData2, vLightDiffuse2.rgb, vLightSpecular2, vLightDiffuse2.a, roughness, NdotV, vLightRadiuses[2]);
-#endif
-#ifdef SHADOW2
-#ifdef SHADOWVSM2
-    notShadowLevel = computeShadowWithVSM(vPositionFromLight2, shadowSampler2, shadowsInfo2.z, shadowsInfo2.x);
-#else
-#ifdef SHADOWPCF2
-#if defined(POINTLIGHT2)
-    notShadowLevel = computeShadowWithPCFCube(vLightData2.xyz, shadowSampler2, shadowsInfo2.y, shadowsInfo2.z, shadowsInfo2.x);
-#else
-    notShadowLevel = computeShadowWithPCF(vPositionFromLight2, shadowSampler2, shadowsInfo2.y, shadowsInfo2.z, shadowsInfo2.x);
-#endif
-#else
-#if defined(POINTLIGHT2)
-    notShadowLevel = computeShadowCube(vLightData2.xyz, shadowSampler2, shadowsInfo2.x, shadowsInfo2.z);
-#else
-    notShadowLevel = computeShadow(vPositionFromLight2, shadowSampler2, shadowsInfo2.x, shadowsInfo2.z);
-#endif
-#endif	
-#endif	
-#else
-    notShadowLevel = 1.;
-#endif
-
-    lightDiffuseContribution += info.diffuse * notShadowLevel;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyLightDiffuseContribution *= notShadowLevel;
-#endif
-
-#ifdef SPECULARTERM
-    lightSpecularContribution += info.specular * notShadowLevel;
-#endif
-#endif
-
-#ifdef LIGHT3
-#ifndef SPECULARTERM
-    vec3 vLightSpecular3 = vec3(0.0);
-#endif
-#ifdef SPOTLIGHT3
-    info = computeSpotLighting(viewDirectionW, normalW, vLightData3, vLightDirection3, vLightDiffuse3.rgb, vLightSpecular3, vLightDiffuse3.a, roughness, NdotV, vLightRadiuses[3]);
-#endif
-#ifdef HEMILIGHT3
-    info = computeHemisphericLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightSpecular3, vLightGround3, roughness, NdotV, vLightRadiuses[3]);
-#endif
-#if defined(POINTLIGHT3) || defined(DIRLIGHT3)
-    info = computeLighting(viewDirectionW, normalW, vLightData3, vLightDiffuse3.rgb, vLightSpecular3, vLightDiffuse3.a, roughness, NdotV, vLightRadiuses[3]);
-#endif
-#ifdef SHADOW3
-#ifdef SHADOWVSM3
-    notShadowLevel = computeShadowWithVSM(vPositionFromLight3, shadowSampler3, shadowsInfo3.z, shadowsInfo3.x);
-#else
-#ifdef SHADOWPCF3
-#if defined(POINTLIGHT3)
-    notShadowLevel = computeShadowWithPCFCube(vLightData3.xyz, shadowSampler3, shadowsInfo3.y, shadowsInfo3.z, shadowsInfo3.x);
-#else
-    notShadowLevel = computeShadowWithPCF(vPositionFromLight3, shadowSampler3, shadowsInfo3.y, shadowsInfo3.z, shadowsInfo3.x);
-#endif
-#else
-#if defined(POINTLIGHT3)
-    notShadowLevel = computeShadowCube(vLightData3.xyz, shadowSampler3, shadowsInfo3.x, shadowsInfo3.z);
-#else
-    notShadowLevel = computeShadow(vPositionFromLight3, shadowSampler3, shadowsInfo3.x, shadowsInfo3.z);
-#endif
-#endif	
-#endif	
-#else
-    notShadowLevel = 1.;
-#endif
-
-    lightDiffuseContribution += info.diffuse * notShadowLevel;
-#ifdef OVERLOADEDSHADOWVALUES
-    shadowedOnlyLightDiffuseContribution *= notShadowLevel;
-#endif
-
-#ifdef SPECULARTERM
-    lightSpecularContribution += info.specular * notShadowLevel;
-#endif
-#endif
+#include<pbrLightFunctionsCall>[0]
+#include<pbrLightFunctionsCall>[1]
+#include<pbrLightFunctionsCall>[2]
+#include<pbrLightFunctionsCall>[3]
 
 #ifdef SPECULARTERM
     lightSpecularContribution *= vLightingIntensity.w;
@@ -1175,10 +414,6 @@ vec3 environmentIrradiance = vReflectionColor.rgb;
         #else
             environmentRadiance = textureCube(reflectionCubeSampler, vReflectionUVW, biasReflection).rgb * vReflectionInfos.x;
         #endif
-        
-        #ifdef PoissonSamplingEnvironment
-            environmentRadiance = environmentSampler(reflectionCubeSampler, vReflectionUVW, alphaG) * vReflectionInfos.x;
-        #endif
 
         #ifdef USESPHERICALFROMREFLECTIONMAP
             #ifndef REFLECTIONMAP_SKYBOX

+ 1 - 1
materialsLibrary/materials/pbr/pbr.vertex.fx

@@ -61,7 +61,7 @@ uniform mat4 reflectivityMatrix;
 
 #ifdef BUMP
 varying vec2 vBumpUV;
-uniform vec2 vBumpInfos;
+uniform vec3 vBumpInfos;
 uniform mat4 bumpMatrix;
 #endif
 

+ 65 - 0
materialsLibrary/test/add/addgrid.js

@@ -0,0 +1,65 @@
+window.prepareGrid = function() {
+	var grid = new BABYLON.GridMaterial("grid", scene);
+
+	registerRangeUI("grid", "LineColorR", 0, 1, function(value) {
+		grid.lineColor.r = value;
+	}, function() {
+		return grid.lineColor.r;
+	});
+    
+    registerRangeUI("grid", "LineColorG", 0, 1, function(value) {
+		grid.lineColor.g = value;
+	}, function() {
+		return grid.lineColor.g;
+	});
+    
+    registerRangeUI("grid", "LineColorB", 0, 1, function(value) {
+		grid.lineColor.b = value;
+	}, function() {
+		return grid.lineColor.b;
+	});
+    
+    registerRangeUI("grid", "MainColorR", 0, 1, function(value) {
+		grid.mainColor.r = value;
+	}, function() {
+		return grid.mainColor.r;
+	});
+    
+    registerRangeUI("grid", "MainColorG", 0, 1, function(value) {
+		grid.mainColor.g = value;
+	}, function() {
+		return grid.mainColor.g;
+	});
+    
+    registerRangeUI("grid", "MainColorB", 0, 1, function(value) {
+		grid.mainColor.b = value;
+	}, function() {
+		return grid.mainColor.b;
+	});
+    
+    registerRangeUI("grid", "GridRatio", 0, 10, function(value) {
+		grid.gridRatio = value;
+	}, function() {
+		return grid.gridRatio;
+	});
+    
+    registerRangeUI("grid", "MajorUnitFrequency", 1, 10, function(value) {
+		grid.majorUnitFrequency = value;
+	}, function() {
+		return grid.majorUnitFrequency;
+	});
+    
+    registerRangeUI("grid", "MinorUnitVisibility", 0, 1, function(value) {
+		grid.minorUnitVisibility = value;
+	}, function() {
+		return grid.minorUnitVisibility;
+	});
+    
+    registerRangeUI("grid", "Opacity", 0, 1, function(value) {
+		grid.opacity = value;
+	}, function() {
+		return grid.opacity;
+	});
+
+	return grid;
+}

+ 8 - 1
materialsLibrary/test/index.html

@@ -15,6 +15,7 @@
 	<script src="../dist/babylon.triPlanarMaterial.js"></script>
 	<script src="../dist/babylon.gradientMaterial.js"></script>
 	<script src="../dist/babylon.skyMaterial.js"></script>
+	<script src="../dist/babylon.gridMaterial.js"></script>
 
 	<style>
 		html, body {
@@ -59,6 +60,7 @@
 	<script src="add/addtriplanar.js"></script>
 	<script src="add/addgradient.js"></script>
 	<script src="add/addsky.js"></script>
+	<script src="add/addgrid.js"></script>
 	
 	<script>
 		if (BABYLON.Engine.isSupported()) {
@@ -204,13 +206,15 @@
 				var triPlanar = prepareTriPlanar();
 				
 				var sky = prepareSky();
+                
+                var grid = prepareGrid();
 				
 				// Default to std
 				var currentMaterial = std;
 				sphere.material = std;				
 				sphere.receiveShadows = true;
 
-				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'fur', 'triPlanar', 'gradient', 'sky']).onFinishChange(function () {
+				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'fur', 'triPlanar', 'gradient', 'sky', 'grid']).onFinishChange(function () {
 					water.enableRenderTargets(false);
 					skybox.material = skyboxMaterial;
 					currentMesh.isVisible = true;
@@ -254,6 +258,9 @@
 							skybox.setEnabled(true);
 							skybox.material = sky;
 							break;
+                        case "grid":
+                            currentMaterial = grid;
+                            break;
 						default:
 							currentMaterial = std;
 							break;

Разница между файлами не показана из-за своего большого размера
+ 4908 - 4546
materialsLibrary/test/refs/babylon.max.js


+ 31 - 0
src/Animations/babylon.animation.js

@@ -6,6 +6,9 @@ var BABYLON;
             this.from = from;
             this.to = to;
         }
+        AnimationRange.prototype.clone = function () {
+            return new AnimationRange(this.name, this.from, this.to);
+        };
         return AnimationRange;
     })();
     BABYLON.AnimationRange = AnimationRange;
@@ -141,6 +144,28 @@ var BABYLON;
         };
         // Methods
         /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        Animation.prototype.toString = function (fullDetails) {
+            var ret = "Name: " + this.name + ", property: " + this.targetProperty;
+            ret += ", datatype: " + (["Float", "Vector3", "Quaternion", "Matrix", "Color3", "Vector2"])[this.dataType];
+            ret += ", nKeys: " + (this._keys ? this._keys.length : "none");
+            ret += ", nRanges: " + (this._ranges ? Object.keys(this._ranges).length : "none");
+            if (fullDetails) {
+                ret += ", Ranges: {";
+                var first = true;
+                for (var name in this._ranges) {
+                    if (!first) {
+                        ret + ", ";
+                        first = false;
+                    }
+                    ret += name;
+                }
+                ret += "}";
+            }
+            return ret;
+        };
+        /**
          * Add an event to this animation.
          */
         Animation.prototype.addEvent = function (event) {
@@ -234,6 +259,12 @@ var BABYLON;
             if (this._keys) {
                 clone.setKeys(this._keys);
             }
+            if (this._ranges) {
+                clone._ranges = {};
+                for (var name in this._ranges) {
+                    clone._ranges[name] = this._ranges[name].clone();
+                }
+            }
             return clone;
         };
         Animation.prototype.setKeys = function (values) {

+ 34 - 0
src/Animations/babylon.animation.ts

@@ -2,6 +2,10 @@
     export class AnimationRange {
         constructor(public name: string, public from: number, public to: number) {
         }
+
+        public clone(): AnimationRange {
+            return new AnimationRange(this.name, this.from, this.to);
+        }
     }
 
     /**
@@ -168,6 +172,29 @@
 
         // Methods
         /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        public toString(fullDetails? : boolean) : string {
+            var ret = "Name: " + this.name + ", property: " + this.targetProperty;
+            ret += ", datatype: " + (["Float", "Vector3", "Quaternion", "Matrix", "Color3", "Vector2"])[this.dataType];
+            ret += ", nKeys: " + (this._keys ? this._keys.length : "none");
+            ret += ", nRanges: " + (this._ranges ? Object.keys(this._ranges).length : "none");
+            if (fullDetails){
+                ret += ", Ranges: {" 
+                var first = true;
+                for (var name in this._ranges) {
+                    if (!first){
+                        ret + ", ";
+                        first = false; 
+                    }
+                    ret += name; 
+                }
+                ret += "}";
+            }
+            return ret;
+        } 
+        
+        /**
          * Add an event to this animation.
          */
         public addEvent(event: AnimationEvent): void {
@@ -281,6 +308,13 @@
                 clone.setKeys(this._keys);
             }
 
+            if (this._ranges) {
+                clone._ranges = {};
+                for (var name in this._ranges) {
+                    clone._ranges[name] = this._ranges[name].clone();
+                }
+            }
+
             return clone;
         }
 

+ 1 - 1
src/Bones/babylon.bone.js

@@ -85,7 +85,7 @@ var BABYLON;
             // all animation may be coming from a library skeleton, so may need to create animation
             if (this.animations.length === 0) {
                 this.animations.push(new BABYLON.Animation(this.name, "_matrix", source.animations[0].framePerSecond, BABYLON.Animation.ANIMATIONTYPE_MATRIX, 0));
-                this.animations[0].setKeys([{}]);
+                this.animations[0].setKeys([]);
             }
             // get animation info / verify there is such a range from the source bone
             var sourceRange = source.animations[0].getRange(rangeName);

+ 1 - 1
src/Bones/babylon.bone.ts

@@ -102,7 +102,7 @@
             // all animation may be coming from a library skeleton, so may need to create animation
             if (this.animations.length === 0) {
                 this.animations.push(new Animation(this.name, "_matrix", source.animations[0].framePerSecond, Animation.ANIMATIONTYPE_MATRIX, 0));
-                this.animations[0].setKeys([{}]);
+                this.animations[0].setKeys([]);
             }
 
             // get animation info / verify there is such a range from the source bone

+ 45 - 1
src/Bones/babylon.skeleton.js

@@ -13,7 +13,6 @@ var BABYLON;
             this.bones = [];
             this._scene = scene;
             scene.skeletons.push(this);
-            this.prepare();
             //make sure it will recalculate the matrix next time prepare is called.
             this._isDirty = true;
         }
@@ -28,6 +27,26 @@ var BABYLON;
             return this._scene;
         };
         // Methods
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        Skeleton.prototype.toString = function (fullDetails) {
+            var ret = "Name: " + this.name + ", nBones: " + this.bones.length;
+            ret += ", nAnimationRanges: " + (this._ranges ? Object.keys(this._ranges).length : "none");
+            if (fullDetails) {
+                ret += ", Ranges: {";
+                var first = true;
+                for (var name in this._ranges) {
+                    if (!first) {
+                        ret + ", ";
+                        first = false;
+                    }
+                    ret += name;
+                }
+                ret += "}";
+            }
+            return ret;
+        };
         Skeleton.prototype.createAnimationRange = function (name, from, to) {
             // check name not already in use
             if (!this._ranges[name]) {
@@ -52,6 +71,19 @@ var BABYLON;
             return this._ranges[name];
         };
         /**
+         *  Returns as an Array, all AnimationRanges defined on this skeleton
+         */
+        Skeleton.prototype.getAnimationRanges = function () {
+            var animationRanges = [];
+            var name;
+            var i = 0;
+            for (name in this._ranges) {
+                animationRanges[i] = this._ranges[name];
+                i++;
+            }
+            return animationRanges;
+        };
+        /**
          *  note: This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
          */
         Skeleton.prototype.copyAnimationRange = function (source, name, rescaleAsRequired) {
@@ -67,6 +99,10 @@ var BABYLON;
             for (var i = 0, nBones = sourceBones.length; i < nBones; i++) {
                 boneDict[sourceBones[i].name] = sourceBones[i];
             }
+            if (this.bones.length !== sourceBones.length) {
+                BABYLON.Tools.Warn("copyAnimationRange: this rig has " + this.bones.length + " bones, while source as " + sourceBones.length);
+                ret = false;
+            }
             for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
                 var boneName = this.bones[i].name;
                 var sourceBone = boneDict[boneName];
@@ -181,6 +217,7 @@ var BABYLON;
         };
         Skeleton.prototype.clone = function (name, id) {
             var result = new Skeleton(name, id || name, this._scene);
+            result.needInitialSkinMatrix = this.needInitialSkinMatrix;
             for (var index = 0; index < this.bones.length; index++) {
                 var source = this.bones[index];
                 var parentBone = null;
@@ -191,6 +228,13 @@ var BABYLON;
                 var bone = new BABYLON.Bone(source.name, result, parentBone, source.getBaseMatrix().clone(), source.getRestPose().clone());
                 BABYLON.Tools.DeepCopy(source.animations, bone.animations);
             }
+            if (this._ranges) {
+                result._ranges = {};
+                for (var rangeName in this._ranges) {
+                    result._ranges[rangeName] = this._ranges[rangeName].clone();
+                }
+            }
+            this._isDirty = true;
             return result;
         };
         Skeleton.prototype.dispose = function () {

+ 51 - 1
src/Bones/babylon.skeleton.ts

@@ -20,7 +20,6 @@
 
             scene.skeletons.push(this);
 
-            this.prepare();
             //make sure it will recalculate the matrix next time prepare is called.
             this._isDirty = true;
         }
@@ -38,6 +37,27 @@
         }
 
         // Methods
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        public toString(fullDetails? : boolean) : string {
+            var ret = "Name: " + this.name + ", nBones: " + this.bones.length;
+            ret += ", nAnimationRanges: " + (this._ranges ? Object.keys(this._ranges).length : "none");
+            if (fullDetails){
+                ret += ", Ranges: {" 
+                var first = true;
+                for (var name in this._ranges) {
+                    if (!first){
+                        ret + ", ";
+                        first = false; 
+                    }
+                    ret += name; 
+                }
+                ret += "}";
+            }
+            return ret;
+        } 
+        
         public createAnimationRange(name: string, from: number, to: number): void {
             // check name not already in use
             if (!this._ranges[name]) {
@@ -62,6 +82,20 @@
         public getAnimationRange(name: string): AnimationRange {
             return this._ranges[name];
         }
+        
+        /**
+         *  Returns as an Array, all AnimationRanges defined on this skeleton
+         */
+        public getAnimationRanges(): AnimationRange[] {
+            var animationRanges :  AnimationRange[] = [];
+            var name : string;
+            var i: number = 0;
+            for (name in this._ranges){
+                animationRanges[i] = this._ranges[name];
+                i++;
+            }
+            return animationRanges;
+        }
 
         /** 
          *  note: This is not for a complete retargeting, only between very similar skeleton's with only possible bone length differences
@@ -80,6 +114,11 @@
                 boneDict[sourceBones[i].name] = sourceBones[i];
             }
 
+            if (this.bones.length !== sourceBones.length){
+                BABYLON.Tools.Warn("copyAnimationRange: this rig has " + this.bones.length + " bones, while source as " + sourceBones.length);
+                ret = false;
+            }
+            
             for (var i = 0, nBones = this.bones.length; i < nBones; i++) {
                 var boneName = this.bones[i].name;
                 var sourceBone = boneDict[boneName];
@@ -218,6 +257,8 @@
         public clone(name: string, id: string): Skeleton {
             var result = new Skeleton(name, id || name, this._scene);
 
+            result.needInitialSkinMatrix = this.needInitialSkinMatrix;
+
             for (var index = 0; index < this.bones.length; index++) {
                 var source = this.bones[index];
                 var parentBone = null;
@@ -231,6 +272,15 @@
                 Tools.DeepCopy(source.animations, bone.animations);
             }
 
+            if (this._ranges) {
+                result._ranges = {};
+                for (var rangeName in this._ranges) {
+                    result._ranges[rangeName] = this._ranges[rangeName].clone();
+                }
+            }
+
+            this._isDirty = true;
+
             return result;
         }
 

+ 69 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.js

@@ -0,0 +1,69 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var ArcRotateCameraGamepadInput = (function () {
+        function ArcRotateCameraGamepadInput() {
+            var _this = this;
+            this.gamepadRotationSensibility = 80;
+            this.gamepadMoveSensibility = 40;
+            this._gamepads = new BABYLON.Gamepads(function (gamepad) { _this._onNewGameConnected(gamepad); });
+        }
+        ArcRotateCameraGamepadInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        ArcRotateCameraGamepadInput.prototype.detach = function () {
+            this._gamepads.dispose();
+        };
+        ArcRotateCameraGamepadInput.prototype.checkInputs = function () {
+            if (this.gamepad) {
+                var camera = this.camera;
+                var RSValues = this.gamepad.rightStick;
+                if (RSValues.x != 0) {
+                    var normalizedRX = RSValues.x / this.gamepadRotationSensibility;
+                    if (normalizedRX != 0 && Math.abs(normalizedRX) > 0.005) {
+                        camera.inertialAlphaOffset += normalizedRX;
+                    }
+                }
+                if (RSValues.y != 0) {
+                    var normalizedRY = RSValues.y / this.gamepadRotationSensibility;
+                    if (normalizedRY != 0 && Math.abs(normalizedRY) > 0.005) {
+                        camera.inertialBetaOffset += normalizedRY;
+                    }
+                }
+                var LSValues = this.gamepad.leftStick;
+                if (LSValues.y != 0) {
+                    var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
+                    if (normalizedLY != 0 && Math.abs(normalizedLY) > 0.005) {
+                        this.camera.inertialRadiusOffset -= normalizedLY;
+                    }
+                }
+            }
+        };
+        ArcRotateCameraGamepadInput.prototype._onNewGameConnected = function (gamepad) {
+            // Only the first gamepad can control the camera
+            if (gamepad.index === 0) {
+                this.gamepad = gamepad;
+            }
+        };
+        ArcRotateCameraGamepadInput.prototype.getTypeName = function () {
+            return "ArcRotateCameraGamepadInput";
+        };
+        ArcRotateCameraGamepadInput.prototype.getSimpleName = function () {
+            return "gamepad";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraGamepadInput.prototype, "gamepadRotationSensibility", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraGamepadInput.prototype, "gamepadMoveSensibility", void 0);
+        return ArcRotateCameraGamepadInput;
+    })();
+    BABYLON.ArcRotateCameraGamepadInput = ArcRotateCameraGamepadInput;
+    BABYLON.CameraInputTypes["ArcRotateCameraGamepadInput"] = ArcRotateCameraGamepadInput;
+})(BABYLON || (BABYLON = {}));

+ 73 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.gamepad.ts

@@ -0,0 +1,73 @@
+module BABYLON {
+    export class ArcRotateCameraGamepadInput implements ICameraInput<ArcRotateCamera> {
+        camera: ArcRotateCamera;
+
+        public gamepad: Gamepad;
+        private _gamepads: Gamepads;
+
+        @serialize()
+        public gamepadRotationSensibility = 80;
+
+        @serialize()
+        public gamepadMoveSensibility = 40;
+
+        constructor() {
+            this._gamepads = new Gamepads((gamepad: Gamepad) => { this._onNewGameConnected(gamepad); });
+        }
+
+        attachCamera(camera: ArcRotateCamera) {
+            this.camera = camera;
+        }
+
+        detach() {
+            this._gamepads.dispose();
+        }
+
+        checkInputs() {
+            if (this.gamepad) {
+                var camera = this.camera;
+                var RSValues = this.gamepad.rightStick;
+
+                if (RSValues.x != 0) {
+                    var normalizedRX = RSValues.x / this.gamepadRotationSensibility;
+                    if (normalizedRX != 0 && Math.abs(normalizedRX) > 0.005) {
+                        camera.inertialAlphaOffset += normalizedRX;
+                    }
+                }
+
+                if (RSValues.y != 0) {
+                    var normalizedRY = RSValues.y / this.gamepadRotationSensibility;
+                    if (normalizedRY != 0 && Math.abs(normalizedRY) > 0.005) {
+                        camera.inertialBetaOffset += normalizedRY;
+                    }
+                }
+
+                var LSValues = this.gamepad.leftStick;
+                if (LSValues.y != 0) {
+                    var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
+                    if (normalizedLY != 0 && Math.abs(normalizedLY) > 0.005) {
+                        this.camera.inertialRadiusOffset -= normalizedLY;
+                    }
+                }
+
+            }
+        }
+
+        private _onNewGameConnected(gamepad: Gamepad) {
+            // Only the first gamepad can control the camera
+            if (gamepad.index === 0) {
+                this.gamepad = gamepad;
+            }
+        }
+
+        getTypeName(): string {
+            return "ArcRotateCameraGamepadInput";
+        }
+
+        getSimpleName() {
+            return "gamepad";
+        }
+    }
+
+    CameraInputTypes["ArcRotateCameraGamepadInput"] = ArcRotateCameraGamepadInput;
+}

+ 108 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.js

@@ -0,0 +1,108 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var ArcRotateCameraKeyboardMoveInput = (function () {
+        function ArcRotateCameraKeyboardMoveInput() {
+            this._keys = [];
+            this.keysUp = [38];
+            this.keysDown = [40];
+            this.keysLeft = [37];
+            this.keysRight = [39];
+        }
+        ArcRotateCameraKeyboardMoveInput.prototype.attachCamera = function (camera) {
+            var _this = this;
+            this.camera = camera;
+            this._onKeyDown = function (evt) {
+                if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysRight.indexOf(evt.keyCode) !== -1) {
+                    var index = _this._keys.indexOf(evt.keyCode);
+                    if (index === -1) {
+                        _this._keys.push(evt.keyCode);
+                    }
+                    if (evt.preventDefault) {
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                }
+            };
+            this._onKeyUp = function (evt) {
+                if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                    _this.keysRight.indexOf(evt.keyCode) !== -1) {
+                    var index = _this._keys.indexOf(evt.keyCode);
+                    if (index >= 0) {
+                        _this._keys.splice(index, 1);
+                    }
+                    if (evt.preventDefault) {
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                }
+            };
+            this._onLostFocus = function () {
+                _this._keys = [];
+            };
+            BABYLON.Tools.RegisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        };
+        ArcRotateCameraKeyboardMoveInput.prototype.detach = function () {
+            BABYLON.Tools.UnregisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        };
+        ArcRotateCameraKeyboardMoveInput.prototype.checkInputs = function () {
+            var camera = this.camera;
+            for (var index = 0; index < this._keys.length; index++) {
+                var keyCode = this._keys[index];
+                if (this.keysLeft.indexOf(keyCode) !== -1) {
+                    camera.inertialAlphaOffset -= 0.01;
+                }
+                else if (this.keysUp.indexOf(keyCode) !== -1) {
+                    camera.inertialBetaOffset -= 0.01;
+                }
+                else if (this.keysRight.indexOf(keyCode) !== -1) {
+                    camera.inertialAlphaOffset += 0.01;
+                }
+                else if (this.keysDown.indexOf(keyCode) !== -1) {
+                    camera.inertialBetaOffset += 0.01;
+                }
+            }
+        };
+        ArcRotateCameraKeyboardMoveInput.prototype.getTypeName = function () {
+            return "ArcRotateCameraKeyboardMoveInput";
+        };
+        ArcRotateCameraKeyboardMoveInput.prototype.getSimpleName = function () {
+            return "keyboard";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraKeyboardMoveInput.prototype, "keysUp", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraKeyboardMoveInput.prototype, "keysDown", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraKeyboardMoveInput.prototype, "keysLeft", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraKeyboardMoveInput.prototype, "keysRight", void 0);
+        return ArcRotateCameraKeyboardMoveInput;
+    })();
+    BABYLON.ArcRotateCameraKeyboardMoveInput = ArcRotateCameraKeyboardMoveInput;
+    BABYLON.CameraInputTypes["ArcRotateCameraKeyboardMoveInput"] = ArcRotateCameraKeyboardMoveInput;
+})(BABYLON || (BABYLON = {}));

+ 108 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.keyboard.ts

@@ -0,0 +1,108 @@
+module BABYLON {
+    export class ArcRotateCameraKeyboardMoveInput implements ICameraInput<ArcRotateCamera> {
+        camera: ArcRotateCamera;
+        private _keys = [];
+        private _onKeyDown: (e: KeyboardEvent) => any;
+        private _onKeyUp: (e: KeyboardEvent) => any;
+        private _onLostFocus: (e: FocusEvent) => any;
+        
+        @serialize()
+        public keysUp = [38];
+
+        @serialize()
+        public keysDown = [40];
+
+        @serialize()
+        public keysLeft = [37];
+
+        @serialize()
+        public keysRight = [39];
+
+        public attachCamera(camera: ArcRotateCamera) {
+            this.camera = camera;
+
+            this._onKeyDown = evt => {
+                if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                    this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                    this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                    this.keysRight.indexOf(evt.keyCode) !== -1) {
+                    var index = this._keys.indexOf(evt.keyCode);
+
+                    if (index === -1) {
+                        this._keys.push(evt.keyCode);
+                    }
+
+                    if (evt.preventDefault) {
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                }
+            };
+
+            this._onKeyUp = evt => {
+                if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                    this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                    this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                    this.keysRight.indexOf(evt.keyCode) !== -1) {
+                    var index = this._keys.indexOf(evt.keyCode);
+
+                    if (index >= 0) {
+                        this._keys.splice(index, 1);
+                    }
+
+                    if (evt.preventDefault) {
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                }
+            };
+
+            this._onLostFocus = () => {
+                this._keys = [];
+            };
+
+            Tools.RegisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        }
+
+        public detach() {
+            Tools.UnregisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        }
+
+        public checkInputs() {
+            var camera = this.camera;
+
+            for (var index = 0; index < this._keys.length; index++) {
+                var keyCode = this._keys[index];
+                if (this.keysLeft.indexOf(keyCode) !== -1) {
+                    camera.inertialAlphaOffset -= 0.01;
+                } else if (this.keysUp.indexOf(keyCode) !== -1) {
+                    camera.inertialBetaOffset -= 0.01;
+                } else if (this.keysRight.indexOf(keyCode) !== -1) {
+                    camera.inertialAlphaOffset += 0.01;
+                } else if (this.keysDown.indexOf(keyCode) !== -1) {
+                    camera.inertialBetaOffset += 0.01;
+                }
+            }
+        }
+
+        getTypeName(): string {
+            return "ArcRotateCameraKeyboardMoveInput";
+        }
+        
+        getSimpleName(){
+            return "keyboard";
+        }
+    }
+    
+    CameraInputTypes["ArcRotateCameraKeyboardMoveInput"] = ArcRotateCameraKeyboardMoveInput;
+}

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

@@ -0,0 +1,55 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var ArcRotateCameraMouseWheelInput = (function () {
+        function ArcRotateCameraMouseWheelInput() {
+            this.wheelPrecision = 3.0;
+        }
+        ArcRotateCameraMouseWheelInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        ArcRotateCameraMouseWheelInput.prototype.attachElement = function (element) {
+            var _this = this;
+            this.attachedElement = element;
+            this._wheel = function (event) {
+                var delta = 0;
+                if (event.wheelDelta) {
+                    delta = event.wheelDelta / (_this.wheelPrecision * 40);
+                }
+                else if (event.detail) {
+                    delta = -event.detail / _this.wheelPrecision;
+                }
+                if (delta)
+                    _this.camera.inertialRadiusOffset += delta;
+                if (event.preventDefault) {
+                    if (!_this.camera._noPreventDefault) {
+                        event.preventDefault();
+                    }
+                }
+            };
+            element.addEventListener('mousewheel', this._wheel, false);
+            element.addEventListener('DOMMouseScroll', this._wheel, false);
+        };
+        ArcRotateCameraMouseWheelInput.prototype.detach = function () {
+            this.attachedElement.removeEventListener('mousewheel', this._wheel);
+            this.attachedElement.removeEventListener('DOMMouseScroll', this._wheel);
+        };
+        ArcRotateCameraMouseWheelInput.prototype.getTypeName = function () {
+            return "ArcRotateCameraMouseWheelInput";
+        };
+        ArcRotateCameraMouseWheelInput.prototype.getSimpleName = function () {
+            return "mousewheel";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraMouseWheelInput.prototype, "wheelPrecision", void 0);
+        return ArcRotateCameraMouseWheelInput;
+    })();
+    BABYLON.ArcRotateCameraMouseWheelInput = ArcRotateCameraMouseWheelInput;
+    BABYLON.CameraInputTypes["ArcRotateCameraMouseWheelInput"] = ArcRotateCameraMouseWheelInput;
+})(BABYLON || (BABYLON = {}));

+ 53 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.mousewheel.ts

@@ -0,0 +1,53 @@
+module BABYLON {
+    export class ArcRotateCameraMouseWheelInput implements ICameraInput<ArcRotateCamera> {
+        camera: ArcRotateCamera;
+        attachedElement: HTMLElement;
+
+        private _wheel: (e: MouseWheelEvent) => void;
+
+        @serialize()
+        public wheelPrecision = 3.0;
+
+        public attachCamera(camera: ArcRotateCamera) {
+            this.camera = camera;
+        }
+
+        public attachElement(element: HTMLElement) {
+            this.attachedElement = element;
+            this._wheel = event => {
+                var delta = 0;
+                if (event.wheelDelta) {
+                    delta = event.wheelDelta / (this.wheelPrecision * 40);
+                } else if (event.detail) {
+                    delta = -event.detail / this.wheelPrecision;
+                }
+
+                if (delta)
+                    this.camera.inertialRadiusOffset += delta;
+
+                if (event.preventDefault) {
+                    if (!this.camera._noPreventDefault) {
+                        event.preventDefault();
+                    }
+                }
+            };
+            element.addEventListener('mousewheel', this._wheel, false);
+            element.addEventListener('DOMMouseScroll', this._wheel, false);
+        }
+
+        public detach() {
+            this.attachedElement.removeEventListener('mousewheel', this._wheel);
+            this.attachedElement.removeEventListener('DOMMouseScroll', this._wheel);
+        }
+
+        getTypeName(): string {
+            return "ArcRotateCameraMouseWheelInput";
+        }
+        
+        getSimpleName(){
+            return "mousewheel";
+        }
+    }
+    
+    CameraInputTypes["ArcRotateCameraMouseWheelInput"] = ArcRotateCameraMouseWheelInput;
+}

+ 193 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.js

@@ -0,0 +1,193 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var eventPrefix = BABYLON.Tools.GetPointerPrefix();
+    var ArcRotateCameraPointersInput = (function () {
+        function ArcRotateCameraPointersInput() {
+            this._isRightClick = false;
+            this._isCtrlPushed = false;
+            this.pinchInwards = true;
+            this.angularSensibilityX = 1000.0;
+            this.angularSensibilityY = 1000.0;
+            this.pinchPrecision = 6.0;
+            this.panningSensibility = 50.0;
+        }
+        ArcRotateCameraPointersInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        ArcRotateCameraPointersInput.prototype.attachElement = function (element, noPreventDefault) {
+            var _this = this;
+            this.attachedElement = element;
+            var engine = this.camera.getEngine();
+            var cacheSoloPointer; // cache pointer object for better perf on camera rotation
+            var pointers = new BABYLON.SmartCollection();
+            var previousPinchDistance = 0;
+            if (this._onPointerDown === undefined) {
+                if (!this.camera._useCtrlForPanning) {
+                    element.addEventListener("contextmenu", this._onContextMenu, false);
+                }
+                this._onLostFocus = function () {
+                    //this._keys = [];
+                    pointers.empty();
+                    previousPinchDistance = 0;
+                    cacheSoloPointer = null;
+                };
+                this._onKeyDown = function (evt) {
+                    _this._isCtrlPushed = evt.ctrlKey;
+                };
+                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._onContextMenu = function (evt) {
+                    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;
+                    }
+                    var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
+                    var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+                    _this.camera.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
+                    _this.camera.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+                this._onGestureStart = function (e) {
+                    if (window.MSGesture === undefined) {
+                        return;
+                    }
+                    if (!_this._MSGestureHandler) {
+                        _this._MSGestureHandler = new MSGesture();
+                        _this._MSGestureHandler.target = element;
+                    }
+                    _this._MSGestureHandler.addPointer(e.pointerId);
+                };
+                this._onGesture = function (e) {
+                    _this.camera.radius *= e.scale;
+                    if (e.preventDefault) {
+                        if (!noPreventDefault) {
+                            e.stopPropagation();
+                            e.preventDefault();
+                        }
+                    }
+                };
+            }
+            this.attachedElement.addEventListener(eventPrefix + "down", this._onPointerDown, false);
+            this.attachedElement.addEventListener(eventPrefix + "up", this._onPointerUp, false);
+            this.attachedElement.addEventListener(eventPrefix + "out", this._onPointerUp, false);
+            this.attachedElement.addEventListener(eventPrefix + "move", this._onPointerMove, false);
+            this.attachedElement.addEventListener("mousemove", this._onMouseMove, false);
+            this.attachedElement.addEventListener("MSPointerDown", this._onGestureStart, false);
+            this.attachedElement.addEventListener("MSGestureChange", this._onGesture, false);
+            BABYLON.Tools.RegisterTopRootEvents([
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        };
+        ArcRotateCameraPointersInput.prototype.detach = function () {
+            this._MSGestureHandler = null;
+            this.attachedElement.removeEventListener("contextmenu", this._onContextMenu);
+            this.attachedElement.removeEventListener(eventPrefix + "down", this._onPointerDown);
+            this.attachedElement.removeEventListener(eventPrefix + "up", this._onPointerUp);
+            this.attachedElement.removeEventListener(eventPrefix + "out", this._onPointerUp);
+            this.attachedElement.removeEventListener(eventPrefix + "move", this._onPointerMove);
+            this.attachedElement.removeEventListener("mousemove", this._onMouseMove);
+            this.attachedElement.removeEventListener("MSPointerDown", this._onGestureStart);
+            this.attachedElement.removeEventListener("MSGestureChange", this._onGesture);
+            BABYLON.Tools.UnregisterTopRootEvents([
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        };
+        ArcRotateCameraPointersInput.prototype.getTypeName = function () {
+            return "ArcRotateCameraPointersInput";
+        };
+        ArcRotateCameraPointersInput.prototype.getSimpleName = function () {
+            return "pointers";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraPointersInput.prototype, "angularSensibilityX", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraPointersInput.prototype, "angularSensibilityY", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraPointersInput.prototype, "pinchPrecision", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], ArcRotateCameraPointersInput.prototype, "panningSensibility", void 0);
+        return ArcRotateCameraPointersInput;
+    })();
+    BABYLON.ArcRotateCameraPointersInput = ArcRotateCameraPointersInput;
+    BABYLON.CameraInputTypes["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;
+})(BABYLON || (BABYLON = {}));

+ 229 - 0
src/Cameras/Inputs/babylon.arcrotatecamera.input.pointers.ts

@@ -0,0 +1,229 @@
+module BABYLON {
+    var eventPrefix = Tools.GetPointerPrefix();
+
+    export class ArcRotateCameraPointersInput implements ICameraInput<ArcRotateCamera> {
+        camera: ArcRotateCamera;
+        attachedElement: HTMLElement;
+        private _isRightClick: boolean = false;
+        private _isCtrlPushed: boolean = false;
+        public pinchInwards = true;
+        
+        @serialize()
+        public angularSensibilityX = 1000.0;
+
+        @serialize()
+        public angularSensibilityY = 1000.0;
+
+        @serialize()
+        public pinchPrecision = 6.0;
+
+        @serialize()
+        public panningSensibility: number = 50.0;       
+
+        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;
+        private _MSGestureHandler: MSGesture;
+        private _onLostFocus: (e: FocusEvent) => any;
+        private _onContextMenu: (e: PointerEvent) => void;
+        
+        public attachCamera(camera: ArcRotateCamera) {
+            this.camera = camera;
+
+        }
+
+        public attachElement(element: HTMLElement, noPreventDefault?: boolean) {
+            this.attachedElement = element;
+
+            var engine = this.camera.getEngine();
+            var cacheSoloPointer; // cache pointer object for better perf on camera rotation
+            var pointers = new SmartCollection();
+            var previousPinchDistance = 0;
+
+            if (this._onPointerDown === undefined) {
+                if (!this.camera._useCtrlForPanning) {
+                    element.addEventListener("contextmenu", this._onContextMenu, false);
+                }
+                
+                this._onLostFocus = () => {
+                    //this._keys = [];
+                    pointers.empty();
+                    previousPinchDistance = 0;
+                    cacheSoloPointer = null;
+                };
+
+                this._onKeyDown = evt => {
+                    this._isCtrlPushed = evt.ctrlKey;
+                };
+
+                this._onKeyUp = evt => {
+                    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._onContextMenu = evt => {
+                    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;
+                    }
+
+                    var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
+                    var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
+
+                    this.camera.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
+                    this.camera.inertialBetaOffset -= offsetY / this.angularSensibilityY;
+
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+
+                this._onGestureStart = e => {
+                    if (window.MSGesture === undefined) {
+                        return;
+                    }
+
+                    if (!this._MSGestureHandler) {
+                        this._MSGestureHandler = new MSGesture();
+                        this._MSGestureHandler.target = element;
+                    }
+
+                    this._MSGestureHandler.addPointer(e.pointerId);
+                };
+
+                this._onGesture = e => {
+                    this.camera.radius *= e.scale;
+
+
+                    if (e.preventDefault) {
+                        if (!noPreventDefault) {
+                            e.stopPropagation();
+                            e.preventDefault();
+                        }
+                    }
+                };
+            }
+
+            this.attachedElement.addEventListener(eventPrefix + "down", this._onPointerDown, false);
+            this.attachedElement.addEventListener(eventPrefix + "up", this._onPointerUp, false);
+            this.attachedElement.addEventListener(eventPrefix + "out", this._onPointerUp, false);
+            this.attachedElement.addEventListener(eventPrefix + "move", this._onPointerMove, false);
+            this.attachedElement.addEventListener("mousemove", this._onMouseMove, false);
+            this.attachedElement.addEventListener("MSPointerDown", this._onGestureStart, false);
+            this.attachedElement.addEventListener("MSGestureChange", this._onGesture, false);
+
+            Tools.RegisterTopRootEvents([
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        }
+
+        public detach() {
+            this._MSGestureHandler = null;
+
+            this.attachedElement.removeEventListener("contextmenu", this._onContextMenu);
+            this.attachedElement.removeEventListener(eventPrefix + "down", this._onPointerDown);
+            this.attachedElement.removeEventListener(eventPrefix + "up", this._onPointerUp);
+            this.attachedElement.removeEventListener(eventPrefix + "out", this._onPointerUp);
+            this.attachedElement.removeEventListener(eventPrefix + "move", this._onPointerMove);
+            this.attachedElement.removeEventListener("mousemove", this._onMouseMove);
+            this.attachedElement.removeEventListener("MSPointerDown", this._onGestureStart);
+            this.attachedElement.removeEventListener("MSGestureChange", this._onGesture);
+
+            Tools.UnregisterTopRootEvents([
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        }        
+
+        getTypeName(): string {
+            return "ArcRotateCameraPointersInput";
+        }
+                
+        getSimpleName(){
+            return "pointers";
+        }
+    }
+    
+    CameraInputTypes["ArcRotateCameraPointersInput"] = ArcRotateCameraPointersInput;
+}

+ 75 - 0
src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.js

@@ -0,0 +1,75 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraDeviceOrientationInput = (function () {
+        function FreeCameraDeviceOrientationInput() {
+            this._offsetX = null;
+            this._offsetY = null;
+            this._orientationGamma = 0;
+            this._orientationBeta = 0;
+            this._initialOrientationGamma = 0;
+            this._initialOrientationBeta = 0;
+            this.angularSensibility = 10000.0;
+            this.moveSensibility = 50.0;
+            this._resetOrientationGamma = this.resetOrientationGamma.bind(this);
+            this._orientationChanged = this.orientationChanged.bind(this);
+        }
+        FreeCameraDeviceOrientationInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+            window.addEventListener("resize", this._resetOrientationGamma, false);
+            window.addEventListener("deviceorientation", this._orientationChanged);
+        };
+        FreeCameraDeviceOrientationInput.prototype.resetOrientationGamma = function () {
+            this._initialOrientationGamma = null;
+        };
+        FreeCameraDeviceOrientationInput.prototype.orientationChanged = function (evt) {
+            if (!this._initialOrientationGamma) {
+                this._initialOrientationGamma = evt.gamma;
+                this._initialOrientationBeta = evt.beta;
+            }
+            this._orientationGamma = evt.gamma;
+            this._orientationBeta = evt.beta;
+            this._offsetY = (this._initialOrientationBeta - this._orientationBeta);
+            this._offsetX = (this._initialOrientationGamma - this._orientationGamma);
+        };
+        FreeCameraDeviceOrientationInput.prototype.detach = function () {
+            window.removeEventListener("resize", this._resetOrientationGamma);
+            window.removeEventListener("deviceorientation", this._orientationChanged);
+            this._orientationGamma = 0;
+            this._orientationBeta = 0;
+            this._initialOrientationGamma = 0;
+            this._initialOrientationBeta = 0;
+        };
+        FreeCameraDeviceOrientationInput.prototype.checkInputs = function () {
+            if (!this._offsetX) {
+                return;
+            }
+            var camera = this.camera;
+            camera.cameraRotation.y -= this._offsetX / this.angularSensibility;
+            var speed = camera._computeLocalCameraSpeed();
+            var direction = new BABYLON.Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
+            BABYLON.Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
+            camera.cameraDirection.addInPlace(BABYLON.Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+        };
+        FreeCameraDeviceOrientationInput.prototype.getTypeName = function () {
+            return "FreeCameraDeviceOrientationInput";
+        };
+        FreeCameraDeviceOrientationInput.prototype.getSimpleName = function () {
+            return "deviceOrientation";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraDeviceOrientationInput.prototype, "angularSensibility", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraDeviceOrientationInput.prototype, "moveSensibility", void 0);
+        return FreeCameraDeviceOrientationInput;
+    })();
+    BABYLON.FreeCameraDeviceOrientationInput = FreeCameraDeviceOrientationInput;
+    BABYLON.CameraInputTypes["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;
+})(BABYLON || (BABYLON = {}));

+ 84 - 0
src/Cameras/Inputs/babylon.freecamera.input.deviceorientation.ts

@@ -0,0 +1,84 @@
+module BABYLON {
+    export class FreeCameraDeviceOrientationInput implements ICameraInput<FreeCamera> {
+        camera: FreeCamera;
+
+        private _offsetX: number = null;
+        private _offsetY: number = null;
+        private _orientationGamma: number = 0;
+        private _orientationBeta: number = 0;
+        private _initialOrientationGamma: number = 0;
+        private _initialOrientationBeta: number = 0;
+        private _orientationChanged: (e: DeviceOrientationEvent) => any;
+        private _resetOrientationGamma: () => any;
+
+        @serialize()
+        public angularSensibility: number = 10000.0;
+
+        @serialize()
+        public moveSensibility: number = 50.0;
+
+        constructor() {
+            this._resetOrientationGamma = this.resetOrientationGamma.bind(this);
+            this._orientationChanged = this.orientationChanged.bind(this);
+        }
+
+        attachCamera(camera: FreeCamera) {
+            this.camera = camera;
+
+            window.addEventListener("resize", this._resetOrientationGamma, false);
+            window.addEventListener("deviceorientation", this._orientationChanged);
+        }
+
+        resetOrientationGamma() {
+            this._initialOrientationGamma = null;
+        }
+
+        orientationChanged(evt) {
+            if (!this._initialOrientationGamma) {
+                this._initialOrientationGamma = evt.gamma;
+                this._initialOrientationBeta = evt.beta;
+            }
+
+            this._orientationGamma = evt.gamma;
+            this._orientationBeta = evt.beta;
+
+            this._offsetY = (this._initialOrientationBeta - this._orientationBeta);
+            this._offsetX = (this._initialOrientationGamma - this._orientationGamma);
+        }
+
+        detach() {
+            window.removeEventListener("resize", this._resetOrientationGamma);
+            window.removeEventListener("deviceorientation", this._orientationChanged);
+            
+            this._orientationGamma = 0;
+            this._orientationBeta = 0;
+            this._initialOrientationGamma = 0;
+            this._initialOrientationBeta = 0;
+        }
+
+        public checkInputs() {
+            if (!this._offsetX) {
+                return;
+            }
+            
+            var camera = this.camera;
+            camera.cameraRotation.y -= this._offsetX / this.angularSensibility;
+
+            var speed = camera._computeLocalCameraSpeed();
+            var direction = new Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
+
+            Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
+            camera.cameraDirection.addInPlace(Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+        }
+
+        getTypeName(): string {
+            return "FreeCameraDeviceOrientationInput";
+        }
+        
+        getSimpleName(){
+            return "deviceOrientation";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraDeviceOrientationInput"] = FreeCameraDeviceOrientationInput;
+}

+ 64 - 0
src/Cameras/Inputs/babylon.freecamera.input.gamepad.js

@@ -0,0 +1,64 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraGamepadInput = (function () {
+        function FreeCameraGamepadInput() {
+            var _this = this;
+            this.gamepadAngularSensibility = 200;
+            this.gamepadMoveSensibility = 40;
+            this._gamepads = new BABYLON.Gamepads(function (gamepad) { _this._onNewGameConnected(gamepad); });
+        }
+        FreeCameraGamepadInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        FreeCameraGamepadInput.prototype.detach = function () {
+            this._gamepads.dispose();
+        };
+        FreeCameraGamepadInput.prototype.checkInputs = function () {
+            if (this.gamepad) {
+                var camera = this.camera;
+                var LSValues = this.gamepad.leftStick;
+                var normalizedLX = LSValues.x / this.gamepadMoveSensibility;
+                var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
+                LSValues.x = Math.abs(normalizedLX) > 0.005 ? 0 + normalizedLX : 0;
+                LSValues.y = Math.abs(normalizedLY) > 0.005 ? 0 + normalizedLY : 0;
+                var RSValues = this.gamepad.rightStick;
+                var normalizedRX = RSValues.x / this.gamepadAngularSensibility;
+                var normalizedRY = RSValues.y / this.gamepadAngularSensibility;
+                RSValues.x = Math.abs(normalizedRX) > 0.001 ? 0 + normalizedRX : 0;
+                RSValues.y = Math.abs(normalizedRY) > 0.001 ? 0 + normalizedRY : 0;
+                var cameraTransform = BABYLON.Matrix.RotationYawPitchRoll(camera.rotation.y, camera.rotation.x, 0);
+                var speed = camera._computeLocalCameraSpeed() * 50.0;
+                var deltaTransform = BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(LSValues.x * speed, 0, -LSValues.y * speed), cameraTransform);
+                camera.cameraDirection = camera.cameraDirection.add(deltaTransform);
+                camera.cameraRotation = camera.cameraRotation.add(new BABYLON.Vector2(RSValues.y, RSValues.x));
+            }
+        };
+        FreeCameraGamepadInput.prototype._onNewGameConnected = function (gamepad) {
+            // Only the first gamepad can control the camera
+            if (gamepad.index === 0) {
+                this.gamepad = gamepad;
+            }
+        };
+        FreeCameraGamepadInput.prototype.getTypeName = function () {
+            return "FreeCameraGamepadInput";
+        };
+        FreeCameraGamepadInput.prototype.getSimpleName = function () {
+            return "gamepad";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraGamepadInput.prototype, "gamepadAngularSensibility", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraGamepadInput.prototype, "gamepadMoveSensibility", void 0);
+        return FreeCameraGamepadInput;
+    })();
+    BABYLON.FreeCameraGamepadInput = FreeCameraGamepadInput;
+    BABYLON.CameraInputTypes["FreeCameraGamepadInput"] = FreeCameraGamepadInput;
+})(BABYLON || (BABYLON = {}));

+ 67 - 0
src/Cameras/Inputs/babylon.freecamera.input.gamepad.ts

@@ -0,0 +1,67 @@
+module BABYLON {       
+    export class FreeCameraGamepadInput implements ICameraInput<FreeCamera> {
+        camera : FreeCamera;
+        
+        public gamepad: Gamepad;
+        private _gamepads: Gamepads;
+
+        @serialize()
+        public gamepadAngularSensibility = 200;
+
+        @serialize()
+        public gamepadMoveSensibility = 40;
+        
+        constructor(){
+            this._gamepads = new Gamepads((gamepad: Gamepad) => { this._onNewGameConnected(gamepad); });
+        }
+        
+        attachCamera(camera : FreeCamera){
+            this.camera = camera;
+        }
+        
+        detach(){
+            this._gamepads.dispose();
+        }
+        
+        checkInputs(){
+            if (this.gamepad) {
+                var camera = this.camera;
+                var LSValues = this.gamepad.leftStick;
+                var normalizedLX = LSValues.x / this.gamepadMoveSensibility;
+                var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
+                LSValues.x = Math.abs(normalizedLX) > 0.005 ? 0 + normalizedLX : 0;
+                LSValues.y = Math.abs(normalizedLY) > 0.005 ? 0 + normalizedLY : 0;
+
+                var RSValues = this.gamepad.rightStick;
+                var normalizedRX = RSValues.x / this.gamepadAngularSensibility;
+                var normalizedRY = RSValues.y / this.gamepadAngularSensibility;
+                RSValues.x = Math.abs(normalizedRX) > 0.001 ? 0 + normalizedRX : 0;
+                RSValues.y = Math.abs(normalizedRY) > 0.001 ? 0 + normalizedRY : 0;
+
+                var cameraTransform = Matrix.RotationYawPitchRoll(camera.rotation.y, camera.rotation.x, 0);
+
+                var speed = camera._computeLocalCameraSpeed() * 50.0;
+                var deltaTransform = Vector3.TransformCoordinates(new Vector3(LSValues.x * speed, 0, -LSValues.y * speed), cameraTransform);
+                camera.cameraDirection = camera.cameraDirection.add(deltaTransform);
+                camera.cameraRotation = camera.cameraRotation.add(new Vector2(RSValues.y, RSValues.x));
+            }
+        }
+        
+        private _onNewGameConnected(gamepad: Gamepad) {
+            // Only the first gamepad can control the camera
+            if (gamepad.index === 0) {
+                this.gamepad = gamepad;
+            }
+        }
+        
+        getTypeName(): string{
+            return "FreeCameraGamepadInput";
+        }
+        
+        getSimpleName(){
+            return "gamepad";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraGamepadInput"] = FreeCameraGamepadInput;
+}

+ 111 - 0
src/Cameras/Inputs/babylon.freecamera.input.keyboard.js

@@ -0,0 +1,111 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraKeyboardMoveInput = (function () {
+        function FreeCameraKeyboardMoveInput() {
+            this._keys = [];
+            this.keysUp = [38];
+            this.keysDown = [40];
+            this.keysLeft = [37];
+            this.keysRight = [39];
+        }
+        FreeCameraKeyboardMoveInput.prototype.attachCamera = function (camera) {
+            var _this = this;
+            this.camera = camera;
+            if (this._onKeyDown === undefined) {
+                this._onKeyDown = function (evt) {
+                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = _this._keys.indexOf(evt.keyCode);
+                        if (index === -1) {
+                            _this._keys.push(evt.keyCode);
+                        }
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                };
+                this._onKeyUp = function (evt) {
+                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = _this._keys.indexOf(evt.keyCode);
+                        if (index >= 0) {
+                            _this._keys.splice(index, 1);
+                        }
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                };
+                BABYLON.Tools.RegisterTopRootEvents([
+                    { name: "keydown", handler: this._onKeyDown },
+                    { name: "keyup", handler: this._onKeyUp },
+                    { name: "blur", handler: this._onLostFocus }
+                ]);
+            }
+        };
+        FreeCameraKeyboardMoveInput.prototype.detach = function () {
+            BABYLON.Tools.UnregisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        };
+        FreeCameraKeyboardMoveInput.prototype.checkInputs = function () {
+            var camera = this.camera;
+            // Keyboard
+            for (var index = 0; index < this._keys.length; index++) {
+                var keyCode = this._keys[index];
+                var speed = camera._computeLocalCameraSpeed();
+                if (this.keysLeft.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(-speed, 0, 0);
+                }
+                else if (this.keysUp.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(0, 0, speed);
+                }
+                else if (this.keysRight.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(speed, 0, 0);
+                }
+                else if (this.keysDown.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(0, 0, -speed);
+                }
+                camera.getViewMatrix().invertToRef(camera._cameraTransformMatrix);
+                BABYLON.Vector3.TransformNormalToRef(camera._localDirection, camera._cameraTransformMatrix, camera._transformedDirection);
+                camera.cameraDirection.addInPlace(camera._transformedDirection);
+            }
+        };
+        FreeCameraKeyboardMoveInput.prototype.getTypeName = function () {
+            return "FreeCameraKeyboardMoveInput";
+        };
+        FreeCameraKeyboardMoveInput.prototype._onLostFocus = function (e) {
+            this._keys = [];
+        };
+        FreeCameraKeyboardMoveInput.prototype.getSimpleName = function () {
+            return "keyboard";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraKeyboardMoveInput.prototype, "keysUp", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraKeyboardMoveInput.prototype, "keysDown", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraKeyboardMoveInput.prototype, "keysLeft", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraKeyboardMoveInput.prototype, "keysRight", void 0);
+        return FreeCameraKeyboardMoveInput;
+    })();
+    BABYLON.FreeCameraKeyboardMoveInput = FreeCameraKeyboardMoveInput;
+    BABYLON.CameraInputTypes["FreeCameraKeyboardMoveInput"] = FreeCameraKeyboardMoveInput;
+})(BABYLON || (BABYLON = {}));

+ 110 - 0
src/Cameras/Inputs/babylon.freecamera.input.keyboard.ts

@@ -0,0 +1,110 @@
+module BABYLON {
+    export class FreeCameraKeyboardMoveInput implements ICameraInput<FreeCamera> {
+        camera: FreeCamera;
+        private _keys = [];
+        private _onKeyDown: (e: KeyboardEvent) => any;
+        private _onKeyUp: (e: KeyboardEvent) => any;
+
+        @serialize()
+        public keysUp = [38];
+
+        @serialize()
+        public keysDown = [40];
+
+        @serialize()
+        public keysLeft = [37];
+
+        @serialize()
+        public keysRight = [39];
+
+        attachCamera(camera: FreeCamera) {
+            this.camera = camera;
+            
+            if (this._onKeyDown === undefined) {
+
+                this._onKeyDown = evt => {
+                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+
+                        if (index === -1) {
+                            this._keys.push(evt.keyCode);
+                        }
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                };
+
+                this._onKeyUp = evt => {
+                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
+                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
+                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
+                        this.keysRight.indexOf(evt.keyCode) !== -1) {
+                        var index = this._keys.indexOf(evt.keyCode);
+
+                        if (index >= 0) {
+                            this._keys.splice(index, 1);
+                        }
+                        if (!camera._noPreventDefault) {
+                            evt.preventDefault();
+                        }
+                    }
+                };
+
+                Tools.RegisterTopRootEvents([
+                    { name: "keydown", handler: this._onKeyDown },
+                    { name: "keyup", handler: this._onKeyUp },
+                    { name: "blur", handler: this._onLostFocus }
+                ]);
+            }
+        }
+
+        detach() {
+            Tools.UnregisterTopRootEvents([
+                { name: "keydown", handler: this._onKeyDown },
+                { name: "keyup", handler: this._onKeyUp },
+                { name: "blur", handler: this._onLostFocus }
+            ]);
+        }
+        
+        public checkInputs() {
+            var camera = this.camera;
+            // Keyboard
+            for (var index = 0; index < this._keys.length; index++) {
+                var keyCode = this._keys[index];
+                var speed = camera._computeLocalCameraSpeed();
+
+                if (this.keysLeft.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(-speed, 0, 0);
+                } else if (this.keysUp.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(0, 0, speed);
+                } else if (this.keysRight.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(speed, 0, 0);
+                } else if (this.keysDown.indexOf(keyCode) !== -1) {
+                    camera._localDirection.copyFromFloats(0, 0, -speed);
+                }
+
+                camera.getViewMatrix().invertToRef(camera._cameraTransformMatrix);
+                Vector3.TransformNormalToRef(camera._localDirection, camera._cameraTransformMatrix, camera._transformedDirection);
+                camera.cameraDirection.addInPlace(camera._transformedDirection);
+            }
+        }
+
+        getTypeName(): string {
+            return "FreeCameraKeyboardMoveInput";
+        }
+
+        public _onLostFocus(e: FocusEvent): void {
+            this._keys = [];
+        }
+        
+        getSimpleName(){
+            return "keyboard";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraKeyboardMoveInput"] = FreeCameraKeyboardMoveInput;
+}

+ 102 - 0
src/Cameras/Inputs/babylon.freecamera.input.mouse.js

@@ -0,0 +1,102 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraMouseInput = (function () {
+        function FreeCameraMouseInput() {
+            this.angularSensibility = 2000.0;
+        }
+        FreeCameraMouseInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        FreeCameraMouseInput.prototype.attachElement = function (element, noPreventDefault) {
+            var _this = this;
+            var previousPosition;
+            this.attachedElement = element;
+            if (this._onMouseDown === undefined) {
+                var camera = this.camera;
+                var engine = this.camera.getEngine();
+                this._onMouseDown = function (evt) {
+                    previousPosition = {
+                        x: evt.clientX,
+                        y: evt.clientY
+                    };
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+                this._onMouseUp = function (evt) {
+                    previousPosition = null;
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+                this._onMouseOut = function (evt) {
+                    previousPosition = null;
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+                this._onMouseMove = function (evt) {
+                    if (!previousPosition && !engine.isPointerLock) {
+                        return;
+                    }
+                    var offsetX;
+                    var offsetY;
+                    if (!engine.isPointerLock) {
+                        offsetX = evt.clientX - previousPosition.x;
+                        offsetY = evt.clientY - 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;
+                    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);
+        };
+        FreeCameraMouseInput.prototype.detachElement = function (element) {
+            if (this.attachedElement !== element) {
+                return;
+            }
+            element.removeEventListener("mousedown", this._onMouseDown);
+            element.removeEventListener("mouseup", this._onMouseUp);
+            element.removeEventListener("mouseout", this._onMouseOut);
+            element.removeEventListener("mousemove", this._onMouseMove);
+            this.attachedElement = null;
+        };
+        FreeCameraMouseInput.prototype.detach = function () {
+            if (this.attachedElement) {
+                this.detachElement(this.attachedElement);
+            }
+        };
+        FreeCameraMouseInput.prototype.getTypeName = function () {
+            return "FreeCameraMouseInput";
+        };
+        FreeCameraMouseInput.prototype.getSimpleName = function () {
+            return "mouse";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraMouseInput.prototype, "angularSensibility", void 0);
+        return FreeCameraMouseInput;
+    })();
+    BABYLON.FreeCameraMouseInput = FreeCameraMouseInput;
+    BABYLON.CameraInputTypes["FreeCameraMouseInput"] = FreeCameraMouseInput;
+})(BABYLON || (BABYLON = {}));

+ 117 - 0
src/Cameras/Inputs/babylon.freecamera.input.mouse.ts

@@ -0,0 +1,117 @@
+module BABYLON {       
+    export class FreeCameraMouseInput implements ICameraInput<FreeCamera> {
+        camera : FreeCamera;
+        attachedElement : HTMLElement;
+        
+        @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;
+        
+        attachCamera(camera : FreeCamera){
+            this.camera = camera;
+        }
+        
+        attachElement(element: HTMLElement, noPreventDefault?: boolean){     
+            var previousPosition;
+            this.attachedElement = element;
+                
+            if (this._onMouseDown === undefined) {
+                var camera = this.camera;
+                var engine = this.camera.getEngine();
+                
+                this._onMouseDown = evt => {
+                    previousPosition = {
+                        x: evt.clientX,
+                        y: evt.clientY
+                    };
+
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+
+                this._onMouseUp = evt => {
+                    previousPosition = null;
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+
+                this._onMouseOut = evt => {
+                    previousPosition = null;
+                    
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+                };
+
+                this._onMouseMove = evt => {
+                    if (!previousPosition && !engine.isPointerLock) {
+                        return;
+                    }
+
+                    var offsetX;
+                    var offsetY;
+
+                    if (!engine.isPointerLock) {
+                        offsetX = evt.clientX - previousPosition.x;
+                        offsetY = evt.clientY - 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;
+
+                    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);
+   
+        }
+        
+        detachElement(element : HTMLElement){   
+            if (this.attachedElement !== element) {
+                return;
+            }
+
+            element.removeEventListener("mousedown", this._onMouseDown);
+            element.removeEventListener("mouseup", this._onMouseUp);
+            element.removeEventListener("mouseout", this._onMouseOut);
+            element.removeEventListener("mousemove", this._onMouseMove); 
+            this.attachedElement = null;        
+        }
+        
+        detach(){          
+            if (this.attachedElement){
+                this.detachElement(this.attachedElement);
+            }  
+        }
+        
+        getTypeName(): string{
+            return "FreeCameraMouseInput";
+        }
+        
+        getSimpleName(){
+            return "mouse";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraMouseInput"] = FreeCameraMouseInput;
+}

+ 139 - 0
src/Cameras/Inputs/babylon.freecamera.input.touch.js

@@ -0,0 +1,139 @@
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraTouchInput = (function () {
+        function FreeCameraTouchInput() {
+            this._offsetX = null;
+            this._offsetY = null;
+            this._pointerCount = 0;
+            this._pointerPressed = [];
+            this.touchAngularSensibility = 200000.0;
+            this.touchMoveSensibility = 250.0;
+        }
+        FreeCameraTouchInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+        };
+        FreeCameraTouchInput.prototype.attachElement = function (element, noPreventDefault) {
+            var _this = this;
+            var previousPosition;
+            if (this._attachedElement) {
+                return;
+            }
+            this._attachedElement = element;
+            if (this._onPointerDown === 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();
+                    }
+                    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.detachElement = function (element) {
+            if (this._attachedElement !== element) {
+                return;
+            }
+            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._attachedElement = null;
+        };
+        FreeCameraTouchInput.prototype.checkInputs = function () {
+            if (this._offsetX) {
+                var camera = this.camera;
+                camera.cameraRotation.y += this._offsetX / this.touchAngularSensibility;
+                if (this._pointerPressed.length > 1) {
+                    camera.cameraRotation.x += -this._offsetY / this.touchAngularSensibility;
+                }
+                else {
+                    var speed = camera._computeLocalCameraSpeed();
+                    var direction = new BABYLON.Vector3(0, 0, speed * this._offsetY / this.touchMoveSensibility);
+                    BABYLON.Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
+                    camera.cameraDirection.addInPlace(BABYLON.Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+                }
+            }
+        };
+        FreeCameraTouchInput.prototype.detach = function () {
+            if (this._attachedElement) {
+                this.detachElement(this._attachedElement);
+            }
+        };
+        FreeCameraTouchInput.prototype.getTypeName = function () {
+            return "FreeCameraTouchInput";
+        };
+        FreeCameraTouchInput.prototype.getSimpleName = function () {
+            return "touch";
+        };
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraTouchInput.prototype, "touchAngularSensibility", void 0);
+        __decorate([
+            BABYLON.serialize()
+        ], FreeCameraTouchInput.prototype, "touchMoveSensibility", void 0);
+        return FreeCameraTouchInput;
+    })();
+    BABYLON.FreeCameraTouchInput = FreeCameraTouchInput;
+    BABYLON.CameraInputTypes["FreeCameraTouchInput"] = FreeCameraTouchInput;
+})(BABYLON || (BABYLON = {}));

+ 166 - 0
src/Cameras/Inputs/babylon.freecamera.input.touch.ts

@@ -0,0 +1,166 @@
+module BABYLON {
+    export class FreeCameraTouchInput implements ICameraInput<FreeCamera> {
+        camera: FreeCamera;
+
+        private _offsetX: number = null;
+        private _offsetY: number = null;
+        private _pointerCount: number = 0;
+        private _pointerPressed = [];
+        private _attachedElement: HTMLElement;
+        private _onPointerDown: (e: PointerEvent) => any;
+        private _onPointerUp: (e: PointerEvent) => any;
+        private _onPointerMove: (e: PointerEvent) => any;
+        private _onLostFocus: (e: FocusEvent) => any;
+
+        @serialize()
+        public touchAngularSensibility: number = 200000.0;
+
+        @serialize()
+        public touchMoveSensibility: number = 250.0;
+
+        attachCamera(camera: FreeCamera) {
+            this.camera = camera;
+        }
+        
+        attachElement(element: HTMLElement, noPreventDefault?: boolean) {
+            var previousPosition;
+
+            if (this._attachedElement) {
+                return;
+            }
+
+            this._attachedElement = element;
+
+            if (this._onPointerDown === 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;
+                    }
+
+                    previousPosition = {
+                        x: evt.clientX,
+                        y: evt.clientY
+                    };
+                };
+
+                this._onPointerUp = (evt) => {
+
+                    if (evt.pointerType === "mouse") {
+                        return;
+                    }
+
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+
+                    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;
+                };
+
+                this._onPointerMove = (evt) => {
+
+                    if (evt.pointerType === "mouse") {
+                        return;
+                    }
+
+                    if (!noPreventDefault) {
+                        evt.preventDefault();
+                    }
+
+                    if (!previousPosition) {
+                        return;
+                    }
+
+                    var index: number = 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);
+        }
+
+        detachElement(element: HTMLElement) {
+            if (this._attachedElement !== element) {
+                return;
+            }
+
+            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._attachedElement = null;
+        }
+
+        checkInputs() {
+            if (this._offsetX) {
+                var camera = this.camera;
+                camera.cameraRotation.y += this._offsetX / this.touchAngularSensibility;
+
+                if (this._pointerPressed.length > 1) {
+                    camera.cameraRotation.x += -this._offsetY / this.touchAngularSensibility;
+                } else {
+                    var speed = camera._computeLocalCameraSpeed();
+                    var direction = new Vector3(0, 0, speed * this._offsetY / this.touchMoveSensibility);
+
+                    Matrix.RotationYawPitchRollToRef(camera.rotation.y, camera.rotation.x, 0, camera._cameraRotationMatrix);
+                    camera.cameraDirection.addInPlace(Vector3.TransformCoordinates(direction, camera._cameraRotationMatrix));
+                }
+            }
+        }
+
+        detach() {
+            if (this._attachedElement) {
+                this.detachElement(this._attachedElement);
+            }
+        }
+
+        getTypeName(): string {
+            return "FreeCameraTouchInput";
+        }
+        
+        getSimpleName(){
+            return "touch";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraTouchInput"] = FreeCameraTouchInput;
+}

+ 52 - 0
src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.js

@@ -0,0 +1,52 @@
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraVirtualJoystickInput = (function () {
+        function FreeCameraVirtualJoystickInput() {
+        }
+        FreeCameraVirtualJoystickInput.prototype.getLeftJoystick = function () {
+            return this._leftjoystick;
+        };
+        FreeCameraVirtualJoystickInput.prototype.getRightJoystick = function () {
+            return this._rightjoystick;
+        };
+        FreeCameraVirtualJoystickInput.prototype.checkInputs = function () {
+            var camera = this.camera;
+            var speed = camera._computeLocalCameraSpeed() * 50;
+            var cameraTransform = BABYLON.Matrix.RotationYawPitchRoll(camera.rotation.y, camera.rotation.x, 0);
+            var deltaTransform = BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(this._leftjoystick.deltaPosition.x * speed, this._leftjoystick.deltaPosition.y * speed, this._leftjoystick.deltaPosition.z * speed), cameraTransform);
+            camera.cameraDirection = camera.cameraDirection.add(deltaTransform);
+            camera.cameraRotation = camera.cameraRotation.addVector3(this._rightjoystick.deltaPosition);
+            if (!this._leftjoystick.pressed) {
+                this._leftjoystick.deltaPosition = this._leftjoystick.deltaPosition.scale(0.9);
+            }
+            if (!this._rightjoystick.pressed) {
+                this._rightjoystick.deltaPosition = this._rightjoystick.deltaPosition.scale(0.9);
+            }
+        };
+        FreeCameraVirtualJoystickInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+            this._leftjoystick = new BABYLON.VirtualJoystick(true);
+            this._leftjoystick.setAxisForUpDown(BABYLON.JoystickAxis.Z);
+            this._leftjoystick.setAxisForLeftRight(BABYLON.JoystickAxis.X);
+            this._leftjoystick.setJoystickSensibility(0.15);
+            this._rightjoystick = new BABYLON.VirtualJoystick(false);
+            this._rightjoystick.setAxisForUpDown(BABYLON.JoystickAxis.X);
+            this._rightjoystick.setAxisForLeftRight(BABYLON.JoystickAxis.Y);
+            this._rightjoystick.reverseUpDown = true;
+            this._rightjoystick.setJoystickSensibility(0.05);
+            this._rightjoystick.setJoystickColor("yellow");
+        };
+        FreeCameraVirtualJoystickInput.prototype.detach = function () {
+            this._leftjoystick.releaseCanvas();
+        };
+        FreeCameraVirtualJoystickInput.prototype.getTypeName = function () {
+            return "FreeCameraVirtualJoystickInput";
+        };
+        FreeCameraVirtualJoystickInput.prototype.getSimpleName = function () {
+            return "virtualJoystick";
+        };
+        return FreeCameraVirtualJoystickInput;
+    })();
+    BABYLON.FreeCameraVirtualJoystickInput = FreeCameraVirtualJoystickInput;
+    BABYLON.CameraInputTypes["FreeCameraVirtualJoystickInput"] = FreeCameraVirtualJoystickInput;
+})(BABYLON || (BABYLON = {}));

+ 61 - 0
src/Cameras/Inputs/babylon.freecamera.input.virtualjoystick.ts

@@ -0,0 +1,61 @@
+module BABYLON {
+    export class FreeCameraVirtualJoystickInput implements ICameraInput<FreeCamera> {
+        camera: FreeCamera;
+
+        private _leftjoystick: VirtualJoystick;
+        private _rightjoystick: VirtualJoystick;
+        
+        public getLeftJoystick(): VirtualJoystick {
+            return this._leftjoystick;
+        }
+
+        public getRightJoystick(): VirtualJoystick {
+            return this._rightjoystick;
+        }
+
+        public checkInputs() {
+            var camera = this.camera;
+            var speed = camera._computeLocalCameraSpeed() * 50;
+            var cameraTransform = Matrix.RotationYawPitchRoll(camera.rotation.y, camera.rotation.x, 0);
+            var deltaTransform = Vector3.TransformCoordinates(new Vector3(this._leftjoystick.deltaPosition.x * speed, this._leftjoystick.deltaPosition.y * speed, this._leftjoystick.deltaPosition.z * speed), cameraTransform);
+            camera.cameraDirection = camera.cameraDirection.add(deltaTransform);
+            camera.cameraRotation = camera.cameraRotation.addVector3(this._rightjoystick.deltaPosition);
+            
+            if (!this._leftjoystick.pressed) {
+                this._leftjoystick.deltaPosition = this._leftjoystick.deltaPosition.scale(0.9);
+            }
+            if (!this._rightjoystick.pressed) {
+                this._rightjoystick.deltaPosition = this._rightjoystick.deltaPosition.scale(0.9);
+            }
+        }
+        
+        attachCamera(camera: FreeCamera) {
+            this.camera = camera;
+            
+            this._leftjoystick = new VirtualJoystick(true);
+            this._leftjoystick.setAxisForUpDown(JoystickAxis.Z);
+            this._leftjoystick.setAxisForLeftRight(JoystickAxis.X);
+            this._leftjoystick.setJoystickSensibility(0.15);
+            this._rightjoystick = new VirtualJoystick(false);
+            this._rightjoystick.setAxisForUpDown(JoystickAxis.X);
+            this._rightjoystick.setAxisForLeftRight(JoystickAxis.Y);
+            this._rightjoystick.reverseUpDown = true;
+            this._rightjoystick.setJoystickSensibility(0.05);
+            this._rightjoystick.setJoystickColor("yellow");
+        }
+
+        detach() {
+            this._leftjoystick.releaseCanvas();
+        }
+
+        getTypeName(): string {
+            return "FreeCameraVirtualJoystickInput";
+        }
+        
+        getSimpleName(){
+            return "virtualJoystick";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraVirtualJoystickInput"] = FreeCameraVirtualJoystickInput;
+}

+ 43 - 0
src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.js

@@ -0,0 +1,43 @@
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraVRDeviceOrientationInput = (function () {
+        function FreeCameraVRDeviceOrientationInput() {
+            this._alpha = 0;
+            this._beta = 0;
+            this._gamma = 0;
+            this._deviceOrientationHandler = this._onOrientationEvent.bind(this);
+        }
+        FreeCameraVRDeviceOrientationInput.prototype.attachCamera = function (camera) {
+            this.camera = camera;
+            window.addEventListener("deviceorientation", this._deviceOrientationHandler);
+        };
+        FreeCameraVRDeviceOrientationInput.prototype._onOrientationEvent = function (evt) {
+            var camera = this.camera;
+            this._alpha = +evt.alpha | 0;
+            this._beta = +evt.beta | 0;
+            this._gamma = +evt.gamma | 0;
+            if (this._gamma < 0) {
+                this._gamma = 90 + this._gamma;
+            }
+            else {
+                // Incline it in the correct angle.
+                this._gamma = 270 - this._gamma;
+            }
+            camera.rotation.x = this._gamma / 180.0 * Math.PI;
+            camera.rotation.y = -this._alpha / 180.0 * Math.PI;
+            camera.rotation.z = this._beta / 180.0 * Math.PI;
+        };
+        FreeCameraVRDeviceOrientationInput.prototype.detach = function () {
+            window.removeEventListener("deviceorientation", this._deviceOrientationHandler);
+        };
+        FreeCameraVRDeviceOrientationInput.prototype.getTypeName = function () {
+            return "FreeCameraVRDeviceOrientationInput";
+        };
+        FreeCameraVRDeviceOrientationInput.prototype.getSimpleName = function () {
+            return "VRDeviceOrientation";
+        };
+        return FreeCameraVRDeviceOrientationInput;
+    })();
+    BABYLON.FreeCameraVRDeviceOrientationInput = FreeCameraVRDeviceOrientationInput;
+    BABYLON.CameraInputTypes["FreeCameraVRDeviceOrientationInput"] = FreeCameraVRDeviceOrientationInput;
+})(BABYLON || (BABYLON = {}));

+ 55 - 0
src/Cameras/Inputs/babylon.freecamera.input.vrdeviceorientation.ts

@@ -0,0 +1,55 @@
+module BABYLON {
+    export class FreeCameraVRDeviceOrientationInput implements ICameraInput<FreeCamera> {
+        camera: FreeCamera;
+
+        public _alpha = 0;
+        public _beta = 0;
+        public _gamma = 0;
+    
+        private _offsetOrientation: { yaw: number; pitch: number; roll: number };
+        private _deviceOrientationHandler;
+        
+        constructor() {
+            this._deviceOrientationHandler = this._onOrientationEvent.bind(this);
+        }
+
+        attachCamera(camera: FreeCamera) {
+            this.camera = camera;
+
+            window.addEventListener("deviceorientation", this._deviceOrientationHandler);
+        }
+
+        public _onOrientationEvent(evt: DeviceOrientationEvent): void {
+            var camera = this.camera;
+            this._alpha = +evt.alpha|0;
+            this._beta = +evt.beta|0;
+            this._gamma = +evt.gamma|0;
+
+            if (this._gamma < 0) {
+                this._gamma = 90 + this._gamma;
+            }
+            else {
+                // Incline it in the correct angle.
+                this._gamma = 270 - this._gamma;
+            }
+
+            camera.rotation.x = this._gamma / 180.0 * Math.PI;   
+            camera.rotation.y = -this._alpha / 180.0 * Math.PI;   
+            camera.rotation.z = this._beta / 180.0 * Math.PI;     
+        }
+
+        detach() {
+            window.removeEventListener("deviceorientation", this._deviceOrientationHandler);
+        }
+
+        getTypeName(): string {
+            return "FreeCameraVRDeviceOrientationInput";
+        }
+        
+        getSimpleName(){
+            return "VRDeviceOrientation";
+        }
+    }
+    
+    CameraInputTypes["FreeCameraVRDeviceOrientationInput"] = FreeCameraVRDeviceOrientationInput;
+}

+ 1 - 27
src/Cameras/VR/babylon.vrDeviceOrientationCamera.js

@@ -10,37 +10,11 @@ var BABYLON;
         function VRDeviceOrientationFreeCamera(name, position, scene, compensateDistortion) {
             if (compensateDistortion === void 0) { compensateDistortion = true; }
             _super.call(this, name, position, scene);
-            this._alpha = 0;
-            this._beta = 0;
-            this._gamma = 0;
             var metrics = BABYLON.VRCameraMetrics.GetDefault();
             metrics.compensateDistortion = compensateDistortion;
             this.setCameraRigMode(BABYLON.Camera.RIG_MODE_VR, { vrCameraMetrics: metrics });
-            this._deviceOrientationHandler = this._onOrientationEvent.bind(this);
+            this.inputs.addVRDeviceOrientation();
         }
-        VRDeviceOrientationFreeCamera.prototype._onOrientationEvent = function (evt) {
-            this._alpha = +evt.alpha | 0;
-            this._beta = +evt.beta | 0;
-            this._gamma = +evt.gamma | 0;
-            if (this._gamma < 0) {
-                this._gamma = 90 + this._gamma;
-            }
-            else {
-                // Incline it in the correct angle.
-                this._gamma = 270 - this._gamma;
-            }
-            this.rotation.x = this._gamma / 180.0 * Math.PI;
-            this.rotation.y = -this._alpha / 180.0 * Math.PI;
-            this.rotation.z = this._beta / 180.0 * Math.PI;
-        };
-        VRDeviceOrientationFreeCamera.prototype.attachControl = function (element, noPreventDefault) {
-            _super.prototype.attachControl.call(this, element, noPreventDefault);
-            window.addEventListener("deviceorientation", this._deviceOrientationHandler);
-        };
-        VRDeviceOrientationFreeCamera.prototype.detachControl = function (element) {
-            _super.prototype.detachControl.call(this, element);
-            window.removeEventListener("deviceorientation", this._deviceOrientationHandler);
-        };
         VRDeviceOrientationFreeCamera.prototype.getTypeName = function () {
             return "VRDeviceOrientationFreeCamera";
         };

+ 2 - 37
src/Cameras/VR/babylon.vrDeviceOrientationCamera.ts

@@ -1,11 +1,5 @@
 module BABYLON {
-    export class VRDeviceOrientationFreeCamera extends FreeCamera {
-        public _alpha = 0;
-        public _beta = 0;
-        public _gamma = 0;
-    
-        private _offsetOrientation: { yaw: number; pitch: number; roll: number };
-        private _deviceOrientationHandler;
+    export class VRDeviceOrientationFreeCamera extends FreeCamera {        
 
         constructor(name: string, position: Vector3, scene: Scene, compensateDistortion = true) {
             super(name, position, scene);
@@ -14,38 +8,9 @@ module BABYLON {
             metrics.compensateDistortion = compensateDistortion;
             this.setCameraRigMode(Camera.RIG_MODE_VR, { vrCameraMetrics: metrics });
 
-            this._deviceOrientationHandler = this._onOrientationEvent.bind(this);
+            this.inputs.addVRDeviceOrientation();
         }
 
-        public _onOrientationEvent(evt: DeviceOrientationEvent): void {
-            this._alpha = +evt.alpha|0;
-            this._beta = +evt.beta|0;
-            this._gamma = +evt.gamma|0;
-
-            if (this._gamma < 0) {
-                this._gamma = 90 + this._gamma;
-            }
-            else {
-                // Incline it in the correct angle.
-                this._gamma = 270 - this._gamma;
-            }
-
-            this.rotation.x = this._gamma / 180.0 * Math.PI;   
-            this.rotation.y = -this._alpha / 180.0 * Math.PI;   
-            this.rotation.z = this._beta / 180.0 * Math.PI;     
-        }
-
-        public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
-            super.attachControl(element, noPreventDefault);
-
-            window.addEventListener("deviceorientation", this._deviceOrientationHandler);
-        }
-
-        public detachControl(element: HTMLElement): void {
-            super.detachControl(element);
-
-            window.removeEventListener("deviceorientation", this._deviceOrientationHandler);
-        }
 
         public getTypeName(): string {
             return "VRDeviceOrientationFreeCamera";

+ 1 - 0
src/Cameras/VR/babylon.webVRCamera.js

@@ -56,6 +56,7 @@ var BABYLON;
         };
         WebVRFreeCamera.prototype.attachControl = function (element, noPreventDefault) {
             _super.prototype.attachControl.call(this, element, noPreventDefault);
+            noPreventDefault = BABYLON.Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
             if (navigator.getVRDevices) {
                 navigator.getVRDevices().then(this._getWebVRDevices);
             }

+ 2 - 0
src/Cameras/VR/babylon.webVRCamera.ts

@@ -65,6 +65,8 @@ module BABYLON {
         public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
             super.attachControl(element, noPreventDefault);
 
+            noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
+
             if (navigator.getVRDevices) {
                 navigator.getVRDevices().then(this._getWebVRDevices);
             }

+ 141 - 268
src/Cameras/babylon.arcRotateCamera.js

@@ -11,7 +11,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
 };
 var BABYLON;
 (function (BABYLON) {
-    var eventPrefix = BABYLON.Tools.GetPointerPrefix();
     var ArcRotateCamera = (function (_super) {
         __extends(ArcRotateCamera, _super);
         function ArcRotateCamera(name, alpha, beta, radius, target, scene) {
@@ -26,27 +25,15 @@ var BABYLON;
             this.upperBetaLimit = Math.PI;
             this.lowerRadiusLimit = null;
             this.upperRadiusLimit = null;
-            this.angularSensibilityX = 1000.0;
-            this.angularSensibilityY = 1000.0;
-            this.wheelPrecision = 3.0;
-            this.pinchPrecision = 2.0;
-            this.panningSensibility = 50.0;
             this.inertialPanningX = 0;
             this.inertialPanningY = 0;
-            this.keysUp = [38];
-            this.keysDown = [40];
-            this.keysLeft = [37];
-            this.keysRight = [39];
+            //-- end properties for backward compatibility for inputs        
             this.zoomOnFactor = 1;
             this.targetScreenOffset = BABYLON.Vector2.Zero();
-            this.pinchInwards = true;
             this.allowUpsideDown = true;
-            this._keys = [];
             this._viewMatrix = new BABYLON.Matrix();
             // Panning
             this.panningAxis = new BABYLON.Vector3(1, 1, 0);
-            this._isRightClick = false;
-            this._isCtrlPushed = false;
             this.checkCollisions = false;
             this.collisionRadius = new BABYLON.Vector3(0.5, 0.5, 0.5);
             this._collider = new BABYLON.Collider();
@@ -98,18 +85,136 @@ var BABYLON;
             this.beta = beta;
             this.radius = radius;
             this.getViewMatrix();
+            this.inputs = new BABYLON.ArcRotateCameraInputsManager(this);
+            this.inputs.addKeyboard().addMouseWheel().addPointers().addGamepad();
         }
-        Object.defineProperty(ArcRotateCamera.prototype, "angularSensibility", {
-            //deprecated angularSensibility support
+        Object.defineProperty(ArcRotateCamera.prototype, "angularSensibilityX", {
+            //-- begin properties for backward compatibility for inputs       
             get: function () {
-                BABYLON.Tools.Warn("Warning: angularSensibility is deprecated, use angularSensibilityX and angularSensibilityY instead.");
-                return Math.max(this.angularSensibilityX, this.angularSensibilityY);
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers)
+                    return pointers.angularSensibilityX;
             },
-            //deprecated angularSensibility support
             set: function (value) {
-                BABYLON.Tools.Warn("Warning: angularSensibility is deprecated, use angularSensibilityX and angularSensibilityY instead.");
-                this.angularSensibilityX = value;
-                this.angularSensibilityY = value;
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers) {
+                    pointers.angularSensibilityX = value;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "angularSensibilityY", {
+            get: function () {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers)
+                    return pointers.angularSensibilityY;
+            },
+            set: function (value) {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers) {
+                    pointers.angularSensibilityY = value;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "pinchPrecision", {
+            get: function () {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers)
+                    return pointers.pinchPrecision;
+            },
+            set: function (value) {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers) {
+                    pointers.pinchPrecision = value;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "panningSensibility", {
+            get: function () {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers)
+                    return pointers.panningSensibility;
+            },
+            set: function (value) {
+                var pointers = this.inputs.attached["pointers"];
+                if (pointers) {
+                    pointers.panningSensibility = value;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "keysUp", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysUp;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysUp = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "keysDown", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysDown;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysDown = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "keysLeft", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysLeft;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysLeft = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "keysRight", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysRight;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysRight = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArcRotateCamera.prototype, "wheelPrecision", {
+            get: function () {
+                var mousewheel = this.inputs.attached["mousewheel"];
+                if (mousewheel)
+                    return mousewheel.wheelPrecision;
+            },
+            set: function (value) {
+                var mousewheel = this.inputs.attached["mousewheel"];
+                if (mousewheel)
+                    mousewheel.wheelPrecision = value;
             },
             enumerable: true,
             configurable: true
@@ -153,218 +258,24 @@ var BABYLON;
         ArcRotateCamera.prototype.attachControl = function (element, noPreventDefault, useCtrlForPanning) {
             var _this = this;
             if (useCtrlForPanning === void 0) { useCtrlForPanning = true; }
-            var cacheSoloPointer; // cache pointer object for better perf on camera rotation
-            var previousPinchDistance = 0;
-            var pointers = new BABYLON.SmartCollection();
             if (this._attachedElement) {
                 return;
             }
             this._attachedElement = element;
-            var engine = this.getEngine();
-            if (this._onPointerDown === undefined) {
-                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._onContextMenu = function (evt) {
-                    evt.preventDefault();
-                };
-                this._onPointerMove = function (evt) {
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                    switch (pointers.count) {
-                        case 1:
-                            if (_this.panningSensibility !== 0 && ((_this._isCtrlPushed && useCtrlForPanning) || (!useCtrlForPanning && _this._isRightClick))) {
-                                _this.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / _this.panningSensibility;
-                                _this.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / _this.panningSensibility;
-                            }
-                            else {
-                                var offsetX = evt.clientX - cacheSoloPointer.x;
-                                var offsetY = evt.clientY - cacheSoloPointer.y;
-                                _this.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
-                                _this.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.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (_this.pinchPrecision * _this.wheelPrecision * ((_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;
-                    }
-                    var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                    var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-                    _this.inertialAlphaOffset -= offsetX / _this.angularSensibilityX;
-                    _this.inertialBetaOffset -= offsetY / _this.angularSensibilityY;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._wheel = function (event) {
-                    var delta = 0;
-                    if (event.wheelDelta) {
-                        delta = event.wheelDelta / (_this.wheelPrecision * 40);
-                    }
-                    else if (event.detail) {
-                        delta = -event.detail / _this.wheelPrecision;
-                    }
-                    if (delta)
-                        _this.inertialRadiusOffset += delta;
-                    if (event.preventDefault) {
-                        if (!noPreventDefault) {
-                            event.preventDefault();
-                        }
-                    }
-                };
-                this._onKeyDown = function (evt) {
-                    _this._isCtrlPushed = evt.ctrlKey;
-                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = _this._keys.indexOf(evt.keyCode);
-                        if (index === -1) {
-                            _this._keys.push(evt.keyCode);
-                        }
-                        if (evt.preventDefault) {
-                            if (!noPreventDefault) {
-                                evt.preventDefault();
-                            }
-                        }
-                    }
-                };
-                this._onKeyUp = function (evt) {
-                    _this._isCtrlPushed = evt.ctrlKey;
-                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = _this._keys.indexOf(evt.keyCode);
-                        if (index >= 0) {
-                            _this._keys.splice(index, 1);
-                        }
-                        if (evt.preventDefault) {
-                            if (!noPreventDefault) {
-                                evt.preventDefault();
-                            }
-                        }
-                    }
-                };
-                this._onLostFocus = function () {
-                    _this._keys = [];
-                    pointers.empty();
-                    previousPinchDistance = 0;
-                    cacheSoloPointer = null;
-                };
-                this._onGestureStart = function (e) {
-                    if (window.MSGesture === undefined) {
-                        return;
-                    }
-                    if (!_this._MSGestureHandler) {
-                        _this._MSGestureHandler = new MSGesture();
-                        _this._MSGestureHandler.target = element;
-                    }
-                    _this._MSGestureHandler.addPointer(e.pointerId);
-                };
-                this._onGesture = function (e) {
-                    _this.radius *= e.scale;
-                    if (e.preventDefault) {
-                        if (!noPreventDefault) {
-                            e.stopPropagation();
-                            e.preventDefault();
-                        }
-                    }
-                };
-                this._reset = function () {
-                    _this._keys = [];
-                    _this.inertialAlphaOffset = 0;
-                    _this.inertialBetaOffset = 0;
-                    _this.inertialRadiusOffset = 0;
-                    pointers.empty();
-                    previousPinchDistance = 0;
-                    cacheSoloPointer = null;
-                };
-            }
-            if (!useCtrlForPanning) {
-                element.addEventListener("contextmenu", this._onContextMenu, false);
-            }
-            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);
-            element.addEventListener('mousewheel', this._wheel, false);
-            element.addEventListener('DOMMouseScroll', this._wheel, false);
-            BABYLON.Tools.RegisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
+            this._noPreventDefault = noPreventDefault;
+            this._useCtrlForPanning = useCtrlForPanning;
+            this.inputs.attachElement(element, noPreventDefault);
+            this._reset = function () {
+                _this.inertialAlphaOffset = 0;
+                _this.inertialBetaOffset = 0;
+                _this.inertialRadiusOffset = 0;
+            };
         };
         ArcRotateCamera.prototype.detachControl = function (element) {
             if (this._attachedElement !== element) {
                 return;
             }
-            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);
-            element.removeEventListener('mousewheel', this._wheel);
-            element.removeEventListener('DOMMouseScroll', this._wheel);
-            BABYLON.Tools.UnregisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
-            this._MSGestureHandler = null;
+            this.inputs.detachElement(this._attachedElement);
             this._attachedElement = null;
             if (this._reset) {
                 this._reset();
@@ -375,22 +286,7 @@ var BABYLON;
             if (this._collisionTriggered) {
                 return;
             }
-            // Keyboard
-            for (var index = 0; index < this._keys.length; index++) {
-                var keyCode = this._keys[index];
-                if (this.keysLeft.indexOf(keyCode) !== -1) {
-                    this.inertialAlphaOffset -= 0.01;
-                }
-                else if (this.keysUp.indexOf(keyCode) !== -1) {
-                    this.inertialBetaOffset -= 0.01;
-                }
-                else if (this.keysRight.indexOf(keyCode) !== -1) {
-                    this.inertialAlphaOffset += 0.01;
-                }
-                else if (this.keysDown.indexOf(keyCode) !== -1) {
-                    this.inertialBetaOffset += 0.01;
-                }
-            }
+            this.inputs.checkInputs();
             // Inertia
             if (this.inertialAlphaOffset !== 0 || this.inertialBetaOffset !== 0 || this.inertialRadiusOffset !== 0) {
                 this.alpha += this.beta <= 0 ? -this.inertialAlphaOffset : this.inertialAlphaOffset;
@@ -585,6 +481,10 @@ var BABYLON;
             }
             _super.prototype._updateRigCameras.call(this);
         };
+        ArcRotateCamera.prototype.dispose = function () {
+            this.inputs.clear();
+            _super.prototype.dispose.call(this);
+        };
         ArcRotateCamera.prototype.getTypeName = function () {
             return "ArcRotateCamera";
         };
@@ -629,39 +529,12 @@ var BABYLON;
         ], ArcRotateCamera.prototype, "upperRadiusLimit", void 0);
         __decorate([
             BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "angularSensibilityX", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "angularSensibilityY", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "wheelPrecision", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "pinchPrecision", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "panningSensibility", void 0);
-        __decorate([
-            BABYLON.serialize()
         ], ArcRotateCamera.prototype, "inertialPanningX", void 0);
         __decorate([
             BABYLON.serialize()
         ], ArcRotateCamera.prototype, "inertialPanningY", void 0);
         __decorate([
             BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "keysUp", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "keysDown", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "keysLeft", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], ArcRotateCamera.prototype, "keysRight", void 0);
-        __decorate([
-            BABYLON.serialize()
         ], ArcRotateCamera.prototype, "zoomOnFactor", void 0);
         __decorate([
             BABYLON.serialize()

+ 136 - 310
src/Cameras/babylon.arcRotateCamera.ts

@@ -1,6 +1,4 @@
 module BABYLON {
-    var eventPrefix = Tools.GetPointerPrefix();
-
     export class ArcRotateCamera extends TargetCamera {
         @serialize()
         public alpha: number;
@@ -40,73 +38,148 @@
 
         @serialize()
         public upperRadiusLimit = null;
-
-        @serialize()
-        public angularSensibilityX = 1000.0;
-
-        @serialize()
-        public angularSensibilityY = 1000.0;
-
-        @serialize()
-        public wheelPrecision = 3.0;
-
-        @serialize()
-        public pinchPrecision = 2.0;
-
-        @serialize()
-        public panningSensibility: number = 50.0;
-
+        
         @serialize()
         public inertialPanningX: number = 0;
 
         @serialize()
         public inertialPanningY: number = 0;
 
-        @serialize()
-        public keysUp = [38];
-
-        @serialize()
-        public keysDown = [40];
-
-        @serialize()
-        public keysLeft = [37];
-
-        @serialize()
-        public keysRight = [39];
+        //-- begin properties for backward compatibility for inputs       
+        public get angularSensibilityX() {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers)
+                return pointers.angularSensibilityX;
+        }
+        
+        public set angularSensibilityX(value) {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers){
+                pointers.angularSensibilityX = value;
+            }
+        }
+        
+        public get angularSensibilityY() {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers)
+                return pointers.angularSensibilityY;
+        }
+        
+        public set angularSensibilityY(value) {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers){
+                pointers.angularSensibilityY = value;
+            }
+        }
+        
+        public get pinchPrecision() {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers)
+                return pointers.pinchPrecision;
+        }
+        
+        public set pinchPrecision(value) {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers){
+                pointers.pinchPrecision = value;
+            }
+        }
+        
+        public get panningSensibility() {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers)
+                return pointers.panningSensibility;
+        }
+        
+        public set panningSensibility(value) {
+            var pointers = <ArcRotateCameraPointersInput>this.inputs.attached["pointers"];
+            if (pointers){
+                pointers.panningSensibility = value;
+            }
+        }
+        
+        public get keysUp() {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysUp;
+        }
+        
+        public set keysUp(value) {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysUp = value;
+        }
+        
+        public get keysDown() {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysDown;
+        }
+        
+        public set keysDown(value) {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysDown = value;
+        }
+        
+        public get keysLeft() {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysLeft;
+        }
+        
+        public set keysLeft(value) {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysLeft = value;
+        }
+        
+        public get keysRight() {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysRight;
+        }
+        
+        public set keysRight(value) {
+            var keyboard = <ArcRotateCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysRight = value;
+        }
+        
+        public get wheelPrecision() {
+            var mousewheel = <ArcRotateCameraMouseWheelInput>this.inputs.attached["mousewheel"];
+            if (mousewheel)
+                return mousewheel.wheelPrecision;
+        }
+        
+        public set wheelPrecision(value) {
+            var mousewheel = <ArcRotateCameraMouseWheelInput>this.inputs.attached["mousewheel"];
+            if (mousewheel)
+                mousewheel.wheelPrecision = value;
+        }
+        
+        //-- end properties for backward compatibility for inputs        
 
         @serialize()
         public zoomOnFactor = 1;
 
         public targetScreenOffset = Vector2.Zero();
-        public pinchInwards = true;
-
+        
         @serialize()
         public allowUpsideDown = true;
 
-        private _keys = [];
         public _viewMatrix = new Matrix();
-        private _attachedElement: HTMLElement;
-
-        private _onContextMenu: (e: PointerEvent) => void;
-        private _onPointerDown: (e: PointerEvent) => void;
-        private _onPointerUp: (e: PointerEvent) => void;
-        private _onPointerMove: (e: PointerEvent) => void;
-        private _wheel: (e: MouseWheelEvent) => void;
-        private _onMouseMove: (e: MouseEvent) => any;
-        private _onKeyDown: (e: KeyboardEvent) => any;
-        private _onKeyUp: (e: KeyboardEvent) => any;
-        private _onLostFocus: (e: FocusEvent) => any;
+        public _attachedElement: HTMLElement;
+        public _noPreventDefault : boolean;
+        public _useCtrlForPanning : boolean;
+        public inputs : ArcRotateCameraInputsManager;
+        
         public _reset: () => void;
-        private _onGestureStart: (e: PointerEvent) => void;
-        private _onGesture: (e: MSGestureEvent) => void;
-        private _MSGestureHandler: MSGesture;
-
+        
         // Panning
         public panningAxis: Vector3 = new Vector3(1, 1, 0);
         private _localDirection: Vector3;
         private _transformedDirection: Vector3;
-        private _isRightClick: boolean = false;
-        private _isCtrlPushed: boolean = false;
 
         // Collisions
         public onCollide: (collidedMesh: AbstractMesh) => void;
@@ -122,19 +195,6 @@
         //due to async collision inspection
         private _collisionTriggered: boolean;
         
-        //deprecated angularSensibility support
-        public get angularSensibility() {
-            Tools.Warn("Warning: angularSensibility is deprecated, use angularSensibilityX and angularSensibilityY instead.");
-            return Math.max(this.angularSensibilityX, this.angularSensibilityY);
-        }
-        
-        //deprecated angularSensibility support
-        public set angularSensibility(value) {
-            Tools.Warn("Warning: angularSensibility is deprecated, use angularSensibilityX and angularSensibilityY instead.");
-            this.angularSensibilityX = value;
-            this.angularSensibilityY = value;
-        }
-
         constructor(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene) {
             super(name, Vector3.Zero(), scene);
 
@@ -149,6 +209,8 @@
             this.radius = radius;
 
             this.getViewMatrix();
+            this.inputs = new ArcRotateCameraInputsManager(this);
+            this.inputs.addKeyboard().addMouseWheel().addPointers().addGamepad();
         }
 
         // Cache
@@ -195,234 +257,21 @@
 
         // Methods
         public attachControl(element: HTMLElement, noPreventDefault?: boolean, useCtrlForPanning: boolean = true): void {
-            var cacheSoloPointer; // cache pointer object for better perf on camera rotation
-            var previousPinchDistance = 0;
-            var pointers = new SmartCollection();
-
             if (this._attachedElement) {
                 return;
             }
             this._attachedElement = element;
+            this._noPreventDefault = noPreventDefault;
+            this._useCtrlForPanning = useCtrlForPanning;
 
-            var engine = this.getEngine();
-
-            if (this._onPointerDown === undefined) {
-                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._onContextMenu = evt => {
-                    evt.preventDefault();
-                };
-
-                this._onPointerMove = evt => {
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-
-                    switch (pointers.count) {
-
-                        case 1: //normal camera rotation
-                            if (this.panningSensibility !== 0 && ((this._isCtrlPushed && useCtrlForPanning) || (!useCtrlForPanning && this._isRightClick))) {
-                                this.inertialPanningX += -(evt.clientX - cacheSoloPointer.x) / this.panningSensibility;
-                                this.inertialPanningY += (evt.clientY - cacheSoloPointer.y) / this.panningSensibility;
-                            } else {
-                                var offsetX = evt.clientX - cacheSoloPointer.x;
-                                var offsetY = evt.clientY - cacheSoloPointer.y;
-                                this.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
-                                this.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.inertialRadiusOffset += (pinchSquaredDistance - previousPinchDistance) / (this.pinchPrecision * this.wheelPrecision * ((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;
-                    }
-
-                    var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                    var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-
-                    this.inertialAlphaOffset -= offsetX / this.angularSensibilityX;
-                    this.inertialBetaOffset -= offsetY / this.angularSensibilityY;
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-
-                this._wheel = event => {
-                    var delta = 0;
-                    if (event.wheelDelta) {
-                        delta = event.wheelDelta / (this.wheelPrecision * 40);
-                    } else if (event.detail) {
-                        delta = -event.detail / this.wheelPrecision;
-                    }
-
-                    if (delta)
-                        this.inertialRadiusOffset += delta;
-
-                    if (event.preventDefault) {
-                        if (!noPreventDefault) {
-                            event.preventDefault();
-                        }
-                    }
-                };
-
-                this._onKeyDown = evt => {
-                    this._isCtrlPushed = evt.ctrlKey;
-                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = this._keys.indexOf(evt.keyCode);
-
-                        if (index === -1) {
-                            this._keys.push(evt.keyCode);
-                        }
-
-                        if (evt.preventDefault) {
-                            if (!noPreventDefault) {
-                                evt.preventDefault();
-                            }
-                        }
-                    }
-                };
-
-                this._onKeyUp = evt => {
-                    this._isCtrlPushed = evt.ctrlKey;
-                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = this._keys.indexOf(evt.keyCode);
-
-                        if (index >= 0) {
-                            this._keys.splice(index, 1);
-                        }
-
-                        if (evt.preventDefault) {
-                            if (!noPreventDefault) {
-                                evt.preventDefault();
-                            }
-                        }
-                    }
-                };
-
-                this._onLostFocus = () => {
-                    this._keys = [];
-                    pointers.empty();
-                    previousPinchDistance = 0;
-                    cacheSoloPointer = null;
-                };
-
-                this._onGestureStart = e => {
-                    if (window.MSGesture === undefined) {
-                        return;
-                    }
-
-                    if (!this._MSGestureHandler) {
-                        this._MSGestureHandler = new MSGesture();
-                        this._MSGestureHandler.target = element;
-                    }
-
-                    this._MSGestureHandler.addPointer(e.pointerId);
-                };
-
-                this._onGesture = e => {
-                    this.radius *= e.scale;
-
-
-                    if (e.preventDefault) {
-                        if (!noPreventDefault) {
-                            e.stopPropagation();
-                            e.preventDefault();
-                        }
-                    }
-                };
+            this.inputs.attachElement(element, noPreventDefault);
+            
 
                 this._reset = () => {
-                    this._keys = [];
                     this.inertialAlphaOffset = 0;
                     this.inertialBetaOffset = 0;
                     this.inertialRadiusOffset = 0;
-                    pointers.empty();
-                    previousPinchDistance = 0;
-                    cacheSoloPointer = null;
                 };
-
-
-            }
-
-            if (!useCtrlForPanning) {
-                element.addEventListener("contextmenu", this._onContextMenu, false);
-            }
-            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);
-            element.addEventListener('mousewheel', this._wheel, false);
-            element.addEventListener('DOMMouseScroll', this._wheel, false);
-
-            Tools.RegisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
         }
 
         public detachControl(element: HTMLElement): void {
@@ -430,24 +279,7 @@
                 return;
             }
 
-            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);
-            element.removeEventListener('mousewheel', this._wheel);
-            element.removeEventListener('DOMMouseScroll', this._wheel);
-
-            Tools.UnregisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
-
-            this._MSGestureHandler = null;
+            this.inputs.detachElement(this._attachedElement);
             this._attachedElement = null;
 
             if (this._reset) {
@@ -460,19 +292,8 @@
             if (this._collisionTriggered) {
                 return;
             }
-            // Keyboard
-            for (var index = 0; index < this._keys.length; index++) {
-                var keyCode = this._keys[index];
-                if (this.keysLeft.indexOf(keyCode) !== -1) {
-                    this.inertialAlphaOffset -= 0.01;
-                } else if (this.keysUp.indexOf(keyCode) !== -1) {
-                    this.inertialBetaOffset -= 0.01;
-                } else if (this.keysRight.indexOf(keyCode) !== -1) {
-                    this.inertialAlphaOffset += 0.01;
-                } else if (this.keysDown.indexOf(keyCode) !== -1) {
-                    this.inertialBetaOffset += 0.01;
-                }
-            }			
+            
+            this.inputs.checkInputs();            
 			
             // Inertia
             if (this.inertialAlphaOffset !== 0 || this.inertialBetaOffset !== 0 || this.inertialRadiusOffset !== 0) {
@@ -740,6 +561,11 @@
             super._updateRigCameras();
         }
 
+        public dispose(): void {
+            this.inputs.clear();
+            super.dispose();
+        }
+        
         public getTypeName(): string {
             return "ArcRotateCamera";
         }

+ 38 - 0
src/Cameras/babylon.arcRotateCameraInputsManager.js

@@ -0,0 +1,38 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    var ArcRotateCameraInputsManager = (function (_super) {
+        __extends(ArcRotateCameraInputsManager, _super);
+        function ArcRotateCameraInputsManager(camera) {
+            _super.call(this, camera);
+        }
+        ArcRotateCameraInputsManager.prototype.add = function (input) {
+            _super.prototype.add.call(this, input);
+            if (this.camera._attachedElement && input.attachElement) {
+                input.attachElement(this.camera._attachedElement, this.camera._noPreventDefault);
+            }
+        };
+        ArcRotateCameraInputsManager.prototype.addMouseWheel = function () {
+            this.add(new BABYLON.ArcRotateCameraMouseWheelInput());
+            return this;
+        };
+        ArcRotateCameraInputsManager.prototype.addPointers = function () {
+            this.add(new BABYLON.ArcRotateCameraPointersInput());
+            return this;
+        };
+        ArcRotateCameraInputsManager.prototype.addKeyboard = function () {
+            this.add(new BABYLON.ArcRotateCameraKeyboardMoveInput());
+            return this;
+        };
+        ArcRotateCameraInputsManager.prototype.addGamepad = function () {
+            this.add(new BABYLON.ArcRotateCameraGamepadInput());
+            return this;
+        };
+        return ArcRotateCameraInputsManager;
+    })(BABYLON.CameraInputsManager);
+    BABYLON.ArcRotateCameraInputsManager = ArcRotateCameraInputsManager;
+})(BABYLON || (BABYLON = {}));

+ 34 - 0
src/Cameras/babylon.arcRotateCameraInputsManager.ts

@@ -0,0 +1,34 @@
+module BABYLON {
+    export class ArcRotateCameraInputsManager extends CameraInputsManager<ArcRotateCamera> {
+        constructor(camera : ArcRotateCamera){
+            super(camera);    
+        }
+        
+        add(input: ICameraInput<ArcRotateCamera>){
+            super.add(input);
+            if (this.camera._attachedElement && input.attachElement) {
+                input.attachElement(this.camera._attachedElement, this.camera._noPreventDefault);
+            }
+        }
+        
+        public addMouseWheel(){
+            this.add(new ArcRotateCameraMouseWheelInput());
+            return this;
+        }
+        
+        public addPointers(){
+            this.add(new ArcRotateCameraPointersInput());
+            return this;
+        }
+        
+        public addKeyboard(){
+            this.add(new ArcRotateCameraKeyboardMoveInput());
+            return this;
+        }
+        
+        public addGamepad(){
+            this.add(new ArcRotateCameraGamepadInput());
+            return this;
+        }
+    }
+}

+ 23 - 0
src/Cameras/babylon.camera.js

@@ -115,6 +115,21 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        Camera.prototype.toString = function (fullDetails) {
+            var ret = "Name: " + this.name;
+            ret += ", type: " + this.getTypeName();
+            if (this.animations) {
+                for (var i = 0; i < this.animations.length; i++) {
+                    ret += ", animation[0]: " + this.animations[i].toString(fullDetails);
+                }
+            }
+            if (fullDetails) {
+            }
+            return ret;
+        };
         Object.defineProperty(Camera.prototype, "globalPosition", {
             get: function () {
                 return this._globalPosition;
@@ -460,6 +475,9 @@ var BABYLON;
             if (this.parent) {
                 serializationObject.parentId = this.parent.id;
             }
+            if (this.inputs) {
+                this.inputs.serialize(serializationObject);
+            }
             // Animations
             BABYLON.Animation.AppendSerializedAnimations(this, serializationObject);
             serializationObject.ranges = this.serializeAnimationRanges();
@@ -523,6 +541,10 @@ var BABYLON;
             if (parsedCamera.parentId) {
                 camera._waitingParentId = parsedCamera.parentId;
             }
+            //If camera has an input manager, let it parse inputs settings
+            if (camera.inputs) {
+                camera.inputs.parse(parsedCamera);
+            }
             // Target
             if (parsedCamera.target) {
                 if (camera.setTarget) {
@@ -558,6 +580,7 @@ var BABYLON;
         Camera._RIG_MODE_STEREOSCOPIC_SIDEBYSIDE_CROSSEYED = 12;
         Camera._RIG_MODE_STEREOSCOPIC_OVERUNDER = 13;
         Camera._RIG_MODE_VR = 20;
+        Camera.ForceAttachControlToAlwaysPreventDefault = false;
         __decorate([
             BABYLON.serializeAsVector3()
         ], Camera.prototype, "position", void 0);

+ 33 - 3
src/Cameras/babylon.camera.ts

@@ -1,5 +1,7 @@
 module BABYLON {
     export class Camera extends Node {
+        public inputs: CameraInputsManager<Camera>;
+        
         // Statics
         private static _PERSPECTIVE_CAMERA = 0;
         private static _ORTHOGRAPHIC_CAMERA = 1;
@@ -53,6 +55,8 @@
         public static get RIG_MODE_VR(): number {
             return Camera._RIG_MODE_VR;
         }
+
+        public static ForceAttachControlToAlwaysPreventDefault = false;
         
         // Members
         @serializeAsVector3()
@@ -134,6 +138,22 @@
             this.position = position;
         }
 
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        public toString(fullDetails?: boolean): string {
+            var ret = "Name: " + this.name;
+            ret += ", type: " + this.getTypeName();
+            if (this.animations) {
+                for (var i = 0; i < this.animations.length; i++) {
+                    ret += ", animation[0]: " + this.animations[i].toString(fullDetails);
+                }
+            }
+            if (fullDetails) {
+            }
+            return ret;
+        }
+
         public get globalPosition(): Vector3 {
             return this._globalPosition;
         }
@@ -567,6 +587,10 @@
             if (this.parent) {
                 serializationObject.parentId = this.parent.id;
             }
+
+            if (this.inputs) {
+                this.inputs.serialize(serializationObject);
+            }
             // Animations
             Animation.AppendSerializedAnimations(this, serializationObject);
             serializationObject.ranges = this.serializeAnimationRanges();
@@ -591,7 +615,7 @@
                 case "FollowCamera":
                     return () => new FollowCamera(name, Vector3.Zero(), scene);
                 case "ArcFollowCamera":
-                    return () => new ArcFollowCamera(name, 0, 0, 1.0, null, scene);                    
+                    return () => new ArcFollowCamera(name, 0, 0, 1.0, null, scene);
                 case "GamepadCamera":
                     return () => new GamepadCamera(name, Vector3.Zero(), scene);
                 case "TouchCamera":
@@ -628,14 +652,19 @@
         public static Parse(parsedCamera: any, scene: Scene): Camera {
             var type = parsedCamera.type;
             var construct = Camera.GetConstructorFromName(type, parsedCamera.name, scene, parsedCamera.interaxial_distance, parsedCamera.isStereoscopicSideBySide);
-           
+
             var camera = SerializationHelper.Parse(construct, parsedCamera, scene);
 
             // Parent
             if (parsedCamera.parentId) {
                 camera._waitingParentId = parsedCamera.parentId;
             }
-
+            
+            //If camera has an input manager, let it parse inputs settings
+            if (camera.inputs) {
+                camera.inputs.parse(parsedCamera);
+            }
+            
             // Target
             if (parsedCamera.target) {
                 if ((<any>camera).setTarget) {
@@ -669,3 +698,4 @@
 }
 
 
+

+ 115 - 0
src/Cameras/babylon.cameraInputsManager.js

@@ -0,0 +1,115 @@
+var BABYLON;
+(function (BABYLON) {
+    BABYLON.CameraInputTypes = {};
+    var CameraInputsManager = (function () {
+        function CameraInputsManager(camera) {
+            this.attached = {};
+            this.camera = camera;
+            this.checkInputs = function () { };
+        }
+        CameraInputsManager.prototype.add = function (input) {
+            var type = input.getSimpleName();
+            if (this.attached[type]) {
+                BABYLON.Tools.Warn("camera input of type " + type + " already exists on camera");
+                return;
+            }
+            this.attached[type] = input;
+            input.attachCamera(this.camera);
+            //for checkInputs, we are dynamically creating a function
+            //the goal is to avoid the performance penalty of looping for inputs in the render loop
+            if (input.checkInputs) {
+                this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
+            }
+        };
+        CameraInputsManager.prototype.remove = function (inputToRemove) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input == inputToRemove) {
+                    input.detach();
+                    delete this.attached[cam];
+                }
+            }
+        };
+        CameraInputsManager.prototype.removeByType = function (inputType) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.getTypeName() == inputType) {
+                    input.detach();
+                    delete this.attached[cam];
+                }
+            }
+        };
+        CameraInputsManager.prototype._addCheckInputs = function (fn) {
+            var current = this.checkInputs;
+            return function () {
+                current();
+                fn();
+            };
+        };
+        CameraInputsManager.prototype.attachElement = function (element, noPreventDefault) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.attachElement)
+                    this.attached[cam].attachElement(element, noPreventDefault);
+            }
+        };
+        CameraInputsManager.prototype.detachElement = function (element) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.detachElement)
+                    this.attached[cam].detachElement(element);
+            }
+        };
+        CameraInputsManager.prototype.rebuildInputCheck = function (element) {
+            this.checkInputs = function () { };
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.checkInputs) {
+                    this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
+                }
+            }
+        };
+        CameraInputsManager.prototype.clear = function () {
+            for (var cam in this.attached) {
+                this.attached[cam].detach();
+            }
+            this.attached = {};
+            this.checkInputs = function () { };
+        };
+        CameraInputsManager.prototype.serialize = function (serializedCamera) {
+            var inputs = {};
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                var res = BABYLON.SerializationHelper.Serialize(input, serializedCamera);
+                inputs[input.getTypeName()] = res;
+            }
+            serializedCamera.inputsmgr = inputs;
+        };
+        CameraInputsManager.prototype.parse = function (parsedCamera) {
+            var parsedInputs = parsedCamera.inputsmgr;
+            if (parsedInputs) {
+                this.clear();
+                for (var n in parsedInputs) {
+                    var construct = BABYLON.CameraInputTypes[n];
+                    if (construct) {
+                        var parsedinput = parsedInputs[n];
+                        var input = BABYLON.SerializationHelper.Parse(function () { return new construct(); }, parsedinput, null);
+                        this.add(input);
+                    }
+                }
+            }
+            else {
+                //2016-03-08 this part is for managing backward compatibility
+                for (var n in this.attached) {
+                    var construct = BABYLON.CameraInputTypes[this.attached[n].getTypeName()];
+                    if (construct) {
+                        var input = BABYLON.SerializationHelper.Parse(function () { return new construct(); }, parsedCamera, null);
+                        this.add(input);
+                    }
+                }
+            }
+        };
+        return CameraInputsManager;
+    })();
+    BABYLON.CameraInputsManager = CameraInputsManager;
+})(BABYLON || (BABYLON = {}));

+ 150 - 0
src/Cameras/babylon.cameraInputsManager.ts

@@ -0,0 +1,150 @@
+module BABYLON {
+    export var CameraInputTypes = {};
+
+    export interface ICameraInput<TCamera extends BABYLON.Camera> {
+        camera: TCamera;
+        attachCamera(camera: TCamera);
+        detach();
+        getTypeName(): string;
+        getSimpleName(): string;
+
+        attachElement?: (element: HTMLElement, noPreventDefault?: boolean) => void;
+        detachElement?: (element: HTMLElement) => void;
+        checkInputs?: () => void;
+    }
+
+    export interface CameraInputsMap<TCamera extends BABYLON.Camera> {
+        [name: string]: ICameraInput<TCamera>;
+        [idx: number]: ICameraInput<TCamera>;
+    }
+
+    export class CameraInputsManager<TCamera extends BABYLON.Camera> {
+        attached: CameraInputsMap<TCamera>;
+        camera: TCamera;
+        checkInputs: () => void;
+
+        constructor(camera: TCamera) {
+            this.attached = {};
+            this.camera = camera;
+            this.checkInputs = () => { };
+        }
+
+        public add(input: ICameraInput<TCamera>) {
+            var type = input.getSimpleName();
+            if (this.attached[type]) {
+                Tools.Warn("camera input of type " + type + " already exists on camera");
+                return;
+            }
+
+            this.attached[type] = input;
+            input.attachCamera(this.camera);
+            
+            //for checkInputs, we are dynamically creating a function
+            //the goal is to avoid the performance penalty of looping for inputs in the render loop
+            if (input.checkInputs) {
+                this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
+            }
+        }
+
+        public remove(inputToRemove: ICameraInput<TCamera>) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input == inputToRemove) {
+                    input.detach();
+                    delete this.attached[cam];
+                }
+            }
+        }
+
+        public removeByType(inputType: string) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.getTypeName() == inputType) {
+                    input.detach();
+                    delete this.attached[cam];
+                }
+            }
+        }
+
+        private _addCheckInputs(fn) {
+            var current = this.checkInputs;
+            return () => {
+                current();
+                fn();
+            }
+        }
+
+        public attachElement(element: HTMLElement, noPreventDefault?: boolean) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.attachElement)
+                    this.attached[cam].attachElement(element, noPreventDefault);
+            }
+        }
+
+        public detachElement(element: HTMLElement) {
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.detachElement)
+                    this.attached[cam].detachElement(element);
+            }
+        }
+
+        public rebuildInputCheck(element: HTMLElement) {
+            this.checkInputs = () => { };
+
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                if (input.checkInputs) {
+                    this.checkInputs = this._addCheckInputs(input.checkInputs.bind(input));
+                }
+            }
+        }
+
+        public clear() {
+            for (var cam in this.attached) {
+                this.attached[cam].detach();
+            }
+
+            this.attached = {};
+            this.checkInputs = () => { };
+        }
+
+        public serialize(serializedCamera) {
+            var inputs = {};
+            for (var cam in this.attached) {
+                var input = this.attached[cam];
+                var res = SerializationHelper.Serialize(input, serializedCamera);
+                inputs[input.getTypeName()] = res;
+            }
+
+            serializedCamera.inputsmgr = inputs;
+        }
+
+        public parse(parsedCamera) {
+            var parsedInputs = parsedCamera.inputsmgr;
+            if (parsedInputs) {
+                this.clear();
+
+                for (var n in parsedInputs) {
+                    var construct = CameraInputTypes[n];
+                    if (construct) {
+                        var parsedinput = parsedInputs[n];
+                        var input = SerializationHelper.Parse(() => { return new construct() }, parsedinput, null);
+                        this.add(input as any);
+                    }
+                }
+            } else { 
+                //2016-03-08 this part is for managing backward compatibility
+                for (var n in this.attached) {
+                    var construct = CameraInputTypes[this.attached[n].getTypeName()];
+                    if (construct) {
+                        var input = SerializationHelper.Parse(() => { return new construct() }, parsedCamera, null);
+                        this.add(input as any);
+                    }
+                }
+            }
+        }
+    }
+} 
+

+ 31 - 66
src/Cameras/babylon.deviceOrientationCamera.js

@@ -3,83 +3,48 @@ var __extends = (this && this.__extends) || function (d, b) {
     function __() { this.constructor = d; }
     d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
 };
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
 var BABYLON;
 (function (BABYLON) {
     // We're mainly based on the logic defined into the FreeCamera code
     var DeviceOrientationCamera = (function (_super) {
         __extends(DeviceOrientationCamera, _super);
+        //-- end properties for backward compatibility for inputs
         function DeviceOrientationCamera(name, position, scene) {
-            var _this = this;
             _super.call(this, name, position, scene);
-            this._offsetX = null;
-            this._offsetY = null;
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
-            this.angularSensibility = 10000.0;
-            this.moveSensibility = 50.0;
-            window.addEventListener("resize", function () {
-                _this._initialOrientationGamma = null;
-            }, false);
+            this.inputs.addDeviceOrientation();
         }
-        DeviceOrientationCamera.prototype.attachControl = function (canvas, noPreventDefault) {
-            var _this = this;
-            if (this._attachedCanvas) {
-                return;
-            }
-            this._attachedCanvas = canvas;
-            if (!this._orientationChanged) {
-                this._orientationChanged = function (evt) {
-                    if (!_this._initialOrientationGamma) {
-                        _this._initialOrientationGamma = evt.gamma;
-                        _this._initialOrientationBeta = evt.beta;
-                    }
-                    _this._orientationGamma = evt.gamma;
-                    _this._orientationBeta = evt.beta;
-                    _this._offsetY = (_this._initialOrientationBeta - _this._orientationBeta);
-                    _this._offsetX = (_this._initialOrientationGamma - _this._orientationGamma);
-                };
-            }
-            window.addEventListener("deviceorientation", this._orientationChanged);
-        };
-        DeviceOrientationCamera.prototype.detachControl = function (canvas) {
-            if (this._attachedCanvas !== canvas) {
-                return;
-            }
-            window.removeEventListener("deviceorientation", this._orientationChanged);
-            this._attachedCanvas = null;
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
-        };
-        DeviceOrientationCamera.prototype._checkInputs = function () {
-            if (!this._offsetX) {
-                return;
-            }
-            this.cameraRotation.y -= this._offsetX / this.angularSensibility;
-            var speed = this._computeLocalCameraSpeed();
-            var direction = new BABYLON.Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
-            BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, 0, this._cameraRotationMatrix);
-            this.cameraDirection.addInPlace(BABYLON.Vector3.TransformCoordinates(direction, this._cameraRotationMatrix));
-            _super.prototype._checkInputs.call(this);
-        };
+        Object.defineProperty(DeviceOrientationCamera.prototype, "angularSensibility", {
+            //-- Begin properties for backward compatibility for inputs
+            get: function () {
+                var gamepad = this.inputs.attached["deviceOrientation"];
+                if (gamepad)
+                    return gamepad.angularSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["deviceOrientation"];
+                if (gamepad)
+                    gamepad.angularSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(DeviceOrientationCamera.prototype, "moveSensibility", {
+            get: function () {
+                var gamepad = this.inputs.attached["deviceOrientation"];
+                if (gamepad)
+                    return gamepad.moveSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["deviceOrientation"];
+                if (gamepad)
+                    gamepad.moveSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         DeviceOrientationCamera.prototype.getTypeName = function () {
             return "DeviceOrientationCamera";
         };
-        __decorate([
-            BABYLON.serialize()
-        ], DeviceOrientationCamera.prototype, "angularSensibility", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], DeviceOrientationCamera.prototype, "moveSensibility", void 0);
         return DeviceOrientationCamera;
     })(BABYLON.FreeCamera);
     BABYLON.DeviceOrientationCamera = DeviceOrientationCamera;

+ 26 - 72
src/Cameras/babylon.deviceOrientationCamera.ts

@@ -1,81 +1,35 @@
 module BABYLON {
     // We're mainly based on the logic defined into the FreeCamera code
     export class DeviceOrientationCamera extends FreeCamera {
-        private _offsetX: number = null;
-        private _offsetY: number = null;
-        private _orientationGamma: number = 0;
-        private _orientationBeta: number = 0;
-        private _initialOrientationGamma: number = 0;
-        private _initialOrientationBeta: number = 0;
-        private _attachedCanvas: HTMLCanvasElement;
-        private _orientationChanged: (e: DeviceOrientationEvent) => any;
-
-        @serialize()
-        public angularSensibility: number = 10000.0;
-
-        @serialize()
-        public moveSensibility: number = 50.0;
-
-        constructor(name: string, position: Vector3, scene: Scene) {
-            super(name, position, scene);
-
-            window.addEventListener("resize", () => {
-                this._initialOrientationGamma = null;
-            }, false);
+        //-- Begin properties for backward compatibility for inputs
+        public get angularSensibility() {
+            var gamepad = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
+            if (gamepad)
+                return gamepad.angularSensibility;
         }
-
-        public attachControl(canvas: HTMLCanvasElement, noPreventDefault: boolean): void {
-            if (this._attachedCanvas) {
-                return;
-            }
-            this._attachedCanvas = canvas;
-
-            if (!this._orientationChanged) {
-                this._orientationChanged = (evt) => {
-
-                    if (!this._initialOrientationGamma) {
-                            this._initialOrientationGamma = evt.gamma;
-                            this._initialOrientationBeta = evt.beta;
-                    }
-
-                    this._orientationGamma = evt.gamma;
-                    this._orientationBeta = evt.beta;
- 
-                    this._offsetY = (this._initialOrientationBeta - this._orientationBeta);
-                    this._offsetX = (this._initialOrientationGamma - this._orientationGamma);
-                };
-            }
-
-            window.addEventListener("deviceorientation", this._orientationChanged);
+        
+        public set angularSensibility(value) {
+            var gamepad = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
+            if (gamepad)
+                gamepad.angularSensibility = value;
         }
-
-        public detachControl(canvas: HTMLCanvasElement): void {
-            if (this._attachedCanvas !== canvas) {
-                return;
-            }
-
-            window.removeEventListener("deviceorientation", this._orientationChanged);
-
-            this._attachedCanvas = null;
-            this._orientationGamma = 0;
-            this._orientationBeta = 0;
-            this._initialOrientationGamma = 0;
-            this._initialOrientationBeta = 0;
+        
+        public get moveSensibility() {
+            var gamepad = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
+            if (gamepad)
+                return gamepad.moveSensibility;
         }
-
-        public _checkInputs(): void {
-            if (!this._offsetX) {
-                return;
-            }
-            this.cameraRotation.y -= this._offsetX / this.angularSensibility;
-
-            var speed = this._computeLocalCameraSpeed();
-            var direction = new Vector3(0, 0, speed * this._offsetY / this.moveSensibility);
-
-            Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, 0, this._cameraRotationMatrix);
-            this.cameraDirection.addInPlace(Vector3.TransformCoordinates(direction, this._cameraRotationMatrix));
-
-            super._checkInputs();
+        
+        public set moveSensibility(value) {
+            var gamepad = <FreeCameraDeviceOrientationInput>this.inputs.attached["deviceOrientation"];
+            if (gamepad)
+                gamepad.moveSensibility = value;
+        }
+        //-- end properties for backward compatibility for inputs
+        
+        constructor(name: string, position: Vector3, scene: Scene) {
+            super(name, position, scene);
+            this.inputs.addDeviceOrientation();
         }
 
         public getTypeName(): string {

+ 84 - 150
src/Cameras/babylon.freeCamera.js

@@ -17,14 +17,8 @@ var BABYLON;
             var _this = this;
             _super.call(this, name, position, scene);
             this.ellipsoid = new BABYLON.Vector3(0.5, 1, 0.5);
-            this.keysUp = [38];
-            this.keysDown = [40];
-            this.keysLeft = [37];
-            this.keysRight = [39];
             this.checkCollisions = false;
             this.applyGravity = false;
-            this.angularSensibility = 2000.0;
-            this._keys = [];
             this._collider = new BABYLON.Collider();
             this._needMoveForGravity = false;
             this._oldPosition = BABYLON.Vector3.Zero();
@@ -48,128 +42,98 @@ var BABYLON;
                 };
                 updatePosition(newPosition);
             };
+            this.inputs = new BABYLON.FreeCameraInputsManager(this);
+            this.inputs.addKeyboard().addMouse();
         }
-        FreeCamera.prototype._onLostFocus = function (e) {
-            this._keys = [];
-        };
+        Object.defineProperty(FreeCamera.prototype, "angularSensibility", {
+            //-- begin properties for backward compatibility for inputs
+            get: function () {
+                var mouse = this.inputs.attached["mouse"];
+                if (mouse)
+                    return mouse.angularSensibility;
+            },
+            set: function (value) {
+                var mouse = this.inputs.attached["mouse"];
+                if (mouse)
+                    mouse.angularSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(FreeCamera.prototype, "keysUp", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysUp;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysUp = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(FreeCamera.prototype, "keysDown", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysDown;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysDown = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(FreeCamera.prototype, "keysLeft", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysLeft;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysLeft = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(FreeCamera.prototype, "keysRight", {
+            get: function () {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    return keyboard.keysRight;
+            },
+            set: function (value) {
+                var keyboard = this.inputs.attached["keyboard"];
+                if (keyboard)
+                    keyboard.keysRight = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         // Controls
         FreeCamera.prototype.attachControl = function (element, noPreventDefault) {
-            var _this = this;
-            var previousPosition;
-            var engine = this.getEngine();
             if (this._attachedElement) {
                 return;
             }
+            this._noPreventDefault = noPreventDefault;
             this._attachedElement = element;
-            if (this._onMouseDown === undefined) {
-                this._onMouseDown = function (evt) {
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseUp = function (evt) {
-                    previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseOut = function (evt) {
-                    previousPosition = null;
-                    _this._keys = [];
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onMouseMove = function (evt) {
-                    if (!previousPosition && !engine.isPointerLock) {
-                        return;
-                    }
-                    var offsetX;
-                    var offsetY;
-                    if (!engine.isPointerLock) {
-                        offsetX = evt.clientX - previousPosition.x;
-                        offsetY = evt.clientY - previousPosition.y;
-                    }
-                    else {
-                        offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                        offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-                    }
-                    _this.cameraRotation.y += offsetX / _this.angularSensibility;
-                    _this.cameraRotation.x += offsetY / _this.angularSensibility;
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-                this._onKeyDown = function (evt) {
-                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = _this._keys.indexOf(evt.keyCode);
-                        if (index === -1) {
-                            _this._keys.push(evt.keyCode);
-                        }
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
-                        }
-                    }
-                };
-                this._onKeyUp = function (evt) {
-                    if (_this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        _this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = _this._keys.indexOf(evt.keyCode);
-                        if (index >= 0) {
-                            _this._keys.splice(index, 1);
-                        }
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
-                        }
-                    }
-                };
-                this._reset = function () {
-                    _this._keys = [];
-                    previousPosition = null;
-                    _this.cameraDirection = new BABYLON.Vector3(0, 0, 0);
-                    _this.cameraRotation = new BABYLON.Vector2(0, 0);
-                };
-            }
-            element.addEventListener("mousedown", this._onMouseDown, false);
-            element.addEventListener("mouseup", this._onMouseUp, false);
-            element.addEventListener("mouseout", this._onMouseOut, false);
-            element.addEventListener("mousemove", this._onMouseMove, false);
-            BABYLON.Tools.RegisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
+            noPreventDefault = BABYLON.Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
+            this.inputs.attachElement(element, noPreventDefault);
         };
         FreeCamera.prototype.detachControl = function (element) {
             if (this._attachedElement !== element) {
                 return;
             }
-            element.removeEventListener("mousedown", this._onMouseDown);
-            element.removeEventListener("mouseup", this._onMouseUp);
-            element.removeEventListener("mouseout", this._onMouseOut);
-            element.removeEventListener("mousemove", this._onMouseMove);
-            BABYLON.Tools.UnregisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
+            this.inputs.detachElement(this._attachedElement);
             this._attachedElement = null;
-            if (this._reset) {
-                this._reset();
-            }
+            this.cameraDirection = new BABYLON.Vector3(0, 0, 0);
+            this.cameraRotation = new BABYLON.Vector2(0, 0);
         };
         FreeCamera.prototype._collideWithWorld = function (velocity) {
             var globalPosition;
@@ -195,26 +159,7 @@ var BABYLON;
                 this._localDirection = BABYLON.Vector3.Zero();
                 this._transformedDirection = BABYLON.Vector3.Zero();
             }
-            // Keyboard
-            for (var index = 0; index < this._keys.length; index++) {
-                var keyCode = this._keys[index];
-                var speed = this._computeLocalCameraSpeed();
-                if (this.keysLeft.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(-speed, 0, 0);
-                }
-                else if (this.keysUp.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(0, 0, speed);
-                }
-                else if (this.keysRight.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(speed, 0, 0);
-                }
-                else if (this.keysDown.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(0, 0, -speed);
-                }
-                this.getViewMatrix().invertToRef(this._cameraTransformMatrix);
-                BABYLON.Vector3.TransformNormalToRef(this._localDirection, this._cameraTransformMatrix, this._transformedDirection);
-                this.cameraDirection.addInPlace(this._transformedDirection);
-            }
+            this.inputs.checkInputs();
             _super.prototype._checkInputs.call(this);
         };
         FreeCamera.prototype._decideIfNeedsToMove = function () {
@@ -228,6 +173,10 @@ var BABYLON;
                 this.position.addInPlace(this.cameraDirection);
             }
         };
+        FreeCamera.prototype.dispose = function () {
+            this.inputs.clear();
+            _super.prototype.dispose.call(this);
+        };
         FreeCamera.prototype.getTypeName = function () {
             return "FreeCamera";
         };
@@ -236,25 +185,10 @@ var BABYLON;
         ], FreeCamera.prototype, "ellipsoid", void 0);
         __decorate([
             BABYLON.serialize()
-        ], FreeCamera.prototype, "keysUp", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCamera.prototype, "keysDown", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCamera.prototype, "keysLeft", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCamera.prototype, "keysRight", void 0);
-        __decorate([
-            BABYLON.serialize()
         ], FreeCamera.prototype, "checkCollisions", void 0);
         __decorate([
             BABYLON.serialize()
         ], FreeCamera.prototype, "applyGravity", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], FreeCamera.prototype, "angularSensibility", void 0);
         return FreeCamera;
     })(BABYLON.TargetCamera);
     BABYLON.FreeCamera = FreeCamera;

+ 90 - 176
src/Cameras/babylon.freeCamera.ts

@@ -1,193 +1,120 @@
 module BABYLON {
-    export class FreeCamera extends TargetCamera {
+    export class FreeCamera extends TargetCamera {        
         @serializeAsVector3()
         public ellipsoid = new Vector3(0.5, 1, 0.5);
 
         @serialize()
-        public keysUp = [38];
-
-        @serialize()
-        public keysDown = [40];
-
-        @serialize()
-        public keysLeft = [37];
-
-        @serialize()
-        public keysRight = [39];
-
-        @serialize()
         public checkCollisions = false;
 
         @serialize()
         public applyGravity = false;
-
-        @serialize()
-        public angularSensibility = 2000.0;
-
+                
+        public inputs : FreeCameraInputsManager;
+        
+        //-- begin properties for backward compatibility for inputs
+        public get angularSensibility() {
+            var mouse = <FreeCameraMouseInput>this.inputs.attached["mouse"];
+            if (mouse)
+                return mouse.angularSensibility;
+        }
+        
+        public set angularSensibility(value) {
+            var mouse = <FreeCameraMouseInput>this.inputs.attached["mouse"];
+            if (mouse)
+                mouse.angularSensibility = value;
+        }
+        
+        public get keysUp() {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysUp;
+        }
+        
+        public set keysUp(value) {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysUp = value;
+        }
+        
+        public get keysDown() {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysDown;
+        }
+        
+        public set keysDown(value) {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysDown = value;
+        }
+        
+        public get keysLeft() {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysLeft;
+        }
+        
+        public set keysLeft(value) {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysLeft = value;
+        }
+        
+        public get keysRight() {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                return keyboard.keysRight;
+        }
+        
+        public set keysRight(value) {
+            var keyboard = <FreeCameraKeyboardMoveInput>this.inputs.attached["keyboard"];
+            if (keyboard)
+                keyboard.keysRight = value;
+        }
+        
+        //-- end properties for backward compatibility for inputs
+        
         public onCollide: (collidedMesh: AbstractMesh) => void;
-
-        private _keys = [];
+        
         private _collider = new Collider();
         private _needMoveForGravity = false;
         private _oldPosition = Vector3.Zero();
         private _diffPosition = Vector3.Zero();
         private _newPosition = Vector3.Zero();
-        private _attachedElement: HTMLElement;
-        private _localDirection: Vector3;
-        private _transformedDirection: Vector3;
-
-        private _onMouseDown: (e: MouseEvent) => any;
-        private _onMouseUp: (e: MouseEvent) => any;
-        private _onMouseOut: (e: MouseEvent) => any;
-        private _onMouseMove: (e: MouseEvent) => any;
-        private _onKeyDown: (e: KeyboardEvent) => any;
-        private _onKeyUp: (e: KeyboardEvent) => any;        
+        public _attachedElement: HTMLElement;
+        public _noPreventDefault: boolean;
+        
+        public _localDirection: Vector3;
+        public _transformedDirection: Vector3;        
         
         constructor(name: string, position: Vector3, scene: Scene) {
             super(name, position, scene);
-        }
-
-        public _onLostFocus(e: FocusEvent): void {
-            this._keys = [];
+            this.inputs = new FreeCameraInputsManager(this);
+            this.inputs.addKeyboard().addMouse();
         }
 
         // Controls
         public attachControl(element: HTMLElement, noPreventDefault?: boolean): void {
-            var previousPosition;
-            var engine = this.getEngine();
-
             if (this._attachedElement) {
                 return;
             }
+            this._noPreventDefault = noPreventDefault;
             this._attachedElement = element;
+            noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
 
-            if (this._onMouseDown === undefined) {
-                this._onMouseDown = evt => {
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-
-                this._onMouseUp = evt => {
-                    previousPosition = null;
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-
-                this._onMouseOut = evt => {
-                    previousPosition = null;
-                    this._keys = [];
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-
-                this._onMouseMove = evt => {
-                    if (!previousPosition && !engine.isPointerLock) {
-                        return;
-                    }
-
-                    var offsetX;
-                    var offsetY;
-
-                    if (!engine.isPointerLock) {
-                        offsetX = evt.clientX - previousPosition.x;
-                        offsetY = evt.clientY - previousPosition.y;
-                    } else {
-                        offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
-                        offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
-                    }
-
-                    this.cameraRotation.y += offsetX / this.angularSensibility;
-                    this.cameraRotation.x += offsetY / this.angularSensibility;
-
-                    previousPosition = {
-                        x: evt.clientX,
-                        y: evt.clientY
-                    };
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-                };
-
-                this._onKeyDown = evt => {
-                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = this._keys.indexOf(evt.keyCode);
-
-                        if (index === -1) {
-                            this._keys.push(evt.keyCode);
-                        }
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
-                        }
-                    }
-                };
-
-                this._onKeyUp = evt => {
-                    if (this.keysUp.indexOf(evt.keyCode) !== -1 ||
-                        this.keysDown.indexOf(evt.keyCode) !== -1 ||
-                        this.keysLeft.indexOf(evt.keyCode) !== -1 ||
-                        this.keysRight.indexOf(evt.keyCode) !== -1) {
-                        var index = this._keys.indexOf(evt.keyCode);
-
-                        if (index >= 0) {
-                            this._keys.splice(index, 1);
-                        }
-                        if (!noPreventDefault) {
-                            evt.preventDefault();
-                        }
-                    }
-                };
-
-                this._reset = () => {
-                    this._keys = [];
-                    previousPosition = null;
-                    this.cameraDirection = new Vector3(0, 0, 0);
-                    this.cameraRotation = new Vector2(0, 0);
-                };
-            }
-
-            element.addEventListener("mousedown", this._onMouseDown, false);
-            element.addEventListener("mouseup", this._onMouseUp, false);
-            element.addEventListener("mouseout", this._onMouseOut, false);
-            element.addEventListener("mousemove", this._onMouseMove, false);
-
-            Tools.RegisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
-        }
+            this.inputs.attachElement(element, noPreventDefault);
+        }        
 
         public detachControl(element: HTMLElement): void {
             if (this._attachedElement !== element) {
                 return;
             }
 
-            element.removeEventListener("mousedown", this._onMouseDown);
-            element.removeEventListener("mouseup", this._onMouseUp);
-            element.removeEventListener("mouseout", this._onMouseOut);
-            element.removeEventListener("mousemove", this._onMouseMove);
-
-            Tools.UnregisterTopRootEvents([
-                { name: "keydown", handler: this._onKeyDown },
-                { name: "keyup", handler: this._onKeyUp },
-                { name: "blur", handler: this._onLostFocus }
-            ]);
-
+            this.inputs.detachElement(this._attachedElement);
             this._attachedElement = null;
-            if (this._reset) {
-                this._reset();
-            }
+            
+            this.cameraDirection = new Vector3(0, 0, 0);
+            this.cameraRotation = new Vector2(0, 0);
         }
 
         public _collideWithWorld(velocity: Vector3): void {
@@ -243,25 +170,7 @@
                 this._transformedDirection = Vector3.Zero();
             }
 
-            // Keyboard
-            for (var index = 0; index < this._keys.length; index++) {
-                var keyCode = this._keys[index];
-                var speed = this._computeLocalCameraSpeed();
-
-                if (this.keysLeft.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(-speed, 0, 0);
-                } else if (this.keysUp.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(0, 0, speed);
-                } else if (this.keysRight.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(speed, 0, 0);
-                } else if (this.keysDown.indexOf(keyCode) !== -1) {
-                    this._localDirection.copyFromFloats(0, 0, -speed);
-                }
-
-                this.getViewMatrix().invertToRef(this._cameraTransformMatrix);
-                Vector3.TransformNormalToRef(this._localDirection, this._cameraTransformMatrix, this._transformedDirection);
-                this.cameraDirection.addInPlace(this._transformedDirection);
-            }
+            this.inputs.checkInputs();
 
             super._checkInputs();
         }
@@ -278,8 +187,13 @@
             }
         }
 
+        public dispose(): void {
+            this.inputs.clear();
+            super.dispose();
+        }
+        
         public getTypeName(): string {
             return "FreeCamera";
         }
-    }
+    }    
 } 

+ 50 - 0
src/Cameras/babylon.freeCameraInputsManager.js

@@ -0,0 +1,50 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    var FreeCameraInputsManager = (function (_super) {
+        __extends(FreeCameraInputsManager, _super);
+        function FreeCameraInputsManager(camera) {
+            _super.call(this, camera);
+        }
+        FreeCameraInputsManager.prototype.add = function (input) {
+            _super.prototype.add.call(this, input);
+            if (this.camera._attachedElement && input.attachElement) {
+                input.attachElement(this.camera._attachedElement, this.camera._noPreventDefault);
+            }
+        };
+        FreeCameraInputsManager.prototype.addKeyboard = function () {
+            this.add(new BABYLON.FreeCameraKeyboardMoveInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addMouse = function () {
+            this.add(new BABYLON.FreeCameraMouseInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addGamepad = function () {
+            this.add(new BABYLON.FreeCameraGamepadInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addDeviceOrientation = function () {
+            this.add(new BABYLON.FreeCameraDeviceOrientationInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addVRDeviceOrientation = function () {
+            this.add(new BABYLON.FreeCameraVRDeviceOrientationInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addTouch = function () {
+            this.add(new BABYLON.FreeCameraTouchInput());
+            return this;
+        };
+        FreeCameraInputsManager.prototype.addVirtualJoystick = function () {
+            this.add(new BABYLON.FreeCameraVirtualJoystickInput());
+            return this;
+        };
+        return FreeCameraInputsManager;
+    })(BABYLON.CameraInputsManager);
+    BABYLON.FreeCameraInputsManager = FreeCameraInputsManager;
+})(BABYLON || (BABYLON = {}));

+ 49 - 0
src/Cameras/babylon.freeCameraInputsManager.ts

@@ -0,0 +1,49 @@
+module BABYLON {
+    export class FreeCameraInputsManager extends CameraInputsManager<FreeCamera> {
+        constructor(camera : FreeCamera){
+            super(camera);    
+        }
+        
+        add(input: ICameraInput<FreeCamera>){
+            super.add(input);
+            if (this.camera._attachedElement && input.attachElement) {
+                input.attachElement(this.camera._attachedElement, this.camera._noPreventDefault);
+            }
+        }
+        
+        addKeyboard(){
+            this.add(new FreeCameraKeyboardMoveInput());
+            return this;
+        }
+        
+        addMouse(){
+            this.add(new FreeCameraMouseInput());
+            return this;
+        }
+        
+        addGamepad(){
+            this.add(new FreeCameraGamepadInput());
+            return this;
+        }
+        
+        addDeviceOrientation(){
+            this.add(new FreeCameraDeviceOrientationInput());
+            return this;
+        }
+        
+        addVRDeviceOrientation(){
+            this.add(new FreeCameraVRDeviceOrientationInput());
+            return this;
+        }
+        
+        addTouch(){
+            this.add(new FreeCameraTouchInput());
+            return this;
+        }
+        
+        addVirtualJoystick(){
+            this.add(new FreeCameraVirtualJoystickInput());
+            return this;
+        }
+    }
+}

+ 30 - 0
src/Cameras/babylon.gamepadCamera.js

@@ -8,10 +8,40 @@ var BABYLON;
     // We're mainly based on the logic defined into the FreeCamera code
     var GamepadCamera = (function (_super) {
         __extends(GamepadCamera, _super);
+        //-- end properties for backward compatibility for inputs
         function GamepadCamera(name, position, scene) {
             BABYLON.Tools.Warn("Deprecated. Please use Universal Camera instead.");
             _super.call(this, name, position, scene);
         }
+        Object.defineProperty(GamepadCamera.prototype, "gamepadAngularSensibility", {
+            //-- Begin properties for backward compatibility for inputs
+            get: function () {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    return gamepad.gamepadAngularSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    gamepad.gamepadAngularSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GamepadCamera.prototype, "gamepadMoveSensibility", {
+            get: function () {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    return gamepad.gamepadMoveSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    gamepad.gamepadMoveSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         GamepadCamera.prototype.getTypeName = function () {
             return "GamepadCamera";
         };

+ 27 - 0
src/Cameras/babylon.gamepadCamera.ts

@@ -1,6 +1,33 @@
 module BABYLON {
     // We're mainly based on the logic defined into the FreeCamera code
     export class GamepadCamera extends UniversalCamera {
+        //-- Begin properties for backward compatibility for inputs
+        public get gamepadAngularSensibility() {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                return gamepad.gamepadAngularSensibility;
+        }
+        
+        public set gamepadAngularSensibility(value) {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                gamepad.gamepadAngularSensibility = value;
+        }
+        
+        public get gamepadMoveSensibility() {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                return gamepad.gamepadMoveSensibility;
+        }
+        
+        public set gamepadMoveSensibility(value) {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                gamepad.gamepadMoveSensibility = value;
+        }
+        //-- end properties for backward compatibility for inputs
+        
+        
         constructor(name: string, position: Vector3, scene: Scene) {
             Tools.Warn("Deprecated. Please use Universal Camera instead.");
             super(name, position, scene);

+ 31 - 114
src/Cameras/babylon.touchCamera.js

@@ -3,131 +3,48 @@ var __extends = (this && this.__extends) || function (d, b) {
     function __() { this.constructor = d; }
     d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
 };
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
 var BABYLON;
 (function (BABYLON) {
     // We're mainly based on the logic defined into the FreeCamera code
     var TouchCamera = (function (_super) {
         __extends(TouchCamera, _super);
+        //-- end properties for backward compatibility for inputs
         function TouchCamera(name, position, scene) {
             _super.call(this, name, position, scene);
-            this._offsetX = null;
-            this._offsetY = null;
-            this._pointerCount = 0;
-            this._pointerPressed = [];
-            this.touchAngularSensibility = 200000.0;
-            this.touchMoveSensibility = 250.0;
+            this.inputs.addTouch();
         }
-        TouchCamera.prototype._onLostFocus = function (e) {
-            this._offsetX = null;
-            this._offsetY = null;
-            _super.prototype._onLostFocus.call(this, e);
-        };
-        TouchCamera.prototype.attachControl = function (canvas, noPreventDefault) {
-            var _this = this;
-            var previousPosition;
-            if (this._attachedCanvas) {
-                return;
-            }
-            if (this._onPointerDown === undefined) {
-                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();
-                    }
-                    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);
-                };
-            }
-            canvas.addEventListener("pointerdown", this._onPointerDown);
-            canvas.addEventListener("pointerup", this._onPointerUp);
-            canvas.addEventListener("pointerout", this._onPointerUp);
-            canvas.addEventListener("pointermove", this._onPointerMove);
-            _super.prototype.attachControl.call(this, canvas);
-        };
-        TouchCamera.prototype.detachControl = function (canvas) {
-            if (this._attachedCanvas !== canvas) {
-                return;
-            }
-            canvas.removeEventListener("pointerdown", this._onPointerDown);
-            canvas.removeEventListener("pointerup", this._onPointerUp);
-            canvas.removeEventListener("pointerout", this._onPointerUp);
-            canvas.removeEventListener("pointermove", this._onPointerMove);
-            _super.prototype.detachControl.call(this, canvas);
-        };
-        TouchCamera.prototype._checkInputs = function () {
-            if (this._offsetX) {
-                this.cameraRotation.y += this._offsetX / this.touchAngularSensibility;
-                if (this._pointerPressed.length > 1) {
-                    this.cameraRotation.x += -this._offsetY / this.touchAngularSensibility;
-                }
-                else {
-                    var speed = this._computeLocalCameraSpeed();
-                    var direction = new BABYLON.Vector3(0, 0, speed * this._offsetY / this.touchMoveSensibility);
-                    BABYLON.Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, 0, this._cameraRotationMatrix);
-                    this.cameraDirection.addInPlace(BABYLON.Vector3.TransformCoordinates(direction, this._cameraRotationMatrix));
-                }
-            }
-            _super.prototype._checkInputs.call(this);
-        };
+        Object.defineProperty(TouchCamera.prototype, "touchAngularSensibility", {
+            //-- Begin properties for backward compatibility for inputs
+            get: function () {
+                var touch = this.inputs.attached["touch"];
+                if (touch)
+                    return touch.touchAngularSensibility;
+            },
+            set: function (value) {
+                var touch = this.inputs.attached["touch"];
+                if (touch)
+                    touch.touchAngularSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(TouchCamera.prototype, "touchMoveSensibility", {
+            get: function () {
+                var touch = this.inputs.attached["touch"];
+                if (touch)
+                    return touch.touchMoveSensibility;
+            },
+            set: function (value) {
+                var touch = this.inputs.attached["touch"];
+                if (touch)
+                    touch.touchMoveSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         TouchCamera.prototype.getTypeName = function () {
             return "TouchCamera";
         };
-        __decorate([
-            BABYLON.serialize()
-        ], TouchCamera.prototype, "touchAngularSensibility", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], TouchCamera.prototype, "touchMoveSensibility", void 0);
         return TouchCamera;
     })(BABYLON.FreeCamera);
     BABYLON.TouchCamera = TouchCamera;

+ 25 - 142
src/Cameras/babylon.touchCamera.ts

@@ -1,152 +1,35 @@
 module BABYLON {
     // We're mainly based on the logic defined into the FreeCamera code
     export class TouchCamera extends FreeCamera {
-        private _offsetX: number = null;
-        private _offsetY: number = null;
-        private _pointerCount:number = 0;
-        private _pointerPressed = [];
-        private _attachedCanvas: HTMLCanvasElement;
-        private _onPointerDown: (e: PointerEvent) => any;
-        private _onPointerUp: (e: PointerEvent) => any;
-        private _onPointerMove: (e: PointerEvent) => any;
-
-        @serialize()
-        public touchAngularSensibility: number = 200000.0;
-
-        @serialize()
-        public touchMoveSensibility: number = 250.0;
-
-        constructor(name: string, position: Vector3, scene: Scene) {
-            super(name, position, scene);
+        //-- Begin properties for backward compatibility for inputs
+        public get touchAngularSensibility() {
+            var touch = <FreeCameraTouchInput>this.inputs.attached["touch"];
+            if (touch)
+                return touch.touchAngularSensibility;
         }
-
-        public _onLostFocus(e: FocusEvent): void {
-            this._offsetX = null;
-            this._offsetY = null;
-            super._onLostFocus(e);
+        
+        public set touchAngularSensibility(value) {
+            var touch = <FreeCameraTouchInput>this.inputs.attached["touch"];
+            if (touch)
+                touch.touchAngularSensibility = value;
         }
-
-        public attachControl(canvas: HTMLCanvasElement, noPreventDefault: boolean): void {
-            var previousPosition;
-
-            if (this._attachedCanvas) {
-                return;
-            }
-
-            if (this._onPointerDown === undefined) {
-
-                this._onPointerDown = (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 = (evt) => {
-
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-
-                    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;
-                };
-
-                this._onPointerMove = (evt) => {
-
-                    if (evt.pointerType === "mouse") {
-                        return;
-                    }
-
-                    if (!noPreventDefault) {
-                        evt.preventDefault();
-                    }
-
-                    if (!previousPosition) {
-                        return;
-                    }
-
-                    var index: number = this._pointerPressed.indexOf(evt.pointerId);
-
-                    if (index != 0) {
-                        return;
-                    }
-
-                    this._offsetX = evt.clientX - previousPosition.x;
-                    this._offsetY = -(evt.clientY - previousPosition.y);
-                };
-
-                
-            }
-
-            canvas.addEventListener("pointerdown", this._onPointerDown);
-            canvas.addEventListener("pointerup", this._onPointerUp);
-            canvas.addEventListener("pointerout", this._onPointerUp);
-            canvas.addEventListener("pointermove", this._onPointerMove);
-
-            super.attachControl(canvas);
+        
+        public get touchMoveSensibility() {
+            var touch = <FreeCameraTouchInput>this.inputs.attached["touch"];
+            if (touch)
+                return touch.touchMoveSensibility;
         }
-
-        public detachControl(canvas: HTMLCanvasElement): void {
-            if (this._attachedCanvas !== canvas) {
-                return;
-            }
-
-            canvas.removeEventListener("pointerdown", this._onPointerDown);
-            canvas.removeEventListener("pointerup", this._onPointerUp);
-            canvas.removeEventListener("pointerout", this._onPointerUp);
-            canvas.removeEventListener("pointermove", this._onPointerMove);
-            
-            super.detachControl(canvas);
+        
+        public set touchMoveSensibility(value) {
+            var touch = <FreeCameraTouchInput>this.inputs.attached["touch"];
+            if (touch)
+                touch.touchMoveSensibility = value;
         }
-
-        public _checkInputs(): void {
-            if (this._offsetX) {
-
-                this.cameraRotation.y += this._offsetX / this.touchAngularSensibility;
-
-                if (this._pointerPressed.length > 1) {
-                    this.cameraRotation.x += -this._offsetY / this.touchAngularSensibility;
-                } else {
-                    var speed = this._computeLocalCameraSpeed();
-                    var direction = new Vector3(0, 0, speed * this._offsetY / this.touchMoveSensibility);
-
-                    Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, 0, this._cameraRotationMatrix);
-                    this.cameraDirection.addInPlace(Vector3.TransformCoordinates(direction, this._cameraRotationMatrix));
-                }
-            }
-
-            super._checkInputs();
+        //-- end properties for backward compatibility for inputs
+        
+        constructor(name: string, position: Vector3, scene: Scene) {
+            super(name, position, scene);
+            this.inputs.addTouch();
         }
 
         public getTypeName(): string {

+ 31 - 52
src/Cameras/babylon.universalCamera.js

@@ -3,69 +3,48 @@ var __extends = (this && this.__extends) || function (d, b) {
     function __() { this.constructor = d; }
     d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
 };
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
 var BABYLON;
 (function (BABYLON) {
     // We're mainly based on the logic defined into the FreeCamera code
     var UniversalCamera = (function (_super) {
         __extends(UniversalCamera, _super);
+        //-- end properties for backward compatibility for inputs
         function UniversalCamera(name, position, scene) {
-            var _this = this;
             _super.call(this, name, position, scene);
-            this.gamepadAngularSensibility = 200;
-            this.gamepadMoveSensibility = 40;
-            this._gamepads = new BABYLON.Gamepads(function (gamepad) { _this._onNewGameConnected(gamepad); });
+            this.inputs.addGamepad();
         }
-        UniversalCamera.prototype._onNewGameConnected = function (gamepad) {
-            // Only the first gamepad can control the camera
-            if (gamepad.index === 0) {
-                this.gamepad = gamepad;
-            }
-        };
-        UniversalCamera.prototype.attachControl = function (canvas, noPreventDefault) {
-            _super.prototype.attachControl.call(this, canvas, false);
-        };
-        UniversalCamera.prototype.detachControl = function (canvas) {
-            _super.prototype.detachControl.call(this, canvas);
-        };
-        UniversalCamera.prototype._checkInputs = function () {
-            if (this.gamepad) {
-                var LSValues = this.gamepad.leftStick;
-                var normalizedLX = LSValues.x / this.gamepadMoveSensibility;
-                var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
-                LSValues.x = Math.abs(normalizedLX) > 0.005 ? 0 + normalizedLX : 0;
-                LSValues.y = Math.abs(normalizedLY) > 0.005 ? 0 + normalizedLY : 0;
-                var RSValues = this.gamepad.rightStick;
-                var normalizedRX = RSValues.x / this.gamepadAngularSensibility;
-                var normalizedRY = RSValues.y / this.gamepadAngularSensibility;
-                RSValues.x = Math.abs(normalizedRX) > 0.001 ? 0 + normalizedRX : 0;
-                RSValues.y = Math.abs(normalizedRY) > 0.001 ? 0 + normalizedRY : 0;
-                var cameraTransform = BABYLON.Matrix.RotationYawPitchRoll(this.rotation.y, this.rotation.x, 0);
-                var speed = this._computeLocalCameraSpeed() * 50.0;
-                var deltaTransform = BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(LSValues.x * speed, 0, -LSValues.y * speed), cameraTransform);
-                this.cameraDirection = this.cameraDirection.add(deltaTransform);
-                this.cameraRotation = this.cameraRotation.add(new BABYLON.Vector2(RSValues.y, RSValues.x));
-            }
-            _super.prototype._checkInputs.call(this);
-        };
-        UniversalCamera.prototype.dispose = function () {
-            this._gamepads.dispose();
-            _super.prototype.dispose.call(this);
-        };
+        Object.defineProperty(UniversalCamera.prototype, "gamepadAngularSensibility", {
+            //-- Begin properties for backward compatibility for inputs
+            get: function () {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    return gamepad.gamepadAngularSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    gamepad.gamepadAngularSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UniversalCamera.prototype, "gamepadMoveSensibility", {
+            get: function () {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    return gamepad.gamepadMoveSensibility;
+            },
+            set: function (value) {
+                var gamepad = this.inputs.attached["gamepad"];
+                if (gamepad)
+                    gamepad.gamepadMoveSensibility = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         UniversalCamera.prototype.getTypeName = function () {
             return "UniversalCamera";
         };
-        __decorate([
-            BABYLON.serialize()
-        ], UniversalCamera.prototype, "gamepadAngularSensibility", void 0);
-        __decorate([
-            BABYLON.serialize()
-        ], UniversalCamera.prototype, "gamepadMoveSensibility", void 0);
         return UniversalCamera;
     })(BABYLON.TouchCamera);
     BABYLON.UniversalCamera = UniversalCamera;

+ 25 - 52
src/Cameras/babylon.universalCamera.ts

@@ -1,62 +1,35 @@
 module BABYLON {
     // We're mainly based on the logic defined into the FreeCamera code
     export class UniversalCamera extends TouchCamera {
-        public gamepad: Gamepad;
-        private _gamepads: Gamepads;
-
-        @serialize()
-        public gamepadAngularSensibility = 200;
-
-        @serialize()
-        public gamepadMoveSensibility = 40;
-
-        constructor(name: string, position: Vector3, scene: Scene) {
-            super(name, position, scene);
-            this._gamepads = new Gamepads((gamepad: Gamepad) => { this._onNewGameConnected(gamepad); });
-        }
-
-        private _onNewGameConnected(gamepad: Gamepad) {
-            // Only the first gamepad can control the camera
-            if (gamepad.index === 0) {
-                this.gamepad = gamepad;
-            }
+        //-- Begin properties for backward compatibility for inputs
+        public get gamepadAngularSensibility() {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                return gamepad.gamepadAngularSensibility;
         }
-
-        public attachControl(canvas: HTMLCanvasElement, noPreventDefault: boolean): void {
-            super.attachControl(canvas, false);
+        
+        public set gamepadAngularSensibility(value) {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                gamepad.gamepadAngularSensibility = value;
         }
-
-        public detachControl(canvas: HTMLCanvasElement): void {
-            super.detachControl(canvas);
+        
+        public get gamepadMoveSensibility() {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                return gamepad.gamepadMoveSensibility;
         }
-
-        public _checkInputs(): void {
-            if (this.gamepad) {
-                var LSValues = this.gamepad.leftStick;
-                var normalizedLX = LSValues.x / this.gamepadMoveSensibility;
-                var normalizedLY = LSValues.y / this.gamepadMoveSensibility;
-                LSValues.x = Math.abs(normalizedLX) > 0.005 ? 0 + normalizedLX : 0;
-                LSValues.y = Math.abs(normalizedLY) > 0.005 ? 0 + normalizedLY : 0;
-
-                var RSValues = this.gamepad.rightStick;
-                var normalizedRX = RSValues.x / this.gamepadAngularSensibility;
-                var normalizedRY = RSValues.y / this.gamepadAngularSensibility;
-                RSValues.x = Math.abs(normalizedRX) > 0.001 ? 0 + normalizedRX : 0;
-                RSValues.y = Math.abs(normalizedRY) > 0.001 ? 0 + normalizedRY : 0;
-
-                var cameraTransform = Matrix.RotationYawPitchRoll(this.rotation.y, this.rotation.x, 0);
-
-                var speed = this._computeLocalCameraSpeed() * 50.0;
-                var deltaTransform = Vector3.TransformCoordinates(new Vector3(LSValues.x * speed, 0, -LSValues.y * speed), cameraTransform);
-                this.cameraDirection = this.cameraDirection.add(deltaTransform);
-                this.cameraRotation = this.cameraRotation.add(new Vector2(RSValues.y, RSValues.x));
-            }
-            super._checkInputs();
+        
+        public set gamepadMoveSensibility(value) {
+            var gamepad = <FreeCameraGamepadInput>this.inputs.attached["gamepad"];
+            if (gamepad)
+                gamepad.gamepadMoveSensibility = value;
         }
-
-        public dispose(): void {
-            this._gamepads.dispose();
-            super.dispose();
+        //-- end properties for backward compatibility for inputs
+        
+        constructor(name: string, position: Vector3, scene: Scene) {
+            super(name, position, scene);
+            this.inputs.addGamepad();
         }
 
         public getTypeName(): string {

+ 1 - 37
src/Cameras/babylon.virtualJoysticksCamera.js

@@ -10,44 +10,8 @@ var BABYLON;
         __extends(VirtualJoysticksCamera, _super);
         function VirtualJoysticksCamera(name, position, scene) {
             _super.call(this, name, position, scene);
-            this._leftjoystick = new BABYLON.VirtualJoystick(true);
-            this._leftjoystick.setAxisForUpDown(BABYLON.JoystickAxis.Z);
-            this._leftjoystick.setAxisForLeftRight(BABYLON.JoystickAxis.X);
-            this._leftjoystick.setJoystickSensibility(0.15);
-            this._rightjoystick = new BABYLON.VirtualJoystick(false);
-            this._rightjoystick.setAxisForUpDown(BABYLON.JoystickAxis.X);
-            this._rightjoystick.setAxisForLeftRight(BABYLON.JoystickAxis.Y);
-            this._rightjoystick.reverseUpDown = true;
-            this._rightjoystick.setJoystickSensibility(0.05);
-            this._rightjoystick.setJoystickColor("yellow");
+            this.inputs.addVirtualJoystick();
         }
-        VirtualJoysticksCamera.prototype.getLeftJoystick = function () {
-            return this._leftjoystick;
-        };
-        VirtualJoysticksCamera.prototype.getRightJoystick = function () {
-            return this._rightjoystick;
-        };
-        VirtualJoysticksCamera.prototype._checkInputs = function () {
-            var speed = this._computeLocalCameraSpeed() * 50;
-            var cameraTransform = BABYLON.Matrix.RotationYawPitchRoll(this.rotation.y, this.rotation.x, 0);
-            var deltaTransform = BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(this._leftjoystick.deltaPosition.x * speed, this._leftjoystick.deltaPosition.y * speed, this._leftjoystick.deltaPosition.z * speed), cameraTransform);
-            this.cameraDirection = this.cameraDirection.add(deltaTransform);
-            this.cameraRotation = this.cameraRotation.addVector3(this._rightjoystick.deltaPosition);
-            if (!this._leftjoystick.pressed) {
-                this._leftjoystick.deltaPosition = this._leftjoystick.deltaPosition.scale(0.9);
-            }
-            if (!this._rightjoystick.pressed) {
-                this._rightjoystick.deltaPosition = this._rightjoystick.deltaPosition.scale(0.9);
-            }
-            _super.prototype._checkInputs.call(this);
-        };
-        VirtualJoysticksCamera.prototype.dispose = function () {
-            this._leftjoystick.releaseCanvas();
-            _super.prototype.dispose.call(this);
-        };
-        VirtualJoysticksCamera.prototype.getTypeName = function () {
-            return "VirtualJoysticksCamera";
-        };
         return VirtualJoysticksCamera;
     })(BABYLON.FreeCamera);
     BABYLON.VirtualJoysticksCamera = VirtualJoysticksCamera;

+ 2 - 46
src/Cameras/babylon.virtualJoysticksCamera.ts

@@ -1,54 +1,10 @@
 module BABYLON {
     // We're mainly based on the logic defined into the FreeCamera code
     export class VirtualJoysticksCamera extends FreeCamera {
-        private _leftjoystick: VirtualJoystick;
-        private _rightjoystick: VirtualJoystick;
-
+        
         constructor(name: string, position: Vector3, scene: Scene) {
             super(name, position, scene);
-            this._leftjoystick = new VirtualJoystick(true);
-            this._leftjoystick.setAxisForUpDown(JoystickAxis.Z);
-            this._leftjoystick.setAxisForLeftRight(JoystickAxis.X);
-            this._leftjoystick.setJoystickSensibility(0.15);
-            this._rightjoystick = new VirtualJoystick(false);
-            this._rightjoystick.setAxisForUpDown(JoystickAxis.X);
-            this._rightjoystick.setAxisForLeftRight(JoystickAxis.Y);
-            this._rightjoystick.reverseUpDown = true;
-            this._rightjoystick.setJoystickSensibility(0.05);
-            this._rightjoystick.setJoystickColor("yellow");
-        }
-
-        public getLeftJoystick(): VirtualJoystick {
-            return this._leftjoystick;
-        }
-
-        public getRightJoystick(): VirtualJoystick {
-            return this._rightjoystick;
-        }
-
-        public _checkInputs(): void {
-            var speed = this._computeLocalCameraSpeed() * 50;
-            var cameraTransform = Matrix.RotationYawPitchRoll(this.rotation.y, this.rotation.x, 0);
-            var deltaTransform = Vector3.TransformCoordinates(new Vector3(this._leftjoystick.deltaPosition.x * speed, this._leftjoystick.deltaPosition.y * speed, this._leftjoystick.deltaPosition.z * speed), cameraTransform);
-            this.cameraDirection = this.cameraDirection.add(deltaTransform);
-            this.cameraRotation = this.cameraRotation.addVector3(this._rightjoystick.deltaPosition);
-            if (!this._leftjoystick.pressed) {
-                this._leftjoystick.deltaPosition = this._leftjoystick.deltaPosition.scale(0.9);
-            }
-            if (!this._rightjoystick.pressed) {
-                this._rightjoystick.deltaPosition = this._rightjoystick.deltaPosition.scale(0.9);
-            }
-
-            super._checkInputs();
-        }
-
-        public dispose(): void {
-            this._leftjoystick.releaseCanvas();
-            super.dispose();
-        }
-
-        public getTypeName(): string {
-            return "VirtualJoysticksCamera";
+            this.inputs.addVirtualJoystick();
         }
     }
 }

+ 15 - 0
src/Lights/babylon.light.js

@@ -29,6 +29,21 @@ var BABYLON;
             this._includedOnlyMeshesIds = new Array();
             scene.addLight(this);
         }
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        Light.prototype.toString = function (fullDetails) {
+            var ret = "Name: " + this.name;
+            ret += ", type: " + (["Point", "Directional", "Spot", "Hemispheric"])[this.getTypeID()];
+            if (this.animations) {
+                for (var i = 0; i < this.animations.length; i++) {
+                    ret += ", animation[0]: " + this.animations[i].toString(fullDetails);
+                }
+            }
+            if (fullDetails) {
+            }
+            return ret;
+        };
         Light.prototype.getShadowGenerator = function () {
             return this._shadowGenerator;
         };

+ 16 - 0
src/Lights/babylon.light.ts

@@ -55,6 +55,22 @@
             scene.addLight(this);
         }
 
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         */
+        public toString(fullDetails? : boolean) : string {
+            var ret = "Name: " + this.name;
+            ret += ", type: " + (["Point", "Directional", "Spot", "Hemispheric"])[this.getTypeID()];
+            if (this.animations){
+                for (var i = 0; i < this.animations.length; i++){
+                   ret += ", animation[0]: " + this.animations[i].toString(fullDetails);
+                }
+            }
+            if (fullDetails){
+            }
+            return ret;
+        } 
+        
         public getShadowGenerator(): ShadowGenerator {
             return this._shadowGenerator;
         }

+ 399 - 321
src/Loading/Plugins/babylon.babylonFileLoader.js

@@ -25,387 +25,465 @@ var BABYLON;
             }
             return false;
         };
+        var logOperation = function (operation, producer) {
+            return operation + " of " + (producer ? producer.file + " from " + producer.name + " version: " + producer.version + ", exporter version: " + producer.exporter_version : "unknown");
+        };
         BABYLON.SceneLoader.RegisterPlugin({
             extensions: ".babylon",
             importMesh: function (meshesNames, scene, data, rootUrl, meshes, particleSystems, skeletons) {
-                var parsedData = JSON.parse(data);
-                var loadedSkeletonsIds = [];
-                var loadedMaterialsIds = [];
-                var hierarchyIds = [];
-                var index;
-                var cache;
-                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
-                    var parsedMesh = parsedData.meshes[index];
-                    if (!meshesNames || isDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
-                        if (meshesNames instanceof Array) {
-                            // Remove found mesh name from list.
-                            delete meshesNames[meshesNames.indexOf(parsedMesh.name)];
-                        }
-                        //Geometry?
-                        if (parsedMesh.geometryId) {
-                            //does the file contain geometries?
-                            if (parsedData.geometries) {
-                                //find the correct geometry and add it to the scene
-                                var found = false;
-                                ["boxes", "spheres", "cylinders", "toruses", "grounds", "planes", "torusKnots", "vertexData"].forEach(function (geometryType) {
-                                    if (found || !parsedData.geometries[geometryType] || !(parsedData.geometries[geometryType] instanceof Array)) {
-                                        return;
-                                    }
-                                    else {
-                                        parsedData.geometries[geometryType].forEach(function (parsedGeometryData) {
-                                            if (parsedGeometryData.id === parsedMesh.geometryId) {
-                                                switch (geometryType) {
-                                                    case "boxes":
-                                                        BABYLON.Geometry.Primitives.Box.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "spheres":
-                                                        BABYLON.Geometry.Primitives.Sphere.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "cylinders":
-                                                        BABYLON.Geometry.Primitives.Cylinder.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "toruses":
-                                                        BABYLON.Geometry.Primitives.Torus.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "grounds":
-                                                        BABYLON.Geometry.Primitives.Ground.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "planes":
-                                                        BABYLON.Geometry.Primitives.Plane.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "torusKnots":
-                                                        BABYLON.Geometry.Primitives.TorusKnot.Parse(parsedGeometryData, scene);
-                                                        break;
-                                                    case "vertexData":
-                                                        BABYLON.Geometry.Parse(parsedGeometryData, scene, rootUrl);
-                                                        break;
+                // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
+                // when SceneLoader.debugLogging = true (default), or exception encountered.
+                // Everything stored in var log instead of writing separate lines to support only writing in exception,
+                // and avoid problems with multiple concurrent .babylon loads.
+                var log = "importMesh has failed JSON parse";
+                try {
+                    var parsedData = JSON.parse(data);
+                    log = "";
+                    var fullDetails = BABYLON.SceneLoader.loggingLevel === BABYLON.SceneLoader.DETAILED_LOGGING;
+                    var loadedSkeletonsIds = [];
+                    var loadedMaterialsIds = [];
+                    var hierarchyIds = [];
+                    var index;
+                    var cache;
+                    for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
+                        var parsedMesh = parsedData.meshes[index];
+                        if (!meshesNames || isDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
+                            if (meshesNames instanceof Array) {
+                                // Remove found mesh name from list.
+                                delete meshesNames[meshesNames.indexOf(parsedMesh.name)];
+                            }
+                            //Geometry?
+                            if (parsedMesh.geometryId) {
+                                //does the file contain geometries?
+                                if (parsedData.geometries) {
+                                    //find the correct geometry and add it to the scene
+                                    var found = false;
+                                    ["boxes", "spheres", "cylinders", "toruses", "grounds", "planes", "torusKnots", "vertexData"].forEach(function (geometryType) {
+                                        if (found || !parsedData.geometries[geometryType] || !(parsedData.geometries[geometryType] instanceof Array)) {
+                                            return;
+                                        }
+                                        else {
+                                            parsedData.geometries[geometryType].forEach(function (parsedGeometryData) {
+                                                if (parsedGeometryData.id === parsedMesh.geometryId) {
+                                                    switch (geometryType) {
+                                                        case "boxes":
+                                                            BABYLON.Geometry.Primitives.Box.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "spheres":
+                                                            BABYLON.Geometry.Primitives.Sphere.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "cylinders":
+                                                            BABYLON.Geometry.Primitives.Cylinder.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "toruses":
+                                                            BABYLON.Geometry.Primitives.Torus.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "grounds":
+                                                            BABYLON.Geometry.Primitives.Ground.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "planes":
+                                                            BABYLON.Geometry.Primitives.Plane.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "torusKnots":
+                                                            BABYLON.Geometry.Primitives.TorusKnot.Parse(parsedGeometryData, scene);
+                                                            break;
+                                                        case "vertexData":
+                                                            BABYLON.Geometry.Parse(parsedGeometryData, scene, rootUrl);
+                                                            break;
+                                                    }
+                                                    found = true;
                                                 }
-                                                found = true;
-                                            }
-                                        });
+                                            });
+                                        }
+                                    });
+                                    if (!found) {
+                                        BABYLON.Tools.Warn("Geometry not found for mesh " + parsedMesh.id);
                                     }
-                                });
-                                if (!found) {
-                                    BABYLON.Tools.Warn("Geometry not found for mesh " + parsedMesh.id);
                                 }
                             }
-                        }
-                        // Material ?
-                        if (parsedMesh.materialId) {
-                            var materialFound = (loadedMaterialsIds.indexOf(parsedMesh.materialId) !== -1);
-                            if (!materialFound && parsedData.multiMaterials) {
-                                for (var multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
-                                    var parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
-                                    if (parsedMultiMaterial.id === parsedMesh.materialId) {
-                                        for (var matIndex = 0, matCache = parsedMultiMaterial.materials.length; matIndex < matCache; matIndex++) {
-                                            var subMatId = parsedMultiMaterial.materials[matIndex];
-                                            loadedMaterialsIds.push(subMatId);
-                                            parseMaterialById(subMatId, parsedData, scene, rootUrl);
+                            // Material ?
+                            if (parsedMesh.materialId) {
+                                var materialFound = (loadedMaterialsIds.indexOf(parsedMesh.materialId) !== -1);
+                                if (!materialFound && parsedData.multiMaterials) {
+                                    for (var multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
+                                        var parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
+                                        if (parsedMultiMaterial.id === parsedMesh.materialId) {
+                                            for (var matIndex = 0, matCache = parsedMultiMaterial.materials.length; matIndex < matCache; matIndex++) {
+                                                var subMatId = parsedMultiMaterial.materials[matIndex];
+                                                loadedMaterialsIds.push(subMatId);
+                                                var mat = parseMaterialById(subMatId, parsedData, scene, rootUrl);
+                                                log += "\n\tMaterial " + mat.toString(fullDetails);
+                                            }
+                                            loadedMaterialsIds.push(parsedMultiMaterial.id);
+                                            var mmat = BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                                            materialFound = true;
+                                            log += "\n\tMulti-Material " + mmat.toString(fullDetails);
+                                            break;
                                         }
-                                        loadedMaterialsIds.push(parsedMultiMaterial.id);
-                                        BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
-                                        materialFound = true;
-                                        break;
                                     }
                                 }
-                            }
-                            if (!materialFound) {
-                                loadedMaterialsIds.push(parsedMesh.materialId);
-                                if (!parseMaterialById(parsedMesh.materialId, parsedData, scene, rootUrl)) {
-                                    BABYLON.Tools.Warn("Material not found for mesh " + parsedMesh.id);
+                                if (!materialFound) {
+                                    loadedMaterialsIds.push(parsedMesh.materialId);
+                                    var mat = parseMaterialById(parsedMesh.materialId, parsedData, scene, rootUrl);
+                                    if (!mat) {
+                                        BABYLON.Tools.Warn("Material not found for mesh " + parsedMesh.id);
+                                    }
+                                    else {
+                                        log += "\n\tMaterial " + mat.toString(fullDetails);
+                                    }
                                 }
                             }
-                        }
-                        // Skeleton ?
-                        if (parsedMesh.skeletonId > -1 && scene.skeletons) {
-                            var skeletonAlreadyLoaded = (loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1);
-                            if (!skeletonAlreadyLoaded) {
-                                for (var skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
-                                    var parsedSkeleton = parsedData.skeletons[skeletonIndex];
-                                    if (parsedSkeleton.id === parsedMesh.skeletonId) {
-                                        skeletons.push(BABYLON.Skeleton.Parse(parsedSkeleton, scene));
-                                        loadedSkeletonsIds.push(parsedSkeleton.id);
+                            // Skeleton ?
+                            if (parsedMesh.skeletonId > -1 && scene.skeletons) {
+                                var skeletonAlreadyLoaded = (loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1);
+                                if (!skeletonAlreadyLoaded) {
+                                    for (var skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
+                                        var parsedSkeleton = parsedData.skeletons[skeletonIndex];
+                                        if (parsedSkeleton.id === parsedMesh.skeletonId) {
+                                            var skeleton = BABYLON.Skeleton.Parse(parsedSkeleton, scene);
+                                            skeletons.push(skeleton);
+                                            loadedSkeletonsIds.push(parsedSkeleton.id);
+                                            log += "\n\tSkeleton " + skeleton.toString(fullDetails);
+                                        }
                                     }
                                 }
                             }
+                            var mesh = BABYLON.Mesh.Parse(parsedMesh, scene, rootUrl);
+                            meshes.push(mesh);
+                            log += "\n\tMesh " + mesh.toString(fullDetails);
                         }
-                        var mesh = BABYLON.Mesh.Parse(parsedMesh, scene, rootUrl);
-                        meshes.push(mesh);
                     }
-                }
-                // Connecting parents
-                var currentMesh;
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    currentMesh = scene.meshes[index];
-                    if (currentMesh._waitingParentId) {
-                        currentMesh.parent = scene.getLastEntryByID(currentMesh._waitingParentId);
-                        currentMesh._waitingParentId = undefined;
+                    // Connecting parents
+                    var currentMesh;
+                    for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                        currentMesh = scene.meshes[index];
+                        if (currentMesh._waitingParentId) {
+                            currentMesh.parent = scene.getLastEntryByID(currentMesh._waitingParentId);
+                            currentMesh._waitingParentId = undefined;
+                        }
                     }
-                }
-                // freeze and compute world matrix application
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    currentMesh = scene.meshes[index];
-                    if (currentMesh._waitingFreezeWorldMatrix) {
-                        currentMesh.freezeWorldMatrix();
-                        currentMesh._waitingFreezeWorldMatrix = undefined;
+                    // freeze and compute world matrix application
+                    for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                        currentMesh = scene.meshes[index];
+                        if (currentMesh._waitingFreezeWorldMatrix) {
+                            currentMesh.freezeWorldMatrix();
+                            currentMesh._waitingFreezeWorldMatrix = undefined;
+                        }
+                        else {
+                            currentMesh.computeWorldMatrix(true);
+                        }
                     }
-                    else {
-                        currentMesh.computeWorldMatrix(true);
+                    // Particles
+                    if (parsedData.particleSystems) {
+                        for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
+                            var parsedParticleSystem = parsedData.particleSystems[index];
+                            if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
+                                particleSystems.push(BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl));
+                            }
+                        }
                     }
+                    return true;
                 }
-                // Particles
-                if (parsedData.particleSystems) {
-                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
-                        var parsedParticleSystem = parsedData.particleSystems[index];
-                        if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
-                            particleSystems.push(BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl));
-                        }
+                catch (err) {
+                    BABYLON.Tools.Log(logOperation("importMesh", parsedData.producer) + log);
+                    log = null;
+                    throw err;
+                }
+                finally {
+                    if (log !== null && BABYLON.SceneLoader.loggingLevel !== BABYLON.SceneLoader.NO_LOGGING) {
+                        BABYLON.Tools.Log(logOperation("importMesh", parsedData.producer) + (BABYLON.SceneLoader.loggingLevel !== BABYLON.SceneLoader.MINIMAL_LOGGING ? log : ""));
                     }
                 }
-                return true;
             },
             load: function (scene, data, rootUrl) {
-                var parsedData = JSON.parse(data);
-                // Scene
-                scene.useDelayedTextureLoading = parsedData.useDelayedTextureLoading && !BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental;
-                scene.autoClear = parsedData.autoClear;
-                scene.clearColor = BABYLON.Color3.FromArray(parsedData.clearColor);
-                scene.ambientColor = BABYLON.Color3.FromArray(parsedData.ambientColor);
-                if (parsedData.gravity) {
-                    scene.gravity = BABYLON.Vector3.FromArray(parsedData.gravity);
-                }
-                // Fog
-                if (parsedData.fogMode && parsedData.fogMode !== 0) {
-                    scene.fogMode = parsedData.fogMode;
-                    scene.fogColor = BABYLON.Color3.FromArray(parsedData.fogColor);
-                    scene.fogStart = parsedData.fogStart;
-                    scene.fogEnd = parsedData.fogEnd;
-                    scene.fogDensity = parsedData.fogDensity;
-                }
-                //Physics
-                if (parsedData.physicsEnabled) {
-                    var physicsPlugin;
-                    if (parsedData.physicsEngine === "cannon") {
-                        physicsPlugin = new BABYLON.CannonJSPlugin();
+                // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
+                // when SceneLoader.debugLogging = true (default), or exception encountered.
+                // Everything stored in var log instead of writing separate lines to support only writing in exception,
+                // and avoid problems with multiple concurrent .babylon loads.
+                var log = "importScene has failed JSON parse";
+                try {
+                    var parsedData = JSON.parse(data);
+                    log = "";
+                    var fullDetails = BABYLON.SceneLoader.loggingLevel === BABYLON.SceneLoader.DETAILED_LOGGING;
+                    // Scene
+                    scene.useDelayedTextureLoading = parsedData.useDelayedTextureLoading && !BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental;
+                    scene.autoClear = parsedData.autoClear;
+                    scene.clearColor = BABYLON.Color3.FromArray(parsedData.clearColor);
+                    scene.ambientColor = BABYLON.Color3.FromArray(parsedData.ambientColor);
+                    if (parsedData.gravity) {
+                        scene.gravity = BABYLON.Vector3.FromArray(parsedData.gravity);
                     }
-                    else if (parsedData.physicsEngine === "oimo") {
-                        physicsPlugin = new BABYLON.OimoJSPlugin();
-                    }
-                    //else - default engine, which is currently oimo
-                    var physicsGravity = parsedData.physicsGravity ? BABYLON.Vector3.FromArray(parsedData.physicsGravity) : null;
-                    scene.enablePhysics(physicsGravity, physicsPlugin);
-                }
-                //collisions, if defined. otherwise, default is true
-                if (parsedData.collisionsEnabled != undefined) {
-                    scene.collisionsEnabled = parsedData.collisionsEnabled;
-                }
-                scene.workerCollisions = !!parsedData.workerCollisions;
-                var index;
-                var cache;
-                // Lights
-                for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
-                    var parsedLight = parsedData.lights[index];
-                    BABYLON.Light.Parse(parsedLight, scene);
-                }
-                // Animations
-                if (parsedData.animations) {
-                    for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
-                        var parsedAnimation = parsedData.animations[index];
-                        scene.animations.push(BABYLON.Animation.Parse(parsedAnimation));
+                    // Fog
+                    if (parsedData.fogMode && parsedData.fogMode !== 0) {
+                        scene.fogMode = parsedData.fogMode;
+                        scene.fogColor = BABYLON.Color3.FromArray(parsedData.fogColor);
+                        scene.fogStart = parsedData.fogStart;
+                        scene.fogEnd = parsedData.fogEnd;
+                        scene.fogDensity = parsedData.fogDensity;
+                        log += "\tFog mode for scene:  ";
+                        switch (scene.fogMode) {
+                            // getters not compiling, so using hardcoded
+                            case 1:
+                                log += "exp\n";
+                                break;
+                            case 2:
+                                log += "exp2\n";
+                                break;
+                            case 3:
+                                log += "linear\n";
+                                break;
+                        }
                     }
-                }
-                if (parsedData.autoAnimate) {
-                    scene.beginAnimation(scene, parsedData.autoAnimateFrom, parsedData.autoAnimateTo, parsedData.autoAnimateLoop, parsedData.autoAnimateSpeed || 1.0);
-                }
-                // Materials
-                if (parsedData.materials) {
-                    for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
-                        var parsedMaterial = parsedData.materials[index];
-                        BABYLON.Material.Parse(parsedMaterial, scene, rootUrl);
+                    //Physics
+                    if (parsedData.physicsEnabled) {
+                        var physicsPlugin;
+                        if (parsedData.physicsEngine === "cannon") {
+                            physicsPlugin = new BABYLON.CannonJSPlugin();
+                        }
+                        else if (parsedData.physicsEngine === "oimo") {
+                            physicsPlugin = new BABYLON.OimoJSPlugin();
+                        }
+                        log = "\tPhysics engine " + (parsedData.physicsEngine ? parsedData.physicsEngine : "oimo") + " enabled\n";
+                        //else - default engine, which is currently oimo
+                        var physicsGravity = parsedData.physicsGravity ? BABYLON.Vector3.FromArray(parsedData.physicsGravity) : null;
+                        scene.enablePhysics(physicsGravity, physicsPlugin);
                     }
-                }
-                if (parsedData.multiMaterials) {
-                    for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
-                        var parsedMultiMaterial = parsedData.multiMaterials[index];
-                        BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                    //collisions, if defined. otherwise, default is true
+                    if (parsedData.collisionsEnabled != undefined) {
+                        scene.collisionsEnabled = parsedData.collisionsEnabled;
                     }
-                }
-                // Skeletons
-                if (parsedData.skeletons) {
-                    for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
-                        var parsedSkeleton = parsedData.skeletons[index];
-                        BABYLON.Skeleton.Parse(parsedSkeleton, scene);
+                    scene.workerCollisions = !!parsedData.workerCollisions;
+                    var index;
+                    var cache;
+                    // Lights
+                    for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
+                        var parsedLight = parsedData.lights[index];
+                        var light = BABYLON.Light.Parse(parsedLight, scene);
+                        log += (index === 0 ? "\n\tLights:" : "");
+                        log += "\n\t\t" + light.toString(fullDetails);
                     }
-                }
-                // Geometries
-                var geometries = parsedData.geometries;
-                if (geometries) {
-                    // Boxes
-                    var boxes = geometries.boxes;
-                    if (boxes) {
-                        for (index = 0, cache = boxes.length; index < cache; index++) {
-                            var parsedBox = boxes[index];
-                            BABYLON.Geometry.Primitives.Box.Parse(parsedBox, scene);
+                    // Animations
+                    if (parsedData.animations) {
+                        for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
+                            var parsedAnimation = parsedData.animations[index];
+                            var animation = BABYLON.Animation.Parse(parsedAnimation);
+                            scene.animations.push(animation);
+                            log += (index === 0 ? "\n\tAnimations:" : "");
+                            log += "\n\t\t" + animation.toString(fullDetails);
                         }
                     }
-                    // Spheres
-                    var spheres = geometries.spheres;
-                    if (spheres) {
-                        for (index = 0, cache = spheres.length; index < cache; index++) {
-                            var parsedSphere = spheres[index];
-                            BABYLON.Geometry.Primitives.Sphere.Parse(parsedSphere, scene);
+                    // Materials
+                    if (parsedData.materials) {
+                        for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
+                            var parsedMaterial = parsedData.materials[index];
+                            var mat = BABYLON.Material.Parse(parsedMaterial, scene, rootUrl);
+                            log += (index === 0 ? "\n\tMaterials:" : "");
+                            log += "\n\t\t" + mat.toString(fullDetails);
                         }
                     }
-                    // Cylinders
-                    var cylinders = geometries.cylinders;
-                    if (cylinders) {
-                        for (index = 0, cache = cylinders.length; index < cache; index++) {
-                            var parsedCylinder = cylinders[index];
-                            BABYLON.Geometry.Primitives.Cylinder.Parse(parsedCylinder, scene);
+                    if (parsedData.multiMaterials) {
+                        for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
+                            var parsedMultiMaterial = parsedData.multiMaterials[index];
+                            var mmat = BABYLON.Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                            log += (index === 0 ? "\n\tMultiMaterials:" : "");
+                            log += "\n\t\t" + mmat.toString(fullDetails);
                         }
                     }
-                    // Toruses
-                    var toruses = geometries.toruses;
-                    if (toruses) {
-                        for (index = 0, cache = toruses.length; index < cache; index++) {
-                            var parsedTorus = toruses[index];
-                            BABYLON.Geometry.Primitives.Torus.Parse(parsedTorus, scene);
+                    // Skeletons
+                    if (parsedData.skeletons) {
+                        for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
+                            var parsedSkeleton = parsedData.skeletons[index];
+                            var skeleton = BABYLON.Skeleton.Parse(parsedSkeleton, scene);
+                            log += (index === 0 ? "\n\tSkeletons:" : "");
+                            log += "\n\t\t" + skeleton.toString(fullDetails);
                         }
                     }
-                    // Grounds
-                    var grounds = geometries.grounds;
-                    if (grounds) {
-                        for (index = 0, cache = grounds.length; index < cache; index++) {
-                            var parsedGround = grounds[index];
-                            BABYLON.Geometry.Primitives.Ground.Parse(parsedGround, scene);
+                    // Geometries
+                    var geometries = parsedData.geometries;
+                    if (geometries) {
+                        // Boxes
+                        var boxes = geometries.boxes;
+                        if (boxes) {
+                            for (index = 0, cache = boxes.length; index < cache; index++) {
+                                var parsedBox = boxes[index];
+                                BABYLON.Geometry.Primitives.Box.Parse(parsedBox, scene);
+                            }
                         }
-                    }
-                    // Planes
-                    var planes = geometries.planes;
-                    if (planes) {
-                        for (index = 0, cache = planes.length; index < cache; index++) {
-                            var parsedPlane = planes[index];
-                            BABYLON.Geometry.Primitives.Plane.Parse(parsedPlane, scene);
+                        // Spheres
+                        var spheres = geometries.spheres;
+                        if (spheres) {
+                            for (index = 0, cache = spheres.length; index < cache; index++) {
+                                var parsedSphere = spheres[index];
+                                BABYLON.Geometry.Primitives.Sphere.Parse(parsedSphere, scene);
+                            }
                         }
-                    }
-                    // TorusKnots
-                    var torusKnots = geometries.torusKnots;
-                    if (torusKnots) {
-                        for (index = 0, cache = torusKnots.length; index < cache; index++) {
-                            var parsedTorusKnot = torusKnots[index];
-                            BABYLON.Geometry.Primitives.TorusKnot.Parse(parsedTorusKnot, scene);
+                        // Cylinders
+                        var cylinders = geometries.cylinders;
+                        if (cylinders) {
+                            for (index = 0, cache = cylinders.length; index < cache; index++) {
+                                var parsedCylinder = cylinders[index];
+                                BABYLON.Geometry.Primitives.Cylinder.Parse(parsedCylinder, scene);
+                            }
                         }
-                    }
-                    // VertexData
-                    var vertexData = geometries.vertexData;
-                    if (vertexData) {
-                        for (index = 0, cache = vertexData.length; index < cache; index++) {
-                            var parsedVertexData = vertexData[index];
-                            BABYLON.Geometry.Parse(parsedVertexData, scene, rootUrl);
+                        // Toruses
+                        var toruses = geometries.toruses;
+                        if (toruses) {
+                            for (index = 0, cache = toruses.length; index < cache; index++) {
+                                var parsedTorus = toruses[index];
+                                BABYLON.Geometry.Primitives.Torus.Parse(parsedTorus, scene);
+                            }
+                        }
+                        // Grounds
+                        var grounds = geometries.grounds;
+                        if (grounds) {
+                            for (index = 0, cache = grounds.length; index < cache; index++) {
+                                var parsedGround = grounds[index];
+                                BABYLON.Geometry.Primitives.Ground.Parse(parsedGround, scene);
+                            }
+                        }
+                        // Planes
+                        var planes = geometries.planes;
+                        if (planes) {
+                            for (index = 0, cache = planes.length; index < cache; index++) {
+                                var parsedPlane = planes[index];
+                                BABYLON.Geometry.Primitives.Plane.Parse(parsedPlane, scene);
+                            }
+                        }
+                        // TorusKnots
+                        var torusKnots = geometries.torusKnots;
+                        if (torusKnots) {
+                            for (index = 0, cache = torusKnots.length; index < cache; index++) {
+                                var parsedTorusKnot = torusKnots[index];
+                                BABYLON.Geometry.Primitives.TorusKnot.Parse(parsedTorusKnot, scene);
+                            }
+                        }
+                        // VertexData
+                        var vertexData = geometries.vertexData;
+                        if (vertexData) {
+                            for (index = 0, cache = vertexData.length; index < cache; index++) {
+                                var parsedVertexData = vertexData[index];
+                                BABYLON.Geometry.Parse(parsedVertexData, scene, rootUrl);
+                            }
                         }
                     }
-                }
-                // Meshes
-                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
-                    var parsedMesh = parsedData.meshes[index];
-                    BABYLON.Mesh.Parse(parsedMesh, scene, rootUrl);
-                }
-                // Cameras
-                for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
-                    var parsedCamera = parsedData.cameras[index];
-                    BABYLON.Camera.Parse(parsedCamera, scene);
-                }
-                if (parsedData.activeCameraID) {
-                    scene.setActiveCameraByID(parsedData.activeCameraID);
-                }
-                // Browsing all the graph to connect the dots
-                for (index = 0, cache = scene.cameras.length; index < cache; index++) {
-                    var camera = scene.cameras[index];
-                    if (camera._waitingParentId) {
-                        camera.parent = scene.getLastEntryByID(camera._waitingParentId);
-                        camera._waitingParentId = undefined;
+                    // Meshes
+                    for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
+                        var parsedMesh = parsedData.meshes[index];
+                        var mesh = BABYLON.Mesh.Parse(parsedMesh, scene, rootUrl);
+                        log += (index === 0 ? "\n\tMeshes:" : "");
+                        log += "\n\t\t" + mesh.toString(fullDetails);
                     }
-                }
-                for (index = 0, cache = scene.lights.length; index < cache; index++) {
-                    var light = scene.lights[index];
-                    if (light._waitingParentId) {
-                        light.parent = scene.getLastEntryByID(light._waitingParentId);
-                        light._waitingParentId = undefined;
+                    // Cameras
+                    for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
+                        var parsedCamera = parsedData.cameras[index];
+                        var camera = BABYLON.Camera.Parse(parsedCamera, scene);
+                        log += (index === 0 ? "\n\tCameras:" : "");
+                        log += "\n\t\t" + camera.toString(fullDetails);
                     }
-                }
-                // Sounds
-                var loadedSounds = [];
-                var loadedSound;
-                if (BABYLON.AudioEngine && parsedData.sounds) {
-                    for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
-                        var parsedSound = parsedData.sounds[index];
-                        if (BABYLON.Engine.audioEngine.canUseWebAudio) {
-                            if (!parsedSound.url)
-                                parsedSound.url = parsedSound.name;
-                            if (!loadedSounds[parsedSound.url]) {
-                                loadedSound = BABYLON.Sound.Parse(parsedSound, scene, rootUrl);
-                                loadedSounds[parsedSound.url] = loadedSound;
+                    if (parsedData.activeCameraID) {
+                        scene.setActiveCameraByID(parsedData.activeCameraID);
+                    }
+                    // Browsing all the graph to connect the dots
+                    for (index = 0, cache = scene.cameras.length; index < cache; index++) {
+                        var camera = scene.cameras[index];
+                        if (camera._waitingParentId) {
+                            camera.parent = scene.getLastEntryByID(camera._waitingParentId);
+                            camera._waitingParentId = undefined;
+                        }
+                    }
+                    for (index = 0, cache = scene.lights.length; index < cache; index++) {
+                        var light = scene.lights[index];
+                        if (light._waitingParentId) {
+                            light.parent = scene.getLastEntryByID(light._waitingParentId);
+                            light._waitingParentId = undefined;
+                        }
+                    }
+                    // Sounds
+                    var loadedSounds = [];
+                    var loadedSound;
+                    if (BABYLON.AudioEngine && parsedData.sounds) {
+                        for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
+                            var parsedSound = parsedData.sounds[index];
+                            if (BABYLON.Engine.audioEngine.canUseWebAudio) {
+                                if (!parsedSound.url)
+                                    parsedSound.url = parsedSound.name;
+                                if (!loadedSounds[parsedSound.url]) {
+                                    loadedSound = BABYLON.Sound.Parse(parsedSound, scene, rootUrl);
+                                    loadedSounds[parsedSound.url] = loadedSound;
+                                }
+                                else {
+                                    BABYLON.Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]);
+                                }
                             }
                             else {
-                                BABYLON.Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]);
+                                var emptySound = new BABYLON.Sound(parsedSound.name, null, scene);
                             }
                         }
-                        else {
-                            var emptySound = new BABYLON.Sound(parsedSound.name, null, scene);
+                        log += (index === 0 ? "\n\tSounds:" : "");
+                        log += "\n\t\t" + mat.toString(fullDetails);
+                    }
+                    loadedSounds = [];
+                    // Connect parents & children and parse actions
+                    for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                        var mesh = scene.meshes[index];
+                        if (mesh._waitingParentId) {
+                            mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
+                            mesh._waitingParentId = undefined;
+                        }
+                        if (mesh._waitingActions) {
+                            BABYLON.ActionManager.Parse(mesh._waitingActions, mesh, scene);
+                            mesh._waitingActions = undefined;
                         }
                     }
-                }
-                loadedSounds = [];
-                // Connect parents & children and parse actions
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    var mesh = scene.meshes[index];
-                    if (mesh._waitingParentId) {
-                        mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
-                        mesh._waitingParentId = undefined;
+                    // freeze world matrix application
+                    for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                        var currentMesh = scene.meshes[index];
+                        if (currentMesh._waitingFreezeWorldMatrix) {
+                            currentMesh.freezeWorldMatrix();
+                            currentMesh._waitingFreezeWorldMatrix = undefined;
+                        }
+                        else {
+                            currentMesh.computeWorldMatrix(true);
+                        }
                     }
-                    if (mesh._waitingActions) {
-                        BABYLON.ActionManager.Parse(mesh._waitingActions, mesh, scene);
-                        mesh._waitingActions = undefined;
+                    // Particles Systems
+                    if (parsedData.particleSystems) {
+                        for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
+                            var parsedParticleSystem = parsedData.particleSystems[index];
+                            BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
+                        }
                     }
-                }
-                // freeze world matrix application
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    var currentMesh = scene.meshes[index];
-                    if (currentMesh._waitingFreezeWorldMatrix) {
-                        currentMesh.freezeWorldMatrix();
-                        currentMesh._waitingFreezeWorldMatrix = undefined;
+                    // Lens flares
+                    if (parsedData.lensFlareSystems) {
+                        for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
+                            var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
+                            BABYLON.LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
+                        }
                     }
-                    else {
-                        currentMesh.computeWorldMatrix(true);
+                    // Shadows
+                    if (parsedData.shadowGenerators) {
+                        for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
+                            var parsedShadowGenerator = parsedData.shadowGenerators[index];
+                            BABYLON.ShadowGenerator.Parse(parsedShadowGenerator, scene);
+                        }
                     }
-                }
-                // Particles Systems
-                if (parsedData.particleSystems) {
-                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
-                        var parsedParticleSystem = parsedData.particleSystems[index];
-                        BABYLON.ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
+                    // Actions (scene)
+                    if (parsedData.actions) {
+                        BABYLON.ActionManager.Parse(parsedData.actions, null, scene);
                     }
+                    // Finish
+                    return true;
                 }
-                // Lens flares
-                if (parsedData.lensFlareSystems) {
-                    for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
-                        var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
-                        BABYLON.LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
-                    }
+                catch (err) {
+                    BABYLON.Tools.Log(logOperation("importScene", parsedData.producer) + log);
+                    log = null;
+                    throw err;
                 }
-                // Shadows
-                if (parsedData.shadowGenerators) {
-                    for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
-                        var parsedShadowGenerator = parsedData.shadowGenerators[index];
-                        BABYLON.ShadowGenerator.Parse(parsedShadowGenerator, scene);
+                finally {
+                    if (log !== null && BABYLON.SceneLoader.loggingLevel !== BABYLON.SceneLoader.NO_LOGGING) {
+                        BABYLON.Tools.Log(logOperation("importScene", parsedData.producer) + (BABYLON.SceneLoader.loggingLevel !== BABYLON.SceneLoader.MINIMAL_LOGGING ? log : ""));
                     }
                 }
-                // Actions (scene)
-                if (parsedData.actions) {
-                    BABYLON.ActionManager.Parse(parsedData.actions, null, scene);
-                }
-                // Finish
-                return true;
             }
         });
     })(Internals = BABYLON.Internals || (BABYLON.Internals = {}));

+ 426 - 356
src/Loading/Plugins/babylon.babylonFileLoader.ts

@@ -25,424 +25,494 @@
         return false;
     };
 
+    var logOperation = (operation, producer) => {
+        return operation + " of " + (producer ? producer.file + " from " + producer.name + " version: " + producer.version + ", exporter version: " + producer.exporter_version : "unknown");
+    }
+
     SceneLoader.RegisterPlugin({
         extensions: ".babylon",
         importMesh: (meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]): boolean => {
-            var parsedData = JSON.parse(data);
-
-            var loadedSkeletonsIds = [];
-            var loadedMaterialsIds = [];
-            var hierarchyIds = [];
-            var index: number;
-            var cache: number;
-            for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
-                var parsedMesh = parsedData.meshes[index];
-
-                if (!meshesNames || isDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
-                    if (meshesNames instanceof Array) {
-                        // Remove found mesh name from list.
-                        delete meshesNames[meshesNames.indexOf(parsedMesh.name)];
-                    }
-
-                    //Geometry?
-                    if (parsedMesh.geometryId) {
-                        //does the file contain geometries?
-                        if (parsedData.geometries) {
-                            //find the correct geometry and add it to the scene
-                            var found: boolean = false;
-                            ["boxes", "spheres", "cylinders", "toruses", "grounds", "planes", "torusKnots", "vertexData"].forEach((geometryType: string) => {
-                                if (found || !parsedData.geometries[geometryType] || !(parsedData.geometries[geometryType] instanceof Array)) {
-                                    return;
-                                } else {
-                                    parsedData.geometries[geometryType].forEach((parsedGeometryData) => {
-                                        if (parsedGeometryData.id === parsedMesh.geometryId) {
-                                            switch (geometryType) {
-                                                case "boxes":
-                                                    Geometry.Primitives.Box.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "spheres":
-                                                    Geometry.Primitives.Sphere.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "cylinders":
-                                                    Geometry.Primitives.Cylinder.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "toruses":
-                                                    Geometry.Primitives.Torus.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "grounds":
-                                                    Geometry.Primitives.Ground.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "planes":
-                                                    Geometry.Primitives.Plane.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "torusKnots":
-                                                    Geometry.Primitives.TorusKnot.Parse(parsedGeometryData, scene);
-                                                    break;
-                                                case "vertexData":
-                                                    Geometry.Parse(parsedGeometryData, scene, rootUrl);
-                                                    break;
+            // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
+            // when SceneLoader.debugLogging = true (default), or exception encountered.
+            // Everything stored in var log instead of writing separate lines to support only writing in exception,
+            // and avoid problems with multiple concurrent .babylon loads.
+            var log = "importMesh has failed JSON parse";
+            try {
+                var parsedData = JSON.parse(data);
+                log = "";
+                var fullDetails = SceneLoader.loggingLevel === SceneLoader.DETAILED_LOGGING;
+
+                var loadedSkeletonsIds = [];
+                var loadedMaterialsIds = [];
+                var hierarchyIds = [];
+                var index: number;
+                var cache: number;
+                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
+                    var parsedMesh = parsedData.meshes[index];
+
+                    if (!meshesNames || isDescendantOf(parsedMesh, meshesNames, hierarchyIds)) {
+                        if (meshesNames instanceof Array) {
+                            // Remove found mesh name from list.
+                            delete meshesNames[meshesNames.indexOf(parsedMesh.name)];
+                        }
+    
+                        //Geometry?
+                        if (parsedMesh.geometryId) {
+                            //does the file contain geometries?
+                            if (parsedData.geometries) {
+                                //find the correct geometry and add it to the scene
+                                var found: boolean = false;
+                                ["boxes", "spheres", "cylinders", "toruses", "grounds", "planes", "torusKnots", "vertexData"].forEach((geometryType: string) => {
+                                    if (found || !parsedData.geometries[geometryType] || !(parsedData.geometries[geometryType] instanceof Array)) {
+                                        return;
+                                    } else {
+                                        parsedData.geometries[geometryType].forEach((parsedGeometryData) => {
+                                            if (parsedGeometryData.id === parsedMesh.geometryId) {
+                                                switch (geometryType) {
+                                                    case "boxes":
+                                                        Geometry.Primitives.Box.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "spheres":
+                                                        Geometry.Primitives.Sphere.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "cylinders":
+                                                        Geometry.Primitives.Cylinder.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "toruses":
+                                                        Geometry.Primitives.Torus.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "grounds":
+                                                        Geometry.Primitives.Ground.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "planes":
+                                                        Geometry.Primitives.Plane.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "torusKnots":
+                                                        Geometry.Primitives.TorusKnot.Parse(parsedGeometryData, scene);
+                                                        break;
+                                                    case "vertexData":
+                                                        Geometry.Parse(parsedGeometryData, scene, rootUrl);
+                                                        break;
+                                                }
+                                                found = true;
                                             }
-                                            found = true;
-                                        }
-                                    });
+                                        });
 
+                                    }
+                                });
+                                if (!found) {
+                                    Tools.Warn("Geometry not found for mesh " + parsedMesh.id);
                                 }
-                            });
-                            if (!found) {
-                                Tools.Warn("Geometry not found for mesh " + parsedMesh.id);
                             }
                         }
-                    }
-
-                    // Material ?
-                    if (parsedMesh.materialId) {
-                        var materialFound = (loadedMaterialsIds.indexOf(parsedMesh.materialId) !== -1);
-                        if (!materialFound && parsedData.multiMaterials) {
-                            for (var multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
-                                var parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
-                                if (parsedMultiMaterial.id === parsedMesh.materialId) {
-                                    for (var matIndex = 0, matCache = parsedMultiMaterial.materials.length; matIndex < matCache; matIndex++) {
-                                        var subMatId = parsedMultiMaterial.materials[matIndex];
-                                        loadedMaterialsIds.push(subMatId);
-                                        parseMaterialById(subMatId, parsedData, scene, rootUrl);
+    
+                        // Material ?
+                        if (parsedMesh.materialId) {
+                            var materialFound = (loadedMaterialsIds.indexOf(parsedMesh.materialId) !== -1);
+                            if (!materialFound && parsedData.multiMaterials) {
+                                for (var multimatIndex = 0, multimatCache = parsedData.multiMaterials.length; multimatIndex < multimatCache; multimatIndex++) {
+                                    var parsedMultiMaterial = parsedData.multiMaterials[multimatIndex];
+                                    if (parsedMultiMaterial.id === parsedMesh.materialId) {
+                                        for (var matIndex = 0, matCache = parsedMultiMaterial.materials.length; matIndex < matCache; matIndex++) {
+                                            var subMatId = parsedMultiMaterial.materials[matIndex];
+                                            loadedMaterialsIds.push(subMatId);
+                                            var mat = parseMaterialById(subMatId, parsedData, scene, rootUrl);
+                                            log += "\n\tMaterial " + mat.toString(fullDetails);
+                                        }
+                                        loadedMaterialsIds.push(parsedMultiMaterial.id);
+                                        var mmat = Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                                        materialFound = true;
+                                        log += "\n\tMulti-Material " + mmat.toString(fullDetails);
+                                        break;
                                     }
-                                    loadedMaterialsIds.push(parsedMultiMaterial.id);
-                                    Material.ParseMultiMaterial(parsedMultiMaterial, scene);
-                                    materialFound = true;
-                                    break;
                                 }
                             }
-                        }
 
-                        if (!materialFound) {
-                            loadedMaterialsIds.push(parsedMesh.materialId);
-                            if (!parseMaterialById(parsedMesh.materialId, parsedData, scene, rootUrl)) {
-                                Tools.Warn("Material not found for mesh " + parsedMesh.id);
+                            if (!materialFound) {
+                                loadedMaterialsIds.push(parsedMesh.materialId);
+                                var mat = parseMaterialById(parsedMesh.materialId, parsedData, scene, rootUrl);
+                                if (!mat) {
+                                    Tools.Warn("Material not found for mesh " + parsedMesh.id);
+                                } else {
+                                    log += "\n\tMaterial " + mat.toString(fullDetails);
+                                }
                             }
                         }
-                    }
-
-                    // Skeleton ?
-                    if (parsedMesh.skeletonId > -1 && scene.skeletons) {
-                        var skeletonAlreadyLoaded = (loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1);
-                        if (!skeletonAlreadyLoaded) {
-                            for (var skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
-                                var parsedSkeleton = parsedData.skeletons[skeletonIndex];
-                                if (parsedSkeleton.id === parsedMesh.skeletonId) {
-                                    skeletons.push(Skeleton.Parse(parsedSkeleton, scene));
-                                    loadedSkeletonsIds.push(parsedSkeleton.id);
+    
+                        // Skeleton ?
+                        if (parsedMesh.skeletonId > -1 && scene.skeletons) {
+                            var skeletonAlreadyLoaded = (loadedSkeletonsIds.indexOf(parsedMesh.skeletonId) > -1);
+                            if (!skeletonAlreadyLoaded) {
+                                for (var skeletonIndex = 0, skeletonCache = parsedData.skeletons.length; skeletonIndex < skeletonCache; skeletonIndex++) {
+                                    var parsedSkeleton = parsedData.skeletons[skeletonIndex];
+                                    if (parsedSkeleton.id === parsedMesh.skeletonId) {
+                                        var skeleton = Skeleton.Parse(parsedSkeleton, scene);
+                                        skeletons.push(skeleton);
+                                        loadedSkeletonsIds.push(parsedSkeleton.id);
+                                        log += "\n\tSkeleton " + skeleton.toString(fullDetails);
+                                    }
                                 }
                             }
                         }
-                    }
 
-                    var mesh = Mesh.Parse(parsedMesh, scene, rootUrl);
-                    meshes.push(mesh);
+                        var mesh = Mesh.Parse(parsedMesh, scene, rootUrl);
+                        meshes.push(mesh);
+                        log += "\n\tMesh " + mesh.toString(fullDetails);
+                    }
                 }
-            }
-
-            // Connecting parents
-            var currentMesh: AbstractMesh;
-            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                currentMesh = scene.meshes[index];
-                if (currentMesh._waitingParentId) {
-                    currentMesh.parent = scene.getLastEntryByID(currentMesh._waitingParentId);
-                    currentMesh._waitingParentId = undefined;
+    
+                // Connecting parents
+                var currentMesh: AbstractMesh;
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                    currentMesh = scene.meshes[index];
+                    if (currentMesh._waitingParentId) {
+                        currentMesh.parent = scene.getLastEntryByID(currentMesh._waitingParentId);
+                        currentMesh._waitingParentId = undefined;
+                    }
                 }
-            }
-
-            // freeze and compute world matrix application
-            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                currentMesh = scene.meshes[index];
-                if (currentMesh._waitingFreezeWorldMatrix) {
-                    currentMesh.freezeWorldMatrix();
-                    currentMesh._waitingFreezeWorldMatrix = undefined;
-                } else {
-                    currentMesh.computeWorldMatrix(true);
+    
+                // freeze and compute world matrix application
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                    currentMesh = scene.meshes[index];
+                    if (currentMesh._waitingFreezeWorldMatrix) {
+                        currentMesh.freezeWorldMatrix();
+                        currentMesh._waitingFreezeWorldMatrix = undefined;
+                    } else {
+                        currentMesh.computeWorldMatrix(true);
+                    }
                 }
-            }
-
-            // Particles
-            if (parsedData.particleSystems) {
-                for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
-                    var parsedParticleSystem = parsedData.particleSystems[index];
-                    if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
-                        particleSystems.push(ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl));
+    
+                // Particles
+                if (parsedData.particleSystems) {
+                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
+                        var parsedParticleSystem = parsedData.particleSystems[index];
+                        if (hierarchyIds.indexOf(parsedParticleSystem.emitterId) !== -1) {
+                            particleSystems.push(ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl));
+                        }
                     }
                 }
-            }
+                return true;
 
-            return true;
-        },
-        load: (scene: Scene, data: string, rootUrl: string): boolean => {
-            var parsedData = JSON.parse(data);
+            } catch (err) {
+                Tools.Log(logOperation("importMesh", parsedData.producer) + log);
+                log = null;
+                throw err;
 
-            // Scene
-            scene.useDelayedTextureLoading = parsedData.useDelayedTextureLoading && !BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental;
-            scene.autoClear = parsedData.autoClear;
-            scene.clearColor = BABYLON.Color3.FromArray(parsedData.clearColor);
-            scene.ambientColor = BABYLON.Color3.FromArray(parsedData.ambientColor);
-            if (parsedData.gravity) {
-                scene.gravity = BABYLON.Vector3.FromArray(parsedData.gravity);
-            }
-            
-            // Fog
-            if (parsedData.fogMode && parsedData.fogMode !== 0) {
-                scene.fogMode = parsedData.fogMode;
-                scene.fogColor = BABYLON.Color3.FromArray(parsedData.fogColor);
-                scene.fogStart = parsedData.fogStart;
-                scene.fogEnd = parsedData.fogEnd;
-                scene.fogDensity = parsedData.fogDensity;
-            }
-            
-            //Physics
-            if (parsedData.physicsEnabled) {
-                var physicsPlugin;
-                if (parsedData.physicsEngine === "cannon") {
-                    physicsPlugin = new BABYLON.CannonJSPlugin();
-                } else if (parsedData.physicsEngine === "oimo") {
-                    physicsPlugin = new BABYLON.OimoJSPlugin();
+            } finally {
+                if (log !== null && SceneLoader.loggingLevel !== SceneLoader.NO_LOGGING) {
+                    Tools.Log(logOperation("importMesh", parsedData.producer) + (SceneLoader.loggingLevel !== SceneLoader.MINIMAL_LOGGING ? log : ""));
                 }
-                //else - default engine, which is currently oimo
-                var physicsGravity = parsedData.physicsGravity ? BABYLON.Vector3.FromArray(parsedData.physicsGravity) : null;
-                scene.enablePhysics(physicsGravity, physicsPlugin);
             }
-            
-            //collisions, if defined. otherwise, default is true
-            if (parsedData.collisionsEnabled != undefined) {
-                scene.collisionsEnabled = parsedData.collisionsEnabled;
-            }
-            scene.workerCollisions = !!parsedData.workerCollisions;
-
-            var index: number;
-            var cache: number;
-            // Lights
-            for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
-                var parsedLight = parsedData.lights[index];
-                Light.Parse(parsedLight, scene);
-            }
-
-            // Animations
-            if (parsedData.animations) {
-                for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
-                    var parsedAnimation = parsedData.animations[index];
-                    scene.animations.push(Animation.Parse(parsedAnimation));
+        },
+        load: (scene: Scene, data: string, rootUrl: string): boolean => {
+            // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
+            // when SceneLoader.debugLogging = true (default), or exception encountered.
+            // Everything stored in var log instead of writing separate lines to support only writing in exception,
+            // and avoid problems with multiple concurrent .babylon loads.
+            var log = "importScene has failed JSON parse";
+            try {
+                var parsedData = JSON.parse(data);
+                log = "";
+                var fullDetails = SceneLoader.loggingLevel === SceneLoader.DETAILED_LOGGING;
+                
+                // Scene
+                scene.useDelayedTextureLoading = parsedData.useDelayedTextureLoading && !BABYLON.SceneLoader.ForceFullSceneLoadingForIncremental;
+                scene.autoClear = parsedData.autoClear;
+                scene.clearColor = BABYLON.Color3.FromArray(parsedData.clearColor);
+                scene.ambientColor = BABYLON.Color3.FromArray(parsedData.ambientColor);
+                if (parsedData.gravity) {
+                    scene.gravity = BABYLON.Vector3.FromArray(parsedData.gravity);
                 }
-            }
-
-            if (parsedData.autoAnimate) {
-                scene.beginAnimation(scene, parsedData.autoAnimateFrom, parsedData.autoAnimateTo, parsedData.autoAnimateLoop, parsedData.autoAnimateSpeed || 1.0);
-            }
-
-            // Materials
-            if (parsedData.materials) {
-                for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
-                    var parsedMaterial = parsedData.materials[index];
-                    Material.Parse(parsedMaterial, scene, rootUrl);
+                
+                // Fog
+                if (parsedData.fogMode && parsedData.fogMode !== 0) {
+                    scene.fogMode = parsedData.fogMode;
+                    scene.fogColor = BABYLON.Color3.FromArray(parsedData.fogColor);
+                    scene.fogStart = parsedData.fogStart;
+                    scene.fogEnd = parsedData.fogEnd;
+                    scene.fogDensity = parsedData.fogDensity;
+                    log += "\tFog mode for scene:  ";
+                    switch (scene.fogMode) {
+                        // getters not compiling, so using hardcoded
+                        case 1: log += "exp\n"; break;
+                        case 2: log += "exp2\n"; break;
+                        case 3: log += "linear\n"; break;
+                    }
                 }
-            }
-
-            if (parsedData.multiMaterials) {
-                for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
-                    var parsedMultiMaterial = parsedData.multiMaterials[index];
-                    Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                
+                //Physics
+                if (parsedData.physicsEnabled) {
+                    var physicsPlugin;
+                    if (parsedData.physicsEngine === "cannon") {
+                        physicsPlugin = new BABYLON.CannonJSPlugin();
+                    } else if (parsedData.physicsEngine === "oimo") {
+                        physicsPlugin = new BABYLON.OimoJSPlugin();
+                    }
+                    log = "\tPhysics engine " + (parsedData.physicsEngine ? parsedData.physicsEngine : "oimo") + " enabled\n";
+                    //else - default engine, which is currently oimo
+                    var physicsGravity = parsedData.physicsGravity ? BABYLON.Vector3.FromArray(parsedData.physicsGravity) : null;
+                    scene.enablePhysics(physicsGravity, physicsPlugin);
                 }
-            }
-
-            // Skeletons
-            if (parsedData.skeletons) {
-                for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
-                    var parsedSkeleton = parsedData.skeletons[index];
-                    Skeleton.Parse(parsedSkeleton, scene);
+                
+                //collisions, if defined. otherwise, default is true
+                if (parsedData.collisionsEnabled != undefined) {
+                    scene.collisionsEnabled = parsedData.collisionsEnabled;
                 }
-            }
-
-            // Geometries
-            var geometries = parsedData.geometries;
-            if (geometries) {
-                // Boxes
-                var boxes = geometries.boxes;
-                if (boxes) {
-                    for (index = 0, cache = boxes.length; index < cache; index++) {
-                        var parsedBox = boxes[index];
-                        Geometry.Primitives.Box.Parse(parsedBox, scene);
-                    }
+                scene.workerCollisions = !!parsedData.workerCollisions;
+
+                var index: number;
+                var cache: number;
+                // Lights
+                for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
+                    var parsedLight = parsedData.lights[index];
+                    var light = Light.Parse(parsedLight, scene);
+                    log += (index === 0 ? "\n\tLights:" : "");
+                    log += "\n\t\t" + light.toString(fullDetails);
                 }
-
-                // Spheres
-                var spheres = geometries.spheres;
-                if (spheres) {
-                    for (index = 0, cache = spheres.length; index < cache; index++) {
-                        var parsedSphere = spheres[index];
-                        Geometry.Primitives.Sphere.Parse(parsedSphere, scene);
+    
+                // Animations
+                if (parsedData.animations) {
+                    for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
+                        var parsedAnimation = parsedData.animations[index];
+                        var animation = Animation.Parse(parsedAnimation);
+                        scene.animations.push(animation);
+                        log += (index === 0 ? "\n\tAnimations:" : "");
+                        log += "\n\t\t" + animation.toString(fullDetails);
                     }
                 }
-
-                // Cylinders
-                var cylinders = geometries.cylinders;
-                if (cylinders) {
-                    for (index = 0, cache = cylinders.length; index < cache; index++) {
-                        var parsedCylinder = cylinders[index];
-                        Geometry.Primitives.Cylinder.Parse(parsedCylinder, scene);
+    
+                // Materials
+                if (parsedData.materials) {
+                    for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
+                        var parsedMaterial = parsedData.materials[index];
+                        var mat = Material.Parse(parsedMaterial, scene, rootUrl);
+                        log += (index === 0 ? "\n\tMaterials:" : "");
+                        log += "\n\t\t" + mat.toString(fullDetails);
                     }
                 }
 
-                // Toruses
-                var toruses = geometries.toruses;
-                if (toruses) {
-                    for (index = 0, cache = toruses.length; index < cache; index++) {
-                        var parsedTorus = toruses[index];
-                        Geometry.Primitives.Torus.Parse(parsedTorus, scene);
+                if (parsedData.multiMaterials) {
+                    for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
+                        var parsedMultiMaterial = parsedData.multiMaterials[index];
+                        var mmat = Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                        log += (index === 0 ? "\n\tMultiMaterials:" : "");
+                        log += "\n\t\t" + mmat.toString(fullDetails);
                     }
                 }
-
-                // Grounds
-                var grounds = geometries.grounds;
-                if (grounds) {
-                    for (index = 0, cache = grounds.length; index < cache; index++) {
-                        var parsedGround = grounds[index];
-                        Geometry.Primitives.Ground.Parse(parsedGround, scene);
+    
+                // Skeletons
+                if (parsedData.skeletons) {
+                    for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
+                        var parsedSkeleton = parsedData.skeletons[index];
+                        var skeleton = Skeleton.Parse(parsedSkeleton, scene);
+                        log += (index === 0 ? "\n\tSkeletons:" : "");
+                        log += "\n\t\t" + skeleton.toString(fullDetails);
                     }
                 }
-
-                // Planes
-                var planes = geometries.planes;
-                if (planes) {
-                    for (index = 0, cache = planes.length; index < cache; index++) {
-                        var parsedPlane = planes[index];
-                        Geometry.Primitives.Plane.Parse(parsedPlane, scene);
+    
+                // Geometries
+                var geometries = parsedData.geometries;
+                if (geometries) {
+                    // Boxes
+                    var boxes = geometries.boxes;
+                    if (boxes) {
+                        for (index = 0, cache = boxes.length; index < cache; index++) {
+                            var parsedBox = boxes[index];
+                            Geometry.Primitives.Box.Parse(parsedBox, scene);
+                        }
+                    }
+    
+                    // Spheres
+                    var spheres = geometries.spheres;
+                    if (spheres) {
+                        for (index = 0, cache = spheres.length; index < cache; index++) {
+                            var parsedSphere = spheres[index];
+                            Geometry.Primitives.Sphere.Parse(parsedSphere, scene);
+                        }
+                    }
+    
+                    // Cylinders
+                    var cylinders = geometries.cylinders;
+                    if (cylinders) {
+                        for (index = 0, cache = cylinders.length; index < cache; index++) {
+                            var parsedCylinder = cylinders[index];
+                            Geometry.Primitives.Cylinder.Parse(parsedCylinder, scene);
+                        }
+                    }
+    
+                    // Toruses
+                    var toruses = geometries.toruses;
+                    if (toruses) {
+                        for (index = 0, cache = toruses.length; index < cache; index++) {
+                            var parsedTorus = toruses[index];
+                            Geometry.Primitives.Torus.Parse(parsedTorus, scene);
+                        }
+                    }
+    
+                    // Grounds
+                    var grounds = geometries.grounds;
+                    if (grounds) {
+                        for (index = 0, cache = grounds.length; index < cache; index++) {
+                            var parsedGround = grounds[index];
+                            Geometry.Primitives.Ground.Parse(parsedGround, scene);
+                        }
+                    }
+    
+                    // Planes
+                    var planes = geometries.planes;
+                    if (planes) {
+                        for (index = 0, cache = planes.length; index < cache; index++) {
+                            var parsedPlane = planes[index];
+                            Geometry.Primitives.Plane.Parse(parsedPlane, scene);
+                        }
+                    }
+    
+                    // TorusKnots
+                    var torusKnots = geometries.torusKnots;
+                    if (torusKnots) {
+                        for (index = 0, cache = torusKnots.length; index < cache; index++) {
+                            var parsedTorusKnot = torusKnots[index];
+                            Geometry.Primitives.TorusKnot.Parse(parsedTorusKnot, scene);
+                        }
+                    }
+    
+                    // VertexData
+                    var vertexData = geometries.vertexData;
+                    if (vertexData) {
+                        for (index = 0, cache = vertexData.length; index < cache; index++) {
+                            var parsedVertexData = vertexData[index];
+                            Geometry.Parse(parsedVertexData, scene, rootUrl);
+                        }
                     }
                 }
-
-                // TorusKnots
-                var torusKnots = geometries.torusKnots;
-                if (torusKnots) {
-                    for (index = 0, cache = torusKnots.length; index < cache; index++) {
-                        var parsedTorusKnot = torusKnots[index];
-                        Geometry.Primitives.TorusKnot.Parse(parsedTorusKnot, scene);
+    
+                // Meshes
+                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
+                    var parsedMesh = parsedData.meshes[index];
+                    var mesh = <AbstractMesh>Mesh.Parse(parsedMesh, scene, rootUrl);
+                    log += (index === 0 ? "\n\tMeshes:" : "");
+                    log += "\n\t\t" + mesh.toString(fullDetails);
+                }
+    
+                // Cameras
+                for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
+                    var parsedCamera = parsedData.cameras[index];
+                    var camera = Camera.Parse(parsedCamera, scene);
+                    log += (index === 0 ? "\n\tCameras:" : "");
+                    log += "\n\t\t" + camera.toString(fullDetails);
+                }
+                if (parsedData.activeCameraID) {
+                    scene.setActiveCameraByID(parsedData.activeCameraID);
+                }
+    
+                // Browsing all the graph to connect the dots
+                for (index = 0, cache = scene.cameras.length; index < cache; index++) {
+                    var camera = scene.cameras[index];
+                    if (camera._waitingParentId) {
+                        camera.parent = scene.getLastEntryByID(camera._waitingParentId);
+                        camera._waitingParentId = undefined;
                     }
                 }
 
-                // VertexData
-                var vertexData = geometries.vertexData;
-                if (vertexData) {
-                    for (index = 0, cache = vertexData.length; index < cache; index++) {
-                        var parsedVertexData = vertexData[index];
-                        Geometry.Parse(parsedVertexData, scene, rootUrl);
+                for (index = 0, cache = scene.lights.length; index < cache; index++) {
+                    var light = scene.lights[index];
+                    if (light._waitingParentId) {
+                        light.parent = scene.getLastEntryByID(light._waitingParentId);
+                        light._waitingParentId = undefined;
                     }
                 }
-            }
-
-            // Meshes
-            for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
-                var parsedMesh = parsedData.meshes[index];
-                Mesh.Parse(parsedMesh, scene, rootUrl);
-            }
-
-            // Cameras
-            for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
-                var parsedCamera = parsedData.cameras[index];
-                Camera.Parse(parsedCamera, scene);
-            }
-            if (parsedData.activeCameraID) {
-                scene.setActiveCameraByID(parsedData.activeCameraID);
-            }
-
-            // Browsing all the graph to connect the dots
-            for (index = 0, cache = scene.cameras.length; index < cache; index++) {
-                var camera = scene.cameras[index];
-                if (camera._waitingParentId) {
-                    camera.parent = scene.getLastEntryByID(camera._waitingParentId);
-                    camera._waitingParentId = undefined;
+    
+                // Sounds
+                var loadedSounds: Sound[] = [];
+                var loadedSound: Sound;
+                if (AudioEngine && parsedData.sounds) {
+                    for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
+                        var parsedSound = parsedData.sounds[index];
+                        if (Engine.audioEngine.canUseWebAudio) {
+                            if (!parsedSound.url) parsedSound.url = parsedSound.name;
+                            if (!loadedSounds[parsedSound.url]) {
+                                loadedSound = Sound.Parse(parsedSound, scene, rootUrl);
+                                loadedSounds[parsedSound.url] = loadedSound;
+                            }
+                            else {
+                                Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]);
+                            }
+                        } else {
+                            var emptySound = new Sound(parsedSound.name, null, scene);
+                        }
+                    }
+                    log += (index === 0 ? "\n\tSounds:" : "");
+                    log += "\n\t\t" + mat.toString(fullDetails);
                 }
-            }
 
-            for (index = 0, cache = scene.lights.length; index < cache; index++) {
-                var light = scene.lights[index];
-                if (light._waitingParentId) {
-                    light.parent = scene.getLastEntryByID(light._waitingParentId);
-                    light._waitingParentId = undefined;
+                loadedSounds = [];
+    
+                // Connect parents & children and parse actions
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                    var mesh = scene.meshes[index];
+                    if (mesh._waitingParentId) {
+                        mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
+                        mesh._waitingParentId = undefined;
+                    }
+                    if (mesh._waitingActions) {
+                        ActionManager.Parse(mesh._waitingActions, mesh, scene);
+                        mesh._waitingActions = undefined;
+                    }
                 }
-            }
-
-            // Sounds
-            var loadedSounds: Sound[] = [];
-            var loadedSound: Sound;
-            if (AudioEngine && parsedData.sounds) {
-                for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
-                    var parsedSound = parsedData.sounds[index];
-                    if (Engine.audioEngine.canUseWebAudio) {
-                        if (!parsedSound.url) parsedSound.url = parsedSound.name;
-                        if (!loadedSounds[parsedSound.url]) {
-                            loadedSound = Sound.Parse(parsedSound, scene, rootUrl);
-                            loadedSounds[parsedSound.url] = loadedSound;
-                        }
-                        else {
-                            Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]);
-                        }
+    
+                // freeze world matrix application
+                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                    var currentMesh = scene.meshes[index];
+                    if (currentMesh._waitingFreezeWorldMatrix) {
+                        currentMesh.freezeWorldMatrix();
+                        currentMesh._waitingFreezeWorldMatrix = undefined;
                     } else {
-                        var emptySound = new Sound(parsedSound.name, null, scene);
+                        currentMesh.computeWorldMatrix(true);
                     }
                 }
-            }
-
-            loadedSounds = [];
-
-            // Connect parents & children and parse actions
-            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                var mesh = scene.meshes[index];
-                if (mesh._waitingParentId) {
-                    mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
-                    mesh._waitingParentId = undefined;
+    
+                // Particles Systems
+                if (parsedData.particleSystems) {
+                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
+                        var parsedParticleSystem = parsedData.particleSystems[index];
+                        ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
+                    }
                 }
-                if (mesh._waitingActions) {
-                    ActionManager.Parse(mesh._waitingActions, mesh, scene);
-                    mesh._waitingActions = undefined;
+    
+                // Lens flares
+                if (parsedData.lensFlareSystems) {
+                    for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
+                        var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
+                        LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
+                    }
                 }
-            }
-
-            // freeze world matrix application
-            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                var currentMesh = scene.meshes[index];
-                if (currentMesh._waitingFreezeWorldMatrix) {
-                    currentMesh.freezeWorldMatrix();
-                    currentMesh._waitingFreezeWorldMatrix = undefined;
-                } else {
-                    currentMesh.computeWorldMatrix(true);
+    
+                // Shadows
+                if (parsedData.shadowGenerators) {
+                    for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
+                        var parsedShadowGenerator = parsedData.shadowGenerators[index];
+                        ShadowGenerator.Parse(parsedShadowGenerator, scene);
+                    }
                 }
-            }
-
-            // Particles Systems
-            if (parsedData.particleSystems) {
-                for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
-                    var parsedParticleSystem = parsedData.particleSystems[index];
-                    ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
+    
+                // Actions (scene)
+                if (parsedData.actions) {
+                    ActionManager.Parse(parsedData.actions, null, scene);
                 }
-            }
+    
+                // Finish
+                return true;
 
-            // Lens flares
-            if (parsedData.lensFlareSystems) {
-                for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
-                    var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
-                    LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
-                }
-            }
+            } catch (err) {
+                Tools.Log(logOperation("importScene", parsedData.producer) + log);
+                log = null;
+                throw err;
 
-            // Shadows
-            if (parsedData.shadowGenerators) {
-                for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
-                    var parsedShadowGenerator = parsedData.shadowGenerators[index];
-                    ShadowGenerator.Parse(parsedShadowGenerator, scene);
+            } finally {
+                if (log !== null && SceneLoader.loggingLevel !== SceneLoader.NO_LOGGING) {
+                    Tools.Log(logOperation("importScene", parsedData.producer) + (SceneLoader.loggingLevel !== SceneLoader.MINIMAL_LOGGING ? log : ""));
                 }
             }
-
-            // Actions (scene)
-            if (parsedData.actions) {
-                ActionManager.Parse(parsedData.actions, null, scene);
-            }
-
-            // Finish
-            return true;
         }
     });
 }

+ 43 - 0
src/Loading/babylon.sceneLoader.js

@@ -3,6 +3,34 @@ var BABYLON;
     var SceneLoader = (function () {
         function SceneLoader() {
         }
+        Object.defineProperty(SceneLoader, "NO_LOGGING", {
+            get: function () {
+                return 0;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SceneLoader, "MINIMAL_LOGGING", {
+            get: function () {
+                return 1;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SceneLoader, "SUMMARY_LOGGING", {
+            get: function () {
+                return 2;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SceneLoader, "DETAILED_LOGGING", {
+            get: function () {
+                return 3;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(SceneLoader, "ForceFullSceneLoadingForIncremental", {
             get: function () {
                 return SceneLoader._ForceFullSceneLoadingForIncremental;
@@ -23,6 +51,16 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(SceneLoader, "loggingLevel", {
+            get: function () {
+                return SceneLoader._loggingLevel;
+            },
+            set: function (value) {
+                SceneLoader._loggingLevel = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
         SceneLoader._getPluginForFilename = function (sceneFilename) {
             var dotPosition = sceneFilename.lastIndexOf(".");
             var queryStringPosition = sceneFilename.indexOf("?");
@@ -113,6 +151,10 @@ var BABYLON;
         * @param engine is the instance of BABYLON.Engine to use to create the scene
         */
         SceneLoader.Load = function (rootUrl, sceneFilename, engine, onsuccess, progressCallBack, onerror) {
+            if (!SceneLoader._warned) {
+                BABYLON.Tools.Warn("SceneLoader.Load deprecated since 2.4.  Use SceneLoader.Append.");
+                SceneLoader._warned = true;
+            }
             SceneLoader.Append(rootUrl, sceneFilename, new BABYLON.Scene(engine), onsuccess, progressCallBack, onerror);
         };
         /**
@@ -177,6 +219,7 @@ var BABYLON;
         // Flags
         SceneLoader._ForceFullSceneLoadingForIncremental = false;
         SceneLoader._ShowLoadingScreen = true;
+        SceneLoader._loggingLevel = SceneLoader.NO_LOGGING;
         // Members
         SceneLoader._registeredPlugins = new Array();
         return SceneLoader;

+ 31 - 0
src/Loading/babylon.sceneLoader.ts

@@ -10,6 +10,24 @@
         private static _ForceFullSceneLoadingForIncremental = false;
         private static _ShowLoadingScreen = true;
 
+        public static get NO_LOGGING(): number {
+            return 0;
+        }
+
+        public static get MINIMAL_LOGGING(): number {
+            return 1;
+        }
+
+        public static get SUMMARY_LOGGING(): number {
+            return 2;
+        }
+
+        public static get DETAILED_LOGGING(): number {
+            return 3;
+        }
+
+        private static _loggingLevel = SceneLoader.NO_LOGGING;
+
         public static get ForceFullSceneLoadingForIncremental() {
             return SceneLoader._ForceFullSceneLoadingForIncremental;
         }
@@ -26,6 +44,14 @@
             SceneLoader._ShowLoadingScreen = value;
         }
 
+        public static get loggingLevel() {
+            return SceneLoader._loggingLevel;
+        }
+
+        public static set loggingLevel(value: number) {
+            SceneLoader._loggingLevel = value;
+        }
+
         // Members
         private static _registeredPlugins = new Array<ISceneLoaderPlugin>();
 
@@ -133,6 +159,7 @@
             }
         }
 
+        private static _warned : boolean;
         /**
         * Load a scene
         * @param rootUrl a string that defines the root url for scene and resources
@@ -140,6 +167,10 @@
         * @param engine is the instance of BABYLON.Engine to use to create the scene
         */
         public static Load(rootUrl: string, sceneFilename: any, engine: Engine, onsuccess?: (scene: Scene) => void, progressCallBack?: any, onerror?: (scene: Scene) => void): void {
+            if (!SceneLoader._warned) {
+                Tools.Warn("SceneLoader.Load deprecated since 2.4.  Use SceneLoader.Append.");
+                SceneLoader._warned = true;
+            }
             SceneLoader.Append(rootUrl, sceneFilename, new Scene(engine), onsuccess, progressCallBack, onerror);
         }
 

+ 29 - 1
src/Materials/Textures/babylon.hdrCubeTexture.js

@@ -5,18 +5,45 @@ var __extends = (this && this.__extends) || function (d, b) {
 };
 var BABYLON;
 (function (BABYLON) {
+    /**
+     * This represents a texture coming from an HDR input.
+     *
+     * The only supported format is currently panorama picture stored in RGBE format.
+     * Example of such files can be found on HDRLib: http://hdrlib.com/
+     */
     var HDRCubeTexture = (function (_super) {
         __extends(HDRCubeTexture, _super);
+        /**
+         * Instantiates an HDRTexture from the following parameters.
+         *
+         * @param url The location of the HDR raw data (Panorama stored in RGBE format)
+         * @param scene The scene the texture will be used in
+         * @param size The cubemap desired size (the more it increases the longer the generation will be)
+         * @param noMipmap Forces to not generate the mipmap if true
+         * @param generateHarmonics Specifies wether you want to extract the polynomial harmonics during the generation process
+         * @param useInGammaSpace Specifies if the texture will be use in gamma or linear space (the PBR material requires those texture in linear space, but the standard material would require them in Gamma space)
+         * @param usePMREMGenerator Specifies wether or not to generate the CubeMap through CubeMapGen to avoid seams issue at run time.
+         */
         function HDRCubeTexture(url, scene, size, noMipmap, generateHarmonics, useInGammaSpace, usePMREMGenerator) {
             if (noMipmap === void 0) { noMipmap = false; }
             if (generateHarmonics === void 0) { generateHarmonics = true; }
             if (useInGammaSpace === void 0) { useInGammaSpace = false; }
             if (usePMREMGenerator === void 0) { usePMREMGenerator = false; }
             _super.call(this, scene);
-            this.coordinatesMode = BABYLON.Texture.CUBIC_MODE;
             this._useInGammaSpace = false;
             this._generateHarmonics = true;
+            /**
+             * The texture coordinates mode. As this texture is stored in a cube format, please modify carefully.
+             */
+            this.coordinatesMode = BABYLON.Texture.CUBIC_MODE;
+            /**
+             * The spherical polynomial data extracted from the texture.
+             */
             this.sphericalPolynomial = null;
+            /**
+             * Specifies wether the texture has been generated through the PMREMGenerator tool.
+             * This is usefull at run time to apply the good shader.
+             */
             this.isPMREM = false;
             this.name = url;
             this.url = url;
@@ -91,6 +118,7 @@ var BABYLON;
             var mipmapGenerator = null;
             if (!this._noMipmap && this._usePMREMGenerator) {
                 mipmapGenerator = function (data) {
+                    // Custom setup of the generator matching with the PBR shader values.
                     var generator = new BABYLON.Internals.PMREMGenerator(data, _this._size, _this._size, 0, 3, _this.getScene().getEngine().getCaps().textureFloat, 2048, 0.25, false, true);
                     return generator.filterCubeMap();
                 };

+ 45 - 10
src/Materials/Textures/babylon.hdrcubetexture.ts

@@ -1,15 +1,12 @@
 module BABYLON {
+    
+    /**
+     * This represents a texture coming from an HDR input.
+     * 
+     * The only supported format is currently panorama picture stored in RGBE format.
+     * Example of such files can be found on HDRLib: http://hdrlib.com/
+     */
     export class HDRCubeTexture extends BaseTexture {
-        public url: string;
-        public coordinatesMode = Texture.CUBIC_MODE;
-
-        private _useInGammaSpace = false;
-        private _generateHarmonics = true;
-        private _noMipmap: boolean;
-        private _extensions: string[];
-        private _textureMatrix: Matrix;
-        private _size: number;
-        private _usePMREMGenerator: boolean;
 
         private static _facesMapping = [
             "right",
@@ -20,10 +17,46 @@ module BABYLON {
             "back"
         ];
 
+        private _useInGammaSpace = false;
+        private _generateHarmonics = true;
+        private _noMipmap: boolean;
+        private _extensions: string[];
+        private _textureMatrix: Matrix;
+        private _size: number;
+        private _usePMREMGenerator: boolean;
+        
+        /**
+         * The texture URL.
+         */
+        public url: string;
+        
+        /**
+         * The texture coordinates mode. As this texture is stored in a cube format, please modify carefully.
+         */
+        public coordinatesMode = Texture.CUBIC_MODE;
+
+        /**
+         * The spherical polynomial data extracted from the texture.
+         */
         public sphericalPolynomial: SphericalPolynomial = null;
 
+        /**
+         * Specifies wether the texture has been generated through the PMREMGenerator tool.
+         * This is usefull at run time to apply the good shader.
+         */
         public isPMREM = false;
 
+        /**
+         * Instantiates an HDRTexture from the following parameters.
+         * 
+         * @param url The location of the HDR raw data (Panorama stored in RGBE format)
+         * @param scene The scene the texture will be used in
+         * @param size The cubemap desired size (the more it increases the longer the generation will be)
+         * @param noMipmap Forces to not generate the mipmap if true
+         * @param generateHarmonics Specifies wether you want to extract the polynomial harmonics during the generation process
+         * @param useInGammaSpace Specifies if the texture will be use in gamma or linear space (the PBR material requires those texture in linear space, but the standard material would require them in Gamma space)
+         * @param usePMREMGenerator Specifies wether or not to generate the CubeMap through CubeMapGen to avoid seams issue at run time.
+         */
         constructor(url: string, scene: Scene, size: number, noMipmap = false, generateHarmonics = true, useInGammaSpace = false, usePMREMGenerator = false) {
             super(scene);
 
@@ -113,6 +146,7 @@ module BABYLON {
             var mipmapGenerator = null;
             if (!this._noMipmap && this._usePMREMGenerator) {
                 mipmapGenerator = (data: ArrayBufferView[]) => {
+                    // Custom setup of the generator matching with the PBR shader values.
                     var generator = new BABYLON.Internals.PMREMGenerator(data,
                         this._size,
                         this._size,
@@ -196,3 +230,4 @@ module BABYLON {
         }
     }
 } 
+

+ 10 - 0
src/Materials/babylon.material.js

@@ -138,6 +138,16 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        /**
+         * @param {boolean} fullDetails - support for multiple levels of logging within scene loading
+         * subclasses should override adding information pertainent to themselves
+         */
+        Material.prototype.toString = function (fullDetails) {
+            var ret = "Name: " + this.name;
+            if (fullDetails) {
+            }
+            return ret;
+        };
         Object.defineProperty(Material.prototype, "isFrozen", {
             get: function () {
                 return this.checkReadyOnlyOnce;

+ 0 - 0
src/Materials/babylon.material.ts


Некоторые файлы не были показаны из-за большого количества измененных файлов