Bladeren bron

Merge branch 'master' into webVRAsyncMethods

Trevor Baron 7 jaren geleden
bovenliggende
commit
6842dbefd1
74 gewijzigde bestanden met toevoegingen van 41999 en 36179 verwijderingen
  1. 12032 11493
      Playground/babylon.d.txt
  2. 1 1
      Playground/scripts/instanced bones.js
  3. 3 1
      Tools/Gulp/config.json
  4. 1 1
      Tools/Gulp/package.json
  5. 12265 11737
      dist/preview release/babylon.d.ts
  6. 41 41
      dist/preview release/babylon.js
  7. 1463 654
      dist/preview release/babylon.max.js
  8. 43 43
      dist/preview release/babylon.worker.js
  9. 8289 7761
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  10. 45 45
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  11. 1575 733
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  12. 1577 735
      dist/preview release/customConfigurations/minimalGLTFViewer/es6.js
  13. 1465 656
      dist/preview release/es6.js
  14. 17 0
      dist/preview release/gui/babylon.gui.d.ts
  15. 52 0
      dist/preview release/gui/babylon.gui.js
  16. 2 2
      dist/preview release/gui/babylon.gui.min.js
  17. 17 0
      dist/preview release/gui/babylon.gui.module.d.ts
  18. 4 4
      dist/preview release/inspector/babylon.inspector.bundle.js
  19. 5 2
      dist/preview release/inspector/babylon.inspector.css
  20. 13 2
      dist/preview release/inspector/babylon.inspector.js
  21. 4 4
      dist/preview release/inspector/babylon.inspector.min.js
  22. 20 13
      dist/preview release/loaders/babylon.glTF1FileLoader.d.ts
  23. 24 13
      dist/preview release/loaders/babylon.glTF1FileLoader.js
  24. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  25. 35 25
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  26. 151 121
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  27. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  28. 36 26
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  29. 152 122
      dist/preview release/loaders/babylon.glTFFileLoader.js
  30. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  31. 152 122
      dist/preview release/loaders/babylonjs.loaders.js
  32. 3 3
      dist/preview release/loaders/babylonjs.loaders.min.js
  33. 36 26
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  34. 2 470
      dist/preview release/typedocValidationBaseline.json
  35. 53 52
      dist/preview release/viewer/babylon.viewer.js
  36. 5 1
      dist/preview release/what's new.md
  37. 49 0
      gui/src/controls/textBlock.ts
  38. 3 2
      inspector/sass/_detailPanel.scss
  39. 3 0
      inspector/sass/_tree.scss
  40. 1 1
      inspector/src/properties.ts
  41. 12 1
      inspector/src/tree/TreeItem.ts
  42. 34 15
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  43. 1 2
      loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts
  44. 12 3
      loaders/src/glTF/babylon.glTFFileLoader.ts
  45. 1 1
      package.json
  46. 1 1
      src/Animations/babylon.animatable.ts
  47. 1 1
      src/Audio/babylon.soundtrack.ts
  48. 0 1
      src/Engine/babylon.nullEngine.ts
  49. 639 0
      src/Layer/babylon.effectLayer.ts
  50. 437 0
      src/Layer/babylon.glowLayer.ts
  51. 672 0
      src/Layer/babylon.highlightLayer.ts
  52. 0 996
      src/Layer/babylon.highlightlayer.ts
  53. 2 2
      src/Loading/Plugins/babylon.babylonFileLoader.ts
  54. 8 10
      src/Materials/Textures/babylon.texture.ts
  55. 0 10
      src/Mesh/babylon.abstractMesh.ts
  56. 6 7
      src/Mesh/babylon.mesh.ts
  57. 8 0
      src/Particles/babylon.particleSystem.ts
  58. 9 0
      src/Particles/babylon.solidParticle.ts
  59. 86 30
      src/Particles/babylon.solidParticleSystem.ts
  60. 1 1
      src/Shaders/glowMapGeneration.fragment.fx
  61. 15 7
      src/Shaders/glowMapMerge.fragment.fx
  62. 1 1
      src/Tools/babylon.assetsManager.ts
  63. 36 51
      src/Tools/babylon.promise.ts
  64. 12 2
      src/Tools/babylon.tools.ts
  65. 162 29
      src/babylon.node.ts
  66. 54 31
      src/babylon.scene.ts
  67. 3 1
      tests/nullEngine/app.js
  68. 5 5
      tests/nullEngine/package.json
  69. 19 17
      tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts
  70. 104 33
      tests/unit/babylon/src/Tools/babylon.promise.tests.ts
  71. BIN
      tests/validation/ReferenceImages/GlowLayer.png
  72. BIN
      tests/validation/ReferenceImages/depthOfField.png
  73. BIN
      tests/validation/ReferenceImages/local cubemaps.png
  74. 12 2
      tests/validation/config.json

File diff suppressed because it is too large
+ 12032 - 11493
Playground/babylon.d.txt


+ 1 - 1
Playground/scripts/instanced bones.js

@@ -21,7 +21,7 @@ var createScene = function () {
 
     // Shadows
     var shadowGenerator = new BABYLON.ShadowGenerator(1024, light);
-    shadowGenerator.useBlurVarianceShadowMap = true;
+    shadowGenerator.useBlurExponentialShadowMap = true;
 
     // Dude
     BABYLON.SceneLoader.ImportMesh("him", "scenes/Dude/", "Dude.babylon", scene, function (newMeshes2, particleSystems2, skeletons2) {

+ 3 - 1
Tools/Gulp/config.json

@@ -1043,7 +1043,9 @@
             "files": [
                 "../../src/Rendering/babylon.outlineRenderer.js",
                 "../../src/Rendering/babylon.edgesRenderer.js",
-                "../../src/Layer/babylon.highlightlayer.js"
+                "../../src/Layer/babylon.effectLayer.js",
+                "../../src/Layer/babylon.highlightLayer.js",
+                "../../src/Layer/babylon.glowLayer.js"
             ],
             "dependUpon": [
                 "shaderMaterial"

+ 1 - 1
Tools/Gulp/package.json

@@ -57,7 +57,7 @@
         "through2": "~0.6.5",
         "ts-loader": "^2.3.7",
         "typedoc": "^0.9.0",
-        "typescript": "^2.6.2",
+        "typescript": "~2.6.2",
         "webpack-stream": "^4.0.0"
     },
     "scripts": {

File diff suppressed because it is too large
+ 12265 - 11737
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 41 - 41
dist/preview release/babylon.js


File diff suppressed because it is too large
+ 1463 - 654
dist/preview release/babylon.max.js


File diff suppressed because it is too large
+ 43 - 43
dist/preview release/babylon.worker.js


File diff suppressed because it is too large
+ 8289 - 7761
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


File diff suppressed because it is too large
+ 45 - 45
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


File diff suppressed because it is too large
+ 1575 - 733
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js


File diff suppressed because it is too large
+ 1577 - 735
dist/preview release/customConfigurations/minimalGLTFViewer/es6.js


File diff suppressed because it is too large
+ 1465 - 656
dist/preview release/es6.js


+ 17 - 0
dist/preview release/gui/babylon.gui.d.ts

@@ -543,6 +543,8 @@ declare module BABYLON.GUI {
         private _lines;
         private _resizeToFit;
         private _lineSpacing;
+        private _outlineWidth;
+        private _outlineColor;
         /**
         * An event triggered after the text is changed
         * @type {BABYLON.Observable}
@@ -600,6 +602,20 @@ declare module BABYLON.GUI {
          */
         lineSpacing: string | number;
         /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        outlineWidth: number;
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        outlineColor: string;
+        /**
          * Creates a new TextBlock object
          * @param name defines the name of the control
          * @param text defines the text to display (emptry string by default)
@@ -613,6 +629,7 @@ declare module BABYLON.GUI {
         private _drawText(text, textWidth, y, context);
         /** @ignore */
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _applyStates(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
         protected _parseLineWithTextWrapping(line: string | undefined, context: CanvasRenderingContext2D): object;

+ 52 - 0
dist/preview release/gui/babylon.gui.js

@@ -3241,6 +3241,8 @@ var BABYLON;
                 _this._textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_CENTER;
                 _this._resizeToFit = false;
                 _this._lineSpacing = new GUI.ValueAndUnit(0);
+                _this._outlineWidth = 0;
+                _this._outlineColor = "white";
                 /**
                 * An event triggered after the text is changed
                 * @type {BABYLON.Observable}
@@ -3383,6 +3385,46 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
+            Object.defineProperty(TextBlock.prototype, "outlineWidth", {
+                /**
+                 * Gets or sets outlineWidth of the text to display
+                 */
+                get: function () {
+                    return this._outlineWidth;
+                },
+                /**
+                 * Gets or sets outlineWidth of the text to display
+                 */
+                set: function (value) {
+                    if (this._outlineWidth === value) {
+                        return;
+                    }
+                    this._outlineWidth = value;
+                    this._markAsDirty();
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(TextBlock.prototype, "outlineColor", {
+                /**
+                 * Gets or sets outlineColor of the text to display
+                 */
+                get: function () {
+                    return this._outlineColor;
+                },
+                /**
+                 * Gets or sets outlineColor of the text to display
+                 */
+                set: function (value) {
+                    if (this._outlineColor === value) {
+                        return;
+                    }
+                    this._outlineColor = value;
+                    this._markAsDirty();
+                },
+                enumerable: true,
+                configurable: true
+            });
             TextBlock.prototype._getTypeName = function () {
                 return "TextBlock";
             };
@@ -3406,6 +3448,9 @@ var BABYLON;
                     context.shadowOffsetX = this.shadowOffsetX;
                     context.shadowOffsetY = this.shadowOffsetY;
                 }
+                if (this.outlineWidth) {
+                    context.strokeText(text, this._currentMeasure.left + x, y);
+                }
                 context.fillText(text, this._currentMeasure.left + x, y);
             };
             /** @ignore */
@@ -3418,6 +3463,13 @@ var BABYLON;
                 }
                 context.restore();
             };
+            TextBlock.prototype._applyStates = function (context) {
+                _super.prototype._applyStates.call(this, context);
+                if (this.outlineWidth) {
+                    context.lineWidth = this.outlineWidth;
+                    context.strokeStyle = this.outlineColor;
+                }
+            };
             TextBlock.prototype._additionalProcessing = function (parentMeasure, context) {
                 this._lines = [];
                 var _lines = this.text.split("\n");

File diff suppressed because it is too large
+ 2 - 2
dist/preview release/gui/babylon.gui.min.js


+ 17 - 0
dist/preview release/gui/babylon.gui.module.d.ts

@@ -549,6 +549,8 @@ declare module BABYLON.GUI {
         private _lines;
         private _resizeToFit;
         private _lineSpacing;
+        private _outlineWidth;
+        private _outlineColor;
         /**
         * An event triggered after the text is changed
         * @type {BABYLON.Observable}
@@ -606,6 +608,20 @@ declare module BABYLON.GUI {
          */
         lineSpacing: string | number;
         /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        outlineWidth: number;
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        outlineColor: string;
+        /**
          * Creates a new TextBlock object
          * @param name defines the name of the control
          * @param text defines the text to display (emptry string by default)
@@ -619,6 +635,7 @@ declare module BABYLON.GUI {
         private _drawText(text, textWidth, y, context);
         /** @ignore */
         _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
+        protected _applyStates(context: CanvasRenderingContext2D): void;
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
         protected _parseLine(line: string | undefined, context: CanvasRenderingContext2D): object;
         protected _parseLineWithTextWrapping(line: string | undefined, context: CanvasRenderingContext2D): object;

File diff suppressed because it is too large
+ 4 - 4
dist/preview release/inspector/babylon.inspector.bundle.js


+ 5 - 2
dist/preview release/inspector/babylon.inspector.css

@@ -283,6 +283,8 @@
           background-color: #242424; }
         .insp-wrapper .insp-tree .line .line-content .line:hover:first-child {
           background-color: #383838; }
+    .insp-wrapper .insp-tree .line_invisible {
+      display: none; }
   .insp-wrapper .insp-details {
     background-color: #242424;
     overflow-y: auto;
@@ -303,7 +305,7 @@
         width: 35%; }
       .insp-wrapper .insp-details .base-row .prop-value, .insp-wrapper .insp-details .row .prop-value, .insp-wrapper .insp-details .header-row .prop-value {
         width: 59%;
-        padding-left: 10px; }
+        padding-left: 5px; }
         .insp-wrapper .insp-details .base-row .prop-value.clickable, .insp-wrapper .insp-details .row .prop-value.clickable, .insp-wrapper .insp-details .header-row .prop-value.clickable {
           cursor: pointer; }
           .insp-wrapper .insp-details .base-row .prop-value.clickable:hover, .insp-wrapper .insp-details .row .prop-value.clickable:hover, .insp-wrapper .insp-details .header-row .prop-value.clickable:hover {
@@ -340,7 +342,8 @@
       display: inline-block;
       margin-left: 5px; }
     .insp-wrapper .insp-details .color-element {
-      top: 2px; }
+      width: 20px;
+      height: 15px; }
     .insp-wrapper .insp-details .texture-element {
       color: #f29766;
       margin-left: 10px; }

+ 13 - 2
dist/preview release/inspector/babylon.inspector.js

@@ -383,7 +383,7 @@ var INSPECTOR;
         },
         'Color3': {
             type: BABYLON.Color3,
-            format: function (color) { return "R:" + color.r + ", G:" + color.g + ", B:" + color.b; },
+            format: function (color) { return "R:" + color.r.toPrecision(2) + ", G:" + color.g.toPrecision(2) + ", B:" + color.b.toPrecision(2); },
             slider: {
                 r: { min: 0, max: 1, step: 0.01 },
                 g: { min: 0, max: 1, step: 0.01 },
@@ -4568,7 +4568,18 @@ var INSPECTOR;
         };
         /** Build the HTML of this item */
         TreeItem.prototype._build = function () {
-            this._div.className = 'line';
+            /**
+             *  Hide the debug objects :
+             * - Axis : xline, yline, zline
+             * */
+            var adapterId = this._adapter.id();
+            if (adapterId == "xline"
+                || adapterId == "yline"
+                || adapterId == "zline") {
+                this._div.className = "line_invisible";
+            }
+            else
+                this._div.className = 'line';
             // special class for transform node ONLY
             if (this.adapter instanceof INSPECTOR.MeshAdapter) {
                 var obj = this.adapter.object;

File diff suppressed because it is too large
+ 4 - 4
dist/preview release/inspector/babylon.inspector.min.js


+ 20 - 13
dist/preview release/loaders/babylon.glTF1FileLoader.d.ts

@@ -28,30 +28,34 @@ declare module BABYLON {
         json: Object;
         bin: Nullable<ArrayBufferView>;
     }
+    interface IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Whether this extension is enabled.
+         */
+        enabled: boolean;
+    }
     enum GLTFLoaderState {
         Loading = 0,
         Ready = 1,
         Complete = 2,
     }
-    interface IGLTFLoaderExtension {
-        enabled: boolean;
-    }
-    interface IGLTFLoaderExtensions {
-        [name: string]: IGLTFLoaderExtension;
-    }
     interface IGLTFLoader extends IDisposable {
         coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
         animationStartMode: GLTFLoaderAnimationStartMode;
         compileMaterials: boolean;
         useClipPlane: boolean;
         compileShadowGenerators: boolean;
-        onDisposeObservable: Observable<IGLTFLoader>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{
             meshes: AbstractMesh[];
             particleSystems: ParticleSystem[];
@@ -125,13 +129,16 @@ declare module BABYLON {
         private _onDisposeObserver;
         onDispose: () => void;
         /**
-         * The loader state or null if not active.
+         * Raised after a loader extension is created.
+         * Set additional options for a loader extension in this event.
          */
-        readonly loaderState: Nullable<GLTFLoaderState>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
+        private _onExtensionLoadedObserver;
+        onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
-         * The loader extensions or null if not active.
+         * The loader state or null if not active.
          */
-        readonly loaderExtensions: Nullable<IGLTFLoaderExtensions>;
+        readonly loaderState: Nullable<GLTFLoaderState>;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -563,8 +570,8 @@ declare module BABYLON.GLTF1 {
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         dispose(): void;
         private _importMeshAsync(meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError);
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress: (event: SceneLoaderProgressEvent) => void): Promise<{

+ 24 - 13
dist/preview release/loaders/babylon.glTF1FileLoader.js

@@ -86,6 +86,11 @@ var BABYLON;
             * Raised when the loader is disposed.
             */
             this.onDisposeObservable = new BABYLON.Observable();
+            /**
+             * Raised after a loader extension is created.
+             * Set additional options for a loader extension in this event.
+             */
+            this.onExtensionLoadedObservable = new BABYLON.Observable();
             // #endregion
             this._loader = null;
             this.name = "gltf";
@@ -154,22 +159,22 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
-            /**
-             * The loader state or null if not active.
-             */
-            get: function () {
-                return this._loader ? this._loader.state : null;
+        Object.defineProperty(GLTFFileLoader.prototype, "onExtensionLoaded", {
+            set: function (callback) {
+                if (this._onExtensionLoadedObserver) {
+                    this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
+                }
+                this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
             },
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderExtensions", {
+        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
-             * The loader extensions or null if not active.
+             * The loader state or null if not active.
              */
             get: function () {
-                return this._loader ? this._loader.extensions : null;
+                return this._loader ? this._loader.state : null;
             },
             enumerable: true,
             configurable: true
@@ -182,11 +187,9 @@ var BABYLON;
                 this._loader.dispose();
                 this._loader = null;
             }
-            this.onParsedObservable.clear();
             this.onMeshLoadedObservable.clear();
             this.onTextureLoadedObservable.clear();
             this.onMaterialLoadedObservable.clear();
-            this.onCompleteObservable.clear();
             this.onDisposeObservable.notifyObservers(this);
             this.onDisposeObservable.clear();
         };
@@ -239,6 +242,7 @@ var BABYLON;
                 };
             }
             this.onParsedObservable.notifyObservers(parsedData);
+            this.onParsedObservable.clear();
             return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
@@ -275,7 +279,14 @@ var BABYLON;
             loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
             loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
             loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
-            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            loader.onExtensionLoadedObservable.add(function (extension) { return _this.onExtensionLoadedObservable.notifyObservers(extension); });
+            loader.onCompleteObservable.add(function () {
+                _this.onMeshLoadedObservable.clear();
+                _this.onTextureLoadedObservable.clear();
+                _this.onMaterialLoadedObservable.clear();
+                _this.onCompleteObservable.notifyObservers(_this);
+                _this.onCompleteObservable.clear();
+            });
             return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
@@ -1827,8 +1838,8 @@ var BABYLON;
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
-                this.extensions = null;
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {

File diff suppressed because it is too large
+ 2 - 2
dist/preview release/loaders/babylon.glTF1FileLoader.min.js


+ 35 - 25
dist/preview release/loaders/babylon.glTF2FileLoader.d.ts

@@ -28,30 +28,34 @@ declare module BABYLON {
         json: Object;
         bin: Nullable<ArrayBufferView>;
     }
+    interface IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Whether this extension is enabled.
+         */
+        enabled: boolean;
+    }
     enum GLTFLoaderState {
         Loading = 0,
         Ready = 1,
         Complete = 2,
     }
-    interface IGLTFLoaderExtension {
-        enabled: boolean;
-    }
-    interface IGLTFLoaderExtensions {
-        [name: string]: IGLTFLoaderExtension;
-    }
     interface IGLTFLoader extends IDisposable {
         coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
         animationStartMode: GLTFLoaderAnimationStartMode;
         compileMaterials: boolean;
         useClipPlane: boolean;
         compileShadowGenerators: boolean;
-        onDisposeObservable: Observable<IGLTFLoader>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{
             meshes: AbstractMesh[];
             particleSystems: ParticleSystem[];
@@ -125,13 +129,16 @@ declare module BABYLON {
         private _onDisposeObserver;
         onDispose: () => void;
         /**
-         * The loader state or null if not active.
+         * Raised after a loader extension is created.
+         * Set additional options for a loader extension in this event.
          */
-        readonly loaderState: Nullable<GLTFLoaderState>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
+        private _onExtensionLoadedObserver;
+        onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
-         * The loader extensions or null if not active.
+         * The loader state or null if not active.
          */
-        readonly loaderExtensions: Nullable<IGLTFLoaderExtensions>;
+        readonly loaderState: Nullable<GLTFLoaderState>;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -283,10 +290,9 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
-        readonly extensions: IGLTFLoaderExtensions;
-        constructor();
         dispose(): void;
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{
             meshes: AbstractMesh[];
@@ -295,6 +301,7 @@ declare module BABYLON.GLTF2 {
         }>;
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
         private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadExtensions();
         private _loadData(data);
         private _setupData();
         private _createRootNode();
@@ -345,19 +352,18 @@ declare module BABYLON.GLTF2 {
         private static _ValidateUri(uri);
         private _compileMaterialsAsync();
         private _compileShadowGeneratorsAsync();
-        private _abortRequests();
-        private _releaseResources();
+        private _clear();
         _applyExtensions<T>(actionAsync: (extension: GLTFLoaderExtension) => Nullable<Promise<T>>): Nullable<Promise<T>>;
     }
 }
 
 
 declare module BABYLON.GLTF2 {
-    abstract class GLTFLoaderExtension {
+    abstract class GLTFLoaderExtension implements IGLTFLoaderExtension {
         enabled: boolean;
+        readonly abstract name: string;
         protected _loader: GLTFLoader;
         constructor(loader: GLTFLoader);
-        protected readonly abstract _name: string;
         /** Override this method to modify the default behavior for loading scenes. */
         protected _loadSceneAsync(context: string, node: ILoaderScene): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading nodes. */
@@ -381,26 +387,30 @@ declare module BABYLON.GLTF2 {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class MSFTLOD extends GLTFLoaderExtension {
+    class MSFT_lod extends GLTFLoaderExtension {
+        readonly name: string;
+        /**
+         * Maximum number of LODs to load, starting from the lowest LOD.
+         */
+        maxLODsToLoad: number;
         private _loadingNodeLOD;
         private _loadNodeSignals;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
-        protected readonly _name: string;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
          * Gets an array of LOD properties from lowest to highest.
          */
-        private static _GetLODs<T>(context, property, array, ids);
+        private _getLODs<T>(context, property, array, ids);
     }
 }
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRMaterialsPbrSpecularGlossiness extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
     }
@@ -408,8 +418,8 @@ declare module BABYLON.GLTF2.Extensions {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRLights extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_lights extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         private readonly _lights;

+ 151 - 121
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -86,6 +86,11 @@ var BABYLON;
             * Raised when the loader is disposed.
             */
             this.onDisposeObservable = new BABYLON.Observable();
+            /**
+             * Raised after a loader extension is created.
+             * Set additional options for a loader extension in this event.
+             */
+            this.onExtensionLoadedObservable = new BABYLON.Observable();
             // #endregion
             this._loader = null;
             this.name = "gltf";
@@ -154,22 +159,22 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
-            /**
-             * The loader state or null if not active.
-             */
-            get: function () {
-                return this._loader ? this._loader.state : null;
+        Object.defineProperty(GLTFFileLoader.prototype, "onExtensionLoaded", {
+            set: function (callback) {
+                if (this._onExtensionLoadedObserver) {
+                    this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
+                }
+                this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
             },
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderExtensions", {
+        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
-             * The loader extensions or null if not active.
+             * The loader state or null if not active.
              */
             get: function () {
-                return this._loader ? this._loader.extensions : null;
+                return this._loader ? this._loader.state : null;
             },
             enumerable: true,
             configurable: true
@@ -182,11 +187,9 @@ var BABYLON;
                 this._loader.dispose();
                 this._loader = null;
             }
-            this.onParsedObservable.clear();
             this.onMeshLoadedObservable.clear();
             this.onTextureLoadedObservable.clear();
             this.onMaterialLoadedObservable.clear();
-            this.onCompleteObservable.clear();
             this.onDisposeObservable.notifyObservers(this);
             this.onDisposeObservable.clear();
         };
@@ -239,6 +242,7 @@ var BABYLON;
                 };
             }
             this.onParsedObservable.notifyObservers(parsedData);
+            this.onParsedObservable.clear();
             return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
@@ -275,7 +279,14 @@ var BABYLON;
             loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
             loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
             loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
-            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            loader.onExtensionLoadedObservable.add(function (extension) { return _this.onExtensionLoadedObservable.notifyObservers(extension); });
+            loader.onCompleteObservable.add(function () {
+                _this.onMeshLoadedObservable.clear();
+                _this.onTextureLoadedObservable.clear();
+                _this.onMaterialLoadedObservable.clear();
+                _this.onCompleteObservable.notifyObservers(_this);
+                _this.onCompleteObservable.clear();
+            });
             return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
@@ -488,12 +499,8 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
-                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
-                    var name_1 = _a[_i];
-                    var extension = GLTFLoader._Factories[name_1](this);
-                    this._extensions[name_1] = extension;
-                }
             }
             GLTFLoader._Register = function (name, factory) {
                 if (GLTFLoader._Factories[name]) {
@@ -511,21 +518,14 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
-            Object.defineProperty(GLTFLoader.prototype, "extensions", {
-                get: function () {
-                    return this.extensions;
-                },
-                enumerable: true,
-                configurable: true
-            });
             GLTFLoader.prototype.dispose = function () {
                 if (this._disposed) {
                     return;
                 }
                 this._disposed = true;
-                this._abortRequests();
-                this._releaseResources();
                 this.onDisposeObservable.notifyObservers(this);
+                this.onDisposeObservable.clear();
+                this._clear();
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
@@ -565,6 +565,7 @@ var BABYLON;
             GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._loadExtensions();
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
@@ -588,22 +589,34 @@ var BABYLON;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         BABYLON.Tools.SetImmediate(function () {
-                            Promise.all(_this._completePromises).then(function () {
-                                _this._releaseResources();
-                                _this._state = BABYLON.GLTFLoaderState.Complete;
-                                _this.onCompleteObservable.notifyObservers(_this);
-                            }).catch(function (error) {
-                                BABYLON.Tools.Error("glTF Loader: " + error.message);
-                                _this.dispose();
-                            });
+                            if (!_this._disposed) {
+                                Promise.all(_this._completePromises).then(function () {
+                                    _this._state = BABYLON.GLTFLoaderState.Complete;
+                                    _this.onCompleteObservable.notifyObservers(_this);
+                                    _this.onCompleteObservable.clear();
+                                    _this._clear();
+                                }).catch(function (error) {
+                                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                                    _this._clear();
+                                });
+                            }
                         });
-                    }).catch(function (error) {
-                        BABYLON.Tools.Error("glTF Loader: " + error.message);
-                        _this.dispose();
-                        throw error;
                     });
+                }).catch(function (error) {
+                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                    _this._clear();
+                    throw error;
                 });
             };
+            GLTFLoader.prototype._loadExtensions = function () {
+                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
+                    var name_1 = _a[_i];
+                    var extension = GLTFLoader._Factories[name_1](this);
+                    this._extensions[name_1] = extension;
+                    this.onExtensionLoadedObservable.notifyObservers(extension);
+                }
+                this.onExtensionLoadedObservable.clear();
+            };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
                 this._setupData();
@@ -828,6 +841,7 @@ var BABYLON;
                     var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
                     promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
                 }
+                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () {
                     babylonMesh.setEnabled(true);
                 });
@@ -1542,6 +1556,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._loadTextureAsync = function (context, textureInfo, assign) {
+                var _this = this;
                 var texture = GLTFLoader._GetProperty(context + "/index", this._gltf.textures, textureInfo.index);
                 context = "#/textures/" + textureInfo.index;
                 var promises = new Array();
@@ -1549,9 +1564,13 @@ var BABYLON;
                 var samplerData = this._loadSampler("#/samplers/" + sampler._index, sampler);
                 var deferred = new BABYLON.Deferred();
                 var babylonTexture = new BABYLON.Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
-                    deferred.resolve();
+                    if (!_this._disposed) {
+                        deferred.resolve();
+                    }
                 }, function (message, exception) {
-                    deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    if (!_this._disposed) {
+                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    }
                 });
                 promises.push(deferred.promise);
                 babylonTexture.name = texture.name || "texture" + texture._index;
@@ -1609,21 +1628,27 @@ var BABYLON;
                 }
                 return new Promise(function (resolve, reject) {
                     var request = BABYLON.Tools.LoadFile(_this._rootUrl + uri, function (data) {
-                        resolve(new Uint8Array(data));
+                        if (!_this._disposed) {
+                            resolve(new Uint8Array(data));
+                        }
                     }, function (event) {
-                        try {
-                            if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
-                                request._lengthComputable = event.lengthComputable;
-                                request._loaded = event.loaded;
-                                request._total = event.total;
-                                _this._onProgress();
+                        if (!_this._disposed) {
+                            try {
+                                if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
+                                    request._lengthComputable = event.lengthComputable;
+                                    request._loaded = event.loaded;
+                                    request._total = event.total;
+                                    _this._onProgress();
+                                }
+                            }
+                            catch (e) {
+                                reject(e);
                             }
-                        }
-                        catch (e) {
-                            reject(e);
                         }
                     }, _this._babylonScene.database, true, function (request, exception) {
-                        reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        if (!_this._disposed) {
+                            reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        }
                     });
                     _this._requests.push(request);
                 });
@@ -1745,17 +1770,15 @@ var BABYLON;
                 }
                 return Promise.all(promises).then(function () { });
             };
-            GLTFLoader.prototype._abortRequests = function () {
+            GLTFLoader.prototype._clear = function () {
                 for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
                     var request = _a[_i];
                     request.abort();
                 }
                 this._requests.length = 0;
-            };
-            GLTFLoader.prototype._releaseResources = function () {
-                if (this._gltf.images) {
-                    for (var _i = 0, _a = this._gltf.images; _i < _a.length; _i++) {
-                        var image = _a[_i];
+                if (this._gltf && this._gltf.images) {
+                    for (var _b = 0, _c = this._gltf.images; _b < _c.length; _b++) {
+                        var image = _c[_b];
                         if (image._objectURL) {
                             image._objectURL.then(function (value) {
                                 URL.revokeObjectURL(value);
@@ -1764,6 +1787,15 @@ var BABYLON;
                         }
                     }
                 }
+                delete this._gltf;
+                delete this._babylonScene;
+                this._completePromises.length = 0;
+                this._extensions = {};
+                delete this._rootBabylonMesh;
+                delete this._progressCallback;
+                this.onMeshLoadedObservable.clear();
+                this.onTextureLoadedObservable.clear();
+                this.onMaterialLoadedObservable.clear();
             };
             GLTFLoader.prototype._applyExtensions = function (actionAsync) {
                 for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
@@ -1816,15 +1848,15 @@ var BABYLON;
                     return null;
                 }
                 var extensions = property.extensions;
-                var extension = extensions[this._name];
+                var extension = extensions[this.name];
                 if (!extension) {
                     return null;
                 }
                 // Clear out the extension before executing the action to avoid recursing into the same property.
-                delete extensions[this._name];
-                return actionAsync(context + "extensions/" + this._name, extension).then(function () {
+                delete extensions[this.name];
+                return actionAsync(context + "/extensions/" + this.name, extension).then(function () {
                     // Restore the extension after completing the action.
-                    extensions[_this._name] = extension;
+                    extensions[_this.name] = extension;
                 });
             };
             /** Helper method called by the loader to allow extensions to override loading scenes. */
@@ -1870,28 +1902,26 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod
             var NAME = "MSFT_lod";
-            var MSFTLOD = /** @class */ (function (_super) {
-                __extends(MSFTLOD, _super);
-                function MSFTLOD() {
+            var MSFT_lod = /** @class */ (function (_super) {
+                __extends(MSFT_lod, _super);
+                function MSFT_lod() {
                     var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    /**
+                     * Maximum number of LODs to load, starting from the lowest LOD.
+                     */
+                    _this.maxLODsToLoad = Number.MAX_VALUE;
                     _this._loadingNodeLOD = null;
                     _this._loadNodeSignals = {};
                     _this._loadingMaterialLOD = null;
                     _this._loadMaterialSignals = {};
                     return _this;
                 }
-                Object.defineProperty(MSFTLOD.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                MSFTLOD.prototype._loadNodeAsync = function (context, node) {
+                MSFT_lod.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var firstPromise;
-                        var nodeLODs = MSFTLOD._GetLODs(context, node, _this._loader._gltf.nodes, extension.ids);
+                        var nodeLODs = _this._getLODs(context, node, _this._loader._gltf.nodes, extension.ids);
                         var _loop_1 = function (indexLOD) {
                             var nodeLOD = nodeLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -1923,11 +1953,15 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
+                    // Don't load material LODs if already loading a node LOD.
+                    if (this._loadingNodeLOD) {
+                        return null;
+                    }
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         var firstPromise;
-                        var materialLODs = MSFTLOD._GetLODs(context, material, _this._loader._gltf.materials, extension.ids);
+                        var materialLODs = _this._getLODs(context, material, _this._loader._gltf.materials, extension.ids);
                         var _loop_2 = function (indexLOD) {
                             var materialLOD = materialLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -1955,7 +1989,7 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadUriAsync = function (context, uri) {
+                MSFT_lod.prototype._loadUriAsync = function (context, uri) {
                     var _this = this;
                     // Defer the loading of uris if loading a material or node LOD.
                     if (this._loadingMaterialLOD) {
@@ -1975,18 +2009,24 @@ var BABYLON;
                 /**
                  * Gets an array of LOD properties from lowest to highest.
                  */
-                MSFTLOD._GetLODs = function (context, property, array, ids) {
-                    var properties = [property];
-                    for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) {
-                        var id = ids_1[_i];
-                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + id, array, id));
+                MSFT_lod.prototype._getLODs = function (context, property, array, ids) {
+                    if (this.maxLODsToLoad <= 0) {
+                        throw new Error("maxLODsToLoad must be greater than zero");
+                    }
+                    var properties = new Array();
+                    for (var i = ids.length - 1; i >= 0; i--) {
+                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + ids[i], array, ids[i]));
+                        if (properties.length === this.maxLODsToLoad) {
+                            return properties;
+                        }
                     }
-                    return properties.reverse();
+                    properties.push(property);
+                    return properties;
                 };
-                return MSFTLOD;
+                return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.MSFTLOD = MSFTLOD;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFTLOD(loader); });
+            Extensions.MSFT_lod = MSFT_lod;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFT_lod(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -2012,19 +2052,14 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
             var NAME = "KHR_materials_pbrSpecularGlossiness";
-            var KHRMaterialsPbrSpecularGlossiness = /** @class */ (function (_super) {
-                __extends(KHRMaterialsPbrSpecularGlossiness, _super);
-                function KHRMaterialsPbrSpecularGlossiness() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_materials_pbrSpecularGlossiness = /** @class */ (function (_super) {
+                __extends(KHR_materials_pbrSpecularGlossiness, _super);
+                function KHR_materials_pbrSpecularGlossiness() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRMaterialsPbrSpecularGlossiness.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
@@ -2043,7 +2078,7 @@ var BABYLON;
                         return (material._loaded = Promise.all(promises).then(function () { }));
                     });
                 };
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
@@ -2070,10 +2105,10 @@ var BABYLON;
                     loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                 };
-                return KHRMaterialsPbrSpecularGlossiness;
+                return KHR_materials_pbrSpecularGlossiness;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRMaterialsPbrSpecularGlossiness = KHRMaterialsPbrSpecularGlossiness;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRMaterialsPbrSpecularGlossiness(loader); });
+            Extensions.KHR_materials_pbrSpecularGlossiness = KHR_materials_pbrSpecularGlossiness;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_materials_pbrSpecularGlossiness(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -2106,19 +2141,14 @@ var BABYLON;
                 LightType["POINT"] = "point";
                 LightType["SPOT"] = "spot";
             })(LightType || (LightType = {}));
-            var KHRLights = /** @class */ (function (_super) {
-                __extends(KHRLights, _super);
-                function KHRLights() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_lights = /** @class */ (function (_super) {
+                __extends(KHR_lights, _super);
+                function KHR_lights() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRLights.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRLights.prototype._loadSceneAsync = function (context, scene) {
+                KHR_lights.prototype._loadSceneAsync = function (context, scene) {
                     var _this = this;
                     return this._loadExtensionAsync(context, scene, function (context, extension) {
                         var promise = _this._loader._loadSceneAsync(context, scene);
@@ -2130,7 +2160,7 @@ var BABYLON;
                         return promise;
                     });
                 };
-                KHRLights.prototype._loadNodeAsync = function (context, node) {
+                KHR_lights.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var promise = _this._loader._loadNodeAsync(context, node);
@@ -2167,22 +2197,22 @@ var BABYLON;
                         return promise;
                     });
                 };
-                Object.defineProperty(KHRLights.prototype, "_lights", {
+                Object.defineProperty(KHR_lights.prototype, "_lights", {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
-                        if (!extensions || !extensions[this._name]) {
-                            throw new Error("#/extensions: " + this._name + " not found");
+                        if (!extensions || !extensions[this.name]) {
+                            throw new Error("#/extensions: " + this.name + " not found");
                         }
-                        var extension = extensions[this._name];
+                        var extension = extensions[this.name];
                         return extension.lights;
                     },
                     enumerable: true,
                     configurable: true
                 });
-                return KHRLights;
+                return KHR_lights;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRLights = KHRLights;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRLights(loader); });
+            Extensions.KHR_lights = KHR_lights;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_lights(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));

File diff suppressed because it is too large
+ 2 - 2
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 36 - 26
dist/preview release/loaders/babylon.glTFFileLoader.d.ts

@@ -28,30 +28,34 @@ declare module BABYLON {
         json: Object;
         bin: Nullable<ArrayBufferView>;
     }
+    interface IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Whether this extension is enabled.
+         */
+        enabled: boolean;
+    }
     enum GLTFLoaderState {
         Loading = 0,
         Ready = 1,
         Complete = 2,
     }
-    interface IGLTFLoaderExtension {
-        enabled: boolean;
-    }
-    interface IGLTFLoaderExtensions {
-        [name: string]: IGLTFLoaderExtension;
-    }
     interface IGLTFLoader extends IDisposable {
         coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
         animationStartMode: GLTFLoaderAnimationStartMode;
         compileMaterials: boolean;
         useClipPlane: boolean;
         compileShadowGenerators: boolean;
-        onDisposeObservable: Observable<IGLTFLoader>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{
             meshes: AbstractMesh[];
             particleSystems: ParticleSystem[];
@@ -125,13 +129,16 @@ declare module BABYLON {
         private _onDisposeObserver;
         onDispose: () => void;
         /**
-         * The loader state or null if not active.
+         * Raised after a loader extension is created.
+         * Set additional options for a loader extension in this event.
          */
-        readonly loaderState: Nullable<GLTFLoaderState>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
+        private _onExtensionLoadedObserver;
+        onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
-         * The loader extensions or null if not active.
+         * The loader state or null if not active.
          */
-        readonly loaderExtensions: Nullable<IGLTFLoaderExtensions>;
+        readonly loaderState: Nullable<GLTFLoaderState>;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -563,8 +570,8 @@ declare module BABYLON.GLTF1 {
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         dispose(): void;
         private _importMeshAsync(meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError);
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress: (event: SceneLoaderProgressEvent) => void): Promise<{
@@ -838,10 +845,9 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
-        readonly extensions: IGLTFLoaderExtensions;
-        constructor();
         dispose(): void;
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{
             meshes: AbstractMesh[];
@@ -850,6 +856,7 @@ declare module BABYLON.GLTF2 {
         }>;
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
         private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadExtensions();
         private _loadData(data);
         private _setupData();
         private _createRootNode();
@@ -900,19 +907,18 @@ declare module BABYLON.GLTF2 {
         private static _ValidateUri(uri);
         private _compileMaterialsAsync();
         private _compileShadowGeneratorsAsync();
-        private _abortRequests();
-        private _releaseResources();
+        private _clear();
         _applyExtensions<T>(actionAsync: (extension: GLTFLoaderExtension) => Nullable<Promise<T>>): Nullable<Promise<T>>;
     }
 }
 
 
 declare module BABYLON.GLTF2 {
-    abstract class GLTFLoaderExtension {
+    abstract class GLTFLoaderExtension implements IGLTFLoaderExtension {
         enabled: boolean;
+        readonly abstract name: string;
         protected _loader: GLTFLoader;
         constructor(loader: GLTFLoader);
-        protected readonly abstract _name: string;
         /** Override this method to modify the default behavior for loading scenes. */
         protected _loadSceneAsync(context: string, node: ILoaderScene): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading nodes. */
@@ -936,26 +942,30 @@ declare module BABYLON.GLTF2 {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class MSFTLOD extends GLTFLoaderExtension {
+    class MSFT_lod extends GLTFLoaderExtension {
+        readonly name: string;
+        /**
+         * Maximum number of LODs to load, starting from the lowest LOD.
+         */
+        maxLODsToLoad: number;
         private _loadingNodeLOD;
         private _loadNodeSignals;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
-        protected readonly _name: string;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
          * Gets an array of LOD properties from lowest to highest.
          */
-        private static _GetLODs<T>(context, property, array, ids);
+        private _getLODs<T>(context, property, array, ids);
     }
 }
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRMaterialsPbrSpecularGlossiness extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
     }
@@ -963,8 +973,8 @@ declare module BABYLON.GLTF2.Extensions {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRLights extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_lights extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         private readonly _lights;

+ 152 - 122
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -86,6 +86,11 @@ var BABYLON;
             * Raised when the loader is disposed.
             */
             this.onDisposeObservable = new BABYLON.Observable();
+            /**
+             * Raised after a loader extension is created.
+             * Set additional options for a loader extension in this event.
+             */
+            this.onExtensionLoadedObservable = new BABYLON.Observable();
             // #endregion
             this._loader = null;
             this.name = "gltf";
@@ -154,22 +159,22 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
-            /**
-             * The loader state or null if not active.
-             */
-            get: function () {
-                return this._loader ? this._loader.state : null;
+        Object.defineProperty(GLTFFileLoader.prototype, "onExtensionLoaded", {
+            set: function (callback) {
+                if (this._onExtensionLoadedObserver) {
+                    this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
+                }
+                this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
             },
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderExtensions", {
+        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
-             * The loader extensions or null if not active.
+             * The loader state or null if not active.
              */
             get: function () {
-                return this._loader ? this._loader.extensions : null;
+                return this._loader ? this._loader.state : null;
             },
             enumerable: true,
             configurable: true
@@ -182,11 +187,9 @@ var BABYLON;
                 this._loader.dispose();
                 this._loader = null;
             }
-            this.onParsedObservable.clear();
             this.onMeshLoadedObservable.clear();
             this.onTextureLoadedObservable.clear();
             this.onMaterialLoadedObservable.clear();
-            this.onCompleteObservable.clear();
             this.onDisposeObservable.notifyObservers(this);
             this.onDisposeObservable.clear();
         };
@@ -239,6 +242,7 @@ var BABYLON;
                 };
             }
             this.onParsedObservable.notifyObservers(parsedData);
+            this.onParsedObservable.clear();
             return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
@@ -275,7 +279,14 @@ var BABYLON;
             loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
             loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
             loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
-            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            loader.onExtensionLoadedObservable.add(function (extension) { return _this.onExtensionLoadedObservable.notifyObservers(extension); });
+            loader.onCompleteObservable.add(function () {
+                _this.onMeshLoadedObservable.clear();
+                _this.onTextureLoadedObservable.clear();
+                _this.onMaterialLoadedObservable.clear();
+                _this.onCompleteObservable.notifyObservers(_this);
+                _this.onCompleteObservable.clear();
+            });
             return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
@@ -1827,8 +1838,8 @@ var BABYLON;
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
-                this.extensions = null;
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -2664,12 +2675,8 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
-                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
-                    var name_1 = _a[_i];
-                    var extension = GLTFLoader._Factories[name_1](this);
-                    this._extensions[name_1] = extension;
-                }
             }
             GLTFLoader._Register = function (name, factory) {
                 if (GLTFLoader._Factories[name]) {
@@ -2687,21 +2694,14 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
-            Object.defineProperty(GLTFLoader.prototype, "extensions", {
-                get: function () {
-                    return this.extensions;
-                },
-                enumerable: true,
-                configurable: true
-            });
             GLTFLoader.prototype.dispose = function () {
                 if (this._disposed) {
                     return;
                 }
                 this._disposed = true;
-                this._abortRequests();
-                this._releaseResources();
                 this.onDisposeObservable.notifyObservers(this);
+                this.onDisposeObservable.clear();
+                this._clear();
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
@@ -2741,6 +2741,7 @@ var BABYLON;
             GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._loadExtensions();
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
@@ -2764,22 +2765,34 @@ var BABYLON;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         BABYLON.Tools.SetImmediate(function () {
-                            Promise.all(_this._completePromises).then(function () {
-                                _this._releaseResources();
-                                _this._state = BABYLON.GLTFLoaderState.Complete;
-                                _this.onCompleteObservable.notifyObservers(_this);
-                            }).catch(function (error) {
-                                BABYLON.Tools.Error("glTF Loader: " + error.message);
-                                _this.dispose();
-                            });
+                            if (!_this._disposed) {
+                                Promise.all(_this._completePromises).then(function () {
+                                    _this._state = BABYLON.GLTFLoaderState.Complete;
+                                    _this.onCompleteObservable.notifyObservers(_this);
+                                    _this.onCompleteObservable.clear();
+                                    _this._clear();
+                                }).catch(function (error) {
+                                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                                    _this._clear();
+                                });
+                            }
                         });
-                    }).catch(function (error) {
-                        BABYLON.Tools.Error("glTF Loader: " + error.message);
-                        _this.dispose();
-                        throw error;
                     });
+                }).catch(function (error) {
+                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                    _this._clear();
+                    throw error;
                 });
             };
+            GLTFLoader.prototype._loadExtensions = function () {
+                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
+                    var name_1 = _a[_i];
+                    var extension = GLTFLoader._Factories[name_1](this);
+                    this._extensions[name_1] = extension;
+                    this.onExtensionLoadedObservable.notifyObservers(extension);
+                }
+                this.onExtensionLoadedObservable.clear();
+            };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
                 this._setupData();
@@ -3004,6 +3017,7 @@ var BABYLON;
                     var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
                     promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
                 }
+                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () {
                     babylonMesh.setEnabled(true);
                 });
@@ -3718,6 +3732,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._loadTextureAsync = function (context, textureInfo, assign) {
+                var _this = this;
                 var texture = GLTFLoader._GetProperty(context + "/index", this._gltf.textures, textureInfo.index);
                 context = "#/textures/" + textureInfo.index;
                 var promises = new Array();
@@ -3725,9 +3740,13 @@ var BABYLON;
                 var samplerData = this._loadSampler("#/samplers/" + sampler._index, sampler);
                 var deferred = new BABYLON.Deferred();
                 var babylonTexture = new BABYLON.Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
-                    deferred.resolve();
+                    if (!_this._disposed) {
+                        deferred.resolve();
+                    }
                 }, function (message, exception) {
-                    deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    if (!_this._disposed) {
+                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    }
                 });
                 promises.push(deferred.promise);
                 babylonTexture.name = texture.name || "texture" + texture._index;
@@ -3785,21 +3804,27 @@ var BABYLON;
                 }
                 return new Promise(function (resolve, reject) {
                     var request = BABYLON.Tools.LoadFile(_this._rootUrl + uri, function (data) {
-                        resolve(new Uint8Array(data));
+                        if (!_this._disposed) {
+                            resolve(new Uint8Array(data));
+                        }
                     }, function (event) {
-                        try {
-                            if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
-                                request._lengthComputable = event.lengthComputable;
-                                request._loaded = event.loaded;
-                                request._total = event.total;
-                                _this._onProgress();
+                        if (!_this._disposed) {
+                            try {
+                                if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
+                                    request._lengthComputable = event.lengthComputable;
+                                    request._loaded = event.loaded;
+                                    request._total = event.total;
+                                    _this._onProgress();
+                                }
+                            }
+                            catch (e) {
+                                reject(e);
                             }
-                        }
-                        catch (e) {
-                            reject(e);
                         }
                     }, _this._babylonScene.database, true, function (request, exception) {
-                        reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        if (!_this._disposed) {
+                            reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        }
                     });
                     _this._requests.push(request);
                 });
@@ -3921,17 +3946,15 @@ var BABYLON;
                 }
                 return Promise.all(promises).then(function () { });
             };
-            GLTFLoader.prototype._abortRequests = function () {
+            GLTFLoader.prototype._clear = function () {
                 for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
                     var request = _a[_i];
                     request.abort();
                 }
                 this._requests.length = 0;
-            };
-            GLTFLoader.prototype._releaseResources = function () {
-                if (this._gltf.images) {
-                    for (var _i = 0, _a = this._gltf.images; _i < _a.length; _i++) {
-                        var image = _a[_i];
+                if (this._gltf && this._gltf.images) {
+                    for (var _b = 0, _c = this._gltf.images; _b < _c.length; _b++) {
+                        var image = _c[_b];
                         if (image._objectURL) {
                             image._objectURL.then(function (value) {
                                 URL.revokeObjectURL(value);
@@ -3940,6 +3963,15 @@ var BABYLON;
                         }
                     }
                 }
+                delete this._gltf;
+                delete this._babylonScene;
+                this._completePromises.length = 0;
+                this._extensions = {};
+                delete this._rootBabylonMesh;
+                delete this._progressCallback;
+                this.onMeshLoadedObservable.clear();
+                this.onTextureLoadedObservable.clear();
+                this.onMaterialLoadedObservable.clear();
             };
             GLTFLoader.prototype._applyExtensions = function (actionAsync) {
                 for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
@@ -3992,15 +4024,15 @@ var BABYLON;
                     return null;
                 }
                 var extensions = property.extensions;
-                var extension = extensions[this._name];
+                var extension = extensions[this.name];
                 if (!extension) {
                     return null;
                 }
                 // Clear out the extension before executing the action to avoid recursing into the same property.
-                delete extensions[this._name];
-                return actionAsync(context + "extensions/" + this._name, extension).then(function () {
+                delete extensions[this.name];
+                return actionAsync(context + "/extensions/" + this.name, extension).then(function () {
                     // Restore the extension after completing the action.
-                    extensions[_this._name] = extension;
+                    extensions[_this.name] = extension;
                 });
             };
             /** Helper method called by the loader to allow extensions to override loading scenes. */
@@ -4046,28 +4078,26 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod
             var NAME = "MSFT_lod";
-            var MSFTLOD = /** @class */ (function (_super) {
-                __extends(MSFTLOD, _super);
-                function MSFTLOD() {
+            var MSFT_lod = /** @class */ (function (_super) {
+                __extends(MSFT_lod, _super);
+                function MSFT_lod() {
                     var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    /**
+                     * Maximum number of LODs to load, starting from the lowest LOD.
+                     */
+                    _this.maxLODsToLoad = Number.MAX_VALUE;
                     _this._loadingNodeLOD = null;
                     _this._loadNodeSignals = {};
                     _this._loadingMaterialLOD = null;
                     _this._loadMaterialSignals = {};
                     return _this;
                 }
-                Object.defineProperty(MSFTLOD.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                MSFTLOD.prototype._loadNodeAsync = function (context, node) {
+                MSFT_lod.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var firstPromise;
-                        var nodeLODs = MSFTLOD._GetLODs(context, node, _this._loader._gltf.nodes, extension.ids);
+                        var nodeLODs = _this._getLODs(context, node, _this._loader._gltf.nodes, extension.ids);
                         var _loop_1 = function (indexLOD) {
                             var nodeLOD = nodeLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -4099,11 +4129,15 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
+                    // Don't load material LODs if already loading a node LOD.
+                    if (this._loadingNodeLOD) {
+                        return null;
+                    }
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         var firstPromise;
-                        var materialLODs = MSFTLOD._GetLODs(context, material, _this._loader._gltf.materials, extension.ids);
+                        var materialLODs = _this._getLODs(context, material, _this._loader._gltf.materials, extension.ids);
                         var _loop_2 = function (indexLOD) {
                             var materialLOD = materialLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -4131,7 +4165,7 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadUriAsync = function (context, uri) {
+                MSFT_lod.prototype._loadUriAsync = function (context, uri) {
                     var _this = this;
                     // Defer the loading of uris if loading a material or node LOD.
                     if (this._loadingMaterialLOD) {
@@ -4151,18 +4185,24 @@ var BABYLON;
                 /**
                  * Gets an array of LOD properties from lowest to highest.
                  */
-                MSFTLOD._GetLODs = function (context, property, array, ids) {
-                    var properties = [property];
-                    for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) {
-                        var id = ids_1[_i];
-                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + id, array, id));
+                MSFT_lod.prototype._getLODs = function (context, property, array, ids) {
+                    if (this.maxLODsToLoad <= 0) {
+                        throw new Error("maxLODsToLoad must be greater than zero");
+                    }
+                    var properties = new Array();
+                    for (var i = ids.length - 1; i >= 0; i--) {
+                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + ids[i], array, ids[i]));
+                        if (properties.length === this.maxLODsToLoad) {
+                            return properties;
+                        }
                     }
-                    return properties.reverse();
+                    properties.push(property);
+                    return properties;
                 };
-                return MSFTLOD;
+                return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.MSFTLOD = MSFTLOD;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFTLOD(loader); });
+            Extensions.MSFT_lod = MSFT_lod;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFT_lod(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -4188,19 +4228,14 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
             var NAME = "KHR_materials_pbrSpecularGlossiness";
-            var KHRMaterialsPbrSpecularGlossiness = /** @class */ (function (_super) {
-                __extends(KHRMaterialsPbrSpecularGlossiness, _super);
-                function KHRMaterialsPbrSpecularGlossiness() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_materials_pbrSpecularGlossiness = /** @class */ (function (_super) {
+                __extends(KHR_materials_pbrSpecularGlossiness, _super);
+                function KHR_materials_pbrSpecularGlossiness() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRMaterialsPbrSpecularGlossiness.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
@@ -4219,7 +4254,7 @@ var BABYLON;
                         return (material._loaded = Promise.all(promises).then(function () { }));
                     });
                 };
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
@@ -4246,10 +4281,10 @@ var BABYLON;
                     loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                 };
-                return KHRMaterialsPbrSpecularGlossiness;
+                return KHR_materials_pbrSpecularGlossiness;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRMaterialsPbrSpecularGlossiness = KHRMaterialsPbrSpecularGlossiness;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRMaterialsPbrSpecularGlossiness(loader); });
+            Extensions.KHR_materials_pbrSpecularGlossiness = KHR_materials_pbrSpecularGlossiness;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_materials_pbrSpecularGlossiness(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -4282,19 +4317,14 @@ var BABYLON;
                 LightType["POINT"] = "point";
                 LightType["SPOT"] = "spot";
             })(LightType || (LightType = {}));
-            var KHRLights = /** @class */ (function (_super) {
-                __extends(KHRLights, _super);
-                function KHRLights() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_lights = /** @class */ (function (_super) {
+                __extends(KHR_lights, _super);
+                function KHR_lights() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRLights.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRLights.prototype._loadSceneAsync = function (context, scene) {
+                KHR_lights.prototype._loadSceneAsync = function (context, scene) {
                     var _this = this;
                     return this._loadExtensionAsync(context, scene, function (context, extension) {
                         var promise = _this._loader._loadSceneAsync(context, scene);
@@ -4306,7 +4336,7 @@ var BABYLON;
                         return promise;
                     });
                 };
-                KHRLights.prototype._loadNodeAsync = function (context, node) {
+                KHR_lights.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var promise = _this._loader._loadNodeAsync(context, node);
@@ -4343,22 +4373,22 @@ var BABYLON;
                         return promise;
                     });
                 };
-                Object.defineProperty(KHRLights.prototype, "_lights", {
+                Object.defineProperty(KHR_lights.prototype, "_lights", {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
-                        if (!extensions || !extensions[this._name]) {
-                            throw new Error("#/extensions: " + this._name + " not found");
+                        if (!extensions || !extensions[this.name]) {
+                            throw new Error("#/extensions: " + this.name + " not found");
                         }
-                        var extension = extensions[this._name];
+                        var extension = extensions[this.name];
                         return extension.lights;
                     },
                     enumerable: true,
                     configurable: true
                 });
-                return KHRLights;
+                return KHR_lights;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRLights = KHRLights;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRLights(loader); });
+            Extensions.KHR_lights = KHR_lights;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_lights(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 152 - 122
dist/preview release/loaders/babylonjs.loaders.js

@@ -1072,6 +1072,11 @@ var BABYLON;
             * Raised when the loader is disposed.
             */
             this.onDisposeObservable = new BABYLON.Observable();
+            /**
+             * Raised after a loader extension is created.
+             * Set additional options for a loader extension in this event.
+             */
+            this.onExtensionLoadedObservable = new BABYLON.Observable();
             // #endregion
             this._loader = null;
             this.name = "gltf";
@@ -1140,22 +1145,22 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
-            /**
-             * The loader state or null if not active.
-             */
-            get: function () {
-                return this._loader ? this._loader.state : null;
+        Object.defineProperty(GLTFFileLoader.prototype, "onExtensionLoaded", {
+            set: function (callback) {
+                if (this._onExtensionLoadedObserver) {
+                    this.onExtensionLoadedObservable.remove(this._onExtensionLoadedObserver);
+                }
+                this._onExtensionLoadedObserver = this.onExtensionLoadedObservable.add(callback);
             },
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(GLTFFileLoader.prototype, "loaderExtensions", {
+        Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
-             * The loader extensions or null if not active.
+             * The loader state or null if not active.
              */
             get: function () {
-                return this._loader ? this._loader.extensions : null;
+                return this._loader ? this._loader.state : null;
             },
             enumerable: true,
             configurable: true
@@ -1168,11 +1173,9 @@ var BABYLON;
                 this._loader.dispose();
                 this._loader = null;
             }
-            this.onParsedObservable.clear();
             this.onMeshLoadedObservable.clear();
             this.onTextureLoadedObservable.clear();
             this.onMaterialLoadedObservable.clear();
-            this.onCompleteObservable.clear();
             this.onDisposeObservable.notifyObservers(this);
             this.onDisposeObservable.clear();
         };
@@ -1225,6 +1228,7 @@ var BABYLON;
                 };
             }
             this.onParsedObservable.notifyObservers(parsedData);
+            this.onParsedObservable.clear();
             return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
@@ -1261,7 +1265,14 @@ var BABYLON;
             loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
             loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
             loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
-            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            loader.onExtensionLoadedObservable.add(function (extension) { return _this.onExtensionLoadedObservable.notifyObservers(extension); });
+            loader.onCompleteObservable.add(function () {
+                _this.onMeshLoadedObservable.clear();
+                _this.onTextureLoadedObservable.clear();
+                _this.onMaterialLoadedObservable.clear();
+                _this.onCompleteObservable.notifyObservers(_this);
+                _this.onCompleteObservable.clear();
+            });
             return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
@@ -2813,8 +2824,8 @@ var BABYLON;
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
-                this.extensions = null;
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -3632,12 +3643,8 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
-                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
-                    var name_1 = _a[_i];
-                    var extension = GLTFLoader._Factories[name_1](this);
-                    this._extensions[name_1] = extension;
-                }
             }
             GLTFLoader._Register = function (name, factory) {
                 if (GLTFLoader._Factories[name]) {
@@ -3655,21 +3662,14 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
-            Object.defineProperty(GLTFLoader.prototype, "extensions", {
-                get: function () {
-                    return this.extensions;
-                },
-                enumerable: true,
-                configurable: true
-            });
             GLTFLoader.prototype.dispose = function () {
                 if (this._disposed) {
                     return;
                 }
                 this._disposed = true;
-                this._abortRequests();
-                this._releaseResources();
                 this.onDisposeObservable.notifyObservers(this);
+                this.onDisposeObservable.clear();
+                this._clear();
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
@@ -3709,6 +3709,7 @@ var BABYLON;
             GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._loadExtensions();
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
@@ -3732,22 +3733,34 @@ var BABYLON;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         BABYLON.Tools.SetImmediate(function () {
-                            Promise.all(_this._completePromises).then(function () {
-                                _this._releaseResources();
-                                _this._state = BABYLON.GLTFLoaderState.Complete;
-                                _this.onCompleteObservable.notifyObservers(_this);
-                            }).catch(function (error) {
-                                BABYLON.Tools.Error("glTF Loader: " + error.message);
-                                _this.dispose();
-                            });
+                            if (!_this._disposed) {
+                                Promise.all(_this._completePromises).then(function () {
+                                    _this._state = BABYLON.GLTFLoaderState.Complete;
+                                    _this.onCompleteObservable.notifyObservers(_this);
+                                    _this.onCompleteObservable.clear();
+                                    _this._clear();
+                                }).catch(function (error) {
+                                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                                    _this._clear();
+                                });
+                            }
                         });
-                    }).catch(function (error) {
-                        BABYLON.Tools.Error("glTF Loader: " + error.message);
-                        _this.dispose();
-                        throw error;
                     });
+                }).catch(function (error) {
+                    BABYLON.Tools.Error("glTF Loader: " + error.message);
+                    _this._clear();
+                    throw error;
                 });
             };
+            GLTFLoader.prototype._loadExtensions = function () {
+                for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
+                    var name_1 = _a[_i];
+                    var extension = GLTFLoader._Factories[name_1](this);
+                    this._extensions[name_1] = extension;
+                    this.onExtensionLoadedObservable.notifyObservers(extension);
+                }
+                this.onExtensionLoadedObservable.clear();
+            };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
                 this._setupData();
@@ -3972,6 +3985,7 @@ var BABYLON;
                     var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
                     promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
                 }
+                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () {
                     babylonMesh.setEnabled(true);
                 });
@@ -4686,6 +4700,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._loadTextureAsync = function (context, textureInfo, assign) {
+                var _this = this;
                 var texture = GLTFLoader._GetProperty(context + "/index", this._gltf.textures, textureInfo.index);
                 context = "#/textures/" + textureInfo.index;
                 var promises = new Array();
@@ -4693,9 +4708,13 @@ var BABYLON;
                 var samplerData = this._loadSampler("#/samplers/" + sampler._index, sampler);
                 var deferred = new BABYLON.Deferred();
                 var babylonTexture = new BABYLON.Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, function () {
-                    deferred.resolve();
+                    if (!_this._disposed) {
+                        deferred.resolve();
+                    }
                 }, function (message, exception) {
-                    deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    if (!_this._disposed) {
+                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    }
                 });
                 promises.push(deferred.promise);
                 babylonTexture.name = texture.name || "texture" + texture._index;
@@ -4753,21 +4772,27 @@ var BABYLON;
                 }
                 return new Promise(function (resolve, reject) {
                     var request = BABYLON.Tools.LoadFile(_this._rootUrl + uri, function (data) {
-                        resolve(new Uint8Array(data));
+                        if (!_this._disposed) {
+                            resolve(new Uint8Array(data));
+                        }
                     }, function (event) {
-                        try {
-                            if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
-                                request._lengthComputable = event.lengthComputable;
-                                request._loaded = event.loaded;
-                                request._total = event.total;
-                                _this._onProgress();
+                        if (!_this._disposed) {
+                            try {
+                                if (request && _this._state === BABYLON.GLTFLoaderState.Loading) {
+                                    request._lengthComputable = event.lengthComputable;
+                                    request._loaded = event.loaded;
+                                    request._total = event.total;
+                                    _this._onProgress();
+                                }
+                            }
+                            catch (e) {
+                                reject(e);
                             }
-                        }
-                        catch (e) {
-                            reject(e);
                         }
                     }, _this._babylonScene.database, true, function (request, exception) {
-                        reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        if (!_this._disposed) {
+                            reject(new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        }
                     });
                     _this._requests.push(request);
                 });
@@ -4889,17 +4914,15 @@ var BABYLON;
                 }
                 return Promise.all(promises).then(function () { });
             };
-            GLTFLoader.prototype._abortRequests = function () {
+            GLTFLoader.prototype._clear = function () {
                 for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
                     var request = _a[_i];
                     request.abort();
                 }
                 this._requests.length = 0;
-            };
-            GLTFLoader.prototype._releaseResources = function () {
-                if (this._gltf.images) {
-                    for (var _i = 0, _a = this._gltf.images; _i < _a.length; _i++) {
-                        var image = _a[_i];
+                if (this._gltf && this._gltf.images) {
+                    for (var _b = 0, _c = this._gltf.images; _b < _c.length; _b++) {
+                        var image = _c[_b];
                         if (image._objectURL) {
                             image._objectURL.then(function (value) {
                                 URL.revokeObjectURL(value);
@@ -4908,6 +4931,15 @@ var BABYLON;
                         }
                     }
                 }
+                delete this._gltf;
+                delete this._babylonScene;
+                this._completePromises.length = 0;
+                this._extensions = {};
+                delete this._rootBabylonMesh;
+                delete this._progressCallback;
+                this.onMeshLoadedObservable.clear();
+                this.onTextureLoadedObservable.clear();
+                this.onMaterialLoadedObservable.clear();
             };
             GLTFLoader.prototype._applyExtensions = function (actionAsync) {
                 for (var _i = 0, _a = GLTFLoader._Names; _i < _a.length; _i++) {
@@ -4960,15 +4992,15 @@ var BABYLON;
                     return null;
                 }
                 var extensions = property.extensions;
-                var extension = extensions[this._name];
+                var extension = extensions[this.name];
                 if (!extension) {
                     return null;
                 }
                 // Clear out the extension before executing the action to avoid recursing into the same property.
-                delete extensions[this._name];
-                return actionAsync(context + "extensions/" + this._name, extension).then(function () {
+                delete extensions[this.name];
+                return actionAsync(context + "/extensions/" + this.name, extension).then(function () {
                     // Restore the extension after completing the action.
-                    extensions[_this._name] = extension;
+                    extensions[_this.name] = extension;
                 });
             };
             /** Helper method called by the loader to allow extensions to override loading scenes. */
@@ -5005,28 +5037,26 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod
             var NAME = "MSFT_lod";
-            var MSFTLOD = /** @class */ (function (_super) {
-                __extends(MSFTLOD, _super);
-                function MSFTLOD() {
+            var MSFT_lod = /** @class */ (function (_super) {
+                __extends(MSFT_lod, _super);
+                function MSFT_lod() {
                     var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    /**
+                     * Maximum number of LODs to load, starting from the lowest LOD.
+                     */
+                    _this.maxLODsToLoad = Number.MAX_VALUE;
                     _this._loadingNodeLOD = null;
                     _this._loadNodeSignals = {};
                     _this._loadingMaterialLOD = null;
                     _this._loadMaterialSignals = {};
                     return _this;
                 }
-                Object.defineProperty(MSFTLOD.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                MSFTLOD.prototype._loadNodeAsync = function (context, node) {
+                MSFT_lod.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var firstPromise;
-                        var nodeLODs = MSFTLOD._GetLODs(context, node, _this._loader._gltf.nodes, extension.ids);
+                        var nodeLODs = _this._getLODs(context, node, _this._loader._gltf.nodes, extension.ids);
                         var _loop_1 = function (indexLOD) {
                             var nodeLOD = nodeLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -5058,11 +5088,15 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
+                    // Don't load material LODs if already loading a node LOD.
+                    if (this._loadingNodeLOD) {
+                        return null;
+                    }
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         var firstPromise;
-                        var materialLODs = MSFTLOD._GetLODs(context, material, _this._loader._gltf.materials, extension.ids);
+                        var materialLODs = _this._getLODs(context, material, _this._loader._gltf.materials, extension.ids);
                         var _loop_2 = function (indexLOD) {
                             var materialLOD = materialLODs[indexLOD];
                             if (indexLOD !== 0) {
@@ -5090,7 +5124,7 @@ var BABYLON;
                         return firstPromise;
                     });
                 };
-                MSFTLOD.prototype._loadUriAsync = function (context, uri) {
+                MSFT_lod.prototype._loadUriAsync = function (context, uri) {
                     var _this = this;
                     // Defer the loading of uris if loading a material or node LOD.
                     if (this._loadingMaterialLOD) {
@@ -5110,18 +5144,24 @@ var BABYLON;
                 /**
                  * Gets an array of LOD properties from lowest to highest.
                  */
-                MSFTLOD._GetLODs = function (context, property, array, ids) {
-                    var properties = [property];
-                    for (var _i = 0, ids_1 = ids; _i < ids_1.length; _i++) {
-                        var id = ids_1[_i];
-                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + id, array, id));
+                MSFT_lod.prototype._getLODs = function (context, property, array, ids) {
+                    if (this.maxLODsToLoad <= 0) {
+                        throw new Error("maxLODsToLoad must be greater than zero");
+                    }
+                    var properties = new Array();
+                    for (var i = ids.length - 1; i >= 0; i--) {
+                        properties.push(GLTF2.GLTFLoader._GetProperty(context + "/ids/" + ids[i], array, ids[i]));
+                        if (properties.length === this.maxLODsToLoad) {
+                            return properties;
+                        }
                     }
-                    return properties.reverse();
+                    properties.push(property);
+                    return properties;
                 };
-                return MSFTLOD;
+                return MSFT_lod;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.MSFTLOD = MSFTLOD;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFTLOD(loader); });
+            Extensions.MSFT_lod = MSFT_lod;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new MSFT_lod(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -5138,19 +5178,14 @@ var BABYLON;
         (function (Extensions) {
             // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
             var NAME = "KHR_materials_pbrSpecularGlossiness";
-            var KHRMaterialsPbrSpecularGlossiness = /** @class */ (function (_super) {
-                __extends(KHRMaterialsPbrSpecularGlossiness, _super);
-                function KHRMaterialsPbrSpecularGlossiness() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_materials_pbrSpecularGlossiness = /** @class */ (function (_super) {
+                __extends(KHR_materials_pbrSpecularGlossiness, _super);
+                function KHR_materials_pbrSpecularGlossiness() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRMaterialsPbrSpecularGlossiness.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
@@ -5169,7 +5204,7 @@ var BABYLON;
                         return (material._loaded = Promise.all(promises).then(function () { }));
                     });
                 };
-                KHRMaterialsPbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
@@ -5196,10 +5231,10 @@ var BABYLON;
                     loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                 };
-                return KHRMaterialsPbrSpecularGlossiness;
+                return KHR_materials_pbrSpecularGlossiness;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRMaterialsPbrSpecularGlossiness = KHRMaterialsPbrSpecularGlossiness;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRMaterialsPbrSpecularGlossiness(loader); });
+            Extensions.KHR_materials_pbrSpecularGlossiness = KHR_materials_pbrSpecularGlossiness;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_materials_pbrSpecularGlossiness(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
@@ -5223,19 +5258,14 @@ var BABYLON;
                 LightType["POINT"] = "point";
                 LightType["SPOT"] = "spot";
             })(LightType || (LightType = {}));
-            var KHRLights = /** @class */ (function (_super) {
-                __extends(KHRLights, _super);
-                function KHRLights() {
-                    return _super !== null && _super.apply(this, arguments) || this;
+            var KHR_lights = /** @class */ (function (_super) {
+                __extends(KHR_lights, _super);
+                function KHR_lights() {
+                    var _this = _super !== null && _super.apply(this, arguments) || this;
+                    _this.name = NAME;
+                    return _this;
                 }
-                Object.defineProperty(KHRLights.prototype, "_name", {
-                    get: function () {
-                        return NAME;
-                    },
-                    enumerable: true,
-                    configurable: true
-                });
-                KHRLights.prototype._loadSceneAsync = function (context, scene) {
+                KHR_lights.prototype._loadSceneAsync = function (context, scene) {
                     var _this = this;
                     return this._loadExtensionAsync(context, scene, function (context, extension) {
                         var promise = _this._loader._loadSceneAsync(context, scene);
@@ -5247,7 +5277,7 @@ var BABYLON;
                         return promise;
                     });
                 };
-                KHRLights.prototype._loadNodeAsync = function (context, node) {
+                KHR_lights.prototype._loadNodeAsync = function (context, node) {
                     var _this = this;
                     return this._loadExtensionAsync(context, node, function (context, extension) {
                         var promise = _this._loader._loadNodeAsync(context, node);
@@ -5284,22 +5314,22 @@ var BABYLON;
                         return promise;
                     });
                 };
-                Object.defineProperty(KHRLights.prototype, "_lights", {
+                Object.defineProperty(KHR_lights.prototype, "_lights", {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
-                        if (!extensions || !extensions[this._name]) {
-                            throw new Error("#/extensions: " + this._name + " not found");
+                        if (!extensions || !extensions[this.name]) {
+                            throw new Error("#/extensions: " + this.name + " not found");
                         }
-                        var extension = extensions[this._name];
+                        var extension = extensions[this.name];
                         return extension.lights;
                     },
                     enumerable: true,
                     configurable: true
                 });
-                return KHRLights;
+                return KHR_lights;
             }(GLTF2.GLTFLoaderExtension));
-            Extensions.KHRLights = KHRLights;
-            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHRLights(loader); });
+            Extensions.KHR_lights = KHR_lights;
+            GLTF2.GLTFLoader._Register(NAME, function (loader) { return new KHR_lights(loader); });
         })(Extensions = GLTF2.Extensions || (GLTF2.Extensions = {}));
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));

File diff suppressed because it is too large
+ 3 - 3
dist/preview release/loaders/babylonjs.loaders.min.js


+ 36 - 26
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -129,30 +129,34 @@ declare module BABYLON {
         json: Object;
         bin: Nullable<ArrayBufferView>;
     }
+    interface IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Whether this extension is enabled.
+         */
+        enabled: boolean;
+    }
     enum GLTFLoaderState {
         Loading = 0,
         Ready = 1,
         Complete = 2,
     }
-    interface IGLTFLoaderExtension {
-        enabled: boolean;
-    }
-    interface IGLTFLoaderExtensions {
-        [name: string]: IGLTFLoaderExtension;
-    }
     interface IGLTFLoader extends IDisposable {
         coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
         animationStartMode: GLTFLoaderAnimationStartMode;
         compileMaterials: boolean;
         useClipPlane: boolean;
         compileShadowGenerators: boolean;
-        onDisposeObservable: Observable<IGLTFLoader>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{
             meshes: AbstractMesh[];
             particleSystems: ParticleSystem[];
@@ -226,13 +230,16 @@ declare module BABYLON {
         private _onDisposeObserver;
         onDispose: () => void;
         /**
-         * The loader state or null if not active.
+         * Raised after a loader extension is created.
+         * Set additional options for a loader extension in this event.
          */
-        readonly loaderState: Nullable<GLTFLoaderState>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
+        private _onExtensionLoadedObserver;
+        onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
-         * The loader extensions or null if not active.
+         * The loader state or null if not active.
          */
-        readonly loaderExtensions: Nullable<IGLTFLoaderExtensions>;
+        readonly loaderState: Nullable<GLTFLoaderState>;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -664,8 +671,8 @@ declare module BABYLON.GLTF1 {
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
+        onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
-        extensions: Nullable<IGLTFLoaderExtensions>;
         dispose(): void;
         private _importMeshAsync(meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError);
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress: (event: SceneLoaderProgressEvent) => void): Promise<{
@@ -939,10 +946,9 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
-        readonly extensions: IGLTFLoaderExtensions;
-        constructor();
         dispose(): void;
         importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{
             meshes: AbstractMesh[];
@@ -951,6 +957,7 @@ declare module BABYLON.GLTF2 {
         }>;
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
         private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadExtensions();
         private _loadData(data);
         private _setupData();
         private _createRootNode();
@@ -1001,19 +1008,18 @@ declare module BABYLON.GLTF2 {
         private static _ValidateUri(uri);
         private _compileMaterialsAsync();
         private _compileShadowGeneratorsAsync();
-        private _abortRequests();
-        private _releaseResources();
+        private _clear();
         _applyExtensions<T>(actionAsync: (extension: GLTFLoaderExtension) => Nullable<Promise<T>>): Nullable<Promise<T>>;
     }
 }
 
 
 declare module BABYLON.GLTF2 {
-    abstract class GLTFLoaderExtension {
+    abstract class GLTFLoaderExtension implements IGLTFLoaderExtension {
         enabled: boolean;
+        readonly abstract name: string;
         protected _loader: GLTFLoader;
         constructor(loader: GLTFLoader);
-        protected readonly abstract _name: string;
         /** Override this method to modify the default behavior for loading scenes. */
         protected _loadSceneAsync(context: string, node: ILoaderScene): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading nodes. */
@@ -1037,26 +1043,30 @@ declare module BABYLON.GLTF2 {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class MSFTLOD extends GLTFLoaderExtension {
+    class MSFT_lod extends GLTFLoaderExtension {
+        readonly name: string;
+        /**
+         * Maximum number of LODs to load, starting from the lowest LOD.
+         */
+        maxLODsToLoad: number;
         private _loadingNodeLOD;
         private _loadNodeSignals;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
-        protected readonly _name: string;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
          * Gets an array of LOD properties from lowest to highest.
          */
-        private static _GetLODs<T>(context, property, array, ids);
+        private _getLODs<T>(context, property, array, ids);
     }
 }
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRMaterialsPbrSpecularGlossiness extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
         private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
     }
@@ -1064,8 +1074,8 @@ declare module BABYLON.GLTF2.Extensions {
 
 
 declare module BABYLON.GLTF2.Extensions {
-    class KHRLights extends GLTFLoaderExtension {
-        protected readonly _name: string;
+    class KHR_lights extends GLTFLoaderExtension {
+        readonly name: string;
         protected _loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         private readonly _lights;

+ 2 - 470
dist/preview release/typedocValidationBaseline.json

@@ -1,7 +1,7 @@
 {
-  "errors": 7933,
+  "errors": 7267,
   "babylon.typedoc.json": {
-    "errors": 7933,
+    "errors": 7267,
     "AnimationKeyInterpolation": {
       "Enumeration": {
         "Comments": {
@@ -334,11 +334,6 @@
             "MissingText": true
           }
         },
-        "_isDisposed": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
         "_lightSources": {
           "Comments": {
             "MissingText": true
@@ -1195,11 +1190,6 @@
             }
           }
         },
-        "isDisposed": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
         "isInFrustum": {
           "Comments": {
             "MissingReturn": true
@@ -14674,42 +14664,6 @@
         }
       }
     },
-    "HighlightLayer": {
-      "Property": {
-        "name": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "glowingMeshStencilReference": {
-          "Naming": {
-            "NotPascalCase": true
-          }
-        },
-        "neutralColor": {
-          "Naming": {
-            "NotPascalCase": true
-          }
-        },
-        "normalMeshStencilReference": {
-          "Naming": {
-            "NotPascalCase": true
-          }
-        }
-      },
-      "Method": {
-        "_rebuild": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "shouldRender": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        }
-      }
-    },
     "HighlightsPostProcess": {
       "Class": {
         "Comments": {
@@ -20177,418 +20131,6 @@
         }
       }
     },
-    "Node": {
-      "Constructor": {
-        "new Node": {
-          "Comments": {
-            "MissingText": true,
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "scene": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      },
-      "Property": {
-        "_cache": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "_currentRenderId": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "_waitingParentId": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "animations": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "behaviors": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "doNotSerialize": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "id": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "metadata": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "name": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "onDispose": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "onReady": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "parent": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "state": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "uniqueId": {
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      },
-      "Method": {
-        "_getDescendants": {
-          "Parameter": {
-            "predicate": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "_initCache": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "_isSynchronized": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "_markSyncedWithParent": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "_setReady": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "state": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "_updateCache": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "ignoreParentClass": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "addBehavior": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "behavior": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "computeWorldMatrix": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "force": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "createAnimationRange": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "name": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "from": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "to": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "deleteAnimationRange": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "name": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "deleteFrames": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "dispose": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "getAnimationByName": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "name": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getAnimationRange": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "name": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getBehaviorByName": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "name": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getChildMeshes": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "directDescendantsOnly": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "predicate": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getChildTransformNodes": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "directDescendantsOnly": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "predicate": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getChildren": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "predicate": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getClassName": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "getDescendants": {
-          "Parameter": {
-            "predicate": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "getEngine": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "getScene": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "getWorldMatrix": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "hasNewParent": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "update": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "isDescendantOf": {
-          "Comments": {
-            "MissingReturn": true
-          }
-        },
-        "isEnabled": {
-          "Parameter": {
-            "checkAncestors": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "isSynchronized": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "updateCache": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "isSynchronizedWithParent": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "removeBehavior": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "behavior": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "serializeAnimationRanges": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "updateCache": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "force": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "ParseAnimationRanges": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "node": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "parsedNode": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "scene": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        }
-      }
-    },
     "NullEngineOptions": {
       "Class": {
         "Comments": {
@@ -27406,11 +26948,6 @@
             "MissingText": true
           }
         },
-        "highlightLayers": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
         "hoverCursor": {
           "Comments": {
             "MissingText": true
@@ -27441,11 +26978,6 @@
             "MissingText": true
           }
         },
-        "layers": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
         "lensFlareSystems": {
           "Comments": {
             "MissingText": true

File diff suppressed because it is too large
+ 53 - 52
dist/preview release/viewer/babylon.viewer.js


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

@@ -11,10 +11,12 @@
 - Introduced [Projection Texture on SpotLight](http://doc.babylonjs.com/babylon101/lights#projection-texture) ([lostink](https://github.com/lostink))
 - Introduced support for [local cubemaps](http://doc.babylonjs.com/how_to/reflect#using-local-cubemap-mode) ([deltakosh](https://github.com/deltakosh))
 - Added [VideoDome](http://doc.babylonjs.com/how_to/360videodome) class to easily support 360 videos ([DavidHGillen](https://github.com/DavidHGillen))
+- Added [GlowLayer](https://doc.babylonjs.com/how_to/glow_layer) to easily support glow from emissive materials ([sebavan](https://github.com/sebavan))
 
 ## Updates
 
 - Tons of functions and classes received the code comments they deserved (All the community)
+- Added `particleSystem.reset()` to clear a particle system ([deltakosh](https://github.com/deltakosh))
 - Added support for all RGBA orders (BGR, RGB, etc..) for the DDS loader ([deltakosh](https://github.com/deltakosh))
 - Improved [SceneOptimizer](http://doc.babylonjs.com/how_to/how_to_use_sceneoptimizer) to provide better adaptability ([deltakosh](https://github.com/deltakosh))
 - Improved `scene.isReady()` function which now takes in account shadows and LOD ([deltakosh](https://github.com/deltakosh))
@@ -47,8 +49,9 @@
 - (Viewer) The viewer supports custom shaders in the configuration. ([RaananW](https://github.com/RaananW))
 - Documented PostProcessRenderEffect, DefaultRenderingPipeline, BlurPostProcess, DepthOfFieldEffect, PostProcess, PostProcessManager, Effect classes ([trevordev](https://github.com/trevordev))
 - SPS internal storage of each solid particle rotation matrix ([jbousquie](https://github.com/jbousquie)) 
+- SPS particle parenting feature ([jbousquie](https://github.com/jbousquie))
 - (Viewer) Introducing the viewer labs - testing new features. ([RaananW](https://github.com/RaananW))
-- AssetContainer Class and loading methods. ([trevordev](https://github.com/trevordev))
+- AssetContainer Class and loading methods ([trevordev](https://github.com/trevordev))
 - KeepAssets class and AssetContainer.moveAllFromScene ([HoloLite](http://www.html5gamedevs.com/profile/28694-hololite/) [trevordev](https://github.com/trevordev))
 - (Viewer) It is now possible to update parts of the configuration without rcreating the objects. ([RaananW](https://github.com/RaananW))
 - (Gulp) extra/external declarations can be prepended to final declarations during build. ([RaananW](https://github.com/RaananW))
@@ -63,6 +66,7 @@
 - Added checks to VertexData.merge to ensure data is valid before merging. ([bghgary](https://github.com/bghgary)]
 - Ability to set a mesh to customize the webVR gaze tracker ([trevordev](https://github.com/trevordev))
 - Added promise-based async functions for initWebVRAsync and useStandingMatrixAsync ([trevordev](https://github.com/trevordev))
+- Add stroke (outline) options on GUI text control ([SvenFrankson](https://github.com/SvenFrankson))
 
 ## Bug fixes
 

+ 49 - 0
gui/src/controls/textBlock.ts

@@ -10,6 +10,8 @@ module BABYLON.GUI {
         private _lines: any[];
         private _resizeToFit: boolean = false;
         private _lineSpacing: ValueAndUnit = new ValueAndUnit(0);
+        private _outlineWidth: number = 0;
+        private _outlineColor: string = "white";
         /**
         * An event triggered after the text is changed
         * @type {BABYLON.Observable}
@@ -141,6 +143,42 @@ module BABYLON.GUI {
         }
 
         /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        public get outlineWidth(): number {
+            return this._outlineWidth;
+        }
+
+        /**
+         * Gets or sets outlineWidth of the text to display
+         */
+        public set outlineWidth(value: number) {
+            if (this._outlineWidth === value) {
+                return;
+            }
+            this._outlineWidth = value;
+            this._markAsDirty();
+        }
+
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        public get outlineColor(): string {
+            return this._outlineColor;
+        }
+
+        /**
+         * Gets or sets outlineColor of the text to display
+         */
+        public set outlineColor(value: string) {
+            if (this._outlineColor === value) {
+                return;
+            }
+            this._outlineColor = value;
+            this._markAsDirty();
+        }
+
+        /**
          * Creates a new TextBlock object
          * @param name defines the name of the control
          * @param text defines the text to display (emptry string by default)
@@ -182,6 +220,9 @@ module BABYLON.GUI {
                 context.shadowOffsetY = this.shadowOffsetY;
             }
 
+            if (this.outlineWidth) {
+                context.strokeText(text, this._currentMeasure.left + x, y);
+            }
             context.fillText(text, this._currentMeasure.left + x, y);
         }
 
@@ -198,6 +239,14 @@ module BABYLON.GUI {
             context.restore();
         }
 
+        protected _applyStates(context: CanvasRenderingContext2D): void {
+            super._applyStates(context);
+            if (this.outlineWidth) {
+                context.lineWidth = this.outlineWidth;
+                context.strokeStyle = this.outlineColor;
+            }
+        }
+
         protected _additionalProcessing(parentMeasure: Measure, context: CanvasRenderingContext2D): void {
             this._lines = [];
             var _lines = this.text.split("\n");

+ 3 - 2
inspector/sass/_detailPanel.scss

@@ -31,7 +31,7 @@
     .prop-value {
         @extend .base-property;
         width:59%;
-        padding-left:10px;
+        padding-left:5px;
         &.clickable {
             cursor:pointer;
             &:hover {
@@ -113,7 +113,8 @@
   // The div displaying a color
   .color-element {
       @extend .element-viewer;
-      top: 2px;
+      width: 20px;
+      height: 15px;
   }
 
   // The div displaying a texture element

+ 3 - 0
inspector/sass/_tree.scss

@@ -65,4 +65,7 @@
             
         }
     }
+    .line_invisible {
+        display: none;
+    }
 }

+ 1 - 1
inspector/src/properties.ts

@@ -28,7 +28,7 @@ module INSPECTOR {
         },
         'Color3': {
             type: BABYLON.Color3,
-            format: (color: BABYLON.Color3) => { return `R:${color.r}, G:${color.g}, B:${color.b}` },
+            format: (color: BABYLON.Color3) => { return `R:${color.r.toPrecision(2)}, G:${color.g.toPrecision(2)}, B:${color.b.toPrecision(2)}` },
             slider: {
                 r: { min: 0, max: 1, step: 0.01 },
                 g: { min: 0, max: 1, step: 0.01 },

+ 12 - 1
inspector/src/tree/TreeItem.ts

@@ -80,7 +80,18 @@ module INSPECTOR {
 
         /** Build the HTML of this item */
         protected _build() {
-            this._div.className = 'line';
+
+            /**
+             *  Hide the debug objects :
+             * - Axis : xline, yline, zline
+             * */
+            let adapterId = this._adapter.id();
+            if (adapterId == "xline"
+                || adapterId == "yline"
+                || adapterId == "zline") {
+                this._div.className = "line_invisible";
+            }
+            else this._div.className = 'line';
 
             // special class for transform node ONLY
             if (this.adapter instanceof MeshAdapter) {

+ 34 - 15
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -68,10 +68,10 @@ module BABYLON.GLTF2 {
 
             this._disposed = true;
 
-            this._abortRequests();
-            this._releaseResources();
-
             this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
+
+            this._clear();
         }
 
         public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }> {
@@ -115,11 +115,7 @@ module BABYLON.GLTF2 {
 
         private _loadAsync(nodes: Nullable<Array<ILoaderNode>>, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
             return Promise.resolve().then(() => {
-                for (const name of GLTFLoader._Names) {
-                    const extension = GLTFLoader._Factories[name](this);
-                    this._extensions[name] = extension;
-                    this.onExtensionLoadedObservable.notifyObservers(extension);
-                }
+                this._loadExtensions();
 
                 this._babylonScene = scene;
                 this._rootUrl = rootUrl;
@@ -153,23 +149,35 @@ module BABYLON.GLTF2 {
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
                             Promise.all(this._completePromises).then(() => {
-                                this._releaseResources();
                                 this._state = GLTFLoaderState.Complete;
                                 this.onCompleteObservable.notifyObservers(this);
+                                this.onCompleteObservable.clear();
+                                this._clear();
                             }).catch(error => {
                                 Tools.Error("glTF Loader: " + error.message);
-                                this.dispose();
+                                this._clear();
                             });
                         }
                     });
                 });
             }).catch(error => {
                 Tools.Error("glTF Loader: " + error.message);
-                this.dispose();
+                this._clear();
                 throw error;
             });
         }
 
+        private _loadExtensions(): void {
+            for (const name of GLTFLoader._Names) {
+                const extension = GLTFLoader._Factories[name](this);
+                this._extensions[name] = extension;
+
+                this.onExtensionLoadedObservable.notifyObservers(extension);
+            }
+
+            this.onExtensionLoadedObservable.clear();
+        }
+
         private _loadData(data: IGLTFLoaderData): void {
             this._gltf = data.json as ILoaderGLTF;
             this._setupData();
@@ -430,6 +438,8 @@ module BABYLON.GLTF2 {
                 promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
             }
 
+            this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+
             return Promise.all(promises).then(() => {
                 babylonMesh.setEnabled(true);
             });
@@ -1513,16 +1523,14 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
         }
 
-        private _abortRequests(): void {
+        private _clear(): void {
             for (const request of this._requests) {
                 request.abort();
             }
 
             this._requests.length = 0;
-        }
 
-        private _releaseResources(): void {
-            if (this._gltf.images) {
+            if (this._gltf && this._gltf.images) {
                 for (const image of this._gltf.images) {
                     if (image._objectURL) {
                         image._objectURL.then(value => {
@@ -1533,6 +1541,17 @@ module BABYLON.GLTF2 {
                     }
                 }
             }
+
+            delete this._gltf;
+            delete this._babylonScene;
+            this._completePromises.length = 0;
+            this._extensions = {};
+            delete this._rootBabylonMesh;
+            delete this._progressCallback;
+
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
         }
 
         public _applyExtensions<T>(actionAsync: (extension: GLTFLoaderExtension) => Nullable<Promise<T>>) {

+ 1 - 2
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -3,6 +3,7 @@
 module BABYLON.GLTF2 {
     export abstract class GLTFLoaderExtension implements IGLTFLoaderExtension {
         public enabled = true;
+        public abstract readonly name: string;
 
         protected _loader: GLTFLoader;
 
@@ -10,8 +11,6 @@ module BABYLON.GLTF2 {
             this._loader = loader;
         }
 
-        public abstract readonly name: string;
-
         // #region Overridable Methods
 
         /** Override this method to modify the default behavior for loading scenes. */

+ 12 - 3
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -239,11 +239,9 @@ module BABYLON {
                 this._loader = null;
             }
 
-            this.onParsedObservable.clear();
             this.onMeshLoadedObservable.clear();
             this.onTextureLoadedObservable.clear();
             this.onMaterialLoadedObservable.clear();
-            this.onCompleteObservable.clear();
 
             this.onDisposeObservable.notifyObservers(this);
             this.onDisposeObservable.clear();
@@ -303,6 +301,8 @@ module BABYLON {
             }
 
             this.onParsedObservable.notifyObservers(parsedData);
+            this.onParsedObservable.clear();
+
             return parsedData;
         }
 
@@ -346,8 +346,17 @@ module BABYLON {
             loader.onMeshLoadedObservable.add(mesh => this.onMeshLoadedObservable.notifyObservers(mesh));
             loader.onTextureLoadedObservable.add(texture => this.onTextureLoadedObservable.notifyObservers(texture));
             loader.onMaterialLoadedObservable.add(material => this.onMaterialLoadedObservable.notifyObservers(material));
-            loader.onCompleteObservable.add(() => this.onCompleteObservable.notifyObservers(this));
             loader.onExtensionLoadedObservable.add(extension => this.onExtensionLoadedObservable.notifyObservers(extension));
+
+            loader.onCompleteObservable.add(() => {
+                this.onMeshLoadedObservable.clear();
+                this.onTextureLoadedObservable.clear();
+                this.onMaterialLoadedObservable.clear();
+
+                this.onCompleteObservable.notifyObservers(this);
+                this.onCompleteObservable.clear();
+            });
+
             return loader;
         }
 

+ 1 - 1
package.json

@@ -41,4 +41,4 @@
     },
     "readme": "Babylon.js is a 3D engine based on webgl and javascript",
     "readmeFilename": "README.md"
-}
+}

+ 1 - 1
src/Animations/babylon.animatable.ts

@@ -110,7 +110,7 @@
                 var fps = runtimeAnimations[0].animation.framePerSecond;
                 var currentFrame = runtimeAnimations[0].currentFrame;
                 var adjustTime = frame - currentFrame;
-                var delay = adjustTime * 1000 / fps;
+                var delay = adjustTime * 1000 / (fps * this.speedRatio);
                 if (this._localDelayOffset === null) {
                     this._localDelayOffset = 0;
                 }

+ 1 - 1
src/Audio/babylon.soundtrack.ts

@@ -35,7 +35,7 @@
         }
 
         public dispose() {
-            if (Engine.audioEngine.canUseWebAudio) {
+            if (Engine.audioEngine && Engine.audioEngine.canUseWebAudio) {
                 if (this._connectedAnalyser) {
                     this._connectedAnalyser.stopDebugCanvas();
                 }

+ 0 - 1
src/Engine/babylon.nullEngine.ts

@@ -265,7 +265,6 @@
 
                 this._stencilState.reset();
                 this._depthCullingState.reset();
-                this.setDepthFunctionToLessOrEqual();
                 this._alphaState.reset();
             }
 

+ 639 - 0
src/Layer/babylon.effectLayer.ts

@@ -0,0 +1,639 @@
+module BABYLON {
+    /**
+     * Effect layer options. This helps customizing the behaviour
+     * of the effect layer.
+     */
+    export interface IEffectLayerOptions {
+        /**
+         * Multiplication factor apply to the canvas size to compute the render target size
+         * used to generated the objects (the smaller the faster).
+         */
+        mainTextureRatio: number;
+
+        /**
+         * Enforces a fixed size texture to ensure effect stability across devices.
+         */
+        mainTextureFixedSize?: number;
+
+        /**
+         * Alpha blending mode used to apply the blur. Default depends of the implementation.
+         */
+        alphaBlendingMode: number;
+
+        /**
+         * The camera attached to the layer.
+         */
+        camera: Nullable<Camera>;
+    }
+
+    /**
+     * The effect layer Helps adding post process effect blended with the main pass.
+     * 
+     * This can be for instance use to generate glow or higlight effects on the scene.
+     * 
+     * The effect layer class can not be used directly and is intented to inherited from to be 
+     * customized per effects.
+     */
+    export abstract class EffectLayer {
+
+        private _vertexBuffers: { [key: string]: Nullable<VertexBuffer> } = {};
+        private _indexBuffer: Nullable<WebGLBuffer>;
+        private _cachedDefines: string;
+        private _effectLayerMapGenerationEffect: Effect;
+        private _effectLayerOptions: IEffectLayerOptions;
+        private _mergeEffect: Effect;
+
+        protected _scene: Scene;
+        protected _engine: Engine;
+        protected _maxSize: number = 0;
+        protected _mainTextureDesiredSize: ISize = { width: 0, height: 0 };
+        protected _mainTexture: RenderTargetTexture;
+        protected _shouldRender = true;
+        protected _postProcesses: PostProcess[] = [];
+        protected _textures: BaseTexture[] = [];
+        protected _emissiveTextureAndColor: { texture: Nullable<BaseTexture>, color: Color4 } = { texture: null, color: new Color4() };
+
+        /**
+         * The clear color of the texture used to generate the glow map.
+         */
+        public neutralColor: Color4 = new Color4();
+
+        /**
+         * Specifies wether the highlight layer is enabled or not.
+         */
+        public isEnabled: boolean = true;
+
+        /**
+         * Gets the camera attached to the layer.
+         */
+        public get camera(): Nullable<Camera> {
+            return this._effectLayerOptions.camera;
+        }
+
+        /**
+         * An event triggered when the effect layer has been disposed.
+         */
+        public onDisposeObservable = new Observable<EffectLayer>();
+
+        /**
+         * An event triggered when the effect layer is about rendering the main texture with the glowy parts.
+         */
+        public onBeforeRenderMainTextureObservable = new Observable<EffectLayer>();
+
+        /**
+         * An event triggered when the generated texture is being merged in the scene.
+         */
+        public onBeforeComposeObservable = new Observable<EffectLayer>();
+
+        /**
+         * An event triggered when the generated texture has been merged in the scene.
+         */
+        public onAfterComposeObservable = new Observable<EffectLayer>();
+
+        /**
+         * An event triggered when the efffect layer changes its size.
+         */
+        public onSizeChangedObservable = new Observable<EffectLayer>();
+
+        /**
+         * Instantiates a new effect Layer and references it in the scene.
+         * @param name The name of the layer
+         * @param scene The scene to use the layer in
+         */
+        constructor(
+            /** The Friendly of the effect in the scene */
+            public name: string, 
+            scene: Scene) {
+            this._scene = scene || Engine.LastCreatedScene;
+            this._engine = scene.getEngine();
+            this._maxSize = this._engine.getCaps().maxTextureSize;
+            this._scene.effectLayers.push(this);
+
+            // Generate Buffers
+            this._generateIndexBuffer();
+            this._genrateVertexBuffer();
+        }
+
+        /**
+         * Get the effect name of the layer.
+         * @return The effect name
+         */ 
+        public abstract getEffectName(): string;
+
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @return true if ready otherwise, false
+         */ 
+        public abstract isReady(subMesh: SubMesh, useInstances: boolean): boolean;
+
+        /**
+         * Returns wether or nood the layer needs stencil enabled during the mesh rendering.
+         * @returns true if the effect requires stencil during the main canvas render pass.
+         */
+        public abstract needStencil(): boolean;
+
+        /**
+         * Create the merge effect. This is the shader use to blit the information back
+         * to the main canvas at the end of the scene rendering.
+         * @returns The effect containing the shader used to merge the effect on the  main canvas
+         */
+        protected abstract _createMergeEffect(): Effect;
+        
+        /**
+         * Creates the render target textures and post processes used in the effect layer.
+         */
+        protected abstract _createTextureAndPostProcesses(): void;
+        
+        /**
+         * Implementation specific of rendering the generating effect on the main canvas.
+         * @param effect The effect used to render through
+         */
+        protected abstract _internalRender(effect: Effect): void;
+
+        /**
+         * Sets the required values for both the emissive texture and and the main color.
+         */
+        protected abstract _setEmissiveTextureAndColor(mesh: Mesh, subMesh: SubMesh, material: Material): void;
+
+        /**
+         * Free any resources and references associated to a mesh.
+         * Internal use
+         * @param mesh The mesh to free.
+         */
+        public abstract _disposeMesh(mesh: Mesh): void;
+
+        /**
+         * Initializes the effect layer with the required options.
+         * @param options Sets of none mandatory options to use with the layer (see IEffectLayerOptions for more information)
+         */
+        protected _init(options: Partial<IEffectLayerOptions>): void {
+            // Adapt options
+            this._effectLayerOptions = {
+                mainTextureRatio: 0.5,
+                alphaBlendingMode: Engine.ALPHA_COMBINE,
+                camera: null,
+                ...options,
+            };
+
+            this._setMainTextureSize();
+            this._createMainTexture();
+            this._createTextureAndPostProcesses();
+            this._mergeEffect = this._createMergeEffect();
+        }
+
+        /**
+         * Generates the index buffer of the full screen quad blending to the main canvas.
+         */
+        private _generateIndexBuffer(): void {
+            // Indices
+            var indices = [];
+            indices.push(0);
+            indices.push(1);
+            indices.push(2);
+
+            indices.push(0);
+            indices.push(2);
+            indices.push(3);
+
+            this._indexBuffer = this._engine.createIndexBuffer(indices);
+        }
+
+        /**
+         * Generates the vertex buffer of the full screen quad blending to the main canvas.
+         */
+        private _genrateVertexBuffer(): void {
+            // VBO
+            var vertices = [];
+            vertices.push(1, 1);
+            vertices.push(-1, 1);
+            vertices.push(-1, -1);
+            vertices.push(1, -1);
+
+            var vertexBuffer = new VertexBuffer(this._engine, vertices, VertexBuffer.PositionKind, false, false, 2);
+            this._vertexBuffers[VertexBuffer.PositionKind] = vertexBuffer;
+        }
+
+        /**
+         * Sets the main texture desired size which is the closest power of two
+         * of the engine canvas size.
+         */
+        private _setMainTextureSize(): void {
+            if (this._effectLayerOptions.mainTextureFixedSize) {
+                this._mainTextureDesiredSize.width = this._effectLayerOptions.mainTextureFixedSize;
+                this._mainTextureDesiredSize.height = this._effectLayerOptions.mainTextureFixedSize;
+            }
+            else {
+                this._mainTextureDesiredSize.width = this._engine.getRenderWidth() * this._effectLayerOptions.mainTextureRatio;
+                this._mainTextureDesiredSize.height = this._engine.getRenderHeight() * this._effectLayerOptions.mainTextureRatio;
+
+                this._mainTextureDesiredSize.width = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize) : this._mainTextureDesiredSize.width;
+                this._mainTextureDesiredSize.height = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize) : this._mainTextureDesiredSize.height;
+            }
+
+            this._mainTextureDesiredSize.width = Math.floor(this._mainTextureDesiredSize.width);
+            this._mainTextureDesiredSize.height = Math.floor(this._mainTextureDesiredSize.height);
+        }
+
+        /**
+         * Creates the main texture for the effect layer.
+         */
+        protected _createMainTexture(): void {
+            this._mainTexture = new RenderTargetTexture("HighlightLayerMainRTT",
+                {
+                    width: this._mainTextureDesiredSize.width,
+                    height: this._mainTextureDesiredSize.height
+                },
+                this._scene,
+                false,
+                true,
+                Engine.TEXTURETYPE_UNSIGNED_INT);
+            this._mainTexture.activeCamera = this._effectLayerOptions.camera;
+            this._mainTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._mainTexture.anisotropicFilteringLevel = 1;
+            this._mainTexture.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
+            this._mainTexture.renderParticles = false;
+            this._mainTexture.renderList = null;
+            this._mainTexture.ignoreCameraViewport = true;
+
+            // Custom render function
+            this._mainTexture.customRenderFunction = (opaqueSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>, depthOnlySubMeshes: SmartArray<SubMesh>): void => {
+                this.onBeforeRenderMainTextureObservable.notifyObservers(this);
+
+                var index: number;
+
+                let engine = this._scene.getEngine();
+
+                if (depthOnlySubMeshes.length) {
+                    engine.setColorWrite(false);
+                    for (index = 0; index < depthOnlySubMeshes.length; index++) {
+                        this._renderSubMesh(depthOnlySubMeshes.data[index]);
+                    }
+                    engine.setColorWrite(true);
+                }
+
+                for (index = 0; index < opaqueSubMeshes.length; index++) {
+                    this._renderSubMesh(opaqueSubMeshes.data[index]);
+                }
+
+                for (index = 0; index < alphaTestSubMeshes.length; index++) {
+                    this._renderSubMesh(alphaTestSubMeshes.data[index]);
+                }
+
+                for (index = 0; index < transparentSubMeshes.length; index++) {
+                    this._renderSubMesh(transparentSubMeshes.data[index]);
+                }
+            };
+
+            this._mainTexture.onClearObservable.add((engine: Engine) => {
+                engine.clear(this.neutralColor, true, true, true);
+            });
+        }
+
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @param emissiveTexture the associated emissive texture used to generate the glow
+         * @return true if ready otherwise, false
+         */
+        protected _isReady(subMesh: SubMesh, useInstances: boolean, emissiveTexture: Nullable<BaseTexture>): boolean {
+            let material = subMesh.getMaterial();
+
+            if (!material) {
+                return false;
+            }
+
+            if (!material.isReady(subMesh.getMesh(), useInstances)) {
+                return false;
+            }
+
+            var defines = [];
+
+            var attribs = [VertexBuffer.PositionKind];
+
+            var mesh = subMesh.getMesh();
+            var uv1 = false;
+            var uv2 = false;
+
+            // Alpha test
+            if (material && material.needAlphaTesting()) {
+                var alphaTexture = material.getAlphaTestTexture();
+                if (alphaTexture) {
+                    defines.push("#define ALPHATEST");
+                    if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind) &&
+                        alphaTexture.coordinatesIndex === 1) {
+                        defines.push("#define DIFFUSEUV2");
+                        uv2 = true;
+                    }
+                    else if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
+                        defines.push("#define DIFFUSEUV1");
+                        uv1 = true;
+                    }
+                }
+            }
+
+            // Emissive
+            if (emissiveTexture) {
+                defines.push("#define EMISSIVE");
+                if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind) &&
+                    emissiveTexture.coordinatesIndex === 1) {
+                    defines.push("#define EMISSIVEUV2");
+                    uv2 = true;
+                }
+                else if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
+                    defines.push("#define EMISSIVEUV1");
+                    uv1 = true;
+                }
+            }
+
+            if (uv1) {
+                attribs.push(VertexBuffer.UVKind);
+                defines.push("#define UV1");
+            }
+            if (uv2) {
+                attribs.push(VertexBuffer.UV2Kind);
+                defines.push("#define UV2");
+            }
+
+            // Bones
+            if (mesh.useBones && mesh.computeBonesUsingShaders) {
+                attribs.push(VertexBuffer.MatricesIndicesKind);
+                attribs.push(VertexBuffer.MatricesWeightsKind);
+                if (mesh.numBoneInfluencers > 4) {
+                    attribs.push(VertexBuffer.MatricesIndicesExtraKind);
+                    attribs.push(VertexBuffer.MatricesWeightsExtraKind);
+                }
+                defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
+                defines.push("#define BonesPerMesh " + (mesh.skeleton ? (mesh.skeleton.bones.length + 1) : 0));
+            } else {
+                defines.push("#define NUM_BONE_INFLUENCERS 0");
+            }
+
+            // Instances
+            if (useInstances) {
+                defines.push("#define INSTANCES");
+                attribs.push("world0");
+                attribs.push("world1");
+                attribs.push("world2");
+                attribs.push("world3");
+            }
+
+            // Get correct effect
+            var join = defines.join("\n");
+            if (this._cachedDefines !== join) {
+                this._cachedDefines = join;
+                this._effectLayerMapGenerationEffect = this._scene.getEngine().createEffect("glowMapGeneration",
+                    attribs,
+                    ["world", "mBones", "viewProjection", "diffuseMatrix", "color", "emissiveMatrix"],
+                    ["diffuseSampler", "emissiveSampler"], join);
+            }
+
+            return this._effectLayerMapGenerationEffect.isReady();
+        }
+
+        /**
+         * Renders the glowing part of the scene by blending the blurred glowing meshes on top of the rendered scene.
+         */
+        public render(): void {
+            var currentEffect = this._mergeEffect;
+
+            // Check
+            if (!currentEffect.isReady())
+                return;
+
+            for (var i = 0; i < this._postProcesses.length; i++) {
+                if (!this._postProcesses[i].isReady()) {
+                    return;
+                }
+            }
+
+            var engine = this._scene.getEngine();
+
+            this.onBeforeComposeObservable.notifyObservers(this);
+
+            // Render
+            engine.enableEffect(currentEffect);
+            engine.setState(false);
+            
+            // VBOs
+            engine.bindBuffers(this._vertexBuffers, this._indexBuffer, currentEffect);
+
+            // Cache
+            var previousAlphaMode = engine.getAlphaMode();
+
+            // Go Blend.
+            engine.setAlphaMode(this._effectLayerOptions.alphaBlendingMode);
+
+            // Blends the map on the main canvas.
+            this._internalRender(currentEffect);
+
+            // Restore Alpha
+            engine.setAlphaMode(previousAlphaMode);
+
+            this.onAfterComposeObservable.notifyObservers(this);
+
+            // Handle size changes.
+            var size = this._mainTexture.getSize();
+            this._setMainTextureSize();
+            if (size.width !== this._mainTextureDesiredSize.width || size.height !== this._mainTextureDesiredSize.height) {
+                // Recreate RTT and post processes on size change.
+                this.onSizeChangedObservable.notifyObservers(this);
+                this._disposeTextureAndPostProcesses();
+                this._createMainTexture();
+                this._createTextureAndPostProcesses();
+            }
+        }
+
+        /**
+         * Determine if a given mesh will be used in the current effect.
+         * @param mesh mesh to test
+         * @returns true if the mesh will be used
+         */
+        public hasMesh(mesh: AbstractMesh): boolean {
+            return true;
+        }
+
+        /**
+         * Returns true if the layer contains information to display, otherwise false.
+         * @returns true if the glow layer should be rendered
+         */
+        public shouldRender(): boolean {
+            return this.isEnabled && this._shouldRender;
+        }
+
+        /**
+         * Returns true if the mesh should render, otherwise false.
+         * @param mesh The mesh to render
+         * @returns true if it should render otherwise false
+         */
+        protected _shouldRenderMesh(mesh: Mesh): boolean {
+            return true;
+        }
+
+        /**
+         * Returns true if the mesh should render, otherwise false.
+         * @param mesh The mesh to render
+         * @returns true if it should render otherwise false
+         */
+        protected _shouldRenderEmissiveTextureForMesh(mesh: Mesh): boolean {
+            return true;
+        }
+
+        /**
+         * Renders the submesh passed in parameter to the generation map.
+         */
+        protected _renderSubMesh(subMesh: SubMesh): void {
+            if (!this.shouldRender()) {
+                return;
+            }
+
+            var material = subMesh.getMaterial();
+            var mesh = subMesh.getRenderingMesh();
+            var scene = this._scene;
+            var engine = scene.getEngine();
+
+            if (!material) {
+                return;
+            }
+
+            // Do not block in blend mode.
+            if (material.needAlphaBlendingForMesh(mesh)) {
+                return;
+            }
+
+            // Culling
+            engine.setState(material.backFaceCulling);
+
+            // Managing instances
+            var batch = mesh._getInstancesRenderList(subMesh._id);
+            if (batch.mustReturn) {
+                return;
+            }
+
+            // Early Exit per mesh
+            if (!this._shouldRenderMesh(mesh)) {
+                return;
+            }
+
+            var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
+
+            this._setEmissiveTextureAndColor(mesh, subMesh, material);
+
+            if (this._isReady(subMesh, hardwareInstancedRendering, this._emissiveTextureAndColor.texture)) {
+                engine.enableEffect(this._effectLayerMapGenerationEffect);
+                mesh._bind(subMesh, this._effectLayerMapGenerationEffect, Material.TriangleFillMode);
+
+                this._effectLayerMapGenerationEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+
+                this._effectLayerMapGenerationEffect.setFloat4("color",
+                    this._emissiveTextureAndColor.color.r,
+                    this._emissiveTextureAndColor.color.g,
+                    this._emissiveTextureAndColor.color.b,
+                    this._emissiveTextureAndColor.color.a);
+
+                // Alpha test
+                if (material && material.needAlphaTesting()) {
+                    var alphaTexture = material.getAlphaTestTexture();
+                    if (alphaTexture) {
+                        this._effectLayerMapGenerationEffect.setTexture("diffuseSampler", alphaTexture);
+                        let textureMatrix = alphaTexture.getTextureMatrix();
+
+                        if (textureMatrix) {
+                            this._effectLayerMapGenerationEffect.setMatrix("diffuseMatrix", textureMatrix);
+                        }
+                    }
+                }
+
+                // Glow emissive only
+                if (this._emissiveTextureAndColor.texture) {
+                    this._effectLayerMapGenerationEffect.setTexture("emissiveSampler", this._emissiveTextureAndColor.texture);
+                    this._effectLayerMapGenerationEffect.setMatrix("emissiveMatrix", this._emissiveTextureAndColor.texture.getTextureMatrix());
+                }
+
+                // Bones
+                if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
+                    this._effectLayerMapGenerationEffect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
+                }
+
+                // Draw
+                mesh._processRendering(subMesh, this._effectLayerMapGenerationEffect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
+                    (isInstance, world) => this._effectLayerMapGenerationEffect.setMatrix("world", world));
+            } else {
+                // Need to reset refresh rate of the shadowMap
+                this._mainTexture.resetRefreshCounter();
+            }
+        }
+
+        /**
+         * Rebuild the required buffers.
+         * @ignore Internal use only.
+         */
+        public _rebuild(): void {
+            let vb = this._vertexBuffers[VertexBuffer.PositionKind];
+
+            if (vb) {
+                vb._rebuild();
+            }
+
+            this._generateIndexBuffer();
+        }
+
+        /**
+         * Dispose only the render target textures and post process.
+         */
+        private _disposeTextureAndPostProcesses(): void {
+            this._mainTexture.dispose();
+
+            for (var i = 0; i < this._postProcesses.length; i++) {
+                if (this._postProcesses[i]) {
+                    this._postProcesses[i].dispose();
+                }
+            }
+            this._postProcesses = [];
+
+            for (var i = 0; i < this._textures.length; i++) {
+                if (this._textures[i]) {
+                    this._textures[i].dispose();
+                }
+            }
+            this._textures = [];
+        }
+
+        /**
+         * Dispose the highlight layer and free resources.
+         */
+        public dispose(): void {
+            var vertexBuffer = this._vertexBuffers[VertexBuffer.PositionKind];
+            if (vertexBuffer) {
+                vertexBuffer.dispose();
+                this._vertexBuffers[VertexBuffer.PositionKind] = null;
+            }
+
+            if (this._indexBuffer) {
+                this._scene.getEngine()._releaseBuffer(this._indexBuffer);
+                this._indexBuffer = null;
+            }
+
+            // Clean textures and post processes
+            this._disposeTextureAndPostProcesses();
+
+            // Remove from scene
+            var index = this._scene.effectLayers.indexOf(this, 0);
+            if (index > -1) {
+                this._scene.effectLayers.splice(index, 1);
+            }
+
+            // Callback
+            this.onDisposeObservable.notifyObservers(this);
+
+            this.onDisposeObservable.clear();
+            this.onBeforeRenderMainTextureObservable.clear();
+            this.onBeforeComposeObservable.clear();
+            this.onAfterComposeObservable.clear();
+            this.onSizeChangedObservable.clear();
+        }
+    }
+} 

+ 437 - 0
src/Layer/babylon.glowLayer.ts

@@ -0,0 +1,437 @@
+module BABYLON {
+    /**
+     * Glow layer options. This helps customizing the behaviour
+     * of the glow layer.
+     */
+    export interface IGlowLayerOptions {
+        /**
+         * Multiplication factor apply to the canvas size to compute the render target size
+         * used to generated the glowing objects (the smaller the faster).
+         */
+        mainTextureRatio: number;
+
+        /**
+         * Enforces a fixed size texture to ensure resize independant blur.
+         */
+        mainTextureFixedSize?: number;
+
+        /**
+         * How big is the kernel of the blur texture.
+         */
+        blurKernelSize: number;
+
+        /**
+         * The camera attached to the layer.
+         */
+        camera: Nullable<Camera>;
+
+        /**
+         * Enable MSAA by chosing the number of samples.
+         */
+        mainTextureSamples?: number;
+    }
+
+    /**
+     * The glow layer Helps adding a glow effect around the emissive parts of a mesh.
+     * 
+     * Once instantiated in a scene, simply use the pushMesh or removeMesh method to add or remove
+     * glowy meshes to your scene.
+     * 
+     * Documentation: https://doc.babylonjs.com/how_to/glow_layer
+     */
+    export class GlowLayer extends EffectLayer {
+        /**
+         * Effect Name of the layer.
+         */
+        public static readonly EffectName = "GlowLayer";
+
+        /**
+         * The default blur kernel size used for the glow.
+         */
+        public static DefaultBlurKernelSize = 32;
+
+        /**
+         * The default texture size ratio used for the glow.
+         */
+        public static DefaultTextureRatio = 0.5;
+
+        /**
+         * Sets the kernel size of the blur.
+         */
+        public set blurKernelSize(value: number) {
+            this._horizontalBlurPostprocess1.kernel = value;
+            this._verticalBlurPostprocess1.kernel = value;
+            this._horizontalBlurPostprocess2.kernel = value;
+            this._verticalBlurPostprocess2.kernel = value;
+        }
+
+        /**
+         * Gets the kernel size of the blur.
+         */
+        public get blurKernelSize(): number {
+            return this._horizontalBlurPostprocess1.kernel;
+        }
+
+        /**
+         * Sets the glow intensity.
+         */
+        public set intensity(value: number) {
+            this._intensity = value;
+        }
+
+        /**
+         * Gets the glow intensity.
+         */
+        public get intensity(): number {
+            return this._intensity;
+        }
+
+        private _options: IGlowLayerOptions;
+        private _intensity: number = 1.0;
+        private _horizontalBlurPostprocess1: BlurPostProcess;
+        private _verticalBlurPostprocess1: BlurPostProcess;
+        private _horizontalBlurPostprocess2: BlurPostProcess;
+        private _verticalBlurPostprocess2: BlurPostProcess;
+        private _blurTexture1: RenderTargetTexture;
+        private _blurTexture2: RenderTargetTexture;
+        private _postProcesses1: PostProcess[];
+        private _postProcesses2: PostProcess[];
+
+        private _includedOnlyMeshes: number[] = [];
+        private _excludedMeshes: number[] = [];
+
+        /**
+         * Callback used to let the user override the color selection on a per mesh basis
+         */
+        public customEmissiveColorSelector: (mesh: Mesh, subMesh: SubMesh, material: Material, result: Color4) => void;
+        /**
+         * Callback used to let the user override the texture selection on a per mesh basis
+         */
+        public customEmissiveTextureSelector: (mesh: Mesh, subMesh: SubMesh, material: Material) => Texture;
+
+        /**
+         * Instantiates a new glow Layer and references it to the scene.
+         * @param name The name of the layer
+         * @param scene The scene to use the layer in
+         * @param options Sets of none mandatory options to use with the layer (see IGlowLayerOptions for more information)
+         */
+        constructor(public name: string, scene: Scene, options?: Partial<IGlowLayerOptions>) {
+            super(name, scene);
+            this.neutralColor = new Color4(0, 0, 0, 1);
+
+            // Adapt options
+            this._options = {
+                mainTextureRatio: GlowLayer.DefaultTextureRatio,
+                blurKernelSize: 32,
+                mainTextureFixedSize: undefined,
+                camera: null,
+                mainTextureSamples: 1,
+                ...options,
+            };
+
+            // Initialize the layer
+            this._init({
+                alphaBlendingMode: Engine.ALPHA_ADD,
+                camera: this._options.camera,
+                mainTextureFixedSize: this._options.mainTextureFixedSize,
+                mainTextureRatio: this._options.mainTextureRatio
+            });
+        }
+
+        /**
+         * Get the effect name of the layer.
+         * @return The effect name
+         */ 
+        public getEffectName(): string {
+            return GlowLayer.EffectName;
+        }
+
+        /**
+         * Create the merge effect. This is the shader use to blit the information back
+         * to the main canvas at the end of the scene rendering.
+         */
+        protected _createMergeEffect(): Effect {
+             // Effect
+             return this._engine.createEffect("glowMapMerge",
+                [VertexBuffer.PositionKind],
+                ["offset"],
+                ["textureSampler", "textureSampler2"],
+                "#define EMISSIVE \n");
+
+        }
+
+        /**
+         * Creates the render target textures and post processes used in the glow layer.
+         */
+        protected _createTextureAndPostProcesses(): void {
+            var blurTextureWidth = this._mainTextureDesiredSize.width;
+            var blurTextureHeight = this._mainTextureDesiredSize.height;
+            blurTextureWidth = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
+            blurTextureHeight = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
+
+            this._blurTexture1 = new RenderTargetTexture("GlowLayerBlurRTT",
+                {
+                    width: blurTextureWidth,
+                    height: blurTextureHeight
+                },
+                this._scene,
+                false,
+                true,
+                Engine.TEXTURETYPE_HALF_FLOAT);
+            this._blurTexture1.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture1.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture1.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
+            this._blurTexture1.renderParticles = false;
+            this._blurTexture1.ignoreCameraViewport = true;
+
+            var blurTextureWidth2 = Math.floor(blurTextureWidth / 2);
+            var blurTextureHeight2 = Math.floor(blurTextureHeight / 2);
+
+            this._blurTexture2 = new RenderTargetTexture("GlowLayerBlurRTT2",
+                {
+                    width: blurTextureWidth2,
+                    height: blurTextureHeight2
+                },
+                this._scene,
+                false,
+                true,
+                Engine.TEXTURETYPE_HALF_FLOAT);
+            this._blurTexture2.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture2.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture2.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
+            this._blurTexture2.renderParticles = false;
+            this._blurTexture2.ignoreCameraViewport = true;
+
+            this._textures = [ this._blurTexture1, this._blurTexture2 ];
+
+            this._horizontalBlurPostprocess1 = new BlurPostProcess("GlowLayerHBP1", new Vector2(1.0, 0), this._options.blurKernelSize / 2, {
+                    width:  blurTextureWidth,
+                    height: blurTextureHeight
+                },
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+            this._horizontalBlurPostprocess1.width = blurTextureWidth;
+            this._horizontalBlurPostprocess1.height = blurTextureHeight;
+            this._horizontalBlurPostprocess1.onApplyObservable.add(effect => {
+                effect.setTexture("textureSampler", this._mainTexture);
+            });
+
+            this._verticalBlurPostprocess1 = new BlurPostProcess("GlowLayerVBP1", new Vector2(0, 1.0), this._options.blurKernelSize / 2, {
+                    width:  blurTextureWidth,
+                    height: blurTextureHeight
+                },
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+
+            this._horizontalBlurPostprocess2 = new BlurPostProcess("GlowLayerHBP2", new Vector2(1.0, 0), this._options.blurKernelSize / 2, {
+                    width:  blurTextureWidth2,
+                    height: blurTextureHeight2
+                },
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+            this._horizontalBlurPostprocess2.width = blurTextureWidth2;
+            this._horizontalBlurPostprocess2.height = blurTextureHeight2;
+            this._horizontalBlurPostprocess2.onApplyObservable.add(effect => {
+                effect.setTexture("textureSampler", this._blurTexture1);
+            });
+
+            this._verticalBlurPostprocess2 = new BlurPostProcess("GlowLayerVBP2", new Vector2(0, 1.0), this._options.blurKernelSize / 2, {
+                    width:  blurTextureWidth2,
+                    height: blurTextureHeight2
+                },
+                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+
+            this._postProcesses = [ this._horizontalBlurPostprocess1, this._verticalBlurPostprocess1, this._horizontalBlurPostprocess2, this._verticalBlurPostprocess2 ];
+            this._postProcesses1 = [ this._horizontalBlurPostprocess1, this._verticalBlurPostprocess1 ];
+            this._postProcesses2 = [ this._horizontalBlurPostprocess2, this._verticalBlurPostprocess2 ];
+
+            this._mainTexture.samples = this._options.mainTextureSamples!;
+            this._mainTexture.onAfterUnbindObservable.add(() => {
+                let internalTexture = this._blurTexture1.getInternalTexture();
+                if (internalTexture) {
+                    this._scene.postProcessManager.directRender(
+                        this._postProcesses1,
+                        internalTexture, 
+                        true);
+
+                        internalTexture = this._blurTexture2.getInternalTexture();
+                        if (internalTexture) {
+                            this._scene.postProcessManager.directRender(
+                                this._postProcesses2,
+                                internalTexture, 
+                                true);
+                        }
+                }
+            });
+
+            // Prevent autoClear.
+            this._postProcesses.map(pp => { pp.autoClear = false; });
+        }
+
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @param emissiveTexture the associated emissive texture used to generate the glow
+         * @return true if ready otherwise, false
+         */
+        public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
+            let material = subMesh.getMaterial();
+            let mesh = subMesh.getRenderingMesh();
+
+            if (!material || !mesh) {
+                return false;
+            }
+
+            let emissiveTexture = (<any>material).emissiveTexture;
+            return super._isReady(subMesh, useInstances, emissiveTexture);
+        }
+
+        /**
+         * Returns wether or nood the layer needs stencil enabled during the mesh rendering.
+         */
+        public needStencil(): boolean {
+            return false;
+        }
+
+        /**
+         * Implementation specific of rendering the generating effect on the main canvas.
+         * @param effect The effect used to render through
+         */
+        protected _internalRender(effect: Effect): void {
+            // Texture
+            effect.setTexture("textureSampler", this._blurTexture1);
+            effect.setTexture("textureSampler2", this._blurTexture2);
+            effect.setFloat("offset", this._intensity);
+
+            // Cache
+            var engine = this._engine;
+            var previousStencilBuffer = engine.getStencilBuffer();
+                
+            // Draw order
+            engine.setStencilBuffer(false);
+
+            engine.drawElementsType(Material.TriangleFillMode, 0, 6);
+
+            // Draw order
+            engine.setStencilBuffer(previousStencilBuffer);
+        }
+
+        /**
+         * Sets the required values for both the emissive texture and and the main color.
+         */
+        protected _setEmissiveTextureAndColor(mesh: Mesh, subMesh: SubMesh, material: Material): void {
+            var textureLevel = 1.0;
+
+            if (this.customEmissiveTextureSelector) {
+                this._emissiveTextureAndColor.texture = this.customEmissiveTextureSelector(mesh, subMesh, material);
+            } else {
+                if (material) {
+                    this._emissiveTextureAndColor.texture = (<any>material).emissiveTexture;
+                    if (this._emissiveTextureAndColor.texture) {
+                        textureLevel = this._emissiveTextureAndColor.texture.level;
+                    }
+                }
+                else {
+                    this._emissiveTextureAndColor.texture = null;
+                }
+            }
+
+            if (this.customEmissiveColorSelector) {
+                this.customEmissiveColorSelector(mesh, subMesh, material, this._emissiveTextureAndColor.color);
+            } else {
+                if ((<any>material).emissiveColor) {
+                    this._emissiveTextureAndColor.color.set(
+                        (<any>material).emissiveColor.r * textureLevel,
+                        (<any>material).emissiveColor.g * textureLevel,
+                        (<any>material).emissiveColor .b * textureLevel,
+                        1.0);
+                }
+                else {
+                    this._emissiveTextureAndColor.color.set(
+                        this.neutralColor.r,
+                        this.neutralColor.g,
+                        this.neutralColor.b,
+                        this.neutralColor.a);
+                }
+            }
+        }
+
+        /**
+         * Returns true if the mesh should render, otherwise false.
+         * @param mesh The mesh to render
+         * @returns true if it should render otherwise false
+         */
+        protected _shouldRenderMesh(mesh: Mesh): boolean {
+            return this.hasMesh(mesh);
+        }
+
+        /**
+         * Add a mesh in the exclusion list to prevent it to impact or being impacted by the glow layer.
+         * @param mesh The mesh to exclude from the glow layer
+         */
+        public addExcludedMesh(mesh: Mesh): void {
+            if (this._excludedMeshes.indexOf(mesh.uniqueId) === -1) {
+                this._excludedMeshes.push(mesh.uniqueId);
+            }
+        }
+
+        /**
+          * Remove a mesh from the exclusion list to let it impact or being impacted by the glow layer.
+          * @param mesh The mesh to remove
+          */
+        public removeExcludedMesh(mesh: Mesh): void {
+            var index = this._excludedMeshes.indexOf(mesh.uniqueId);
+            if (index !== -1) {
+                this._excludedMeshes.splice(index, 1);
+            } 
+        }
+
+        /**
+         * Add a mesh in the inclusion list to impact or being impacted by the glow layer.
+         * @param mesh The mesh to include in the glow layer
+         */
+        public addIncludedOnlyMesh(mesh: Mesh): void {
+            if (this._includedOnlyMeshes.indexOf(mesh.uniqueId) === -1) {
+                this._includedOnlyMeshes.push(mesh.uniqueId);
+            }
+        }
+
+        /**
+          * Remove a mesh from the Inclusion list to prevent it to impact or being impacted by the glow layer.
+          * @param mesh The mesh to remove
+          */
+        public removeIncludedOnlyMesh(mesh: Mesh): void {
+            var index = this._includedOnlyMeshes.indexOf(mesh.uniqueId);
+            if (index !== -1) {
+                this._includedOnlyMeshes.splice(index, 1);
+            } 
+        }
+
+        /**
+         * Determine if a given mesh will be used in the glow layer
+         * @param mesh The mesh to test
+         * @returns true if the mesh will be highlighted by the current glow layer
+         */
+        public hasMesh(mesh: AbstractMesh): boolean {
+            // Included Mesh
+            if (this._includedOnlyMeshes.length) {
+                return this._includedOnlyMeshes.indexOf(mesh.uniqueId) !== -1;
+            };
+
+            // Excluded Mesh
+            if (this._excludedMeshes.length) {
+                return this._excludedMeshes.indexOf(mesh.uniqueId) === -1;
+            };
+
+            return true;
+        }
+
+        /**
+         * Free any resources and references associated to a mesh.
+         * Internal use
+         * @param mesh The mesh to free.
+         */
+        public _disposeMesh(mesh: Mesh): void {
+            this.removeIncludedOnlyMesh(mesh);
+            this.removeExcludedMesh(mesh);
+        }
+    }
+} 

+ 672 - 0
src/Layer/babylon.highlightLayer.ts

@@ -0,0 +1,672 @@
+module BABYLON {
+    /**
+     * Special Glow Blur post process only blurring the alpha channel
+     * It enforces keeping the most luminous color in the color channel.
+     */
+    class GlowBlurPostProcess extends PostProcess {
+        constructor(name: string, public direction: Vector2, public kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean) {
+            super(name, "glowBlurPostProcess", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
+            
+            this.onApplyObservable.add((effect: Effect) => {
+                effect.setFloat2("screenSize", this.width, this.height);
+                effect.setVector2("direction", this.direction);
+                effect.setFloat("blurWidth", this.kernel);
+            });
+        }
+    }
+
+    /**
+     * Highlight layer options. This helps customizing the behaviour
+     * of the highlight layer.
+     */
+    export interface IHighlightLayerOptions {
+        /**
+         * Multiplication factor apply to the canvas size to compute the render target size
+         * used to generated the glowing objects (the smaller the faster).
+         */
+        mainTextureRatio: number;
+
+        /**
+         * Enforces a fixed size texture to ensure resize independant blur.
+         */
+        mainTextureFixedSize?: number;
+
+        /**
+         * Multiplication factor apply to the main texture size in the first step of the blur to reduce the size 
+         * of the picture to blur (the smaller the faster).
+         */
+        blurTextureSizeRatio: number;
+
+        /**
+         * How big in texel of the blur texture is the vertical blur.
+         */
+        blurVerticalSize: number;
+
+        /**
+         * How big in texel of the blur texture is the horizontal blur.
+         */
+        blurHorizontalSize: number;
+
+        /**
+         * Alpha blending mode used to apply the blur. Default is combine.
+         */
+        alphaBlendingMode: number
+
+        /**
+         * The camera attached to the layer.
+         */
+        camera: Nullable<Camera>;
+
+        /**
+         * Should we display highlight as a solid stroke?
+         */
+        isStroke?: boolean;
+    }
+
+    /**
+     * Storage interface grouping all the information required for glowing a mesh.
+     */
+    interface IHighlightLayerMesh {
+        /** 
+         * The glowy mesh
+         */
+        mesh: Mesh;
+        /**
+         * The color of the glow
+         */
+        color: Color3;
+        /**
+         * The mesh render callback use to insert stencil information
+         */
+        observerHighlight: Nullable<Observer<Mesh>>;
+        /**
+         * The mesh render callback use to come to the default behavior
+         */
+        observerDefault: Nullable<Observer<Mesh>>;
+        /**
+         * If it exists, the emissive color of the material will be used to generate the glow.
+         * Else it falls back to the current color.
+         */
+        glowEmissiveOnly: boolean;
+    }
+
+    /**
+     * Storage interface grouping all the information required for an excluded mesh.
+     */
+    interface IHighlightLayerExcludedMesh {
+        /** 
+         * The glowy mesh
+         */
+        mesh: Mesh;
+        /**
+         * The mesh render callback use to prevent stencil use
+         */
+        beforeRender: Nullable<Observer<Mesh>>;
+        /**
+         * The mesh render callback use to restore previous stencil use
+         */
+        afterRender: Nullable<Observer<Mesh>>;
+    }
+
+    /**
+     * The highlight layer Helps adding a glow effect around a mesh.
+     * 
+     * Once instantiated in a scene, simply use the pushMesh or removeMesh method to add or remove
+     * glowy meshes to your scene.
+     * 
+     * !!! THIS REQUIRES AN ACTIVE STENCIL BUFFER ON THE CANVAS !!!
+     */
+    export class HighlightLayer extends EffectLayer {
+        /**
+         * Effect Name of the highlight layer.
+         */
+        public static readonly EffectName = "HighlightLayer";
+
+        /**
+         * The neutral color used during the preparation of the glow effect.
+         * This is black by default as the blend operation is a blend operation. 
+         */
+        public static NeutralColor: Color4 = new Color4(0, 0, 0, 0);
+
+        /**
+         * Stencil value used for glowing meshes.
+         */
+        public static GlowingMeshStencilReference = 0x02;
+
+        /**
+         * Stencil value used for the other meshes in the scene.
+         */
+        public static NormalMeshStencilReference = 0x01;
+
+        /**
+         * Specifies whether or not the inner glow is ACTIVE in the layer.
+         */
+        public innerGlow: boolean = true;
+
+        /**
+         * Specifies whether or not the outer glow is ACTIVE in the layer.
+         */
+        public outerGlow: boolean = true;
+
+        /**
+         * Specifies the horizontal size of the blur.
+         */
+        public set blurHorizontalSize(value: number) {
+            this._horizontalBlurPostprocess.kernel = value;
+        }
+
+        /**
+         * Specifies the vertical size of the blur.
+         */
+        public set blurVerticalSize(value: number) {
+            this._verticalBlurPostprocess.kernel = value;
+        }
+
+        /**
+         * Gets the horizontal size of the blur.
+         */
+        public get blurHorizontalSize(): number {
+            return this._horizontalBlurPostprocess.kernel
+        }
+
+        /**
+         * Gets the vertical size of the blur.
+         */
+        public get blurVerticalSize(): number {
+            return this._verticalBlurPostprocess.kernel;
+        }
+
+        /**
+         * An event triggered when the highlight layer is being blurred.
+         */
+        public onBeforeBlurObservable = new Observable<HighlightLayer>();
+
+        /**
+         * An event triggered when the highlight layer has been blurred.
+         */
+        public onAfterBlurObservable = new Observable<HighlightLayer>();
+
+        private _instanceGlowingMeshStencilReference = HighlightLayer.GlowingMeshStencilReference++;
+
+        private _options: IHighlightLayerOptions;
+        private _downSamplePostprocess: PassPostProcess;
+        private _horizontalBlurPostprocess: GlowBlurPostProcess;
+        private _verticalBlurPostprocess: GlowBlurPostProcess;
+        private _blurTexture: RenderTargetTexture;
+
+        private _meshes: Nullable<{ [id: string]: Nullable<IHighlightLayerMesh> }> = {};
+        private _excludedMeshes: Nullable<{ [id: string]: Nullable<IHighlightLayerExcludedMesh> }> = {};
+
+        /**
+         * Instantiates a new highlight Layer and references it to the scene..
+         * @param name The name of the layer
+         * @param scene The scene to use the layer in
+         * @param options Sets of none mandatory options to use with the layer (see IHighlightLayerOptions for more information)
+         */
+        constructor(public name: string, scene: Scene, options?: Partial<IHighlightLayerOptions>) {
+            super(name, scene);
+            this.neutralColor = HighlightLayer.NeutralColor;
+
+            // Warn on stencil
+            if (!this._engine.isStencilEnable) {
+                Tools.Warn("Rendering the Highlight Layer requires the stencil to be active on the canvas. var engine = new BABYLON.Engine(canvas, antialias, { stencil: true }");
+            }
+
+            // Adapt options
+            this._options = {
+                mainTextureRatio: 0.5,
+                blurTextureSizeRatio: 0.5,
+                blurHorizontalSize: 1.0,
+                blurVerticalSize: 1.0,
+                alphaBlendingMode: Engine.ALPHA_COMBINE,
+                camera: null,
+                ...options,
+            };
+
+            // Initialize the layer
+            this._init({
+                alphaBlendingMode: this._options.alphaBlendingMode,
+                camera: this._options.camera,
+                mainTextureFixedSize: this._options.mainTextureFixedSize,
+                mainTextureRatio: this._options.mainTextureRatio
+            });
+
+            // Do not render as long as no meshes have been added
+            this._shouldRender = false;
+        }
+
+        /**
+         * Get the effect name of the layer.
+         * @return The effect name
+         */ 
+        public getEffectName(): string {
+            return HighlightLayer.EffectName;
+        }
+
+        /**
+         * Create the merge effect. This is the shader use to blit the information back
+         * to the main canvas at the end of the scene rendering.
+         */
+        protected _createMergeEffect(): Effect {
+             // Effect
+             return this._engine.createEffect("glowMapMerge",
+                [VertexBuffer.PositionKind],
+                ["offset"],
+                ["textureSampler"],
+                this._options.isStroke ? "#define STROKE \n" : undefined);
+
+        }
+
+        /**
+         * Creates the render target textures and post processes used in the highlight layer.
+         */
+        protected _createTextureAndPostProcesses(): void {
+            var blurTextureWidth = this._mainTextureDesiredSize.width * this._options.blurTextureSizeRatio;
+            var blurTextureHeight = this._mainTextureDesiredSize.height * this._options.blurTextureSizeRatio;
+            blurTextureWidth = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
+            blurTextureHeight = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
+
+            this._blurTexture = new RenderTargetTexture("HighlightLayerBlurRTT",
+                {
+                    width: blurTextureWidth,
+                    height: blurTextureHeight
+                },
+                this._scene,
+                false,
+                true,
+                Engine.TEXTURETYPE_HALF_FLOAT);
+            this._blurTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
+            this._blurTexture.anisotropicFilteringLevel = 16;
+            this._blurTexture.updateSamplingMode(Texture.TRILINEAR_SAMPLINGMODE);
+            this._blurTexture.renderParticles = false;
+            this._blurTexture.ignoreCameraViewport = true;
+
+            this._textures = [ this._blurTexture ];
+
+            if (this._options.alphaBlendingMode === Engine.ALPHA_COMBINE) {
+                this._downSamplePostprocess = new PassPostProcess("HighlightLayerPPP", this._options.blurTextureSizeRatio,
+                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+                this._downSamplePostprocess.onApplyObservable.add(effect => {
+                    effect.setTexture("textureSampler", this._mainTexture);
+                });
+
+                this._horizontalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerHBP", new Vector2(1.0, 0), this._options.blurHorizontalSize, 1,
+                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+                this._horizontalBlurPostprocess.onApplyObservable.add(effect => {
+                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+                });
+
+                this._verticalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerVBP", new Vector2(0, 1.0), this._options.blurVerticalSize, 1,
+                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
+                this._verticalBlurPostprocess.onApplyObservable.add(effect => {
+                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
+                });
+
+                this._postProcesses = [this._downSamplePostprocess, this._horizontalBlurPostprocess, this._verticalBlurPostprocess];
+            }
+            else {
+                this._horizontalBlurPostprocess = new BlurPostProcess("HighlightLayerHBP", new Vector2(1.0, 0), this._options.blurHorizontalSize / 2, {
+                        width:  blurTextureWidth,
+                        height: blurTextureHeight
+                    },
+                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+                this._horizontalBlurPostprocess.width = blurTextureWidth;
+                this._horizontalBlurPostprocess.height = blurTextureHeight;
+                this._horizontalBlurPostprocess.onApplyObservable.add(effect => {
+                    effect.setTexture("textureSampler", this._mainTexture);
+                });
+
+                this._verticalBlurPostprocess = new BlurPostProcess("HighlightLayerVBP", new Vector2(0, 1.0), this._options.blurVerticalSize / 2, {
+                        width:  blurTextureWidth,
+                        height: blurTextureHeight
+                    },
+                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine(), false, Engine.TEXTURETYPE_HALF_FLOAT);
+
+                this._postProcesses = [this._horizontalBlurPostprocess, this._verticalBlurPostprocess];
+            }
+
+            this._mainTexture.onAfterUnbindObservable.add(() => {
+                this.onBeforeBlurObservable.notifyObservers(this);
+
+                let internalTexture = this._blurTexture.getInternalTexture();
+                if (internalTexture) {
+                    this._scene.postProcessManager.directRender(
+                        this._postProcesses,
+                        internalTexture, 
+                        true);
+                }
+
+                this.onAfterBlurObservable.notifyObservers(this);
+            });
+
+            // Prevent autoClear.
+            this._postProcesses.map(pp => { pp.autoClear = false; });
+        }
+
+        /**
+         * Returns wether or nood the layer needs stencil enabled during the mesh rendering.
+         */
+        public needStencil(): boolean {
+            return true;
+        }
+
+        /**
+         * Checks for the readiness of the element composing the layer.
+         * @param subMesh the mesh to check for
+         * @param useInstances specify wether or not to use instances to render the mesh
+         * @param emissiveTexture the associated emissive texture used to generate the glow
+         * @return true if ready otherwise, false
+         */
+        public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
+            let material = subMesh.getMaterial();
+            let mesh = subMesh.getRenderingMesh();
+
+            if (!material || !mesh || !this._meshes) {
+                return false;
+            }
+
+            let emissiveTexture: Nullable<Texture> = null;
+            let highlightLayerMesh = this._meshes[mesh.uniqueId];
+
+            if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) {
+                emissiveTexture = (<any>material).emissiveTexture;
+            }
+            return super._isReady(subMesh, useInstances, emissiveTexture);
+        }
+
+        /**
+         * Implementation specific of rendering the generating effect on the main canvas.
+         * @param effect The effect used to render through
+         */
+        protected _internalRender(effect: Effect): void {
+            // Texture
+            effect.setTexture("textureSampler", this._blurTexture);
+
+            // Cache
+            var engine = this._engine;
+            var previousStencilBuffer = engine.getStencilBuffer();
+            var previousStencilFunction = engine.getStencilFunction();
+            var previousStencilMask = engine.getStencilMask();
+            var previousStencilOperationPass = engine.getStencilOperationPass();
+            var previousStencilOperationFail = engine.getStencilOperationFail();
+            var previousStencilOperationDepthFail = engine.getStencilOperationDepthFail();
+            var previousStencilReference = engine.getStencilFunctionReference();
+
+            // Stencil operations
+            engine.setStencilOperationPass(Engine.REPLACE);
+            engine.setStencilOperationFail(Engine.KEEP);
+            engine.setStencilOperationDepthFail(Engine.KEEP);
+
+            // Draw order
+            engine.setStencilMask(0x00);
+            engine.setStencilBuffer(true);
+            engine.setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
+
+            // 2 passes inner outer
+            if (this.outerGlow) {
+                effect.setFloat("offset", 0);
+                engine.setStencilFunction(Engine.NOTEQUAL);
+                engine.drawElementsType(Material.TriangleFillMode, 0, 6);
+            }
+            if (this.innerGlow) {
+                effect.setFloat("offset", 1);
+                engine.setStencilFunction(Engine.EQUAL);
+                engine.drawElementsType(Material.TriangleFillMode, 0, 6);
+            }
+
+            // Restore Cache
+            engine.setStencilFunction(previousStencilFunction);
+            engine.setStencilMask(previousStencilMask);
+            engine.setStencilBuffer(previousStencilBuffer);
+            engine.setStencilOperationPass(previousStencilOperationPass);
+            engine.setStencilOperationFail(previousStencilOperationFail);
+            engine.setStencilOperationDepthFail(previousStencilOperationDepthFail);
+            engine.setStencilFunctionReference(previousStencilReference);
+        }
+
+        /**
+         * Returns true if the layer contains information to display, otherwise false.
+         */
+        public shouldRender(): boolean {
+            if (super.shouldRender()) {
+                return this._meshes ? true : false;
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns true if the mesh should render, otherwise false.
+         * @param mesh The mesh to render
+         * @returns true if it should render otherwise false
+         */
+        protected _shouldRenderMesh(mesh: Mesh): boolean {
+            // Excluded Mesh
+            if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) {
+                return false;
+            };
+
+            return true;
+        }
+
+        /**
+         * Sets the required values for both the emissive texture and and the main color.
+         */
+        protected _setEmissiveTextureAndColor(mesh: Mesh, subMesh: SubMesh, material: Material): void {
+            var highlightLayerMesh = this._meshes![mesh.uniqueId];
+            if (highlightLayerMesh) {
+                this._emissiveTextureAndColor.color.set(
+                    highlightLayerMesh.color.r,
+                    highlightLayerMesh.color.g,
+                    highlightLayerMesh.color.b,
+                    1.0);
+            }
+            else {
+                this._emissiveTextureAndColor.color.set(
+                    this.neutralColor.r,
+                    this.neutralColor.g,
+                    this.neutralColor.b,
+                    this.neutralColor.a);
+            }
+
+            if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) {
+                this._emissiveTextureAndColor.texture = (<any>material).emissiveTexture;
+                this._emissiveTextureAndColor.color.set(
+                    1.0,
+                    1.0,
+                    1.0,
+                    1.0);
+            }
+            else {
+                this._emissiveTextureAndColor.texture = null;
+            }
+
+        }
+
+        /**
+         * Add a mesh in the exclusion list to prevent it to impact or being impacted by the highlight layer.
+         * @param mesh The mesh to exclude from the highlight layer
+         */
+        public addExcludedMesh(mesh: Mesh) {
+            if (!this._excludedMeshes) {
+                return;
+            }
+
+            var meshExcluded = this._excludedMeshes[mesh.uniqueId];
+            if (!meshExcluded) {
+                this._excludedMeshes[mesh.uniqueId] = {
+                    mesh: mesh,
+                    beforeRender: mesh.onBeforeRenderObservable.add((mesh: Mesh) => {
+                        mesh.getEngine().setStencilBuffer(false);
+                    }),
+                    afterRender: mesh.onAfterRenderObservable.add((mesh: Mesh) => {
+                        mesh.getEngine().setStencilBuffer(true);
+                    }),
+                }
+            }
+        }
+
+        /**
+          * Remove a mesh from the exclusion list to let it impact or being impacted by the highlight layer.
+          * @param mesh The mesh to highlight
+          */
+        public removeExcludedMesh(mesh: Mesh) {
+            if (!this._excludedMeshes) {
+                return;
+            }
+
+            var meshExcluded = this._excludedMeshes[mesh.uniqueId];
+            if (meshExcluded) {
+                if (meshExcluded.beforeRender) {
+                    mesh.onBeforeRenderObservable.remove(meshExcluded.beforeRender);
+                }
+
+                if (meshExcluded.afterRender) {
+                    mesh.onAfterRenderObservable.remove(meshExcluded.afterRender);
+                }
+            }
+
+            this._excludedMeshes[mesh.uniqueId] = null;
+        }
+
+        /**
+         * Determine if a given mesh will be highlighted by the current HighlightLayer
+         * @param mesh mesh to test
+         * @returns true if the mesh will be highlighted by the current HighlightLayer
+         */
+        public hasMesh(mesh: AbstractMesh): boolean {
+            if (!this._meshes) {
+                return false;
+            }
+
+            return this._meshes[mesh.uniqueId] !== undefined && this._meshes[mesh.uniqueId] !== null;
+        }
+
+        /**
+         * Add a mesh in the highlight layer in order to make it glow with the chosen color.
+         * @param mesh The mesh to highlight
+         * @param color The color of the highlight
+         * @param glowEmissiveOnly Extract the glow from the emissive texture
+         */
+        public addMesh(mesh: Mesh, color: Color3, glowEmissiveOnly = false) {
+            if (!this._meshes) {
+                return;
+            }
+
+            var meshHighlight = this._meshes[mesh.uniqueId];
+            if (meshHighlight) {
+                meshHighlight.color = color;
+            }
+            else {
+                this._meshes[mesh.uniqueId] = {
+                    mesh: mesh,
+                    color: color,
+                    // Lambda required for capture due to Observable this context
+                    observerHighlight: mesh.onBeforeRenderObservable.add((mesh: Mesh) => {
+                        if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) {
+                            this._defaultStencilReference(mesh);
+                        }
+                        else {
+                            mesh.getScene().getEngine().setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
+                        }
+                    }),
+                    observerDefault: mesh.onAfterRenderObservable.add(this._defaultStencilReference),
+                    glowEmissiveOnly: glowEmissiveOnly
+                };
+            }
+
+            this._shouldRender = true;
+        }
+
+        /**
+         * Remove a mesh from the highlight layer in order to make it stop glowing.
+         * @param mesh The mesh to highlight
+         */
+        public removeMesh(mesh: Mesh) {
+            if (!this._meshes) {
+                return;
+            }
+
+            var meshHighlight = this._meshes[mesh.uniqueId];
+            if (meshHighlight) {
+
+                if (meshHighlight.observerHighlight) {
+                    mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                }
+
+                if (meshHighlight.observerDefault) {
+                    mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
+                }
+                delete this._meshes[mesh.uniqueId];
+            }
+
+            this._shouldRender = false;
+            for (var meshHighlightToCheck in this._meshes) {
+                if (this._meshes[meshHighlightToCheck]) {
+                    this._shouldRender = true;
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Force the stencil to the normal expected value for none glowing parts
+         */
+        private _defaultStencilReference(mesh: Mesh) {
+            mesh.getScene().getEngine().setStencilFunctionReference(HighlightLayer.NormalMeshStencilReference);
+        }
+
+        /**
+         * Free any resources and references associated to a mesh.
+         * Internal use
+         * @param mesh The mesh to free.
+         */
+        public _disposeMesh(mesh: Mesh): void {
+            this.removeMesh(mesh);
+            this.removeExcludedMesh(mesh);
+        }
+
+        /**
+         * Dispose the highlight layer and free resources.
+         */
+        public dispose(): void {
+            if (this._meshes) {
+                // Clean mesh references 
+                for (let id in this._meshes) {
+                    let meshHighlight = this._meshes[id];
+                    if (meshHighlight && meshHighlight.mesh) {
+
+                        if (meshHighlight.observerHighlight) {
+                            meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
+                        }
+
+                        if (meshHighlight.observerDefault) {
+                            meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
+                        }
+                    }
+                }
+                this._meshes = null;
+            }
+
+            if (this._excludedMeshes) {
+                for (let id in this._excludedMeshes) {
+                    let meshHighlight = this._excludedMeshes[id];
+                    if (meshHighlight) {
+
+                        if (meshHighlight.beforeRender) {
+                            meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.beforeRender);
+                        }
+
+                        if (meshHighlight.afterRender) {
+                            meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.afterRender);
+                        }
+                    }
+                }
+                this._excludedMeshes = null;
+            }
+
+            super.dispose();
+        }
+    }
+} 

+ 0 - 996
src/Layer/babylon.highlightlayer.ts

@@ -1,996 +0,0 @@
-module BABYLON {
-    /**
-     * Special Glow Blur post process only blurring the alpha channel
-     * It enforces keeping the most luminous color in the color channel.
-     */
-    class GlowBlurPostProcess extends PostProcess {
-        constructor(name: string, public direction: Vector2, public kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean) {
-            super(name, "glowBlurPostProcess", ["screenSize", "direction", "blurWidth"], null, options, camera, samplingMode, engine, reusable);
-            
-            this.onApplyObservable.add((effect: Effect) => {
-                effect.setFloat2("screenSize", this.width, this.height);
-                effect.setVector2("direction", this.direction);
-                effect.setFloat("blurWidth", this.kernel);
-            });
-        }
-    }
-
-    /**
-     * Highlight layer options. This helps customizing the behaviour
-     * of the highlight layer.
-     */
-    export interface IHighlightLayerOptions {
-        /**
-         * Multiplication factor apply to the canvas size to compute the render target size
-         * used to generated the glowing objects (the smaller the faster).
-         */
-        mainTextureRatio: number;
-
-        /**
-         * Enforces a fixed size texture to ensure resize independant blur.
-         */
-        mainTextureFixedSize?: number;
-
-        /**
-         * Multiplication factor apply to the main texture size in the first step of the blur to reduce the size 
-         * of the picture to blur (the smaller the faster).
-         */
-        blurTextureSizeRatio: number;
-
-        /**
-         * How big in texel of the blur texture is the vertical blur.
-         */
-        blurVerticalSize: number;
-
-        /**
-         * How big in texel of the blur texture is the horizontal blur.
-         */
-        blurHorizontalSize: number;
-
-        /**
-         * Alpha blending mode used to apply the blur. Default is combine.
-         */
-        alphaBlendingMode: number
-
-        /**
-         * The camera attached to the layer.
-         */
-        camera: Nullable<Camera>;
-
-        /**
-         * Should we display highlight as a solid stroke?
-         */
-        isStroke?: boolean;
-    }
-
-    /**
-     * Storage interface grouping all the information required for glowing a mesh.
-     */
-    interface IHighlightLayerMesh {
-        /** 
-         * The glowy mesh
-         */
-        mesh: Mesh;
-        /**
-         * The color of the glow
-         */
-        color: Color3;
-        /**
-         * The mesh render callback use to insert stencil information
-         */
-        observerHighlight: Nullable<Observer<Mesh>>;
-        /**
-         * The mesh render callback use to come to the default behavior
-         */
-        observerDefault: Nullable<Observer<Mesh>>;
-        /**
-         * If it exists, the emissive color of the material will be used to generate the glow.
-         * Else it falls back to the current color.
-         */
-        glowEmissiveOnly: boolean;
-    }
-
-    /**
-     * Storage interface grouping all the information required for an excluded mesh.
-     */
-    interface IHighlightLayerExcludedMesh {
-        /** 
-         * The glowy mesh
-         */
-        mesh: Mesh;
-        /**
-         * The mesh render callback use to prevent stencil use
-         */
-        beforeRender: Nullable<Observer<Mesh>>;
-        /**
-         * The mesh render callback use to restore previous stencil use
-         */
-        afterRender: Nullable<Observer<Mesh>>;
-    }
-
-    /**
-     * The highlight layer Helps adding a glow effect around a mesh.
-     * 
-     * Once instantiated in a scene, simply use the pushMesh or removeMesh method to add or remove
-     * glowy meshes to your scene.
-     * 
-     * !!! THIS REQUIRES AN ACTIVE STENCIL BUFFER ON THE CANVAS !!!
-     */
-    export class HighlightLayer {
-        /**
-         * The neutral color used during the preparation of the glow effect.
-         * This is black by default as the blend operation is a blend operation. 
-         */
-        public static neutralColor: Color4 = new Color4(0, 0, 0, 0);
-
-        /**
-         * Stencil value used for glowing meshes.
-         */
-        public static glowingMeshStencilReference = 0x02;
-
-        /**
-         * Stencil value used for the other meshes in the scene.
-         */
-        public static normalMeshStencilReference = 0x01;
-
-        private _scene: Scene;
-        private _engine: Engine;
-        private _options: IHighlightLayerOptions;
-        private _vertexBuffers: { [key: string]: Nullable<VertexBuffer> } = {};
-        private _indexBuffer: Nullable<WebGLBuffer>;
-        private _downSamplePostprocess: PassPostProcess;
-        private _horizontalBlurPostprocess: GlowBlurPostProcess;
-        private _verticalBlurPostprocess: GlowBlurPostProcess;
-        private _cachedDefines: string;
-        private _glowMapGenerationEffect: Effect;
-        private _glowMapMergeEffect: Effect;
-        private _blurTexture: RenderTargetTexture;
-        private _mainTexture: RenderTargetTexture;
-        private _mainTextureDesiredSize: ISize = { width: 0, height: 0 };
-        private _meshes: Nullable<{ [id: string]: Nullable<IHighlightLayerMesh> }> = {};
-        private _maxSize: number = 0;
-        private _shouldRender = false;
-        private _instanceGlowingMeshStencilReference = HighlightLayer.glowingMeshStencilReference++;
-        private _excludedMeshes: Nullable<{ [id: string]: Nullable<IHighlightLayerExcludedMesh> }> = {};
-
-        /**
-         * Specifies whether or not the inner glow is ACTIVE in the layer.
-         */
-        public innerGlow: boolean = true;
-
-        /**
-         * Specifies whether or not the outer glow is ACTIVE in the layer.
-         */
-        public outerGlow: boolean = true;
-
-        /**
-         * Specifies wether the highlight layer is enabled or not.
-         */
-        public isEnabled: boolean = true;
-
-        /**
-         * Specifies the horizontal size of the blur.
-         */
-        public set blurHorizontalSize(value: number) {
-            this._horizontalBlurPostprocess.kernel = value;
-        }
-
-        /**
-         * Specifies the vertical size of the blur.
-         */
-        public set blurVerticalSize(value: number) {
-            this._verticalBlurPostprocess.kernel = value;
-        }
-
-        /**
-         * Gets the horizontal size of the blur.
-         */
-        public get blurHorizontalSize(): number {
-            return this._horizontalBlurPostprocess.kernel
-        }
-
-        /**
-         * Gets the vertical size of the blur.
-         */
-        public get blurVerticalSize(): number {
-            return this._verticalBlurPostprocess.kernel;
-        }
-
-        /**
-         * Gets the camera attached to the layer.
-         */
-        public get camera(): Nullable<Camera> {
-            return this._options.camera;
-        }
-
-        /**
-         * An event triggered when the highlight layer has been disposed.
-         * @type {BABYLON.Observable}
-         */
-        public onDisposeObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the highlight layer is about rendering the main texture with the glowy parts.
-         * @type {BABYLON.Observable}
-         */
-        public onBeforeRenderMainTextureObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the highlight layer is being blurred.
-         * @type {BABYLON.Observable}
-         */
-        public onBeforeBlurObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the highlight layer has been blurred.
-         * @type {BABYLON.Observable}
-         */
-        public onAfterBlurObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the glowing blurred texture is being merged in the scene.
-         * @type {BABYLON.Observable}
-         */
-        public onBeforeComposeObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the glowing blurred texture has been merged in the scene.
-         * @type {BABYLON.Observable}
-         */
-        public onAfterComposeObservable = new Observable<HighlightLayer>();
-
-        /**
-         * An event triggered when the highlight layer changes its size.
-         * @type {BABYLON.Observable}
-         */
-        public onSizeChangedObservable = new Observable<HighlightLayer>();
-
-        /**
-         * Instantiates a new highlight Layer and references it to the scene..
-         * @param name The name of the layer
-         * @param scene The scene to use the layer in
-         * @param options Sets of none mandatory options to use with the layer (see IHighlightLayerOptions for more information)
-         */
-        constructor(public name: string, scene: Scene, options?: IHighlightLayerOptions) {
-            this._scene = scene || Engine.LastCreatedScene;
-            var engine = scene.getEngine();
-            this._engine = engine;
-            this._maxSize = this._engine.getCaps().maxTextureSize;
-            this._scene.highlightLayers.push(this);
-
-            // Warn on stencil.
-            if (!this._engine.isStencilEnable) {
-                Tools.Warn("Rendering the Highlight Layer requires the stencil to be active on the canvas. var engine = new BABYLON.Engine(canvas, antialias, { stencil: true }");
-            }
-
-            // Adapt options
-            this._options = options || {
-                mainTextureRatio: 0.5,
-                blurTextureSizeRatio: 0.5,
-                blurHorizontalSize: 1.0,
-                blurVerticalSize: 1.0,
-                alphaBlendingMode: Engine.ALPHA_COMBINE,
-                camera: null
-            };
-            this._options.mainTextureRatio = this._options.mainTextureRatio || 0.5;
-            this._options.blurTextureSizeRatio = this._options.blurTextureSizeRatio || 1.0;
-            this._options.blurHorizontalSize = this._options.blurHorizontalSize || 1;
-            this._options.blurVerticalSize = this._options.blurVerticalSize || 1;
-            this._options.alphaBlendingMode = this._options.alphaBlendingMode || Engine.ALPHA_COMBINE;
-
-            // VBO
-            var vertices = [];
-            vertices.push(1, 1);
-            vertices.push(-1, 1);
-            vertices.push(-1, -1);
-            vertices.push(1, -1);
-
-            var vertexBuffer = new VertexBuffer(engine, vertices, VertexBuffer.PositionKind, false, false, 2);
-            this._vertexBuffers[VertexBuffer.PositionKind] = vertexBuffer;
-
-            this._createIndexBuffer();
-
-            // Effect
-            this._glowMapMergeEffect = engine.createEffect("glowMapMerge",
-                [VertexBuffer.PositionKind],
-                ["offset"],
-                ["textureSampler"],
-                this._options.isStroke ? "#define STROKE \n" : undefined);
-
-            // Render target
-            this.setMainTextureSize();
-
-            // Create Textures and post processes
-            this.createTextureAndPostProcesses();
-        }
-
-        private _createIndexBuffer(): void {
-            var engine = this._scene.getEngine();
-
-            // Indices
-            var indices = [];
-            indices.push(0);
-            indices.push(1);
-            indices.push(2);
-
-            indices.push(0);
-            indices.push(2);
-            indices.push(3);
-
-            this._indexBuffer = engine.createIndexBuffer(indices);
-        }
-
-        public _rebuild(): void {
-            let vb = this._vertexBuffers[VertexBuffer.PositionKind];
-
-            if (vb) {
-                vb._rebuild();
-            }
-
-            this._createIndexBuffer();
-        }
-
-        /**
-         * Creates the render target textures and post processes used in the highlight layer.
-         */
-        private createTextureAndPostProcesses(): void {
-            var blurTextureWidth = this._mainTextureDesiredSize.width * this._options.blurTextureSizeRatio;
-            var blurTextureHeight = this._mainTextureDesiredSize.height * this._options.blurTextureSizeRatio;
-            blurTextureWidth = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureWidth, this._maxSize) : blurTextureWidth;
-            blurTextureHeight = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(blurTextureHeight, this._maxSize) : blurTextureHeight;
-
-            this._mainTexture = new RenderTargetTexture("HighlightLayerMainRTT",
-                {
-                    width: this._mainTextureDesiredSize.width,
-                    height: this._mainTextureDesiredSize.height
-                },
-                this._scene,
-                false,
-                true,
-                Engine.TEXTURETYPE_UNSIGNED_INT);
-            this._mainTexture.activeCamera = this._options.camera;
-            this._mainTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
-            this._mainTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this._mainTexture.anisotropicFilteringLevel = 1;
-            this._mainTexture.updateSamplingMode(Texture.BILINEAR_SAMPLINGMODE);
-            this._mainTexture.renderParticles = false;
-            this._mainTexture.renderList = null;
-            this._mainTexture.ignoreCameraViewport = true;
-
-            this._blurTexture = new RenderTargetTexture("HighlightLayerBlurRTT",
-                {
-                    width: blurTextureWidth,
-                    height: blurTextureHeight
-                },
-                this._scene,
-                false,
-                true,
-                Engine.TEXTURETYPE_UNSIGNED_INT);
-            this._blurTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
-            this._blurTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this._blurTexture.anisotropicFilteringLevel = 16;
-            this._blurTexture.updateSamplingMode(Texture.TRILINEAR_SAMPLINGMODE);
-            this._blurTexture.renderParticles = false;
-            this._blurTexture.ignoreCameraViewport = true;
-
-            this._downSamplePostprocess = new PassPostProcess("HighlightLayerPPP", this._options.blurTextureSizeRatio,
-                null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
-            this._downSamplePostprocess.onApplyObservable.add(effect => {
-                effect.setTexture("textureSampler", this._mainTexture);
-            });
-
-            if (this._options.alphaBlendingMode === Engine.ALPHA_COMBINE) {
-                this._horizontalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerHBP", new Vector2(1.0, 0), this._options.blurHorizontalSize, 1,
-                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
-                this._horizontalBlurPostprocess.onApplyObservable.add(effect => {
-                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
-                });
-
-                this._verticalBlurPostprocess = new GlowBlurPostProcess("HighlightLayerVBP", new Vector2(0, 1.0), this._options.blurVerticalSize, 1,
-                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
-                this._verticalBlurPostprocess.onApplyObservable.add(effect => {
-                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
-                });
-            }
-            else {
-                this._horizontalBlurPostprocess = new BlurPostProcess("HighlightLayerHBP", new Vector2(1.0, 0), this._options.blurHorizontalSize, 1,
-                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
-                this._horizontalBlurPostprocess.onApplyObservable.add(effect => {
-                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
-                });
-
-                this._verticalBlurPostprocess = new BlurPostProcess("HighlightLayerVBP", new Vector2(0, 1.0), this._options.blurVerticalSize, 1,
-                    null, Texture.BILINEAR_SAMPLINGMODE, this._scene.getEngine());
-                this._verticalBlurPostprocess.onApplyObservable.add(effect => {
-                    effect.setFloat2("screenSize", blurTextureWidth, blurTextureHeight);
-                });
-            }
-
-            this._mainTexture.onAfterUnbindObservable.add(() => {
-                this.onBeforeBlurObservable.notifyObservers(this);
-
-                let internalTexture = this._blurTexture.getInternalTexture();
-
-                if (internalTexture) {
-                    this._scene.postProcessManager.directRender(
-                        [this._downSamplePostprocess, this._horizontalBlurPostprocess, this._verticalBlurPostprocess],
-                        internalTexture, true);
-                }
-
-                this.onAfterBlurObservable.notifyObservers(this);
-            });
-
-            // Custom render function
-            var renderSubMesh = (subMesh: SubMesh): void => {
-                if (!this._meshes) {
-                    return;
-                }
-
-                var material = subMesh.getMaterial();
-                var mesh = subMesh.getRenderingMesh();
-                var scene = this._scene;
-                var engine = scene.getEngine();
-
-                if (!material) {
-                    return;
-                }
-
-                // Do not block in blend mode.
-                if (material.needAlphaBlendingForMesh(mesh)) {
-                    return;
-                }
-
-                // Culling
-                engine.setState(material.backFaceCulling);
-
-                // Managing instances
-                var batch = mesh._getInstancesRenderList(subMesh._id);
-                if (batch.mustReturn) {
-                    return;
-                }
-
-                // Excluded Mesh
-                if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) {
-                    return;
-                };
-
-                var hardwareInstancedRendering = (engine.getCaps().instancedArrays) && (batch.visibleInstances[subMesh._id] !== null) && (batch.visibleInstances[subMesh._id] !== undefined);
-
-                var highlightLayerMesh = this._meshes[mesh.uniqueId];
-                var emissiveTexture: Nullable<Texture> = null;
-                if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) {
-                    emissiveTexture = (<any>material).emissiveTexture;
-                }
-
-                if (this._isReady(subMesh, hardwareInstancedRendering, emissiveTexture)) {
-                    engine.enableEffect(this._glowMapGenerationEffect);
-                    mesh._bind(subMesh, this._glowMapGenerationEffect, Material.TriangleFillMode);
-
-                    this._glowMapGenerationEffect.setMatrix("viewProjection", scene.getTransformMatrix());
-                    if (highlightLayerMesh) {
-                        this._glowMapGenerationEffect.setFloat4("color",
-                            highlightLayerMesh.color.r,
-                            highlightLayerMesh.color.g,
-                            highlightLayerMesh.color.b,
-                            1.0);
-                    }
-                    else {
-                        this._glowMapGenerationEffect.setFloat4("color",
-                            HighlightLayer.neutralColor.r,
-                            HighlightLayer.neutralColor.g,
-                            HighlightLayer.neutralColor.b,
-                            HighlightLayer.neutralColor.a);
-                    }
-
-                    // Alpha test
-                    if (material && material.needAlphaTesting()) {
-                        var alphaTexture = material.getAlphaTestTexture();
-                        if (alphaTexture) {
-                            this._glowMapGenerationEffect.setTexture("diffuseSampler", alphaTexture);
-                            let textureMatrix = alphaTexture.getTextureMatrix();
-
-                            if (textureMatrix) {
-                                this._glowMapGenerationEffect.setMatrix("diffuseMatrix", textureMatrix);
-                            }
-                        }
-                    }
-
-                    // Glow emissive only
-                    if (emissiveTexture) {
-                        this._glowMapGenerationEffect.setTexture("emissiveSampler", emissiveTexture);
-                        this._glowMapGenerationEffect.setMatrix("emissiveMatrix", emissiveTexture.getTextureMatrix());
-                    }
-
-                    // Bones
-                    if (mesh.useBones && mesh.computeBonesUsingShaders && mesh.skeleton) {
-                        this._glowMapGenerationEffect.setMatrices("mBones", mesh.skeleton.getTransformMatrices(mesh));
-                    }
-
-                    // Draw
-                    mesh._processRendering(subMesh, this._glowMapGenerationEffect, Material.TriangleFillMode, batch, hardwareInstancedRendering,
-                        (isInstance, world) => this._glowMapGenerationEffect.setMatrix("world", world));
-                } else {
-                    // Need to reset refresh rate of the shadowMap
-                    this._mainTexture.resetRefreshCounter();
-                }
-            };
-
-            this._mainTexture.customRenderFunction = (opaqueSubMeshes: SmartArray<SubMesh>, alphaTestSubMeshes: SmartArray<SubMesh>, transparentSubMeshes: SmartArray<SubMesh>, depthOnlySubMeshes: SmartArray<SubMesh>): void => {
-                this.onBeforeRenderMainTextureObservable.notifyObservers(this);
-
-                var index: number;
-
-                let engine = this._scene.getEngine();
-
-                if (depthOnlySubMeshes.length) {
-                    engine.setColorWrite(false);
-                    for (index = 0; index < depthOnlySubMeshes.length; index++) {
-                        renderSubMesh(depthOnlySubMeshes.data[index]);
-                    }
-                    engine.setColorWrite(true);
-                }
-
-                for (index = 0; index < opaqueSubMeshes.length; index++) {
-                    renderSubMesh(opaqueSubMeshes.data[index]);
-                }
-
-                for (index = 0; index < alphaTestSubMeshes.length; index++) {
-                    renderSubMesh(alphaTestSubMeshes.data[index]);
-                }
-
-                for (index = 0; index < transparentSubMeshes.length; index++) {
-                    renderSubMesh(transparentSubMeshes.data[index]);
-                }
-            };
-
-            this._mainTexture.onClearObservable.add((engine: Engine) => {
-                engine.clear(HighlightLayer.neutralColor, true, true, true);
-            });
-        }
-
-        /**
-         * Checks for the readiness of the element composing the layer.
-         * @param subMesh the mesh to check for
-         * @param useInstances specify wether or not to use instances to render the mesh
-         * @param emissiveTexture the associated emissive texture used to generate the glow
-         * @return true if ready otherwise, false
-         */
-        public isReady(subMesh: SubMesh, useInstances: boolean): boolean {
-            let material = subMesh.getMaterial();
-            let mesh = subMesh.getRenderingMesh();
-
-            if (!material || !mesh || !this._meshes) {
-                return false;
-            }
-
-            let emissiveTexture: Nullable<Texture> = null;
-            let highlightLayerMesh = this._meshes[mesh.uniqueId];
-
-            if (highlightLayerMesh && highlightLayerMesh.glowEmissiveOnly && material) {
-                emissiveTexture = (<any>material).emissiveTexture;
-            }
-            return this._isReady(subMesh, useInstances, emissiveTexture);
-        }
-
-        /**
-         * Checks for the readiness of the element composing the layer.
-         * @param subMesh the mesh to check for
-         * @param useInstances specify wether or not to use instances to render the mesh
-         * @param emissiveTexture the associated emissive texture used to generate the glow
-         * @return true if ready otherwise, false
-         */
-        private _isReady(subMesh: SubMesh, useInstances: boolean, emissiveTexture: Nullable<Texture>): boolean {
-            let material = subMesh.getMaterial();
-
-            if (!material) {
-                return false;
-            }
-
-            if (!material.isReady(subMesh.getMesh(), useInstances)) {
-                return false;
-            }
-
-            var defines = [];
-
-            var attribs = [VertexBuffer.PositionKind];
-
-            var mesh = subMesh.getMesh();
-            var uv1 = false;
-            var uv2 = false;
-
-            // Alpha test
-            if (material && material.needAlphaTesting()) {
-                var alphaTexture = material.getAlphaTestTexture();
-                if (alphaTexture) {
-                    defines.push("#define ALPHATEST");
-                    if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind) &&
-                        alphaTexture.coordinatesIndex === 1) {
-                        defines.push("#define DIFFUSEUV2");
-                        uv2 = true;
-                    }
-                    else if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                        defines.push("#define DIFFUSEUV1");
-                        uv1 = true;
-                    }
-                }
-            }
-
-            // Emissive
-            if (emissiveTexture) {
-                defines.push("#define EMISSIVE");
-                if (mesh.isVerticesDataPresent(VertexBuffer.UV2Kind) &&
-                    emissiveTexture.coordinatesIndex === 1) {
-                    defines.push("#define EMISSIVEUV2");
-                    uv2 = true;
-                }
-                else if (mesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
-                    defines.push("#define EMISSIVEUV1");
-                    uv1 = true;
-                }
-            }
-
-            if (uv1) {
-                attribs.push(VertexBuffer.UVKind);
-                defines.push("#define UV1");
-            }
-            if (uv2) {
-                attribs.push(VertexBuffer.UV2Kind);
-                defines.push("#define UV2");
-            }
-
-            // Bones
-            if (mesh.useBones && mesh.computeBonesUsingShaders) {
-                attribs.push(VertexBuffer.MatricesIndicesKind);
-                attribs.push(VertexBuffer.MatricesWeightsKind);
-                if (mesh.numBoneInfluencers > 4) {
-                    attribs.push(VertexBuffer.MatricesIndicesExtraKind);
-                    attribs.push(VertexBuffer.MatricesWeightsExtraKind);
-                }
-                defines.push("#define NUM_BONE_INFLUENCERS " + mesh.numBoneInfluencers);
-                defines.push("#define BonesPerMesh " + (mesh.skeleton ? (mesh.skeleton.bones.length + 1) : 0));
-            } else {
-                defines.push("#define NUM_BONE_INFLUENCERS 0");
-            }
-
-            // Instances
-            if (useInstances) {
-                defines.push("#define INSTANCES");
-                attribs.push("world0");
-                attribs.push("world1");
-                attribs.push("world2");
-                attribs.push("world3");
-            }
-
-            // Get correct effect      
-            var join = defines.join("\n");
-            if (this._cachedDefines !== join) {
-                this._cachedDefines = join;
-                this._glowMapGenerationEffect = this._scene.getEngine().createEffect("glowMapGeneration",
-                    attribs,
-                    ["world", "mBones", "viewProjection", "diffuseMatrix", "color", "emissiveMatrix"],
-                    ["diffuseSampler", "emissiveSampler"], join);
-            }
-
-            return this._glowMapGenerationEffect.isReady();
-        }
-
-        /**
-         * Renders the glowing part of the scene by blending the blurred glowing meshes on top of the rendered scene.
-         */
-        public render(): void {
-            var currentEffect = this._glowMapMergeEffect;
-
-            // Check
-            if (!currentEffect.isReady() || !this._blurTexture.isReady())
-                return;
-
-            var engine = this._scene.getEngine();
-
-            this.onBeforeComposeObservable.notifyObservers(this);
-
-            // Render
-            engine.enableEffect(currentEffect);
-            engine.setState(false);
-
-            // Cache
-            var previousStencilBuffer = engine.getStencilBuffer();
-            var previousStencilFunction = engine.getStencilFunction();
-            var previousStencilMask = engine.getStencilMask();
-            var previousStencilOperationPass = engine.getStencilOperationPass();
-            var previousStencilOperationFail = engine.getStencilOperationFail();
-            var previousStencilOperationDepthFail = engine.getStencilOperationDepthFail();
-            var previousAlphaMode = engine.getAlphaMode();
-
-            // Texture
-            currentEffect.setTexture("textureSampler", this._blurTexture);
-
-            // VBOs
-            engine.bindBuffers(this._vertexBuffers, this._indexBuffer, currentEffect);
-
-            // Stencil operations
-            engine.setStencilOperationPass(Engine.REPLACE);
-            engine.setStencilOperationFail(Engine.KEEP);
-            engine.setStencilOperationDepthFail(Engine.KEEP);
-
-            // Draw order
-            engine.setAlphaMode(this._options.alphaBlendingMode);
-            engine.setStencilMask(0x00);
-            engine.setStencilBuffer(true);
-            engine.setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
-
-            if (this.outerGlow) {
-                currentEffect.setFloat("offset", 0);
-                engine.setStencilFunction(Engine.NOTEQUAL);
-                engine.drawElementsType(Material.TriangleFillMode, 0, 6);
-            }
-            if (this.innerGlow) {
-                currentEffect.setFloat("offset", 1);
-                engine.setStencilFunction(Engine.EQUAL);
-                engine.drawElementsType(Material.TriangleFillMode, 0, 6);
-            }
-
-            // Restore Cache
-            engine.setStencilFunction(previousStencilFunction);
-            engine.setStencilMask(previousStencilMask);
-            engine.setAlphaMode(previousAlphaMode);
-            engine.setStencilBuffer(previousStencilBuffer);
-            engine.setStencilOperationPass(previousStencilOperationPass);
-            engine.setStencilOperationFail(previousStencilOperationFail);
-            engine.setStencilOperationDepthFail(previousStencilOperationDepthFail);
-
-            (<any>engine)._stencilState.reset();
-
-            this.onAfterComposeObservable.notifyObservers(this);
-
-            // Handle size changes.
-            var size = this._mainTexture.getSize();
-            this.setMainTextureSize();
-            if (size.width !== this._mainTextureDesiredSize.width || size.height !== this._mainTextureDesiredSize.height) {
-                // Recreate RTT and post processes on size change.
-                this.onSizeChangedObservable.notifyObservers(this);
-                this.disposeTextureAndPostProcesses();
-                this.createTextureAndPostProcesses();
-            }
-        }
-
-        /**
-         * Add a mesh in the exclusion list to prevent it to impact or being impacted by the highlight layer.
-         * @param mesh The mesh to exclude from the highlight layer
-         */
-        public addExcludedMesh(mesh: Mesh) {
-            if (!this._excludedMeshes) {
-                return;
-            }
-
-            var meshExcluded = this._excludedMeshes[mesh.uniqueId];
-            if (!meshExcluded) {
-                this._excludedMeshes[mesh.uniqueId] = {
-                    mesh: mesh,
-                    beforeRender: mesh.onBeforeRenderObservable.add((mesh: Mesh) => {
-                        mesh.getEngine().setStencilBuffer(false);
-                    }),
-                    afterRender: mesh.onAfterRenderObservable.add((mesh: Mesh) => {
-                        mesh.getEngine().setStencilBuffer(true);
-                    }),
-                }
-            }
-        }
-
-        /**
-          * Remove a mesh from the exclusion list to let it impact or being impacted by the highlight layer.
-          * @param mesh The mesh to highlight
-          */
-        public removeExcludedMesh(mesh: Mesh) {
-            if (!this._excludedMeshes) {
-                return;
-            }
-
-            var meshExcluded = this._excludedMeshes[mesh.uniqueId];
-            if (meshExcluded) {
-                if (meshExcluded.beforeRender) {
-                    mesh.onBeforeRenderObservable.remove(meshExcluded.beforeRender);
-                }
-
-                if (meshExcluded.afterRender) {
-                    mesh.onAfterRenderObservable.remove(meshExcluded.afterRender);
-                }
-            }
-
-            this._excludedMeshes[mesh.uniqueId] = null;
-        }
-
-        /**
-         * Determine if a given mesh will be highlighted by the current HighlightLayer
-         * @param mesh mesh to test
-         * @returns true if the mesh will be highlighted by the current HighlightLayer
-         */
-        public hasMesh(mesh: AbstractMesh): boolean {
-            if (!this._meshes) {
-                return false;
-            }
-
-            return this._meshes[mesh.uniqueId] !== undefined && this._meshes[mesh.uniqueId] !== null;
-        }
-
-        /**
-         * Add a mesh in the highlight layer in order to make it glow with the chosen color.
-         * @param mesh The mesh to highlight
-         * @param color The color of the highlight
-         * @param glowEmissiveOnly Extract the glow from the emissive texture
-         */
-        public addMesh(mesh: Mesh, color: Color3, glowEmissiveOnly = false) {
-            if (!this._meshes) {
-                return;
-            }
-
-            var meshHighlight = this._meshes[mesh.uniqueId];
-            if (meshHighlight) {
-                meshHighlight.color = color;
-            }
-            else {
-                this._meshes[mesh.uniqueId] = {
-                    mesh: mesh,
-                    color: color,
-                    // Lambda required for capture due to Observable this context
-                    observerHighlight: mesh.onBeforeRenderObservable.add((mesh: Mesh) => {
-                        if (this._excludedMeshes && this._excludedMeshes[mesh.uniqueId]) {
-                            this.defaultStencilReference(mesh);
-                        }
-                        else {
-                            mesh.getScene().getEngine().setStencilFunctionReference(this._instanceGlowingMeshStencilReference);
-                        }
-                    }),
-                    observerDefault: mesh.onAfterRenderObservable.add(this.defaultStencilReference),
-                    glowEmissiveOnly: glowEmissiveOnly
-                };
-            }
-
-            this._shouldRender = true;
-        }
-
-        /**
-         * Remove a mesh from the highlight layer in order to make it stop glowing.
-         * @param mesh The mesh to highlight
-         */
-        public removeMesh(mesh: Mesh) {
-            if (!this._meshes) {
-                return;
-            }
-
-            var meshHighlight = this._meshes[mesh.uniqueId];
-            if (meshHighlight) {
-
-                if (meshHighlight.observerHighlight) {
-                    mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
-                }
-
-                if (meshHighlight.observerDefault) {
-                    mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
-                }
-                delete this._meshes[mesh.uniqueId];
-            }
-
-            this._shouldRender = false;
-            for (var meshHighlightToCheck in this._meshes) {
-                if (this._meshes[meshHighlightToCheck]) {
-                    this._shouldRender = true;
-                    break;
-                }
-            }
-        }
-
-        /**
-         * Returns true if the layer contains information to display, otherwise false.
-         */
-        public shouldRender(): boolean {
-            return this.isEnabled && this._shouldRender;
-        }
-
-        /**
-         * Sets the main texture desired size which is the closest power of two
-         * of the engine canvas size.
-         */
-        private setMainTextureSize(): void {
-            if (this._options.mainTextureFixedSize) {
-                this._mainTextureDesiredSize.width = this._options.mainTextureFixedSize;
-                this._mainTextureDesiredSize.height = this._options.mainTextureFixedSize;
-            }
-            else {
-                this._mainTextureDesiredSize.width = this._engine.getRenderWidth() * this._options.mainTextureRatio;
-                this._mainTextureDesiredSize.height = this._engine.getRenderHeight() * this._options.mainTextureRatio;
-
-                this._mainTextureDesiredSize.width = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.width, this._maxSize) : this._mainTextureDesiredSize.width;
-                this._mainTextureDesiredSize.height = this._engine.needPOTTextures ? Tools.GetExponentOfTwo(this._mainTextureDesiredSize.height, this._maxSize) : this._mainTextureDesiredSize.height;
-            }
-        }
-
-        /**
-         * Force the stencil to the normal expected value for none glowing parts
-         */
-        private defaultStencilReference(mesh: Mesh) {
-            mesh.getScene().getEngine().setStencilFunctionReference(HighlightLayer.normalMeshStencilReference);
-        }
-
-        /**
-         * Dispose only the render target textures and post process.
-         */
-        private disposeTextureAndPostProcesses(): void {
-            this._blurTexture.dispose();
-            this._mainTexture.dispose();
-
-            this._downSamplePostprocess.dispose();
-            this._horizontalBlurPostprocess.dispose();
-            this._verticalBlurPostprocess.dispose();
-        }
-
-        /**
-         * Dispose the highlight layer and free resources.
-         */
-        public dispose(): void {
-            var vertexBuffer = this._vertexBuffers[VertexBuffer.PositionKind];
-            if (vertexBuffer) {
-                vertexBuffer.dispose();
-                this._vertexBuffers[VertexBuffer.PositionKind] = null;
-            }
-
-            if (this._indexBuffer) {
-                this._scene.getEngine()._releaseBuffer(this._indexBuffer);
-                this._indexBuffer = null;
-            }
-
-            // Clean textures and post processes
-            this.disposeTextureAndPostProcesses();
-
-            if (this._meshes) {
-                // Clean mesh references 
-                for (let id in this._meshes) {
-                    let meshHighlight = this._meshes[id];
-                    if (meshHighlight && meshHighlight.mesh) {
-
-                        if (meshHighlight.observerHighlight) {
-                            meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.observerHighlight);
-                        }
-
-                        if (meshHighlight.observerDefault) {
-                            meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.observerDefault);
-                        }
-                    }
-                }
-                this._meshes = null;
-            }
-
-            if (this._excludedMeshes) {
-                for (let id in this._excludedMeshes) {
-                    let meshHighlight = this._excludedMeshes[id];
-                    if (meshHighlight) {
-
-                        if (meshHighlight.beforeRender) {
-                            meshHighlight.mesh.onBeforeRenderObservable.remove(meshHighlight.beforeRender);
-                        }
-
-                        if (meshHighlight.afterRender) {
-                            meshHighlight.mesh.onAfterRenderObservable.remove(meshHighlight.afterRender);
-                        }
-                    }
-                }
-                this._excludedMeshes = null;
-            }
-
-            // Remove from scene
-            var index = this._scene.highlightLayers.indexOf(this, 0);
-            if (index > -1) {
-                this._scene.highlightLayers.splice(index, 1);
-            }
-
-            // Callback
-            this.onDisposeObservable.notifyObservers(this);
-
-            this.onDisposeObservable.clear();
-            this.onBeforeRenderMainTextureObservable.clear();
-            this.onBeforeBlurObservable.clear();
-            this.onBeforeComposeObservable.clear();
-            this.onAfterComposeObservable.clear();
-            this.onSizeChangedObservable.clear();
-        }
-    }
-} 

+ 2 - 2
src/Loading/Plugins/babylon.babylonFileLoader.ts

@@ -644,7 +644,7 @@
                 }
                 scene.workerCollisions = !!parsedData.workerCollisions;
 
-                var container = loadAssetContainer(scene, data, rootUrl, onerror, true);
+                var container = loadAssetContainer(scene, data, rootUrl, onError, true);
                 if (!container) {
                     return false;
                 }
@@ -684,7 +684,7 @@
             return false;
         },
         loadAssetContainer: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer => {
-            var container = loadAssetContainer(scene, data, rootUrl, onerror);
+            var container = loadAssetContainer(scene, data, rootUrl, onError);
             return container;
         }
     });

+ 8 - 10
src/Materials/Textures/babylon.texture.ts

@@ -182,7 +182,7 @@
             if (!scene) {
                 return;
             }
-            
+
             this.delayLoadState = Engine.DELAYLOADSTATE_LOADED;
             this._texture = this._getFromCache(this.url, this._noMipmap, this._samplingMode);
 
@@ -192,19 +192,17 @@
                     delete this._buffer;
                 }
             } else {
-                if (this._texture.isReady) {
-                    Tools.SetImmediate(() => {
-                        if (!this._delayedOnLoad) {
-                            return;
-                        }
-                        this._delayedOnLoad();
-                    });
-                } else {
-                    if (this._delayedOnLoad) {
+                if (this._delayedOnLoad) {
+                    if (this._texture.isReady) {
+                        Tools.SetImmediate(this._delayedOnLoad);
+                    } else {
                         this._texture.onLoadedObservable.add(this._delayedOnLoad);
                     }
                 }
             }
+
+            this._delayedOnLoad = null;
+            this._delayedOnError = null;
         }
 
         public updateSamplingMode(samplingMode: number): void {

+ 0 - 10
src/Mesh/babylon.abstractMesh.ts

@@ -412,7 +412,6 @@
         public _masterMesh: Nullable<AbstractMesh>;
 
         public _boundingInfo: Nullable<BoundingInfo>;
-        public _isDisposed = false;
         public _renderId = 0;
 
         public subMeshes: SubMesh[];
@@ -467,13 +466,6 @@
         }
 
         /**
-         * Boolean : true if the mesh has been disposed.  
-         */
-        public isDisposed(): boolean {
-            return this._isDisposed;
-        }
-
-        /**
          * Returns the string "AbstractMesh"
          */
         public getClassName(): string {
@@ -1456,8 +1448,6 @@
             this.onCollideObservable.clear();
             this.onCollisionPositionChangeObservable.clear();
 
-            this._isDisposed = true;
-
             super.dispose(doNotRecurse);
         }
 

+ 6 - 7
src/Mesh/babylon.mesh.ts

@@ -1740,13 +1740,12 @@
                 this.instances[0].dispose();
             }
 
-            // Highlight layers.
-            let highlightLayers = this.getScene().highlightLayers;
-            for (let i = 0; i < highlightLayers.length; i++) {
-                let highlightLayer = highlightLayers[i];
-                if (highlightLayer) {
-                    highlightLayer.removeMesh(this);
-                    highlightLayer.removeExcludedMesh(this);
+            // Effect layers.
+            let effectLayers = this.getScene().effectLayers;
+            for (let i = 0; i < effectLayers.length; i++) {
+                let effectLayer = effectLayers[i];
+                if (effectLayer) {
+                    effectLayer._disposeMesh(this);
                 }
             }
             super.dispose(doNotRecurse, disposeMaterialAndTextures);

+ 8 - 0
src/Particles/babylon.particleSystem.ts

@@ -494,6 +494,14 @@
         }
 
         /**
+         * Remove all active particles
+         */
+        public reset(): void {
+            this._stockParticles = [];
+            this._particles = [];
+        }
+
+        /**
          * @ignore (for internal use only)
          */
         public _appendParticleVertex(index: number, particle: Particle, offsetX: number, offsetY: number): void {

+ 9 - 0
src/Particles/babylon.solidParticle.ts

@@ -94,6 +94,15 @@ module BABYLON {
          * Last computed particle rotation matrix
          */
         public _rotationMatrix: number[] = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
+        /**
+         * Parent particle Id, if any.
+         * Default null.
+         */
+        public parentId: Nullable<number> = null;
+        /**
+         * Internal global position in the SPS.
+         */
+        public _globalPosition: Vector3 = Vector3.Zero();
 
         /**
          * Creates a Solid Particle object.

+ 86 - 30
src/Particles/babylon.solidParticleSystem.ts

@@ -134,6 +134,9 @@
                 };
             private _needs32Bits: boolean = false;
             private _pivotBackTranslation: Vector3 = Vector3.Zero();
+            private _scaledPivot: Vector3 = Vector3.Zero();
+            private _particleHasParent: boolean = false;
+            private _parent: SolidParticle;
 
             /**
              * Creates a SPS (Solid Particle System) object.
@@ -399,12 +402,16 @@
                     this._quaternionRotationYPR();
                 }
                 this._quaternionToRotationMatrix();
+
+                this._scaledPivot.x = this._copy.pivot.x * this._copy.scaling.x;
+                this._scaledPivot.y = this._copy.pivot.y * this._copy.scaling.y;
+                this._scaledPivot.z = this._copy.pivot.z * this._copy.scaling.z;
                 
                 if (this._copy.translateFromPivot) {
                     this._pivotBackTranslation.copyFromFloats(0.0, 0.0, 0.0);
                 }
                 else {
-                    this._pivotBackTranslation.copyFrom(this._copy.pivot);
+                    this._pivotBackTranslation.copyFrom(this._scaledPivot);
                 }
     
                 for (i = 0; i < shape.length; i++) {
@@ -420,9 +427,9 @@
                     this._vertex.y *= this._copy.scaling.y;
                     this._vertex.z *= this._copy.scaling.z;
 
-                    this._vertex.x -= this._copy.pivot.x;
-                    this._vertex.y -= this._copy.pivot.y;
-                    this._vertex.z -= this._copy.pivot.z;
+                    this._vertex.x -= this._scaledPivot.x;
+                    this._vertex.y -= this._scaledPivot.y;
+                    this._vertex.z -= this._scaledPivot.z;
     
                     Vector3.TransformCoordinatesToRef(this._vertex, this._rotMatrix, this._rotated);
 
@@ -582,11 +589,15 @@
                 }
                 this._quaternionToRotationMatrix();
 
+                this._scaledPivot.x = this._particle.pivot.x * this._particle.scaling.x;
+                this._scaledPivot.y = this._particle.pivot.y * this._particle.scaling.y;
+                this._scaledPivot.z = this._particle.pivot.z * this._particle.scaling.z;
+
                 if (this._copy.translateFromPivot) {
                     this._pivotBackTranslation.copyFromFloats(0.0, 0.0, 0.0);
                 }
                 else {
-                    this._pivotBackTranslation.copyFrom(this._copy.pivot);
+                    this._pivotBackTranslation.copyFrom(this._scaledPivot);
                 }
     
                 this._shape = particle._model._shape;
@@ -603,9 +614,9 @@
                     this._vertex.y *= this._copy.scaling.y;
                     this._vertex.z *= this._copy.scaling.z;
 
-                    this._vertex.x -= this._copy.pivot.x;
-                    this._vertex.y -= this._copy.pivot.y;
-                    this._vertex.z -= this._copy.pivot.z;
+                    this._vertex.x -= this._scaledPivot.x;
+                    this._vertex.y -= this._scaledPivot.y;
+                    this._vertex.z -= this._scaledPivot.z;
     
                     Vector3.TransformCoordinatesToRef(this._vertex, this._rotMatrix, this._rotated);
                     this._rotated.addInPlace(this._pivotBackTranslation);
@@ -624,6 +635,15 @@
                 particle.scaling.x = 1.0;
                 particle.scaling.y = 1.0;
                 particle.scaling.z = 1.0;
+                particle.uvs.x = 0.0;
+                particle.uvs.y = 0.0;
+                particle.uvs.z = 1.0;
+                particle.uvs.w = 1.0;
+                particle.pivot.x = 0.0;
+                particle.pivot.y = 0.0;
+                particle.pivot.z = 0.0;
+                particle.translateFromPivot = false;
+                particle.parentId = null;
             }
 
             /**
@@ -751,7 +771,12 @@
     
                     if (this._particle.isVisible) {
                         this._particle._stillInvisible = false; // un-mark permanent invisibility
-    
+                        this._particleHasParent = (this._particle.parentId !== null);
+
+                        this._scaledPivot.x = this._particle.pivot.x * this._particle.scaling.x;
+                        this._scaledPivot.y = this._particle.pivot.y * this._particle.scaling.y;
+                        this._scaledPivot.z = this._particle.pivot.z * this._particle.scaling.z;
+
                         // particle rotation matrix
                         if (this.billboard) {
                             this._particle.rotation.x = 0.0;
@@ -767,15 +792,46 @@
                                 this._quaternionRotationYPR();
                             }
                             this._quaternionToRotationMatrix();
-                            this._particle._rotationMatrix[0] = this._rotMatrix.m[0];
-                            this._particle._rotationMatrix[1] = this._rotMatrix.m[1];
-                            this._particle._rotationMatrix[2] = this._rotMatrix.m[2];
-                            this._particle._rotationMatrix[3] = this._rotMatrix.m[4];
-                            this._particle._rotationMatrix[4] = this._rotMatrix.m[5];
-                            this._particle._rotationMatrix[5] = this._rotMatrix.m[6];
-                            this._particle._rotationMatrix[6] = this._rotMatrix.m[8];
-                            this._particle._rotationMatrix[7] = this._rotMatrix.m[9];
-                            this._particle._rotationMatrix[8] = this._rotMatrix.m[10];
+                        }
+
+                        if (this._particleHasParent) {
+                            this._parent = this.particles[this._particle.parentId!];
+                            this._rotated.x = this._particle.position.x * this._parent._rotationMatrix[0] + this._particle.position.y * this._parent._rotationMatrix[3] + this._particle.position.z * this._parent._rotationMatrix[6];
+                            this._rotated.y = this._particle.position.x * this._parent._rotationMatrix[1] + this._particle.position.y * this._parent._rotationMatrix[4] + this._particle.position.z * this._parent._rotationMatrix[7];
+                            this._rotated.z = this._particle.position.x * this._parent._rotationMatrix[2] + this._particle.position.y * this._parent._rotationMatrix[5] + this._particle.position.z * this._parent._rotationMatrix[8];
+
+                            this._particle._globalPosition.x = this._parent._globalPosition.x + this._rotated.x;
+                            this._particle._globalPosition.y = this._parent._globalPosition.y + this._rotated.y;
+                            this._particle._globalPosition.z = this._parent._globalPosition.z + this._rotated.z;
+
+                            if (this._computeParticleRotation || this.billboard) {
+                                this._particle._rotationMatrix[0] = this._rotMatrix.m[0] * this._parent._rotationMatrix[0] + this._rotMatrix.m[1] * this._parent._rotationMatrix[3] + this._rotMatrix.m[2] * this._parent._rotationMatrix[6];
+                                this._particle._rotationMatrix[1] = this._rotMatrix.m[0] * this._parent._rotationMatrix[1] + this._rotMatrix.m[1] * this._parent._rotationMatrix[4] + this._rotMatrix.m[2] * this._parent._rotationMatrix[7];
+                                this._particle._rotationMatrix[2] = this._rotMatrix.m[0] * this._parent._rotationMatrix[2] + this._rotMatrix.m[1] * this._parent._rotationMatrix[5] + this._rotMatrix.m[2] * this._parent._rotationMatrix[8];
+                                this._particle._rotationMatrix[3] = this._rotMatrix.m[4] * this._parent._rotationMatrix[0] + this._rotMatrix.m[5] * this._parent._rotationMatrix[3] + this._rotMatrix.m[6] * this._parent._rotationMatrix[6];
+                                this._particle._rotationMatrix[4] = this._rotMatrix.m[4] * this._parent._rotationMatrix[1] + this._rotMatrix.m[5] * this._parent._rotationMatrix[4] + this._rotMatrix.m[6] * this._parent._rotationMatrix[7];
+                                this._particle._rotationMatrix[5] = this._rotMatrix.m[4] * this._parent._rotationMatrix[2] + this._rotMatrix.m[5] * this._parent._rotationMatrix[5] + this._rotMatrix.m[6] * this._parent._rotationMatrix[8];
+                                this._particle._rotationMatrix[6] = this._rotMatrix.m[8] * this._parent._rotationMatrix[0] + this._rotMatrix.m[9] * this._parent._rotationMatrix[3] + this._rotMatrix.m[10] * this._parent._rotationMatrix[6];
+                                this._particle._rotationMatrix[7] = this._rotMatrix.m[8] * this._parent._rotationMatrix[1] + this._rotMatrix.m[9] * this._parent._rotationMatrix[4] + this._rotMatrix.m[10] * this._parent._rotationMatrix[7];
+                                this._particle._rotationMatrix[8] = this._rotMatrix.m[8] * this._parent._rotationMatrix[2] + this._rotMatrix.m[9] * this._parent._rotationMatrix[5] + this._rotMatrix.m[10] * this._parent._rotationMatrix[8];
+                            }
+                        }
+                        else {
+                            this._particle._globalPosition.x = this._particle.position.x;
+                            this._particle._globalPosition.y = this._particle.position.y;
+                            this._particle._globalPosition.z = this._particle.position.z;
+
+                            if (this._computeParticleRotation || this.billboard) {
+                                this._particle._rotationMatrix[0] = this._rotMatrix.m[0];
+                                this._particle._rotationMatrix[1] = this._rotMatrix.m[1];
+                                this._particle._rotationMatrix[2] = this._rotMatrix.m[2];
+                                this._particle._rotationMatrix[3] = this._rotMatrix.m[4];
+                                this._particle._rotationMatrix[4] = this._rotMatrix.m[5];
+                                this._particle._rotationMatrix[5] = this._rotMatrix.m[6];
+                                this._particle._rotationMatrix[6] = this._rotMatrix.m[8];
+                                this._particle._rotationMatrix[7] = this._rotMatrix.m[9];
+                                this._particle._rotationMatrix[8] = this._rotMatrix.m[10];
+                            }
                         }
        
                         if (this._particle.translateFromPivot) {
@@ -784,9 +840,9 @@
                             this._pivotBackTranslation.z = 0.0;
                         }
                         else {
-                            this._pivotBackTranslation.x = this._particle.pivot.x;
-                            this._pivotBackTranslation.y = this._particle.pivot.y;
-                            this._pivotBackTranslation.z = this._particle.pivot.z;
+                            this._pivotBackTranslation.x = this._scaledPivot.x;
+                            this._pivotBackTranslation.y = this._scaledPivot.y;
+                            this._pivotBackTranslation.z = this._scaledPivot.z;
                         }
                         // particle vertex loop
                         for (pt = 0; pt < this._shape.length; pt++) {
@@ -807,9 +863,9 @@
                             this._vertex.y *= this._particle.scaling.y;
                             this._vertex.z *= this._particle.scaling.z;
 
-                            this._vertex.x -= this._particle.pivot.x;
-                            this._vertex.y -= this._particle.pivot.y;
-                            this._vertex.z -= this._particle.pivot.z;
+                            this._vertex.x -= this._scaledPivot.x;
+                            this._vertex.y -= this._scaledPivot.y;
+                            this._vertex.z -= this._scaledPivot.z;
     
                             this._rotated.x = this._vertex.x * this._particle._rotationMatrix[0] + this._vertex.y * this._particle._rotationMatrix[3] + this._vertex.z * this._particle._rotationMatrix[6];
                             this._rotated.y = this._vertex.x * this._particle._rotationMatrix[1] + this._vertex.y * this._particle._rotationMatrix[4] + this._vertex.z * this._particle._rotationMatrix[7];
@@ -819,9 +875,9 @@
                             this._rotated.y += this._pivotBackTranslation.y;
                             this._rotated.z += this._pivotBackTranslation.z;
 
-                            this._positions32[idx] = this._particle.position.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
-                            this._positions32[idx + 1] = this._particle.position.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
-                            this._positions32[idx + 2] = this._particle.position.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
+                            this._positions32[idx] = this._particle._globalPosition.x + this._cam_axisX.x * this._rotated.x + this._cam_axisY.x * this._rotated.y + this._cam_axisZ.x * this._rotated.z;
+                            this._positions32[idx + 1] = this._particle._globalPosition.y + this._cam_axisX.y * this._rotated.x + this._cam_axisY.y * this._rotated.y + this._cam_axisZ.y * this._rotated.z;
+                            this._positions32[idx + 2] = this._particle._globalPosition.z + this._cam_axisX.z * this._rotated.x + this._cam_axisY.z * this._rotated.y + this._cam_axisZ.z * this._rotated.z;
     
                             if (this._computeBoundingBox) {
                                 if (this._positions32[idx] < this._minimum.x) {
@@ -926,9 +982,9 @@
                         this._maxBbox.x = this._particle._modelBoundingInfo.maximum.x * this._particle.scaling.x;
                         this._maxBbox.y = this._particle._modelBoundingInfo.maximum.y * this._particle.scaling.y;
                         this._maxBbox.z = this._particle._modelBoundingInfo.maximum.z * this._particle.scaling.z;
-                        bSphere.center.x = this._particle.position.x + (this._minBbox.x + this._maxBbox.x) * 0.5;
-                        bSphere.center.y = this._particle.position.y + (this._minBbox.y + this._maxBbox.y) * 0.5;
-                        bSphere.center.z = this._particle.position.z + (this._minBbox.z + this._maxBbox.z) * 0.5;
+                        bSphere.center.x = this._particle._globalPosition.x + (this._minBbox.x + this._maxBbox.x) * 0.5;
+                        bSphere.center.y = this._particle._globalPosition.y + (this._minBbox.y + this._maxBbox.y) * 0.5;
+                        bSphere.center.z = this._particle._globalPosition.z + (this._minBbox.z + this._maxBbox.z) * 0.5;
                         bSphere.radius = this._bSphereRadiusFactor * 0.5 * Math.sqrt((this._maxBbox.x - this._minBbox.x) * (this._maxBbox.x - this._minBbox.x) + (this._maxBbox.y - this._minBbox.y) * (this._maxBbox.y - this._minBbox.y) + (this._maxBbox.z - this._minBbox.z) * (this._maxBbox.z - this._minBbox.z));
                         bSphere._update(this.mesh._worldMatrix);
                     }

+ 1 - 1
src/Shaders/glowMapGeneration.fragment.fx

@@ -18,7 +18,7 @@ void main(void)
 #endif
 
 #ifdef EMISSIVE
-	gl_FragColor = texture2D(emissiveSampler, vUVEmissive);
+	gl_FragColor = texture2D(emissiveSampler, vUVEmissive) * color;
 #else
 	gl_FragColor = color;
 #endif

+ 15 - 7
src/Shaders/glowMapMerge.fragment.fx

@@ -1,20 +1,28 @@
 // Samplers
 varying vec2 vUV;
 uniform sampler2D textureSampler;
+#ifdef EMISSIVE
+    uniform sampler2D textureSampler2;
+#endif
 
 // Offset
 uniform float offset;
 
 void main(void) {
-	vec4 baseColor = texture2D(textureSampler, vUV);
+    vec4 baseColor = texture2D(textureSampler, vUV);
 
-	baseColor.a = abs(offset - baseColor.a);
+    #ifdef EMISSIVE
+        baseColor += texture2D(textureSampler2, vUV);
+        baseColor *= offset;
+    #else
+        baseColor.a = abs(offset - baseColor.a);
 
-	#ifdef STROKE
-        float alpha = smoothstep(.0, .1, baseColor.a);
-        baseColor.a = alpha;
-        baseColor.rgb = baseColor.rgb * alpha;
+        #ifdef STROKE
+            float alpha = smoothstep(.0, .1, baseColor.a);
+            baseColor.a = alpha;
+            baseColor.rgb = baseColor.rgb * alpha;
+        #endif
     #endif
 
-	gl_FragColor = baseColor;
+    gl_FragColor = baseColor;
 }

+ 1 - 1
src/Tools/babylon.assetsManager.ts

@@ -311,7 +311,7 @@ module BABYLON {
             scene._loadFile(this.url, (data) => {
                 this.text = data as string;
                 onSuccess();
-            }, undefined, false, true, (request, exception) => {
+            }, undefined, false, false, (request, exception) => {
                 if (request) {
                     onError(request.status + " " + request.statusText, exception);
                 }

+ 36 - 51
src/Tools/babylon.promise.ts

@@ -22,36 +22,6 @@ module BABYLON {
         private _onRejected?: (reason: any) => void;
         private _rejectWasConsumed = false;
 
-        public get state(): PromiseStates {
-            return this._state;
-        }
-
-        public get isFulfilled(): boolean {
-            return this._state === PromiseStates.Fulfilled;
-        }
-
-        public get isRejected(): boolean {
-            return this._state === PromiseStates.Rejected;
-        }
-
-        public get isPending(): boolean {
-            return this._state === PromiseStates.Pending;
-        }
-
-        public value(): Nullable<T> | undefined {
-            if (!this.isFulfilled) {
-                throw new Error("Promise is not fulfilled");
-            }
-            return this._result;
-        }
-
-        public reason(): any {
-            if (!this.isRejected) {
-                throw new Error("Promise is not rejected");
-            }
-            return this._reason;
-        }
-
         public constructor(resolver?: (
             resolve: (value?: Nullable<T>) => void,
             reject: (reason: any) => void
@@ -85,21 +55,23 @@ module BABYLON {
             this._children.push(newPromise);
 
             if (this._state !== PromiseStates.Pending) {
-                if (this._state === PromiseStates.Fulfilled || this._rejectWasConsumed) {
-                    let returnedValue = newPromise._resolve(this._result);
-
-                    if (returnedValue !== undefined && returnedValue !== null) {
-                        if ((<InternalPromise<T>>returnedValue)._state !== undefined) {
-                            let returnedPromise = returnedValue as InternalPromise<T>;
-                            newPromise._children.push(returnedPromise);
-                            newPromise = returnedPromise;
-                        } else {
-                            newPromise._result = (<T>returnedValue);
+                Tools.SetImmediate(() => {
+                    if (this._state === PromiseStates.Fulfilled || this._rejectWasConsumed) {
+                        let returnedValue = newPromise._resolve(this._result);
+
+                        if (returnedValue !== undefined && returnedValue !== null) {
+                            if ((<InternalPromise<T>>returnedValue)._state !== undefined) {
+                                let returnedPromise = returnedValue as InternalPromise<T>;
+                                newPromise._children.push(returnedPromise);
+                                newPromise = returnedPromise;
+                            } else {
+                                newPromise._result = (<T>returnedValue);
+                            }
                         }
+                    } else {
+                        newPromise._reject(this._reason);
                     }
-                } else {
-                    newPromise._reject(this._reason);
-                }
+                });
             }
 
             return newPromise;
@@ -108,11 +80,11 @@ module BABYLON {
         private _moveChildren(children: InternalPromise<T>[]): void {
             this._children.push(...children.splice(0, children.length));
 
-            if (this.isFulfilled) {
+            if (this._state === PromiseStates.Fulfilled) {
                 for (var child of this._children) {
                     child._resolve(this._result);
                 }
-            } else if (this.isRejected) {
+            } else if (this._state === PromiseStates.Rejected) {
                 for (var child of this._children) {
                     child._reject(this._reason);
                 }
@@ -144,21 +116,30 @@ module BABYLON {
                     child._resolve(value);
                 }
 
+                this._children.length = 0;
+                delete this._onFulfilled;
+                delete this._onRejected;
+
                 return returnedValue;
             } catch (e) {
-                this._reject(e);
+                this._reject(e, true);
             }
 
             return null;
         }
 
-        private _reject(reason: any): void {
+        private _reject(reason: any, onLocalThrow = false): void {
             this._state = PromiseStates.Rejected;
             this._reason = reason;
 
-            if (this._onRejected) {
-                this._onRejected(reason);
-                this._rejectWasConsumed = true;
+            if (this._onRejected && !onLocalThrow) {
+                try {
+                    this._onRejected(reason);
+                    this._rejectWasConsumed = true;
+                }
+                catch (e) {
+                    reason = e;
+                }
             }
 
             for (var child of this._children) {
@@ -168,6 +149,10 @@ module BABYLON {
                     child._reject(reason);
                 }
             }
+
+            this._children.length = 0;
+            delete this._onFulfilled;
+            delete this._onRejected;
         }
 
         public static resolve<T>(value: T): InternalPromise<T> {
@@ -188,7 +173,7 @@ module BABYLON {
                 }
                 return null;
             }, (reason: any) => {
-                if (!agregator.rootPromise.isRejected) {
+                if (agregator.rootPromise._state !== PromiseStates.Rejected) {
                     agregator.rootPromise._reject(reason);
                 }
             })

+ 12 - 2
src/Tools/babylon.tools.ts

@@ -494,11 +494,16 @@
             var img = new Image();
             Tools.SetCorsBehavior(url, img);
 
-            img.onload = () => {
+            const loadHandler = () => {
+                img.removeEventListener("load", loadHandler);
+                img.removeEventListener("error", errorHandler);
                 onLoad(img);
             };
 
-            img.onerror = err => {
+            const errorHandler = (err: any) => {
+                img.removeEventListener("load", loadHandler);
+                img.removeEventListener("error", errorHandler);
+
                 Tools.Error("Error while trying to load image: " + url);
 
                 if (onError) {
@@ -506,6 +511,9 @@
                 }
             };
 
+            img.addEventListener("load", loadHandler);
+            img.addEventListener("error", errorHandler);
+
             var noIndexedDB = () => {
                 img.src = url;
             };
@@ -599,7 +607,9 @@
                     }
 
                     const onLoadEnd = () => {
+                        request.removeEventListener("loadend", onLoadEnd);
                         fileRequest.onCompleteObservable.notifyObservers(fileRequest);
+                        fileRequest.onCompleteObservable.clear();
                     };
 
                     request.addEventListener("loadend", onLoadEnd);

+ 162 - 29
src/babylon.node.ts

@@ -4,41 +4,82 @@
      * Node is the basic class for all scene objects (Mesh, Light Camera).
      */
     export class Node {
+        /**
+         * Gets or sets the name of the node
+         */
         @serialize()
         public name: string;
 
+        /**
+         * Gets or sets the id of the node
+         */
         @serialize()
         public id: string;
 
+        /**
+         * Gets or sets the unique id of the node
+         */
         @serialize()
         public uniqueId: number;
 
+        /**
+         * Gets or sets a string used to store user defined state for the node
+         */
         @serialize()
         public state = "";
 
+        /**
+         * Gets or sets an object used to store user defined information for the node
+         */
         @serialize()
         public metadata: any = null;
 
+        /**
+         * Gets or sets a boolean used to define if the node must be serialized
+         */
         public doNotSerialize = false;
+        
+        /** @ignore */
+        public _isDisposed = false;        
 
+        /**
+         * Gets a list of {BABYLON.Animation} associated with the node
+         */
         public animations = new Array<Animation>();
         private _ranges: { [name: string]: Nullable<AnimationRange> } = {};
 
+        /**
+         * Callback raised when the node is ready to be used
+         */
         public onReady: (node: Node) => void;
 
         private _isEnabled = true;
         private _isReady = true;
+        /** @ignore */
         public _currentRenderId = -1;
         private _parentRenderId = -1;
 
+        /** @ignore */
         public _waitingParentId: Nullable<string>;
 
         private _scene: Scene;
+        /** @ignore */
         public _cache: any;
 
         private _parentNode: Nullable<Node>;
         private _children: Node[];
 
+        /**
+         * Gets a boolean indicating if the node has been disposed
+         * @returns true if the node was disposed
+         */
+        public isDisposed(): boolean {
+            return this._isDisposed;
+        }        
+
+        /**
+         * Gets or sets the parent of the node
+         */
         public set parent(parent: Nullable<Node>) {
             if (this._parentNode === parent) {
                 return;
@@ -68,17 +109,24 @@
             return this._parentNode;
         }
 
+        /**
+         * Gets a string idenfifying the name of the class
+         * @returns "Node" string
+         */
         public getClassName(): string {
             return "Node";
         }
 
         /**
-        * An event triggered when the mesh is disposed.
+        * An event triggered when the mesh is disposed
         * @type {BABYLON.Observable}
         */
         public onDisposeObservable = new Observable<Node>();
 
         private _onDisposeObserver: Nullable<Observer<Node>>;
+        /**
+         * Sets a callback that will be raised when the node will be disposed
+         */
         public set onDispose(callback: () => void) {
             if (this._onDisposeObserver) {
                 this.onDisposeObservable.remove(this._onDisposeObserver);
@@ -87,7 +135,7 @@
         }
 
         /**
-         * @constructor
+         * Creates a new Node
          * @param {string} name - the name and id to be given to this node
          * @param {BABYLON.Scene} the scene this node will be added to
          */
@@ -99,10 +147,18 @@
             this._initCache();
         }
 
+        /**
+         * Gets the scene of the node
+         * @returns a {BABYLON.Scene}
+         */
         public getScene(): Scene {
             return this._scene;
         }
 
+        /**
+         * Gets the engine of the node
+         * @returns a {BABYLON.Engine}
+         */
         public getEngine(): Engine {
             return this._scene.getEngine();
         }
@@ -110,6 +166,12 @@
         // Behaviors
         private _behaviors = new Array<Behavior<Node>>();
 
+        /**
+         * Attach a behavior to the node
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current Node
+         */
         public addBehavior(behavior: Behavior<Node>): Node {
             var index = this._behaviors.indexOf(behavior);
 
@@ -135,6 +197,12 @@
             return this;
         }
 
+        /**
+         * Remove an attached behavior
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current Node
+         */
         public removeBehavior(behavior: Behavior<Node>): Node {
             var index = this._behaviors.indexOf(behavior);
 
@@ -148,10 +216,20 @@
             return this;
         }
 
+        /**
+         * Gets the list of attached behaviors
+         * @see http://doc.babylonjs.com/features/behaviour
+         */
         public get behaviors(): Behavior<Node>[] {
             return this._behaviors;
         }
 
+        /**
+         * Gets an attached behavior by name
+         * @param name defines the name of the behavior to look for
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @returns null if behavior was not found else the requested behavior
+         */
         public getBehaviorByName(name: string): Nullable<Behavior<Node>> {
             for (var behavior of this._behaviors) {
                 if (behavior.name === name) {
@@ -162,18 +240,23 @@
             return null;
         }
 
-        // override it in derived class
+        /**
+         * Returns the world matrix of the node
+         * @returns a matrix containing the node's world matrix
+         */
         public getWorldMatrix(): Matrix {
             return Matrix.Identity();
         }
 
         // override it in derived class if you add new variables to the cache
         // and call the parent class method
+        /** @ignore */
         public _initCache() {
             this._cache = {};
             this._cache.parent = undefined;
         }
 
+        /** @ignore */
         public updateCache(force?: boolean): void {
             if (!force && this.isSynchronized())
                 return;
@@ -185,20 +268,24 @@
 
         // override it in derived class if you add new variables to the cache
         // and call the parent class method if !ignoreParentClass
+        /** @ignore */
         public _updateCache(ignoreParentClass?: boolean): void {
         }
 
         // override it in derived class if you add new variables to the cache
+        /** @ignore */
         public _isSynchronized(): boolean {
             return true;
         }
 
+        /** @ignore */
         public _markSyncedWithParent() {
             if (this.parent) {
                 this._parentRenderId = this.parent._currentRenderId;
             }
         }
 
+        /** @ignore */
         public isSynchronizedWithParent(): boolean {
             if (!this.parent) {
                 return true;
@@ -211,6 +298,7 @@
             return this.parent.isSynchronized();
         }
 
+        /** @ignore */
         public isSynchronized(updateCache?: boolean): boolean {
             var check = this.hasNewParent();
 
@@ -224,6 +312,7 @@
             return !check;
         }
 
+        /** @ignore */
         public hasNewParent(update?: boolean): boolean {
             if (this._cache.parent === this.parent)
                 return false;
@@ -236,17 +325,17 @@
 
         /**
          * Is this node ready to be used/rendered
-         * @return {boolean} is it ready
+         * @return true if the node is ready
          */
         public isReady(): boolean {
             return this._isReady;
         }
 
         /**
-         * Is this node enabled. 
-         * If the node has a parent, all ancestors will be checked and false will be returned if any are false (not enabled), otherwise will return true.
-         * @param {boolean} [checkAncestors=true] - Indicates if this method should check the ancestors. The default is to check the ancestors. If set to false, the method will return the value of this node without checking ancestors.
-         * @return {boolean} whether this node (and its parent) is enabled.
+         * Is this node enabled?
+         * If the node has a parent, all ancestors will be checked and false will be returned if any are false (not enabled), otherwise will return true
+         * @param checkAncestors indicates if this method should check the ancestors. The default is to check the ancestors. If set to false, the method will return the value of this node without checking ancestors
+         * @return whether this node (and its parent) is enabled
          * @see setEnabled
          */
         public isEnabled(checkAncestors: boolean = true): boolean {
@@ -266,8 +355,8 @@
         }
 
         /**
-         * Set the enabled state of this node.
-         * @param {boolean} value - the new enabled state
+         * Set the enabled state of this node
+         * @param value defines the new enabled state
          * @see isEnabled
          */
         public setEnabled(value: boolean): void {
@@ -275,10 +364,11 @@
         }
 
         /**
-         * Is this node a descendant of the given node.
-         * The function will iterate up the hierarchy until the ancestor was found or no more parents defined.
-         * @param {BABYLON.Node} ancestor - The parent node to inspect
+         * Is this node a descendant of the given node?
+         * The function will iterate up the hierarchy until the ancestor was found or no more parents defined
+         * @param ancestor defines the parent node to inspect
          * @see parent
+         * @returns a boolean indicating if this node is a descendant of the given node
          */
         public isDescendantOf(ancestor: Node): boolean {
             if (this.parent) {
@@ -291,12 +381,7 @@
             return false;
         }
 
-        /**
-         * Evaluate the list of children and determine if they should be considered as descendants considering the given criterias
-         * @param {BABYLON.Node[]} results the result array containing the nodes matching the given criterias
-         * @param {boolean} directDescendantsOnly if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered.
-         * @param predicate: an optional predicate that will be called on every evaluated children, the predicate must return true for a given child to be part of the result, otherwise it will be ignored.
-         */
+        /** @ignore */
         public _getDescendants(results: Node[], directDescendantsOnly: boolean = false, predicate?: (node: Node) => boolean): void {
             if (!this._children) {
                 return;
@@ -316,10 +401,10 @@
         }
 
         /**
-         * Will return all nodes that have this node as ascendant.
-         * @param {boolean} directDescendantsOnly if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered.
-         * @param predicate: an optional predicate that will be called on every evaluated children, the predicate must return true for a given child to be part of the result, otherwise it will be ignored.
-         * @return {BABYLON.Node[]} all children nodes of all types.
+         * Will return all nodes that have this node as ascendant
+         * @param directDescendantsOnly defines if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered
+         * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
+         * @return all children nodes of all types
          */
         public getDescendants(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean): Node[] {
             var results = new Array<Node>();
@@ -330,7 +415,10 @@
         }
 
         /**
-         * Get all child-meshes of this node.
+         * Get all child-meshes of this node
+         * @param directDescendantsOnly defines if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered
+         * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
+         * @returns an array of {BABYLON.AbstractMesh}
          */
         public getChildMeshes(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean): AbstractMesh[] {
             var results: Array<AbstractMesh> = [];
@@ -341,7 +429,10 @@
         }
 
         /**
-         * Get all child-transformNodes of this node.
+         * Get all child-transformNodes of this node
+         * @param directDescendantsOnly defines if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered
+         * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
+         * @returns an array of {BABYLON.TransformNode}
          */
         public getChildTransformNodes(directDescendantsOnly?: boolean, predicate?: (node: Node) => boolean): TransformNode[] {
             var results: Array<TransformNode> = [];
@@ -352,12 +443,15 @@
         }
 
         /**
-         * Get all direct children of this node.
-        */
+         * Get all direct children of this node
+         * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
+         * @returns an array of {BABYLON.Node}
+         */
         public getChildren(predicate?: (node: Node) => boolean): Node[] {
             return this.getDescendants(true, predicate);
         }
 
+        /** @ignore */
         public _setReady(state: boolean): void {
             if (state === this._isReady) {
                 return;
@@ -374,6 +468,11 @@
             }
         }
 
+        /**
+         * Get an animation by name
+         * @param name defines the name of the animation to look for
+         * @returns null if not found else the requested animation
+         */
         public getAnimationByName(name: string): Nullable<Animation> {
             for (var i = 0; i < this.animations.length; i++) {
                 var animation = this.animations[i];
@@ -386,6 +485,12 @@
             return null;
         }
 
+        /**
+         * Creates an animation range for this node
+         * @param name defines the name of the range
+         * @param from defines the starting key
+         * @param to defines the end key
+         */
         public createAnimationRange(name: string, from: number, to: number): void {
             // check name not already in use
             if (!this._ranges[name]) {
@@ -398,6 +503,11 @@
             }
         }
 
+        /**
+         * Delete a specific animation range
+         * @param name defines the name of the range to delete
+         * @param deleteFrames defines if animation frames from the range must be deleted as well
+         */
         public deleteAnimationRange(name: string, deleteFrames = true): void {
             for (var i = 0, nAnimations = this.animations.length; i < nAnimations; i++) {
                 if (this.animations[i]) {
@@ -407,6 +517,11 @@
             this._ranges[name] = null; // said much faster than 'delete this._range[name]' 
         }
 
+        /**
+         * Get an animation range by name
+         * @param name defines the name of the animation range to look for
+         * @returns null if not found else the requested animation range
+         */
         public getAnimationRange(name: string): Nullable<AnimationRange> {
             return this._ranges[name];
         }
@@ -417,7 +532,7 @@
          * @param loop defines if the animation should loop (false by default)
          * @param speedRatio defines the speed factor in which to run the animation (1 by default)
          * @param onAnimationEnd defines a function to be executed when the animation ended (undefined by default)
-         * @returns the {BABYLON.Animatable} object created for this animation. If range does not exist, it will return null
+         * @returns the object created for this animation. If range does not exist, it will return null
          */
         public beginAnimation(name: string, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): Nullable<Animatable> {
             var range = this.getAnimationRange(name);
@@ -429,6 +544,10 @@
             return this._scene.beginAnimation(this, range.from, range.to, loop, speedRatio, onAnimationEnd);
         }
 
+        /**
+         * Serialize animation ranges into a JSON compatible object
+         * @returns serialization object
+         */
         public serializeAnimationRanges(): any {
             var serializationRanges = [];
             for (var name in this._ranges) {
@@ -445,11 +564,18 @@
             return serializationRanges;
         }
 
-        // override it in derived class
+        /**
+         * Computes the world matrix of the node
+         * @param force defines if the cache version should be invalidated forcing the world matrix to be created from scratch
+         * @returns the world matrix
+         */
         public computeWorldMatrix(force?: boolean): Matrix {
             return Matrix.Identity();
         }
 
+        /**
+         * Releases all associated resources
+         */
         public dispose(): void {
             this.parent = null;
 
@@ -463,8 +589,15 @@
             }
 
             this._behaviors = [];
+            this._isDisposed = true;            
         }
 
+        /**
+         * Parse animation range data from a serialization object and store them into a given node
+         * @param node defines where to store the animation ranges
+         * @param parsedNode defines the serialization object to read data from
+         * @param scene defines the hosting scene
+         */
         public static ParseAnimationRanges(node: Node, parsedNode: any, scene: Scene): void {
             if (parsedNode.ranges) {
                 for (var index = 0; index < parsedNode.ranges.length; index++) {

+ 54 - 31
src/babylon.scene.ts

@@ -748,9 +748,15 @@
         public spritesEnabled = true;
         public spriteManagers = new Array<SpriteManager>();
 
-        // Layers
+        /**
+         * The list of layers (background and foreground) of the scene.
+         */
         public layers = new Array<Layer>();
-        public highlightLayers = new Array<HighlightLayer>();
+
+        /**
+         * The list of effect layers (highlights/glow) contained in the scene.
+         */
+        public effectLayers = new Array<EffectLayer>();
 
         // Skeletons
         private _skeletonsEnabled = true;
@@ -1911,9 +1917,9 @@
                     return false;
                 }
 
-                // Highlight layers
+                // Effect layers
                 let hardwareInstancedRendering = mesh.getClassName() === "InstancedMesh" || engine.getCaps().instancedArrays && (<Mesh>mesh).instances.length > 0;
-                for (var layer of this.highlightLayers) {
+                for (var layer of this.effectLayers) {
                     if (!layer.hasMesh(mesh)) {
                         continue;
                     }
@@ -3049,9 +3055,24 @@
          * @return The highlight layer if found otherwise null.
          */
         public getHighlightLayerByName(name: string): Nullable<HighlightLayer> {
-            for (var index = 0; index < this.highlightLayers.length; index++) {
-                if (this.highlightLayers[index].name === name) {
-                    return this.highlightLayers[index];
+            for (var index = 0; index < this.effectLayers.length; index++) {
+                if (this.effectLayers[index].name === name && this.effectLayers[index].getEffectName() === HighlightLayer.EffectName) {
+                    return (<any>this.effectLayers[index]) as HighlightLayer;
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * Return a the first highlight layer of the scene with a given name.
+         * @param name The name of the highlight layer to look for.
+         * @return The highlight layer if found otherwise null.
+         */
+        public getGlowLayerByName(name: string): Nullable<GlowLayer> {
+            for (var index = 0; index < this.effectLayers.length; index++) {
+                if (this.effectLayers[index].name === name && this.effectLayers[index].getEffectName() === GlowLayer.EffectName) {
+                    return (<any>this.effectLayers[index]) as GlowLayer;
                 }
             }
 
@@ -3410,22 +3431,24 @@
                 needsRestoreFrameBuffer = true; // Restore back buffer
             }
 
-            // Render HighlightLayer Texture
+            // Render EffecttLayer Texture
             var stencilState = this._engine.getStencilBuffer();
-            var renderhighlights = false;
-            if (this.renderTargetsEnabled && this.highlightLayers && this.highlightLayers.length > 0) {
+            var renderEffects = false;
+            var needStencil = false;
+            if (this.renderTargetsEnabled && this.effectLayers && this.effectLayers.length > 0) {
                 this._intermediateRendering = true;
-                for (let i = 0; i < this.highlightLayers.length; i++) {
-                    let highlightLayer = this.highlightLayers[i];
+                for (let i = 0; i < this.effectLayers.length; i++) {
+                    let effectLayer = this.effectLayers[i];
 
-                    if (highlightLayer.shouldRender() &&
-                        (!highlightLayer.camera ||
-                            (highlightLayer.camera.cameraRigMode === Camera.RIG_MODE_NONE && camera === highlightLayer.camera) ||
-                            (highlightLayer.camera.cameraRigMode !== Camera.RIG_MODE_NONE && highlightLayer.camera._rigCameras.indexOf(camera) > -1))) {
+                    if (effectLayer.shouldRender() &&
+                        (!effectLayer.camera ||
+                            (effectLayer.camera.cameraRigMode === Camera.RIG_MODE_NONE && camera === effectLayer.camera) ||
+                            (effectLayer.camera.cameraRigMode !== Camera.RIG_MODE_NONE && effectLayer.camera._rigCameras.indexOf(camera) > -1))) {
 
-                        renderhighlights = true;
+                        renderEffects = true;
+                        needStencil = needStencil || effectLayer.needStencil();
 
-                        let renderTarget = (<RenderTargetTexture>(<any>highlightLayer)._mainTexture);
+                        let renderTarget = (<RenderTargetTexture>(<any>effectLayer)._mainTexture);
                         if (renderTarget._shouldRender()) {
                             this._renderId++;
                             renderTarget.render(false, false);
@@ -3461,8 +3484,8 @@
                 engine.setDepthBuffer(true);
             }
 
-            // Activate HighlightLayer stencil
-            if (renderhighlights) {
+            // Activate effect Layer stencil
+            if (needStencil) {
                 this._engine.setStencilBuffer(true);
             }
 
@@ -3471,8 +3494,8 @@
             this._renderingManager.render(null, null, true, true);
             this.onAfterDrawPhaseObservable.notifyObservers(this);
 
-            // Restore HighlightLayer stencil
-            if (renderhighlights) {
+            // Restore effect Layer stencil
+            if (needStencil) {
                 this._engine.setStencilBuffer(stencilState);
             }
 
@@ -3506,12 +3529,12 @@
                 engine.setDepthBuffer(true);
             }
 
-            // Highlight Layer
-            if (renderhighlights) {
+            // Effect Layer
+            if (renderEffects) {
                 engine.setDepthBuffer(false);
-                for (let i = 0; i < this.highlightLayers.length; i++) {
-                    if (this.highlightLayers[i].shouldRender()) {
-                        this.highlightLayers[i].render();
+                for (let i = 0; i < this.effectLayers.length; i++) {
+                    if (this.effectLayers[i].shouldRender()) {
+                        this.effectLayers[i].render();
                     }
                 }
                 engine.setDepthBuffer(true);
@@ -4146,8 +4169,8 @@
             while (this.layers.length) {
                 this.layers[0].dispose();
             }
-            while (this.highlightLayers.length) {
-                this.highlightLayers[0].dispose();
+            while (this.effectLayers.length) {
+                this.effectLayers[0].dispose();
             }
 
             // Release textures
@@ -4606,8 +4629,8 @@
                 layer._rebuild();
             }
 
-            for (var highlightLayer of this.highlightLayers) {
-                highlightLayer._rebuild();
+            for (var effectLayer of this.effectLayers) {
+                effectLayer._rebuild();
             }
 
             if (this._boundingBoxRenderer) {

+ 3 - 1
tests/nullEngine/app.js

@@ -129,6 +129,8 @@ var engine = new BABYLON.NullEngine();
 //     scene.render();
 // })
 
+new BABYLON.Scene(engine).dispose()
+
 BABYLON.SceneLoader.Load("https://playground.babylonjs.com/scenes/", "skull.babylon", engine, (scene) => {
     console.log('scene loaded!');
     for (var index = 0; index < scene.meshes.length; index++) {
@@ -139,4 +141,4 @@ BABYLON.SceneLoader.Load("https://playground.babylonjs.com/scenes/", "skull.baby
      //   scene.render();
     //});
   
-  }, progress => {}, (scene, err) => console.error('error:', err));
+  }, progress => {}, (scene, err) => console.error('error:', err));

+ 5 - 5
tests/nullEngine/package.json

@@ -1,5 +1,5 @@
-{
-  "devDependencies": {
-    "xhr2": "^0.1.4"
-  }
-}
+{
+  "devDependencies": {
+    "xhr2": "^0.1.4"
+  }
+}

+ 19 - 17
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -39,19 +39,25 @@ describe('Babylon Scene Loader', function () {
     describe('#glTF', () => {
         it('Load BoomBox', () => {
             const scene = new BABYLON.Scene(subject);
-            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene);
+            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(scene => {
+                expect(scene.meshes.length, "scene.meshes.length").to.equal(3);
+                expect(scene.materials.length, "scene.materials.length").to.equal(1);
+            });
         });
 
         it('Load BoomBox GLB', () => {
             const scene = new BABYLON.Scene(subject);
-            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/", "BoomBox.glb", scene);
+            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/", "BoomBox.glb", scene).then(scene => {
+                expect(scene.meshes.length, "scene.meshes.length").to.equal(3);
+                expect(scene.materials.length, "scene.materials.length").to.equal(1);
+            });
         });
 
         it('Load BoomBox with callbacks', () => {
             let parsedCount = 0;
-            let primaryMeshLoadCount = 0;
-            let primaryMaterialLoadCount = 0;
-            let textureLoadCounts: { [name: string]: number } = {};
+            let meshCount = 0;
+            let materialCount = 0;
+            let textureCounts: { [name: string]: number } = {};
             let ready = false;
 
             const deferred = new BABYLON.Deferred();
@@ -61,18 +67,14 @@ describe('Babylon Scene Loader', function () {
                 };
 
                 loader.onMeshLoaded = mesh => {
-                    if (mesh.name === "BoomBox") {
-                        primaryMeshLoadCount++;
-                    }
+                    meshCount++;
                 };
                 loader.onMaterialLoaded = material => {
-                    if (material.name === "BoomBox_Mat") {
-                        primaryMaterialLoadCount++;
-                    }
+                    materialCount++;
                 };
                 loader.onTextureLoaded = texture => {
-                    textureLoadCounts[texture.name] = textureLoadCounts[texture.name] || 0;
-                    textureLoadCounts[texture.name]++;
+                    textureCounts[texture.name] = textureCounts[texture.name] || 0;
+                    textureCounts[texture.name]++;
                 };
 
                 loader.onComplete = () => {
@@ -91,8 +93,8 @@ describe('Babylon Scene Loader', function () {
                 ready = true;
 
                 expect(parsedCount, "parsedCount").to.equal(1);
-                expect(primaryMeshLoadCount, "primaryMeshLoadCount").to.equal(1);
-                expect(primaryMaterialLoadCount, "primaryMaterialLoadCount").to.equal(1);
+                expect(meshCount, "meshCount").to.equal(scene.meshes.length);
+                expect(materialCount, "materialCount").to.equal(scene.materials.length);
 
                 const expectedTextureLoadCounts = {
                     "baseColor": 1,
@@ -100,9 +102,9 @@ describe('Babylon Scene Loader', function () {
                     "normal": 1,
                     "emissive": 1
                 };
-                expect(Object.keys(textureLoadCounts), "Object.keys(textureLoadCounts)").to.have.lengthOf(Object.keys(expectedTextureLoadCounts).length);
+                expect(Object.keys(textureCounts), "Object.keys(textureCounts)").to.have.lengthOf(Object.keys(expectedTextureLoadCounts).length);
                 for (const textureName in expectedTextureLoadCounts) {
-                    expect(textureLoadCounts, "textureLoadCounts").to.have.property(textureName, expectedTextureLoadCounts[textureName]);
+                    expect(textureCounts, "textureCounts").to.have.property(textureName, expectedTextureLoadCounts[textureName]);
                 }
             });
 

+ 104 - 33
tests/unit/babylon/src/Tools/babylon.promise.tests.ts

@@ -45,13 +45,16 @@ describe('Babylon.Promise', function () {
             }).catch(() => {
                 tempString += " to check promises";
             }).then(() => {
-                expect(tempString).to.eq("Initial message to check promises");
-                done();
+                try {
+                    expect(tempString).to.eq("Initial message to check promises");
+                    done();
+                }
+                catch (error) {
+                    done(error);
+                }
             });
         });
-    });
 
-    describe('#Composition', () => {
         it('should chain promises correctly #2', (done) => {
             var tempString = "";
             var p1 = new Promise((resolve, reject) => {
@@ -64,13 +67,16 @@ describe('Babylon.Promise', function () {
             }).catch(() => {
                 tempString += " wrong!";
             }).then(() => {
-                expect(tempString).to.eq("Initial message to check promises");
-                done();
+                try {
+                    expect(tempString).to.eq("Initial message to check promises");
+                    done();
+                }
+                catch (error) {
+                    done(error);
+                }
             });
         });
-    });
 
-    describe('#Delayed', () => {
         it('should chain promises correctly #3', (done) => {
             var tempString = "";
             function resolveLater(resolve, reject) {
@@ -105,10 +111,32 @@ describe('Babylon.Promise', function () {
                 tempString += 'resolved ' + v;
             }, function (e) {
                 tempString += 'rejected ' + e; // "rejected", 20
-                expect(tempString).to.eq("resolved 10rejected 20");
-                done();
+                try {
+                    expect(tempString).to.eq("resolved 10rejected 20");
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
             });
         });
+
+        it('should chain promises correctly #4', (done) => {
+            var tempString = "first";
+            var promise = Promise.resolve().then(() => {
+                tempString += " third";
+            }).then(() => {
+                try {
+                    expect(tempString).to.eq("first second third");
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
+            });
+
+            tempString += " second";
+        });
     });
 
     describe('#Promise.all', () => {
@@ -120,8 +148,13 @@ describe('Babylon.Promise', function () {
             var promise3 = Promise.resolve(42);
 
             Promise.all([promise1, promise2, promise3]).then(function (values) {
-                values.should.deep.equal([3, "foo", 42]);
-                done();
+                try {
+                    values.should.deep.equal([3, "foo", 42]);
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
             });
         });
     });
@@ -132,8 +165,13 @@ describe('Babylon.Promise', function () {
                 .then(number => { return number + 1; })
                 .then(number => { return number + 1; })
                 .then(number => {
-                    number.should.be.equal(3);
-                    done();
+                    try {
+                        number.should.be.equal(3);
+                        done();
+                    }
+                    catch(error) {
+                        done(error);
+                    }
                 });
         });
     });
@@ -144,21 +182,21 @@ describe('Babylon.Promise', function () {
             var promise1 = BABYLON.Tools.DelayAsync(500).then(() => successValue);
 
             var sum = 0;
-            promise1.then(function (value) {
+            var checkDone = (value: string) => {
                 sum++;
                 if (sum === 2) {
-                    expect(value).to.equal(successValue);
-                    done();
+                    try {
+                        expect(value).to.equal(successValue);
+                        done();
+                    }
+                    catch (error) {
+                        done(error);
+                    }
                 }
-            });
+            };
 
-            promise1.then(function (value) {
-                sum++;
-                if (sum === 2) {
-                    expect(value).to.equal(successValue);
-                    done();
-                }
-            });
+            promise1.then(checkDone);
+            promise1.then(checkDone);
         });
     });
 
@@ -169,8 +207,13 @@ describe('Babylon.Promise', function () {
             });
 
             promise.then(function (value) {
-                expect(value).to.equal(1);
-                done();
+                try {
+                    expect(value).to.equal(1);
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
             });
         });
     });
@@ -188,9 +231,14 @@ describe('Babylon.Promise', function () {
                 });
                 return promise;
             }).then(function () {
-                expect(callback1Count).to.equal(1);
-                expect(callback2Count).to.equal(1);
-                done();
+                try {
+                    expect(callback1Count).to.equal(1);
+                    expect(callback2Count).to.equal(1);
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
             });
         });
     });
@@ -201,9 +249,32 @@ describe('Babylon.Promise', function () {
             var promise = new Promise((resolve, reject) => {
                 throw new Error(errorValue);
             }).catch(error => {
-                expect(error.constructor).to.equal(Error);
-                expect(error.message).to.equal(errorValue);
-                done();
+                try {
+                    expect(error.constructor).to.equal(Error);
+                    expect(error.message).to.equal(errorValue);
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
+            });
+        });
+
+        it('should correctly handle exceptions in a reject handler', (done) => {
+            var errorValue = 'Failed!';
+            var promise = new Promise((resolve, reject) => {
+                throw new Error(errorValue);
+            }).catch(error => {
+                throw error;
+            }).catch(error => {
+                try {
+                    expect(error.constructor).to.equal(Error);
+                    expect(error.message).to.equal(errorValue);
+                    done();
+                }
+                catch(error) {
+                    done(error);
+                }
             });
         });
     });

BIN
tests/validation/ReferenceImages/GlowLayer.png


BIN
tests/validation/ReferenceImages/depthOfField.png


BIN
tests/validation/ReferenceImages/local cubemaps.png


+ 12 - 2
tests/validation/config.json

@@ -256,7 +256,7 @@
     },
     {
       "title": "Depth of field",
-      "playgroundId": "#IDSQK2#16",
+      "playgroundId": "#IDSQK2#21",
       "renderCount": 20,
       "referenceImage": "depthOfField.png"
     },
@@ -303,7 +303,7 @@
     },
     {
       "title": "PBR",
-      "playgroundId": "#LCA0Q4",
+      "playgroundId": "#LCA0Q4#0",
       "referenceImage": "pbr.png"
     },
     {
@@ -365,6 +365,16 @@
       "title": "Light Projection Texture",
       "playgroundId": "#CQNGRK",
       "referenceImage": "LightProjectionTexture.png"
+    },
+    {
+      "title": "GlowLayer",
+      "playgroundId": "#LRFB2D#1",
+      "referenceImage": "GlowLayer.png"
+    },
+    {
+      "title": "Local cubemaps",
+      "playgroundId": "#RNASML#4",
+      "referenceImage": "local cubemaps.png"
     }
   ]
 }