Browse Source

Merge branch 'master' into subEmitter2

David Catuhe 7 years ago
parent
commit
912009b4bb
100 changed files with 36550 additions and 38839 deletions
  1. 10425 10243
      Playground/babylon.d.txt
  2. 2 0
      Playground/scenes/TwoQuads/TwoQuads.gltf
  3. 31 3
      Tools/Gulp/config.json
  4. 1 1
      Tools/Gulp/gulp-addModuleExports.js
  5. 8 7
      Viewer/package.json
  6. 5 0
      Viewer/src/configuration/configuration.ts
  7. 6 3
      Viewer/src/configuration/mappers.ts
  8. 2 3
      Viewer/src/index.ts
  9. 167 0
      Viewer/src/model/modelAnimation.ts
  10. 142 0
      Viewer/src/model/viewerModel.ts
  11. 9 112
      Viewer/src/viewer/defaultViewer.ts
  12. 50 63
      Viewer/src/viewer/viewer.ts
  13. 6872 6769
      dist/preview release/babylon.d.ts
  14. 56 56
      dist/preview release/babylon.js
  15. 827 534
      dist/preview release/babylon.max.js
  16. 57 57
      dist/preview release/babylon.worker.js
  17. 10827 10724
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  18. 57 57
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  19. 952 571
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  20. 953 572
      dist/preview release/customConfigurations/minimalGLTFViewer/es6.js
  21. 828 535
      dist/preview release/es6.js
  22. 3 3
      dist/preview release/gui/babylon.gui.min.js
  23. 4 4
      dist/preview release/inspector/babylon.inspector.bundle.js
  24. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  25. 15 2
      dist/preview release/loaders/babylon.glTF1FileLoader.d.ts
  26. 27 0
      dist/preview release/loaders/babylon.glTF1FileLoader.js
  27. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  28. 24 10
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  29. 142 93
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  30. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  31. 25 10
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  32. 143 93
      dist/preview release/loaders/babylon.glTFFileLoader.js
  33. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  34. 1 1
      dist/preview release/loaders/babylon.objFileLoader.min.js
  35. 143 93
      dist/preview release/loaders/babylonjs.loaders.js
  36. 3 3
      dist/preview release/loaders/babylonjs.loaders.min.js
  37. 25 10
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  38. 4 282
      dist/preview release/materialsLibrary/babylon.customMaterial.d.ts
  39. 19 1908
      dist/preview release/materialsLibrary/babylon.customMaterial.js
  40. 1 2
      dist/preview release/materialsLibrary/babylon.customMaterial.min.js
  41. 1 1
      dist/preview release/materialsLibrary/babylon.furMaterial.js
  42. 1 1
      dist/preview release/materialsLibrary/babylon.furMaterial.min.js
  43. 1 1
      dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js
  44. 1 1
      dist/preview release/materialsLibrary/babylon.waterMaterial.min.js
  45. 23 1907
      dist/preview release/materialsLibrary/babylonjs.materials.js
  46. 5 7
      dist/preview release/materialsLibrary/babylonjs.materials.min.js
  47. 4 282
      dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts
  48. 1 1
      dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js
  49. 1 1
      dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js
  50. 1 1
      dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js
  51. 86 29
      dist/preview release/serializers/babylon.glTF2Serializer.d.ts
  52. 385 158
      dist/preview release/serializers/babylon.glTF2Serializer.js
  53. 1 1
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  54. 385 158
      dist/preview release/serializers/babylonjs.serializers.js
  55. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  56. 86 29
      dist/preview release/serializers/babylonjs.serializers.module.d.ts
  57. 2 132
      dist/preview release/typedocValidationBaseline.json
  58. 80 24
      dist/preview release/viewer/babylon.viewer.d.ts
  59. 69 69
      dist/preview release/viewer/babylon.viewer.js
  60. 1310 700
      dist/preview release/viewer/babylon.viewer.max.js
  61. 7 3
      dist/preview release/what's new.md
  62. 1 0
      loaders/src/glTF/1.0/babylon.glTFLoader.ts
  63. 4 4
      loaders/src/glTF/2.0/Extensions/KHR_lights.ts
  64. 16 19
      loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts
  65. 18 5
      loaders/src/glTF/2.0/Extensions/MSFT_lod.ts
  66. 167 149
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  67. 4 4
      loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts
  68. 28 2
      loaders/src/glTF/babylon.glTFFileLoader.ts
  69. 35 1973
      materialsLibrary/src/custom/babylon.customMaterial.ts
  70. 0 4
      materialsLibrary/src/fur/fur.vertex.fx
  71. 4 4
      serializers/src/glTF/2.0/babylon.glTFExporter.ts
  72. 475 158
      serializers/src/glTF/2.0/babylon.glTFMaterial.ts
  73. 27 1
      src/Animations/babylon.animationGroup.ts
  74. 1 0
      src/Behaviors/Cameras/babylon.framingBehavior.ts
  75. 1 0
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  76. 11 7
      src/Cameras/babylon.arcRotateCamera.ts
  77. 7 2
      src/Cameras/babylon.camera.ts
  78. 2 1
      src/Engine/babylon.engine.ts
  79. 13 2
      src/Helpers/babylon.environmentHelper.ts
  80. 5 4
      src/Helpers/babylon.videoDome.ts
  81. 8 6
      src/Lights/Shadows/babylon.shadowGenerator.ts
  82. 5 3
      src/Lights/babylon.light.ts
  83. 9 0
      src/Materials/Textures/babylon.renderTargetTexture.ts
  84. 53 0
      src/Materials/babylon.effect.ts
  85. 1 0
      src/Materials/babylon.material.ts
  86. 19 3
      src/Math/babylon.math.ts
  87. 4 0
      src/Mesh/Compression/babylon.dracoCompression.ts
  88. 9 5
      src/Mesh/babylon.abstractMesh.ts
  89. 2 2
      src/Mesh/babylon.instancedMesh.ts
  90. 4 4
      src/Mesh/babylon.mesh.ts
  91. 13 20
      src/Mesh/babylon.transformNode.ts
  92. 16 3
      src/Particles/babylon.gpuParticleSystem.ts
  93. 109 40
      src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts
  94. 25 6
      src/PostProcess/babylon.blurPostProcess.ts
  95. 3 2
      src/PostProcess/babylon.chromaticAberrationPostProcess.ts
  96. 23 9
      src/PostProcess/babylon.circleOfConfusionPostProcess.ts
  97. 3 2
      src/PostProcess/babylon.depthOfFieldBlurPostProcess.ts
  98. 87 52
      src/PostProcess/babylon.depthOfFieldEffect.ts
  99. 21 2
      src/PostProcess/babylon.depthOfFieldMergePostProcess.ts
  100. 0 0
      src/PostProcess/babylon.imageProcessingPostProcess.ts

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


+ 2 - 0
Playground/scenes/TwoQuads/TwoQuads.gltf

@@ -121,6 +121,7 @@
   ],
   ],
   "nodes": [
   "nodes": [
     {
     {
+      "name": "node0",
       "mesh": 0,
       "mesh": 0,
       "translation": [
       "translation": [
         -0.55,
         -0.55,
@@ -129,6 +130,7 @@
       ]
       ]
     },
     },
     {
     {
+      "name": "node1",
       "mesh": 0,
       "mesh": 0,
       "translation": [
       "translation": [
         0.55,
         0.55,

+ 31 - 3
Tools/Gulp/config.json

@@ -744,7 +744,8 @@
                 "../../src/PostProcess/babylon.depthOfFieldBlurPostProcess.js"
                 "../../src/PostProcess/babylon.depthOfFieldBlurPostProcess.js"
             ],
             ],
             "dependUpon": [
             "dependUpon": [
-                "postProcesses"
+                "postProcesses",
+                "additionalPostProcess_blur"
             ],
             ],
             "shaders": [
             "shaders": [
                 "kernelBlur.vertex",
                 "kernelBlur.vertex",
@@ -768,6 +769,28 @@
                 "circleOfConfusion.fragment"
                 "circleOfConfusion.fragment"
             ]
             ]
         },
         },
+        "additionalPostProcess_sharpen": {
+            "files": [
+                "../../src/PostProcess/babylon.sharpenPostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "sharpen.fragment"
+            ]
+        },
+        "additionalPostProcess_chromaticAberration": {
+            "files": [
+                "../../src/PostProcess/babylon.chromaticAberrationPostProcess.js"
+            ],
+            "dependUpon": [
+                "postProcesses"
+            ],
+            "shaders": [
+                "chromaticAberration.fragment"
+            ]
+        },
         "additionalPostProcess_depthOfFieldMerge": {
         "additionalPostProcess_depthOfFieldMerge": {
             "files": [
             "files": [
                 "../../src/PostProcess/babylon.depthOfFieldMergePostProcess.js"
                 "../../src/PostProcess/babylon.depthOfFieldMergePostProcess.js"
@@ -782,6 +805,11 @@
         "additionalPostProcess_depthOfFieldEffect": {
         "additionalPostProcess_depthOfFieldEffect": {
             "files": [
             "files": [
                 "../../src/PostProcess/babylon.depthOfFieldEffect.js"
                 "../../src/PostProcess/babylon.depthOfFieldEffect.js"
+            ],
+            "dependUpon": [
+                "additionalPostProcess_depthOfFieldBlur",
+                "additionalPostProcess_depthOfFieldMerge",
+                "additionalPostProcess_circleOfConfusion"
             ]
             ]
         },
         },
         "additionalPostProcess_fxaa": {
         "additionalPostProcess_fxaa": {
@@ -892,8 +920,8 @@
             "dependUpon": [
             "dependUpon": [
                 "renderingPipeline",
                 "renderingPipeline",
                 "additionalPostProcess_fxaa",
                 "additionalPostProcess_fxaa",
-                "additionalPostProcess_circleOfConfusion",
-                "additionalPostProcess_depthOfFieldMerge",
+                "additionalPostProcess_chromaticAberration",
+                "additionalPostProcess_sharpen",
                 "additionalPostProcess_depthOfFieldEffect"
                 "additionalPostProcess_depthOfFieldEffect"
             ]
             ]
         },
         },

+ 1 - 1
Tools/Gulp/gulp-addModuleExports.js

@@ -25,7 +25,7 @@ module.exports = function (varName, subModule, extendsRoot, externalUsingBabylon
     if(typeof exports === 'object' && typeof module === 'object')
     if(typeof exports === 'object' && typeof module === 'object')
         module.exports = factory(${subModule || extendsRoot ? 'require("babylonjs")' : ''});
         module.exports = factory(${subModule || extendsRoot ? 'require("babylonjs")' : ''});
     else if(typeof define === 'function' && define.amd)
     else if(typeof define === 'function' && define.amd)
-        define("${varName.module}", ${subModule || extendsRoot ? '["babylonjs"],' : ''} factory);
+        define("${varName.module}", ${subModule || extendsRoot ? '["babylonjs"],' : '[],'} factory);
     else if(typeof exports === 'object')
     else if(typeof exports === 'object')
         exports["${varName.module}"] = factory(${subModule || extendsRoot ? 'require("babylonjs")' : ''});
         exports["${varName.module}"] = factory(${subModule || extendsRoot ? 'require("babylonjs")' : ''});
     else {
     else {

+ 8 - 7
Viewer/package.json

@@ -23,14 +23,15 @@
     },
     },
     "homepage": "https://github.com/BabylonJS/Babylon.js#readme",
     "homepage": "https://github.com/BabylonJS/Babylon.js#readme",
     "devDependencies": {
     "devDependencies": {
-        "@types/node": "^8.5.8",
+        "@types/node": "^8.9.4",
         "base64-image-loader": "^1.2.1",
         "base64-image-loader": "^1.2.1",
-        "html-loader": "^0.5.4",
+        "html-loader": "^0.5.5",
         "json-loader": "^0.5.7",
         "json-loader": "^0.5.7",
         "ts-loader": "^2.3.7",
         "ts-loader": "^2.3.7",
-        "typescript": "^2.6.2",
-        "uglifyjs-webpack-plugin": "^1.1.6",
-        "webpack": "^3.10.0",
-        "webpack-dev-server": "^2.11.0"
-    }
+        "typescript": "^2.7.2",
+        "uglifyjs-webpack-plugin": "^1.2.2",
+        "webpack": "^3.11.0",
+        "webpack-dev-server": "^2.11.2"
+    },
+    "dependencies": {}
 }
 }

+ 5 - 0
Viewer/src/configuration/configuration.ts

@@ -85,6 +85,11 @@ export interface IModelConfiguration {
     subtitle?: string;
     subtitle?: string;
     thumbnail?: string; // URL or data-url
     thumbnail?: string; // URL or data-url
 
 
+    animation?: {
+        autoStart?: boolean | string;
+        playOnce?: boolean;
+    }
+
     // [propName: string]: any; // further configuration, like title and creator
     // [propName: string]: any; // further configuration, like title and creator
 }
 }
 
 

+ 6 - 3
Viewer/src/configuration/mappers.ts

@@ -26,9 +26,12 @@ class HTMLMapper implements IMapper {
                     } else if (val === "false") {
                     } else if (val === "false") {
                         val = false;
                         val = false;
                     } else {
                     } else {
-                        let number = parseFloat(val);
-                        if (!isNaN(number)) {
-                            val = number;
+                        var isnum = /^\d+$/.test(val);
+                        if (isnum) {
+                            let number = parseFloat(val);
+                            if (!isNaN(number)) {
+                                val = number;
+                            }
                         }
                         }
                     }
                     }
                     currentConfig[camelKey] = val;
                     currentConfig[camelKey] = val;

+ 2 - 3
Viewer/src/index.ts

@@ -9,13 +9,12 @@ import { AbstractViewer } from './viewer/viewer';
  * An HTML-Based viewer for 3D models, based on BabylonJS and its extensions.
  * An HTML-Based viewer for 3D models, based on BabylonJS and its extensions.
  */
  */
 
 
+import { PromisePolyfill } from 'babylonjs';
 
 
-// load babylon and needed modules.
-import 'babylonjs';
+// load needed modules.
 import 'babylonjs-loaders';
 import 'babylonjs-loaders';
 import '../assets/pep.min';
 import '../assets/pep.min';
 
 
-import { PromisePolyfill } from 'babylonjs';
 
 
 import { InitTags } from './initializer';
 import { InitTags } from './initializer';
 
 

+ 167 - 0
Viewer/src/model/modelAnimation.ts

@@ -0,0 +1,167 @@
+import { AnimationGroup, Animatable, Skeleton, IDisposable } from "babylonjs";
+
+export enum AnimationPlayMode {
+    ONCE,
+    LOOP
+}
+
+export enum AnimationState {
+    INIT,
+    PLAYING,
+    PAUSED,
+    STOPPED,
+    ENDED
+}
+
+export interface IModelAnimation extends IDisposable {
+    readonly state: AnimationState;
+    readonly name: string;
+    readonly frames: number;
+    readonly currentFrame: number;
+    readonly fps: number;
+    speedRatio: number;
+    playMode: AnimationPlayMode;
+    start();
+    stop();
+    pause();
+    reset();
+    restart();
+    goToFrame(frameNumber: number);
+}
+
+export class GroupModelAnimation implements IModelAnimation {
+
+    private _playMode: AnimationPlayMode;
+    private _state: AnimationState;
+
+    constructor(private _animationGroup: AnimationGroup) {
+        this._state = AnimationState.INIT;
+        this._playMode = AnimationPlayMode.LOOP;
+
+        this._animationGroup.onAnimationEndObservable.add(() => {
+            this.stop();
+            this._state = AnimationState.ENDED;
+        })
+    }
+
+    public get name() {
+        return this._animationGroup.name;
+    }
+
+    public get state() {
+        return this._state;
+    }
+
+    /**
+     * Gets or sets the speed ratio to use for all animations
+     */
+    public get speedRatio(): number {
+        return this._animationGroup.speedRatio;
+    }
+
+    /**
+     * Gets or sets the speed ratio to use for all animations
+     */
+    public set speedRatio(value: number) {
+        this._animationGroup.speedRatio = value;
+    }
+
+    public get frames(): number {
+        let animationFrames = this._animationGroup.targetedAnimations.map(ta => {
+            let keys = ta.animation.getKeys();
+            return keys[keys.length - 1].frame;
+        });
+        return Math.max.apply(null, animationFrames);
+    }
+
+    public get currentFrame(): number {
+        // get the first currentFrame found
+        for (let i = 0; i < this._animationGroup.animatables.length; ++i) {
+            let animatable: Animatable = this._animationGroup.animatables[i];
+            let animations = animatable.getAnimations();
+            if (!animations || !animations.length) {
+                continue;
+            }
+            for (let idx = 0; idx < animations.length; ++idx) {
+                if (animations[idx].currentFrame) {
+                    return animations[idx].currentFrame;
+                }
+            }
+        }
+        return 0;
+    }
+
+    public get fps(): number {
+        // get the first currentFrame found
+        for (let i = 0; i < this._animationGroup.animatables.length; ++i) {
+            let animatable: Animatable = this._animationGroup.animatables[i];
+            let animations = animatable.getAnimations();
+            if (!animations || !animations.length) {
+                continue;
+            }
+            for (let idx = 0; idx < animations.length; ++idx) {
+                if (animations[idx].animation && animations[idx].animation.framePerSecond) {
+                    return animations[idx].animation.framePerSecond;
+                }
+            }
+        }
+        return 0;
+    }
+
+    public get playMode(): AnimationPlayMode {
+        return this._playMode;
+    }
+
+    public set playMode(value: AnimationPlayMode) {
+        if (value === this._playMode) {
+            return;
+        }
+
+        this._playMode = value;
+
+        if (this.state === AnimationState.PLAYING) {
+            this._animationGroup.play(this._playMode === AnimationPlayMode.LOOP);
+        } else {
+            this._animationGroup.reset();
+            this._state = AnimationState.INIT;
+        }
+    }
+
+    reset() {
+        this._animationGroup.reset();
+    }
+
+    restart() {
+        this._animationGroup.restart();
+    }
+
+    goToFrame(frameNumber: number) {
+        // this._animationGroup.goToFrame(frameNumber);
+        this._animationGroup['_animatables'].forEach(a => {
+            a.goToFrame(frameNumber);
+        })
+    }
+
+    public start() {
+        this._animationGroup.start(this.playMode === AnimationPlayMode.LOOP, this.speedRatio);
+        if (this._animationGroup.isStarted) {
+            this._state = AnimationState.PLAYING;
+        }
+    }
+
+    pause() {
+        this._animationGroup.pause();
+        this._state = AnimationState.PAUSED;
+    }
+
+    public stop() {
+        this._animationGroup.stop();
+        if (!this._animationGroup.isStarted) {
+            this._state = AnimationState.STOPPED;
+        }
+    }
+
+    public dispose() {
+        this._animationGroup.dispose();
+    }
+}

+ 142 - 0
Viewer/src/model/viewerModel.ts

@@ -0,0 +1,142 @@
+import { ISceneLoaderPlugin, ISceneLoaderPluginAsync, AnimationGroup, Animatable, AbstractMesh, Tools, Scene, SceneLoader, Observable, SceneLoaderProgressEvent, Tags, ParticleSystem, Skeleton, IDisposable, Nullable, Animation } from "babylonjs";
+import { IModelConfiguration } from "../configuration/configuration";
+import { IModelAnimation, GroupModelAnimation, AnimationPlayMode } from "./modelAnimation";
+
+export class ViewerModel implements IDisposable {
+
+    public loader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
+
+    private _animations: Array<IModelAnimation>;
+    public meshes: Array<AbstractMesh>;
+    public particleSystems: Array<ParticleSystem>;
+    public skeletons: Array<Skeleton>;
+    public currentAnimation: IModelAnimation;
+
+    public onLoadedObservable: Observable<ViewerModel>;
+    public onLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
+    public onLoadErrorObservable: Observable<{ message: string; exception: any }>;
+
+    constructor(private _modelConfiguration: IModelConfiguration, private _scene: Scene, disableAutoLoad = false) {
+        this.onLoadedObservable = new Observable();
+        this.onLoadErrorObservable = new Observable();
+        this.onLoadProgressObservable = new Observable();
+
+        this._animations = [];
+
+        if (!disableAutoLoad) {
+            this._initLoad();
+        }
+    }
+
+    public load() {
+        if (this.loader) {
+            Tools.Error("Model was already loaded or in the process of loading.");
+        } else {
+            this._initLoad();
+        }
+    }
+
+    //public getAnimations() {
+    //    return this._animations;
+    //}
+
+    public getAnimationNames() {
+        return this._animations.map(a => a.name);
+    }
+
+    protected _getAnimationByName(name: string): Nullable<IModelAnimation> {
+        // can't use .find, noe available on IE
+        let filtered = this._animations.filter(a => a.name === name);
+        // what the next line means - if two animations have the same name, they will not be returned!
+        if (filtered.length === 1) {
+            return filtered[0];
+        } else {
+            return null;
+        }
+    }
+
+    public playAnimation(name: string): IModelAnimation {
+        let animation = this._getAnimationByName(name);
+        if (animation) {
+            if (this.currentAnimation) {
+                this.currentAnimation.stop();
+            }
+            this.currentAnimation = animation;
+            animation.start();
+            return animation;
+        } else {
+            throw new Error("animation not found - " + name);
+        }
+    }
+
+    private _initLoad() {
+        if (!this._modelConfiguration || !this._modelConfiguration.url) {
+            return Tools.Error("No model URL to load.");
+        }
+        let parts = this._modelConfiguration.url.split('/');
+        let filename = parts.pop() || this._modelConfiguration.url;
+        let base = parts.length ? parts.join('/') + '/' : './';
+
+        let plugin = this._modelConfiguration.loader;
+
+        //temp solution for animation group handling
+        let animationsArray = this._scene.animationGroups.slice();
+
+        this.loader = SceneLoader.ImportMesh(undefined, base, filename, this._scene, (meshes, particleSystems, skeletons) => {
+            meshes.forEach(mesh => {
+                Tags.AddTagsTo(mesh, "viewerMesh");
+            });
+            this.meshes = meshes;
+            this.particleSystems = particleSystems;
+            this.skeletons = skeletons;
+
+            // check if this is a gltf loader and load the animations
+            if (this.loader.name === 'gltf') {
+                this._scene.animationGroups.forEach(ag => {
+                    // add animations that didn't exist before
+                    if (animationsArray.indexOf(ag) === -1) {
+                        this._animations.push(new GroupModelAnimation(ag));
+                    }
+                })
+            } else {
+                skeletons.forEach((skeleton, idx) => {
+                    let ag = new BABYLON.AnimationGroup("animation-" + idx, this._scene);
+                    skeleton.getAnimatables().forEach(a => {
+                        if (a.animations[0]) {
+                            ag.addTargetedAnimation(a.animations[0], a);
+                        }
+                    });
+                    this._animations.push(new GroupModelAnimation(ag));
+                });
+            }
+
+            if (this._modelConfiguration.animation) {
+                if (this._modelConfiguration.animation.playOnce) {
+                    this._animations.forEach(a => {
+                        a.playMode = AnimationPlayMode.ONCE;
+                    });
+                }
+                if (this._modelConfiguration.animation.autoStart && this._animations.length) {
+                    let animationName = this._modelConfiguration.animation.autoStart === true ?
+                        this._animations[0].name : this._modelConfiguration.animation.autoStart;
+                    this.playAnimation(animationName);
+                }
+            }
+
+            this.onLoadedObservable.notifyObserversWithPromise(this);
+        }, (progressEvent) => {
+            this.onLoadProgressObservable.notifyObserversWithPromise(progressEvent);
+        }, (e, m, exception) => {
+            this.onLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception });
+        }, plugin)!;
+
+        this.loader['animationStartMode'] = 0;
+    }
+
+    public dispose() {
+        this.particleSystems.forEach(ps => ps.dispose());
+        this.skeletons.forEach(s => s.dispose());
+        this._animations.forEach(ag => ag.dispose());
+        this.meshes.forEach(m => m.dispose());
+    }
+}

+ 9 - 112
Viewer/src/viewer/defaultViewer.ts

@@ -5,6 +5,7 @@ import { Template, EventCallback } from './../templateManager';
 import { AbstractViewer } from './viewer';
 import { AbstractViewer } from './viewer';
 import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight } from 'babylonjs';
 import { SpotLight, MirrorTexture, Plane, ShadowGenerator, Texture, BackgroundMaterial, Observable, ShadowLight, CubeTexture, BouncingBehavior, FramingBehavior, Behavior, Light, Engine, Scene, AutoRotationBehavior, AbstractMesh, Quaternion, StandardMaterial, ArcRotateCamera, ImageProcessingConfiguration, Color3, Vector3, SceneLoader, Mesh, HemisphericLight } from 'babylonjs';
 import { CameraBehavior } from '../interfaces';
 import { CameraBehavior } from '../interfaces';
+import { ViewerModel } from '../model/viewerModel';
 
 
 export class DefaultViewer extends AbstractViewer {
 export class DefaultViewer extends AbstractViewer {
 
 
@@ -103,8 +104,8 @@ export class DefaultViewer extends AbstractViewer {
         this.containerElement.style.display = 'flex';
         this.containerElement.style.display = 'flex';
     }
     }
 
 
-    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
-        super.configureModel(modelConfiguration, focusMeshes);
+    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, model: ViewerModel) {
+        super.configureModel(modelConfiguration, model);
 
 
         let navbar = this.templateManager.getTemplate('navBar');
         let navbar = this.templateManager.getTemplate('navBar');
         if (!navbar) return;
         if (!navbar) return;
@@ -131,17 +132,17 @@ export class DefaultViewer extends AbstractViewer {
         }
         }
     }
     }
 
 
-    public loadModel(model: any = this.configuration.model): Promise<Scene> {
+    public loadModel(model: any = this.configuration.model): Promise<ViewerModel> {
         this.showLoadingScreen();
         this.showLoadingScreen();
         return super.loadModel(model, true).catch((error) => {
         return super.loadModel(model, true).catch((error) => {
             console.log(error);
             console.log(error);
             this.hideLoadingScreen();
             this.hideLoadingScreen();
             this.showOverlayScreen('error');
             this.showOverlayScreen('error');
-            return this.scene;
+            return Promise.reject(error);
         });
         });
     }
     }
 
 
-    private onModelLoaded = (meshes: Array<AbstractMesh>) => {
+    private onModelLoaded = (model: ViewerModel) => {
         // with a short timeout, making sure everything is there already.
         // with a short timeout, making sure everything is there already.
         let hideLoadingDelay = 500;
         let hideLoadingDelay = 500;
         if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
         if (this.configuration.lab && this.configuration.lab.hideLoadingDelay !== undefined) {
@@ -151,113 +152,9 @@ export class DefaultViewer extends AbstractViewer {
             this.hideLoadingScreen();
             this.hideLoadingScreen();
         }, hideLoadingDelay);
         }, hideLoadingDelay);
 
 
-        meshes[0].rotation.y += Math.PI;
-
-        return; //this.initEnvironment(meshes);
+        return;
     }
     }
 
 
-    /*protected initEnvironment(focusMeshes: Array<AbstractMesh> = []): Promise<Scene> {
-        if (this.configuration.skybox) {
-            // Define a general environment textue
-            let texture;
-            // this is obligatory, but still - making sure it is there.
-            if (this.configuration.skybox.cubeTexture) {
-                if (typeof this.configuration.skybox.cubeTexture.url === 'string') {
-                    texture = CubeTexture.CreateFromPrefilteredData(this.configuration.skybox.cubeTexture.url, this.scene);
-                } else {
-                    texture = CubeTexture.CreateFromImages(this.configuration.skybox.cubeTexture.url, this.scene, this.configuration.skybox.cubeTexture.noMipMap);
-                }
-            }
-            if (texture) {
-                this.extendClassWithConfig(texture, this.configuration.skybox.cubeTexture);
-
-                let scale = this.configuration.skybox.scale || this.scene.activeCamera && (this.scene.activeCamera.maxZ - this.scene.activeCamera.minZ) / 2 || 1;
-
-                let box = this.scene.createDefaultSkybox(texture, this.configuration.skybox.pbr, scale, this.configuration.skybox.blur);
-
-                // before extending, set the material's imageprocessing configuration object, if needed:
-                if (this.configuration.skybox.material && this.configuration.skybox.material.imageProcessingConfiguration && box) {
-                    (<StandardMaterial>box.material).imageProcessingConfiguration = new ImageProcessingConfiguration();
-                }
-
-                this.extendClassWithConfig(box, this.configuration.skybox);
-
-                box && focusMeshes.push(box);
-            }
-        }
-
-        if (this.configuration.ground) {
-            let groundConfig = (typeof this.configuration.ground === 'boolean') ? {} : this.configuration.ground;
-
-            let groundSize = groundConfig.size || (this.configuration.skybox && this.configuration.skybox.scale) || 3000;
-
-            let ground = Mesh.CreatePlane("BackgroundPlane", groundSize, this.scene);
-            let backgroundMaterial = new BackgroundMaterial('groundmat', this.scene);
-            ground.rotation.x = Math.PI / 2; // Face up by default.
-            ground.receiveShadows = groundConfig.receiveShadows || false;
-
-            // position the ground correctly
-            let groundPosition = focusMeshes[0].getHierarchyBoundingVectors().min.y;
-            ground.position.y = groundPosition;
-
-            // default values
-            backgroundMaterial.alpha = 0.9;
-            backgroundMaterial.alphaMode = Engine.ALPHA_PREMULTIPLIED_PORTERDUFF;
-            backgroundMaterial.shadowLevel = 0.5;
-            backgroundMaterial.primaryLevel = 1;
-            backgroundMaterial.primaryColor = new Color3(0.2, 0.2, 0.3).toLinearSpace().scale(3);
-            backgroundMaterial.secondaryLevel = 0;
-            backgroundMaterial.tertiaryLevel = 0;
-            backgroundMaterial.useRGBColor = false;
-            backgroundMaterial.enableNoise = true;
-
-            // if config provided, extend the default values
-            if (groundConfig.material) {
-                this.extendClassWithConfig(ground, ground.material);
-            }
-
-            ground.material = backgroundMaterial;
-            if (this.configuration.ground === true || groundConfig.shadowOnly) {
-                // shadow only:
-                ground.receiveShadows = true;
-                const diffuseTexture = new Texture("https://assets.babylonjs.com/environments/backgroundGround.png", this.scene);
-                diffuseTexture.gammaSpace = false;
-                diffuseTexture.hasAlpha = true;
-                backgroundMaterial.diffuseTexture = diffuseTexture;
-            } else if (groundConfig.mirror) {
-                var mirror = new MirrorTexture("mirror", 512, this.scene);
-                mirror.mirrorPlane = new Plane(0, -1, 0, 0);
-                mirror.renderList = mirror.renderList || [];
-                focusMeshes.length && focusMeshes.forEach(m => {
-                    m && mirror.renderList && mirror.renderList.push(m);
-                });
-
-                backgroundMaterial.reflectionTexture = mirror;
-            } else {
-                if (groundConfig.material) {
-                    if (groundConfig.material.diffuseTexture) {
-                        const diffuseTexture = new Texture(groundConfig.material.diffuseTexture, this.scene);
-                        backgroundMaterial.diffuseTexture = diffuseTexture;
-                    }
-                }
-                // ground.material = new StandardMaterial('groundmat', this.scene);
-            }
-            //default configuration
-            if (this.configuration.ground === true) {
-                ground.receiveShadows = true;
-                if (ground.material)
-                    ground.material.alpha = 0.4;
-            }
-
-
-
-
-            this.extendClassWithConfig(ground, groundConfig);
-        }
-
-        return Promise.resolve(this.scene);
-    }*/
-
     public showOverlayScreen(subScreen: string) {
     public showOverlayScreen(subScreen: string) {
         let template = this.templateManager.getTemplate('overlay');
         let template = this.templateManager.getTemplate('overlay');
         if (!template) return Promise.resolve('Overlay template not found');
         if (!template) return Promise.resolve('Overlay template not found');
@@ -345,8 +242,8 @@ export class DefaultViewer extends AbstractViewer {
         }));
         }));
     }
     }
 
 
-    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
-        super.configureLights(lightsConfiguration, focusMeshes);
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model: ViewerModel) {
+        super.configureLights(lightsConfiguration, model);
         // labs feature - flashlight
         // labs feature - flashlight
         if (this.configuration.lab && this.configuration.lab.flashlight) {
         if (this.configuration.lab && this.configuration.lab.flashlight) {
             let pointerPosition = BABYLON.Vector3.Zero();
             let pointerPosition = BABYLON.Vector3.Zero();

+ 50 - 63
Viewer/src/viewer/viewer.ts

@@ -6,6 +6,7 @@ import { ViewerConfiguration, ISceneConfiguration, ISceneOptimizerConfiguration,
 
 
 import * as deepmerge from '../../assets/deepmerge.min.js';
 import * as deepmerge from '../../assets/deepmerge.min.js';
 import { CameraBehavior } from 'src/interfaces';
 import { CameraBehavior } from 'src/interfaces';
+import { ViewerModel } from '../model/viewerModel';
 
 
 export abstract class AbstractViewer {
 export abstract class AbstractViewer {
 
 
@@ -16,12 +17,10 @@ export abstract class AbstractViewer {
     public camera: ArcRotateCamera;
     public camera: ArcRotateCamera;
     public sceneOptimizer: SceneOptimizer;
     public sceneOptimizer: SceneOptimizer;
     public baseId: string;
     public baseId: string;
+    public models: Array<ViewerModel>;
 
 
     /**
     /**
      * The last loader used to load a model. 
      * The last loader used to load a model. 
-     * 
-     * @type {(ISceneLoaderPlugin | ISceneLoaderPluginAsync)}
-     * @memberof AbstractViewer
      */
      */
     public lastUsedLoader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
     public lastUsedLoader: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
 
 
@@ -42,7 +41,7 @@ export abstract class AbstractViewer {
     // observables
     // observables
     public onSceneInitObservable: Observable<Scene>;
     public onSceneInitObservable: Observable<Scene>;
     public onEngineInitObservable: Observable<Engine>;
     public onEngineInitObservable: Observable<Engine>;
-    public onModelLoadedObservable: Observable<AbstractMesh[]>;
+    public onModelLoadedObservable: Observable<ViewerModel>;
     public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
     public onModelLoadProgressObservable: Observable<SceneLoaderProgressEvent>;
     public onModelLoadErrorObservable: Observable<{ message: string; exception: any }>;
     public onModelLoadErrorObservable: Observable<{ message: string; exception: any }>;
     public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
     public onLoaderInitObservable: Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
@@ -69,6 +68,7 @@ export abstract class AbstractViewer {
         this.onLoaderInitObservable = new Observable();
         this.onLoaderInitObservable = new Observable();
 
 
         this.registeredOnBeforerenderFunctions = [];
         this.registeredOnBeforerenderFunctions = [];
+        this.models = [];
 
 
         // add this viewer to the viewer manager
         // add this viewer to the viewer manager
         viewerManager.addViewer(this);
         viewerManager.addViewer(this);
@@ -397,7 +397,9 @@ export abstract class AbstractViewer {
         }
         }
     }
     }
 
 
-    protected configureCamera(cameraConfig: ICameraConfiguration, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    protected configureCamera(cameraConfig: ICameraConfiguration, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
+
         if (!this.scene.activeCamera) {
         if (!this.scene.activeCamera) {
             this.scene.createDefaultCamera(true, true, true);
             this.scene.createDefaultCamera(true, true, true);
             this.camera = <ArcRotateCamera>this.scene.activeCamera!;
             this.camera = <ArcRotateCamera>this.scene.activeCamera!;
@@ -430,7 +432,8 @@ export abstract class AbstractViewer {
             this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
             this.camera.upperRadiusLimit = sceneDiagonalLenght * 3;
     }
     }
 
 
-    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
         // sanity check!
         // sanity check!
         if (!Object.keys(lightsConfiguration).length) return;
         if (!Object.keys(lightsConfiguration).length) return;
 
 
@@ -516,17 +519,10 @@ export abstract class AbstractViewer {
                 }
                 }
             }
             }
         });
         });
-
-        // remove the unneeded lights
-        /*lightsAvailable.forEach(name => {
-            let light = this.scene.getLightByName(name);
-            if (light && !Tags.MatchesQuery(light, "fixed")) {
-                light.dispose();
-            }
-        });*/
     }
     }
 
 
-    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<AbstractMesh> = this.scene.meshes) {
+    protected configureModel(modelConfiguration: Partial<IModelConfiguration>, model?: ViewerModel) {
+        let focusMeshes = model ? model.meshes : this.scene.meshes;
         let meshesWithNoParent: Array<AbstractMesh> = focusMeshes.filter(m => !m.parent);
         let meshesWithNoParent: Array<AbstractMesh> = focusMeshes.filter(m => !m.parent);
         let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
         let updateMeshesWithNoParent = (variable: string, value: any, param?: string) => {
             meshesWithNoParent.forEach(mesh => {
             meshesWithNoParent.forEach(mesh => {
@@ -665,7 +661,7 @@ export abstract class AbstractViewer {
                 return this.onEngineInitObservable.notifyObserversWithPromise(engine);
                 return this.onEngineInitObservable.notifyObserversWithPromise(engine);
             }).then(() => {
             }).then(() => {
                 if (autoLoadModel) {
                 if (autoLoadModel) {
-                    return this.loadModel();
+                    return this.loadModel().then(() => { return this.scene });
                 } else {
                 } else {
                     return this.scene || this.initScene();
                     return this.scene || this.initScene();
                 }
                 }
@@ -673,8 +669,6 @@ export abstract class AbstractViewer {
                 return this.onSceneInitObservable.notifyObserversWithPromise(scene);
                 return this.onSceneInitObservable.notifyObserversWithPromise(scene);
             }).then(() => {
             }).then(() => {
                 return this.onInitDoneObservable.notifyObserversWithPromise(this);
                 return this.onInitDoneObservable.notifyObserversWithPromise(this);
-            }).then(() => {
-                return this;
             });
             });
         })
         })
     }
     }
@@ -747,88 +741,82 @@ export abstract class AbstractViewer {
     private isLoading: boolean;
     private isLoading: boolean;
     private nextLoading: Function;
     private nextLoading: Function;
 
 
-    public loadModel(model: any = this.configuration.model, clearScene: boolean = true): Promise<Scene> {
+    public loadModel(modelConfig: any = this.configuration.model, clearScene: boolean = true): Promise<ViewerModel> {
         // no model was provided? Do nothing!
         // no model was provided? Do nothing!
-        let modelUrl = (typeof model === 'string') ? model : model.url;
+        let modelUrl = (typeof modelConfig === 'string') ? modelConfig : modelConfig.url;
         if (!modelUrl) {
         if (!modelUrl) {
-            return Promise.resolve(this.scene);
+            return Promise.reject("no model configuration found");
         }
         }
         if (this.isLoading) {
         if (this.isLoading) {
             //another model is being model. Wait for it to finish, trigger the load afterwards
             //another model is being model. Wait for it to finish, trigger the load afterwards
-            this.nextLoading = () => {
+            /*this.nextLoading = () => {
                 delete this.nextLoading;
                 delete this.nextLoading;
-                this.loadModel(model, clearScene);
-            }
-            return Promise.resolve(this.scene);
+                return this.loadModel(modelConfig, clearScene);
+            }*/
+            return Promise.reject("sanother model is curently being loaded.");
         }
         }
         this.isLoading = true;
         this.isLoading = true;
-        if ((typeof model === 'string')) {
+        if ((typeof modelConfig === 'string')) {
             if (this.configuration.model && typeof this.configuration.model === 'object') {
             if (this.configuration.model && typeof this.configuration.model === 'object') {
-                this.configuration.model.url = model;
+                this.configuration.model.url = modelConfig;
             }
             }
         } else {
         } else {
             if (this.configuration.model) {
             if (this.configuration.model) {
-                deepmerge(this.configuration.model, model)
+                deepmerge(this.configuration.model, modelConfig)
             } else {
             } else {
-                this.configuration.model = model;
+                this.configuration.model = modelConfig;
             }
             }
         }
         }
 
 
-        let parts = modelUrl.split('/');
-        let filename = parts.pop();
-        let base = parts.join('/') + '/';
-        let plugin = (typeof model === 'string') ? undefined : model.loader;
-
         return Promise.resolve(this.scene).then((scene) => {
         return Promise.resolve(this.scene).then((scene) => {
             if (!scene) return this.initScene();
             if (!scene) return this.initScene();
 
 
             if (clearScene) {
             if (clearScene) {
-                scene.meshes.forEach(mesh => {
-                    if (Tags.MatchesQuery(mesh, "viewerMesh")) {
-                        mesh.dispose();
-                    }
-                });
+                this.models.forEach(m => m.dispose());
+                this.models.length = 0;
             }
             }
-            return scene!;
+            return scene;
         }).then(() => {
         }).then(() => {
-            return new Promise<Array<AbstractMesh>>((resolve, reject) => {
-                this.lastUsedLoader = SceneLoader.ImportMesh(undefined, base, filename, this.scene, (meshes) => {
-                    meshes.forEach(mesh => {
-                        Tags.AddTagsTo(mesh, "viewerMesh");
-                    });
-                    resolve(meshes);
-                }, (progressEvent) => {
-                    this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
-                }, (e, m, exception) => {
-                    // console.log(m, exception);
-                    this.onModelLoadErrorObservable.notifyObserversWithPromise({ message: m, exception: exception }).then(() => {
-                        reject(exception);
+            return new Promise<ViewerModel>((resolve, reject) => {
+                // at this point, configuration.model is an object, not a string
+                let model = new ViewerModel(<IModelConfiguration>this.configuration.model, this.scene);
+                this.models.push(model);
+                this.lastUsedLoader = model.loader;
+                model.onLoadedObservable.add((model) => {
+                    resolve(model);
+                });
+                model.onLoadErrorObservable.add((errorObject) => {
+                    this.onModelLoadErrorObservable.notifyObserversWithPromise(errorObject).then(() => {
+                        reject(errorObject.exception);
                     });
                     });
-                }, plugin)!;
+                });
+                model.onLoadProgressObservable.add((progressEvent) => {
+                    return this.onModelLoadProgressObservable.notifyObserversWithPromise(progressEvent);
+                });
                 this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
                 this.onLoaderInitObservable.notifyObserversWithPromise(this.lastUsedLoader);
             });
             });
-        }).then((meshes: Array<AbstractMesh>) => {
-            return this.onModelLoadedObservable.notifyObserversWithPromise(meshes)
+        }).then((model: ViewerModel) => {
+            return this.onModelLoadedObservable.notifyObserversWithPromise(model)
                 .then(() => {
                 .then(() => {
                     // update the models' configuration
                     // update the models' configuration
-                    this.configureModel(this.configuration.model || model, meshes);
+                    this.configureModel(this.configuration.model || modelConfig, model);
                     this.configureLights(this.configuration.lights);
                     this.configureLights(this.configuration.lights);
 
 
                     if (this.configuration.camera) {
                     if (this.configuration.camera) {
-                        this.configureCamera(this.configuration.camera, meshes);
+                        this.configureCamera(this.configuration.camera, model);
                     }
                     }
-                    return this.initEnvironment(meshes);
+                    return this.initEnvironment(model);
                 }).then(() => {
                 }).then(() => {
                     this.isLoading = false;
                     this.isLoading = false;
-                    if (this.nextLoading) {
+                    /*if (this.nextLoading) {
                         return this.nextLoading();
                         return this.nextLoading();
-                    }
-                    return this.scene;
+                    }*/
+                    return model;
                 });
                 });
         });
         });
     }
     }
 
 
-    protected initEnvironment(focusMeshes: Array<AbstractMesh> = this.scene.meshes): Promise<Scene> {
+    protected initEnvironment(model?: ViewerModel): Promise<Scene> {
         this.configureEnvironment(this.configuration.skybox, this.configuration.ground);
         this.configureEnvironment(this.configuration.skybox, this.configuration.ground);
 
 
         return Promise.resolve(this.scene);
         return Promise.resolve(this.scene);
@@ -944,7 +932,6 @@ export abstract class AbstractViewer {
             if (typeof behaviorConfig === "object") {
             if (typeof behaviorConfig === "object") {
                 this.extendClassWithConfig(behavior, behaviorConfig);
                 this.extendClassWithConfig(behavior, behaviorConfig);
             }
             }
-            //this.camera.addBehavior(behavior);
         }
         }
 
 
         // post attach configuration. Some functionalities require the attached camera.
         // post attach configuration. Some functionalities require the attached camera.

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


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


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


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


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


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


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


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


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


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


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


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


+ 15 - 2
dist/preview release/loaders/babylon.glTF1FileLoader.d.ts

@@ -52,6 +52,7 @@ declare module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -107,13 +108,19 @@ declare module BABYLON {
          */
          */
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         private _onTextureLoadedObserver;
         private _onTextureLoadedObserver;
-        onTextureLoaded: (Texture: BaseTexture) => void;
+        onTextureLoaded: (texture: BaseTexture) => void;
         /**
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
          */
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         private _onMaterialLoadedObserver;
         private _onMaterialLoadedObserver;
-        onMaterialLoaded: (Material: Material) => void;
+        onMaterialLoaded: (material: Material) => void;
+        /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+         */
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
+        private _onAnimationGroupLoadedObserver;
+        onAnimationGroupLoaded: (animationGroup: AnimationGroup) => void;
         /**
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets with LODs, raised when all of the LODs are complete.
@@ -136,6 +143,11 @@ declare module BABYLON {
         private _onExtensionLoadedObserver;
         private _onExtensionLoadedObserver;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
         /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        whenCompleteAsync(): Promise<void>;
+        /**
          * The loader state or null if not active.
          * The loader state or null if not active.
          */
          */
         readonly loaderState: Nullable<GLTFLoaderState>;
         readonly loaderState: Nullable<GLTFLoaderState>;
@@ -569,6 +581,7 @@ declare module BABYLON.GLTF1 {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
         state: Nullable<GLTFLoaderState>;

+ 27 - 0
dist/preview release/loaders/babylon.glTF1FileLoader.js

@@ -77,6 +77,10 @@ var BABYLON;
              */
              */
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             /**
             /**
+             * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+             */
+            this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
+            /**
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -139,6 +143,16 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        Object.defineProperty(GLTFFileLoader.prototype, "onAnimationGroupLoaded", {
+            set: function (callback) {
+                if (this._onAnimationGroupLoadedObserver) {
+                    this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+                }
+                this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
             set: function (callback) {
             set: function (callback) {
                 if (this._onCompleteObserver) {
                 if (this._onCompleteObserver) {
@@ -169,6 +183,18 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        GLTFFileLoader.prototype.whenCompleteAsync = function () {
+            var _this = this;
+            return new Promise(function (resolve) {
+                _this.onCompleteObservable.add(function () {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        };
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
             /**
              * The loader state or null if not active.
              * The loader state or null if not active.
@@ -1837,6 +1863,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
                 this.state = null;

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


+ 24 - 10
dist/preview release/loaders/babylon.glTF2FileLoader.d.ts

@@ -52,6 +52,7 @@ declare module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -107,13 +108,19 @@ declare module BABYLON {
          */
          */
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         private _onTextureLoadedObserver;
         private _onTextureLoadedObserver;
-        onTextureLoaded: (Texture: BaseTexture) => void;
+        onTextureLoaded: (texture: BaseTexture) => void;
         /**
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
          */
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         private _onMaterialLoadedObserver;
         private _onMaterialLoadedObserver;
-        onMaterialLoaded: (Material: Material) => void;
+        onMaterialLoaded: (material: Material) => void;
+        /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+         */
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
+        private _onAnimationGroupLoadedObserver;
+        onAnimationGroupLoaded: (animationGroup: AnimationGroup) => void;
         /**
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets with LODs, raised when all of the LODs are complete.
@@ -136,6 +143,11 @@ declare module BABYLON {
         private _onExtensionLoadedObserver;
         private _onExtensionLoadedObserver;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
         /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        whenCompleteAsync(): Promise<void>;
+        /**
          * The loader state or null if not active.
          * The loader state or null if not active.
          */
          */
         readonly loaderState: Nullable<GLTFLoaderState>;
         readonly loaderState: Nullable<GLTFLoaderState>;
@@ -290,6 +302,7 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
         readonly state: Nullable<GLTFLoaderState>;
@@ -308,12 +321,13 @@ declare module BABYLON.GLTF2 {
         private _createRootNode();
         private _createRootNode();
         private _loadNodesAsync(nodes);
         private _loadNodesAsync(nodes);
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
+        private _forEachNodeMesh(node, callback);
         private _getMeshes();
         private _getMeshes();
         private _getSkeletons();
         private _getSkeletons();
         private _startAnimations();
         private _startAnimations();
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
-        private _loadMeshAsync(context, node, mesh);
-        private _loadPrimitiveAsync(context, node, mesh, primitive);
+        private _loadMeshAsync(context, node, mesh, babylonMesh);
+        private _loadPrimitiveAsync(context, node, mesh, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
@@ -337,7 +351,7 @@ declare module BABYLON.GLTF2 {
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _getDefaultMaterial();
         private _getDefaultMaterial();
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
-        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Promise<void>;
+        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Promise<void>;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
@@ -373,7 +387,7 @@ declare module BABYLON.GLTF2 {
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Override this method to modify the default behavior for loading materials. */
         /** Override this method to modify the default behavior for loading materials. */
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading uris. */
         /** Override this method to modify the default behavior for loading uris. */
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /** Helper method called by a loader extension to load an glTF extension. */
         /** Helper method called by a loader extension to load an glTF extension. */
@@ -385,7 +399,7 @@ declare module BABYLON.GLTF2 {
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Helper method called by the loader to allow extensions to override loading materials. */
         /** Helper method called by the loader to allow extensions to override loading materials. */
-        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Helper method called by the loader to allow extensions to override loading uris. */
         /** Helper method called by the loader to allow extensions to override loading uris. */
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
     }
     }
@@ -404,7 +418,7 @@ declare module BABYLON.GLTF2.Extensions {
         private _loadingMaterialLOD;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
         private _loadMaterialSignals;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
         /**
          * Gets an array of LOD properties from lowest to highest.
          * Gets an array of LOD properties from lowest to highest.
@@ -428,8 +442,8 @@ declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
-        private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        private _loadSpecularGlossinessPropertiesAsync(context, material, properties);
     }
     }
 }
 }
 
 

+ 142 - 93
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -77,6 +77,10 @@ var BABYLON;
              */
              */
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             /**
             /**
+             * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+             */
+            this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
+            /**
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -139,6 +143,16 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        Object.defineProperty(GLTFFileLoader.prototype, "onAnimationGroupLoaded", {
+            set: function (callback) {
+                if (this._onAnimationGroupLoadedObserver) {
+                    this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+                }
+                this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
             set: function (callback) {
             set: function (callback) {
                 if (this._onCompleteObserver) {
                 if (this._onCompleteObserver) {
@@ -169,6 +183,18 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        GLTFFileLoader.prototype.whenCompleteAsync = function () {
+            var _this = this;
+            return new Promise(function (resolve) {
+                _this.onCompleteObservable.add(function () {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        };
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
             /**
              * The loader state or null if not active.
              * The loader state or null if not active.
@@ -499,6 +525,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
             }
             }
@@ -545,7 +572,7 @@ var BABYLON;
                         nodes = names.map(function (name) {
                         nodes = names.map(function (name) {
                             var node = nodeMap_1[name];
                             var node = nodeMap_1[name];
                             if (!node) {
                             if (!node) {
-                                throw new Error("Failed to find node " + name);
+                                throw new Error("Failed to find node '" + name + "'");
                             }
                             }
                             return node;
                             return node;
                         });
                         });
@@ -586,9 +613,12 @@ var BABYLON;
                     if (_this.compileShadowGenerators) {
                     if (_this.compileShadowGenerators) {
                         promises.push(_this._compileShadowGeneratorsAsync());
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     }
-                    return Promise.all(promises).then(function () {
+                    var resultPromise = Promise.all(promises).then(function () {
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         _this._startAnimations();
+                    });
+                    resultPromise.then(function () {
+                        _this._rootBabylonMesh.setEnabled(true);
                         BABYLON.Tools.SetImmediate(function () {
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
                                 Promise.all(_this._completePromises).then(function () {
@@ -603,6 +633,7 @@ var BABYLON;
                             }
                             }
                         });
                         });
                     });
                     });
+                    return resultPromise;
                 }).catch(function (error) {
                 }).catch(function (error) {
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     _this._clear();
                     _this._clear();
@@ -681,6 +712,7 @@ var BABYLON;
             };
             };
             GLTFLoader.prototype._createRootNode = function () {
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this.coordinateSystemMode) {
                 switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -696,7 +728,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error("Invalid coordinate system mode " + this.coordinateSystemMode);
+                        throw new Error("Invalid coordinate system mode (" + this.coordinateSystemMode + ")");
                     }
                     }
                 }
                 }
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
@@ -725,6 +757,17 @@ var BABYLON;
                 promises.push(this._loadAnimationsAsync());
                 promises.push(this._loadAnimationsAsync());
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
+            GLTFLoader.prototype._forEachNodeMesh = function (node, callback) {
+                if (node._babylonMesh) {
+                    callback(node._babylonMesh);
+                }
+                if (node._primitiveBabylonMeshes) {
+                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
+                        var babylonMesh = _a[_i];
+                        callback(babylonMesh);
+                    }
+                }
+            };
             GLTFLoader.prototype._getMeshes = function () {
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = new Array();
                 var meshes = new Array();
                 // Root mesh is always first.
                 // Root mesh is always first.
@@ -733,15 +776,9 @@ var BABYLON;
                 if (nodes) {
                 if (nodes) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                         var node = nodes_2[_i];
                         var node = nodes_2[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachNodeMesh(node, function (mesh) {
+                            meshes.push(mesh);
+                        });
                     }
                     }
                 }
                 }
                 return meshes;
                 return meshes;
@@ -782,7 +819,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode (" + this.animationStartMode + ")");
                         return;
                         return;
                     }
                     }
                 }
                 }
@@ -803,7 +840,7 @@ var BABYLON;
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh));
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh, babylonMesh));
                 }
                 }
                 if (node.children) {
                 if (node.children) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
@@ -815,38 +852,42 @@ var BABYLON;
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh) {
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 // TODO: instancing
                 // TODO: instancing
+                var _this = this;
                 var promises = new Array();
                 var promises = new Array();
                 var primitives = mesh.primitives;
                 var primitives = mesh.primitives;
                 if (!primitives || primitives.length === 0) {
                 if (!primitives || primitives.length === 0) {
                     throw new Error(context + ": Primitives are missing");
                     throw new Error(context + ": Primitives are missing");
                 }
                 }
                 GLTF2.ArrayItem.Assign(primitives);
                 GLTF2.ArrayItem.Assign(primitives);
-                for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
-                    var primitive = primitives_1[_i];
-                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive));
+                if (primitives.length === 1) {
+                    var primitive = primitives[0];
+                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, babylonMesh));
+                }
+                else {
+                    node._primitiveBabylonMeshes = [];
+                    for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
+                        var primitive = primitives_1[_i];
+                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive._index, this._babylonScene, babylonMesh);
+                        node._primitiveBabylonMeshes.push(babylonMesh);
+                        promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, primitiveBabylonMesh));
+                        this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                    }
                 }
                 }
                 if (node.skin != undefined) {
                 if (node.skin != undefined) {
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                 }
                 }
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
-                    if (node._primitiveBabylonMeshes) {
-                        for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                            var primitiveBabylonMesh = _a[_i];
-                            primitiveBabylonMesh._refreshBoundingInfo(true);
-                        }
-                    }
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
+                        babylonMesh._refreshBoundingInfo(true);
+                    });
                 });
                 });
             };
             };
-            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive) {
+            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
                 var promises = new Array();
                 var promises = new Array();
-                var babylonMesh = new BABYLON.Mesh((mesh.name || node._babylonMesh.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-                babylonMesh.setEnabled(false);
-                node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
-                node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
@@ -856,13 +897,12 @@ var BABYLON;
                     babylonMesh.material = this._getDefaultMaterial();
                     babylonMesh.material = this._getDefaultMaterial();
                 }
                 }
                 else {
                 else {
-                    var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
+                    var material = GLTFLoader._GetProperty(context + "/material}", this._gltf.materials, primitive.material);
+                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh, function (babylonMaterial) {
+                        babylonMesh.material = babylonMaterial;
+                    }));
                 }
                 }
-                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
-                return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                });
+                return Promise.all(promises).then(function () { });
             };
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
@@ -876,7 +916,7 @@ var BABYLON;
                 }
                 }
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                     // TODO: handle other primitive modes
                     // TODO: handle other primitive modes
-                    throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
+                    throw new Error(context + ": Mode (" + primitive.mode + ") is not currently supported");
                 }
                 }
                 var promises = new Array();
                 var promises = new Array();
                 var babylonVertexData = new BABYLON.VertexData();
                 var babylonVertexData = new BABYLON.VertexData();
@@ -1018,7 +1058,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid component type " + accessor.componentType);
+                            throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                         }
                         }
                     }
                     }
                 }
                 }
@@ -1064,10 +1104,9 @@ var BABYLON;
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
                 var _this = this;
                 var _this = this;
                 var assignSkeleton = function () {
                 var assignSkeleton = function () {
-                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                        var babylonMesh = _a[_i];
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
                         babylonMesh.skeleton = skin._babylonSkeleton;
                         babylonMesh.skeleton = skin._babylonSkeleton;
-                    }
+                    });
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
@@ -1157,13 +1196,15 @@ var BABYLON;
                     var channel = _a[_i];
                     var channel = _a[_i];
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                 }
                 }
+                this.onAnimationGroupLoadedObservable.notifyObservers(babylonAnimationGroup);
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
                     babylonAnimationGroup.normalize();
                     babylonAnimationGroup.normalize();
                 });
                 });
             };
             };
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+                var _this = this;
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
-                if (!targetNode._babylonMesh) {
+                if (!targetNode._babylonMesh || targetNode.skin != undefined) {
                     return Promise.resolve();
                     return Promise.resolve();
                 }
                 }
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
@@ -1192,7 +1233,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid target path " + channel.target.path);
+                            throw new Error(context + ": Invalid target path (" + channel.target.path + ")");
                         }
                         }
                     }
                     }
                     var outputBufferOffset = 0;
                     var outputBufferOffset = 0;
@@ -1284,11 +1325,10 @@ var BABYLON;
                                 value: key.value[targetIndex],
                                 value: key.value[targetIndex],
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                             }); }));
                             }); }));
-                            for (var _i = 0, _a = targetNode._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                                var babylonMesh = _a[_i];
+                            _this._forEachNodeMesh(targetNode, function (babylonMesh) {
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
-                            }
+                            });
                         };
                         };
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                             _loop_1(targetIndex);
                             _loop_1(targetIndex);
@@ -1319,7 +1359,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid interpolation " + sampler.interpolation);
+                        throw new Error(context + ": Invalid interpolation (" + sampler.interpolation + ")");
                     }
                     }
                 }
                 }
                 var inputData;
                 var inputData;
@@ -1404,12 +1444,12 @@ var BABYLON;
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid component type " + accessor.componentType);
+                                throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                             }
                             }
                         }
                         }
                     }
                     }
                     catch (e) {
                     catch (e) {
-                        throw new Error(context + ": " + e);
+                        throw new Error(context + ": " + e.messsage);
                     }
                     }
                 });
                 });
                 return accessor._data;
                 return accessor._data;
@@ -1481,25 +1521,24 @@ var BABYLON;
                 this._loadMaterialAlphaProperties(context, material);
                 this._loadMaterialAlphaProperties(context, material);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
-                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh);
+            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh, assign);
                 if (promise) {
                 if (promise) {
                     return promise;
                     return promise;
                 }
                 }
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes.push(babylonMesh);
                 material._babylonMeshes.push(babylonMesh);
-                if (material._loaded) {
-                    babylonMesh.material = material._babylonMaterial;
-                    return material._loaded;
+                if (!material._loaded) {
+                    var promises = new Array();
+                    var babylonMaterial = this._createMaterial(material);
+                    material._babylonMaterial = babylonMaterial;
+                    promises.push(this._loadMaterialBasePropertiesAsync(context, material));
+                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
+                    this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                    material._loaded = Promise.all(promises).then(function () { });
                 }
                 }
-                var promises = new Array();
-                var babylonMaterial = this._createMaterial(material);
-                material._babylonMaterial = babylonMaterial;
-                promises.push(this._loadMaterialBasePropertiesAsync(context, material));
-                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
-                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                babylonMesh.material = babylonMaterial;
-                return (material._loaded = Promise.all(promises).then(function () { }));
+                assign(material._babylonMaterial);
+                return material._loaded;
             };
             };
             GLTFLoader.prototype._createMaterial = function (material) {
             GLTFLoader.prototype._createMaterial = function (material) {
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
@@ -1574,7 +1613,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid alpha mode " + material.alphaMode);
+                        throw new Error(context + ": Invalid alpha mode (" + material.alphaMode + ")");
                     }
                     }
                 }
                 }
             };
             };
@@ -1592,7 +1631,7 @@ var BABYLON;
                     }
                     }
                 }, function (message, exception) {
                 }, function (message, exception) {
                     if (!_this._disposed) {
                     if (!_this._disposed) {
-                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                        deferred.reject(new Error(context + ": " + ((exception && exception.message) ? exception.message : message || "Failed to load texture")));
                     }
                     }
                 });
                 });
                 promises.push(deferred.promise);
                 promises.push(deferred.promise);
@@ -1696,7 +1735,7 @@ var BABYLON;
             };
             };
             GLTFLoader._GetProperty = function (context, array, index) {
             GLTFLoader._GetProperty = function (context, array, index) {
                 if (!array || index == undefined || !array[index]) {
                 if (!array || index == undefined || !array[index]) {
-                    throw new Error(context + ": Failed to find index " + index);
+                    throw new Error(context + ": Failed to find index (" + index + ")");
                 }
                 }
                 return array[index];
                 return array[index];
             };
             };
@@ -1708,7 +1747,7 @@ var BABYLON;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     default:
                     default:
-                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode " + mode);
+                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode (" + mode + ")");
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                 }
                 }
             };
             };
@@ -1725,13 +1764,13 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                     }
                     }
                 }
                 }
                 else {
                 else {
                     if (magFilter !== 9728 /* NEAREST */) {
                     if (magFilter !== 9728 /* NEAREST */) {
-                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter " + magFilter);
+                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter (" + magFilter + ")");
                     }
                     }
                     switch (minFilter) {
                     switch (minFilter) {
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
@@ -1741,7 +1780,7 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                     }
                     }
                 }
                 }
@@ -1756,7 +1795,7 @@ var BABYLON;
                     case "MAT3": return 9;
                     case "MAT3": return 9;
                     case "MAT4": return 16;
                     case "MAT4": return 16;
                 }
                 }
-                throw new Error(context + ": Invalid type " + type);
+                throw new Error(context + ": Invalid type (" + type + ")");
             };
             };
             GLTFLoader._ValidateUri = function (uri) {
             GLTFLoader._ValidateUri = function (uri) {
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
@@ -1868,7 +1907,7 @@ var BABYLON;
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             /** Override this method to modify the default behavior for loading materials. */
             /** Override this method to modify the default behavior for loading materials. */
-            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh) { return null; };
+            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) { return null; };
             /** Override this method to modify the default behavior for loading uris. */
             /** Override this method to modify the default behavior for loading uris. */
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             // #endregion
             // #endregion
@@ -1905,8 +1944,8 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading materials. */
             /** Helper method called by the loader to allow extensions to override loading materials. */
-            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh) {
-                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh); });
+            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh, assign) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh, assign); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading uris. */
             /** Helper method called by the loader to allow extensions to override loading uris. */
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
@@ -1970,7 +2009,10 @@ var BABYLON;
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                                 if (indexLOD !== 0) {
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                    previousNodeLOD._babylonMesh.setEnabled(false);
+                                    if (previousNodeLOD._babylonMesh) {
+                                        previousNodeLOD._babylonMesh.dispose();
+                                        delete previousNodeLOD._babylonMesh;
+                                    }
                                 }
                                 }
                                 if (indexLOD !== nodeLODs.length - 1) {
                                 if (indexLOD !== nodeLODs.length - 1) {
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
@@ -1994,7 +2036,7 @@ var BABYLON;
                         return firstPromise;
                         return firstPromise;
                     });
                     });
                 };
                 };
-                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     // Don't load material LODs if already loading a node LOD.
                     // Don't load material LODs if already loading a node LOD.
                     if (this._loadingNodeLOD) {
                     if (this._loadingNodeLOD) {
@@ -2011,7 +2053,15 @@ var BABYLON;
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                 }
                                 }
                             }
                             }
-                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(function () {
+                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh, indexLOD === 0 ? assign : function () { }).then(function () {
+                                if (indexLOD !== 0) {
+                                    assign(materialLOD._babylonMaterial);
+                                    var previousMaterialLOD = materialLODs[indexLOD - 1];
+                                    if (previousMaterialLOD._babylonMaterial) {
+                                        previousMaterialLOD._babylonMaterial.dispose();
+                                        delete previousMaterialLOD._babylonMaterial;
+                                    }
+                                }
                                 if (indexLOD !== materialLODs.length - 1) {
                                 if (indexLOD !== materialLODs.length - 1) {
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     if (_this._loadMaterialSignals[materialIndex]) {
                                     if (_this._loadMaterialSignals[materialIndex]) {
@@ -2198,26 +2248,25 @@ var BABYLON;
                     _this.name = NAME;
                     _this.name = NAME;
                     return _this;
                     return _this;
                 }
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes.push(babylonMesh);
                         material._babylonMeshes.push(babylonMesh);
-                        if (material._loaded) {
-                            babylonMesh.material = material._babylonMaterial;
-                            return material._loaded;
+                        if (!material._loaded) {
+                            var promises = new Array();
+                            var babylonMaterial = _this._loader._createMaterial(material);
+                            material._babylonMaterial = babylonMaterial;
+                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
+                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(context, material, extension));
+                            _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            material._loaded = Promise.all(promises).then(function () { });
                         }
                         }
-                        var promises = new Array();
-                        var babylonMaterial = _this._loader._createMaterial(material);
-                        material._babylonMaterial = babylonMaterial;
-                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
-                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(_this._loader, context, material, extension));
-                        _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        babylonMesh.material = babylonMaterial;
-                        return (material._loaded = Promise.all(promises).then(function () { }));
+                        assign(material._babylonMaterial);
+                        return material._loaded;
                     });
                     });
                 };
                 };
-                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties) {
                     var promises = new Array();
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
                     if (properties.diffuseFactor) {
@@ -2230,18 +2279,18 @@ var BABYLON;
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     if (properties.diffuseTexture) {
                     if (properties.diffuseTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
                             babylonMaterial.albedoTexture = texture;
                             babylonMaterial.albedoTexture = texture;
                         }));
                         }));
                     }
                     }
                     if (properties.specularGlossinessTexture) {
                     if (properties.specularGlossinessTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
                             babylonMaterial.reflectivityTexture = texture;
                             babylonMaterial.reflectivityTexture = texture;
                         }));
                         }));
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                     }
                     }
-                    loader._loadMaterialAlphaProperties(context, material);
+                    this._loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                     return Promise.all(promises).then(function () { });
                 };
                 };
                 return KHR_materials_pbrSpecularGlossiness;
                 return KHR_materials_pbrSpecularGlossiness;
@@ -2327,7 +2376,7 @@ var BABYLON;
                                 break;
                                 break;
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid light type " + light.type);
+                                throw new Error(context + ": Invalid light type (" + light.type + ")");
                             }
                             }
                         }
                         }
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
@@ -2340,7 +2389,7 @@ var BABYLON;
                     get: function () {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
                         var extensions = this._loader._gltf.extensions;
                         if (!extensions || !extensions[this.name]) {
                         if (!extensions || !extensions[this.name]) {
-                            throw new Error("#/extensions: " + this.name + " not found");
+                            throw new Error("#/extensions: '" + this.name + "' not found");
                         }
                         }
                         var extension = extensions[this.name];
                         var extension = extensions[this.name];
                         return extension.lights;
                         return extension.lights;

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


+ 25 - 10
dist/preview release/loaders/babylon.glTFFileLoader.d.ts

@@ -52,6 +52,7 @@ declare module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -107,13 +108,19 @@ declare module BABYLON {
          */
          */
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         private _onTextureLoadedObserver;
         private _onTextureLoadedObserver;
-        onTextureLoaded: (Texture: BaseTexture) => void;
+        onTextureLoaded: (texture: BaseTexture) => void;
         /**
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
          */
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         private _onMaterialLoadedObserver;
         private _onMaterialLoadedObserver;
-        onMaterialLoaded: (Material: Material) => void;
+        onMaterialLoaded: (material: Material) => void;
+        /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+         */
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
+        private _onAnimationGroupLoadedObserver;
+        onAnimationGroupLoaded: (animationGroup: AnimationGroup) => void;
         /**
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets with LODs, raised when all of the LODs are complete.
@@ -136,6 +143,11 @@ declare module BABYLON {
         private _onExtensionLoadedObserver;
         private _onExtensionLoadedObserver;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
         /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        whenCompleteAsync(): Promise<void>;
+        /**
          * The loader state or null if not active.
          * The loader state or null if not active.
          */
          */
         readonly loaderState: Nullable<GLTFLoaderState>;
         readonly loaderState: Nullable<GLTFLoaderState>;
@@ -569,6 +581,7 @@ declare module BABYLON.GLTF1 {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
         state: Nullable<GLTFLoaderState>;
@@ -845,6 +858,7 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
         readonly state: Nullable<GLTFLoaderState>;
@@ -863,12 +877,13 @@ declare module BABYLON.GLTF2 {
         private _createRootNode();
         private _createRootNode();
         private _loadNodesAsync(nodes);
         private _loadNodesAsync(nodes);
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
+        private _forEachNodeMesh(node, callback);
         private _getMeshes();
         private _getMeshes();
         private _getSkeletons();
         private _getSkeletons();
         private _startAnimations();
         private _startAnimations();
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
-        private _loadMeshAsync(context, node, mesh);
-        private _loadPrimitiveAsync(context, node, mesh, primitive);
+        private _loadMeshAsync(context, node, mesh, babylonMesh);
+        private _loadPrimitiveAsync(context, node, mesh, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
@@ -892,7 +907,7 @@ declare module BABYLON.GLTF2 {
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _getDefaultMaterial();
         private _getDefaultMaterial();
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
-        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Promise<void>;
+        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Promise<void>;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
@@ -928,7 +943,7 @@ declare module BABYLON.GLTF2 {
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Override this method to modify the default behavior for loading materials. */
         /** Override this method to modify the default behavior for loading materials. */
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading uris. */
         /** Override this method to modify the default behavior for loading uris. */
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /** Helper method called by a loader extension to load an glTF extension. */
         /** Helper method called by a loader extension to load an glTF extension. */
@@ -940,7 +955,7 @@ declare module BABYLON.GLTF2 {
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Helper method called by the loader to allow extensions to override loading materials. */
         /** Helper method called by the loader to allow extensions to override loading materials. */
-        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Helper method called by the loader to allow extensions to override loading uris. */
         /** Helper method called by the loader to allow extensions to override loading uris. */
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
     }
     }
@@ -959,7 +974,7 @@ declare module BABYLON.GLTF2.Extensions {
         private _loadingMaterialLOD;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
         private _loadMaterialSignals;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
         /**
          * Gets an array of LOD properties from lowest to highest.
          * Gets an array of LOD properties from lowest to highest.
@@ -983,8 +998,8 @@ declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
-        private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        private _loadSpecularGlossinessPropertiesAsync(context, material, properties);
     }
     }
 }
 }
 
 

+ 143 - 93
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -77,6 +77,10 @@ var BABYLON;
              */
              */
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             /**
             /**
+             * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+             */
+            this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
+            /**
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -139,6 +143,16 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        Object.defineProperty(GLTFFileLoader.prototype, "onAnimationGroupLoaded", {
+            set: function (callback) {
+                if (this._onAnimationGroupLoadedObserver) {
+                    this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+                }
+                this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
             set: function (callback) {
             set: function (callback) {
                 if (this._onCompleteObserver) {
                 if (this._onCompleteObserver) {
@@ -169,6 +183,18 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        GLTFFileLoader.prototype.whenCompleteAsync = function () {
+            var _this = this;
+            return new Promise(function (resolve) {
+                _this.onCompleteObservable.add(function () {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        };
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
             /**
              * The loader state or null if not active.
              * The loader state or null if not active.
@@ -1837,6 +1863,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
                 this.state = null;
@@ -2675,6 +2702,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
             }
             }
@@ -2721,7 +2749,7 @@ var BABYLON;
                         nodes = names.map(function (name) {
                         nodes = names.map(function (name) {
                             var node = nodeMap_1[name];
                             var node = nodeMap_1[name];
                             if (!node) {
                             if (!node) {
-                                throw new Error("Failed to find node " + name);
+                                throw new Error("Failed to find node '" + name + "'");
                             }
                             }
                             return node;
                             return node;
                         });
                         });
@@ -2762,9 +2790,12 @@ var BABYLON;
                     if (_this.compileShadowGenerators) {
                     if (_this.compileShadowGenerators) {
                         promises.push(_this._compileShadowGeneratorsAsync());
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     }
-                    return Promise.all(promises).then(function () {
+                    var resultPromise = Promise.all(promises).then(function () {
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         _this._startAnimations();
+                    });
+                    resultPromise.then(function () {
+                        _this._rootBabylonMesh.setEnabled(true);
                         BABYLON.Tools.SetImmediate(function () {
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
                                 Promise.all(_this._completePromises).then(function () {
@@ -2779,6 +2810,7 @@ var BABYLON;
                             }
                             }
                         });
                         });
                     });
                     });
+                    return resultPromise;
                 }).catch(function (error) {
                 }).catch(function (error) {
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     _this._clear();
                     _this._clear();
@@ -2857,6 +2889,7 @@ var BABYLON;
             };
             };
             GLTFLoader.prototype._createRootNode = function () {
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this.coordinateSystemMode) {
                 switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -2872,7 +2905,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error("Invalid coordinate system mode " + this.coordinateSystemMode);
+                        throw new Error("Invalid coordinate system mode (" + this.coordinateSystemMode + ")");
                     }
                     }
                 }
                 }
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
@@ -2901,6 +2934,17 @@ var BABYLON;
                 promises.push(this._loadAnimationsAsync());
                 promises.push(this._loadAnimationsAsync());
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
+            GLTFLoader.prototype._forEachNodeMesh = function (node, callback) {
+                if (node._babylonMesh) {
+                    callback(node._babylonMesh);
+                }
+                if (node._primitiveBabylonMeshes) {
+                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
+                        var babylonMesh = _a[_i];
+                        callback(babylonMesh);
+                    }
+                }
+            };
             GLTFLoader.prototype._getMeshes = function () {
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = new Array();
                 var meshes = new Array();
                 // Root mesh is always first.
                 // Root mesh is always first.
@@ -2909,15 +2953,9 @@ var BABYLON;
                 if (nodes) {
                 if (nodes) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                         var node = nodes_2[_i];
                         var node = nodes_2[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachNodeMesh(node, function (mesh) {
+                            meshes.push(mesh);
+                        });
                     }
                     }
                 }
                 }
                 return meshes;
                 return meshes;
@@ -2958,7 +2996,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode (" + this.animationStartMode + ")");
                         return;
                         return;
                     }
                     }
                 }
                 }
@@ -2979,7 +3017,7 @@ var BABYLON;
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh));
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh, babylonMesh));
                 }
                 }
                 if (node.children) {
                 if (node.children) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
@@ -2991,38 +3029,42 @@ var BABYLON;
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh) {
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 // TODO: instancing
                 // TODO: instancing
+                var _this = this;
                 var promises = new Array();
                 var promises = new Array();
                 var primitives = mesh.primitives;
                 var primitives = mesh.primitives;
                 if (!primitives || primitives.length === 0) {
                 if (!primitives || primitives.length === 0) {
                     throw new Error(context + ": Primitives are missing");
                     throw new Error(context + ": Primitives are missing");
                 }
                 }
                 GLTF2.ArrayItem.Assign(primitives);
                 GLTF2.ArrayItem.Assign(primitives);
-                for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
-                    var primitive = primitives_1[_i];
-                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive));
+                if (primitives.length === 1) {
+                    var primitive = primitives[0];
+                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, babylonMesh));
+                }
+                else {
+                    node._primitiveBabylonMeshes = [];
+                    for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
+                        var primitive = primitives_1[_i];
+                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive._index, this._babylonScene, babylonMesh);
+                        node._primitiveBabylonMeshes.push(babylonMesh);
+                        promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, primitiveBabylonMesh));
+                        this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                    }
                 }
                 }
                 if (node.skin != undefined) {
                 if (node.skin != undefined) {
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                 }
                 }
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
-                    if (node._primitiveBabylonMeshes) {
-                        for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                            var primitiveBabylonMesh = _a[_i];
-                            primitiveBabylonMesh._refreshBoundingInfo(true);
-                        }
-                    }
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
+                        babylonMesh._refreshBoundingInfo(true);
+                    });
                 });
                 });
             };
             };
-            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive) {
+            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
                 var promises = new Array();
                 var promises = new Array();
-                var babylonMesh = new BABYLON.Mesh((mesh.name || node._babylonMesh.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-                babylonMesh.setEnabled(false);
-                node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
-                node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
@@ -3032,13 +3074,12 @@ var BABYLON;
                     babylonMesh.material = this._getDefaultMaterial();
                     babylonMesh.material = this._getDefaultMaterial();
                 }
                 }
                 else {
                 else {
-                    var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
+                    var material = GLTFLoader._GetProperty(context + "/material}", this._gltf.materials, primitive.material);
+                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh, function (babylonMaterial) {
+                        babylonMesh.material = babylonMaterial;
+                    }));
                 }
                 }
-                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
-                return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                });
+                return Promise.all(promises).then(function () { });
             };
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
@@ -3052,7 +3093,7 @@ var BABYLON;
                 }
                 }
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                     // TODO: handle other primitive modes
                     // TODO: handle other primitive modes
-                    throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
+                    throw new Error(context + ": Mode (" + primitive.mode + ") is not currently supported");
                 }
                 }
                 var promises = new Array();
                 var promises = new Array();
                 var babylonVertexData = new BABYLON.VertexData();
                 var babylonVertexData = new BABYLON.VertexData();
@@ -3194,7 +3235,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid component type " + accessor.componentType);
+                            throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                         }
                         }
                     }
                     }
                 }
                 }
@@ -3240,10 +3281,9 @@ var BABYLON;
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
                 var _this = this;
                 var _this = this;
                 var assignSkeleton = function () {
                 var assignSkeleton = function () {
-                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                        var babylonMesh = _a[_i];
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
                         babylonMesh.skeleton = skin._babylonSkeleton;
                         babylonMesh.skeleton = skin._babylonSkeleton;
-                    }
+                    });
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
@@ -3333,13 +3373,15 @@ var BABYLON;
                     var channel = _a[_i];
                     var channel = _a[_i];
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                 }
                 }
+                this.onAnimationGroupLoadedObservable.notifyObservers(babylonAnimationGroup);
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
                     babylonAnimationGroup.normalize();
                     babylonAnimationGroup.normalize();
                 });
                 });
             };
             };
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+                var _this = this;
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
-                if (!targetNode._babylonMesh) {
+                if (!targetNode._babylonMesh || targetNode.skin != undefined) {
                     return Promise.resolve();
                     return Promise.resolve();
                 }
                 }
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
@@ -3368,7 +3410,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid target path " + channel.target.path);
+                            throw new Error(context + ": Invalid target path (" + channel.target.path + ")");
                         }
                         }
                     }
                     }
                     var outputBufferOffset = 0;
                     var outputBufferOffset = 0;
@@ -3460,11 +3502,10 @@ var BABYLON;
                                 value: key.value[targetIndex],
                                 value: key.value[targetIndex],
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                             }); }));
                             }); }));
-                            for (var _i = 0, _a = targetNode._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                                var babylonMesh = _a[_i];
+                            _this._forEachNodeMesh(targetNode, function (babylonMesh) {
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
-                            }
+                            });
                         };
                         };
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                             _loop_1(targetIndex);
                             _loop_1(targetIndex);
@@ -3495,7 +3536,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid interpolation " + sampler.interpolation);
+                        throw new Error(context + ": Invalid interpolation (" + sampler.interpolation + ")");
                     }
                     }
                 }
                 }
                 var inputData;
                 var inputData;
@@ -3580,12 +3621,12 @@ var BABYLON;
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid component type " + accessor.componentType);
+                                throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                             }
                             }
                         }
                         }
                     }
                     }
                     catch (e) {
                     catch (e) {
-                        throw new Error(context + ": " + e);
+                        throw new Error(context + ": " + e.messsage);
                     }
                     }
                 });
                 });
                 return accessor._data;
                 return accessor._data;
@@ -3657,25 +3698,24 @@ var BABYLON;
                 this._loadMaterialAlphaProperties(context, material);
                 this._loadMaterialAlphaProperties(context, material);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
-                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh);
+            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh, assign);
                 if (promise) {
                 if (promise) {
                     return promise;
                     return promise;
                 }
                 }
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes.push(babylonMesh);
                 material._babylonMeshes.push(babylonMesh);
-                if (material._loaded) {
-                    babylonMesh.material = material._babylonMaterial;
-                    return material._loaded;
+                if (!material._loaded) {
+                    var promises = new Array();
+                    var babylonMaterial = this._createMaterial(material);
+                    material._babylonMaterial = babylonMaterial;
+                    promises.push(this._loadMaterialBasePropertiesAsync(context, material));
+                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
+                    this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                    material._loaded = Promise.all(promises).then(function () { });
                 }
                 }
-                var promises = new Array();
-                var babylonMaterial = this._createMaterial(material);
-                material._babylonMaterial = babylonMaterial;
-                promises.push(this._loadMaterialBasePropertiesAsync(context, material));
-                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
-                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                babylonMesh.material = babylonMaterial;
-                return (material._loaded = Promise.all(promises).then(function () { }));
+                assign(material._babylonMaterial);
+                return material._loaded;
             };
             };
             GLTFLoader.prototype._createMaterial = function (material) {
             GLTFLoader.prototype._createMaterial = function (material) {
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
@@ -3750,7 +3790,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid alpha mode " + material.alphaMode);
+                        throw new Error(context + ": Invalid alpha mode (" + material.alphaMode + ")");
                     }
                     }
                 }
                 }
             };
             };
@@ -3768,7 +3808,7 @@ var BABYLON;
                     }
                     }
                 }, function (message, exception) {
                 }, function (message, exception) {
                     if (!_this._disposed) {
                     if (!_this._disposed) {
-                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                        deferred.reject(new Error(context + ": " + ((exception && exception.message) ? exception.message : message || "Failed to load texture")));
                     }
                     }
                 });
                 });
                 promises.push(deferred.promise);
                 promises.push(deferred.promise);
@@ -3872,7 +3912,7 @@ var BABYLON;
             };
             };
             GLTFLoader._GetProperty = function (context, array, index) {
             GLTFLoader._GetProperty = function (context, array, index) {
                 if (!array || index == undefined || !array[index]) {
                 if (!array || index == undefined || !array[index]) {
-                    throw new Error(context + ": Failed to find index " + index);
+                    throw new Error(context + ": Failed to find index (" + index + ")");
                 }
                 }
                 return array[index];
                 return array[index];
             };
             };
@@ -3884,7 +3924,7 @@ var BABYLON;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     default:
                     default:
-                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode " + mode);
+                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode (" + mode + ")");
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                 }
                 }
             };
             };
@@ -3901,13 +3941,13 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                     }
                     }
                 }
                 }
                 else {
                 else {
                     if (magFilter !== 9728 /* NEAREST */) {
                     if (magFilter !== 9728 /* NEAREST */) {
-                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter " + magFilter);
+                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter (" + magFilter + ")");
                     }
                     }
                     switch (minFilter) {
                     switch (minFilter) {
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
@@ -3917,7 +3957,7 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                     }
                     }
                 }
                 }
@@ -3932,7 +3972,7 @@ var BABYLON;
                     case "MAT3": return 9;
                     case "MAT3": return 9;
                     case "MAT4": return 16;
                     case "MAT4": return 16;
                 }
                 }
-                throw new Error(context + ": Invalid type " + type);
+                throw new Error(context + ": Invalid type (" + type + ")");
             };
             };
             GLTFLoader._ValidateUri = function (uri) {
             GLTFLoader._ValidateUri = function (uri) {
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
@@ -4044,7 +4084,7 @@ var BABYLON;
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             /** Override this method to modify the default behavior for loading materials. */
             /** Override this method to modify the default behavior for loading materials. */
-            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh) { return null; };
+            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) { return null; };
             /** Override this method to modify the default behavior for loading uris. */
             /** Override this method to modify the default behavior for loading uris. */
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             // #endregion
             // #endregion
@@ -4081,8 +4121,8 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading materials. */
             /** Helper method called by the loader to allow extensions to override loading materials. */
-            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh) {
-                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh); });
+            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh, assign) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh, assign); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading uris. */
             /** Helper method called by the loader to allow extensions to override loading uris. */
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
@@ -4146,7 +4186,10 @@ var BABYLON;
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                                 if (indexLOD !== 0) {
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                    previousNodeLOD._babylonMesh.setEnabled(false);
+                                    if (previousNodeLOD._babylonMesh) {
+                                        previousNodeLOD._babylonMesh.dispose();
+                                        delete previousNodeLOD._babylonMesh;
+                                    }
                                 }
                                 }
                                 if (indexLOD !== nodeLODs.length - 1) {
                                 if (indexLOD !== nodeLODs.length - 1) {
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
@@ -4170,7 +4213,7 @@ var BABYLON;
                         return firstPromise;
                         return firstPromise;
                     });
                     });
                 };
                 };
-                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     // Don't load material LODs if already loading a node LOD.
                     // Don't load material LODs if already loading a node LOD.
                     if (this._loadingNodeLOD) {
                     if (this._loadingNodeLOD) {
@@ -4187,7 +4230,15 @@ var BABYLON;
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                 }
                                 }
                             }
                             }
-                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(function () {
+                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh, indexLOD === 0 ? assign : function () { }).then(function () {
+                                if (indexLOD !== 0) {
+                                    assign(materialLOD._babylonMaterial);
+                                    var previousMaterialLOD = materialLODs[indexLOD - 1];
+                                    if (previousMaterialLOD._babylonMaterial) {
+                                        previousMaterialLOD._babylonMaterial.dispose();
+                                        delete previousMaterialLOD._babylonMaterial;
+                                    }
+                                }
                                 if (indexLOD !== materialLODs.length - 1) {
                                 if (indexLOD !== materialLODs.length - 1) {
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     if (_this._loadMaterialSignals[materialIndex]) {
                                     if (_this._loadMaterialSignals[materialIndex]) {
@@ -4374,26 +4425,25 @@ var BABYLON;
                     _this.name = NAME;
                     _this.name = NAME;
                     return _this;
                     return _this;
                 }
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes.push(babylonMesh);
                         material._babylonMeshes.push(babylonMesh);
-                        if (material._loaded) {
-                            babylonMesh.material = material._babylonMaterial;
-                            return material._loaded;
+                        if (!material._loaded) {
+                            var promises = new Array();
+                            var babylonMaterial = _this._loader._createMaterial(material);
+                            material._babylonMaterial = babylonMaterial;
+                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
+                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(context, material, extension));
+                            _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            material._loaded = Promise.all(promises).then(function () { });
                         }
                         }
-                        var promises = new Array();
-                        var babylonMaterial = _this._loader._createMaterial(material);
-                        material._babylonMaterial = babylonMaterial;
-                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
-                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(_this._loader, context, material, extension));
-                        _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        babylonMesh.material = babylonMaterial;
-                        return (material._loaded = Promise.all(promises).then(function () { }));
+                        assign(material._babylonMaterial);
+                        return material._loaded;
                     });
                     });
                 };
                 };
-                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties) {
                     var promises = new Array();
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
                     if (properties.diffuseFactor) {
@@ -4406,18 +4456,18 @@ var BABYLON;
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     if (properties.diffuseTexture) {
                     if (properties.diffuseTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
                             babylonMaterial.albedoTexture = texture;
                             babylonMaterial.albedoTexture = texture;
                         }));
                         }));
                     }
                     }
                     if (properties.specularGlossinessTexture) {
                     if (properties.specularGlossinessTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
                             babylonMaterial.reflectivityTexture = texture;
                             babylonMaterial.reflectivityTexture = texture;
                         }));
                         }));
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                     }
                     }
-                    loader._loadMaterialAlphaProperties(context, material);
+                    this._loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                     return Promise.all(promises).then(function () { });
                 };
                 };
                 return KHR_materials_pbrSpecularGlossiness;
                 return KHR_materials_pbrSpecularGlossiness;
@@ -4503,7 +4553,7 @@ var BABYLON;
                                 break;
                                 break;
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid light type " + light.type);
+                                throw new Error(context + ": Invalid light type (" + light.type + ")");
                             }
                             }
                         }
                         }
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
@@ -4516,7 +4566,7 @@ var BABYLON;
                     get: function () {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
                         var extensions = this._loader._gltf.extensions;
                         if (!extensions || !extensions[this.name]) {
                         if (!extensions || !extensions[this.name]) {
-                            throw new Error("#/extensions: " + this.name + " not found");
+                            throw new Error("#/extensions: '" + this.name + "' not found");
                         }
                         }
                         var extension = extensions[this.name];
                         var extension = extensions[this.name];
                         return extension.lights;
                         return extension.lights;

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


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


+ 143 - 93
dist/preview release/loaders/babylonjs.loaders.js

@@ -1073,6 +1073,10 @@ var BABYLON;
              */
              */
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             this.onMaterialLoadedObservable = new BABYLON.Observable();
             /**
             /**
+             * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+             */
+            this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
+            /**
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * Raised when the asset is completely loaded, immediately before the loader is disposed.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets with LODs, raised when all of the LODs are complete.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
              * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -1135,6 +1139,16 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        Object.defineProperty(GLTFFileLoader.prototype, "onAnimationGroupLoaded", {
+            set: function (callback) {
+                if (this._onAnimationGroupLoadedObserver) {
+                    this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+                }
+                this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
         Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
             set: function (callback) {
             set: function (callback) {
                 if (this._onCompleteObserver) {
                 if (this._onCompleteObserver) {
@@ -1165,6 +1179,18 @@ var BABYLON;
             enumerable: true,
             enumerable: true,
             configurable: true
             configurable: true
         });
         });
+        /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        GLTFFileLoader.prototype.whenCompleteAsync = function () {
+            var _this = this;
+            return new Promise(function (resolve) {
+                _this.onCompleteObservable.add(function () {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        };
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
         Object.defineProperty(GLTFFileLoader.prototype, "loaderState", {
             /**
             /**
              * The loader state or null if not active.
              * The loader state or null if not active.
@@ -2833,6 +2859,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.state = null;
                 this.state = null;
@@ -3653,6 +3680,7 @@ var BABYLON;
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onMeshLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onTextureLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
                 this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onAnimationGroupLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onExtensionLoadedObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
                 this.onCompleteObservable = new BABYLON.Observable();
             }
             }
@@ -3699,7 +3727,7 @@ var BABYLON;
                         nodes = names.map(function (name) {
                         nodes = names.map(function (name) {
                             var node = nodeMap_1[name];
                             var node = nodeMap_1[name];
                             if (!node) {
                             if (!node) {
-                                throw new Error("Failed to find node " + name);
+                                throw new Error("Failed to find node '" + name + "'");
                             }
                             }
                             return node;
                             return node;
                         });
                         });
@@ -3740,9 +3768,12 @@ var BABYLON;
                     if (_this.compileShadowGenerators) {
                     if (_this.compileShadowGenerators) {
                         promises.push(_this._compileShadowGeneratorsAsync());
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     }
-                    return Promise.all(promises).then(function () {
+                    var resultPromise = Promise.all(promises).then(function () {
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._state = BABYLON.GLTFLoaderState.Ready;
                         _this._startAnimations();
                         _this._startAnimations();
+                    });
+                    resultPromise.then(function () {
+                        _this._rootBabylonMesh.setEnabled(true);
                         BABYLON.Tools.SetImmediate(function () {
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {
                                 Promise.all(_this._completePromises).then(function () {
@@ -3757,6 +3788,7 @@ var BABYLON;
                             }
                             }
                         });
                         });
                     });
                     });
+                    return resultPromise;
                 }).catch(function (error) {
                 }).catch(function (error) {
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     BABYLON.Tools.Error("glTF Loader: " + error.message);
                     _this._clear();
                     _this._clear();
@@ -3835,6 +3867,7 @@ var BABYLON;
             };
             };
             GLTFLoader.prototype._createRootNode = function () {
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this._babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 var rootNode = { _babylonMesh: this._rootBabylonMesh };
                 switch (this.coordinateSystemMode) {
                 switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -3850,7 +3883,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error("Invalid coordinate system mode " + this.coordinateSystemMode);
+                        throw new Error("Invalid coordinate system mode (" + this.coordinateSystemMode + ")");
                     }
                     }
                 }
                 }
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(this._rootBabylonMesh);
@@ -3879,6 +3912,17 @@ var BABYLON;
                 promises.push(this._loadAnimationsAsync());
                 promises.push(this._loadAnimationsAsync());
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
+            GLTFLoader.prototype._forEachNodeMesh = function (node, callback) {
+                if (node._babylonMesh) {
+                    callback(node._babylonMesh);
+                }
+                if (node._primitiveBabylonMeshes) {
+                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
+                        var babylonMesh = _a[_i];
+                        callback(babylonMesh);
+                    }
+                }
+            };
             GLTFLoader.prototype._getMeshes = function () {
             GLTFLoader.prototype._getMeshes = function () {
                 var meshes = new Array();
                 var meshes = new Array();
                 // Root mesh is always first.
                 // Root mesh is always first.
@@ -3887,15 +3931,9 @@ var BABYLON;
                 if (nodes) {
                 if (nodes) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                     for (var _i = 0, nodes_2 = nodes; _i < nodes_2.length; _i++) {
                         var node = nodes_2[_i];
                         var node = nodes_2[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachNodeMesh(node, function (mesh) {
+                            meshes.push(mesh);
+                        });
                     }
                     }
                 }
                 }
                 return meshes;
                 return meshes;
@@ -3936,7 +3974,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode (" + this.animationStartMode + ")");
                         return;
                         return;
                     }
                     }
                 }
                 }
@@ -3957,7 +3995,7 @@ var BABYLON;
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 GLTFLoader._LoadTransform(node, babylonMesh);
                 if (node.mesh != undefined) {
                 if (node.mesh != undefined) {
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
                     var mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh));
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh, babylonMesh));
                 }
                 }
                 if (node.children) {
                 if (node.children) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
                     for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
@@ -3969,38 +4007,42 @@ var BABYLON;
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh) {
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
                 // TODO: instancing
                 // TODO: instancing
+                var _this = this;
                 var promises = new Array();
                 var promises = new Array();
                 var primitives = mesh.primitives;
                 var primitives = mesh.primitives;
                 if (!primitives || primitives.length === 0) {
                 if (!primitives || primitives.length === 0) {
                     throw new Error(context + ": Primitives are missing");
                     throw new Error(context + ": Primitives are missing");
                 }
                 }
                 GLTF2.ArrayItem.Assign(primitives);
                 GLTF2.ArrayItem.Assign(primitives);
-                for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
-                    var primitive = primitives_1[_i];
-                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive));
+                if (primitives.length === 1) {
+                    var primitive = primitives[0];
+                    promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, babylonMesh));
+                }
+                else {
+                    node._primitiveBabylonMeshes = [];
+                    for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
+                        var primitive = primitives_1[_i];
+                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive._index, this._babylonScene, babylonMesh);
+                        node._primitiveBabylonMeshes.push(babylonMesh);
+                        promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive, primitiveBabylonMesh));
+                        this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                    }
                 }
                 }
                 if (node.skin != undefined) {
                 if (node.skin != undefined) {
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     var skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                     promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
                 }
                 }
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
-                    if (node._primitiveBabylonMeshes) {
-                        for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                            var primitiveBabylonMesh = _a[_i];
-                            primitiveBabylonMesh._refreshBoundingInfo(true);
-                        }
-                    }
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
+                        babylonMesh._refreshBoundingInfo(true);
+                    });
                 });
                 });
             };
             };
-            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive) {
+            GLTFLoader.prototype._loadPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
                 var promises = new Array();
                 var promises = new Array();
-                var babylonMesh = new BABYLON.Mesh((mesh.name || node._babylonMesh.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-                babylonMesh.setEnabled(false);
-                node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
-                node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                 promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonVertexData) {
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
                     new BABYLON.Geometry(babylonMesh.name, _this._babylonScene, babylonVertexData, false, babylonMesh);
@@ -4010,13 +4052,12 @@ var BABYLON;
                     babylonMesh.material = this._getDefaultMaterial();
                     babylonMesh.material = this._getDefaultMaterial();
                 }
                 }
                 else {
                 else {
-                    var material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
+                    var material = GLTFLoader._GetProperty(context + "/material}", this._gltf.materials, primitive.material);
+                    promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh, function (babylonMaterial) {
+                        babylonMesh.material = babylonMaterial;
+                    }));
                 }
                 }
-                this.onMeshLoadedObservable.notifyObservers(babylonMesh);
-                return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                });
+                return Promise.all(promises).then(function () { });
             };
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
                 var _this = this;
@@ -4030,7 +4071,7 @@ var BABYLON;
                 }
                 }
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                 if (primitive.mode != undefined && primitive.mode !== 4 /* TRIANGLES */) {
                     // TODO: handle other primitive modes
                     // TODO: handle other primitive modes
-                    throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
+                    throw new Error(context + ": Mode (" + primitive.mode + ") is not currently supported");
                 }
                 }
                 var promises = new Array();
                 var promises = new Array();
                 var babylonVertexData = new BABYLON.VertexData();
                 var babylonVertexData = new BABYLON.VertexData();
@@ -4172,7 +4213,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid component type " + accessor.componentType);
+                            throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                         }
                         }
                     }
                     }
                 }
                 }
@@ -4218,10 +4259,9 @@ var BABYLON;
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
             GLTFLoader.prototype._loadSkinAsync = function (context, node, mesh, skin) {
                 var _this = this;
                 var _this = this;
                 var assignSkeleton = function () {
                 var assignSkeleton = function () {
-                    for (var _i = 0, _a = node._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                        var babylonMesh = _a[_i];
+                    _this._forEachNodeMesh(node, function (babylonMesh) {
                         babylonMesh.skeleton = skin._babylonSkeleton;
                         babylonMesh.skeleton = skin._babylonSkeleton;
-                    }
+                    });
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.parent = _this._rootBabylonMesh;
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.position = BABYLON.Vector3.Zero();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
                     node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
@@ -4311,13 +4351,15 @@ var BABYLON;
                     var channel = _a[_i];
                     var channel = _a[_i];
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                     promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
                 }
                 }
+                this.onAnimationGroupLoadedObservable.notifyObservers(babylonAnimationGroup);
                 return Promise.all(promises).then(function () {
                 return Promise.all(promises).then(function () {
                     babylonAnimationGroup.normalize();
                     babylonAnimationGroup.normalize();
                 });
                 });
             };
             };
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
             GLTFLoader.prototype._loadAnimationChannelAsync = function (context, animationContext, animation, channel, babylonAnimationGroup) {
+                var _this = this;
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
                 var targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
-                if (!targetNode._babylonMesh) {
+                if (!targetNode._babylonMesh || targetNode.skin != undefined) {
                     return Promise.resolve();
                     return Promise.resolve();
                 }
                 }
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
                 var sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
@@ -4346,7 +4388,7 @@ var BABYLON;
                             break;
                             break;
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid target path " + channel.target.path);
+                            throw new Error(context + ": Invalid target path (" + channel.target.path + ")");
                         }
                         }
                     }
                     }
                     var outputBufferOffset = 0;
                     var outputBufferOffset = 0;
@@ -4438,11 +4480,10 @@ var BABYLON;
                                 value: key.value[targetIndex],
                                 value: key.value[targetIndex],
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                                 outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                             }); }));
                             }); }));
-                            for (var _i = 0, _a = targetNode._primitiveBabylonMeshes; _i < _a.length; _i++) {
-                                var babylonMesh = _a[_i];
+                            _this._forEachNodeMesh(targetNode, function (babylonMesh) {
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 var morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
                                 babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
-                            }
+                            });
                         };
                         };
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                         for (var targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) {
                             _loop_1(targetIndex);
                             _loop_1(targetIndex);
@@ -4473,7 +4514,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid interpolation " + sampler.interpolation);
+                        throw new Error(context + ": Invalid interpolation (" + sampler.interpolation + ")");
                     }
                     }
                 }
                 }
                 var inputData;
                 var inputData;
@@ -4558,12 +4599,12 @@ var BABYLON;
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                                 return _this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid component type " + accessor.componentType);
+                                throw new Error(context + ": Invalid component type (" + accessor.componentType + ")");
                             }
                             }
                         }
                         }
                     }
                     }
                     catch (e) {
                     catch (e) {
-                        throw new Error(context + ": " + e);
+                        throw new Error(context + ": " + e.messsage);
                     }
                     }
                 });
                 });
                 return accessor._data;
                 return accessor._data;
@@ -4635,25 +4676,24 @@ var BABYLON;
                 this._loadMaterialAlphaProperties(context, material);
                 this._loadMaterialAlphaProperties(context, material);
                 return Promise.all(promises).then(function () { });
                 return Promise.all(promises).then(function () { });
             };
             };
-            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
-                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh);
+            GLTFLoader.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
+                var promise = GLTF2.GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh, assign);
                 if (promise) {
                 if (promise) {
                     return promise;
                     return promise;
                 }
                 }
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes.push(babylonMesh);
                 material._babylonMeshes.push(babylonMesh);
-                if (material._loaded) {
-                    babylonMesh.material = material._babylonMaterial;
-                    return material._loaded;
+                if (!material._loaded) {
+                    var promises = new Array();
+                    var babylonMaterial = this._createMaterial(material);
+                    material._babylonMaterial = babylonMaterial;
+                    promises.push(this._loadMaterialBasePropertiesAsync(context, material));
+                    promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
+                    this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                    material._loaded = Promise.all(promises).then(function () { });
                 }
                 }
-                var promises = new Array();
-                var babylonMaterial = this._createMaterial(material);
-                material._babylonMaterial = babylonMaterial;
-                promises.push(this._loadMaterialBasePropertiesAsync(context, material));
-                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
-                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                babylonMesh.material = babylonMaterial;
-                return (material._loaded = Promise.all(promises).then(function () { }));
+                assign(material._babylonMaterial);
+                return material._loaded;
             };
             };
             GLTFLoader.prototype._createMaterial = function (material) {
             GLTFLoader.prototype._createMaterial = function (material) {
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
                 var babylonMaterial = new BABYLON.PBRMaterial(material.name || "material" + material._index, this._babylonScene);
@@ -4728,7 +4768,7 @@ var BABYLON;
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid alpha mode " + material.alphaMode);
+                        throw new Error(context + ": Invalid alpha mode (" + material.alphaMode + ")");
                     }
                     }
                 }
                 }
             };
             };
@@ -4746,7 +4786,7 @@ var BABYLON;
                     }
                     }
                 }, function (message, exception) {
                 }, function (message, exception) {
                     if (!_this._disposed) {
                     if (!_this._disposed) {
-                        deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                        deferred.reject(new Error(context + ": " + ((exception && exception.message) ? exception.message : message || "Failed to load texture")));
                     }
                     }
                 });
                 });
                 promises.push(deferred.promise);
                 promises.push(deferred.promise);
@@ -4850,7 +4890,7 @@ var BABYLON;
             };
             };
             GLTFLoader._GetProperty = function (context, array, index) {
             GLTFLoader._GetProperty = function (context, array, index) {
                 if (!array || index == undefined || !array[index]) {
                 if (!array || index == undefined || !array[index]) {
-                    throw new Error(context + ": Failed to find index " + index);
+                    throw new Error(context + ": Failed to find index (" + index + ")");
                 }
                 }
                 return array[index];
                 return array[index];
             };
             };
@@ -4862,7 +4902,7 @@ var BABYLON;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 33648 /* MIRRORED_REPEAT */: return BABYLON.Texture.MIRROR_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     case 10497 /* REPEAT */: return BABYLON.Texture.WRAP_ADDRESSMODE;
                     default:
                     default:
-                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode " + mode);
+                        BABYLON.Tools.Warn(context + ": Invalid texture wrap mode (" + mode + ")");
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                         return BABYLON.Texture.WRAP_ADDRESSMODE;
                 }
                 }
             };
             };
@@ -4879,13 +4919,13 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                             return BABYLON.Texture.LINEAR_LINEAR_MIPLINEAR;
                     }
                     }
                 }
                 }
                 else {
                 else {
                     if (magFilter !== 9728 /* NEAREST */) {
                     if (magFilter !== 9728 /* NEAREST */) {
-                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter " + magFilter);
+                        BABYLON.Tools.Warn(context + ": Invalid texture magnification filter (" + magFilter + ")");
                     }
                     }
                     switch (minFilter) {
                     switch (minFilter) {
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
                         case 9728 /* NEAREST */: return BABYLON.Texture.NEAREST_NEAREST;
@@ -4895,7 +4935,7 @@ var BABYLON;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9986 /* NEAREST_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_NEAREST_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         case 9987 /* LINEAR_MIPMAP_LINEAR */: return BABYLON.Texture.NEAREST_LINEAR_MIPLINEAR;
                         default:
                         default:
-                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                            BABYLON.Tools.Warn(context + ": Invalid texture minification filter (" + minFilter + ")");
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                             return BABYLON.Texture.NEAREST_NEAREST_MIPNEAREST;
                     }
                     }
                 }
                 }
@@ -4910,7 +4950,7 @@ var BABYLON;
                     case "MAT3": return 9;
                     case "MAT3": return 9;
                     case "MAT4": return 16;
                     case "MAT4": return 16;
                 }
                 }
-                throw new Error(context + ": Invalid type " + type);
+                throw new Error(context + ": Invalid type (" + type + ")");
             };
             };
             GLTFLoader._ValidateUri = function (uri) {
             GLTFLoader._ValidateUri = function (uri) {
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
                 return (BABYLON.Tools.IsBase64(uri) || uri.indexOf("..") === -1);
@@ -5022,7 +5062,7 @@ var BABYLON;
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             GLTFLoaderExtension.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) { return null; };
             /** Override this method to modify the default behavior for loading materials. */
             /** Override this method to modify the default behavior for loading materials. */
-            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh) { return null; };
+            GLTFLoaderExtension.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) { return null; };
             /** Override this method to modify the default behavior for loading uris. */
             /** Override this method to modify the default behavior for loading uris. */
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             GLTFLoaderExtension.prototype._loadUriAsync = function (context, uri) { return null; };
             // #endregion
             // #endregion
@@ -5059,8 +5099,8 @@ var BABYLON;
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
                 return loader._applyExtensions(function (extension) { return extension._loadVertexDataAsync(context, primitive, babylonMesh); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading materials. */
             /** Helper method called by the loader to allow extensions to override loading materials. */
-            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh) {
-                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh); });
+            GLTFLoaderExtension._LoadMaterialAsync = function (loader, context, material, babylonMesh, assign) {
+                return loader._applyExtensions(function (extension) { return extension._loadMaterialAsync(context, material, babylonMesh, assign); });
             };
             };
             /** Helper method called by the loader to allow extensions to override loading uris. */
             /** Helper method called by the loader to allow extensions to override loading uris. */
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
             GLTFLoaderExtension._LoadUriAsync = function (loader, context, uri) {
@@ -5115,7 +5155,10 @@ var BABYLON;
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                             var promise = _this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(function () {
                                 if (indexLOD !== 0) {
                                 if (indexLOD !== 0) {
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
                                     var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                    previousNodeLOD._babylonMesh.setEnabled(false);
+                                    if (previousNodeLOD._babylonMesh) {
+                                        previousNodeLOD._babylonMesh.dispose();
+                                        delete previousNodeLOD._babylonMesh;
+                                    }
                                 }
                                 }
                                 if (indexLOD !== nodeLODs.length - 1) {
                                 if (indexLOD !== nodeLODs.length - 1) {
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
                                     var nodeIndex = nodeLODs[indexLOD + 1]._index;
@@ -5139,7 +5182,7 @@ var BABYLON;
                         return firstPromise;
                         return firstPromise;
                     });
                     });
                 };
                 };
-                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                MSFT_lod.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     // Don't load material LODs if already loading a node LOD.
                     // Don't load material LODs if already loading a node LOD.
                     if (this._loadingNodeLOD) {
                     if (this._loadingNodeLOD) {
@@ -5156,7 +5199,15 @@ var BABYLON;
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                     _this._loadMaterialSignals[materialLOD._index] = new BABYLON.Deferred();
                                 }
                                 }
                             }
                             }
-                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(function () {
+                            var promise = _this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh, indexLOD === 0 ? assign : function () { }).then(function () {
+                                if (indexLOD !== 0) {
+                                    assign(materialLOD._babylonMaterial);
+                                    var previousMaterialLOD = materialLODs[indexLOD - 1];
+                                    if (previousMaterialLOD._babylonMaterial) {
+                                        previousMaterialLOD._babylonMaterial.dispose();
+                                        delete previousMaterialLOD._babylonMaterial;
+                                    }
+                                }
                                 if (indexLOD !== materialLODs.length - 1) {
                                 if (indexLOD !== materialLODs.length - 1) {
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     var materialIndex = materialLODs[indexLOD + 1]._index;
                                     if (_this._loadMaterialSignals[materialIndex]) {
                                     if (_this._loadMaterialSignals[materialIndex]) {
@@ -5325,26 +5376,25 @@ var BABYLON;
                     _this.name = NAME;
                     _this.name = NAME;
                     return _this;
                     return _this;
                 }
                 }
-                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadMaterialAsync = function (context, material, babylonMesh, assign) {
                     var _this = this;
                     var _this = this;
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                     return this._loadExtensionAsync(context, material, function (context, extension) {
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes = material._babylonMeshes || [];
                         material._babylonMeshes.push(babylonMesh);
                         material._babylonMeshes.push(babylonMesh);
-                        if (material._loaded) {
-                            babylonMesh.material = material._babylonMaterial;
-                            return material._loaded;
+                        if (!material._loaded) {
+                            var promises = new Array();
+                            var babylonMaterial = _this._loader._createMaterial(material);
+                            material._babylonMaterial = babylonMaterial;
+                            promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
+                            promises.push(_this._loadSpecularGlossinessPropertiesAsync(context, material, extension));
+                            _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            material._loaded = Promise.all(promises).then(function () { });
                         }
                         }
-                        var promises = new Array();
-                        var babylonMaterial = _this._loader._createMaterial(material);
-                        material._babylonMaterial = babylonMaterial;
-                        promises.push(_this._loader._loadMaterialBasePropertiesAsync(context, material));
-                        promises.push(_this._loadSpecularGlossinessPropertiesAsync(_this._loader, context, material, extension));
-                        _this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        babylonMesh.material = babylonMaterial;
-                        return (material._loaded = Promise.all(promises).then(function () { }));
+                        assign(material._babylonMaterial);
+                        return material._loaded;
                     });
                     });
                 };
                 };
-                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (loader, context, material, properties) {
+                KHR_materials_pbrSpecularGlossiness.prototype._loadSpecularGlossinessPropertiesAsync = function (context, material, properties) {
                     var promises = new Array();
                     var promises = new Array();
                     var babylonMaterial = material._babylonMaterial;
                     var babylonMaterial = material._babylonMaterial;
                     if (properties.diffuseFactor) {
                     if (properties.diffuseFactor) {
@@ -5357,18 +5407,18 @@ var BABYLON;
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.reflectivityColor = properties.specularFactor ? BABYLON.Color3.FromArray(properties.specularFactor) : BABYLON.Color3.White();
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
                     if (properties.diffuseTexture) {
                     if (properties.diffuseTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, function (texture) {
                             babylonMaterial.albedoTexture = texture;
                             babylonMaterial.albedoTexture = texture;
                         }));
                         }));
                     }
                     }
                     if (properties.specularGlossinessTexture) {
                     if (properties.specularGlossinessTexture) {
-                        promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
+                        promises.push(this._loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, function (texture) {
                             babylonMaterial.reflectivityTexture = texture;
                             babylonMaterial.reflectivityTexture = texture;
                         }));
                         }));
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.reflectivityTexture.hasAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                         babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                     }
                     }
-                    loader._loadMaterialAlphaProperties(context, material);
+                    this._loader._loadMaterialAlphaProperties(context, material);
                     return Promise.all(promises).then(function () { });
                     return Promise.all(promises).then(function () { });
                 };
                 };
                 return KHR_materials_pbrSpecularGlossiness;
                 return KHR_materials_pbrSpecularGlossiness;
@@ -5445,7 +5495,7 @@ var BABYLON;
                                 break;
                                 break;
                             }
                             }
                             default: {
                             default: {
-                                throw new Error(context + ": Invalid light type " + light.type);
+                                throw new Error(context + ": Invalid light type (" + light.type + ")");
                             }
                             }
                         }
                         }
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
                         babylonLight.diffuse = light.color ? BABYLON.Color3.FromArray(light.color) : BABYLON.Color3.White();
@@ -5458,7 +5508,7 @@ var BABYLON;
                     get: function () {
                     get: function () {
                         var extensions = this._loader._gltf.extensions;
                         var extensions = this._loader._gltf.extensions;
                         if (!extensions || !extensions[this.name]) {
                         if (!extensions || !extensions[this.name]) {
-                            throw new Error("#/extensions: " + this.name + " not found");
+                            throw new Error("#/extensions: '" + this.name + "' not found");
                         }
                         }
                         var extension = extensions[this.name];
                         var extension = extensions[this.name];
                         return extension.lights;
                         return extension.lights;

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


+ 25 - 10
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -154,6 +154,7 @@ declare module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -209,13 +210,19 @@ declare module BABYLON {
          */
          */
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         private _onTextureLoadedObserver;
         private _onTextureLoadedObserver;
-        onTextureLoaded: (Texture: BaseTexture) => void;
+        onTextureLoaded: (texture: BaseTexture) => void;
         /**
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
          */
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         private _onMaterialLoadedObserver;
         private _onMaterialLoadedObserver;
-        onMaterialLoaded: (Material: Material) => void;
+        onMaterialLoaded: (material: Material) => void;
+        /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+         */
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
+        private _onAnimationGroupLoadedObserver;
+        onAnimationGroupLoaded: (animationGroup: AnimationGroup) => void;
         /**
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets with LODs, raised when all of the LODs are complete.
@@ -238,6 +245,11 @@ declare module BABYLON {
         private _onExtensionLoadedObserver;
         private _onExtensionLoadedObserver;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         onExtensionLoaded: (extension: IGLTFLoaderExtension) => void;
         /**
         /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        whenCompleteAsync(): Promise<void>;
+        /**
          * The loader state or null if not active.
          * The loader state or null if not active.
          */
          */
         readonly loaderState: Nullable<GLTFLoaderState>;
         readonly loaderState: Nullable<GLTFLoaderState>;
@@ -671,6 +683,7 @@ declare module BABYLON.GLTF1 {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         state: Nullable<GLTFLoaderState>;
         state: Nullable<GLTFLoaderState>;
@@ -947,6 +960,7 @@ declare module BABYLON.GLTF2 {
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onMeshLoadedObservable: Observable<AbstractMesh>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onTextureLoadedObservable: Observable<BaseTexture>;
         readonly onMaterialLoadedObservable: Observable<Material>;
         readonly onMaterialLoadedObservable: Observable<Material>;
+        readonly onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly onCompleteObservable: Observable<IGLTFLoader>;
         readonly state: Nullable<GLTFLoaderState>;
         readonly state: Nullable<GLTFLoaderState>;
@@ -965,12 +979,13 @@ declare module BABYLON.GLTF2 {
         private _createRootNode();
         private _createRootNode();
         private _loadNodesAsync(nodes);
         private _loadNodesAsync(nodes);
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
         _loadSceneAsync(context: string, scene: ILoaderScene): Promise<void>;
+        private _forEachNodeMesh(node, callback);
         private _getMeshes();
         private _getMeshes();
         private _getSkeletons();
         private _getSkeletons();
         private _startAnimations();
         private _startAnimations();
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
         _loadNodeAsync(context: string, node: ILoaderNode): Promise<void>;
-        private _loadMeshAsync(context, node, mesh);
-        private _loadPrimitiveAsync(context, node, mesh, primitive);
+        private _loadMeshAsync(context, node, mesh, babylonMesh);
+        private _loadPrimitiveAsync(context, node, mesh, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _loadVertexDataAsync(context, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _createMorphTargets(context, node, mesh, primitive, babylonMesh);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
         private _loadMorphTargetsAsync(context, primitive, babylonMesh, babylonVertexData);
@@ -994,7 +1009,7 @@ declare module BABYLON.GLTF2 {
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _buildArrayBuffer<T>(typedArray, data, byteOffset, count, numComponents, byteStride?);
         private _getDefaultMaterial();
         private _getDefaultMaterial();
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
         private _loadMaterialMetallicRoughnessPropertiesAsync(context, material);
-        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Promise<void>;
+        _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Promise<void>;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _createMaterial(material: ILoaderMaterial): PBRMaterial;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialBasePropertiesAsync(context: string, material: ILoaderMaterial): Promise<void>;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
         _loadMaterialAlphaProperties(context: string, material: ILoaderMaterial): void;
@@ -1030,7 +1045,7 @@ declare module BABYLON.GLTF2 {
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Override this method to modify the default behavior for loading materials. */
         /** Override this method to modify the default behavior for loading materials. */
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Override this method to modify the default behavior for loading uris. */
         /** Override this method to modify the default behavior for loading uris. */
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /** Helper method called by a loader extension to load an glTF extension. */
         /** Helper method called by a loader extension to load an glTF extension. */
@@ -1042,7 +1057,7 @@ declare module BABYLON.GLTF2 {
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>>;
         /** Helper method called by the loader to allow extensions to override loading materials. */
         /** Helper method called by the loader to allow extensions to override loading materials. */
-        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         /** Helper method called by the loader to allow extensions to override loading uris. */
         /** Helper method called by the loader to allow extensions to override loading uris. */
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
     }
     }
@@ -1061,7 +1076,7 @@ declare module BABYLON.GLTF2.Extensions {
         private _loadingMaterialLOD;
         private _loadingMaterialLOD;
         private _loadMaterialSignals;
         private _loadMaterialSignals;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>>;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
         /**
         /**
          * Gets an array of LOD properties from lowest to highest.
          * Gets an array of LOD properties from lowest to highest.
@@ -1085,8 +1100,8 @@ declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
 declare module BABYLON.GLTF2.Extensions {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
     class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         readonly name: string;
         readonly name: string;
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>>;
-        private _loadSpecularGlossinessPropertiesAsync(loader, context, material, properties);
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>>;
+        private _loadSpecularGlossinessPropertiesAsync(context, material, properties);
     }
     }
 }
 }
 
 

+ 4 - 282
dist/preview release/materialsLibrary/babylon.customMaterial.d.ts

@@ -1,277 +1,5 @@
 
 
 declare module BABYLON {
 declare module BABYLON {
-    class StandardMaterialDefines_OldVer extends MaterialDefines implements IImageProcessingConfigurationDefines {
-        DIFFUSE: boolean;
-        AMBIENT: boolean;
-        OPACITY: boolean;
-        OPACITYRGB: boolean;
-        REFLECTION: boolean;
-        EMISSIVE: boolean;
-        SPECULAR: boolean;
-        BUMP: boolean;
-        PARALLAX: boolean;
-        PARALLAXOCCLUSION: boolean;
-        SPECULAROVERALPHA: boolean;
-        CLIPPLANE: boolean;
-        ALPHATEST: boolean;
-        ALPHAFROMDIFFUSE: boolean;
-        POINTSIZE: boolean;
-        FOG: boolean;
-        SPECULARTERM: boolean;
-        DIFFUSEFRESNEL: boolean;
-        OPACITYFRESNEL: boolean;
-        REFLECTIONFRESNEL: boolean;
-        REFRACTIONFRESNEL: boolean;
-        EMISSIVEFRESNEL: boolean;
-        FRESNEL: boolean;
-        NORMAL: boolean;
-        UV1: boolean;
-        UV2: boolean;
-        VERTEXCOLOR: boolean;
-        VERTEXALPHA: boolean;
-        NUM_BONE_INFLUENCERS: number;
-        BonesPerMesh: number;
-        INSTANCES: boolean;
-        GLOSSINESS: boolean;
-        ROUGHNESS: boolean;
-        EMISSIVEASILLUMINATION: boolean;
-        LINKEMISSIVEWITHDIFFUSE: boolean;
-        REFLECTIONFRESNELFROMSPECULAR: boolean;
-        LIGHTMAP: boolean;
-        USELIGHTMAPASSHADOWMAP: boolean;
-        REFLECTIONMAP_3D: boolean;
-        REFLECTIONMAP_SPHERICAL: boolean;
-        REFLECTIONMAP_PLANAR: boolean;
-        REFLECTIONMAP_CUBIC: boolean;
-        REFLECTIONMAP_PROJECTION: boolean;
-        REFLECTIONMAP_SKYBOX: boolean;
-        REFLECTIONMAP_EXPLICIT: boolean;
-        REFLECTIONMAP_EQUIRECTANGULAR: boolean;
-        REFLECTIONMAP_EQUIRECTANGULAR_FIXED: boolean;
-        REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED: boolean;
-        INVERTCUBICMAP: boolean;
-        LOGARITHMICDEPTH: boolean;
-        REFRACTION: boolean;
-        REFRACTIONMAP_3D: boolean;
-        REFLECTIONOVERALPHA: boolean;
-        TWOSIDEDLIGHTING: boolean;
-        SHADOWFLOAT: boolean;
-        MORPHTARGETS: boolean;
-        MORPHTARGETS_NORMAL: boolean;
-        MORPHTARGETS_TANGENT: boolean;
-        NUM_MORPH_INFLUENCERS: number;
-        IMAGEPROCESSING: boolean;
-        VIGNETTE: boolean;
-        VIGNETTEBLENDMODEMULTIPLY: boolean;
-        VIGNETTEBLENDMODEOPAQUE: boolean;
-        TONEMAPPING: boolean;
-        CONTRAST: boolean;
-        COLORCURVES: boolean;
-        COLORGRADING: boolean;
-        COLORGRADING3D: boolean;
-        SAMPLER3DGREENDEPTH: boolean;
-        SAMPLER3DBGRMAP: boolean;
-        IMAGEPROCESSINGPOSTPROCESS: boolean;
-        EXPOSURE: boolean;
-        GRAIN: boolean;
-        constructor();
-        setReflectionMode(modeToEnable: string): void;
-    }
-    class StandardMaterial_OldVer extends PushMaterial {
-        private _diffuseTexture;
-        diffuseTexture: BaseTexture;
-        private _ambientTexture;
-        ambientTexture: BaseTexture;
-        private _opacityTexture;
-        opacityTexture: BaseTexture;
-        private _reflectionTexture;
-        reflectionTexture: BaseTexture;
-        private _emissiveTexture;
-        emissiveTexture: BaseTexture;
-        private _specularTexture;
-        specularTexture: BaseTexture;
-        private _bumpTexture;
-        bumpTexture: BaseTexture;
-        private _lightmapTexture;
-        lightmapTexture: BaseTexture;
-        private _refractionTexture;
-        refractionTexture: BaseTexture;
-        ambientColor: Color3;
-        diffuseColor: Color3;
-        specularColor: Color3;
-        emissiveColor: Color3;
-        specularPower: number;
-        private _useAlphaFromDiffuseTexture;
-        useAlphaFromDiffuseTexture: boolean;
-        private _useEmissiveAsIllumination;
-        useEmissiveAsIllumination: boolean;
-        private _linkEmissiveWithDiffuse;
-        linkEmissiveWithDiffuse: boolean;
-        private _useSpecularOverAlpha;
-        useSpecularOverAlpha: boolean;
-        private _useReflectionOverAlpha;
-        useReflectionOverAlpha: boolean;
-        private _disableLighting;
-        disableLighting: boolean;
-        private _useParallax;
-        useParallax: boolean;
-        private _useParallaxOcclusion;
-        useParallaxOcclusion: boolean;
-        parallaxScaleBias: number;
-        private _roughness;
-        roughness: number;
-        indexOfRefraction: number;
-        invertRefractionY: boolean;
-        private _useLightmapAsShadowmap;
-        useLightmapAsShadowmap: boolean;
-        private _diffuseFresnelParameters;
-        diffuseFresnelParameters: FresnelParameters;
-        private _opacityFresnelParameters;
-        opacityFresnelParameters: FresnelParameters;
-        private _reflectionFresnelParameters;
-        reflectionFresnelParameters: FresnelParameters;
-        private _refractionFresnelParameters;
-        refractionFresnelParameters: FresnelParameters;
-        private _emissiveFresnelParameters;
-        emissiveFresnelParameters: FresnelParameters;
-        private _useReflectionFresnelFromSpecular;
-        useReflectionFresnelFromSpecular: boolean;
-        private _useGlossinessFromSpecularMapAlpha;
-        useGlossinessFromSpecularMapAlpha: boolean;
-        private _maxSimultaneousLights;
-        maxSimultaneousLights: number;
-        /**
-         * If sets to true, x component of normal map value will invert (x = 1.0 - x).
-         */
-        private _invertNormalMapX;
-        invertNormalMapX: boolean;
-        /**
-         * If sets to true, y component of normal map value will invert (y = 1.0 - y).
-         */
-        private _invertNormalMapY;
-        invertNormalMapY: boolean;
-        /**
-         * If sets to true and backfaceCulling is false, normals will be flipped on the backside.
-         */
-        private _twoSidedLighting;
-        twoSidedLighting: boolean;
-        /**
-         * Default configuration related to image processing available in the standard Material.
-         */
-        protected _imageProcessingConfiguration: ImageProcessingConfiguration;
-        /**
-         * Gets the image processing configuration used either in this material.
-         */
-        /**
-         * Sets the Default image processing configuration used either in the this material.
-         *
-         * If sets to null, the scene one is in use.
-         */
-        imageProcessingConfiguration: ImageProcessingConfiguration;
-        /**
-         * Keep track of the image processing observer to allow dispose and replace.
-         */
-        private _imageProcessingObserver;
-        /**
-         * Attaches a new image processing configuration to the Standard Material.
-         * @param configuration
-         */
-        protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void;
-        /**
-         * Gets wether the color curves effect is enabled.
-         */
-        /**
-         * Sets wether the color curves effect is enabled.
-         */
-        cameraColorCurvesEnabled: boolean;
-        /**
-         * Gets wether the color grading effect is enabled.
-         */
-        /**
-         * Gets wether the color grading effect is enabled.
-         */
-        cameraColorGradingEnabled: boolean;
-        /**
-         * Gets wether tonemapping is enabled or not.
-         */
-        /**
-         * Sets wether tonemapping is enabled or not
-         */
-        cameraToneMappingEnabled: boolean;
-        /**
-         * The camera exposure used on this material.
-         * This property is here and not in the camera to allow controlling exposure without full screen post process.
-         * This corresponds to a photographic exposure.
-         */
-        /**
-         * The camera exposure used on this material.
-         * This property is here and not in the camera to allow controlling exposure without full screen post process.
-         * This corresponds to a photographic exposure.
-         */
-        cameraExposure: number;
-        /**
-         * Gets The camera contrast used on this material.
-         */
-        /**
-         * Sets The camera contrast used on this material.
-         */
-        cameraContrast: number;
-        /**
-         * Gets the Color Grading 2D Lookup Texture.
-         */
-        /**
-         * Sets the Color Grading 2D Lookup Texture.
-         */
-        cameraColorGradingTexture: Nullable<BaseTexture>;
-        protected _shouldTurnAlphaTestOn(mesh: AbstractMesh): boolean;
-        customShaderNameResolve: (shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines_OldVer) => string;
-        protected _renderTargets: SmartArray<RenderTargetTexture>;
-        protected _worldViewProjectionMatrix: Matrix;
-        protected _globalAmbientColor: Color3;
-        protected _useLogarithmicDepth: boolean;
-        constructor(name: string, scene: Scene);
-        getClassName(): string;
-        useLogarithmicDepth: boolean;
-        needAlphaBlending(): boolean;
-        needAlphaTesting(): boolean;
-        protected _shouldUseAlphaFromDiffuseTexture(): boolean;
-        getAlphaTestTexture(): BaseTexture;
-        /**
-         * Child classes can use it to update shaders
-         */
-        isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean;
-        buildUniformLayout(): void;
-        unbind(): void;
-        bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void;
-        getAnimatables(): IAnimatable[];
-        getActiveTextures(): BaseTexture[];
-        dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void;
-        clone(name: string): StandardMaterial_OldVer;
-        serialize(): any;
-        static Parse(source: any, scene: Scene, rootUrl: string): StandardMaterial_OldVer;
-        static _DiffuseTextureEnabled: boolean;
-        static DiffuseTextureEnabled: boolean;
-        static _AmbientTextureEnabled: boolean;
-        static AmbientTextureEnabled: boolean;
-        static _OpacityTextureEnabled: boolean;
-        static OpacityTextureEnabled: boolean;
-        static _ReflectionTextureEnabled: boolean;
-        static ReflectionTextureEnabled: boolean;
-        static _EmissiveTextureEnabled: boolean;
-        static EmissiveTextureEnabled: boolean;
-        static _SpecularTextureEnabled: boolean;
-        static SpecularTextureEnabled: boolean;
-        static _BumpTextureEnabled: boolean;
-        static BumpTextureEnabled: boolean;
-        static _LightmapTextureEnabled: boolean;
-        static LightmapTextureEnabled: boolean;
-        static _RefractionTextureEnabled: boolean;
-        static RefractionTextureEnabled: boolean;
-        static _ColorGradingTextureEnabled: boolean;
-        static ColorGradingTextureEnabled: boolean;
-        static _FresnelEnabled: boolean;
-        static FresnelEnabled: boolean;
-    }
     class CustomShaderStructure {
     class CustomShaderStructure {
         FragmentStore: string;
         FragmentStore: string;
         VertexStore: string;
         VertexStore: string;
@@ -291,26 +19,20 @@ declare module BABYLON {
         Vertex_Before_PositionUpdated: string;
         Vertex_Before_PositionUpdated: string;
         Vertex_Before_NormalUpdated: string;
         Vertex_Before_NormalUpdated: string;
     }
     }
-    class ShaderForVer3_0 extends CustomShaderStructure {
-        constructor();
-    }
-    class StandardShaderVersions {
-        static Ver3_0: string;
-    }
-    class CustomMaterial extends StandardMaterial_OldVer {
+    class CustomMaterial extends StandardMaterial {
         static ShaderIndexer: number;
         static ShaderIndexer: number;
         CustomParts: ShaderSpecialParts;
         CustomParts: ShaderSpecialParts;
-        ShaderVersion: CustomShaderStructure;
         _isCreatedShader: boolean;
         _isCreatedShader: boolean;
         _createdShaderName: string;
         _createdShaderName: string;
         _customUniform: string[];
         _customUniform: string[];
         _newUniforms: string[];
         _newUniforms: string[];
         _newUniformInstances: any[];
         _newUniformInstances: any[];
         _newSamplerInstances: Texture[];
         _newSamplerInstances: Texture[];
+        FragmentShader: string;
+        VertexShader: string;
         AttachAfterBind(mesh: Mesh, effect: Effect): void;
         AttachAfterBind(mesh: Mesh, effect: Effect): void;
         ReviewUniform(name: string, arr: string[]): string[];
         ReviewUniform(name: string, arr: string[]): string[];
-        Builder(shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines_OldVer): string;
-        SelectVersion(ver: string): void;
+        Builder(shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines): string;
         constructor(name: string, scene: Scene);
         constructor(name: string, scene: Scene);
         AddUniform(name: string, kind: string, param: any): CustomMaterial;
         AddUniform(name: string, kind: string, param: any): CustomMaterial;
         Fragment_Begin(shaderPart: string): CustomMaterial;
         Fragment_Begin(shaderPart: string): CustomMaterial;

File diff suppressed because it is too large
+ 19 - 1908
dist/preview release/materialsLibrary/babylon.customMaterial.js


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


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


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.furMaterial.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.shadowOnlyMaterial.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/materialsLibrary/babylon.waterMaterial.min.js


File diff suppressed because it is too large
+ 23 - 1907
dist/preview release/materialsLibrary/babylonjs.materials.js


File diff suppressed because it is too large
+ 5 - 7
dist/preview release/materialsLibrary/babylonjs.materials.min.js


+ 4 - 282
dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts

@@ -506,278 +506,6 @@ declare module BABYLON {
 
 
 
 
 declare module BABYLON {
 declare module BABYLON {
-    class StandardMaterialDefines_OldVer extends MaterialDefines implements IImageProcessingConfigurationDefines {
-        DIFFUSE: boolean;
-        AMBIENT: boolean;
-        OPACITY: boolean;
-        OPACITYRGB: boolean;
-        REFLECTION: boolean;
-        EMISSIVE: boolean;
-        SPECULAR: boolean;
-        BUMP: boolean;
-        PARALLAX: boolean;
-        PARALLAXOCCLUSION: boolean;
-        SPECULAROVERALPHA: boolean;
-        CLIPPLANE: boolean;
-        ALPHATEST: boolean;
-        ALPHAFROMDIFFUSE: boolean;
-        POINTSIZE: boolean;
-        FOG: boolean;
-        SPECULARTERM: boolean;
-        DIFFUSEFRESNEL: boolean;
-        OPACITYFRESNEL: boolean;
-        REFLECTIONFRESNEL: boolean;
-        REFRACTIONFRESNEL: boolean;
-        EMISSIVEFRESNEL: boolean;
-        FRESNEL: boolean;
-        NORMAL: boolean;
-        UV1: boolean;
-        UV2: boolean;
-        VERTEXCOLOR: boolean;
-        VERTEXALPHA: boolean;
-        NUM_BONE_INFLUENCERS: number;
-        BonesPerMesh: number;
-        INSTANCES: boolean;
-        GLOSSINESS: boolean;
-        ROUGHNESS: boolean;
-        EMISSIVEASILLUMINATION: boolean;
-        LINKEMISSIVEWITHDIFFUSE: boolean;
-        REFLECTIONFRESNELFROMSPECULAR: boolean;
-        LIGHTMAP: boolean;
-        USELIGHTMAPASSHADOWMAP: boolean;
-        REFLECTIONMAP_3D: boolean;
-        REFLECTIONMAP_SPHERICAL: boolean;
-        REFLECTIONMAP_PLANAR: boolean;
-        REFLECTIONMAP_CUBIC: boolean;
-        REFLECTIONMAP_PROJECTION: boolean;
-        REFLECTIONMAP_SKYBOX: boolean;
-        REFLECTIONMAP_EXPLICIT: boolean;
-        REFLECTIONMAP_EQUIRECTANGULAR: boolean;
-        REFLECTIONMAP_EQUIRECTANGULAR_FIXED: boolean;
-        REFLECTIONMAP_MIRROREDEQUIRECTANGULAR_FIXED: boolean;
-        INVERTCUBICMAP: boolean;
-        LOGARITHMICDEPTH: boolean;
-        REFRACTION: boolean;
-        REFRACTIONMAP_3D: boolean;
-        REFLECTIONOVERALPHA: boolean;
-        TWOSIDEDLIGHTING: boolean;
-        SHADOWFLOAT: boolean;
-        MORPHTARGETS: boolean;
-        MORPHTARGETS_NORMAL: boolean;
-        MORPHTARGETS_TANGENT: boolean;
-        NUM_MORPH_INFLUENCERS: number;
-        IMAGEPROCESSING: boolean;
-        VIGNETTE: boolean;
-        VIGNETTEBLENDMODEMULTIPLY: boolean;
-        VIGNETTEBLENDMODEOPAQUE: boolean;
-        TONEMAPPING: boolean;
-        CONTRAST: boolean;
-        COLORCURVES: boolean;
-        COLORGRADING: boolean;
-        COLORGRADING3D: boolean;
-        SAMPLER3DGREENDEPTH: boolean;
-        SAMPLER3DBGRMAP: boolean;
-        IMAGEPROCESSINGPOSTPROCESS: boolean;
-        EXPOSURE: boolean;
-        GRAIN: boolean;
-        constructor();
-        setReflectionMode(modeToEnable: string): void;
-    }
-    class StandardMaterial_OldVer extends PushMaterial {
-        private _diffuseTexture;
-        diffuseTexture: BaseTexture;
-        private _ambientTexture;
-        ambientTexture: BaseTexture;
-        private _opacityTexture;
-        opacityTexture: BaseTexture;
-        private _reflectionTexture;
-        reflectionTexture: BaseTexture;
-        private _emissiveTexture;
-        emissiveTexture: BaseTexture;
-        private _specularTexture;
-        specularTexture: BaseTexture;
-        private _bumpTexture;
-        bumpTexture: BaseTexture;
-        private _lightmapTexture;
-        lightmapTexture: BaseTexture;
-        private _refractionTexture;
-        refractionTexture: BaseTexture;
-        ambientColor: Color3;
-        diffuseColor: Color3;
-        specularColor: Color3;
-        emissiveColor: Color3;
-        specularPower: number;
-        private _useAlphaFromDiffuseTexture;
-        useAlphaFromDiffuseTexture: boolean;
-        private _useEmissiveAsIllumination;
-        useEmissiveAsIllumination: boolean;
-        private _linkEmissiveWithDiffuse;
-        linkEmissiveWithDiffuse: boolean;
-        private _useSpecularOverAlpha;
-        useSpecularOverAlpha: boolean;
-        private _useReflectionOverAlpha;
-        useReflectionOverAlpha: boolean;
-        private _disableLighting;
-        disableLighting: boolean;
-        private _useParallax;
-        useParallax: boolean;
-        private _useParallaxOcclusion;
-        useParallaxOcclusion: boolean;
-        parallaxScaleBias: number;
-        private _roughness;
-        roughness: number;
-        indexOfRefraction: number;
-        invertRefractionY: boolean;
-        private _useLightmapAsShadowmap;
-        useLightmapAsShadowmap: boolean;
-        private _diffuseFresnelParameters;
-        diffuseFresnelParameters: FresnelParameters;
-        private _opacityFresnelParameters;
-        opacityFresnelParameters: FresnelParameters;
-        private _reflectionFresnelParameters;
-        reflectionFresnelParameters: FresnelParameters;
-        private _refractionFresnelParameters;
-        refractionFresnelParameters: FresnelParameters;
-        private _emissiveFresnelParameters;
-        emissiveFresnelParameters: FresnelParameters;
-        private _useReflectionFresnelFromSpecular;
-        useReflectionFresnelFromSpecular: boolean;
-        private _useGlossinessFromSpecularMapAlpha;
-        useGlossinessFromSpecularMapAlpha: boolean;
-        private _maxSimultaneousLights;
-        maxSimultaneousLights: number;
-        /**
-         * If sets to true, x component of normal map value will invert (x = 1.0 - x).
-         */
-        private _invertNormalMapX;
-        invertNormalMapX: boolean;
-        /**
-         * If sets to true, y component of normal map value will invert (y = 1.0 - y).
-         */
-        private _invertNormalMapY;
-        invertNormalMapY: boolean;
-        /**
-         * If sets to true and backfaceCulling is false, normals will be flipped on the backside.
-         */
-        private _twoSidedLighting;
-        twoSidedLighting: boolean;
-        /**
-         * Default configuration related to image processing available in the standard Material.
-         */
-        protected _imageProcessingConfiguration: ImageProcessingConfiguration;
-        /**
-         * Gets the image processing configuration used either in this material.
-         */
-        /**
-         * Sets the Default image processing configuration used either in the this material.
-         *
-         * If sets to null, the scene one is in use.
-         */
-        imageProcessingConfiguration: ImageProcessingConfiguration;
-        /**
-         * Keep track of the image processing observer to allow dispose and replace.
-         */
-        private _imageProcessingObserver;
-        /**
-         * Attaches a new image processing configuration to the Standard Material.
-         * @param configuration
-         */
-        protected _attachImageProcessingConfiguration(configuration: Nullable<ImageProcessingConfiguration>): void;
-        /**
-         * Gets wether the color curves effect is enabled.
-         */
-        /**
-         * Sets wether the color curves effect is enabled.
-         */
-        cameraColorCurvesEnabled: boolean;
-        /**
-         * Gets wether the color grading effect is enabled.
-         */
-        /**
-         * Gets wether the color grading effect is enabled.
-         */
-        cameraColorGradingEnabled: boolean;
-        /**
-         * Gets wether tonemapping is enabled or not.
-         */
-        /**
-         * Sets wether tonemapping is enabled or not
-         */
-        cameraToneMappingEnabled: boolean;
-        /**
-         * The camera exposure used on this material.
-         * This property is here and not in the camera to allow controlling exposure without full screen post process.
-         * This corresponds to a photographic exposure.
-         */
-        /**
-         * The camera exposure used on this material.
-         * This property is here and not in the camera to allow controlling exposure without full screen post process.
-         * This corresponds to a photographic exposure.
-         */
-        cameraExposure: number;
-        /**
-         * Gets The camera contrast used on this material.
-         */
-        /**
-         * Sets The camera contrast used on this material.
-         */
-        cameraContrast: number;
-        /**
-         * Gets the Color Grading 2D Lookup Texture.
-         */
-        /**
-         * Sets the Color Grading 2D Lookup Texture.
-         */
-        cameraColorGradingTexture: Nullable<BaseTexture>;
-        protected _shouldTurnAlphaTestOn(mesh: AbstractMesh): boolean;
-        customShaderNameResolve: (shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines_OldVer) => string;
-        protected _renderTargets: SmartArray<RenderTargetTexture>;
-        protected _worldViewProjectionMatrix: Matrix;
-        protected _globalAmbientColor: Color3;
-        protected _useLogarithmicDepth: boolean;
-        constructor(name: string, scene: Scene);
-        getClassName(): string;
-        useLogarithmicDepth: boolean;
-        needAlphaBlending(): boolean;
-        needAlphaTesting(): boolean;
-        protected _shouldUseAlphaFromDiffuseTexture(): boolean;
-        getAlphaTestTexture(): BaseTexture;
-        /**
-         * Child classes can use it to update shaders
-         */
-        isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean;
-        buildUniformLayout(): void;
-        unbind(): void;
-        bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void;
-        getAnimatables(): IAnimatable[];
-        getActiveTextures(): BaseTexture[];
-        dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void;
-        clone(name: string): StandardMaterial_OldVer;
-        serialize(): any;
-        static Parse(source: any, scene: Scene, rootUrl: string): StandardMaterial_OldVer;
-        static _DiffuseTextureEnabled: boolean;
-        static DiffuseTextureEnabled: boolean;
-        static _AmbientTextureEnabled: boolean;
-        static AmbientTextureEnabled: boolean;
-        static _OpacityTextureEnabled: boolean;
-        static OpacityTextureEnabled: boolean;
-        static _ReflectionTextureEnabled: boolean;
-        static ReflectionTextureEnabled: boolean;
-        static _EmissiveTextureEnabled: boolean;
-        static EmissiveTextureEnabled: boolean;
-        static _SpecularTextureEnabled: boolean;
-        static SpecularTextureEnabled: boolean;
-        static _BumpTextureEnabled: boolean;
-        static BumpTextureEnabled: boolean;
-        static _LightmapTextureEnabled: boolean;
-        static LightmapTextureEnabled: boolean;
-        static _RefractionTextureEnabled: boolean;
-        static RefractionTextureEnabled: boolean;
-        static _ColorGradingTextureEnabled: boolean;
-        static ColorGradingTextureEnabled: boolean;
-        static _FresnelEnabled: boolean;
-        static FresnelEnabled: boolean;
-    }
     class CustomShaderStructure {
     class CustomShaderStructure {
         FragmentStore: string;
         FragmentStore: string;
         VertexStore: string;
         VertexStore: string;
@@ -797,26 +525,20 @@ declare module BABYLON {
         Vertex_Before_PositionUpdated: string;
         Vertex_Before_PositionUpdated: string;
         Vertex_Before_NormalUpdated: string;
         Vertex_Before_NormalUpdated: string;
     }
     }
-    class ShaderForVer3_0 extends CustomShaderStructure {
-        constructor();
-    }
-    class StandardShaderVersions {
-        static Ver3_0: string;
-    }
-    class CustomMaterial extends StandardMaterial_OldVer {
+    class CustomMaterial extends StandardMaterial {
         static ShaderIndexer: number;
         static ShaderIndexer: number;
         CustomParts: ShaderSpecialParts;
         CustomParts: ShaderSpecialParts;
-        ShaderVersion: CustomShaderStructure;
         _isCreatedShader: boolean;
         _isCreatedShader: boolean;
         _createdShaderName: string;
         _createdShaderName: string;
         _customUniform: string[];
         _customUniform: string[];
         _newUniforms: string[];
         _newUniforms: string[];
         _newUniformInstances: any[];
         _newUniformInstances: any[];
         _newSamplerInstances: Texture[];
         _newSamplerInstances: Texture[];
+        FragmentShader: string;
+        VertexShader: string;
         AttachAfterBind(mesh: Mesh, effect: Effect): void;
         AttachAfterBind(mesh: Mesh, effect: Effect): void;
         ReviewUniform(name: string, arr: string[]): string[];
         ReviewUniform(name: string, arr: string[]): string[];
-        Builder(shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines_OldVer): string;
-        SelectVersion(ver: string): void;
+        Builder(shaderName: string, uniforms: string[], uniformBuffers: string[], samplers: string[], defines: StandardMaterialDefines): string;
         constructor(name: string, scene: Scene);
         constructor(name: string, scene: Scene);
         AddUniform(name: string, kind: string, param: any): CustomMaterial;
         AddUniform(name: string, kind: string, param: any): CustomMaterial;
         Fragment_Begin(shaderPart: string): CustomMaterial;
         Fragment_Begin(shaderPart: string): CustomMaterial;

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.asciiArtPostProcess.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylon.digitalRainPostProcess.min.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/postProcessesLibrary/babylonjs.postProcess.min.js


+ 86 - 29
dist/preview release/serializers/babylon.glTF2Serializer.d.ts

@@ -290,23 +290,33 @@ declare module BABYLON.GLTF2 {
         /**
         /**
          * Represents the dielectric specular values for R, G and B.
          * Represents the dielectric specular values for R, G and B.
          */
          */
-        private static readonly dielectricSpecular;
+        private static readonly _dielectricSpecular;
         /**
         /**
          * Allows the maximum specular power to be defined for material calculations.
          * Allows the maximum specular power to be defined for material calculations.
          */
          */
-        private static maxSpecularPower;
-        private static epsilon;
+        private static _maxSpecularPower;
+        /**
+         * Numeric tolerance value
+         */
+        private static _epsilon;
+        /**
+         * Specifies if two colors are approximately equal in value.
+         * @param color1 - first color to compare to.
+         * @param color2 - second color to compare to.
+         * @param epsilon - threshold value
+         */
+        private static FuzzyEquals(color1, color2, epsilon);
         /**
         /**
          * Gets the materials from a Babylon scene and converts them to glTF materials.
          * Gets the materials from a Babylon scene and converts them to glTF materials.
-         * @param scene
-         * @param mimeType
-         * @param images
-         * @param textures
-         * @param materials
-         * @param imageData
-         * @param hasTextureCoords
-         */
-        static ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+         * @param scene - babylonjs scene.
+         * @param mimeType - texture mime type.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param materials - array of materials.
+         * @param imageData - mapping of texture names to base64 textures
+         * @param hasTextureCoords - specifies if texture coordinates are present on the material.
+         */
+        static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -317,19 +327,19 @@ declare module BABYLON.GLTF2 {
          * @param originalMaterial - original glTF material.
          * @param originalMaterial - original glTF material.
          * @returns glTF material without texture parameters
          * @returns glTF material without texture parameters
          */
          */
-        static StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial;
+        static _StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial;
         /**
         /**
          * Specifies if the material has any texture parameters present.
          * Specifies if the material has any texture parameters present.
          * @param material - glTF Material.
          * @param material - glTF Material.
          * @returns boolean specifying if texture parameters are present
          * @returns boolean specifying if texture parameters are present
          */
          */
-        static HasTexturesPresent(material: IMaterial): boolean;
+        static _HasTexturesPresent(material: IMaterial): boolean;
         /**
         /**
          * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
          * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
          * @param babylonStandardMaterial
          * @param babylonStandardMaterial
          * @returns - glTF Metallic Roughness Material representation
          * @returns - glTF Metallic Roughness Material representation
          */
          */
-        static ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness;
+        static _ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness;
         /**
         /**
          * Computes the metallic factor
          * Computes the metallic factor
          * @param diffuse - diffused value
          * @param diffuse - diffused value
@@ -337,13 +347,13 @@ declare module BABYLON.GLTF2 {
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @returns - metallic value
          * @returns - metallic value
          */
          */
-        static SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number;
+        static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number;
         /**
         /**
          * Gets the glTF alpha mode from the Babylon Material
          * Gets the glTF alpha mode from the Babylon Material
          * @param babylonMaterial - Babylon Material
          * @param babylonMaterial - Babylon Material
          * @returns - The Babylon alpha mode value
          * @returns - The Babylon alpha mode value
          */
          */
-        static GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode;
+        static _GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode;
         /**
         /**
          * Converts a Babylon Standard Material to a glTF Material.
          * Converts a Babylon Standard Material to a glTF Material.
          * @param babylonStandardMaterial - BJS Standard Material.
          * @param babylonStandardMaterial - BJS Standard Material.
@@ -354,7 +364,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -370,25 +380,66 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
             };
             };
         }, hasTextureCoords: boolean): void;
         }, hasTextureCoords: boolean): void;
         /**
         /**
+         * Converts an image typed array buffer to a base64 image.
+         * @param buffer - typed array buffer.
+         * @param width - width of the image.
+         * @param height - height of the image.
+         * @param mimeType - mimetype of the image.
+         * @returns - base64 image string.
+         */
+        private static _CreateBase64FromCanvas(buffer, width, height, mimeType);
+        /**
+         * Generates a white texture based on the specified width and height.
+         * @param width - width of the texture in pixels.
+         * @param height - height of the texture in pixels.
+         * @param scene - babylonjs scene.
+         * @returns - white texture.
+         */
+        private static _CreateWhiteTexture(width, height, scene);
+        /**
+         * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
+         * @param texture1 - first texture to resize.
+         * @param texture2 - second texture to resize.
+         * @param scene - babylonjs scene.
+         * @returns resized textures or null.
+         */
+        private static _ResizeTexturesToSameDimensions(texture1, texture2, scene);
+        /**
+         * Convert Specular Glossiness Textures to Metallic Roughness.
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
+         * @param diffuseTexture - texture used to store diffuse information.
+         * @param specularGlossinessTexture - texture used to store specular and glossiness information.
+         * @param factors - specular glossiness material factors.
+         * @param mimeType - the mime type to use for the texture.
+         * @returns pbr metallic roughness interface or null.
+         */
+        private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture, specularGlossinessTexture, factors, mimeType);
+        /**
+         * Converts specular glossiness material properties to metallic roughness.
+         * @param specularGlossiness - interface with specular glossiness material properties.
+         * @returns - interface with metallic roughness material properties.
+         */
+        private static _ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+        /**
+         * Calculates the surface reflectance, independent of lighting conditions.
          * @param color - Color source to calculate brightness from.
          * @param color - Color source to calculate brightness from.
          * @returns number representing the perceived brightness, or zero if color is undefined.
          * @returns number representing the perceived brightness, or zero if color is undefined.
          */
          */
-        static GetPerceivedBrightness(color: Color3): number;
+        private static _GetPerceivedBrightness(color);
         /**
         /**
          * Returns the maximum color component value.
          * Returns the maximum color component value.
          * @param color
          * @param color
          * @returns maximum color component value, or zero if color is null or undefined.
          * @returns maximum color component value, or zero if color is null or undefined.
          */
          */
-        static GetMaxComponent(color: Color3): number;
+        private static _GetMaxComponent(color);
         /**
         /**
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
          * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
          * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
@@ -399,7 +450,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -412,13 +463,19 @@ declare module BABYLON.GLTF2 {
          * @param images - Array of glTF images.
          * @param images - Array of glTF images.
          * @param textures - Array of glTF textures.
          * @param textures - Array of glTF textures.
          * @param imageData - map of image file name and data.
          * @param imageData - map of image file name and data.
-         * @return - glTF texture, or null if the texture format is not supported.
+         * @return - glTF texture info, or null if the texture format is not supported.
          */
          */
-        static ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: {
-            [fileName: string]: {
-                data: Uint8Array;
-                mimeType: ImageMimeType;
-            };
-        }): Nullable<ITextureInfo>;
+        private static _ExportTexture(babylonTexture, mimeType, images, textures, imageData);
+        /**
+         * Builds a texture from base64 string.
+         * @param base64Texture - base64 texture string.
+         * @param textureName - Name to use for the texture.
+         * @param mimeType - image mime type for the texture.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param imageData - map of image data.
+         * @returns - glTF texture info, or null if the texture format is not supported.
+         */
+        private static _GetTextureInfoFromBase64(base64Texture, textureName, mimeType, images, textures, imageData);
     }
     }
 }
 }

+ 385 - 158
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -613,13 +613,13 @@ var BABYLON;
                                 else {
                                 else {
                                     BABYLON.Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                                     BABYLON.Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                                 }
                                 }
-                                if (materialIndex != null) {
-                                    if (uvCoordsPresent || !GLTF2._GLTFMaterial.HasTexturesPresent(this.materials[materialIndex])) {
+                                if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
+                                    if (uvCoordsPresent || !GLTF2._GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
                                         meshPrimitive.material = materialIndex;
                                         meshPrimitive.material = materialIndex;
                                     }
                                     }
                                     else {
                                     else {
                                         // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
                                         // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
-                                        var newMat = GLTF2._GLTFMaterial.StripTexturesFromMaterial(this.materials[materialIndex]);
+                                        var newMat = GLTF2._GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
                                         this.materials.push(newMat);
                                         this.materials.push(newMat);
                                         meshPrimitive.material = this.materials.length - 1;
                                         meshPrimitive.material = this.materials.length - 1;
                                     }
                                     }
@@ -642,7 +642,7 @@ var BABYLON;
                 if (babylonScene.meshes.length) {
                 if (babylonScene.meshes.length) {
                     var babylonMeshes = babylonScene.meshes;
                     var babylonMeshes = babylonScene.meshes;
                     var scene = { nodes: new Array() };
                     var scene = { nodes: new Array() };
-                    GLTF2._GLTFMaterial.ConvertMaterialsToGLTF(babylonScene.materials, "image/jpeg" /* JPEG */, this.images, this.textures, this.materials, this.imageData, true);
+                    GLTF2._GLTFMaterial._ConvertMaterialsToGLTF(babylonScene.materials, "image/png" /* PNG */, this.images, this.textures, this.materials, this.imageData, true);
                     var result = this.createNodeMap(babylonScene, byteOffset);
                     var result = this.createNodeMap(babylonScene, byteOffset);
                     this.nodeMap = result.nodeMap;
                     this.nodeMap = result.nodeMap;
                     this.totalByteLength = result.byteOffset;
                     this.totalByteLength = result.byteOffset;
@@ -799,29 +799,40 @@ var BABYLON;
             function _GLTFMaterial() {
             function _GLTFMaterial() {
             }
             }
             /**
             /**
+             * Specifies if two colors are approximately equal in value.
+             * @param color1 - first color to compare to.
+             * @param color2 - second color to compare to.
+             * @param epsilon - threshold value
+             */
+            _GLTFMaterial.FuzzyEquals = function (color1, color2, epsilon) {
+                return BABYLON.Scalar.WithinEpsilon(color1.r, color2.r, epsilon) &&
+                    BABYLON.Scalar.WithinEpsilon(color1.g, color2.g, epsilon) &&
+                    BABYLON.Scalar.WithinEpsilon(color1.b, color2.b, epsilon);
+            };
+            /**
              * Gets the materials from a Babylon scene and converts them to glTF materials.
              * Gets the materials from a Babylon scene and converts them to glTF materials.
-             * @param scene
-             * @param mimeType
-             * @param images
-             * @param textures
-             * @param materials
-             * @param imageData
-             * @param hasTextureCoords
+             * @param scene - babylonjs scene.
+             * @param mimeType - texture mime type.
+             * @param images - array of images.
+             * @param textures - array of textures.
+             * @param materials - array of materials.
+             * @param imageData - mapping of texture names to base64 textures
+             * @param hasTextureCoords - specifies if texture coordinates are present on the material.
              */
              */
-            _GLTFMaterial.ConvertMaterialsToGLTF = function (babylonMaterials, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertMaterialsToGLTF = function (babylonMaterials, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 for (var i = 0; i < babylonMaterials.length; ++i) {
                 for (var i = 0; i < babylonMaterials.length; ++i) {
                     var babylonMaterial = babylonMaterials[i];
                     var babylonMaterial = babylonMaterials[i];
                     if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                     if (babylonMaterial instanceof BABYLON.StandardMaterial) {
-                        _GLTFMaterial.ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else if (babylonMaterial instanceof BABYLON.PBRMetallicRoughnessMaterial) {
                     else if (babylonMaterial instanceof BABYLON.PBRMetallicRoughnessMaterial) {
-                        _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else if (babylonMaterial instanceof BABYLON.PBRMaterial) {
                     else if (babylonMaterial instanceof BABYLON.PBRMaterial) {
-                        _GLTFMaterial.ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else {
                     else {
-                        BABYLON.Tools.Error("Unsupported material type: " + babylonMaterial.name);
+                        throw new Error("Unsupported material type: " + babylonMaterial.name);
                     }
                     }
                 }
                 }
             };
             };
@@ -830,7 +841,7 @@ var BABYLON;
              * @param originalMaterial - original glTF material.
              * @param originalMaterial - original glTF material.
              * @returns glTF material without texture parameters
              * @returns glTF material without texture parameters
              */
              */
-            _GLTFMaterial.StripTexturesFromMaterial = function (originalMaterial) {
+            _GLTFMaterial._StripTexturesFromMaterial = function (originalMaterial) {
                 var newMaterial = {};
                 var newMaterial = {};
                 if (originalMaterial) {
                 if (originalMaterial) {
                     newMaterial.name = originalMaterial.name;
                     newMaterial.name = originalMaterial.name;
@@ -853,7 +864,7 @@ var BABYLON;
              * @param material - glTF Material.
              * @param material - glTF Material.
              * @returns boolean specifying if texture parameters are present
              * @returns boolean specifying if texture parameters are present
              */
              */
-            _GLTFMaterial.HasTexturesPresent = function (material) {
+            _GLTFMaterial._HasTexturesPresent = function (material) {
                 if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
                 if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
                     return true;
                     return true;
                 }
                 }
@@ -870,7 +881,7 @@ var BABYLON;
              * @param babylonStandardMaterial
              * @param babylonStandardMaterial
              * @returns - glTF Metallic Roughness Material representation
              * @returns - glTF Metallic Roughness Material representation
              */
              */
-            _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness = function (babylonStandardMaterial) {
+            _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness = function (babylonStandardMaterial) {
                 var P0 = new BABYLON.Vector2(0, 1);
                 var P0 = new BABYLON.Vector2(0, 1);
                 var P1 = new BABYLON.Vector2(0, 0.1);
                 var P1 = new BABYLON.Vector2(0, 0.1);
                 var P2 = new BABYLON.Vector2(0, 0.1);
                 var P2 = new BABYLON.Vector2(0, 0.1);
@@ -884,7 +895,7 @@ var BABYLON;
                  * @param p3 - fourth control point.
                  * @param p3 - fourth control point.
                  * @returns - number result of cubic bezier curve at the specified t.
                  * @returns - number result of cubic bezier curve at the specified t.
                  */
                  */
-                function cubicBezierCurve(t, p0, p1, p2, p3) {
+                function _cubicBezierCurve(t, p0, p1, p2, p3) {
                     return ((1 - t) * (1 - t) * (1 - t) * p0 +
                     return ((1 - t) * (1 - t) * (1 - t) * p0 +
                         3 * (1 - t) * (1 - t) * t * p1 +
                         3 * (1 - t) * (1 - t) * t * p1 +
                         3 * (1 - t) * t * t * p2 +
                         3 * (1 - t) * t * t * p2 +
@@ -897,14 +908,14 @@ var BABYLON;
                  * @param specularPower - specular power of standard material.
                  * @param specularPower - specular power of standard material.
                  * @returns - Number representing the roughness value.
                  * @returns - Number representing the roughness value.
                  */
                  */
-                function solveForRoughness(specularPower) {
+                function _solveForRoughness(specularPower) {
                     var t = Math.pow(specularPower / P3.x, 0.333333);
                     var t = Math.pow(specularPower / P3.x, 0.333333);
-                    return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
+                    return _cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
                 }
                 }
                 var diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
                 var diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
                 var opacity = babylonStandardMaterial.alpha;
                 var opacity = babylonStandardMaterial.alpha;
-                var specularPower = BABYLON.Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this.maxSpecularPower);
-                var roughness = solveForRoughness(specularPower);
+                var specularPower = BABYLON.Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this._maxSpecularPower);
+                var roughness = _solveForRoughness(specularPower);
                 var glTFPbrMetallicRoughness = {
                 var glTFPbrMetallicRoughness = {
                     baseColorFactor: [
                     baseColorFactor: [
                         diffuse.r,
                         diffuse.r,
@@ -924,14 +935,14 @@ var BABYLON;
              * @param oneMinusSpecularStrength - one minus the specular strength
              * @param oneMinusSpecularStrength - one minus the specular strength
              * @returns - metallic value
              * @returns - metallic value
              */
              */
-            _GLTFMaterial.SolveMetallic = function (diffuse, specular, oneMinusSpecularStrength) {
-                if (specular < _GLTFMaterial.dielectricSpecular.r) {
-                    _GLTFMaterial.dielectricSpecular;
+            _GLTFMaterial._SolveMetallic = function (diffuse, specular, oneMinusSpecularStrength) {
+                if (specular < _GLTFMaterial._dielectricSpecular.r) {
+                    _GLTFMaterial._dielectricSpecular;
                     return 0;
                     return 0;
                 }
                 }
-                var a = _GLTFMaterial.dielectricSpecular.r;
-                var b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial.dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial.dielectricSpecular.r;
-                var c = _GLTFMaterial.dielectricSpecular.r - specular;
+                var a = _GLTFMaterial._dielectricSpecular.r;
+                var b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial._dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial._dielectricSpecular.r;
+                var c = _GLTFMaterial._dielectricSpecular.r - specular;
                 var D = b * b - 4.0 * a * c;
                 var D = b * b - 4.0 * a * c;
                 return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
                 return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
             };
             };
@@ -940,7 +951,7 @@ var BABYLON;
              * @param babylonMaterial - Babylon Material
              * @param babylonMaterial - Babylon Material
              * @returns - The Babylon alpha mode value
              * @returns - The Babylon alpha mode value
              */
              */
-            _GLTFMaterial.GetAlphaMode = function (babylonMaterial) {
+            _GLTFMaterial._GetAlphaMode = function (babylonMaterial) {
                 if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                 if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                     var babylonStandardMaterial = babylonMaterial;
                     var babylonStandardMaterial = babylonMaterial;
                     if ((babylonStandardMaterial.alpha != 1.0) ||
                     if ((babylonStandardMaterial.alpha != 1.0) ||
@@ -1008,11 +1019,11 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertStandardMaterial = function (babylonStandardMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertStandardMaterial = function (babylonStandardMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
                 BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
-                var glTFPbrMetallicRoughness = _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
+                var glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
                 var glTFMaterial = { name: babylonStandardMaterial.name };
                 var glTFMaterial = { name: babylonStandardMaterial.name };
-                if (babylonStandardMaterial.backFaceCulling) {
+                if (babylonStandardMaterial.backFaceCulling != null && !babylonStandardMaterial.backFaceCulling) {
                     if (!babylonStandardMaterial.twoSidedLighting) {
                     if (!babylonStandardMaterial.twoSidedLighting) {
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     }
                     }
@@ -1020,26 +1031,26 @@ var BABYLON;
                 }
                 }
                 if (hasTextureCoords) {
                 if (hasTextureCoords) {
                     if (babylonStandardMaterial.diffuseTexture) {
                     if (babylonStandardMaterial.diffuseTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonStandardMaterial.bumpTexture) {
                     if (babylonStandardMaterial.bumpTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.normalTexture = glTFTexture;
                             glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonStandardMaterial.emissiveTexture) {
                     if (babylonStandardMaterial.emissiveTexture) {
-                        var glTFEmissiveTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        var glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData);
                         if (glTFEmissiveTexture) {
                         if (glTFEmissiveTexture) {
                             glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                             glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                         }
                         }
                         glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                         glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                     }
                     }
                     if (babylonStandardMaterial.ambientTexture) {
                     if (babylonStandardMaterial.ambientTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             var occlusionTexture = {
                             var occlusionTexture = {
                                 index: glTFTexture.index
                                 index: glTFTexture.index
@@ -1057,7 +1068,7 @@ var BABYLON;
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                     }
                     }
                 }
                 }
-                if (babylonStandardMaterial.emissiveColor) {
+                if (babylonStandardMaterial.emissiveColor && !this.FuzzyEquals(babylonStandardMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
                     glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
                     glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
                 }
                 }
                 glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
                 glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
@@ -1073,7 +1084,7 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial = function (babylonPBRMetalRoughMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial = function (babylonPBRMetalRoughMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 var glTFPbrMetallicRoughness = {};
                 var glTFPbrMetallicRoughness = {};
                 if (babylonPBRMetalRoughMaterial.baseColor) {
                 if (babylonPBRMetalRoughMaterial.baseColor) {
                     glTFPbrMetallicRoughness.baseColorFactor = [
                     glTFPbrMetallicRoughness.baseColorFactor = [
@@ -1083,10 +1094,10 @@ var BABYLON;
                         babylonPBRMetalRoughMaterial.alpha
                         babylonPBRMetalRoughMaterial.alpha
                     ];
                     ];
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.metallic != null) {
+                if (babylonPBRMetalRoughMaterial.metallic != null && babylonPBRMetalRoughMaterial.metallic !== 1) {
                     glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
                     glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.roughness != null) {
+                if (babylonPBRMetalRoughMaterial.roughness != null && babylonPBRMetalRoughMaterial.roughness !== 1) {
                     glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
                     glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
                 }
                 }
                 var glTFMaterial = {
                 var glTFMaterial = {
@@ -1097,19 +1108,19 @@ var BABYLON;
                 }
                 }
                 if (hasTextureCoords) {
                 if (hasTextureCoords) {
                     if (babylonPBRMetalRoughMaterial.baseTexture != null) {
                     if (babylonPBRMetalRoughMaterial.baseTexture != null) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.normalTexture) {
                     if (babylonPBRMetalRoughMaterial.normalTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.normalTexture = glTFTexture;
                             glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.occlusionTexture) {
                     if (babylonPBRMetalRoughMaterial.occlusionTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.occlusionTexture = glTFTexture;
                             glTFMaterial.occlusionTexture = glTFTexture;
                             if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
                             if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
@@ -1118,17 +1129,17 @@ var BABYLON;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.emissiveTexture) {
                     if (babylonPBRMetalRoughMaterial.emissiveTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFMaterial.emissiveTexture = glTFTexture;
                             glTFMaterial.emissiveTexture = glTFTexture;
                         }
                         }
                     }
                     }
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
+                if (this.FuzzyEquals(babylonPBRMetalRoughMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
                     glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
                     glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
                 }
                 }
                 if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
                 if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
-                    var alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMetalRoughMaterial);
+                    var alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
                     if (alphaMode !== "OPAQUE" /* OPAQUE */) {
                     if (alphaMode !== "OPAQUE" /* OPAQUE */) {
                         glTFMaterial.alphaMode = alphaMode;
                         glTFMaterial.alphaMode = alphaMode;
                         if (alphaMode === "BLEND" /* BLEND */) {
                         if (alphaMode === "BLEND" /* BLEND */) {
@@ -1140,12 +1151,213 @@ var BABYLON;
                 materials.push(glTFMaterial);
                 materials.push(glTFMaterial);
             };
             };
             /**
             /**
+             * Converts an image typed array buffer to a base64 image.
+             * @param buffer - typed array buffer.
+             * @param width - width of the image.
+             * @param height - height of the image.
+             * @param mimeType - mimetype of the image.
+             * @returns - base64 image string.
+             */
+            _GLTFMaterial._CreateBase64FromCanvas = function (buffer, width, height, mimeType) {
+                var imageCanvas = document.createElement('canvas');
+                imageCanvas.id = "WriteCanvas";
+                var ctx = imageCanvas.getContext('2d');
+                imageCanvas.width = width;
+                imageCanvas.height = height;
+                var imgData = ctx.createImageData(width, height);
+                imgData.data.set(buffer);
+                ctx.putImageData(imgData, 0, 0);
+                return imageCanvas.toDataURL(mimeType);
+            };
+            /**
+             * Generates a white texture based on the specified width and height.
+             * @param width - width of the texture in pixels.
+             * @param height - height of the texture in pixels.
+             * @param scene - babylonjs scene.
+             * @returns - white texture.
+             */
+            _GLTFMaterial._CreateWhiteTexture = function (width, height, scene) {
+                var data = new Uint8Array(width * height * 4);
+                for (var i = 0; i < data.length; ++i) {
+                    data[i] = 255;
+                }
+                var rawTexture = BABYLON.RawTexture.CreateRGBATexture(data, width, height, scene);
+                return rawTexture;
+            };
+            /**
+             * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
+             * @param texture1 - first texture to resize.
+             * @param texture2 - second texture to resize.
+             * @param scene - babylonjs scene.
+             * @returns resized textures or null.
+             */
+            _GLTFMaterial._ResizeTexturesToSameDimensions = function (texture1, texture2, scene) {
+                var texture1Size = texture1 ? texture1.getSize() : { width: 0, height: 0 };
+                var texture2Size = texture2 ? texture2.getSize() : { width: 0, height: 0 };
+                var resizedTexture1;
+                var resizedTexture2;
+                if (texture1Size.width < texture2Size.width) {
+                    if (texture1) {
+                        resizedTexture1 = BABYLON.TextureTools.CreateResizedCopy(texture1, texture2Size.width, texture2Size.height, true);
+                    }
+                    else {
+                        resizedTexture1 = this._CreateWhiteTexture(texture2Size.width, texture2Size.height, scene);
+                    }
+                    resizedTexture2 = texture2;
+                }
+                else if (texture1Size.width > texture2Size.width) {
+                    if (texture2) {
+                        resizedTexture2 = BABYLON.TextureTools.CreateResizedCopy(texture2, texture1Size.width, texture1Size.height, true);
+                    }
+                    else {
+                        resizedTexture2 = this._CreateWhiteTexture(texture1Size.width, texture1Size.height, scene);
+                    }
+                    resizedTexture1 = texture1;
+                }
+                else {
+                    resizedTexture1 = texture1;
+                    resizedTexture2 = texture2;
+                }
+                return {
+                    "texture1": resizedTexture1,
+                    "texture2": resizedTexture2
+                };
+            };
+            /**
+             * Convert Specular Glossiness Textures to Metallic Roughness.
              * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
              * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
              * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
              * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
+             * @param diffuseTexture - texture used to store diffuse information.
+             * @param specularGlossinessTexture - texture used to store specular and glossiness information.
+             * @param factors - specular glossiness material factors.
+             * @param mimeType - the mime type to use for the texture.
+             * @returns pbr metallic roughness interface or null.
+             */
+            _GLTFMaterial._ConvertSpecularGlossinessTexturesToMetallicRoughness = function (diffuseTexture, specularGlossinessTexture, factors, mimeType) {
+                if (!(diffuseTexture || specularGlossinessTexture)) {
+                    return null;
+                }
+                var scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
+                if (!scene) {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
+                }
+                var resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
+                var diffuseSize = resizedTextures.texture1.getSize();
+                var diffuseBuffer;
+                var specularGlossinessBuffer;
+                var width = diffuseSize.width;
+                var height = diffuseSize.height;
+                var pixels = (resizedTextures.texture1.readPixels());
+                if (pixels instanceof Uint8Array) {
+                    diffuseBuffer = (resizedTextures.texture1.readPixels());
+                }
+                else {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+                }
+                pixels = resizedTextures.texture2.readPixels();
+                if (pixels instanceof Uint8Array) {
+                    specularGlossinessBuffer = (resizedTextures.texture2.readPixels());
+                }
+                else {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
+                }
+                var byteLength = specularGlossinessBuffer.byteLength;
+                var metallicRoughnessBuffer = new Uint8Array(byteLength);
+                var baseColorBuffer = new Uint8Array(byteLength);
+                var strideSize = 4;
+                var maxBaseColor = BABYLON.Color3.Black();
+                var maxMetallic = 0;
+                var maxRoughness = 0;
+                for (var h = 0; h < height; ++h) {
+                    for (var w = 0; w < width; ++w) {
+                        var offset = (width * h + w) * strideSize;
+                        var diffuseColor = BABYLON.Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).multiply(factors.diffuseColor);
+                        var specularColor = BABYLON.Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).multiply(factors.specularColor);
+                        var glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
+                        var specularGlossiness = {
+                            diffuseColor: diffuseColor,
+                            specularColor: specularColor,
+                            glossiness: glossiness
+                        };
+                        var metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+                        maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
+                        maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
+                        maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
+                        maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
+                        maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
+                        baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
+                        baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
+                        baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
+                        baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
+                        metallicRoughnessBuffer[offset] = 255;
+                        metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
+                        metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
+                        metallicRoughnessBuffer[offset + 3] = 255;
+                    }
+                }
+                // Retrieves the metallic roughness factors from the maximum texture values.
+                var metallicRoughnessFactors = {
+                    baseColor: maxBaseColor,
+                    metallic: maxMetallic,
+                    roughness: maxRoughness
+                };
+                var writeOutMetallicRoughnessTexture = false;
+                var writeOutBaseColorTexture = false;
+                for (var h = 0; h < height; ++h) {
+                    for (var w = 0; w < width; ++w) {
+                        var destinationOffset = (width * h + w) * strideSize;
+                        baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
+                        baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
+                        baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
+                        var baseColorPixel = new BABYLON.Color3(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
+                        if (!this.FuzzyEquals(baseColorPixel, BABYLON.Color3.White(), this._epsilon)) {
+                            writeOutBaseColorTexture = true;
+                        }
+                        metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
+                        metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
+                        var metallicRoughnessPixel = new BABYLON.Color3(metallicRoughnessBuffer[destinationOffset], metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
+                        if (!this.FuzzyEquals(metallicRoughnessPixel, BABYLON.Color3.White(), this._epsilon)) {
+                            writeOutMetallicRoughnessTexture = true;
+                        }
+                    }
+                }
+                if (writeOutMetallicRoughnessTexture) {
+                    var metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
+                    metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
+                }
+                if (writeOutBaseColorTexture) {
+                    var baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
+                    metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
+                }
+                return metallicRoughnessFactors;
+            };
+            /**
+             * Converts specular glossiness material properties to metallic roughness.
+             * @param specularGlossiness - interface with specular glossiness material properties.
+             * @returns - interface with metallic roughness material properties.
+             */
+            _GLTFMaterial._ConvertSpecularGlossinessToMetallicRoughness = function (specularGlossiness) {
+                var diffusePerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.diffuseColor);
+                var specularPerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.specularColor);
+                var oneMinusSpecularStrength = 1 - _GLTFMaterial._GetMaxComponent(specularGlossiness.specularColor);
+                var metallic = _GLTFMaterial._SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
+                var baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this._dielectricSpecular.r) / Math.max(1 - metallic, this._epsilon));
+                var baseColorFromSpecular = specularGlossiness.specularColor.subtract(this._dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this._epsilon));
+                var baseColor = BABYLON.Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
+                baseColor = baseColor.clampToRef(0, 1, baseColor);
+                var metallicRoughness = {
+                    baseColor: baseColor,
+                    metallic: metallic,
+                    roughness: 1 - specularGlossiness.glossiness
+                };
+                return metallicRoughness;
+            };
+            /**
+             * Calculates the surface reflectance, independent of lighting conditions.
              * @param color - Color source to calculate brightness from.
              * @param color - Color source to calculate brightness from.
              * @returns number representing the perceived brightness, or zero if color is undefined.
              * @returns number representing the perceived brightness, or zero if color is undefined.
              */
              */
-            _GLTFMaterial.GetPerceivedBrightness = function (color) {
+            _GLTFMaterial._GetPerceivedBrightness = function (color) {
                 if (color) {
                 if (color) {
                     return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
                     return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
                 }
                 }
@@ -1156,7 +1368,7 @@ var BABYLON;
              * @param color
              * @param color
              * @returns maximum color component value, or zero if color is null or undefined.
              * @returns maximum color component value, or zero if color is null or undefined.
              */
              */
-            _GLTFMaterial.GetMaxComponent = function (color) {
+            _GLTFMaterial._GetMaxComponent = function (color) {
                 if (color) {
                 if (color) {
                     return Math.max(color.r, Math.max(color.g, color.b));
                     return Math.max(color.r, Math.max(color.g, color.b));
                 }
                 }
@@ -1172,110 +1384,117 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertPBRMaterial = function (babylonPBRMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertPBRMaterial = function (babylonPBRMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 var glTFPbrMetallicRoughness = {};
                 var glTFPbrMetallicRoughness = {};
+                var metallicRoughness;
                 var glTFMaterial = {
                 var glTFMaterial = {
                     name: babylonPBRMaterial.name
                     name: babylonPBRMaterial.name
                 };
                 };
                 var useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
                 var useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
-                if (babylonPBRMaterial) {
-                    if (useMetallicRoughness) {
-                        glTFPbrMetallicRoughness.baseColorFactor = [
-                            babylonPBRMaterial.albedoColor.r,
-                            babylonPBRMaterial.albedoColor.g,
-                            babylonPBRMaterial.albedoColor.b,
-                            babylonPBRMaterial.alpha
-                        ];
-                        if (babylonPBRMaterial.metallic != null) {
-                            if (babylonPBRMaterial.metallic !== 1) {
-                                glTFPbrMetallicRoughness.metallicFactor = babylonPBRMaterial.metallic;
+                if (!useMetallicRoughness) {
+                    var specGloss = {
+                        diffuseColor: babylonPBRMaterial.albedoColor || BABYLON.Color3.White(),
+                        specularColor: babylonPBRMaterial.reflectivityColor || BABYLON.Color3.White(),
+                        glossiness: babylonPBRMaterial.microSurface || 1,
+                    };
+                    if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
+                        throw new Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                    }
+                    metallicRoughness = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
+                    if (!metallicRoughness) {
+                        metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
+                    }
+                    else {
+                        if (hasTextureCoords) {
+                            if (metallicRoughness.baseColorTextureBase64) {
+                                var glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                                if (glTFBaseColorTexture != null) {
+                                    glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
+                                }
                             }
                             }
-                        }
-                        if (babylonPBRMaterial.roughness != null) {
-                            if (babylonPBRMaterial.roughness !== 1) {
-                                glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMaterial.roughness;
+                            if (metallicRoughness.metallicRoughnessTextureBase64) {
+                                var glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                                if (glTFMRColorTexture != null) {
+                                    glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
-                    else {
-                        var diffuseColor = babylonPBRMaterial.albedoColor || BABYLON.Color3.Black();
-                        var specularColor = babylonPBRMaterial.reflectionColor || BABYLON.Color3.Black();
-                        var diffusePerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(diffuseColor);
-                        var specularPerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(specularColor);
-                        var oneMinusSpecularStrength = 1 - _GLTFMaterial.GetMaxComponent(babylonPBRMaterial.reflectionColor);
-                        var metallic = _GLTFMaterial.SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
-                        var glossiness = babylonPBRMaterial.microSurface || 0;
-                        var baseColorFromDiffuse = diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this.dielectricSpecular.r) / Math.max(1 - metallic, this.epsilon));
-                        var baseColorFromSpecular = specularColor.subtract(this.dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this.epsilon));
-                        var baseColor = BABYLON.Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
-                        baseColor = baseColor.clampToRef(0, 1, baseColor);
-                        glTFPbrMetallicRoughness.baseColorFactor = [
-                            baseColor.r,
-                            baseColor.g,
-                            baseColor.b,
-                            babylonPBRMaterial.alpha
-                        ];
-                        if (metallic !== 1) {
-                            glTFPbrMetallicRoughness.metallicFactor = metallic;
-                        }
-                        if (glossiness) {
-                            glTFPbrMetallicRoughness.roughnessFactor = 1 - glossiness;
-                        }
+                }
+                else {
+                    metallicRoughness = {
+                        baseColor: babylonPBRMaterial.albedoColor,
+                        metallic: babylonPBRMaterial.metallic,
+                        roughness: babylonPBRMaterial.roughness
+                    };
+                }
+                if (!(this.FuzzyEquals(metallicRoughness.baseColor, BABYLON.Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
+                    glTFPbrMetallicRoughness.baseColorFactor = [
+                        metallicRoughness.baseColor.r,
+                        metallicRoughness.baseColor.g,
+                        metallicRoughness.baseColor.b,
+                        babylonPBRMaterial.alpha
+                    ];
+                }
+                if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
+                    glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
+                }
+                if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
+                    glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
+                }
+                if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
+                    if (!babylonPBRMaterial.twoSidedLighting) {
+                        BABYLON.Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     }
                     }
-                    if (babylonPBRMaterial.backFaceCulling) {
-                        if (!babylonPBRMaterial.twoSidedLighting) {
-                            BABYLON.Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
+                    glTFMaterial.doubleSided = true;
+                }
+                if (hasTextureCoords) {
+                    if (useMetallicRoughness && babylonPBRMaterial.albedoTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
-                        glTFMaterial.doubleSided = true;
                     }
                     }
-                    if (hasTextureCoords) {
-                        if (babylonPBRMaterial.albedoTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                            }
-                        }
-                        if (babylonPBRMaterial.bumpTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                glTFMaterial.normalTexture = glTFTexture;
-                            }
-                        }
-                        if (babylonPBRMaterial.ambientTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                var occlusionTexture = {
-                                    index: glTFTexture.index
-                                };
-                                glTFMaterial.occlusionTexture = occlusionTexture;
-                                if (babylonPBRMaterial.ambientTextureStrength) {
-                                    occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
-                                }
-                            }
+                    if (babylonPBRMaterial.bumpTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
-                        if (babylonPBRMaterial.emissiveTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture != null) {
-                                glTFMaterial.emissiveTexture = glTFTexture;
+                    }
+                    if (babylonPBRMaterial.ambientTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            var occlusionTexture = {
+                                index: glTFTexture.index
+                            };
+                            glTFMaterial.occlusionTexture = occlusionTexture;
+                            if (babylonPBRMaterial.ambientTextureStrength) {
+                                occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
                             }
                             }
                         }
                         }
-                        if (babylonPBRMaterial.metallicTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture != null) {
-                                glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
-                            }
+                    }
+                    if (babylonPBRMaterial.emissiveTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture != null) {
+                            glTFMaterial.emissiveTexture = glTFTexture;
                         }
                         }
                     }
                     }
-                    if (!babylonPBRMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
-                        glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                    if (babylonPBRMaterial.metallicTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture != null) {
+                            glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
+                        }
                     }
                     }
-                    if (babylonPBRMaterial.transparencyMode != null) {
-                        var alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMaterial);
-                        if (alphaMode !== "OPAQUE" /* OPAQUE */) {
-                            glTFMaterial.alphaMode = alphaMode;
-                            if (alphaMode === "BLEND" /* BLEND */) {
-                                glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
-                            }
+                }
+                if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
+                    glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                }
+                if (babylonPBRMaterial.transparencyMode != null) {
+                    var alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
+                    if (alphaMode !== "OPAQUE" /* OPAQUE */) {
+                        glTFMaterial.alphaMode = alphaMode;
+                        if (alphaMode === "BLEND" /* BLEND */) {
+                            glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
                         }
                         }
                     }
                     }
                 }
                 }
@@ -1289,17 +1508,13 @@ var BABYLON;
              * @param images - Array of glTF images.
              * @param images - Array of glTF images.
              * @param textures - Array of glTF textures.
              * @param textures - Array of glTF textures.
              * @param imageData - map of image file name and data.
              * @param imageData - map of image file name and data.
-             * @return - glTF texture, or null if the texture format is not supported.
+             * @return - glTF texture info, or null if the texture format is not supported.
              */
              */
-            _GLTFMaterial.ExportTexture = function (babylonTexture, mimeType, images, textures, imageData) {
-                var textureInfo = null;
-                var glTFTexture = {
-                    source: images.length
-                };
+            _GLTFMaterial._ExportTexture = function (babylonTexture, mimeType, images, textures, imageData) {
                 var textureName = "texture_" + (textures.length - 1).toString();
                 var textureName = "texture_" + (textures.length - 1).toString();
                 var textureData = babylonTexture.getInternalTexture();
                 var textureData = babylonTexture.getInternalTexture();
                 if (textureData != null) {
                 if (textureData != null) {
-                    textureName = textureData.url;
+                    textureName = textureData.url || textureName;
                 }
                 }
                 textureName = BABYLON.Tools.GetFilename(textureName);
                 textureName = BABYLON.Tools.GetFilename(textureName);
                 var baseFile = textureName.split('.')[0];
                 var baseFile = textureName.split('.')[0];
@@ -1311,28 +1526,37 @@ var BABYLON;
                     extension = ".png";
                     extension = ".png";
                 }
                 }
                 else {
                 else {
-                    BABYLON.Tools.Error("Unsupported mime type " + mimeType);
+                    throw new Error("Unsupported mime type " + mimeType);
                 }
                 }
                 textureName = baseFile + extension;
                 textureName = baseFile + extension;
                 var pixels = babylonTexture.readPixels();
                 var pixels = babylonTexture.readPixels();
-                var imageCanvas = document.createElement('canvas');
-                imageCanvas.id = "ImageCanvas";
-                var ctx = imageCanvas.getContext('2d');
                 var size = babylonTexture.getSize();
                 var size = babylonTexture.getSize();
-                imageCanvas.width = size.width;
-                imageCanvas.height = size.height;
-                var imgData = ctx.createImageData(size.width, size.height);
-                imgData.data.set(pixels);
-                ctx.putImageData(imgData, 0, 0);
-                var base64Data = imageCanvas.toDataURL(mimeType);
-                var binStr = atob(base64Data.split(',')[1]);
+                var base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
+                return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, imageData);
+            };
+            /**
+             * Builds a texture from base64 string.
+             * @param base64Texture - base64 texture string.
+             * @param textureName - Name to use for the texture.
+             * @param mimeType - image mime type for the texture.
+             * @param images - array of images.
+             * @param textures - array of textures.
+             * @param imageData - map of image data.
+             * @returns - glTF texture info, or null if the texture format is not supported.
+             */
+            _GLTFMaterial._GetTextureInfoFromBase64 = function (base64Texture, textureName, mimeType, images, textures, imageData) {
+                var textureInfo = null;
+                var glTFTexture = {
+                    source: images.length
+                };
+                var binStr = atob(base64Texture.split(',')[1]);
                 var arr = new Uint8Array(binStr.length);
                 var arr = new Uint8Array(binStr.length);
                 for (var i = 0; i < binStr.length; ++i) {
                 for (var i = 0; i < binStr.length; ++i) {
                     arr[i] = binStr.charCodeAt(i);
                     arr[i] = binStr.charCodeAt(i);
                 }
                 }
                 var imageValues = { data: arr, mimeType: mimeType };
                 var imageValues = { data: arr, mimeType: mimeType };
                 imageData[textureName] = imageValues;
                 imageData[textureName] = imageValues;
-                if (mimeType === "image/jpeg" /* JPEG */) {
+                if (mimeType === "image/jpeg" /* JPEG */ || mimeType === "image/png" /* PNG */) {
                     var glTFImage = {
                     var glTFImage = {
                         uri: textureName
                         uri: textureName
                     };
                     };
@@ -1365,12 +1589,15 @@ var BABYLON;
             /**
             /**
              * Represents the dielectric specular values for R, G and B.
              * Represents the dielectric specular values for R, G and B.
              */
              */
-            _GLTFMaterial.dielectricSpecular = new BABYLON.Color3(0.04, 0.04, 0.04);
+            _GLTFMaterial._dielectricSpecular = new BABYLON.Color3(0.04, 0.04, 0.04);
             /**
             /**
              * Allows the maximum specular power to be defined for material calculations.
              * Allows the maximum specular power to be defined for material calculations.
              */
              */
-            _GLTFMaterial.maxSpecularPower = 1024;
-            _GLTFMaterial.epsilon = 1e-6;
+            _GLTFMaterial._maxSpecularPower = 1024;
+            /**
+             * Numeric tolerance value
+             */
+            _GLTFMaterial._epsilon = 1e-6;
             return _GLTFMaterial;
             return _GLTFMaterial;
         }());
         }());
         GLTF2._GLTFMaterial = _GLTFMaterial;
         GLTF2._GLTFMaterial = _GLTFMaterial;

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/serializers/babylon.glTF2Serializer.min.js


+ 385 - 158
dist/preview release/serializers/babylonjs.serializers.js

@@ -767,13 +767,13 @@ var BABYLON;
                                 else {
                                 else {
                                     BABYLON.Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                                     BABYLON.Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                                 }
                                 }
-                                if (materialIndex != null) {
-                                    if (uvCoordsPresent || !GLTF2._GLTFMaterial.HasTexturesPresent(this.materials[materialIndex])) {
+                                if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
+                                    if (uvCoordsPresent || !GLTF2._GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
                                         meshPrimitive.material = materialIndex;
                                         meshPrimitive.material = materialIndex;
                                     }
                                     }
                                     else {
                                     else {
                                         // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
                                         // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
-                                        var newMat = GLTF2._GLTFMaterial.StripTexturesFromMaterial(this.materials[materialIndex]);
+                                        var newMat = GLTF2._GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
                                         this.materials.push(newMat);
                                         this.materials.push(newMat);
                                         meshPrimitive.material = this.materials.length - 1;
                                         meshPrimitive.material = this.materials.length - 1;
                                     }
                                     }
@@ -796,7 +796,7 @@ var BABYLON;
                 if (babylonScene.meshes.length) {
                 if (babylonScene.meshes.length) {
                     var babylonMeshes = babylonScene.meshes;
                     var babylonMeshes = babylonScene.meshes;
                     var scene = { nodes: new Array() };
                     var scene = { nodes: new Array() };
-                    GLTF2._GLTFMaterial.ConvertMaterialsToGLTF(babylonScene.materials, "image/jpeg" /* JPEG */, this.images, this.textures, this.materials, this.imageData, true);
+                    GLTF2._GLTFMaterial._ConvertMaterialsToGLTF(babylonScene.materials, "image/png" /* PNG */, this.images, this.textures, this.materials, this.imageData, true);
                     var result = this.createNodeMap(babylonScene, byteOffset);
                     var result = this.createNodeMap(babylonScene, byteOffset);
                     this.nodeMap = result.nodeMap;
                     this.nodeMap = result.nodeMap;
                     this.totalByteLength = result.byteOffset;
                     this.totalByteLength = result.byteOffset;
@@ -953,29 +953,40 @@ var BABYLON;
             function _GLTFMaterial() {
             function _GLTFMaterial() {
             }
             }
             /**
             /**
+             * Specifies if two colors are approximately equal in value.
+             * @param color1 - first color to compare to.
+             * @param color2 - second color to compare to.
+             * @param epsilon - threshold value
+             */
+            _GLTFMaterial.FuzzyEquals = function (color1, color2, epsilon) {
+                return BABYLON.Scalar.WithinEpsilon(color1.r, color2.r, epsilon) &&
+                    BABYLON.Scalar.WithinEpsilon(color1.g, color2.g, epsilon) &&
+                    BABYLON.Scalar.WithinEpsilon(color1.b, color2.b, epsilon);
+            };
+            /**
              * Gets the materials from a Babylon scene and converts them to glTF materials.
              * Gets the materials from a Babylon scene and converts them to glTF materials.
-             * @param scene
-             * @param mimeType
-             * @param images
-             * @param textures
-             * @param materials
-             * @param imageData
-             * @param hasTextureCoords
+             * @param scene - babylonjs scene.
+             * @param mimeType - texture mime type.
+             * @param images - array of images.
+             * @param textures - array of textures.
+             * @param materials - array of materials.
+             * @param imageData - mapping of texture names to base64 textures
+             * @param hasTextureCoords - specifies if texture coordinates are present on the material.
              */
              */
-            _GLTFMaterial.ConvertMaterialsToGLTF = function (babylonMaterials, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertMaterialsToGLTF = function (babylonMaterials, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 for (var i = 0; i < babylonMaterials.length; ++i) {
                 for (var i = 0; i < babylonMaterials.length; ++i) {
                     var babylonMaterial = babylonMaterials[i];
                     var babylonMaterial = babylonMaterials[i];
                     if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                     if (babylonMaterial instanceof BABYLON.StandardMaterial) {
-                        _GLTFMaterial.ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else if (babylonMaterial instanceof BABYLON.PBRMetallicRoughnessMaterial) {
                     else if (babylonMaterial instanceof BABYLON.PBRMetallicRoughnessMaterial) {
-                        _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else if (babylonMaterial instanceof BABYLON.PBRMaterial) {
                     else if (babylonMaterial instanceof BABYLON.PBRMaterial) {
-                        _GLTFMaterial.ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                        _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                     }
                     }
                     else {
                     else {
-                        BABYLON.Tools.Error("Unsupported material type: " + babylonMaterial.name);
+                        throw new Error("Unsupported material type: " + babylonMaterial.name);
                     }
                     }
                 }
                 }
             };
             };
@@ -984,7 +995,7 @@ var BABYLON;
              * @param originalMaterial - original glTF material.
              * @param originalMaterial - original glTF material.
              * @returns glTF material without texture parameters
              * @returns glTF material without texture parameters
              */
              */
-            _GLTFMaterial.StripTexturesFromMaterial = function (originalMaterial) {
+            _GLTFMaterial._StripTexturesFromMaterial = function (originalMaterial) {
                 var newMaterial = {};
                 var newMaterial = {};
                 if (originalMaterial) {
                 if (originalMaterial) {
                     newMaterial.name = originalMaterial.name;
                     newMaterial.name = originalMaterial.name;
@@ -1007,7 +1018,7 @@ var BABYLON;
              * @param material - glTF Material.
              * @param material - glTF Material.
              * @returns boolean specifying if texture parameters are present
              * @returns boolean specifying if texture parameters are present
              */
              */
-            _GLTFMaterial.HasTexturesPresent = function (material) {
+            _GLTFMaterial._HasTexturesPresent = function (material) {
                 if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
                 if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
                     return true;
                     return true;
                 }
                 }
@@ -1024,7 +1035,7 @@ var BABYLON;
              * @param babylonStandardMaterial
              * @param babylonStandardMaterial
              * @returns - glTF Metallic Roughness Material representation
              * @returns - glTF Metallic Roughness Material representation
              */
              */
-            _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness = function (babylonStandardMaterial) {
+            _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness = function (babylonStandardMaterial) {
                 var P0 = new BABYLON.Vector2(0, 1);
                 var P0 = new BABYLON.Vector2(0, 1);
                 var P1 = new BABYLON.Vector2(0, 0.1);
                 var P1 = new BABYLON.Vector2(0, 0.1);
                 var P2 = new BABYLON.Vector2(0, 0.1);
                 var P2 = new BABYLON.Vector2(0, 0.1);
@@ -1038,7 +1049,7 @@ var BABYLON;
                  * @param p3 - fourth control point.
                  * @param p3 - fourth control point.
                  * @returns - number result of cubic bezier curve at the specified t.
                  * @returns - number result of cubic bezier curve at the specified t.
                  */
                  */
-                function cubicBezierCurve(t, p0, p1, p2, p3) {
+                function _cubicBezierCurve(t, p0, p1, p2, p3) {
                     return ((1 - t) * (1 - t) * (1 - t) * p0 +
                     return ((1 - t) * (1 - t) * (1 - t) * p0 +
                         3 * (1 - t) * (1 - t) * t * p1 +
                         3 * (1 - t) * (1 - t) * t * p1 +
                         3 * (1 - t) * t * t * p2 +
                         3 * (1 - t) * t * t * p2 +
@@ -1051,14 +1062,14 @@ var BABYLON;
                  * @param specularPower - specular power of standard material.
                  * @param specularPower - specular power of standard material.
                  * @returns - Number representing the roughness value.
                  * @returns - Number representing the roughness value.
                  */
                  */
-                function solveForRoughness(specularPower) {
+                function _solveForRoughness(specularPower) {
                     var t = Math.pow(specularPower / P3.x, 0.333333);
                     var t = Math.pow(specularPower / P3.x, 0.333333);
-                    return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
+                    return _cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
                 }
                 }
                 var diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
                 var diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
                 var opacity = babylonStandardMaterial.alpha;
                 var opacity = babylonStandardMaterial.alpha;
-                var specularPower = BABYLON.Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this.maxSpecularPower);
-                var roughness = solveForRoughness(specularPower);
+                var specularPower = BABYLON.Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this._maxSpecularPower);
+                var roughness = _solveForRoughness(specularPower);
                 var glTFPbrMetallicRoughness = {
                 var glTFPbrMetallicRoughness = {
                     baseColorFactor: [
                     baseColorFactor: [
                         diffuse.r,
                         diffuse.r,
@@ -1078,14 +1089,14 @@ var BABYLON;
              * @param oneMinusSpecularStrength - one minus the specular strength
              * @param oneMinusSpecularStrength - one minus the specular strength
              * @returns - metallic value
              * @returns - metallic value
              */
              */
-            _GLTFMaterial.SolveMetallic = function (diffuse, specular, oneMinusSpecularStrength) {
-                if (specular < _GLTFMaterial.dielectricSpecular.r) {
-                    _GLTFMaterial.dielectricSpecular;
+            _GLTFMaterial._SolveMetallic = function (diffuse, specular, oneMinusSpecularStrength) {
+                if (specular < _GLTFMaterial._dielectricSpecular.r) {
+                    _GLTFMaterial._dielectricSpecular;
                     return 0;
                     return 0;
                 }
                 }
-                var a = _GLTFMaterial.dielectricSpecular.r;
-                var b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial.dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial.dielectricSpecular.r;
-                var c = _GLTFMaterial.dielectricSpecular.r - specular;
+                var a = _GLTFMaterial._dielectricSpecular.r;
+                var b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial._dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial._dielectricSpecular.r;
+                var c = _GLTFMaterial._dielectricSpecular.r - specular;
                 var D = b * b - 4.0 * a * c;
                 var D = b * b - 4.0 * a * c;
                 return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
                 return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
             };
             };
@@ -1094,7 +1105,7 @@ var BABYLON;
              * @param babylonMaterial - Babylon Material
              * @param babylonMaterial - Babylon Material
              * @returns - The Babylon alpha mode value
              * @returns - The Babylon alpha mode value
              */
              */
-            _GLTFMaterial.GetAlphaMode = function (babylonMaterial) {
+            _GLTFMaterial._GetAlphaMode = function (babylonMaterial) {
                 if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                 if (babylonMaterial instanceof BABYLON.StandardMaterial) {
                     var babylonStandardMaterial = babylonMaterial;
                     var babylonStandardMaterial = babylonMaterial;
                     if ((babylonStandardMaterial.alpha != 1.0) ||
                     if ((babylonStandardMaterial.alpha != 1.0) ||
@@ -1162,11 +1173,11 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertStandardMaterial = function (babylonStandardMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertStandardMaterial = function (babylonStandardMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
                 BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
-                var glTFPbrMetallicRoughness = _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
+                var glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
                 var glTFMaterial = { name: babylonStandardMaterial.name };
                 var glTFMaterial = { name: babylonStandardMaterial.name };
-                if (babylonStandardMaterial.backFaceCulling) {
+                if (babylonStandardMaterial.backFaceCulling != null && !babylonStandardMaterial.backFaceCulling) {
                     if (!babylonStandardMaterial.twoSidedLighting) {
                     if (!babylonStandardMaterial.twoSidedLighting) {
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     }
                     }
@@ -1174,26 +1185,26 @@ var BABYLON;
                 }
                 }
                 if (hasTextureCoords) {
                 if (hasTextureCoords) {
                     if (babylonStandardMaterial.diffuseTexture) {
                     if (babylonStandardMaterial.diffuseTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonStandardMaterial.bumpTexture) {
                     if (babylonStandardMaterial.bumpTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.normalTexture = glTFTexture;
                             glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonStandardMaterial.emissiveTexture) {
                     if (babylonStandardMaterial.emissiveTexture) {
-                        var glTFEmissiveTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        var glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData);
                         if (glTFEmissiveTexture) {
                         if (glTFEmissiveTexture) {
                             glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                             glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                         }
                         }
                         glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                         glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                     }
                     }
                     if (babylonStandardMaterial.ambientTexture) {
                     if (babylonStandardMaterial.ambientTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             var occlusionTexture = {
                             var occlusionTexture = {
                                 index: glTFTexture.index
                                 index: glTFTexture.index
@@ -1211,7 +1222,7 @@ var BABYLON;
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                         BABYLON.Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                     }
                     }
                 }
                 }
-                if (babylonStandardMaterial.emissiveColor) {
+                if (babylonStandardMaterial.emissiveColor && !this.FuzzyEquals(babylonStandardMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
                     glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
                     glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
                 }
                 }
                 glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
                 glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
@@ -1227,7 +1238,7 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial = function (babylonPBRMetalRoughMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial = function (babylonPBRMetalRoughMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 var glTFPbrMetallicRoughness = {};
                 var glTFPbrMetallicRoughness = {};
                 if (babylonPBRMetalRoughMaterial.baseColor) {
                 if (babylonPBRMetalRoughMaterial.baseColor) {
                     glTFPbrMetallicRoughness.baseColorFactor = [
                     glTFPbrMetallicRoughness.baseColorFactor = [
@@ -1237,10 +1248,10 @@ var BABYLON;
                         babylonPBRMetalRoughMaterial.alpha
                         babylonPBRMetalRoughMaterial.alpha
                     ];
                     ];
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.metallic != null) {
+                if (babylonPBRMetalRoughMaterial.metallic != null && babylonPBRMetalRoughMaterial.metallic !== 1) {
                     glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
                     glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.roughness != null) {
+                if (babylonPBRMetalRoughMaterial.roughness != null && babylonPBRMetalRoughMaterial.roughness !== 1) {
                     glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
                     glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
                 }
                 }
                 var glTFMaterial = {
                 var glTFMaterial = {
@@ -1251,19 +1262,19 @@ var BABYLON;
                 }
                 }
                 if (hasTextureCoords) {
                 if (hasTextureCoords) {
                     if (babylonPBRMetalRoughMaterial.baseTexture != null) {
                     if (babylonPBRMetalRoughMaterial.baseTexture != null) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                             glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.normalTexture) {
                     if (babylonPBRMetalRoughMaterial.normalTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.normalTexture = glTFTexture;
                             glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.occlusionTexture) {
                     if (babylonPBRMetalRoughMaterial.occlusionTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
                         if (glTFTexture) {
                         if (glTFTexture) {
                             glTFMaterial.occlusionTexture = glTFTexture;
                             glTFMaterial.occlusionTexture = glTFTexture;
                             if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
                             if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
@@ -1272,17 +1283,17 @@ var BABYLON;
                         }
                         }
                     }
                     }
                     if (babylonPBRMetalRoughMaterial.emissiveTexture) {
                     if (babylonPBRMetalRoughMaterial.emissiveTexture) {
-                        var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
                         if (glTFTexture != null) {
                         if (glTFTexture != null) {
                             glTFMaterial.emissiveTexture = glTFTexture;
                             glTFMaterial.emissiveTexture = glTFTexture;
                         }
                         }
                     }
                     }
                 }
                 }
-                if (babylonPBRMetalRoughMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
+                if (this.FuzzyEquals(babylonPBRMetalRoughMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
                     glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
                     glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
                 }
                 }
                 if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
                 if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
-                    var alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMetalRoughMaterial);
+                    var alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
                     if (alphaMode !== "OPAQUE" /* OPAQUE */) {
                     if (alphaMode !== "OPAQUE" /* OPAQUE */) {
                         glTFMaterial.alphaMode = alphaMode;
                         glTFMaterial.alphaMode = alphaMode;
                         if (alphaMode === "BLEND" /* BLEND */) {
                         if (alphaMode === "BLEND" /* BLEND */) {
@@ -1294,12 +1305,213 @@ var BABYLON;
                 materials.push(glTFMaterial);
                 materials.push(glTFMaterial);
             };
             };
             /**
             /**
+             * Converts an image typed array buffer to a base64 image.
+             * @param buffer - typed array buffer.
+             * @param width - width of the image.
+             * @param height - height of the image.
+             * @param mimeType - mimetype of the image.
+             * @returns - base64 image string.
+             */
+            _GLTFMaterial._CreateBase64FromCanvas = function (buffer, width, height, mimeType) {
+                var imageCanvas = document.createElement('canvas');
+                imageCanvas.id = "WriteCanvas";
+                var ctx = imageCanvas.getContext('2d');
+                imageCanvas.width = width;
+                imageCanvas.height = height;
+                var imgData = ctx.createImageData(width, height);
+                imgData.data.set(buffer);
+                ctx.putImageData(imgData, 0, 0);
+                return imageCanvas.toDataURL(mimeType);
+            };
+            /**
+             * Generates a white texture based on the specified width and height.
+             * @param width - width of the texture in pixels.
+             * @param height - height of the texture in pixels.
+             * @param scene - babylonjs scene.
+             * @returns - white texture.
+             */
+            _GLTFMaterial._CreateWhiteTexture = function (width, height, scene) {
+                var data = new Uint8Array(width * height * 4);
+                for (var i = 0; i < data.length; ++i) {
+                    data[i] = 255;
+                }
+                var rawTexture = BABYLON.RawTexture.CreateRGBATexture(data, width, height, scene);
+                return rawTexture;
+            };
+            /**
+             * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
+             * @param texture1 - first texture to resize.
+             * @param texture2 - second texture to resize.
+             * @param scene - babylonjs scene.
+             * @returns resized textures or null.
+             */
+            _GLTFMaterial._ResizeTexturesToSameDimensions = function (texture1, texture2, scene) {
+                var texture1Size = texture1 ? texture1.getSize() : { width: 0, height: 0 };
+                var texture2Size = texture2 ? texture2.getSize() : { width: 0, height: 0 };
+                var resizedTexture1;
+                var resizedTexture2;
+                if (texture1Size.width < texture2Size.width) {
+                    if (texture1) {
+                        resizedTexture1 = BABYLON.TextureTools.CreateResizedCopy(texture1, texture2Size.width, texture2Size.height, true);
+                    }
+                    else {
+                        resizedTexture1 = this._CreateWhiteTexture(texture2Size.width, texture2Size.height, scene);
+                    }
+                    resizedTexture2 = texture2;
+                }
+                else if (texture1Size.width > texture2Size.width) {
+                    if (texture2) {
+                        resizedTexture2 = BABYLON.TextureTools.CreateResizedCopy(texture2, texture1Size.width, texture1Size.height, true);
+                    }
+                    else {
+                        resizedTexture2 = this._CreateWhiteTexture(texture1Size.width, texture1Size.height, scene);
+                    }
+                    resizedTexture1 = texture1;
+                }
+                else {
+                    resizedTexture1 = texture1;
+                    resizedTexture2 = texture2;
+                }
+                return {
+                    "texture1": resizedTexture1,
+                    "texture2": resizedTexture2
+                };
+            };
+            /**
+             * Convert Specular Glossiness Textures to Metallic Roughness.
              * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
              * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
              * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
              * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
+             * @param diffuseTexture - texture used to store diffuse information.
+             * @param specularGlossinessTexture - texture used to store specular and glossiness information.
+             * @param factors - specular glossiness material factors.
+             * @param mimeType - the mime type to use for the texture.
+             * @returns pbr metallic roughness interface or null.
+             */
+            _GLTFMaterial._ConvertSpecularGlossinessTexturesToMetallicRoughness = function (diffuseTexture, specularGlossinessTexture, factors, mimeType) {
+                if (!(diffuseTexture || specularGlossinessTexture)) {
+                    return null;
+                }
+                var scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
+                if (!scene) {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
+                }
+                var resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
+                var diffuseSize = resizedTextures.texture1.getSize();
+                var diffuseBuffer;
+                var specularGlossinessBuffer;
+                var width = diffuseSize.width;
+                var height = diffuseSize.height;
+                var pixels = (resizedTextures.texture1.readPixels());
+                if (pixels instanceof Uint8Array) {
+                    diffuseBuffer = (resizedTextures.texture1.readPixels());
+                }
+                else {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+                }
+                pixels = resizedTextures.texture2.readPixels();
+                if (pixels instanceof Uint8Array) {
+                    specularGlossinessBuffer = (resizedTextures.texture2.readPixels());
+                }
+                else {
+                    throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
+                }
+                var byteLength = specularGlossinessBuffer.byteLength;
+                var metallicRoughnessBuffer = new Uint8Array(byteLength);
+                var baseColorBuffer = new Uint8Array(byteLength);
+                var strideSize = 4;
+                var maxBaseColor = BABYLON.Color3.Black();
+                var maxMetallic = 0;
+                var maxRoughness = 0;
+                for (var h = 0; h < height; ++h) {
+                    for (var w = 0; w < width; ++w) {
+                        var offset = (width * h + w) * strideSize;
+                        var diffuseColor = BABYLON.Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).multiply(factors.diffuseColor);
+                        var specularColor = BABYLON.Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).multiply(factors.specularColor);
+                        var glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
+                        var specularGlossiness = {
+                            diffuseColor: diffuseColor,
+                            specularColor: specularColor,
+                            glossiness: glossiness
+                        };
+                        var metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+                        maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
+                        maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
+                        maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
+                        maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
+                        maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
+                        baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
+                        baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
+                        baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
+                        baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
+                        metallicRoughnessBuffer[offset] = 255;
+                        metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
+                        metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
+                        metallicRoughnessBuffer[offset + 3] = 255;
+                    }
+                }
+                // Retrieves the metallic roughness factors from the maximum texture values.
+                var metallicRoughnessFactors = {
+                    baseColor: maxBaseColor,
+                    metallic: maxMetallic,
+                    roughness: maxRoughness
+                };
+                var writeOutMetallicRoughnessTexture = false;
+                var writeOutBaseColorTexture = false;
+                for (var h = 0; h < height; ++h) {
+                    for (var w = 0; w < width; ++w) {
+                        var destinationOffset = (width * h + w) * strideSize;
+                        baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
+                        baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
+                        baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
+                        var baseColorPixel = new BABYLON.Color3(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
+                        if (!this.FuzzyEquals(baseColorPixel, BABYLON.Color3.White(), this._epsilon)) {
+                            writeOutBaseColorTexture = true;
+                        }
+                        metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
+                        metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
+                        var metallicRoughnessPixel = new BABYLON.Color3(metallicRoughnessBuffer[destinationOffset], metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
+                        if (!this.FuzzyEquals(metallicRoughnessPixel, BABYLON.Color3.White(), this._epsilon)) {
+                            writeOutMetallicRoughnessTexture = true;
+                        }
+                    }
+                }
+                if (writeOutMetallicRoughnessTexture) {
+                    var metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
+                    metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
+                }
+                if (writeOutBaseColorTexture) {
+                    var baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
+                    metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
+                }
+                return metallicRoughnessFactors;
+            };
+            /**
+             * Converts specular glossiness material properties to metallic roughness.
+             * @param specularGlossiness - interface with specular glossiness material properties.
+             * @returns - interface with metallic roughness material properties.
+             */
+            _GLTFMaterial._ConvertSpecularGlossinessToMetallicRoughness = function (specularGlossiness) {
+                var diffusePerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.diffuseColor);
+                var specularPerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.specularColor);
+                var oneMinusSpecularStrength = 1 - _GLTFMaterial._GetMaxComponent(specularGlossiness.specularColor);
+                var metallic = _GLTFMaterial._SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
+                var baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this._dielectricSpecular.r) / Math.max(1 - metallic, this._epsilon));
+                var baseColorFromSpecular = specularGlossiness.specularColor.subtract(this._dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this._epsilon));
+                var baseColor = BABYLON.Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
+                baseColor = baseColor.clampToRef(0, 1, baseColor);
+                var metallicRoughness = {
+                    baseColor: baseColor,
+                    metallic: metallic,
+                    roughness: 1 - specularGlossiness.glossiness
+                };
+                return metallicRoughness;
+            };
+            /**
+             * Calculates the surface reflectance, independent of lighting conditions.
              * @param color - Color source to calculate brightness from.
              * @param color - Color source to calculate brightness from.
              * @returns number representing the perceived brightness, or zero if color is undefined.
              * @returns number representing the perceived brightness, or zero if color is undefined.
              */
              */
-            _GLTFMaterial.GetPerceivedBrightness = function (color) {
+            _GLTFMaterial._GetPerceivedBrightness = function (color) {
                 if (color) {
                 if (color) {
                     return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
                     return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
                 }
                 }
@@ -1310,7 +1522,7 @@ var BABYLON;
              * @param color
              * @param color
              * @returns maximum color component value, or zero if color is null or undefined.
              * @returns maximum color component value, or zero if color is null or undefined.
              */
              */
-            _GLTFMaterial.GetMaxComponent = function (color) {
+            _GLTFMaterial._GetMaxComponent = function (color) {
                 if (color) {
                 if (color) {
                     return Math.max(color.r, Math.max(color.g, color.b));
                     return Math.max(color.r, Math.max(color.g, color.b));
                 }
                 }
@@ -1326,110 +1538,117 @@ var BABYLON;
              * @param imageData - map of image file name to data.
              * @param imageData - map of image file name to data.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
              */
              */
-            _GLTFMaterial.ConvertPBRMaterial = function (babylonPBRMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
+            _GLTFMaterial._ConvertPBRMaterial = function (babylonPBRMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords) {
                 var glTFPbrMetallicRoughness = {};
                 var glTFPbrMetallicRoughness = {};
+                var metallicRoughness;
                 var glTFMaterial = {
                 var glTFMaterial = {
                     name: babylonPBRMaterial.name
                     name: babylonPBRMaterial.name
                 };
                 };
                 var useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
                 var useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
-                if (babylonPBRMaterial) {
-                    if (useMetallicRoughness) {
-                        glTFPbrMetallicRoughness.baseColorFactor = [
-                            babylonPBRMaterial.albedoColor.r,
-                            babylonPBRMaterial.albedoColor.g,
-                            babylonPBRMaterial.albedoColor.b,
-                            babylonPBRMaterial.alpha
-                        ];
-                        if (babylonPBRMaterial.metallic != null) {
-                            if (babylonPBRMaterial.metallic !== 1) {
-                                glTFPbrMetallicRoughness.metallicFactor = babylonPBRMaterial.metallic;
+                if (!useMetallicRoughness) {
+                    var specGloss = {
+                        diffuseColor: babylonPBRMaterial.albedoColor || BABYLON.Color3.White(),
+                        specularColor: babylonPBRMaterial.reflectivityColor || BABYLON.Color3.White(),
+                        glossiness: babylonPBRMaterial.microSurface || 1,
+                    };
+                    if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
+                        throw new Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                    }
+                    metallicRoughness = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
+                    if (!metallicRoughness) {
+                        metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
+                    }
+                    else {
+                        if (hasTextureCoords) {
+                            if (metallicRoughness.baseColorTextureBase64) {
+                                var glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                                if (glTFBaseColorTexture != null) {
+                                    glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
+                                }
                             }
                             }
-                        }
-                        if (babylonPBRMaterial.roughness != null) {
-                            if (babylonPBRMaterial.roughness !== 1) {
-                                glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMaterial.roughness;
+                            if (metallicRoughness.metallicRoughnessTextureBase64) {
+                                var glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                                if (glTFMRColorTexture != null) {
+                                    glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
+                                }
                             }
                             }
                         }
                         }
                     }
                     }
-                    else {
-                        var diffuseColor = babylonPBRMaterial.albedoColor || BABYLON.Color3.Black();
-                        var specularColor = babylonPBRMaterial.reflectionColor || BABYLON.Color3.Black();
-                        var diffusePerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(diffuseColor);
-                        var specularPerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(specularColor);
-                        var oneMinusSpecularStrength = 1 - _GLTFMaterial.GetMaxComponent(babylonPBRMaterial.reflectionColor);
-                        var metallic = _GLTFMaterial.SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
-                        var glossiness = babylonPBRMaterial.microSurface || 0;
-                        var baseColorFromDiffuse = diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this.dielectricSpecular.r) / Math.max(1 - metallic, this.epsilon));
-                        var baseColorFromSpecular = specularColor.subtract(this.dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this.epsilon));
-                        var baseColor = BABYLON.Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
-                        baseColor = baseColor.clampToRef(0, 1, baseColor);
-                        glTFPbrMetallicRoughness.baseColorFactor = [
-                            baseColor.r,
-                            baseColor.g,
-                            baseColor.b,
-                            babylonPBRMaterial.alpha
-                        ];
-                        if (metallic !== 1) {
-                            glTFPbrMetallicRoughness.metallicFactor = metallic;
-                        }
-                        if (glossiness) {
-                            glTFPbrMetallicRoughness.roughnessFactor = 1 - glossiness;
-                        }
+                }
+                else {
+                    metallicRoughness = {
+                        baseColor: babylonPBRMaterial.albedoColor,
+                        metallic: babylonPBRMaterial.metallic,
+                        roughness: babylonPBRMaterial.roughness
+                    };
+                }
+                if (!(this.FuzzyEquals(metallicRoughness.baseColor, BABYLON.Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
+                    glTFPbrMetallicRoughness.baseColorFactor = [
+                        metallicRoughness.baseColor.r,
+                        metallicRoughness.baseColor.g,
+                        metallicRoughness.baseColor.b,
+                        babylonPBRMaterial.alpha
+                    ];
+                }
+                if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
+                    glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
+                }
+                if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
+                    glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
+                }
+                if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
+                    if (!babylonPBRMaterial.twoSidedLighting) {
+                        BABYLON.Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     }
                     }
-                    if (babylonPBRMaterial.backFaceCulling) {
-                        if (!babylonPBRMaterial.twoSidedLighting) {
-                            BABYLON.Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
+                    glTFMaterial.doubleSided = true;
+                }
+                if (hasTextureCoords) {
+                    if (useMetallicRoughness && babylonPBRMaterial.albedoTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         }
                         }
-                        glTFMaterial.doubleSided = true;
                     }
                     }
-                    if (hasTextureCoords) {
-                        if (babylonPBRMaterial.albedoTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                            }
-                        }
-                        if (babylonPBRMaterial.bumpTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                glTFMaterial.normalTexture = glTFTexture;
-                            }
-                        }
-                        if (babylonPBRMaterial.ambientTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture) {
-                                var occlusionTexture = {
-                                    index: glTFTexture.index
-                                };
-                                glTFMaterial.occlusionTexture = occlusionTexture;
-                                if (babylonPBRMaterial.ambientTextureStrength) {
-                                    occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
-                                }
-                            }
+                    if (babylonPBRMaterial.bumpTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            glTFMaterial.normalTexture = glTFTexture;
                         }
                         }
-                        if (babylonPBRMaterial.emissiveTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture != null) {
-                                glTFMaterial.emissiveTexture = glTFTexture;
+                    }
+                    if (babylonPBRMaterial.ambientTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            var occlusionTexture = {
+                                index: glTFTexture.index
+                            };
+                            glTFMaterial.occlusionTexture = occlusionTexture;
+                            if (babylonPBRMaterial.ambientTextureStrength) {
+                                occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
                             }
                             }
                         }
                         }
-                        if (babylonPBRMaterial.metallicTexture) {
-                            var glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
-                            if (glTFTexture != null) {
-                                glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
-                            }
+                    }
+                    if (babylonPBRMaterial.emissiveTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture != null) {
+                            glTFMaterial.emissiveTexture = glTFTexture;
                         }
                         }
                     }
                     }
-                    if (!babylonPBRMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
-                        glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                    if (babylonPBRMaterial.metallicTexture) {
+                        var glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture != null) {
+                            glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
+                        }
                     }
                     }
-                    if (babylonPBRMaterial.transparencyMode != null) {
-                        var alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMaterial);
-                        if (alphaMode !== "OPAQUE" /* OPAQUE */) {
-                            glTFMaterial.alphaMode = alphaMode;
-                            if (alphaMode === "BLEND" /* BLEND */) {
-                                glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
-                            }
+                }
+                if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, BABYLON.Color3.Black(), this._epsilon)) {
+                    glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                }
+                if (babylonPBRMaterial.transparencyMode != null) {
+                    var alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
+                    if (alphaMode !== "OPAQUE" /* OPAQUE */) {
+                        glTFMaterial.alphaMode = alphaMode;
+                        if (alphaMode === "BLEND" /* BLEND */) {
+                            glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
                         }
                         }
                     }
                     }
                 }
                 }
@@ -1443,17 +1662,13 @@ var BABYLON;
              * @param images - Array of glTF images.
              * @param images - Array of glTF images.
              * @param textures - Array of glTF textures.
              * @param textures - Array of glTF textures.
              * @param imageData - map of image file name and data.
              * @param imageData - map of image file name and data.
-             * @return - glTF texture, or null if the texture format is not supported.
+             * @return - glTF texture info, or null if the texture format is not supported.
              */
              */
-            _GLTFMaterial.ExportTexture = function (babylonTexture, mimeType, images, textures, imageData) {
-                var textureInfo = null;
-                var glTFTexture = {
-                    source: images.length
-                };
+            _GLTFMaterial._ExportTexture = function (babylonTexture, mimeType, images, textures, imageData) {
                 var textureName = "texture_" + (textures.length - 1).toString();
                 var textureName = "texture_" + (textures.length - 1).toString();
                 var textureData = babylonTexture.getInternalTexture();
                 var textureData = babylonTexture.getInternalTexture();
                 if (textureData != null) {
                 if (textureData != null) {
-                    textureName = textureData.url;
+                    textureName = textureData.url || textureName;
                 }
                 }
                 textureName = BABYLON.Tools.GetFilename(textureName);
                 textureName = BABYLON.Tools.GetFilename(textureName);
                 var baseFile = textureName.split('.')[0];
                 var baseFile = textureName.split('.')[0];
@@ -1465,28 +1680,37 @@ var BABYLON;
                     extension = ".png";
                     extension = ".png";
                 }
                 }
                 else {
                 else {
-                    BABYLON.Tools.Error("Unsupported mime type " + mimeType);
+                    throw new Error("Unsupported mime type " + mimeType);
                 }
                 }
                 textureName = baseFile + extension;
                 textureName = baseFile + extension;
                 var pixels = babylonTexture.readPixels();
                 var pixels = babylonTexture.readPixels();
-                var imageCanvas = document.createElement('canvas');
-                imageCanvas.id = "ImageCanvas";
-                var ctx = imageCanvas.getContext('2d');
                 var size = babylonTexture.getSize();
                 var size = babylonTexture.getSize();
-                imageCanvas.width = size.width;
-                imageCanvas.height = size.height;
-                var imgData = ctx.createImageData(size.width, size.height);
-                imgData.data.set(pixels);
-                ctx.putImageData(imgData, 0, 0);
-                var base64Data = imageCanvas.toDataURL(mimeType);
-                var binStr = atob(base64Data.split(',')[1]);
+                var base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
+                return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, imageData);
+            };
+            /**
+             * Builds a texture from base64 string.
+             * @param base64Texture - base64 texture string.
+             * @param textureName - Name to use for the texture.
+             * @param mimeType - image mime type for the texture.
+             * @param images - array of images.
+             * @param textures - array of textures.
+             * @param imageData - map of image data.
+             * @returns - glTF texture info, or null if the texture format is not supported.
+             */
+            _GLTFMaterial._GetTextureInfoFromBase64 = function (base64Texture, textureName, mimeType, images, textures, imageData) {
+                var textureInfo = null;
+                var glTFTexture = {
+                    source: images.length
+                };
+                var binStr = atob(base64Texture.split(',')[1]);
                 var arr = new Uint8Array(binStr.length);
                 var arr = new Uint8Array(binStr.length);
                 for (var i = 0; i < binStr.length; ++i) {
                 for (var i = 0; i < binStr.length; ++i) {
                     arr[i] = binStr.charCodeAt(i);
                     arr[i] = binStr.charCodeAt(i);
                 }
                 }
                 var imageValues = { data: arr, mimeType: mimeType };
                 var imageValues = { data: arr, mimeType: mimeType };
                 imageData[textureName] = imageValues;
                 imageData[textureName] = imageValues;
-                if (mimeType === "image/jpeg" /* JPEG */) {
+                if (mimeType === "image/jpeg" /* JPEG */ || mimeType === "image/png" /* PNG */) {
                     var glTFImage = {
                     var glTFImage = {
                         uri: textureName
                         uri: textureName
                     };
                     };
@@ -1519,12 +1743,15 @@ var BABYLON;
             /**
             /**
              * Represents the dielectric specular values for R, G and B.
              * Represents the dielectric specular values for R, G and B.
              */
              */
-            _GLTFMaterial.dielectricSpecular = new BABYLON.Color3(0.04, 0.04, 0.04);
+            _GLTFMaterial._dielectricSpecular = new BABYLON.Color3(0.04, 0.04, 0.04);
             /**
             /**
              * Allows the maximum specular power to be defined for material calculations.
              * Allows the maximum specular power to be defined for material calculations.
              */
              */
-            _GLTFMaterial.maxSpecularPower = 1024;
-            _GLTFMaterial.epsilon = 1e-6;
+            _GLTFMaterial._maxSpecularPower = 1024;
+            /**
+             * Numeric tolerance value
+             */
+            _GLTFMaterial._epsilon = 1e-6;
             return _GLTFMaterial;
             return _GLTFMaterial;
         }());
         }());
         GLTF2._GLTFMaterial = _GLTFMaterial;
         GLTF2._GLTFMaterial = _GLTFMaterial;

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


+ 86 - 29
dist/preview release/serializers/babylonjs.serializers.module.d.ts

@@ -305,23 +305,33 @@ declare module BABYLON.GLTF2 {
         /**
         /**
          * Represents the dielectric specular values for R, G and B.
          * Represents the dielectric specular values for R, G and B.
          */
          */
-        private static readonly dielectricSpecular;
+        private static readonly _dielectricSpecular;
         /**
         /**
          * Allows the maximum specular power to be defined for material calculations.
          * Allows the maximum specular power to be defined for material calculations.
          */
          */
-        private static maxSpecularPower;
-        private static epsilon;
+        private static _maxSpecularPower;
+        /**
+         * Numeric tolerance value
+         */
+        private static _epsilon;
+        /**
+         * Specifies if two colors are approximately equal in value.
+         * @param color1 - first color to compare to.
+         * @param color2 - second color to compare to.
+         * @param epsilon - threshold value
+         */
+        private static FuzzyEquals(color1, color2, epsilon);
         /**
         /**
          * Gets the materials from a Babylon scene and converts them to glTF materials.
          * Gets the materials from a Babylon scene and converts them to glTF materials.
-         * @param scene
-         * @param mimeType
-         * @param images
-         * @param textures
-         * @param materials
-         * @param imageData
-         * @param hasTextureCoords
-         */
-        static ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+         * @param scene - babylonjs scene.
+         * @param mimeType - texture mime type.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param materials - array of materials.
+         * @param imageData - mapping of texture names to base64 textures
+         * @param hasTextureCoords - specifies if texture coordinates are present on the material.
+         */
+        static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -332,19 +342,19 @@ declare module BABYLON.GLTF2 {
          * @param originalMaterial - original glTF material.
          * @param originalMaterial - original glTF material.
          * @returns glTF material without texture parameters
          * @returns glTF material without texture parameters
          */
          */
-        static StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial;
+        static _StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial;
         /**
         /**
          * Specifies if the material has any texture parameters present.
          * Specifies if the material has any texture parameters present.
          * @param material - glTF Material.
          * @param material - glTF Material.
          * @returns boolean specifying if texture parameters are present
          * @returns boolean specifying if texture parameters are present
          */
          */
-        static HasTexturesPresent(material: IMaterial): boolean;
+        static _HasTexturesPresent(material: IMaterial): boolean;
         /**
         /**
          * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
          * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
          * @param babylonStandardMaterial
          * @param babylonStandardMaterial
          * @returns - glTF Metallic Roughness Material representation
          * @returns - glTF Metallic Roughness Material representation
          */
          */
-        static ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness;
+        static _ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness;
         /**
         /**
          * Computes the metallic factor
          * Computes the metallic factor
          * @param diffuse - diffused value
          * @param diffuse - diffused value
@@ -352,13 +362,13 @@ declare module BABYLON.GLTF2 {
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @returns - metallic value
          * @returns - metallic value
          */
          */
-        static SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number;
+        static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number;
         /**
         /**
          * Gets the glTF alpha mode from the Babylon Material
          * Gets the glTF alpha mode from the Babylon Material
          * @param babylonMaterial - Babylon Material
          * @param babylonMaterial - Babylon Material
          * @returns - The Babylon alpha mode value
          * @returns - The Babylon alpha mode value
          */
          */
-        static GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode;
+        static _GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode;
         /**
         /**
          * Converts a Babylon Standard Material to a glTF Material.
          * Converts a Babylon Standard Material to a glTF Material.
          * @param babylonStandardMaterial - BJS Standard Material.
          * @param babylonStandardMaterial - BJS Standard Material.
@@ -369,7 +379,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -385,25 +395,66 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
             };
             };
         }, hasTextureCoords: boolean): void;
         }, hasTextureCoords: boolean): void;
         /**
         /**
+         * Converts an image typed array buffer to a base64 image.
+         * @param buffer - typed array buffer.
+         * @param width - width of the image.
+         * @param height - height of the image.
+         * @param mimeType - mimetype of the image.
+         * @returns - base64 image string.
+         */
+        private static _CreateBase64FromCanvas(buffer, width, height, mimeType);
+        /**
+         * Generates a white texture based on the specified width and height.
+         * @param width - width of the texture in pixels.
+         * @param height - height of the texture in pixels.
+         * @param scene - babylonjs scene.
+         * @returns - white texture.
+         */
+        private static _CreateWhiteTexture(width, height, scene);
+        /**
+         * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
+         * @param texture1 - first texture to resize.
+         * @param texture2 - second texture to resize.
+         * @param scene - babylonjs scene.
+         * @returns resized textures or null.
+         */
+        private static _ResizeTexturesToSameDimensions(texture1, texture2, scene);
+        /**
+         * Convert Specular Glossiness Textures to Metallic Roughness.
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
+         * @param diffuseTexture - texture used to store diffuse information.
+         * @param specularGlossinessTexture - texture used to store specular and glossiness information.
+         * @param factors - specular glossiness material factors.
+         * @param mimeType - the mime type to use for the texture.
+         * @returns pbr metallic roughness interface or null.
+         */
+        private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture, specularGlossinessTexture, factors, mimeType);
+        /**
+         * Converts specular glossiness material properties to metallic roughness.
+         * @param specularGlossiness - interface with specular glossiness material properties.
+         * @returns - interface with metallic roughness material properties.
+         */
+        private static _ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+        /**
+         * Calculates the surface reflectance, independent of lighting conditions.
          * @param color - Color source to calculate brightness from.
          * @param color - Color source to calculate brightness from.
          * @returns number representing the perceived brightness, or zero if color is undefined.
          * @returns number representing the perceived brightness, or zero if color is undefined.
          */
          */
-        static GetPerceivedBrightness(color: Color3): number;
+        private static _GetPerceivedBrightness(color);
         /**
         /**
          * Returns the maximum color component value.
          * Returns the maximum color component value.
          * @param color
          * @param color
          * @returns maximum color component value, or zero if color is null or undefined.
          * @returns maximum color component value, or zero if color is null or undefined.
          */
          */
-        static GetMaxComponent(color: Color3): number;
+        private static _GetMaxComponent(color);
         /**
         /**
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
          * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
          * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
@@ -414,7 +465,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        static ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: {
             [fileName: string]: {
             [fileName: string]: {
                 data: Uint8Array;
                 data: Uint8Array;
                 mimeType: ImageMimeType;
                 mimeType: ImageMimeType;
@@ -427,13 +478,19 @@ declare module BABYLON.GLTF2 {
          * @param images - Array of glTF images.
          * @param images - Array of glTF images.
          * @param textures - Array of glTF textures.
          * @param textures - Array of glTF textures.
          * @param imageData - map of image file name and data.
          * @param imageData - map of image file name and data.
-         * @return - glTF texture, or null if the texture format is not supported.
+         * @return - glTF texture info, or null if the texture format is not supported.
          */
          */
-        static ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: {
-            [fileName: string]: {
-                data: Uint8Array;
-                mimeType: ImageMimeType;
-            };
-        }): Nullable<ITextureInfo>;
+        private static _ExportTexture(babylonTexture, mimeType, images, textures, imageData);
+        /**
+         * Builds a texture from base64 string.
+         * @param base64Texture - base64 texture string.
+         * @param textureName - Name to use for the texture.
+         * @param mimeType - image mime type for the texture.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param imageData - map of image data.
+         * @returns - glTF texture info, or null if the texture format is not supported.
+         */
+        private static _GetTextureInfoFromBase64(base64Texture, textureName, mimeType, images, textures, imageData);
     }
     }
 }
 }

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

@@ -1,7 +1,7 @@
 {
 {
-  "errors": 7220,
+  "errors": 7189,
   "babylon.typedoc.json": {
   "babylon.typedoc.json": {
-    "errors": 7220,
+    "errors": 7189,
     "AnimationKeyInterpolation": {
     "AnimationKeyInterpolation": {
       "Enumeration": {
       "Enumeration": {
         "Comments": {
         "Comments": {
@@ -16462,28 +16462,6 @@
             }
             }
           }
           }
         },
         },
-        "Lerp": {
-          "Comments": {
-            "MissingReturn": true
-          },
-          "Parameter": {
-            "startValue": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "endValue": {
-              "Comments": {
-                "MissingText": true
-              }
-            },
-            "gradient": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
         "LookAtLH": {
         "LookAtLH": {
           "Comments": {
           "Comments": {
             "MissingReturn": true
             "MissingReturn": true
@@ -38823,114 +38801,6 @@
         }
         }
       }
       }
     },
     },
-    "IImageProcessingConfigurationDefines": {
-      "Property": {
-        "COLORCURVES": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "COLORGRADING": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "COLORGRADING3D": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "CONTRAST": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "EXPOSURE": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "IMAGEPROCESSING": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "IMAGEPROCESSINGPOSTPROCESS": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "SAMPLER3DBGRMAP": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "SAMPLER3DGREENDEPTH": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "TONEMAPPING": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "VIGNETTE": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "VIGNETTEBLENDMODEMULTIPLY": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "VIGNETTEBLENDMODEOPAQUE": {
-          "Naming": {
-            "NotCamelCase": true
-          },
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      }
-    },
     "ILoadingScreen": {
     "ILoadingScreen": {
       "Interface": {
       "Interface": {
         "Comments": {
         "Comments": {

+ 80 - 24
dist/preview release/viewer/babylon.viewer.d.ts

@@ -224,6 +224,11 @@ declare module BabylonViewer {
         subtitle?: string;
         subtitle?: string;
         thumbnail?: string; // URL or data-url
         thumbnail?: string; // URL or data-url
 
 
+        animation?: {
+            autoStart?: boolean | string;
+            playOnce?: boolean;
+        }
+
         // [propName: string]: any; // further configuration, like title and creator
         // [propName: string]: any; // further configuration, like title and creator
     }
     }
 
 
@@ -400,57 +405,108 @@ declare module BabylonViewer {
     }
     }
     /////>configuration
     /////>configuration
 
 
+    export enum AnimationPlayMode {
+        ONCE = 0,
+        LOOP = 1,
+    }
+    export enum AnimationState {
+        INIT = 0,
+        PLAYING = 1,
+        PAUSED = 2,
+        STOPPED = 3,
+        ENDED = 4,
+    }
+    interface IModelAnimation extends BABYLON.IDisposable {
+        readonly state: AnimationState;
+        readonly name: string;
+        readonly frames: number;
+        readonly currentFrame: number;
+        readonly fps: number;
+        speedRatio: number;
+        playMode: AnimationPlayMode;
+        start(): any;
+        stop(): any;
+        pause(): any;
+        reset(): any;
+        restart(): any;
+        goToFrame(frameNumber: number): any;
+    }
+
+    class ViewerModel implements BABYLON.IDisposable {
+        loader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
+        meshes: Array<BABYLON.AbstractMesh>;
+        particleSystems: Array<BABYLON.ParticleSystem>;
+        skeletons: Array<BABYLON.Skeleton>;
+        currentAnimation: IModelAnimation;
+        onLoadedObservable: BABYLON.Observable<ViewerModel>;
+        onLoadProgressObservable: BABYLON.Observable<BABYLON.SceneLoaderProgressEvent>;
+        onLoadErrorObservable: BABYLON.Observable<{
+            message: string;
+            exception: any;
+        }>;
+        constructor(_modelConfiguration: IModelConfiguration, _scene: BABYLON.Scene, disableAutoLoad?: boolean);
+        load(): void;
+        getAnimationNames(): string[];
+        protected _getAnimationByName(name: string): BABYLON.Nullable<IModelAnimation>;
+        playAnimation(name: string): IModelAnimation;
+        dispose(): void;
+    }
+
     /////<viewer
     /////<viewer
     export abstract class AbstractViewer {
     export abstract class AbstractViewer {
         containerElement: HTMLElement;
         containerElement: HTMLElement;
         templateManager: TemplateManager;
         templateManager: TemplateManager;
-        camera: BABYLON.ArcRotateCamera;
         engine: BABYLON.Engine;
         engine: BABYLON.Engine;
         scene: BABYLON.Scene;
         scene: BABYLON.Scene;
+        camera: BABYLON.ArcRotateCamera;
+        sceneOptimizer: BABYLON.SceneOptimizer;
         baseId: string;
         baseId: string;
-        canvas: HTMLCanvasElement;
+        models: Array<ViewerModel>;
+        lastUsedLoader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
         protected configuration: ViewerConfiguration;
         protected configuration: ViewerConfiguration;
         environmentHelper: BABYLON.EnvironmentHelper;
         environmentHelper: BABYLON.EnvironmentHelper;
         protected defaultHighpTextureType: number;
         protected defaultHighpTextureType: number;
         protected shadowGeneratorBias: number;
         protected shadowGeneratorBias: number;
         protected defaultPipelineTextureType: number;
         protected defaultPipelineTextureType: number;
         protected maxShadows: number;
         protected maxShadows: number;
+        readonly isHdrSupported: boolean;
         onSceneInitObservable: BABYLON.Observable<BABYLON.Scene>;
         onSceneInitObservable: BABYLON.Observable<BABYLON.Scene>;
         onEngineInitObservable: BABYLON.Observable<BABYLON.Engine>;
         onEngineInitObservable: BABYLON.Observable<BABYLON.Engine>;
-        onModelLoadedObservable: BABYLON.Observable<BABYLON.AbstractMesh[]>;
+        onModelLoadedObservable: BABYLON.Observable<ViewerModel>;
         onModelLoadProgressObservable: BABYLON.Observable<BABYLON.SceneLoaderProgressEvent>;
         onModelLoadProgressObservable: BABYLON.Observable<BABYLON.SceneLoaderProgressEvent>;
-        onModelLoadErrorObservable: BABYLON.Observable<{ message: string; exception: any }>;
+        onModelLoadErrorObservable: BABYLON.Observable<{
+            message: string;
+            exception: any;
+        }>;
         onLoaderInitObservable: BABYLON.Observable<BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync>;
         onLoaderInitObservable: BABYLON.Observable<BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync>;
         onInitDoneObservable: BABYLON.Observable<AbstractViewer>;
         onInitDoneObservable: BABYLON.Observable<AbstractViewer>;
+        canvas: HTMLCanvasElement;
+        protected registeredOnBeforerenderFunctions: Array<() => void>;
         constructor(containerElement: HTMLElement, initialConfiguration?: ViewerConfiguration);
         constructor(containerElement: HTMLElement, initialConfiguration?: ViewerConfiguration);
         getBaseId(): string;
         getBaseId(): string;
-        protected abstract prepareContainerElement(): any;
-        protected onTemplatesLoaded(): Promise<AbstractViewer>;
-        protected initEngine(): Promise<BABYLON.Engine>;
-        protected initScene(): Promise<BABYLON.Scene>;
-        dispose(): void;
-        loadModel(model?: any, clearScene?: boolean): Promise<BABYLON.Scene>;
-        lastUsedLoader: BABYLON.ISceneLoaderPlugin | BABYLON.ISceneLoaderPluginAsync;
-        sceneOptimizer: BABYLON.SceneOptimizer;
-        protected registeredOnBeforerenderFunctions: Array<() => void>;
         isCanvasInDOM(): boolean;
         isCanvasInDOM(): boolean;
         protected resize: () => void;
         protected resize: () => void;
         protected render: () => void;
         protected render: () => void;
-        updateConfiguration(newConfiguration: Partial<ViewerConfiguration>): void;
-        protected configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean): void;
+        updateConfiguration(newConfiguration?: Partial<ViewerConfiguration>): void;
+        protected configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean): Promise<BABYLON.Scene> | undefined;
         protected configureScene(sceneConfig: ISceneConfiguration, optimizerConfig?: ISceneOptimizerConfiguration): void;
         protected configureScene(sceneConfig: ISceneConfiguration, optimizerConfig?: ISceneOptimizerConfiguration): void;
         protected configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean): void;
         protected configureOptimizer(optimizerConfig: ISceneOptimizerConfiguration | boolean): void;
         protected configureObservers(observersConfiguration: IObserversConfiguration): void;
         protected configureObservers(observersConfiguration: IObserversConfiguration): void;
-        protected configureCamera(cameraConfig: ICameraConfiguration, focusMeshes: Array<BABYLON.AbstractMesh>): void;
-        protected configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean }, focusMeshes: Array<BABYLON.AbstractMesh>): void;
-        protected configureModel(modelConfiguration: Partial<IModelConfiguration>, focusMeshes: Array<BABYLON.AbstractMesh>): void;
+        protected configureCamera(cameraConfig: ICameraConfiguration, model?: ViewerModel): void;
+        protected configureLights(lightsConfiguration?: {
+            [name: string]: ILightConfiguration | boolean;
+        }, model?: ViewerModel): void;
+        protected configureModel(modelConfiguration: Partial<IModelConfiguration>, model?: ViewerModel): void;
         dispose(): void;
         dispose(): void;
-        protected initEnvironment(focusMeshes: Array<BABYLON.AbstractMesh>): Promise<BABYLON.Scene>;
+        protected abstract prepareContainerElement(): any;
+        protected onTemplatesLoaded(): Promise<AbstractViewer>;
+        protected initEngine(): Promise<BABYLON.Engine>;
+        protected initScene(): Promise<BABYLON.Scene>;
+        loadModel(modelConfig?: any, clearScene?: boolean): Promise<ViewerModel>;
+        protected initEnvironment(viewerModel?: ViewerModel): Promise<BABYLON.Scene>;
+        protected handleHardwareLimitations(): void;
         protected injectCustomShaders(): void;
         protected injectCustomShaders(): void;
         protected extendClassWithConfig(object: any, config: any): void;
         protected extendClassWithConfig(object: any, config: any): void;
-        protected handleHardwareLimitations(): void;
-
-
     }
     }
 
 
     export class DefaultViewer extends AbstractViewer {
     export class DefaultViewer extends AbstractViewer {
@@ -460,8 +516,8 @@ declare module BabylonViewer {
         initScene(): Promise<BABYLON.Scene>;
         initScene(): Promise<BABYLON.Scene>;
         protected onTemplatesLoaded(): Promise<AbstractViewer>;
         protected onTemplatesLoaded(): Promise<AbstractViewer>;
         protected prepareContainerElement(): void;
         protected prepareContainerElement(): void;
-        loadModel(model?: any): Promise<BABYLON.Scene>;
-        initEnvironment(focusMeshes?: Array<BABYLON.AbstractMesh>): Promise<BABYLON.Scene>;
+        loadModel(model?: any): Promise<ViewerModel>;
+        initEnvironment(viewerModel?: ViewerModel): Promise<BABYLON.Scene>;
         showOverlayScreen(subScreen: string): Promise<Template>;
         showOverlayScreen(subScreen: string): Promise<Template>;
         hideOverlayScreen(): Promise<Template>;
         hideOverlayScreen(): Promise<Template>;
         showLoadingScreen(): Promise<Template>;
         showLoadingScreen(): Promise<Template>;

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


File diff suppressed because it is too large
+ 1310 - 700
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -14,12 +14,13 @@
 - Added [VideoDome](http://doc.babylonjs.com/how_to/360videodome) class to easily support 360 videos ([DavidHGillen](https://github.com/DavidHGillen))
 - 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))
 - Added [GlowLayer](https://doc.babylonjs.com/how_to/glow_layer) to easily support glow from emissive materials ([sebavan](https://github.com/sebavan))
 - New [AssetContainer](http://doc.babylonjs.com/how_to/how_to_use_assetcontainer) Class and loading methods ([trevordev](https://github.com/trevordev))
 - New [AssetContainer](http://doc.babylonjs.com/how_to/how_to_use_assetcontainer) Class and loading methods ([trevordev](https://github.com/trevordev))
-- Added depth of field, MSAA, sharpening, grain and chromatic aberration effect to the default pipeline ([trevordev](https://github.com/trevordev))
+- Added depth of field, MSAA, sharpening, chromatic aberration and grain effect to the default pipeline ([trevordev](https://github.com/trevordev))
 - Particle System SubEmitters- Spawn new Sub emitter when particles dies. Cone/Sphere shapes emitters ([IbraheemOsama](https://github.com/IbraheemOsama))
 - Particle System SubEmitters- Spawn new Sub emitter when particles dies. Cone/Sphere shapes emitters ([IbraheemOsama](https://github.com/IbraheemOsama))
 
 
 ## Updates
 ## Updates
 
 
 - Tons of functions and classes received the code comments they deserved (All the community)
 - Tons of functions and classes received the code comments they deserved (All the community)
+- Added support for 16bits TGA ([deltakosh](https://github.com/deltakosh))
 - New `AnimationPropertiesOverride` class used to simplify setting animation properties on child animations. [Documentation](http://doc.babylonjs.com/babylon101/animations#overriding-properties) ([deltakosh](https://github.com/deltakosh))
 - New `AnimationPropertiesOverride` class used to simplify setting animation properties on child animations. [Documentation](http://doc.babylonjs.com/babylon101/animations#overriding-properties) ([deltakosh](https://github.com/deltakosh))
 - New `Texture.UseSerializedUrlIfAny` static property to let textures serialize complete URL instead of using side by side loading ([deltakosh](https://github.com/deltakosh))
 - New `Texture.UseSerializedUrlIfAny` static property to let textures serialize complete URL instead of using side by side loading ([deltakosh](https://github.com/deltakosh))
 - Added `particleSystem.reset()` to clear a particle system ([deltakosh](https://github.com/deltakosh))
 - Added `particleSystem.reset()` to clear a particle system ([deltakosh](https://github.com/deltakosh))
@@ -85,6 +86,8 @@
 - WebVRExperienceHelper will create an empty controller model so that controller interactions can be used while the actual model is still loading ([trevordev](https://github.com/trevordev))
 - WebVRExperienceHelper will create an empty controller model so that controller interactions can be used while the actual model is still loading ([trevordev](https://github.com/trevordev))
 - Default fragment shader will clamp negative values to avoid underflow, webVR post processing will render to eye texture size ([trevordev](https://github.com/trevordev))
 - Default fragment shader will clamp negative values to avoid underflow, webVR post processing will render to eye texture size ([trevordev](https://github.com/trevordev))
 - Supports Environment Drag and Drop in Sandbox ([sebavan](https://github.com/sebavan))
 - Supports Environment Drag and Drop in Sandbox ([sebavan](https://github.com/sebavan))
+- EnvironmentHelper has no an onError observable to handle errors when loading the textures ([RaananW](https://github.com/RaananW))
+- (Viewer) Viewer supports model animations ([RaananW](https://github.com/RaananW))
 
 
 ## Bug fixes
 ## Bug fixes
 
 
@@ -92,7 +95,7 @@
 - SPS solid particle `.pivot` property now also behaves like the standard mesh pivot. Former behavior (particle translation) can be kept with the particle property `.translateFromPivot` set to true ([jbousquie](https://github.com/jbousquie))
 - SPS solid particle `.pivot` property now also behaves like the standard mesh pivot. Former behavior (particle translation) can be kept with the particle property `.translateFromPivot` set to true ([jbousquie](https://github.com/jbousquie))
 - Texture extension detection in `Engine.CreateTexture` ([sebavan](https://github.com/sebavan))
 - Texture extension detection in `Engine.CreateTexture` ([sebavan](https://github.com/sebavan))
 - SPS internal temporary vector3 instead of Tmp.Vector3 to avoid possible concurrent uses ([jbousquie](https://github.com/jbousquie))
 - SPS internal temporary vector3 instead of Tmp.Vector3 to avoid possible concurrent uses ([jbousquie](https://github.com/jbousquie))
-- Fixed a bug when calling load on an empty assets manager - [#3739](https://github.com/BabylonJS/Babylon.js/issues/3739). ([RaananW](https://github.com/RaananW))
+- Fixed a bug when calling load on an empty assets manager - [#3739](https://github.com/BabylonJS/Babylon.js/issues/3739) ([RaananW](https://github.com/RaananW))
 - Enabling teleportation in the vr helper class caused a redundant post process to be added ([trevordev](https://github.com/trevordev))
 - Enabling teleportation in the vr helper class caused a redundant post process to be added ([trevordev](https://github.com/trevordev))
 - (Viewer) Fixed a bug where loading another mesh positioned it incorrectly ([RaananW](https://github.com/RaananW))
 - (Viewer) Fixed a bug where loading another mesh positioned it incorrectly ([RaananW](https://github.com/RaananW))
 - Scale vr controllers by deviceScale when it is set in VRExperienceHelper ([trevordev](https://github.com/trevordev))
 - Scale vr controllers by deviceScale when it is set in VRExperienceHelper ([trevordev](https://github.com/trevordev))
@@ -102,10 +105,11 @@
 - postMessage calls in webworkers were fixed. ([RaananW](https://github.com/RaananW))
 - postMessage calls in webworkers were fixed. ([RaananW](https://github.com/RaananW))
 - Fixed WebCam Texture on Firefox and Edge - [#3825](https://github.com/BabylonJS/Babylon.js/issues/3825) ([sebavan](https://github.com/sebavan))
 - Fixed WebCam Texture on Firefox and Edge - [#3825](https://github.com/BabylonJS/Babylon.js/issues/3825) ([sebavan](https://github.com/sebavan))
 - Add onLoadObservable on VideoTexture - [#3845](https://github.com/BabylonJS/Babylon.js/issues/3845) ([sebavan](https://github.com/sebavan))
 - Add onLoadObservable on VideoTexture - [#3845](https://github.com/BabylonJS/Babylon.js/issues/3845) ([sebavan](https://github.com/sebavan))
+- beforeRender is now triggered after the camera updated its state - [#3873](https://github.com/BabylonJS/Babylon.js/issues/3873) ([RaananW](https://github.com/RaananW))
 
 
 ## Breaking changes
 ## Breaking changes
 
 
 - Removed the unused PostProcessRenderPass class and extended postProcessingRenderingEffect to support multiple PostProcesses ([trevordev](https://github.com/trevordev))
 - Removed the unused PostProcessRenderPass class and extended postProcessingRenderingEffect to support multiple PostProcesses ([trevordev](https://github.com/trevordev))
 - VertexData.merge no longer supports merging of data that do not have the same set of attributes. ([bghgary](https://github.com/bghgary)]
 - VertexData.merge no longer supports merging of data that do not have the same set of attributes. ([bghgary](https://github.com/bghgary)]
-- glTF 2.0 loader will now create a mesh for each primitive instead of merging the primitives together into one mesh. ([bghgary](https://github.com/bghgary)]
+- glTF 2.0 loader now creates a mesh for each primitive instead of merging the primitives together into one mesh. If a mesh only has one primitive, the behavior is the same as before. This change only affects meshes that have multiple primitives. ([bghgary](https://github.com/bghgary)]
 - Engine's onCanvasPointerOutObservable will now return a PointerEvent instead of the Engine. ([trevordev](https://github.com/trevordev))
 - Engine's onCanvasPointerOutObservable will now return a PointerEvent instead of the Engine. ([trevordev](https://github.com/trevordev))

+ 1 - 0
loaders/src/glTF/1.0/babylon.glTFLoader.ts

@@ -1571,6 +1571,7 @@ module BABYLON.GLTF1 {
         public onMeshLoadedObservable = new Observable<AbstractMesh>();
         public onMeshLoadedObservable = new Observable<AbstractMesh>();
         public onTextureLoadedObservable = new Observable<BaseTexture>();
         public onTextureLoadedObservable = new Observable<BaseTexture>();
         public onMaterialLoadedObservable = new Observable<Material>();
         public onMaterialLoadedObservable = new Observable<Material>();
+        public onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
         public onCompleteObservable = new Observable<IGLTFLoader>();
         public onCompleteObservable = new Observable<IGLTFLoader>();
         public onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
         public onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
 
 

+ 4 - 4
loaders/src/glTF/2.0/Extensions/KHR_lights.ts

@@ -40,7 +40,7 @@ module BABYLON.GLTF2.Extensions {
 
 
                 const light = GLTFLoader._GetProperty(context, this._lights, extension.light);
                 const light = GLTFLoader._GetProperty(context, this._lights, extension.light);
                 if (light.type !== LightType.AMBIENT) {
                 if (light.type !== LightType.AMBIENT) {
-                    throw new Error(context + ": Only ambient lights are allowed on a scene");
+                    throw new Error(`${context}: Only ambient lights are allowed on a scene`);
                 }
                 }
 
 
                 this._loader._babylonScene.ambientColor = light.color ? Color3.FromArray(light.color) : Color3.Black();
                 this._loader._babylonScene.ambientColor = light.color ? Color3.FromArray(light.color) : Color3.Black();
@@ -59,7 +59,7 @@ module BABYLON.GLTF2.Extensions {
                 const name = node._babylonMesh!.name;
                 const name = node._babylonMesh!.name;
                 switch (light.type) {
                 switch (light.type) {
                     case LightType.AMBIENT: {
                     case LightType.AMBIENT: {
-                        throw new Error(context + ": Ambient lights are not allowed on a node");
+                        throw new Error(`${context}: Ambient lights are not allowed on a node`);
                     }
                     }
                     case LightType.DIRECTIONAL: {
                     case LightType.DIRECTIONAL: {
                         babylonLight = new DirectionalLight(name, Vector3.Forward(), this._loader._babylonScene);
                         babylonLight = new DirectionalLight(name, Vector3.Forward(), this._loader._babylonScene);
@@ -78,7 +78,7 @@ module BABYLON.GLTF2.Extensions {
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid light type " + light.type);
+                        throw new Error(`${context}: Invalid light type (${light.type})`);
                     }
                     }
                 }
                 }
 
 
@@ -93,7 +93,7 @@ module BABYLON.GLTF2.Extensions {
         private get _lights(): Array<ILight> {
         private get _lights(): Array<ILight> {
             const extensions = this._loader._gltf.extensions;
             const extensions = this._loader._gltf.extensions;
             if (!extensions || !extensions[this.name]) {
             if (!extensions || !extensions[this.name]) {
-                throw new Error("#/extensions: " + this.name + " not found");
+                throw new Error(`#/extensions: '${this.name}' not found`);
             }
             }
 
 
             const extension = extensions[this.name] as ILights;
             const extension = extensions[this.name] as ILights;

+ 16 - 19
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -16,33 +16,30 @@ module BABYLON.GLTF2.Extensions {
     export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
     export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
         public readonly name = NAME;
         public readonly name = NAME;
 
 
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
             return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension) => {
             return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension) => {
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes = material._babylonMeshes || [];
                 material._babylonMeshes.push(babylonMesh);
                 material._babylonMeshes.push(babylonMesh);
 
 
-                if (material._loaded) {
-                    babylonMesh.material = material._babylonMaterial!;
-                    return material._loaded;
-                }
-
-                const promises = new Array<Promise<void>>();
+                if (!material._loaded) {
+                    const promises = new Array<Promise<void>>();
 
 
-                const babylonMaterial = this._loader._createMaterial(material);
-                material._babylonMaterial = babylonMaterial;
+                    const babylonMaterial = this._loader._createMaterial(material);
+                    material._babylonMaterial = babylonMaterial;
 
 
-                promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material));
-                promises.push(this._loadSpecularGlossinessPropertiesAsync(this._loader, context, material, extension));
+                    promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material));
+                    promises.push(this._loadSpecularGlossinessPropertiesAsync(context, material, extension));
 
 
-                this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-
-                babylonMesh.material = babylonMaterial;
+                    this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                    material._loaded = Promise.all(promises).then(() => {});
+                }
 
 
-                return (material._loaded = Promise.all(promises).then(() => {}));
+                assign(material._babylonMaterial!);
+                return material._loaded;
             });
             });
         }
         }
 
 
-        private _loadSpecularGlossinessPropertiesAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): Promise<void> {
+        private _loadSpecularGlossinessPropertiesAsync(context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): Promise<void> {
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
             const babylonMaterial = material._babylonMaterial as PBRMaterial;
             const babylonMaterial = material._babylonMaterial as PBRMaterial;
@@ -59,13 +56,13 @@ module BABYLON.GLTF2.Extensions {
             babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
             babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
 
 
             if (properties.diffuseTexture) {
             if (properties.diffuseTexture) {
-                promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, texture => {
+                promises.push(this._loader._loadTextureAsync(`${context}/diffuseTexture`, properties.diffuseTexture, texture => {
                     babylonMaterial.albedoTexture = texture;
                     babylonMaterial.albedoTexture = texture;
                 }));
                 }));
             }
             }
 
 
             if (properties.specularGlossinessTexture) {
             if (properties.specularGlossinessTexture) {
-                promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, texture => {
+                promises.push(this._loader._loadTextureAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, texture => {
                     babylonMaterial.reflectivityTexture = texture;
                     babylonMaterial.reflectivityTexture = texture;
                 }));
                 }));
 
 
@@ -73,7 +70,7 @@ module BABYLON.GLTF2.Extensions {
                 babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
                 babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
             }
             }
 
 
-            loader._loadMaterialAlphaProperties(context, material);
+            this._loader._loadMaterialAlphaProperties(context, material);
 
 
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
         }
         }

+ 18 - 5
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -39,10 +39,13 @@ module BABYLON.GLTF2.Extensions {
                         }
                         }
                     }
                     }
 
 
-                    const promise = this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(() => {
+                    const promise = this._loader._loadNodeAsync(`#/nodes/${nodeLOD._index}`, nodeLOD).then(() => {
                         if (indexLOD !== 0) {
                         if (indexLOD !== 0) {
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
-                            previousNodeLOD._babylonMesh!.setEnabled(false);
+                            if (previousNodeLOD._babylonMesh) {
+                                previousNodeLOD._babylonMesh.dispose();
+                                delete previousNodeLOD._babylonMesh;
+                            }
                         }
                         }
 
 
                         if (indexLOD !== nodeLODs.length - 1) {
                         if (indexLOD !== nodeLODs.length - 1) {
@@ -68,7 +71,7 @@ module BABYLON.GLTF2.Extensions {
             });
             });
         }
         }
 
 
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
             // Don't load material LODs if already loading a node LOD.
             // Don't load material LODs if already loading a node LOD.
             if (this._loadingNodeLOD) {
             if (this._loadingNodeLOD) {
                 return null;
                 return null;
@@ -89,7 +92,17 @@ module BABYLON.GLTF2.Extensions {
                         }
                         }
                     }
                     }
 
 
-                    const promise = this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(() => {
+                    const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD._index}`, materialLOD, babylonMesh, indexLOD === 0 ? assign : () => {}).then(() => {
+                        if (indexLOD !== 0) {
+                            assign(materialLOD._babylonMaterial!);
+
+                            const previousMaterialLOD = materialLODs[indexLOD - 1];
+                            if (previousMaterialLOD._babylonMaterial) {
+                                previousMaterialLOD._babylonMaterial.dispose();
+                                delete previousMaterialLOD._babylonMaterial;
+                            }
+                        }
+
                         if (indexLOD !== materialLODs.length - 1) {
                         if (indexLOD !== materialLODs.length - 1) {
                             const materialIndex = materialLODs[indexLOD + 1]._index;
                             const materialIndex = materialLODs[indexLOD + 1]._index;
                             if (this._loadMaterialSignals[materialIndex]) {
                             if (this._loadMaterialSignals[materialIndex]) {
@@ -141,7 +154,7 @@ module BABYLON.GLTF2.Extensions {
             const properties = new Array<T>();
             const properties = new Array<T>();
 
 
             for (let i = ids.length - 1; i >= 0; i--) {
             for (let i = ids.length - 1; i >= 0; i--) {
-                properties.push(GLTFLoader._GetProperty(context + "/ids/" + ids[i], array, ids[i]));
+                properties.push(GLTFLoader._GetProperty(`${context}/ids/${ids[i]}`, array, ids[i]));
                 if (properties.length === this.maxLODsToLoad) {
                 if (properties.length === this.maxLODsToLoad) {
                     return properties;
                     return properties;
                 }
                 }

+ 167 - 149
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -34,7 +34,7 @@ module BABYLON.GLTF2 {
         private static _Factories: { [name: string]: (loader: GLTFLoader) => GLTFLoaderExtension } = {};
         private static _Factories: { [name: string]: (loader: GLTFLoader) => GLTFLoaderExtension } = {};
         public static _Register(name: string, factory: (loader: GLTFLoader) => GLTFLoaderExtension): void {
         public static _Register(name: string, factory: (loader: GLTFLoader) => GLTFLoaderExtension): void {
             if (GLTFLoader._Factories[name]) {
             if (GLTFLoader._Factories[name]) {
-                Tools.Error("Extension with the name '" + name + "' already exists");
+                Tools.Error(`Extension with the name '${name}' already exists`);
                 return;
                 return;
             }
             }
 
 
@@ -54,6 +54,7 @@ module BABYLON.GLTF2 {
         public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
         public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
         public readonly onMaterialLoadedObservable = new Observable<Material>();
         public readonly onMaterialLoadedObservable = new Observable<Material>();
+        public readonly onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
         public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
         public readonly onExtensionLoadedObservable = new Observable<IGLTFLoaderExtension>();
         public readonly onCompleteObservable = new Observable<IGLTFLoader>();
         public readonly onCompleteObservable = new Observable<IGLTFLoader>();
 
 
@@ -92,7 +93,7 @@ module BABYLON.GLTF2 {
                     nodes = names.map(name => {
                     nodes = names.map(name => {
                         const node = nodeMap[name];
                         const node = nodeMap[name];
                         if (!node) {
                         if (!node) {
-                            throw new Error("Failed to find node " + name);
+                            throw new Error(`Failed to find node '${name}'`);
                         }
                         }
 
 
                         return node;
                         return node;
@@ -131,8 +132,8 @@ module BABYLON.GLTF2 {
                     promises.push(this._loadNodesAsync(nodes));
                     promises.push(this._loadNodesAsync(nodes));
                 }
                 }
                 else {
                 else {
-                    const scene = GLTFLoader._GetProperty("#/scene", this._gltf.scenes, this._gltf.scene || 0);
-                    promises.push(this._loadSceneAsync("#/scenes/" + scene._index, scene));
+                    const scene = GLTFLoader._GetProperty(`#/scene`, this._gltf.scenes, this._gltf.scene || 0);
+                    promises.push(this._loadSceneAsync(`#/scenes/${scene._index}`, scene));
                 }
                 }
 
 
                 if (this.compileMaterials) {
                 if (this.compileMaterials) {
@@ -143,9 +144,13 @@ module BABYLON.GLTF2 {
                     promises.push(this._compileShadowGeneratorsAsync());
                     promises.push(this._compileShadowGeneratorsAsync());
                 }
                 }
 
 
-                return Promise.all(promises).then(() => {
+                const resultPromise = Promise.all(promises).then(() => {
                     this._state = GLTFLoaderState.Ready;
                     this._state = GLTFLoaderState.Ready;
                     this._startAnimations();
                     this._startAnimations();
+                });
+
+                resultPromise.then(() => {
+                    this._rootBabylonMesh.setEnabled(true);
 
 
                     Tools.SetImmediate(() => {
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
                         if (!this._disposed) {
@@ -155,14 +160,16 @@ module BABYLON.GLTF2 {
                                 this.onCompleteObservable.clear();
                                 this.onCompleteObservable.clear();
                                 this._clear();
                                 this._clear();
                             }).catch(error => {
                             }).catch(error => {
-                                Tools.Error("glTF Loader: " + error.message);
+                                Tools.Error(`glTF Loader: ${error.message}`);
                                 this._clear();
                                 this._clear();
                             });
                             });
                         }
                         }
                     });
                     });
                 });
                 });
+
+                return resultPromise;
             }).catch(error => {
             }).catch(error => {
-                Tools.Error("glTF Loader: " + error.message);
+                Tools.Error(`glTF Loader: ${error.message}`);
                 this._clear();
                 this._clear();
                 throw error;
                 throw error;
             });
             });
@@ -188,7 +195,7 @@ module BABYLON.GLTF2 {
                 if (buffers && buffers[0] && !buffers[0].uri) {
                 if (buffers && buffers[0] && !buffers[0].uri) {
                     const binaryBuffer = buffers[0];
                     const binaryBuffer = buffers[0];
                     if (binaryBuffer.byteLength < data.bin.byteLength - 3 || binaryBuffer.byteLength > data.bin.byteLength) {
                     if (binaryBuffer.byteLength < data.bin.byteLength - 3 || binaryBuffer.byteLength > data.bin.byteLength) {
-                        Tools.Warn("Binary buffer length (" + binaryBuffer.byteLength + ") from JSON does not match chunk length (" + data.bin.byteLength + ")");
+                        Tools.Warn(`Binary buffer length (${binaryBuffer.byteLength}) from JSON does not match chunk length (${data.bin.byteLength})`);
                     }
                     }
 
 
                     binaryBuffer._data = Promise.resolve(data.bin);
                     binaryBuffer._data = Promise.resolve(data.bin);
@@ -245,6 +252,8 @@ module BABYLON.GLTF2 {
 
 
         private _createRootNode(): ILoaderNode {
         private _createRootNode(): ILoaderNode {
             this._rootBabylonMesh = new Mesh("__root__", this._babylonScene);
             this._rootBabylonMesh = new Mesh("__root__", this._babylonScene);
+            this._rootBabylonMesh.setEnabled(false);
+
             const rootNode = { _babylonMesh: this._rootBabylonMesh } as ILoaderNode;
             const rootNode = { _babylonMesh: this._rootBabylonMesh } as ILoaderNode;
             switch (this.coordinateSystemMode) {
             switch (this.coordinateSystemMode) {
                 case GLTFLoaderCoordinateSystemMode.AUTO: {
                 case GLTFLoaderCoordinateSystemMode.AUTO: {
@@ -260,7 +269,7 @@ module BABYLON.GLTF2 {
                     break;
                     break;
                 }
                 }
                 default: {
                 default: {
-                    throw new Error("Invalid coordinate system mode " + this.coordinateSystemMode);
+                    throw new Error(`Invalid coordinate system mode (${this.coordinateSystemMode})`);
                 }
                 }
             }
             }
 
 
@@ -272,7 +281,7 @@ module BABYLON.GLTF2 {
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
             for (let node of nodes) {
             for (let node of nodes) {
-                promises.push(this._loadNodeAsync("#/nodes/" + node._index, node));
+                promises.push(this._loadNodeAsync(`#/nodes/${node._index}`, node));
             }
             }
 
 
             promises.push(this._loadAnimationsAsync());
             promises.push(this._loadAnimationsAsync());
@@ -289,8 +298,8 @@ module BABYLON.GLTF2 {
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
             for (let index of scene.nodes) {
             for (let index of scene.nodes) {
-                const node = GLTFLoader._GetProperty(context + "/nodes/" + index, this._gltf.nodes, index);
-                promises.push(this._loadNodeAsync("#/nodes/" + node._index, node));
+                const node = GLTFLoader._GetProperty(`${context}/nodes/${index}`, this._gltf.nodes, index);
+                promises.push(this._loadNodeAsync(`#/nodes/${node._index}`, node));
             }
             }
 
 
             promises.push(this._loadAnimationsAsync());
             promises.push(this._loadAnimationsAsync());
@@ -298,8 +307,20 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
         }
         }
 
 
-        private _getMeshes(): AbstractMesh[] {
-            const meshes = new Array<AbstractMesh>();
+        private _forEachNodeMesh(node: ILoaderNode, callback: (babylonMesh: Mesh) => void): void {
+            if (node._babylonMesh) {
+                callback(node._babylonMesh);
+            }
+
+            if (node._primitiveBabylonMeshes) {
+                for (const babylonMesh of node._primitiveBabylonMeshes) {
+                    callback(babylonMesh);
+                }
+            }
+        }
+
+        private _getMeshes(): Mesh[] {
+            const meshes = new Array<Mesh>();
 
 
             // Root mesh is always first.
             // Root mesh is always first.
             meshes.push(this._rootBabylonMesh);
             meshes.push(this._rootBabylonMesh);
@@ -307,15 +328,9 @@ module BABYLON.GLTF2 {
             const nodes = this._gltf.nodes;
             const nodes = this._gltf.nodes;
             if (nodes) {
             if (nodes) {
                 for (const node of nodes) {
                 for (const node of nodes) {
-                    if (node._babylonMesh) {
-                        meshes.push(node._babylonMesh);
-                    }
-
-                    if (node._primitiveBabylonMeshes) {
-                        for (const babylonMesh of node._primitiveBabylonMeshes) {
-                            meshes.push(babylonMesh);
-                        }
-                    }
+                    this._forEachNodeMesh(node, mesh => {
+                        meshes.push(mesh);
+                    });
                 }
                 }
             }
             }
 
 
@@ -360,7 +375,7 @@ module BABYLON.GLTF2 {
                     break;
                     break;
                 }
                 }
                 default: {
                 default: {
-                    Tools.Error("Invalid animation start mode " + this.animationStartMode);
+                    Tools.Error(`Invalid animation start mode (${this.animationStartMode})`);
                     return;
                     return;
                 }
                 }
             }
             }
@@ -373,12 +388,12 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (node._babylonMesh) {
             if (node._babylonMesh) {
-                throw new Error(context + ": Invalid recursive node hierarchy");
+                throw new Error(`${context}: Invalid recursive node hierarchy`);
             }
             }
 
 
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
-            const babylonMesh = new Mesh(node.name || "node" + node._index, this._babylonScene, node._parent._babylonMesh);
+            const babylonMesh = new Mesh(node.name || `node${node._index}`, this._babylonScene, node._parent._babylonMesh);
             node._babylonMesh = babylonMesh;
             node._babylonMesh = babylonMesh;
 
 
             node._babylonAnimationTargets = node._babylonAnimationTargets || [];
             node._babylonAnimationTargets = node._babylonAnimationTargets || [];
@@ -387,14 +402,14 @@ module BABYLON.GLTF2 {
             GLTFLoader._LoadTransform(node, babylonMesh);
             GLTFLoader._LoadTransform(node, babylonMesh);
 
 
             if (node.mesh != undefined) {
             if (node.mesh != undefined) {
-                const mesh = GLTFLoader._GetProperty(context + "/mesh", this._gltf.meshes, node.mesh);
-                promises.push(this._loadMeshAsync("#/meshes/" + mesh._index, node, mesh));
+                const mesh = GLTFLoader._GetProperty(`${context}/mesh`, this._gltf.meshes, node.mesh);
+                promises.push(this._loadMeshAsync(`#/meshes/${mesh._index}`, node, mesh, babylonMesh));
             }
             }
 
 
             if (node.children) {
             if (node.children) {
                 for (const index of node.children) {
                 for (const index of node.children) {
-                    const childNode = GLTFLoader._GetProperty(context + "/children/" + index, this._gltf.nodes, index);
-                    promises.push(this._loadNodeAsync("#/nodes/" + index, childNode));
+                    const childNode = GLTFLoader._GetProperty(`${context}/children/${index}`, this._gltf.nodes, index);
+                    promises.push(this._loadNodeAsync(`#/nodes/${index}`, childNode));
                 }
                 }
             }
             }
 
 
@@ -403,44 +418,46 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
         }
         }
 
 
-        private _loadMeshAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh): Promise<void> {
+        private _loadMeshAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, babylonMesh: Mesh): Promise<void> {
             // TODO: instancing
             // TODO: instancing
 
 
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
             const primitives = mesh.primitives;
             const primitives = mesh.primitives;
             if (!primitives || primitives.length === 0) {
             if (!primitives || primitives.length === 0) {
-                throw new Error(context + ": Primitives are missing");
+                throw new Error(`${context}: Primitives are missing`);
             }
             }
 
 
             ArrayItem.Assign(primitives);
             ArrayItem.Assign(primitives);
-            for (const primitive of primitives) {
-                promises.push(this._loadPrimitiveAsync(context + "/primitives/" + primitive._index, node, mesh, primitive));
+            if (primitives.length === 1) {
+                const primitive = primitives[0];
+                promises.push(this._loadPrimitiveAsync(`${context}/primitives/${primitive._index}`, node, mesh, primitive, babylonMesh));
+            }
+            else {
+                node._primitiveBabylonMeshes = [];
+                for (const primitive of primitives) {
+                    const primitiveBabylonMesh = new Mesh(`${mesh.name || babylonMesh.name}_${primitive._index}`, this._babylonScene, babylonMesh);
+                    node._primitiveBabylonMeshes.push(babylonMesh);
+                    promises.push(this._loadPrimitiveAsync(`${context}/primitives/${primitive._index}`, node, mesh, primitive, primitiveBabylonMesh));
+                    this.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                }
             }
             }
 
 
             if (node.skin != undefined) {
             if (node.skin != undefined) {
-                const skin = GLTFLoader._GetProperty(context + "/skin", this._gltf.skins, node.skin);
-                promises.push(this._loadSkinAsync("#/skins/" + skin._index, node, mesh, skin));
+                const skin = GLTFLoader._GetProperty(`${context}/skin`, this._gltf.skins, node.skin);
+                promises.push(this._loadSkinAsync(`#/skins/${skin._index}`, node, mesh, skin));
             }
             }
 
 
             return Promise.all(promises).then(() => {
             return Promise.all(promises).then(() => {
-                if (node._primitiveBabylonMeshes) {
-                    for (const primitiveBabylonMesh of node._primitiveBabylonMeshes) {
-                        primitiveBabylonMesh._refreshBoundingInfo(true);
-                    }
-                }
+                this._forEachNodeMesh(node, babylonMesh => {
+                    babylonMesh._refreshBoundingInfo(true);
+                });
             });
             });
         }
         }
 
 
-        private _loadPrimitiveAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, primitive: ILoaderMeshPrimitive): Promise<void> {
+        private _loadPrimitiveAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<void> {
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
-            const babylonMesh = new Mesh((mesh.name || node._babylonMesh!.name) + "_" + primitive._index, this._babylonScene, node._babylonMesh);
-            babylonMesh.setEnabled(false);
-
-            node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
-            node._primitiveBabylonMeshes[primitive._index] = babylonMesh;
-
             this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
             this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
 
 
             promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(babylonVertexData => {
             promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(babylonVertexData => {
@@ -452,15 +469,13 @@ module BABYLON.GLTF2 {
                 babylonMesh.material = this._getDefaultMaterial();
                 babylonMesh.material = this._getDefaultMaterial();
             }
             }
             else {
             else {
-                const material = GLTFLoader._GetProperty(context + "/material", this._gltf.materials, primitive.material);
-                promises.push(this._loadMaterialAsync("#/materials/" + material._index, material, babylonMesh));
+                const material = GLTFLoader._GetProperty(`${context}/material}`, this._gltf.materials, primitive.material);
+                promises.push(this._loadMaterialAsync(`#/materials/${material._index}`, material, babylonMesh, babylonMaterial => {
+                    babylonMesh.material = babylonMaterial;
+                }));
             }
             }
 
 
-            this.onMeshLoadedObservable.notifyObservers(babylonMesh);
-
-            return Promise.all(promises).then(() => {
-                babylonMesh.setEnabled(true);
-            });
+            return Promise.all(promises).then(() => {});
         }
         }
 
 
         private _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<VertexData> {
         private _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<VertexData> {
@@ -471,12 +486,12 @@ module BABYLON.GLTF2 {
 
 
             const attributes = primitive.attributes;
             const attributes = primitive.attributes;
             if (!attributes) {
             if (!attributes) {
-                throw new Error(context + ": Attributes are missing");
+                throw new Error(`${context}: Attributes are missing`);
             }
             }
 
 
             if (primitive.mode != undefined && primitive.mode !== MeshPrimitiveMode.TRIANGLES) {
             if (primitive.mode != undefined && primitive.mode !== MeshPrimitiveMode.TRIANGLES) {
                 // TODO: handle other primitive modes
                 // TODO: handle other primitive modes
-                throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
+                throw new Error(`${context}: Mode (${primitive.mode}) is not currently supported`);
             }
             }
 
 
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
@@ -486,7 +501,7 @@ module BABYLON.GLTF2 {
             if (primitive.indices == undefined) {
             if (primitive.indices == undefined) {
                 const positionAccessorIndex = attributes["POSITION"];
                 const positionAccessorIndex = attributes["POSITION"];
                 if (positionAccessorIndex != undefined) {
                 if (positionAccessorIndex != undefined) {
-                    const accessor = GLTFLoader._GetProperty(context + "/attributes/POSITION", this._gltf.accessors, positionAccessorIndex);
+                    const accessor = GLTFLoader._GetProperty(`${context}/attributes/POSITION`, this._gltf.accessors, positionAccessorIndex);
                     babylonVertexData.indices = new Uint32Array(accessor.count);
                     babylonVertexData.indices = new Uint32Array(accessor.count);
                     for (let i = 0; i < babylonVertexData.indices.length; i++) {
                     for (let i = 0; i < babylonVertexData.indices.length; i++) {
                         babylonVertexData.indices[i] = i;
                         babylonVertexData.indices[i] = i;
@@ -494,8 +509,8 @@ module BABYLON.GLTF2 {
                 }
                 }
             }
             }
             else {
             else {
-                const indicesAccessor = GLTFLoader._GetProperty(context + "/indices", this._gltf.accessors, primitive.indices);
-                promises.push(this._loadAccessorAsync("#/accessors/" + indicesAccessor._index, indicesAccessor).then(data => {
+                const indicesAccessor = GLTFLoader._GetProperty(`${context}/indices`, this._gltf.accessors, primitive.indices);
+                promises.push(this._loadAccessorAsync(`#/accessors/${indicesAccessor._index}`, indicesAccessor).then(data => {
                     babylonVertexData.indices = data as IndicesArray;
                     babylonVertexData.indices = data as IndicesArray;
                 }));
                 }));
             }
             }
@@ -510,17 +525,18 @@ module BABYLON.GLTF2 {
                     babylonMesh._delayInfo.push(kind);
                     babylonMesh._delayInfo.push(kind);
                 }
                 }
 
 
-                const accessor = GLTFLoader._GetProperty(context + "/attributes/" + attribute, this._gltf.accessors, attributes[attribute]);
+                if (attribute === "COLOR_0") {
+                    // Assume vertex color has alpha on the mesh. The alphaMode of the material controls whether the material should use alpha or not.
+                    babylonMesh.hasVertexAlpha = true;
+                }
+
+                const accessor = GLTFLoader._GetProperty(`${context}/attributes/${attribute}`, this._gltf.accessors, attributes[attribute]);
 
 
-                promises.push(this._loadAccessorAsync("#/accessors/" + accessor._index, accessor).then(data => {
+                promises.push(this._loadAccessorAsync(`#/accessors/${accessor._index}`, accessor).then(data => {
                     let attributeData = GLTFLoader._ConvertToFloat32Array(context, accessor, data);
                     let attributeData = GLTFLoader._ConvertToFloat32Array(context, accessor, data);
 
 
-                    if (attribute === "COLOR_0") {
-                        // Assume vertex color has alpha on the mesh. The alphaMode of the material controls whether the material should use alpha or not.
-                        babylonMesh.hasVertexAlpha = true;
-                        if (accessor.type === "VEC3") {
-                            attributeData = GLTFLoader._ConvertVec3ToVec4(context, attributeData);
-                        }
+                    if (attribute === "COLOR_0" && accessor.type === "VEC3") {
+                        attributeData = GLTFLoader._ConvertVec3ToVec4(context, attributeData);
                     }
                     }
 
 
                     babylonVertexData.set(attributeData, kind);
                     babylonVertexData.set(attributeData, kind);
@@ -550,13 +566,13 @@ module BABYLON.GLTF2 {
                 node._numMorphTargets = primitive.targets.length;
                 node._numMorphTargets = primitive.targets.length;
             }
             }
             else if (primitive.targets.length !== node._numMorphTargets) {
             else if (primitive.targets.length !== node._numMorphTargets) {
-                throw new Error(context + ": Primitives do not have the same number of targets");
+                throw new Error(`${context}: Primitives do not have the same number of targets`);
             }
             }
 
 
             babylonMesh.morphTargetManager = new MorphTargetManager();
             babylonMesh.morphTargetManager = new MorphTargetManager();
             for (let index = 0; index < primitive.targets.length; index++) {
             for (let index = 0; index < primitive.targets.length; index++) {
                 const weight = node.weights ? node.weights[index] : mesh.weights ? mesh.weights[index] : 0;
                 const weight = node.weights ? node.weights[index] : mesh.weights ? mesh.weights[index] : 0;
-                babylonMesh.morphTargetManager.addTarget(new MorphTarget("morphTarget" + index, weight));
+                babylonMesh.morphTargetManager.addTarget(new MorphTarget(`morphTarget${index}`, weight));
                 // TODO: tell the target whether it has positions, normals, tangents
                 // TODO: tell the target whether it has positions, normals, tangents
             }
             }
         }
         }
@@ -571,7 +587,7 @@ module BABYLON.GLTF2 {
             const morphTargetManager = babylonMesh.morphTargetManager!;
             const morphTargetManager = babylonMesh.morphTargetManager!;
             for (let index = 0; index < morphTargetManager.numTargets; index++) {
             for (let index = 0; index < morphTargetManager.numTargets; index++) {
                 const babylonMorphTarget = morphTargetManager.getTarget(index);
                 const babylonMorphTarget = morphTargetManager.getTarget(index);
-                promises.push(this._loadMorphTargetVertexDataAsync(context + "/targets/" + index, babylonVertexData, primitive.targets[index], babylonMorphTarget));
+                promises.push(this._loadMorphTargetVertexDataAsync(`${context}/targets/${index}`, babylonVertexData, primitive.targets[index], babylonMorphTarget));
             }
             }
 
 
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
@@ -585,8 +601,8 @@ module BABYLON.GLTF2 {
                     return;
                     return;
                 }
                 }
 
 
-                const accessor = GLTFLoader._GetProperty(context + "/" + attribute, this._gltf.accessors, attributes[attribute]);
-                promises.push(this._loadAccessorAsync("#/accessors/" + accessor._index, accessor).then(data => {
+                const accessor = GLTFLoader._GetProperty(`${context}/${attribute}`, this._gltf.accessors, attributes[attribute]);
+                promises.push(this._loadAccessorAsync(`#/accessors/${accessor._index}`, accessor).then(data => {
                     setData(data as Float32Array);
                     setData(data as Float32Array);
                 }));
                 }));
             };
             };
@@ -644,7 +660,7 @@ module BABYLON.GLTF2 {
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid component type " + accessor.componentType);
+                        throw new Error(`${context}: Invalid component type (${accessor.componentType})`);
                     }
                     }
                 }
                 }
             }
             }
@@ -695,9 +711,9 @@ module BABYLON.GLTF2 {
 
 
         private _loadSkinAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, skin: ILoaderSkin): Promise<void> {
         private _loadSkinAsync(context: string, node: ILoaderNode, mesh: ILoaderMesh, skin: ILoaderSkin): Promise<void> {
             const assignSkeleton = () => {
             const assignSkeleton = () => {
-                for (const babylonMesh of node._primitiveBabylonMeshes!) {
+                this._forEachNodeMesh(node, babylonMesh => {
                     babylonMesh.skeleton = skin._babylonSkeleton;
                     babylonMesh.skeleton = skin._babylonSkeleton;
-                }
+                });
 
 
                 node._babylonMesh!.parent = this._rootBabylonMesh;
                 node._babylonMesh!.parent = this._rootBabylonMesh;
                 node._babylonMesh!.position = Vector3.Zero();
                 node._babylonMesh!.position = Vector3.Zero();
@@ -714,7 +730,7 @@ module BABYLON.GLTF2 {
             // TODO: split into two parts so that bones are created before inverseBindMatricesData is loaded (for compiling materials).
             // TODO: split into two parts so that bones are created before inverseBindMatricesData is loaded (for compiling materials).
 
 
             return (skin._loaded = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(inverseBindMatricesData => {
             return (skin._loaded = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(inverseBindMatricesData => {
-                const skeletonId = "skeleton" + skin._index;
+                const skeletonId = `skeleton${skin._index}`;
                 const babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
                 const babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
                 skin._babylonSkeleton = babylonSkeleton;
                 skin._babylonSkeleton = babylonSkeleton;
                 this._loadBones(context, skin, inverseBindMatricesData);
                 this._loadBones(context, skin, inverseBindMatricesData);
@@ -727,14 +743,14 @@ module BABYLON.GLTF2 {
                 return Promise.resolve(null);
                 return Promise.resolve(null);
             }
             }
 
 
-            const accessor = GLTFLoader._GetProperty(context + "/inverseBindMatrices", this._gltf.accessors, skin.inverseBindMatrices);
-            return this._loadAccessorAsync("#/accessors/" + accessor._index, accessor).then(data => {
+            const accessor = GLTFLoader._GetProperty(`${context}/inverseBindMatrices`, this._gltf.accessors, skin.inverseBindMatrices);
+            return this._loadAccessorAsync(`#/accessors/${accessor._index}`, accessor).then(data => {
                 return data as Float32Array;
                 return data as Float32Array;
             });
             });
         }
         }
 
 
         private _createBone(node: ILoaderNode, skin: ILoaderSkin, parent: Nullable<Bone>, localMatrix: Matrix, baseMatrix: Matrix, index: number): Bone {
         private _createBone(node: ILoaderNode, skin: ILoaderSkin, parent: Nullable<Bone>, localMatrix: Matrix, baseMatrix: Matrix, index: number): Bone {
-            const babylonBone = new Bone(node.name || "joint" + node._index, skin._babylonSkeleton!, parent, localMatrix, null, baseMatrix, index);
+            const babylonBone = new Bone(node.name || `joint${node._index}`, skin._babylonSkeleton!, parent, localMatrix, null, baseMatrix, index);
 
 
             node._babylonAnimationTargets = node._babylonAnimationTargets || [];
             node._babylonAnimationTargets = node._babylonAnimationTargets || [];
             node._babylonAnimationTargets.push(babylonBone);
             node._babylonAnimationTargets.push(babylonBone);
@@ -745,7 +761,7 @@ module BABYLON.GLTF2 {
         private _loadBones(context: string, skin: ILoaderSkin, inverseBindMatricesData: Nullable<Float32Array>): void {
         private _loadBones(context: string, skin: ILoaderSkin, inverseBindMatricesData: Nullable<Float32Array>): void {
             const babylonBones: { [index: number]: Bone } = {};
             const babylonBones: { [index: number]: Bone } = {};
             for (const index of skin.joints) {
             for (const index of skin.joints) {
-                const node = GLTFLoader._GetProperty(context + "/joints/" + index, this._gltf.nodes, index);
+                const node = GLTFLoader._GetProperty(`${context}/joints/${index}`, this._gltf.nodes, index);
                 this._loadBone(node, skin, inverseBindMatricesData, babylonBones);
                 this._loadBone(node, skin, inverseBindMatricesData, babylonBones);
             }
             }
         }
         }
@@ -794,14 +810,14 @@ module BABYLON.GLTF2 {
 
 
             for (let index = 0; index < animations.length; index++) {
             for (let index = 0; index < animations.length; index++) {
                 const animation = animations[index];
                 const animation = animations[index];
-                promises.push(this._loadAnimationAsync("#/animations/" + index, animation));
+                promises.push(this._loadAnimationAsync(`#/animations/${index}`, animation));
             }
             }
 
 
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
         }
         }
 
 
         private _loadAnimationAsync(context: string, animation: ILoaderAnimation): Promise<void> {
         private _loadAnimationAsync(context: string, animation: ILoaderAnimation): Promise<void> {
-            const babylonAnimationGroup = new AnimationGroup(animation.name || "animation" + animation._index, this._babylonScene);
+            const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation._index}`, this._babylonScene);
             animation._babylonAnimationGroup = babylonAnimationGroup;
             animation._babylonAnimationGroup = babylonAnimationGroup;
 
 
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
@@ -810,22 +826,24 @@ module BABYLON.GLTF2 {
             ArrayItem.Assign(animation.samplers);
             ArrayItem.Assign(animation.samplers);
 
 
             for (const channel of animation.channels) {
             for (const channel of animation.channels) {
-                promises.push(this._loadAnimationChannelAsync(context + "/channels/" + channel._index, context, animation, channel, babylonAnimationGroup));
+                promises.push(this._loadAnimationChannelAsync(`${context}/channels/${channel._index}`, context, animation, channel, babylonAnimationGroup));
             }
             }
 
 
+            this.onAnimationGroupLoadedObservable.notifyObservers(babylonAnimationGroup);
+
             return Promise.all(promises).then(() => {
             return Promise.all(promises).then(() => {
                 babylonAnimationGroup.normalize();
                 babylonAnimationGroup.normalize();
             });
             });
         }
         }
 
 
         private _loadAnimationChannelAsync(context: string, animationContext: string, animation: ILoaderAnimation, channel: ILoaderAnimationChannel, babylonAnimationGroup: AnimationGroup): Promise<void> {
         private _loadAnimationChannelAsync(context: string, animationContext: string, animation: ILoaderAnimation, channel: ILoaderAnimationChannel, babylonAnimationGroup: AnimationGroup): Promise<void> {
-            const targetNode = GLTFLoader._GetProperty(context + "/target/node", this._gltf.nodes, channel.target.node);
-            if (!targetNode._babylonMesh) {
+            const targetNode = GLTFLoader._GetProperty(`${context}/target/node`, this._gltf.nodes, channel.target.node);
+            if (!targetNode._babylonMesh || targetNode.skin != undefined) {
                 return Promise.resolve();
                 return Promise.resolve();
             }
             }
 
 
-            const sampler = GLTFLoader._GetProperty(context + "/sampler", animation.samplers, channel.sampler);
-            return this._loadAnimationSamplerAsync(animationContext + "/samplers/" + channel.sampler, sampler).then(data => {
+            const sampler = GLTFLoader._GetProperty(`${context}/sampler`, animation.samplers, channel.sampler);
+            return this._loadAnimationSamplerAsync(`${animationContext}/samplers/${channel.sampler}`, sampler).then(data => {
                 let targetPath: string;
                 let targetPath: string;
                 let animationType: number;
                 let animationType: number;
                 switch (channel.target.path) {
                 switch (channel.target.path) {
@@ -850,7 +868,7 @@ module BABYLON.GLTF2 {
                         break;
                         break;
                     }
                     }
                     default: {
                     default: {
-                        throw new Error(context + ": Invalid target path " + channel.target.path);
+                        throw new Error(`${context}: Invalid target path (${channel.target.path})`);
                     }
                     }
                 }
                 }
 
 
@@ -938,7 +956,7 @@ module BABYLON.GLTF2 {
 
 
                 if (targetPath === "influence") {
                 if (targetPath === "influence") {
                     for (let targetIndex = 0; targetIndex < targetNode._numMorphTargets!; targetIndex++) {
                     for (let targetIndex = 0; targetIndex < targetNode._numMorphTargets!; targetIndex++) {
-                        const animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
+                        const animationName = `${babylonAnimationGroup.name}_channel${babylonAnimationGroup.targetedAnimations.length}`;
                         const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                         const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                         babylonAnimation.setKeys(keys.map(key => ({
                         babylonAnimation.setKeys(keys.map(key => ({
                             frame: key.frame,
                             frame: key.frame,
@@ -947,14 +965,14 @@ module BABYLON.GLTF2 {
                             outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                             outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                         })));
                         })));
 
 
-                        for (const babylonMesh of targetNode._primitiveBabylonMeshes!) {
+                        this._forEachNodeMesh(targetNode, babylonMesh => {
                             const morphTarget = babylonMesh.morphTargetManager!.getTarget(targetIndex);
                             const morphTarget = babylonMesh.morphTargetManager!.getTarget(targetIndex);
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, morphTarget);
-                        }
+                        });
                     }
                     }
                 }
                 }
                 else {
                 else {
-                    const animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
+                    const animationName = `${babylonAnimationGroup.name}_channel${babylonAnimationGroup.targetedAnimations.length}`;
                     const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                     const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                     babylonAnimation.setKeys(keys);
                     babylonAnimation.setKeys(keys);
 
 
@@ -980,21 +998,21 @@ module BABYLON.GLTF2 {
                     break;
                     break;
                 }
                 }
                 default: {
                 default: {
-                    throw new Error(context + ": Invalid interpolation " + sampler.interpolation);
+                    throw new Error(`${context}: Invalid interpolation (${sampler.interpolation})`);
                 }
                 }
             }
             }
 
 
             let inputData: Nullable<Float32Array>;
             let inputData: Nullable<Float32Array>;
             let outputData: Nullable<Float32Array>;
             let outputData: Nullable<Float32Array>;
 
 
-            const inputAccessor = GLTFLoader._GetProperty(context + "/input", this._gltf.accessors, sampler.input);
-            const outputAccessor = GLTFLoader._GetProperty(context + "/output", this._gltf.accessors, sampler.output);
+            const inputAccessor = GLTFLoader._GetProperty(`${context}/input`, this._gltf.accessors, sampler.input);
+            const outputAccessor = GLTFLoader._GetProperty(`${context}/output`, this._gltf.accessors, sampler.output);
 
 
             sampler._data = Promise.all([
             sampler._data = Promise.all([
-                this._loadAccessorAsync("#/accessors/" + inputAccessor._index, inputAccessor).then(data => {
+                this._loadAccessorAsync(`#/accessors/${inputAccessor._index}`, inputAccessor).then(data => {
                     inputData = data as Float32Array;
                     inputData = data as Float32Array;
                 }),
                 }),
-                this._loadAccessorAsync("#/accessors/" + outputAccessor._index, outputAccessor).then(data => {
+                this._loadAccessorAsync(`#/accessors/${outputAccessor._index}`, outputAccessor).then(data => {
                     outputData = data as Float32Array;
                     outputData = data as Float32Array;
                 })
                 })
             ]).then(() => {
             ]).then(() => {
@@ -1014,7 +1032,7 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (!buffer.uri) {
             if (!buffer.uri) {
-                throw new Error(context + ": Uri is missing");
+                throw new Error(`${context}: Uri is missing`);
             }
             }
 
 
             buffer._data = this._loadUriAsync(context, buffer.uri);
             buffer._data = this._loadUriAsync(context, buffer.uri);
@@ -1027,13 +1045,13 @@ module BABYLON.GLTF2 {
                 return bufferView._data;
                 return bufferView._data;
             }
             }
 
 
-            const buffer = GLTFLoader._GetProperty(context + "/buffer", this._gltf.buffers, bufferView.buffer);
-            bufferView._data = this._loadBufferAsync("#/buffers/" + buffer._index, buffer).then(bufferData => {
+            const buffer = GLTFLoader._GetProperty(`${context}/buffer`, this._gltf.buffers, bufferView.buffer);
+            bufferView._data = this._loadBufferAsync(`#/buffers/${buffer._index}`, buffer).then(bufferData => {
                 try {
                 try {
                     return new Uint8Array(bufferData.buffer, bufferData.byteOffset + (bufferView.byteOffset || 0), bufferView.byteLength);
                     return new Uint8Array(bufferData.buffer, bufferData.byteOffset + (bufferView.byteOffset || 0), bufferView.byteLength);
                 }
                 }
                 catch (e) {
                 catch (e) {
-                    throw new Error(context + ": " + e.message);
+                    throw new Error(`${context}: ${e.message}`);
                 }
                 }
             });
             });
 
 
@@ -1042,21 +1060,21 @@ module BABYLON.GLTF2 {
 
 
         private _loadAccessorAsync(context: string, accessor: ILoaderAccessor): Promise<TypedArray> {
         private _loadAccessorAsync(context: string, accessor: ILoaderAccessor): Promise<TypedArray> {
             if (accessor.sparse) {
             if (accessor.sparse) {
-                throw new Error(context + ": Sparse accessors are not currently supported");
+                throw new Error(`${context}: Sparse accessors are not currently supported`);
             }
             }
 
 
             if (accessor._data) {
             if (accessor._data) {
                 return accessor._data;
                 return accessor._data;
             }
             }
 
 
-            const bufferView = GLTFLoader._GetProperty(context + "/bufferView", this._gltf.bufferViews, accessor.bufferView);
-            accessor._data = this._loadBufferViewAsync("#/bufferViews/" + bufferView._index, bufferView).then(bufferViewData => {
+            const bufferView = GLTFLoader._GetProperty(`${context}/bufferView`, this._gltf.bufferViews, accessor.bufferView);
+            accessor._data = this._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView).then(bufferViewData => {
                 const numComponents = GLTFLoader._GetNumComponents(context, accessor.type);
                 const numComponents = GLTFLoader._GetNumComponents(context, accessor.type);
                 const byteOffset = accessor.byteOffset || 0;
                 const byteOffset = accessor.byteOffset || 0;
                 const byteStride = bufferView.byteStride;
                 const byteStride = bufferView.byteStride;
 
 
                 if (byteStride === 0) {
                 if (byteStride === 0) {
-                    Tools.Warn(context + ": Byte stride of 0 is not valid");
+                    Tools.Warn(`${context}: Byte stride of 0 is not valid`);
                 }
                 }
 
 
                 try {
                 try {
@@ -1080,12 +1098,12 @@ module BABYLON.GLTF2 {
                             return this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                             return this._buildArrayBuffer(Float32Array, bufferViewData, byteOffset, accessor.count, numComponents, byteStride);
                         }
                         }
                         default: {
                         default: {
-                            throw new Error(context + ": Invalid component type " + accessor.componentType);
+                            throw new Error(`${context}: Invalid component type (${accessor.componentType})`);
                         }
                         }
                     }
                     }
                 }
                 }
                 catch (e) {
                 catch (e) {
-                    throw new Error(context + ": " + e);
+                    throw new Error(`${context}: ${e.messsage}`);
                 }
                 }
             });
             });
 
 
@@ -1156,13 +1174,13 @@ module BABYLON.GLTF2 {
                 babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
                 babylonMaterial.roughness = properties.roughnessFactor == undefined ? 1 : properties.roughnessFactor;
 
 
                 if (properties.baseColorTexture) {
                 if (properties.baseColorTexture) {
-                    promises.push(this._loadTextureAsync(context + "/baseColorTexture", properties.baseColorTexture, texture => {
+                    promises.push(this._loadTextureAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
                         babylonMaterial.albedoTexture = texture;
                         babylonMaterial.albedoTexture = texture;
                     }));
                     }));
                 }
                 }
 
 
                 if (properties.metallicRoughnessTexture) {
                 if (properties.metallicRoughnessTexture) {
-                    promises.push(this._loadTextureAsync(context + "/metallicRoughnessTexture", properties.metallicRoughnessTexture, texture => {
+                    promises.push(this._loadTextureAsync(`${context}/metallicRoughnessTexture`, properties.metallicRoughnessTexture, texture => {
                         babylonMaterial.metallicTexture = texture;
                         babylonMaterial.metallicTexture = texture;
                     }));
                     }));
 
 
@@ -1177,8 +1195,8 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => {});
             return Promise.all(promises).then(() => {});
         }
         }
 
 
-        public _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Promise<void> {
-            const promise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh);
+        public _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Promise<void> {
+            const promise = GLTFLoaderExtension._LoadMaterialAsync(this, context, material, babylonMesh, assign);
             if (promise) {
             if (promise) {
                 return promise;
                 return promise;
             }
             }
@@ -1186,28 +1204,25 @@ module BABYLON.GLTF2 {
             material._babylonMeshes = material._babylonMeshes || [];
             material._babylonMeshes = material._babylonMeshes || [];
             material._babylonMeshes.push(babylonMesh);
             material._babylonMeshes.push(babylonMesh);
 
 
-            if (material._loaded) {
-                babylonMesh.material = material._babylonMaterial!;
-                return material._loaded;
-            }
-
-            const promises = new Array<Promise<void>>();
-
-            const babylonMaterial = this._createMaterial(material);
-            material._babylonMaterial = babylonMaterial;
+            if (!material._loaded) {
+                const promises = new Array<Promise<void>>();
 
 
-            promises.push(this._loadMaterialBasePropertiesAsync(context, material));
-            promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
+                const babylonMaterial = this._createMaterial(material);
+                material._babylonMaterial = babylonMaterial;
 
 
-            this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                promises.push(this._loadMaterialBasePropertiesAsync(context, material));
+                promises.push(this._loadMaterialMetallicRoughnessPropertiesAsync(context, material));
 
 
-            babylonMesh.material = babylonMaterial;
+                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                material._loaded = Promise.all(promises).then(() => {});
+            }
 
 
-            return (material._loaded = Promise.all(promises).then(() => {}));
+            assign(material._babylonMaterial!);
+            return material._loaded;
         }
         }
 
 
         public _createMaterial(material: ILoaderMaterial): PBRMaterial {
         public _createMaterial(material: ILoaderMaterial): PBRMaterial {
-            const babylonMaterial = new PBRMaterial(material.name || "material" + material._index, this._babylonScene);
+            const babylonMaterial = new PBRMaterial(material.name || `material${material._index}`, this._babylonScene);
             babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation;
             babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation;
             return babylonMaterial;
             return babylonMaterial;
         }
         }
@@ -1224,7 +1239,7 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (material.normalTexture) {
             if (material.normalTexture) {
-                promises.push(this._loadTextureAsync(context + "/normalTexture", material.normalTexture, texture => {
+                promises.push(this._loadTextureAsync(`${context}/normalTexture`, material.normalTexture, texture => {
                     babylonMaterial.bumpTexture = texture;
                     babylonMaterial.bumpTexture = texture;
                 }));
                 }));
 
 
@@ -1236,7 +1251,7 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (material.occlusionTexture) {
             if (material.occlusionTexture) {
-                promises.push(this._loadTextureAsync(context + "/occlusionTexture", material.occlusionTexture, texture => {
+                promises.push(this._loadTextureAsync(`${context}/occlusionTexture`, material.occlusionTexture, texture => {
                     babylonMaterial.ambientTexture = texture;
                     babylonMaterial.ambientTexture = texture;
                 }));
                 }));
 
 
@@ -1247,7 +1262,7 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (material.emissiveTexture) {
             if (material.emissiveTexture) {
-                promises.push(this._loadTextureAsync(context + "/emissiveTexture", material.emissiveTexture, texture => {
+                promises.push(this._loadTextureAsync(`${context}/emissiveTexture`, material.emissiveTexture, texture => {
                     babylonMaterial.emissiveTexture = texture;
                     babylonMaterial.emissiveTexture = texture;
                 }));
                 }));
             }
             }
@@ -1295,19 +1310,19 @@ module BABYLON.GLTF2 {
                     break;
                     break;
                 }
                 }
                 default: {
                 default: {
-                    throw new Error(context + ": Invalid alpha mode " + material.alphaMode);
+                    throw new Error(`${context}: Invalid alpha mode (${material.alphaMode})`);
                 }
                 }
             }
             }
         }
         }
 
 
         public _loadTextureAsync(context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Promise<void> {
         public _loadTextureAsync(context: string, textureInfo: ITextureInfo, assign: (texture: Texture) => void): Promise<void> {
-            const texture = GLTFLoader._GetProperty(context + "/index", this._gltf.textures, textureInfo.index);
-            context = "#/textures/" + textureInfo.index;
+            const texture = GLTFLoader._GetProperty(`${context}/index`, this._gltf.textures, textureInfo.index);
+            context = `#/textures/${textureInfo.index}`;
 
 
             const promises = new Array<Promise<void>>();
             const promises = new Array<Promise<void>>();
 
 
-            const sampler = (texture.sampler == undefined ? this._defaultSampler : GLTFLoader._GetProperty(context + "/sampler", this._gltf.samplers, texture.sampler));
-            const samplerData = this._loadSampler("#/samplers/" + sampler._index, sampler);
+            const sampler = (texture.sampler == undefined ? this._defaultSampler : GLTFLoader._GetProperty(`${context}/sampler`, this._gltf.samplers, texture.sampler));
+            const samplerData = this._loadSampler(`#/samplers/${sampler._index}`, sampler);
 
 
             const deferred = new Deferred<void>();
             const deferred = new Deferred<void>();
             const babylonTexture = new Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
             const babylonTexture = new Texture(null, this._babylonScene, samplerData.noMipMaps, false, samplerData.samplingMode, () => {
@@ -1316,18 +1331,18 @@ module BABYLON.GLTF2 {
                 }
                 }
             }, (message, exception) => {
             }, (message, exception) => {
                 if (!this._disposed) {
                 if (!this._disposed) {
-                    deferred.reject(new Error(context + ": " + (exception && exception.message) ? exception.message : message || "Failed to load texture"));
+                    deferred.reject(new Error(`${context}: ${(exception && exception.message) ? exception.message : message || "Failed to load texture"}`));
                 }
                 }
             });
             });
             promises.push(deferred.promise);
             promises.push(deferred.promise);
 
 
-            babylonTexture.name = texture.name || "texture" + texture._index;
+            babylonTexture.name = texture.name || `texture${texture._index}`;
             babylonTexture.wrapU = samplerData.wrapU;
             babylonTexture.wrapU = samplerData.wrapU;
             babylonTexture.wrapV = samplerData.wrapV;
             babylonTexture.wrapV = samplerData.wrapV;
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
             babylonTexture.coordinatesIndex = textureInfo.texCoord || 0;
 
 
-            const image = GLTFLoader._GetProperty(context + "/source", this._gltf.images, texture.source);
-            promises.push(this._loadImageAsync("#/images/" + image._index, image).then(objectURL => {
+            const image = GLTFLoader._GetProperty(`${context}/source`, this._gltf.images, texture.source);
+            promises.push(this._loadImageAsync(`#/images/${image._index}`, image).then(objectURL => {
                 babylonTexture.updateURL(objectURL);
                 babylonTexture.updateURL(objectURL);
             }));
             }));
 
 
@@ -1360,8 +1375,8 @@ module BABYLON.GLTF2 {
                 promise = this._loadUriAsync(context, image.uri);
                 promise = this._loadUriAsync(context, image.uri);
             }
             }
             else {
             else {
-                const bufferView = GLTFLoader._GetProperty(context + "/bufferView", this._gltf.bufferViews, image.bufferView);
-                promise = this._loadBufferViewAsync("#/bufferViews/" + bufferView._index, bufferView);
+                const bufferView = GLTFLoader._GetProperty(`${context}/bufferView`, this._gltf.bufferViews, image.bufferView);
+                promise = this._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView);
             }
             }
 
 
             image._objectURL = promise.then(data => {
             image._objectURL = promise.then(data => {
@@ -1378,7 +1393,7 @@ module BABYLON.GLTF2 {
             }
             }
 
 
             if (!GLTFLoader._ValidateUri(uri)) {
             if (!GLTFLoader._ValidateUri(uri)) {
-                throw new Error(context + ": Uri '" + uri + "' is invalid");
+                throw new Error(`${context}: Uri '${uri}' is invalid`);
             }
             }
 
 
             if (Tools.IsBase64(uri)) {
             if (Tools.IsBase64(uri)) {
@@ -1406,7 +1421,7 @@ module BABYLON.GLTF2 {
                     }
                     }
                 }, this._babylonScene.database, true, (request, exception) => {
                 }, this._babylonScene.database, true, (request, exception) => {
                     if (!this._disposed) {
                     if (!this._disposed) {
-                        reject(new LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request));
+                        reject(new LoadFileError(`${context}: Failed to load '${uri}'${request ? ": " + request.status + " " + request.statusText : ""}`, request));
                     }
                     }
                 }) as IFileRequestInfo;
                 }) as IFileRequestInfo;
 
 
@@ -1437,7 +1452,7 @@ module BABYLON.GLTF2 {
 
 
         public static _GetProperty<T>(context: string, array: ArrayLike<T> | undefined, index: number | undefined): T {
         public static _GetProperty<T>(context: string, array: ArrayLike<T> | undefined, index: number | undefined): T {
             if (!array || index == undefined || !array[index]) {
             if (!array || index == undefined || !array[index]) {
-                throw new Error(context + ": Failed to find index " + index);
+                throw new Error(`${context}: Failed to find index (${index})`);
             }
             }
 
 
             return array[index];
             return array[index];
@@ -1452,7 +1467,7 @@ module BABYLON.GLTF2 {
                 case TextureWrapMode.MIRRORED_REPEAT: return Texture.MIRROR_ADDRESSMODE;
                 case TextureWrapMode.MIRRORED_REPEAT: return Texture.MIRROR_ADDRESSMODE;
                 case TextureWrapMode.REPEAT: return Texture.WRAP_ADDRESSMODE;
                 case TextureWrapMode.REPEAT: return Texture.WRAP_ADDRESSMODE;
                 default:
                 default:
-                    Tools.Warn(context + ": Invalid texture wrap mode " + mode);
+                    Tools.Warn(`${context}: Invalid texture wrap mode (${mode})`);
                     return Texture.WRAP_ADDRESSMODE;
                     return Texture.WRAP_ADDRESSMODE;
             }
             }
         }
         }
@@ -1471,13 +1486,13 @@ module BABYLON.GLTF2 {
                     case TextureMinFilter.NEAREST_MIPMAP_LINEAR: return Texture.LINEAR_NEAREST_MIPLINEAR;
                     case TextureMinFilter.NEAREST_MIPMAP_LINEAR: return Texture.LINEAR_NEAREST_MIPLINEAR;
                     case TextureMinFilter.LINEAR_MIPMAP_LINEAR: return Texture.LINEAR_LINEAR_MIPLINEAR;
                     case TextureMinFilter.LINEAR_MIPMAP_LINEAR: return Texture.LINEAR_LINEAR_MIPLINEAR;
                     default:
                     default:
-                        Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                        Tools.Warn(`${context}: Invalid texture minification filter (${minFilter})`);
                         return Texture.LINEAR_LINEAR_MIPLINEAR;
                         return Texture.LINEAR_LINEAR_MIPLINEAR;
                 }
                 }
             }
             }
             else {
             else {
                 if (magFilter !== TextureMagFilter.NEAREST) {
                 if (magFilter !== TextureMagFilter.NEAREST) {
-                    Tools.Warn(context + ": Invalid texture magnification filter " + magFilter);
+                    Tools.Warn(`${context}: Invalid texture magnification filter (${magFilter})`);
                 }
                 }
 
 
                 switch (minFilter) {
                 switch (minFilter) {
@@ -1488,7 +1503,7 @@ module BABYLON.GLTF2 {
                     case TextureMinFilter.NEAREST_MIPMAP_LINEAR: return Texture.NEAREST_NEAREST_MIPLINEAR;
                     case TextureMinFilter.NEAREST_MIPMAP_LINEAR: return Texture.NEAREST_NEAREST_MIPLINEAR;
                     case TextureMinFilter.LINEAR_MIPMAP_LINEAR: return Texture.NEAREST_LINEAR_MIPLINEAR;
                     case TextureMinFilter.LINEAR_MIPMAP_LINEAR: return Texture.NEAREST_LINEAR_MIPLINEAR;
                     default:
                     default:
-                        Tools.Warn(context + ": Invalid texture minification filter " + minFilter);
+                        Tools.Warn(`${context}: Invalid texture minification filter (${minFilter})`);
                         return Texture.NEAREST_NEAREST_MIPNEAREST;
                         return Texture.NEAREST_NEAREST_MIPNEAREST;
                 }
                 }
             }
             }
@@ -1505,7 +1520,7 @@ module BABYLON.GLTF2 {
                 case "MAT4": return 16;
                 case "MAT4": return 16;
             }
             }
 
 
-            throw new Error(context + ": Invalid type " + type);
+            throw new Error(`${context}: Invalid type (${type})`);
         }
         }
 
 
         private static _ValidateUri(uri: string): boolean {
         private static _ValidateUri(uri: string): boolean {
@@ -1521,6 +1536,9 @@ module BABYLON.GLTF2 {
                     const babylonMeshes = material._babylonMeshes;
                     const babylonMeshes = material._babylonMeshes;
                     if (babylonMaterial && babylonMeshes) {
                     if (babylonMaterial && babylonMeshes) {
                         for (const babylonMesh of babylonMeshes) {
                         for (const babylonMesh of babylonMeshes) {
+                            // Ensure nonUniformScaling is set if necessary.
+                            babylonMesh.computeWorldMatrix(true);
+
                             promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                             promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                             if (this.useClipPlane) {
                             if (this.useClipPlane) {
                                 promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));
                                 promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));

+ 4 - 4
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -27,7 +27,7 @@ module BABYLON.GLTF2 {
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> { return null; }
         protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> { return null; }
 
 
         /** Override this method to modify the default behavior for loading materials. */
         /** Override this method to modify the default behavior for loading materials. */
-        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> { return null; }
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> { return null; }
 
 
         /** Override this method to modify the default behavior for loading uris. */
         /** Override this method to modify the default behavior for loading uris. */
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> { return null; }
         protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> { return null; }
@@ -51,7 +51,7 @@ module BABYLON.GLTF2 {
             delete extensions[this.name];
             delete extensions[this.name];
 
 
             try {
             try {
-                return actionAsync(context + "/extensions/" + this.name, extension);
+                return actionAsync(`${context}/extensions/${this.name}`, extension);
             }
             }
             finally {
             finally {
                 // Restore the extension after executing the action.
                 // Restore the extension after executing the action.
@@ -75,8 +75,8 @@ module BABYLON.GLTF2 {
         }
         }
 
 
         /** Helper method called by the loader to allow extensions to override loading materials. */
         /** Helper method called by the loader to allow extensions to override loading materials. */
-        public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, babylonMesh));
+        public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, babylonMesh, assign));
         }
         }
 
 
         /** Helper method called by the loader to allow extensions to override loading uris. */
         /** Helper method called by the loader to allow extensions to override loading uris. */

+ 28 - 2
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -63,6 +63,7 @@ module BABYLON {
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onMeshLoadedObservable: Observable<AbstractMesh>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onTextureLoadedObservable: Observable<BaseTexture>;
         onMaterialLoadedObservable: Observable<Material>;
         onMaterialLoadedObservable: Observable<Material>;
+        onAnimationGroupLoadedObservable: Observable<AnimationGroup>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onCompleteObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onDisposeObservable: Observable<IGLTFLoader>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
         onExtensionLoadedObservable: Observable<IGLTFLoaderExtension>;
@@ -150,7 +151,7 @@ module BABYLON {
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
         public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
 
 
         private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
         private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
-        public set onTextureLoaded(callback: (Texture: BaseTexture) => void) {
+        public set onTextureLoaded(callback: (texture: BaseTexture) => void) {
             if (this._onTextureLoadedObserver) {
             if (this._onTextureLoadedObserver) {
                 this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
                 this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
             }
             }
@@ -163,7 +164,7 @@ module BABYLON {
         public readonly onMaterialLoadedObservable = new Observable<Material>();
         public readonly onMaterialLoadedObservable = new Observable<Material>();
 
 
         private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
         private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
-        public set onMaterialLoaded(callback: (Material: Material) => void) {
+        public set onMaterialLoaded(callback: (material: Material) => void) {
             if (this._onMaterialLoadedObserver) {
             if (this._onMaterialLoadedObserver) {
                 this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
                 this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
             }
             }
@@ -171,6 +172,19 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
+         * Raised when the loader creates an animation group after parsing the glTF properties of the animation.
+         */
+        public readonly onAnimationGroupLoadedObservable = new Observable<AnimationGroup>();
+
+        private _onAnimationGroupLoadedObserver: Nullable<Observer<AnimationGroup>>;
+        public set onAnimationGroupLoaded(callback: (animationGroup: AnimationGroup) => void) {
+            if (this._onAnimationGroupLoadedObserver) {
+                this.onAnimationGroupLoadedObservable.remove(this._onAnimationGroupLoadedObserver);
+            }
+            this._onAnimationGroupLoadedObserver = this.onAnimationGroupLoadedObservable.add(callback);
+        }
+
+        /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
@@ -213,6 +227,18 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
+         * Gets a promise that resolves when the asset is completely loaded.
+         * @returns A promise that resolves when the asset is completely loaded.
+         */
+        public whenCompleteAsync(): Promise<void> {
+            return new Promise(resolve => {
+                this.onCompleteObservable.add(() => {
+                    resolve();
+                }, undefined, undefined, undefined, true);
+            });
+        }
+
+        /**
          * The loader state or null if not active.
          * The loader state or null if not active.
          */
          */
         public get loaderState(): Nullable<GLTFLoaderState> {
         public get loaderState(): Nullable<GLTFLoaderState> {

File diff suppressed because it is too large
+ 35 - 1973
materialsLibrary/src/custom/babylon.customMaterial.ts


+ 0 - 4
materialsLibrary/src/fur/fur.vertex.fx

@@ -115,12 +115,8 @@ float r = Rand(position);
 	#endif
 	#endif
 	
 	
 	#ifdef NORMAL
 	#ifdef NORMAL
-	#ifdef HIGHLEVEL
-	vNormalW = normalize(vec3(finalWorld * vec4(normal, 0.0)) * aNormal);
-	#else
 	vNormalW = normalize(vec3(finalWorld * vec4(normal, 0.0)));
 	vNormalW = normalize(vec3(finalWorld * vec4(normal, 0.0)));
 	#endif
 	#endif
-	#endif
 	
 	
 //END FUR
 //END FUR
 	gl_Position = viewProjection * finalWorld * vec4(newPosition, 1.0);
 	gl_Position = viewProjection * finalWorld * vec4(newPosition, 1.0);

+ 4 - 4
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -719,13 +719,13 @@ module BABYLON.GLTF2 {
                             else {
                             else {
                                 Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                                 Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
                             }
                             }
-                            if (materialIndex != null) {
-                                if (uvCoordsPresent || !_GLTFMaterial.HasTexturesPresent(this.materials[materialIndex])) {
+                            if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
+                                if (uvCoordsPresent || !_GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
                                     meshPrimitive.material = materialIndex;
                                     meshPrimitive.material = materialIndex;
                                 }
                                 }
                                 else {
                                 else {
                                     // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
                                     // If no texture coordinate information is present, make a copy of the material without the textures to be glTF compliant.
-                                    const newMat = _GLTFMaterial.StripTexturesFromMaterial(this.materials[materialIndex]);
+                                    const newMat = _GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
                                     this.materials.push(newMat);
                                     this.materials.push(newMat);
                                     meshPrimitive.material = this.materials.length - 1;
                                     meshPrimitive.material = this.materials.length - 1;
                                 }
                                 }
@@ -750,7 +750,7 @@ module BABYLON.GLTF2 {
                 const babylonMeshes = babylonScene.meshes;
                 const babylonMeshes = babylonScene.meshes;
                 const scene = { nodes: new Array<number>() };
                 const scene = { nodes: new Array<number>() };
 
 
-                _GLTFMaterial.ConvertMaterialsToGLTF(babylonScene.materials, ImageMimeType.JPEG, this.images, this.textures, this.materials, this.imageData, true);
+                _GLTFMaterial._ConvertMaterialsToGLTF(babylonScene.materials, ImageMimeType.PNG, this.images, this.textures, this.materials, this.imageData, true);
                 const result = this.createNodeMap(babylonScene, byteOffset);
                 const result = this.createNodeMap(babylonScene, byteOffset);
                 this.nodeMap = result.nodeMap;
                 this.nodeMap = result.nodeMap;
                 this.totalByteLength = result.byteOffset;
                 this.totalByteLength = result.byteOffset;

+ 475 - 158
serializers/src/glTF/2.0/babylon.glTFMaterial.ts

@@ -1,6 +1,50 @@
 /// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
 /// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
 
 
 module BABYLON.GLTF2 {
 module BABYLON.GLTF2 {
+    /** 
+     * Interface for storing specular glossiness factors.
+    */
+    interface _IPBRSpecularGlossiness {
+        /** 
+         * Represents the linear diffuse factors of the material.
+        */
+        diffuseColor: BABYLON.Color3;
+        /** 
+         * Represents the linear specular factors of the material.
+        */
+        specularColor: BABYLON.Color3;
+        /** 
+         * Represents the smoothness of the material.
+        */
+        glossiness: number;
+    }
+
+    /** 
+     * Interface for storing metallic roughness factors.
+    */
+    interface _IPBRMetallicRoughness {
+        /** 
+         * Represents the albedo color of the material.
+        */
+        baseColor: BABYLON.Color3;
+        /** 
+         * Represents the metallness of the material.
+        */
+        metallic: number;
+        /** 
+         * Represents the roughness of the material.
+        */
+        roughness: number;
+        /** 
+         * The metallic roughness texture as a base64 string.
+        */
+        metallicRoughnessTextureBase64?: Nullable<string>;
+        /** 
+         * The base color texture as a base64 string.
+        */
+        baseColorTextureBase64?: Nullable<string>;
+    }
+
     /**
     /**
      * Utility methods for working with glTF material conversion properties.  This class should only be used internally.
      * Utility methods for working with glTF material conversion properties.  This class should only be used internally.
      */
      */
@@ -8,39 +52,54 @@ module BABYLON.GLTF2 {
         /**
         /**
          * Represents the dielectric specular values for R, G and B.
          * Represents the dielectric specular values for R, G and B.
          */
          */
-        private static readonly dielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04);
+        private static readonly _dielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04);
 
 
         /**
         /**
          * Allows the maximum specular power to be defined for material calculations.
          * Allows the maximum specular power to be defined for material calculations.
          */
          */
-        private static maxSpecularPower = 1024;
+        private static _maxSpecularPower = 1024;
 
 
-        private static epsilon = 1e-6;
+        /**
+         * Numeric tolerance value
+         */
+        private static _epsilon = 1e-6;
+
+        /**
+         * Specifies if two colors are approximately equal in value.
+         * @param color1 - first color to compare to.
+         * @param color2 - second color to compare to.
+         * @param epsilon - threshold value
+         */
+        private static FuzzyEquals(color1: Color3, color2: Color3, epsilon: number): boolean {
+            return Scalar.WithinEpsilon(color1.r, color2.r, epsilon) &&
+                Scalar.WithinEpsilon(color1.g, color2.g, epsilon) &&
+                Scalar.WithinEpsilon(color1.b, color2.b, epsilon);
+        }
 
 
         /**
         /**
          * Gets the materials from a Babylon scene and converts them to glTF materials.
          * Gets the materials from a Babylon scene and converts them to glTF materials.
-         * @param scene
-         * @param mimeType
-         * @param images
-         * @param textures
-         * @param materials
-         * @param imageData
-         * @param hasTextureCoords
+         * @param scene - babylonjs scene.
+         * @param mimeType - texture mime type.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param materials - array of materials.
+         * @param imageData - mapping of texture names to base64 textures
+         * @param hasTextureCoords - specifies if texture coordinates are present on the material.
          */
          */
-        public static ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             for (let i = 0; i < babylonMaterials.length; ++i) {
             for (let i = 0; i < babylonMaterials.length; ++i) {
                 const babylonMaterial = babylonMaterials[i];
                 const babylonMaterial = babylonMaterials[i];
                 if (babylonMaterial instanceof StandardMaterial) {
                 if (babylonMaterial instanceof StandardMaterial) {
-                    _GLTFMaterial.ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                 }
                 }
                 else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
                 else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
-                    _GLTFMaterial.ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                 }
                 }
                 else if (babylonMaterial instanceof PBRMaterial) {
                 else if (babylonMaterial instanceof PBRMaterial) {
-                    _GLTFMaterial.ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                 }
                 }
                 else {
                 else {
-                    Tools.Error("Unsupported material type: " + babylonMaterial.name);
+                    throw new Error("Unsupported material type: " + babylonMaterial.name);
                 }
                 }
             }
             }
         }
         }
@@ -50,7 +109,7 @@ module BABYLON.GLTF2 {
          * @param originalMaterial - original glTF material.
          * @param originalMaterial - original glTF material.
          * @returns glTF material without texture parameters
          * @returns glTF material without texture parameters
          */
          */
-        public static StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial {
+        public static _StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial {
             let newMaterial: IMaterial = {};
             let newMaterial: IMaterial = {};
             if (originalMaterial) {
             if (originalMaterial) {
                 newMaterial.name = originalMaterial.name;
                 newMaterial.name = originalMaterial.name;
@@ -74,7 +133,7 @@ module BABYLON.GLTF2 {
          * @param material - glTF Material.
          * @param material - glTF Material.
          * @returns boolean specifying if texture parameters are present
          * @returns boolean specifying if texture parameters are present
          */
          */
-        public static HasTexturesPresent(material: IMaterial): boolean {
+        public static _HasTexturesPresent(material: IMaterial): boolean {
             if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
             if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) {
                 return true;
                 return true;
             }
             }
@@ -93,7 +152,7 @@ module BABYLON.GLTF2 {
          * @param babylonStandardMaterial 
          * @param babylonStandardMaterial 
          * @returns - glTF Metallic Roughness Material representation
          * @returns - glTF Metallic Roughness Material representation
          */
          */
-        public static ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness {
+        public static _ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness {
             const P0 = new BABYLON.Vector2(0, 1);
             const P0 = new BABYLON.Vector2(0, 1);
             const P1 = new BABYLON.Vector2(0, 0.1);
             const P1 = new BABYLON.Vector2(0, 0.1);
             const P2 = new BABYLON.Vector2(0, 0.1);
             const P2 = new BABYLON.Vector2(0, 0.1);
@@ -108,7 +167,7 @@ module BABYLON.GLTF2 {
              * @param p3 - fourth control point.
              * @param p3 - fourth control point.
              * @returns - number result of cubic bezier curve at the specified t.
              * @returns - number result of cubic bezier curve at the specified t.
              */
              */
-            function cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number {
+            function _cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number {
                 return (
                 return (
                     (1 - t) * (1 - t) * (1 - t) * p0 +
                     (1 - t) * (1 - t) * (1 - t) * p0 +
                     3 * (1 - t) * (1 - t) * t * p1 +
                     3 * (1 - t) * (1 - t) * t * p1 +
@@ -124,16 +183,16 @@ module BABYLON.GLTF2 {
              * @param specularPower - specular power of standard material.
              * @param specularPower - specular power of standard material.
              * @returns - Number representing the roughness value.
              * @returns - Number representing the roughness value.
              */
              */
-            function solveForRoughness(specularPower: number): number {
+            function _solveForRoughness(specularPower: number): number {
                 var t = Math.pow(specularPower / P3.x, 0.333333);
                 var t = Math.pow(specularPower / P3.x, 0.333333);
-                return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
+                return _cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y);
             }
             }
 
 
             let diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
             let diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace().scale(0.5);
             let opacity = babylonStandardMaterial.alpha;
             let opacity = babylonStandardMaterial.alpha;
-            let specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this.maxSpecularPower);
+            let specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, this._maxSpecularPower);
 
 
-            const roughness = solveForRoughness(specularPower);
+            const roughness = _solveForRoughness(specularPower);
 
 
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {
                 baseColorFactor: [
                 baseColorFactor: [
@@ -156,15 +215,15 @@ module BABYLON.GLTF2 {
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @param oneMinusSpecularStrength - one minus the specular strength
          * @returns - metallic value
          * @returns - metallic value
          */
          */
-        public static SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number {
-            if (specular < _GLTFMaterial.dielectricSpecular.r) {
-                _GLTFMaterial.dielectricSpecular
+        public static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number {
+            if (specular < _GLTFMaterial._dielectricSpecular.r) {
+                _GLTFMaterial._dielectricSpecular
                 return 0;
                 return 0;
             }
             }
 
 
-            const a = _GLTFMaterial.dielectricSpecular.r;
-            const b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial.dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial.dielectricSpecular.r;
-            const c = _GLTFMaterial.dielectricSpecular.r - specular;
+            const a = _GLTFMaterial._dielectricSpecular.r;
+            const b = diffuse * oneMinusSpecularStrength / (1.0 - _GLTFMaterial._dielectricSpecular.r) + specular - 2.0 * _GLTFMaterial._dielectricSpecular.r;
+            const c = _GLTFMaterial._dielectricSpecular.r - specular;
             const D = b * b - 4.0 * a * c;
             const D = b * b - 4.0 * a * c;
             return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
             return BABYLON.Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1);
         }
         }
@@ -174,7 +233,7 @@ module BABYLON.GLTF2 {
          * @param babylonMaterial - Babylon Material
          * @param babylonMaterial - Babylon Material
          * @returns - The Babylon alpha mode value
          * @returns - The Babylon alpha mode value
          */
          */
-        public static GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode {
+        public static _GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode {
             if (babylonMaterial instanceof StandardMaterial) {
             if (babylonMaterial instanceof StandardMaterial) {
                 const babylonStandardMaterial = babylonMaterial as StandardMaterial;
                 const babylonStandardMaterial = babylonMaterial as StandardMaterial;
                 if ((babylonStandardMaterial.alpha != 1.0) ||
                 if ((babylonStandardMaterial.alpha != 1.0) ||
@@ -245,12 +304,12 @@ module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        public static ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
             Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
-            const glTFPbrMetallicRoughness = _GLTFMaterial.ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
+            const glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
 
 
             const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
             const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
-            if (babylonStandardMaterial.backFaceCulling) {
+            if (babylonStandardMaterial.backFaceCulling != null && !babylonStandardMaterial.backFaceCulling) {
                 if (!babylonStandardMaterial.twoSidedLighting) {
                 if (!babylonStandardMaterial.twoSidedLighting) {
                     Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     Tools.Warn(babylonStandardMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                 }
                 }
@@ -258,26 +317,26 @@ module BABYLON.GLTF2 {
             }
             }
             if (hasTextureCoords) {
             if (hasTextureCoords) {
                 if (babylonStandardMaterial.diffuseTexture) {
                 if (babylonStandardMaterial.diffuseTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
                     if (glTFTexture != null) {
                     if (glTFTexture != null) {
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                     }
                 }
                 }
                 if (babylonStandardMaterial.bumpTexture) {
                 if (babylonStandardMaterial.bumpTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData)
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData)
                     if (glTFTexture) {
                     if (glTFTexture) {
                         glTFMaterial.normalTexture = glTFTexture;
                         glTFMaterial.normalTexture = glTFTexture;
                     }
                     }
                 }
                 }
                 if (babylonStandardMaterial.emissiveTexture) {
                 if (babylonStandardMaterial.emissiveTexture) {
-                    const glTFEmissiveTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData)
+                    const glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData)
                     if (glTFEmissiveTexture) {
                     if (glTFEmissiveTexture) {
                         glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                         glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                     }
                     }
                     glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                     glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                 }
                 }
                 if (babylonStandardMaterial.ambientTexture) {
                 if (babylonStandardMaterial.ambientTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
                     if (glTFTexture) {
                     if (glTFTexture) {
                         const occlusionTexture: IMaterialOcclusionTextureInfo = {
                         const occlusionTexture: IMaterialOcclusionTextureInfo = {
                             index: glTFTexture.index
                             index: glTFTexture.index
@@ -297,7 +356,7 @@ module BABYLON.GLTF2 {
                     Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                     Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString());
                 }
                 }
             }
             }
-            if (babylonStandardMaterial.emissiveColor) {
+            if (babylonStandardMaterial.emissiveColor && !this.FuzzyEquals(babylonStandardMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
                 glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
                 glTFMaterial.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray();
             }
             }
 
 
@@ -316,7 +375,7 @@ module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        public static ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
 
 
             if (babylonPBRMetalRoughMaterial.baseColor) {
             if (babylonPBRMetalRoughMaterial.baseColor) {
@@ -328,10 +387,10 @@ module BABYLON.GLTF2 {
                 ];
                 ];
             }
             }
 
 
-            if (babylonPBRMetalRoughMaterial.metallic != null) {
+            if (babylonPBRMetalRoughMaterial.metallic != null && babylonPBRMetalRoughMaterial.metallic !== 1) {
                 glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
                 glTFPbrMetallicRoughness.metallicFactor = babylonPBRMetalRoughMaterial.metallic;
             }
             }
-            if (babylonPBRMetalRoughMaterial.roughness != null) {
+            if (babylonPBRMetalRoughMaterial.roughness != null && babylonPBRMetalRoughMaterial.roughness !== 1) {
                 glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
                 glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMetalRoughMaterial.roughness;
             }
             }
 
 
@@ -344,19 +403,19 @@ module BABYLON.GLTF2 {
 
 
             if (hasTextureCoords) {
             if (hasTextureCoords) {
                 if (babylonPBRMetalRoughMaterial.baseTexture != null) {
                 if (babylonPBRMetalRoughMaterial.baseTexture != null) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
                     if (glTFTexture != null) {
                     if (glTFTexture != null) {
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                     }
                 }
                 }
                 if (babylonPBRMetalRoughMaterial.normalTexture) {
                 if (babylonPBRMetalRoughMaterial.normalTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
                     if (glTFTexture) {
                     if (glTFTexture) {
                         glTFMaterial.normalTexture = glTFTexture;
                         glTFMaterial.normalTexture = glTFTexture;
                     }
                     }
                 }
                 }
                 if (babylonPBRMetalRoughMaterial.occlusionTexture) {
                 if (babylonPBRMetalRoughMaterial.occlusionTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
                     if (glTFTexture) {
                     if (glTFTexture) {
                         glTFMaterial.occlusionTexture = glTFTexture;
                         glTFMaterial.occlusionTexture = glTFTexture;
                         if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
                         if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
@@ -365,18 +424,18 @@ module BABYLON.GLTF2 {
                     }
                     }
                 }
                 }
                 if (babylonPBRMetalRoughMaterial.emissiveTexture) {
                 if (babylonPBRMetalRoughMaterial.emissiveTexture) {
-                    const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
                     if (glTFTexture != null) {
                     if (glTFTexture != null) {
                         glTFMaterial.emissiveTexture = glTFTexture;
                         glTFMaterial.emissiveTexture = glTFTexture;
                     }
                     }
                 }
                 }
             }
             }
 
 
-            if (babylonPBRMetalRoughMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
+            if (this.FuzzyEquals(babylonPBRMetalRoughMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
                 glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
                 glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
             }
             }
             if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
             if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
-                const alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMetalRoughMaterial);
+                const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
 
 
                 if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
                 if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
                     glTFMaterial.alphaMode = alphaMode;
                     glTFMaterial.alphaMode = alphaMode;
@@ -392,12 +451,255 @@ module BABYLON.GLTF2 {
         }
         }
 
 
         /**
         /**
+         * Converts an image typed array buffer to a base64 image.
+         * @param buffer - typed array buffer.
+         * @param width - width of the image.
+         * @param height - height of the image.
+         * @param mimeType - mimetype of the image.
+         * @returns - base64 image string.
+         */
+        private static _CreateBase64FromCanvas(buffer: Uint8ClampedArray, width: number, height: number, mimeType: ImageMimeType): string {
+            const imageCanvas = document.createElement('canvas');
+            imageCanvas.id = "WriteCanvas";
+            const ctx = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
+            imageCanvas.width = width;
+            imageCanvas.height = height;
+
+            const imgData = ctx.createImageData(width, height);
+
+            imgData.data.set(buffer);
+            ctx.putImageData(imgData, 0, 0);
+
+            return imageCanvas.toDataURL(mimeType);
+        }
+
+        /**
+         * Generates a white texture based on the specified width and height.
+         * @param width - width of the texture in pixels.
+         * @param height - height of the texture in pixels.
+         * @param scene - babylonjs scene.
+         * @returns - white texture.
+         */
+        private static _CreateWhiteTexture(width: number, height: number, scene: Scene): Texture {
+            const data = new Uint8Array(width * height * 4);
+
+            for (let i = 0; i < data.length; ++i) {
+                data[i] = 255;
+            }
+
+            const rawTexture = RawTexture.CreateRGBATexture(data, width, height, scene);
+
+            return rawTexture;
+        }
+
+        /**
+         * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
+         * @param texture1 - first texture to resize.
+         * @param texture2 - second texture to resize.
+         * @param scene - babylonjs scene.
+         * @returns resized textures or null.
+         */
+        private static _ResizeTexturesToSameDimensions(texture1: BaseTexture, texture2: BaseTexture, scene: Scene): { "texture1": BaseTexture, "texture2": BaseTexture } {
+            let texture1Size = texture1 ? texture1.getSize() : { width: 0, height: 0 };
+            let texture2Size = texture2 ? texture2.getSize() : { width: 0, height: 0 };
+            let resizedTexture1;
+            let resizedTexture2;
+
+            if (texture1Size.width < texture2Size.width) {
+                if (texture1) {
+                    resizedTexture1 = TextureTools.CreateResizedCopy(texture1 as Texture, texture2Size.width, texture2Size.height, true);
+                }
+                else {
+                    resizedTexture1 = this._CreateWhiteTexture(texture2Size.width, texture2Size.height, scene);
+                }
+                resizedTexture2 = texture2;
+            }
+            else if (texture1Size.width > texture2Size.width) {
+                if (texture2) {
+                    resizedTexture2 = TextureTools.CreateResizedCopy(texture2 as Texture, texture1Size.width, texture1Size.height, true);
+                }
+                else {
+                    resizedTexture2 = this._CreateWhiteTexture(texture1Size.width, texture1Size.height, scene);
+                }
+                resizedTexture1 = texture1;
+            }
+            else {
+                resizedTexture1 = texture1;
+                resizedTexture2 = texture2;
+            }
+
+            return {
+                "texture1": resizedTexture1,
+                "texture2": resizedTexture2
+            }
+        }
+
+        /**
+         * Convert Specular Glossiness Textures to Metallic Roughness.
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
+         * @param diffuseTexture - texture used to store diffuse information.
+         * @param specularGlossinessTexture - texture used to store specular and glossiness information.
+         * @param factors - specular glossiness material factors.
+         * @param mimeType - the mime type to use for the texture.
+         * @returns pbr metallic roughness interface or null.
+         */
+        private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Nullable<_IPBRMetallicRoughness> {
+            if (!(diffuseTexture || specularGlossinessTexture)) {
+                return null;
+            }
+
+            const scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
+            if (!scene) {
+                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
+            }
+
+            const resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
+
+            let diffuseSize = resizedTextures.texture1.getSize();
+
+            let diffuseBuffer: Uint8Array;
+            let specularGlossinessBuffer: Uint8Array;
+
+            const width = diffuseSize.width;
+            const height = diffuseSize.height;
+
+            let pixels = (resizedTextures.texture1.readPixels());
+            if (pixels instanceof Uint8Array) {
+                diffuseBuffer = (resizedTextures.texture1.readPixels()) as Uint8Array;
+            }
+            else {
+                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+            }
+            pixels = resizedTextures.texture2.readPixels();
+
+            if (pixels instanceof Uint8Array) {
+                specularGlossinessBuffer = (resizedTextures.texture2.readPixels()) as Uint8Array;
+            }
+            else {
+                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
+            }
+
+            const byteLength = specularGlossinessBuffer.byteLength;
+
+            const metallicRoughnessBuffer = new Uint8Array(byteLength);
+            const baseColorBuffer = new Uint8Array(byteLength);
+
+            const strideSize = 4;
+            const maxBaseColor = Color3.Black();
+            let maxMetallic = 0;
+            let maxRoughness = 0;
+
+            for (let h = 0; h < height; ++h) {
+                for (let w = 0; w < width; ++w) {
+                    const offset = (width * h + w) * strideSize;
+
+                    const diffuseColor = Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).multiply(factors.diffuseColor);
+                    const specularColor = Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).multiply(factors.specularColor);
+                    const glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
+
+                    const specularGlossiness: _IPBRSpecularGlossiness = {
+                        diffuseColor: diffuseColor,
+                        specularColor: specularColor,
+                        glossiness: glossiness
+                    };
+
+                    const metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+                    maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
+                    maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
+                    maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
+                    maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
+                    maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
+
+                    baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
+                    baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
+                    baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
+                    baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
+
+                    metallicRoughnessBuffer[offset] = 255;
+                    metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
+                    metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
+                    metallicRoughnessBuffer[offset + 3] = 255;
+                }
+            }
+
+            // Retrieves the metallic roughness factors from the maximum texture values.
+            const metallicRoughnessFactors: _IPBRMetallicRoughness = {
+                baseColor: maxBaseColor,
+                metallic: maxMetallic,
+                roughness: maxRoughness
+            };
+
+            let writeOutMetallicRoughnessTexture = false;
+            let writeOutBaseColorTexture = false;
+
+            for (let h = 0; h < height; ++h) {
+                for (let w = 0; w < width; ++w) {
+                    const destinationOffset = (width * h + w) * strideSize;
+
+                    baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
+                    baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
+                    baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
+
+                    const baseColorPixel = new Color3(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
+
+                    if (!this.FuzzyEquals(baseColorPixel, Color3.White(), this._epsilon)) {
+                        writeOutBaseColorTexture = true;
+                    }
+
+                    metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
+                    metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
+
+                    const metallicRoughnessPixel = new Color3(metallicRoughnessBuffer[destinationOffset], metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
+
+                    if (!this.FuzzyEquals(metallicRoughnessPixel, Color3.White(), this._epsilon)) {
+                        writeOutMetallicRoughnessTexture = true;
+                    }
+                }
+            }
+
+            if (writeOutMetallicRoughnessTexture) {
+                const metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
+                metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
+            }
+            if (writeOutBaseColorTexture) {
+                const baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
+                metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
+            }
+
+            return metallicRoughnessFactors;
+        }
+
+        /**
+         * Converts specular glossiness material properties to metallic roughness.
+         * @param specularGlossiness - interface with specular glossiness material properties.
+         * @returns - interface with metallic roughness material properties.
+         */
+        private static _ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness: _IPBRSpecularGlossiness): _IPBRMetallicRoughness {
+            const diffusePerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.diffuseColor);
+            const specularPerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.specularColor);
+            const oneMinusSpecularStrength = 1 - _GLTFMaterial._GetMaxComponent(specularGlossiness.specularColor);
+            const metallic = _GLTFMaterial._SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
+            const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this._dielectricSpecular.r) / Math.max(1 - metallic, this._epsilon));
+            const baseColorFromSpecular = specularGlossiness.specularColor.subtract(this._dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this._epsilon));
+            let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
+            baseColor = baseColor.clampToRef(0, 1, baseColor);
+
+            const metallicRoughness: _IPBRMetallicRoughness = {
+                baseColor: baseColor,
+                metallic: metallic,
+                roughness: 1 - specularGlossiness.glossiness
+            }
+
+            return metallicRoughness;
+        }
+
+        /**
+         * Calculates the surface reflectance, independent of lighting conditions.
          * @param color - Color source to calculate brightness from.
          * @param color - Color source to calculate brightness from.
          * @returns number representing the perceived brightness, or zero if color is undefined. 
          * @returns number representing the perceived brightness, or zero if color is undefined. 
          */
          */
-        public static GetPerceivedBrightness(color: Color3): number {
+        private static _GetPerceivedBrightness(color: Color3): number {
             if (color) {
             if (color) {
                 return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
                 return Math.sqrt(0.299 * color.r * color.r + 0.587 * color.g * color.g + 0.114 * color.b * color.b);
             }
             }
@@ -409,7 +711,7 @@ module BABYLON.GLTF2 {
          * @param color 
          * @param color 
          * @returns maximum color component value, or zero if color is null or undefined.
          * @returns maximum color component value, or zero if color is null or undefined.
          */
          */
-        public static GetMaxComponent(color: Color3): number {
+        private static _GetMaxComponent(color: Color3): number {
             if (color) {
             if (color) {
                 return Math.max(color.r, Math.max(color.g, color.b));
                 return Math.max(color.r, Math.max(color.g, color.b));
             }
             }
@@ -426,115 +728,126 @@ module BABYLON.GLTF2 {
          * @param imageData - map of image file name to data.
          * @param imageData - map of image file name to data.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          * @param hasTextureCoords - specifies if texture coordinates are present on the submesh to determine if textures should be applied.
          */
          */
-        public static ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
+            let metallicRoughness: Nullable<_IPBRMetallicRoughness>;
             const glTFMaterial: IMaterial = {
             const glTFMaterial: IMaterial = {
                 name: babylonPBRMaterial.name
                 name: babylonPBRMaterial.name
             };
             };
             const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
             const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
 
 
-            if (babylonPBRMaterial) {
-                if (useMetallicRoughness) {
-                    glTFPbrMetallicRoughness.baseColorFactor = [
-                        babylonPBRMaterial.albedoColor.r,
-                        babylonPBRMaterial.albedoColor.g,
-                        babylonPBRMaterial.albedoColor.b,
-                        babylonPBRMaterial.alpha
-                    ];
-                    if (babylonPBRMaterial.metallic != null) {
-                        if (babylonPBRMaterial.metallic !== 1) {
-                            glTFPbrMetallicRoughness.metallicFactor = babylonPBRMaterial.metallic;
+            if (!useMetallicRoughness) {
+                const specGloss: _IPBRSpecularGlossiness = {
+                    diffuseColor: babylonPBRMaterial.albedoColor || Color3.White(),
+                    specularColor: babylonPBRMaterial.reflectivityColor || Color3.White(),
+                    glossiness: babylonPBRMaterial.microSurface || 1,
+                };
+                if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
+                    throw new Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                }
+
+                metallicRoughness = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
+
+                if (!metallicRoughness) {
+                    metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
+                }
+                else {
+                    if (hasTextureCoords) {
+                        if (metallicRoughness.baseColorTextureBase64) {
+                            const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                            if (glTFBaseColorTexture != null) {
+                                glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
+                            }
                         }
                         }
-                    }
-                    if (babylonPBRMaterial.roughness != null) {
-                        if (babylonPBRMaterial.roughness !== 1) {
-                            glTFPbrMetallicRoughness.roughnessFactor = babylonPBRMaterial.roughness;
+                        if (metallicRoughness.metallicRoughnessTextureBase64) {
+                            const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughness.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                            if (glTFMRColorTexture != null) {
+                                glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
-                else {
-                    const diffuseColor = babylonPBRMaterial.albedoColor || Color3.Black();
-                    const specularColor = babylonPBRMaterial.reflectionColor || Color3.Black();
-                    const diffusePerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(diffuseColor);
-                    const specularPerceivedBrightness = _GLTFMaterial.GetPerceivedBrightness(specularColor);
-                    const oneMinusSpecularStrength = 1 - _GLTFMaterial.GetMaxComponent(babylonPBRMaterial.reflectionColor);
-                    const metallic = _GLTFMaterial.SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength);
-                    const glossiness = babylonPBRMaterial.microSurface || 0;
-                    const baseColorFromDiffuse = diffuseColor.scale(oneMinusSpecularStrength / (1.0 - this.dielectricSpecular.r) / Math.max(1 - metallic, this.epsilon));
-                    const baseColorFromSpecular = specularColor.subtract(this.dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic, this.epsilon));
-                    let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic);
-                    baseColor = baseColor.clampToRef(0, 1, baseColor);
-
-                    glTFPbrMetallicRoughness.baseColorFactor = [
-                        baseColor.r,
-                        baseColor.g,
-                        baseColor.b,
-                        babylonPBRMaterial.alpha
-                    ];
-                    if (metallic !== 1) {
-                        glTFPbrMetallicRoughness.metallicFactor = metallic;
-                    }
-                    if (glossiness) {
-                        glTFPbrMetallicRoughness.roughnessFactor = 1 - glossiness;
-                    }
+            }
+            else {
+                metallicRoughness = {
+                    baseColor: babylonPBRMaterial.albedoColor,
+                    metallic: babylonPBRMaterial.metallic,
+                    roughness: babylonPBRMaterial.roughness
+                };
+            }
+
+            if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
+                glTFPbrMetallicRoughness.baseColorFactor = [
+                    metallicRoughness.baseColor.r,
+                    metallicRoughness.baseColor.g,
+                    metallicRoughness.baseColor.b,
+                    babylonPBRMaterial.alpha
+                ];
+            }
+
+            if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
+                glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
+            }
+            if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
+                glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
+            }
+
+            if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
+                if (!babylonPBRMaterial.twoSidedLighting) {
+                    Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                 }
                 }
-                if (babylonPBRMaterial.backFaceCulling) {
-                    if (!babylonPBRMaterial.twoSidedLighting) {
-                        Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
+                glTFMaterial.doubleSided = true;
+            }
+            if (hasTextureCoords) {
+                if (useMetallicRoughness && babylonPBRMaterial.albedoTexture) {
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture) {
+                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                     }
-                    glTFMaterial.doubleSided = true;
                 }
                 }
-                if (hasTextureCoords) {
-                    if (babylonPBRMaterial.albedoTexture) {
-                        const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
-                        if (glTFTexture) {
-                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                        }
-                    }
-                    if (babylonPBRMaterial.bumpTexture) {
-                        const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
-                        if (glTFTexture) {
-                            glTFMaterial.normalTexture = glTFTexture;
-                        }
+                if (babylonPBRMaterial.bumpTexture) {
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture) {
+                        glTFMaterial.normalTexture = glTFTexture;
                     }
                     }
-                    if (babylonPBRMaterial.ambientTexture) {
-                        const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
-                        if (glTFTexture) {
-                            let occlusionTexture: IMaterialOcclusionTextureInfo = {
-                                index: glTFTexture.index
-                            };
+                }
+                if (babylonPBRMaterial.ambientTexture) {
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture) {
+                        let occlusionTexture: IMaterialOcclusionTextureInfo = {
+                            index: glTFTexture.index
+                        };
 
 
-                            glTFMaterial.occlusionTexture = occlusionTexture;
+                        glTFMaterial.occlusionTexture = occlusionTexture;
 
 
-                            if (babylonPBRMaterial.ambientTextureStrength) {
-                                occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
-                            }
+                        if (babylonPBRMaterial.ambientTextureStrength) {
+                            occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
                         }
                         }
                     }
                     }
-                    if (babylonPBRMaterial.emissiveTexture) {
-                        const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
-                        if (glTFTexture != null) {
-                            glTFMaterial.emissiveTexture = glTFTexture;
-                        }
-                    }
-                    if (babylonPBRMaterial.metallicTexture) {
-                        const glTFTexture = _GLTFMaterial.ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
-                        if (glTFTexture != null) {
-                            glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
-                        }
+                }
+                if (babylonPBRMaterial.emissiveTexture) {
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture != null) {
+                        glTFMaterial.emissiveTexture = glTFTexture;
                     }
                     }
                 }
                 }
-                if (!babylonPBRMaterial.emissiveColor.equalsFloats(0.0, 0.0, 0.0)) {
-                    glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                if (babylonPBRMaterial.metallicTexture) {
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
+                    if (glTFTexture != null) {
+                        glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
+                    }
                 }
                 }
-                if (babylonPBRMaterial.transparencyMode != null) {
-                    const alphaMode = _GLTFMaterial.GetAlphaMode(babylonPBRMaterial);
+            }
+            if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
+                glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+            }
+            if (babylonPBRMaterial.transparencyMode != null) {
+                const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
 
 
-                    if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                        glTFMaterial.alphaMode = alphaMode;
-                        if (alphaMode === MaterialAlphaMode.BLEND) {
-                            glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
-                        }
+                if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                    glTFMaterial.alphaMode = alphaMode;
+                    if (alphaMode === MaterialAlphaMode.BLEND) {
+                        glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
                     }
                     }
                 }
                 }
             }
             }
@@ -550,20 +863,14 @@ module BABYLON.GLTF2 {
          * @param images - Array of glTF images.
          * @param images - Array of glTF images.
          * @param textures - Array of glTF textures.
          * @param textures - Array of glTF textures.
          * @param imageData - map of image file name and data.
          * @param imageData - map of image file name and data.
-         * @return - glTF texture, or null if the texture format is not supported.
+         * @return - glTF texture info, or null if the texture format is not supported.
          */
          */
-        public static ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
-            let textureInfo: Nullable<ITextureInfo> = null;
-
-            let glTFTexture = {
-                source: images.length
-            };
-
+        private static _ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
             let textureName = "texture_" + (textures.length - 1).toString();
             let textureName = "texture_" + (textures.length - 1).toString();
             let textureData = babylonTexture.getInternalTexture();
             let textureData = babylonTexture.getInternalTexture();
 
 
             if (textureData != null) {
             if (textureData != null) {
-                textureName = textureData.url;
+                textureName = textureData.url || textureName;
             }
             }
 
 
             textureName = Tools.GetFilename(textureName);
             textureName = Tools.GetFilename(textureName);
@@ -578,28 +885,38 @@ module BABYLON.GLTF2 {
                 extension = ".png";
                 extension = ".png";
             }
             }
             else {
             else {
-                Tools.Error("Unsupported mime type " + mimeType);
+                throw new Error("Unsupported mime type " + mimeType);
             }
             }
             textureName = baseFile + extension;
             textureName = baseFile + extension;
 
 
 
 
             const pixels = babylonTexture.readPixels() as Uint8Array;
             const pixels = babylonTexture.readPixels() as Uint8Array;
 
 
-            const imageCanvas = document.createElement('canvas');
-            imageCanvas.id = "ImageCanvas";
-
-            const ctx = <CanvasRenderingContext2D>imageCanvas.getContext('2d');
             const size = babylonTexture.getSize();
             const size = babylonTexture.getSize();
-            imageCanvas.width = size.width;
-            imageCanvas.height = size.height;
 
 
-            const imgData = ctx.createImageData(size.width, size.height);
+            const base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
 
 
+            return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, imageData);
+        }
 
 
-            imgData.data.set(pixels);
-            ctx.putImageData(imgData, 0, 0);
-            const base64Data = imageCanvas.toDataURL(mimeType);
-            const binStr = atob(base64Data.split(',')[1]);
+        /**
+         * Builds a texture from base64 string.
+         * @param base64Texture - base64 texture string.
+         * @param textureName - Name to use for the texture.
+         * @param mimeType - image mime type for the texture.
+         * @param images - array of images.
+         * @param textures - array of textures.
+         * @param imageData - map of image data.
+         * @returns - glTF texture info, or null if the texture format is not supported.
+         */
+        private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+            let textureInfo: Nullable<ITextureInfo> = null;
+
+            const glTFTexture = {
+                source: images.length
+            };
+
+            const binStr = atob(base64Texture.split(',')[1]);
             const arr = new Uint8Array(binStr.length);
             const arr = new Uint8Array(binStr.length);
             for (let i = 0; i < binStr.length; ++i) {
             for (let i = 0; i < binStr.length; ++i) {
                 arr[i] = binStr.charCodeAt(i);
                 arr[i] = binStr.charCodeAt(i);
@@ -607,7 +924,7 @@ module BABYLON.GLTF2 {
             const imageValues = { data: arr, mimeType: mimeType };
             const imageValues = { data: arr, mimeType: mimeType };
 
 
             imageData[textureName] = imageValues;
             imageData[textureName] = imageValues;
-            if (mimeType === ImageMimeType.JPEG) {
+            if (mimeType === ImageMimeType.JPEG || mimeType === ImageMimeType.PNG) {
                 const glTFImage: IImage = {
                 const glTFImage: IImage = {
                     uri: textureName
                     uri: textureName
                 }
                 }

+ 27 - 1
src/Animations/babylon.animationGroup.ts

@@ -59,6 +59,13 @@ module BABYLON {
             return this._targetedAnimations;
             return this._targetedAnimations;
         }
         }
 
 
+        /**
+         * returning the list of animatables controlled by this animation group.
+         */
+        public get animatables(): Array<Animatable> {
+            return this._animatables;
+        }
+
         public constructor(public name: string, scene: Nullable<Scene> = null) {
         public constructor(public name: string, scene: Nullable<Scene> = null) {
             this._scene = scene || Engine.LastCreatedScene!;
             this._scene = scene || Engine.LastCreatedScene!;
 
 
@@ -245,6 +252,25 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
+         * Goes to a specific frame in this animation group
+         * 
+         * @param frame the frame number to go to
+         * @return the animationGroup
+         */
+        public goToFrame(frame: number): AnimationGroup {
+            if (!this._isStarted) {
+                return this;
+            }
+
+            for (var index = 0; index < this._animatables.length; index++) {
+                let animatable = this._animatables[index];
+                animatable.goToFrame(frame);
+            }
+
+            return this;
+        }
+
+        /**
          * Dispose all associated resources
          * Dispose all associated resources
          */
          */
         public dispose(): void {
         public dispose(): void {
@@ -258,4 +284,4 @@ module BABYLON {
             }
             }
         }
         }
     }
     }
-}
+}

+ 1 - 0
src/Behaviors/Cameras/babylon.framingBehavior.ts

@@ -328,6 +328,7 @@ module BABYLON {
 
 
             animatable = Animation.TransitionTo("radius", radius, this._attachedCamera, this._attachedCamera.getScene(),
             animatable = Animation.TransitionTo("radius", radius, this._attachedCamera, this._attachedCamera.getScene(),
                 60, this._radiusTransition, this._framingTime, () => {
                 60, this._radiusTransition, this._framingTime, () => {
+                    this.stopAllAnimations();
                     if (onAnimationEnd) {
                     if (onAnimationEnd) {
                         onAnimationEnd();
                         onAnimationEnd();
                     }
                     }

+ 1 - 0
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -157,6 +157,7 @@ module BABYLON {
             makeNotPick(mesh);
             makeNotPick(mesh);
             var childMeshes = mesh.getChildMeshes();
             var childMeshes = mesh.getChildMeshes();
 
 
+            this.webVRController._pointingPoseNode = null;
             for (var i = 0; i < childMeshes.length; i++) {
             for (var i = 0; i < childMeshes.length; i++) {
                 if (childMeshes[i].name && childMeshes[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
                 if (childMeshes[i].name && childMeshes[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
                     mesh = childMeshes[i];
                     mesh = childMeshes[i];

+ 11 - 7
src/Cameras/babylon.arcRotateCamera.ts

@@ -338,6 +338,8 @@
 
 
         protected _targetBoundingCenter: Nullable<Vector3>;
         protected _targetBoundingCenter: Nullable<Vector3>;
 
 
+        private _computationVector: Vector3 = Vector3.Zero();
+
         constructor(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene) {
         constructor(name: string, alpha: number, beta: number, radius: number, target: Vector3, scene: Scene) {
             super(name, Vector3.Zero(), scene);
             super(name, Vector3.Zero(), scene);
 
 
@@ -584,22 +586,22 @@
         }
         }
 
 
         public rebuildAnglesAndRadius() {
         public rebuildAnglesAndRadius() {
-            var radiusv3 = this.position.subtract(this._getTargetPosition());
-            this.radius = radiusv3.length();
+            this.position.subtractToRef(this._getTargetPosition(), this._computationVector);
+            this.radius = this._computationVector.length();
 
 
             if (this.radius === 0) {
             if (this.radius === 0) {
                 this.radius = 0.0001; // Just to avoid division by zero
                 this.radius = 0.0001; // Just to avoid division by zero
             }
             }
 
 
             // Alpha
             // Alpha
-            this.alpha = Math.acos(radiusv3.x / Math.sqrt(Math.pow(radiusv3.x, 2) + Math.pow(radiusv3.z, 2)));
+            this.alpha = Math.acos(this._computationVector.x / Math.sqrt(Math.pow(this._computationVector.x, 2) + Math.pow(this._computationVector.z, 2)));
 
 
-            if (radiusv3.z < 0) {
+            if (this._computationVector.z < 0) {
                 this.alpha = 2 * Math.PI - this.alpha;
                 this.alpha = 2 * Math.PI - this.alpha;
             }
             }
 
 
             // Beta
             // Beta
-            this.beta = Math.acos(radiusv3.y / this.radius);
+            this.beta = Math.acos(this._computationVector.y / this.radius);
 
 
             this._checkLimits();
             this._checkLimits();
         }
         }
@@ -652,7 +654,8 @@
             }
             }
 
 
             var target = this._getTargetPosition();
             var target = this._getTargetPosition();
-            target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);
+            this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+            target.addToRef(this._computationVector, this._newPosition);
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
             if (this.getScene().collisionsEnabled && this.checkCollisions) {
                 if (!this._collider) {
                 if (!this._collider) {
                     this._collider = new Collider();
                     this._collider = new Collider();
@@ -709,7 +712,8 @@
             }
             }
 
 
             var target = this._getTargetPosition();
             var target = this._getTargetPosition();
-            target.addToRef(new Vector3(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb), this._newPosition);
+            this._computationVector.copyFromFloats(this.radius * cosa * sinb, this.radius * cosb, this.radius * sina * sinb);
+            target.addToRef(this._computationVector, this._newPosition);
             this.position.copyFrom(this._newPosition);
             this.position.copyFrom(this._newPosition);
 
 
             var up = this.upVector;
             var up = this.upVector;

+ 7 - 2
src/Cameras/babylon.camera.ts

@@ -589,7 +589,12 @@
             return new Ray(origin, direction, length);
             return new Ray(origin, direction, length);
         }
         }
 
 
-        public dispose(): void {
+        /**
+         * Releases resources associated with this node.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
+         */
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             // Observables
             // Observables
             this.onViewMatrixChangedObservable.clear();
             this.onViewMatrixChangedObservable.clear();
             this.onProjectionMatrixChangedObservable.clear();
             this.onProjectionMatrixChangedObservable.clear();
@@ -639,7 +644,7 @@
             // Active Meshes
             // Active Meshes
             this._activeMeshes.dispose();
             this._activeMeshes.dispose();
 
 
-            super.dispose();
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
 
 
         // ---- Camera rigs section ----
         // ---- Camera rigs section ----

+ 2 - 1
src/Engine/babylon.engine.ts

@@ -5580,7 +5580,7 @@
                 if (this._renderingCanvas) {
                 if (this._renderingCanvas) {
                     this._renderingCanvas.removeEventListener("focus", this._onCanvasFocus);
                     this._renderingCanvas.removeEventListener("focus", this._onCanvasFocus);
                     this._renderingCanvas.removeEventListener("blur", this._onCanvasBlur);
                     this._renderingCanvas.removeEventListener("blur", this._onCanvasBlur);
-                    this._renderingCanvas.removeEventListener("pointerout", this._onCanvasBlur);
+                    this._renderingCanvas.removeEventListener("pointerout", this._onCanvasPointerOut);
 
 
                     if (!this._doNotHandleContextLost) {
                     if (!this._doNotHandleContextLost) {
                         this._renderingCanvas.removeEventListener("webglcontextlost", this._onContextLost);
                         this._renderingCanvas.removeEventListener("webglcontextlost", this._onContextLost);
@@ -5622,6 +5622,7 @@
             this._currentBufferPointers = [];
             this._currentBufferPointers = [];
             this._renderingCanvas = null;
             this._renderingCanvas = null;
             this._currentProgram = null;
             this._currentProgram = null;
+            this._bindedRenderFunction = null;
 
 
             this.onResizeObservable.clear();
             this.onResizeObservable.clear();
             this.onCanvasBlurObservable.clear();
             this.onCanvasBlurObservable.clear();

+ 13 - 2
src/Helpers/babylon.environmentHelper.ts

@@ -310,6 +310,12 @@ module BABYLON {
         private _options: IEnvironmentHelperOptions;
         private _options: IEnvironmentHelperOptions;
 
 
         /**
         /**
+         * This observable will be notified with any error during the creation of the environment, 
+         * mainly texture creation errors.
+         */
+        public onErrorObservable: Observable<{ message?: string, exception?: any }>;
+
+        /**
          * constructor
          * constructor
          * @param options 
          * @param options 
          * @param scene The scene to add the material to
          * @param scene The scene to add the material to
@@ -320,6 +326,7 @@ module BABYLON {
                 ...options
                 ...options
             }
             }
             this._scene = scene;
             this._scene = scene;
+            this.onErrorObservable = new Observable();
 
 
             this._setupBackground();
             this._setupBackground();
             this._setupImageProcessing();
             this._setupImageProcessing();
@@ -557,7 +564,7 @@ module BABYLON {
                 return;
                 return;
             }
             }
 
 
-            const diffuseTexture = new Texture(this._options.groundTexture, this._scene);
+            const diffuseTexture = new Texture(this._options.groundTexture, this._scene, undefined, undefined, undefined, undefined, this._errorHandler);
             diffuseTexture.gammaSpace = false;
             diffuseTexture.gammaSpace = false;
             diffuseTexture.hasAlpha = true;
             diffuseTexture.hasAlpha = true;
             this._groundMaterial.diffuseTexture = diffuseTexture;
             this._groundMaterial.diffuseTexture = diffuseTexture;
@@ -664,12 +671,16 @@ module BABYLON {
                 return;
                 return;
             }
             }
 
 
-            this._skyboxTexture = new CubeTexture(this._options.skyboxTexture, this._scene);
+            this._skyboxTexture = new CubeTexture(this._options.skyboxTexture, this._scene, undefined, undefined, undefined, undefined, this._errorHandler);
             this._skyboxTexture.coordinatesMode = Texture.SKYBOX_MODE;
             this._skyboxTexture.coordinatesMode = Texture.SKYBOX_MODE;
             this._skyboxTexture.gammaSpace = false;
             this._skyboxTexture.gammaSpace = false;
             this._skyboxMaterial.reflectionTexture = this._skyboxTexture;
             this._skyboxMaterial.reflectionTexture = this._skyboxTexture;
         }
         }
 
 
+        private _errorHandler = (message?: string, exception?: any) => {
+            this.onErrorObservable.notifyObservers({ message: message, exception: exception });
+        }
+
         /**
         /**
          * Dispose all the elements created by the Helper.
          * Dispose all the elements created by the Helper.
          */
          */

+ 5 - 4
src/Helpers/babylon.videoDome.ts

@@ -92,15 +92,16 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
-         * Releases all associated resources
+         * Releases resources associated with this node.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
          */
-        public dispose(): void {
-            super.dispose();
-
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             this._videoTexture.dispose();
             this._videoTexture.dispose();
             this._mesh.dispose();
             this._mesh.dispose();
             this._material.dispose();
             this._material.dispose();
 
 
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
     }
     }
 }
 }

+ 8 - 6
src/Lights/Shadows/babylon.shadowGenerator.ts

@@ -540,8 +540,8 @@
         private _viewMatrix = Matrix.Zero();
         private _viewMatrix = Matrix.Zero();
         private _projectionMatrix = Matrix.Zero();
         private _projectionMatrix = Matrix.Zero();
         private _transformMatrix = Matrix.Zero();
         private _transformMatrix = Matrix.Zero();
-        private _cachedPosition: Vector3;
-        private _cachedDirection: Vector3;
+        private _cachedPosition: Vector3 = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
+        private _cachedDirection: Vector3 = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
         private _cachedDefines: string;
         private _cachedDefines: string;
         private _currentRenderID: number;
         private _currentRenderID: number;
         private _boxBlurPostprocess: Nullable<PostProcess>;
         private _boxBlurPostprocess: Nullable<PostProcess>;
@@ -633,12 +633,14 @@
             });
             });
 
 
             // Clear according to the chosen filter.
             // Clear according to the chosen filter.
+            let zero = new Color4(0, 0, 0, 0);
+            let one = new Color4(1.0, 1.0, 1.0, 1.0);
             this._shadowMap.onClearObservable.add((engine: Engine) => {
             this._shadowMap.onClearObservable.add((engine: Engine) => {
                 if (this.useExponentialShadowMap || this.useBlurExponentialShadowMap) {
                 if (this.useExponentialShadowMap || this.useBlurExponentialShadowMap) {
-                    engine.clear(new Color4(0, 0, 0, 0), true, true, true);
+                    engine.clear(zero, true, true, true);
                 }
                 }
                 else {
                 else {
-                    engine.clear(new Color4(1.0, 1.0, 1.0, 1.0), true, true, true);
+                    engine.clear(one, true, true, true);
                 }
                 }
             });
             });
         }
         }
@@ -1050,8 +1052,8 @@
 
 
             if (this._light.needProjectionMatrixCompute() || !this._cachedPosition || !this._cachedDirection || !lightPosition.equals(this._cachedPosition) || !this._lightDirection.equals(this._cachedDirection)) {
             if (this._light.needProjectionMatrixCompute() || !this._cachedPosition || !this._cachedDirection || !lightPosition.equals(this._cachedPosition) || !this._lightDirection.equals(this._cachedDirection)) {
 
 
-                this._cachedPosition = lightPosition.clone();
-                this._cachedDirection = this._lightDirection.clone();
+                this._cachedPosition.copyFrom(lightPosition);
+                this._cachedDirection.copyFrom(this._lightDirection);
 
 
                 Matrix.LookAtLHToRef(lightPosition, lightPosition.add(this._lightDirection), Vector3.Up(), this._viewMatrix);
                 Matrix.LookAtLHToRef(lightPosition, lightPosition.add(this._lightDirection), Vector3.Up(), this._viewMatrix);
 
 

+ 5 - 3
src/Lights/babylon.light.ts

@@ -467,9 +467,11 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
-         * Disposes the light.  
+         * Releases resources associated with this node.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
          */
-        public dispose(): void {
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             if (this._shadowGenerator) {
             if (this._shadowGenerator) {
                 this._shadowGenerator.dispose();
                 this._shadowGenerator.dispose();
                 this._shadowGenerator = null;
                 this._shadowGenerator = null;
@@ -487,7 +489,7 @@ module BABYLON {
 
 
             // Remove from scene
             // Remove from scene
             this.getScene().removeLight(this);
             this.getScene().removeLight(this);
-            super.dispose();
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
 
 
         /**
         /**

+ 9 - 0
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -759,5 +759,14 @@
                 this._postProcessManager._rebuild();
                 this._postProcessManager._rebuild();
             }
             }
         }
         }
+
+        /**
+         * Clear the info related to rendering groups preventing retention point in material dispose.
+         */
+        public freeRenderingGroups(): void {
+            if (this._renderingManager) {
+                this._renderingManager.freeRenderingGroups();
+            }
+        }
     }
     }
 }
 }

+ 53 - 0
src/Materials/babylon.effect.ts

@@ -288,6 +288,11 @@
 
 
             this.uniqueId = Effect._uniqueIdSeed++;
             this.uniqueId = Effect._uniqueIdSeed++;
 
 
+            if (this._getFromCache(baseName)) {
+                this._prepareEffect();
+                return;
+            }
+
             var vertexSource: any;
             var vertexSource: any;
             var fragmentSource: any;
             var fragmentSource: any;
 
 
@@ -336,10 +341,58 @@
                     this._vertexSourceCode = finalVertexCode;
                     this._vertexSourceCode = finalVertexCode;
                     this._fragmentSourceCode = migratedFragmentCode;
                     this._fragmentSourceCode = migratedFragmentCode;
                 }
                 }
+
+                this._setInCache(baseName);
                 this._prepareEffect(); 
                 this._prepareEffect(); 
             });
             });
         }
         }
 
 
+        private static _sourceCache: { [baseName: string]: { vertex: string, fragment: string } } = { };
+
+        private _getSourceCacheKey(baseName: string): string {
+            let cacheKey: string = baseName;
+            if (this._indexParameters) {
+                for (let key in this._indexParameters) {
+                    if (this._indexParameters.hasOwnProperty(key)) {
+                        cacheKey += "|";
+                        cacheKey += key
+                        cacheKey += "_";
+                        cacheKey += this._indexParameters[key];
+                    }
+                }
+            }
+
+            return cacheKey;
+        }
+
+        private _getFromCache(baseName: string): boolean {
+            if (typeof baseName !== "string") {
+                return false;
+            }
+
+            let cacheKey = this._getSourceCacheKey(baseName);
+            let sources = Effect._sourceCache[cacheKey];
+            if (!sources) {
+                return false;
+            }
+
+            this._vertexSourceCode = sources.vertex;
+            this._fragmentSourceCode = sources.fragment;
+            return true;
+        }
+
+        private _setInCache(baseName: string): void {
+            if (typeof baseName !== "string") {
+                return;
+            }
+
+            let cacheKey = this._getSourceCacheKey(baseName);
+            Effect._sourceCache[cacheKey] = {
+                vertex: this._vertexSourceCode,
+                fragment: this._fragmentSourceCode
+            };
+        }
+
         /**
         /**
          * Unique key for this effect
          * Unique key for this effect
          */
          */

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

@@ -1212,6 +1212,7 @@
         public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
         public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void {
             // Animations
             // Animations
             this.getScene().stopAnimation(this);
             this.getScene().stopAnimation(this);
+            this.getScene().freeProcessedMaterials();
 
 
             // Remove from scene
             // Remove from scene
             var index = this._scene.materials.indexOf(this);
             var index = this._scene.materials.indexOf(this);

+ 19 - 3
src/Math/babylon.math.ts

@@ -4369,17 +4369,33 @@
                 0.0, 0.0, 1.0, 0.0,
                 0.0, 0.0, 1.0, 0.0,
                 x, y, z, 1.0, result);
                 x, y, z, 1.0, result);
         }
         }
+
         /**
         /**
-         * Returns a new Matrix whose values are the interpolated values for "gradien" (float) between the ones of the matrices "startValue" and "endValue".
+         * Returns a new Matrix whose values are the interpolated values for "gradient" (float) between the ones of the matrices "startValue" and "endValue".
+         * @param startValue defines the start value
+         * @param endValue defines the end value
+         * @param gradient defines the gradient factor
+         * @returns the new matrix
          */
          */
         public static Lerp(startValue: Matrix, endValue: Matrix, gradient: number): Matrix {
         public static Lerp(startValue: Matrix, endValue: Matrix, gradient: number): Matrix {
             var result = Matrix.Zero();
             var result = Matrix.Zero();
+            Matrix.LerpToRef(startValue, endValue, gradient, result);
+            return result;
+        }
+
+        /**
+         * Set the passed matrix "result" as the interpolated values for "gradient" (float) between the ones of the matrices "startValue" and "endValue".
+         * @param startValue defines the start value
+         * @param endValue defines the end value
+         * @param gradient defines the gradient factor
+         * @param result defines the Matrix object where to store data
+         */
+        public static LerpToRef(startValue: Matrix, endValue: Matrix, gradient: number, result: Matrix): void {
             for (var index = 0; index < 16; index++) {
             for (var index = 0; index < 16; index++) {
                 result.m[index] = startValue.m[index] * (1.0 - gradient) + endValue.m[index] * gradient;
                 result.m[index] = startValue.m[index] * (1.0 - gradient) + endValue.m[index] * gradient;
             }
             }
             result._markAsUpdated();
             result._markAsUpdated();
-            return result;
-        }
+        }        
 
 
         /**
         /**
          * Returns a new Matrix whose values are computed by : 
          * Returns a new Matrix whose values are computed by : 

+ 4 - 0
src/Mesh/Compression/babylon.dracoCompression.ts

@@ -188,6 +188,10 @@ module BABYLON {
         }
         }
 
 
         private static _GetDefaultDecoderUrl(): Nullable<string> {
         private static _GetDefaultDecoderUrl(): Nullable<string> {
+            if (!Tools.IsWindowObjectExist) {
+                return null;
+            }
+
             for (let i = 0; i < document.scripts.length; i++) {
             for (let i = 0; i < document.scripts.length; i++) {
                 if (document.scripts[i].type === "text/x-draco-decoder") {
                 if (document.scripts[i].type === "text/x-draco-decoder") {
                     return document.scripts[i].src;
                     return document.scripts[i].src;

+ 9 - 5
src/Mesh/babylon.abstractMesh.ts

@@ -1326,13 +1326,17 @@
         }
         }
 
 
         /**
         /**
-         * Disposes the AbstractMesh.  
-         * By default, all the mesh children are also disposed unless the parameter `doNotRecurse` is set to `true`.  
-         * Returns nothing.  
+         * Releases resources associated with this abstract mesh.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
          */
-        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures: boolean = false): void {
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             var index: number;
             var index: number;
 
 
+            // Smart Array Retainers.
+            this.getScene().freeActiveMeshes();
+            this.getScene().freeRenderingGroups();
+
             // Action manager
             // Action manager
             if (this.actionManager !== undefined && this.actionManager !== null) {
             if (this.actionManager !== undefined && this.actionManager !== null) {
                 this.actionManager.dispose();
                 this.actionManager.dispose();
@@ -1448,7 +1452,7 @@
             this.onCollideObservable.clear();
             this.onCollideObservable.clear();
             this.onCollisionPositionChangeObservable.clear();
             this.onCollisionPositionChangeObservable.clear();
 
 
-            super.dispose(doNotRecurse);
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
 
 
         /**
         /**

+ 2 - 2
src/Mesh/babylon.instancedMesh.ts

@@ -282,13 +282,13 @@
          * Disposes the InstancedMesh.  
          * Disposes the InstancedMesh.  
          * Returns nothing.  
          * Returns nothing.  
          */
          */
-        public dispose(doNotRecurse?: boolean): void {
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
 
 
             // Remove from mesh
             // Remove from mesh
             var index = this._sourceMesh.instances.indexOf(this);
             var index = this._sourceMesh.instances.indexOf(this);
             this._sourceMesh.instances.splice(index, 1);
             this._sourceMesh.instances.splice(index, 1);
 
 
-            super.dispose(doNotRecurse);
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
     }
     }
 } 
 } 

+ 4 - 4
src/Mesh/babylon.mesh.ts

@@ -1714,11 +1714,11 @@
         }
         }
 
 
         /**
         /**
-         * Disposes the Mesh.  
-         * By default, all the mesh children are also disposed unless the parameter `doNotRecurse` is set to `true`.  
-         * Returns nothing.  
+         * Releases resources associated with this mesh.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
          */
-        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures: boolean = false): void {
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             this.morphTargetManager = null;
             this.morphTargetManager = null;
 
 
             if (this._geometry) {
             if (this._geometry) {

+ 13 - 20
src/Mesh/babylon.transformNode.ts

@@ -58,6 +58,14 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
+         * Gets a string idenfifying the name of the class
+         * @returns "TransformNode" string
+         */
+        public getClassName(): string {
+            return "TransformNode";
+        }
+
+        /**
           * Rotation property : a Vector3 depicting the rotation value in radians around each local axis X, Y, Z. 
           * Rotation property : a Vector3 depicting the rotation value in radians around each local axis X, Y, Z. 
           * If rotation quaternion is set, this Vector3 will (almost always) be the Zero vector!
           * If rotation quaternion is set, this Vector3 will (almost always) be the Zero vector!
           * Default : (0.0, 0.0, 0.0)
           * Default : (0.0, 0.0, 0.0)
@@ -965,35 +973,20 @@ module BABYLON {
         }
         }
 
 
         /**
         /**
-         * Disposes the TransformNode.  
-         * By default, all the children are also disposed unless the parameter `doNotRecurse` is set to `true`.  
-         * Returns nothing.  
+         * Releases resources associated with this transform node.
+         * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
+         * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
          */
          */
-        public dispose(doNotRecurse?: boolean): void {
+        public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
             // Animations
             // Animations
             this.getScene().stopAnimation(this);
             this.getScene().stopAnimation(this);
 
 
             // Remove from scene
             // Remove from scene
             this.getScene().removeTransformNode(this);
             this.getScene().removeTransformNode(this);
 
 
-            if (!doNotRecurse) {
-                // Children
-                var objects = this.getDescendants(true);
-                for (var index = 0; index < objects.length; index++) {
-                    objects[index].dispose();
-                }
-            } else {
-                var childMeshes = this.getChildMeshes(true);
-                for (index = 0; index < childMeshes.length; index++) {
-                    var child = childMeshes[index];
-                    child.parent = null;
-                    child.computeWorldMatrix(true);
-                }
-            }
-
             this.onAfterWorldMatrixUpdateObservable.clear();
             this.onAfterWorldMatrixUpdateObservable.clear();
 
 
-            super.dispose();
+            super.dispose(doNotRecurse, disposeMaterialAndTextures);
         }
         }
 
 
     }
     }

+ 16 - 3
src/Particles/babylon.gpuParticleSystem.ts

@@ -241,6 +241,12 @@
         }
         }
 
 
         /**
         /**
+         * Forces the particle to write their depth information to the depth buffer. This can help preventing other draw calls
+         * to override the particles.
+         */
+        public forceDepthWrite = false;        
+
+        /**
          * Gets or set the number of active particles
          * Gets or set the number of active particles
          */
          */
         public get activeParticleCount(): number {
         public get activeParticleCount(): number {
@@ -323,7 +329,7 @@
             this._updateEffectOptions = {
             this._updateEffectOptions = {
                 attributes: ["position", "age", "life", "seed", "size", "color", "direction"],
                 attributes: ["position", "age", "life", "seed", "size", "color", "direction"],
                 uniformsNames: ["currentCount", "timeDelta", "generalRandoms", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "gravity", "emitPower",
                 uniformsNames: ["currentCount", "timeDelta", "generalRandoms", "emitterWM", "lifeTime", "color1", "color2", "sizeRange", "gravity", "emitPower",
-                                "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "angle"],
+                                "direction1", "direction2", "minEmitBox", "maxEmitBox", "radius", "directionRandomizer", "height", "angle", "stopFactor"],
                 uniformBuffersNames: [],
                 uniformBuffersNames: [],
                 samplers:["randomSampler"],
                 samplers:["randomSampler"],
                 defines: "",
                 defines: "",
@@ -484,8 +490,6 @@
 
 
                 if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration)
                 if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration)
                     this.stop();
                     this.stop();
-            } else {
-                this._timeDelta = 0;
             }             
             }             
         }        
         }        
 
 
@@ -523,6 +527,7 @@
             
             
             this._updateEffect.setFloat("currentCount", this._currentActiveCount);
             this._updateEffect.setFloat("currentCount", this._currentActiveCount);
             this._updateEffect.setFloat("timeDelta", this._timeDelta);
             this._updateEffect.setFloat("timeDelta", this._timeDelta);
+            this._updateEffect.setFloat("stopFactor", this._stopped ? 0 : 1);
             this._updateEffect.setFloat3("generalRandoms", Math.random(), Math.random(), Math.random());
             this._updateEffect.setFloat3("generalRandoms", Math.random(), Math.random(), Math.random());
             this._updateEffect.setTexture("randomSampler", this._randomTexture);
             this._updateEffect.setTexture("randomSampler", this._randomTexture);
             this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
             this._updateEffect.setFloat2("lifeTime", this.minLifeTime, this.maxLifeTime);
@@ -582,6 +587,10 @@
                 this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
                 this._engine.setAlphaMode(Engine.ALPHA_COMBINE);
             }            
             }            
 
 
+            if (this.forceDepthWrite) {
+                this._engine.setDepthWrite(true);
+            }
+
             // Bind source VAO
             // Bind source VAO
             this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
             this._engine.bindVertexArrayObject(this._renderVAO[this._targetIndex], null);
 
 
@@ -626,6 +635,10 @@
         }
         }
 
 
         private _releaseVAOs() {
         private _releaseVAOs() {
+            if (!this._updateVAO) {
+                return;
+            }
+            
             for (var index = 0; index < this._updateVAO.length; index++) {
             for (var index = 0; index < this._updateVAO.length; index++) {
                 this._engine.releaseVertexArrayObject(this._updateVAO[index]);
                 this._engine.releaseVertexArrayObject(this._updateVAO[index]);
             }
             }

+ 109 - 40
src/PostProcess/RenderPipeline/Pipelines/babylon.defaultRenderingPipeline.ts

@@ -5,7 +5,7 @@
      */
      */
     export class DefaultRenderingPipeline extends PostProcessRenderPipeline implements IDisposable, IAnimatable {
     export class DefaultRenderingPipeline extends PostProcessRenderPipeline implements IDisposable, IAnimatable {
         private _scene: Scene;
         private _scene: Scene;
-
+        private _originalCameras:Array<Camera> = [];
         /**
         /**
 		 * ID of the sharpen post process,
 		 * ID of the sharpen post process,
 		 */
 		 */
@@ -52,6 +52,7 @@
 		 * Sharpen post process which will apply a sharpen convolution to enhance edges
 		 * Sharpen post process which will apply a sharpen convolution to enhance edges
 		 */
 		 */
         public sharpen: SharpenPostProcess;
         public sharpen: SharpenPostProcess;
+        private _sharpenEffect: PostProcessRenderEffect;
         /**
         /**
 		 * First pass of bloom to capture the original image texture for later use.
 		 * First pass of bloom to capture the original image texture for later use.
 		 */
 		 */
@@ -92,6 +93,7 @@
 		 * Chromatic aberration post process which will shift rgb colors in the image
 		 * Chromatic aberration post process which will shift rgb colors in the image
 		 */
 		 */
         public chromaticAberration: ChromaticAberrationPostProcess;
         public chromaticAberration: ChromaticAberrationPostProcess;
+        private _chromaticAberrationEffect: PostProcessRenderEffect;
 
 
         /**
         /**
          * Animations which can be used to tweak settings over a period of time
          * Animations which can be used to tweak settings over a period of time
@@ -228,6 +230,20 @@
                 return;
                 return;
             }
             }
             this._depthOfFieldBlurLevel = value;
             this._depthOfFieldBlurLevel = value;
+            
+            // recreate dof and dispose old as this setting is not dynamic
+            var oldDof = this.depthOfField;
+            
+            this.depthOfField = new DepthOfFieldEffect(this._scene, null, this._depthOfFieldBlurLevel, this._defaultPipelineTextureType);
+            this.depthOfField.focalLength = oldDof.focalLength;
+            this.depthOfField.focusDistance = oldDof.focusDistance;
+            this.depthOfField.fStop = oldDof.fStop;
+            this.depthOfField.lensSize = oldDof.lensSize;
+            
+            for (var i = 0; i < this._cameras.length; i++) {
+                oldDof.disposeEffects(this._cameras[i]);
+            }
+
             this._buildPipeline();
             this._buildPipeline();
         }
         }
 
 
@@ -310,6 +326,7 @@
         constructor(name: string, hdr: boolean, scene: Scene, cameras?: Camera[], automaticBuild = true) {
         constructor(name: string, hdr: boolean, scene: Scene, cameras?: Camera[], automaticBuild = true) {
             super(scene.getEngine(), name);
             super(scene.getEngine(), name);
             this._cameras = cameras ||  [];
             this._cameras = cameras ||  [];
+            this._originalCameras = this._cameras.slice();
 
 
             this._buildAllowed = automaticBuild;
             this._buildAllowed = automaticBuild;
 
 
@@ -333,6 +350,17 @@
             // Attach
             // Attach
             scene.postProcessRenderPipelineManager.addPipeline(this);
             scene.postProcessRenderPipelineManager.addPipeline(this);
 
 
+            var engine = this._scene.getEngine();
+            // Create post processes before hand so they can be modified before enabled.
+            // Block compilation flag is set to true to avoid compilation prior to use, these will be updated on first use in build pipeline.
+            this.sharpen = new SharpenPostProcess("sharpen", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType, true);
+            this._sharpenEffect = new PostProcessRenderEffect(engine, this.SharpenPostProcessId, () => { return this.sharpen; }, true);
+
+            this.depthOfField = new DepthOfFieldEffect(this._scene, null, this._depthOfFieldBlurLevel, this._defaultPipelineTextureType, true);
+            
+            this.chromaticAberration = new ChromaticAberrationPostProcess("ChromaticAberration", engine.getRenderWidth(), engine.getRenderHeight(), 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType, true);
+            this._chromaticAberrationEffect = new PostProcessRenderEffect(engine, this.ChromaticAberrationPostProcessId, () => { return this.chromaticAberration; }, true);
+            
             this._buildPipeline();
             this._buildPipeline();
         }
         }
 
 
@@ -346,6 +374,30 @@
             this._buildAllowed = previousState;
             this._buildAllowed = previousState;
         }
         }
 
 
+        private _prevPostProcess:Nullable<PostProcess> = null;
+        private _prevPrevPostProcess:Nullable<PostProcess> = null;
+
+        private _setAutoClearAndTextureSharing(postProcess:PostProcess, skipTextureSharing = false){
+            if(this._prevPostProcess && this._prevPostProcess.autoClear){
+                postProcess.autoClear = false;
+            }else{
+                postProcess.autoClear = true;
+            }
+
+            if(!skipTextureSharing){
+                if(this._prevPrevPostProcess){
+                    postProcess.shareOutputWith(this._prevPrevPostProcess);
+                }else{
+                    postProcess.useOwnOutput();
+                }
+
+                if(this._prevPostProcess){
+                    this._prevPrevPostProcess = this._prevPostProcess;
+                }
+                this._prevPostProcess = postProcess;
+            }
+        }
+
         private _buildPipeline() {
         private _buildPipeline() {
             if (!this._buildAllowed) {
             if (!this._buildAllowed) {
                 return;
                 return;
@@ -354,23 +406,36 @@
             var engine = this._scene.getEngine();
             var engine = this._scene.getEngine();
 
 
             this._disposePostProcesses();
             this._disposePostProcesses();
+            if (this._cameras !== null) {
+                this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._cameras);
+                // get back cameras to be used to reattach pipeline
+                this._cameras = this._originalCameras.slice();
+            }
             this._reset();
             this._reset();
+            this._prevPostProcess = null;
+            this._prevPrevPostProcess = null;
 
 
             if (this.sharpenEnabled) {
             if (this.sharpenEnabled) {
-                this.sharpen = new SharpenPostProcess("sharpen", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
-                this.addEffect(new PostProcessRenderEffect(engine, this.SharpenPostProcessId, () => { return this.sharpen; }, true));
+                if(!this.sharpen.isReady()){
+                    this.sharpen.updateEffect();
+                }
+                this.addEffect(this._sharpenEffect);
+                this._setAutoClearAndTextureSharing(this.sharpen);
             }
             }
 
 
-            if(this.depthOfFieldEnabled){
-                // Enable and get current depth map
+            if (this.depthOfFieldEnabled) {
                 var depthTexture = this._scene.enableDepthRenderer(this._cameras[0]).getDepthMap();
                 var depthTexture = this._scene.enableDepthRenderer(this._cameras[0]).getDepthMap();
-
-                this.depthOfField = new DepthOfFieldEffect(this._scene, depthTexture, this._depthOfFieldBlurLevel, this._defaultPipelineTextureType);
+                this.depthOfField.depthTexture = depthTexture;
+                if(!this.depthOfField._isReady()){
+                    this.depthOfField._updateEffects();
+                }
                 this.addEffect(this.depthOfField);
                 this.addEffect(this.depthOfField);
+                this._setAutoClearAndTextureSharing(this.depthOfField._depthOfFieldMerge);
             }
             }
 
 
             if (this.bloomEnabled) {
             if (this.bloomEnabled) {
                 this.pass = new PassPostProcess("sceneRenderTarget", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
                 this.pass = new PassPostProcess("sceneRenderTarget", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
+                this._setAutoClearAndTextureSharing(this.pass, true);
                 this.addEffect(new PostProcessRenderEffect(engine, this.PassPostProcessId, () => { return this.pass; }, true));
                 this.addEffect(new PostProcessRenderEffect(engine, this.PassPostProcessId, () => { return this.pass; }, true));
 
 
                 if (!this._hdr) { // Need to enhance highlights if not using float rendering
                 if (!this._hdr) { // Need to enhance highlights if not using float rendering
@@ -420,18 +485,14 @@
                 }
                 }
             }
             }
 
 
-            if (this.fxaaEnabled) {
-                this.fxaa = new FxaaPostProcess("fxaa", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
-                this.addEffect(new PostProcessRenderEffect(engine, this.FxaaPostProcessId, () => { return this.fxaa; }, true));
-
-                this.fxaa.autoClear = !this.bloomEnabled && (!this._hdr || !this.imageProcessing);
-            } else if (this._hdr && this.imageProcessing) {
+            if (this._hdr && this.imageProcessing) {
                 this.finalMerge = this.imageProcessing;
                 this.finalMerge = this.imageProcessing;
             }
             }
             else {
             else {
                 this.finalMerge = new PassPostProcess("finalMerge", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
                 this.finalMerge = new PassPostProcess("finalMerge", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
                 this.addEffect(new PostProcessRenderEffect(engine, this.FinalMergePostProcessId, () => { return this.finalMerge; }, true));
                 this.addEffect(new PostProcessRenderEffect(engine, this.FinalMergePostProcessId, () => { return this.finalMerge; }, true));
-
+                this._setAutoClearAndTextureSharing(this.finalMerge, true);
+                
                 this.finalMerge.autoClear = !this.bloomEnabled && (!this._hdr || !this.imageProcessing);
                 this.finalMerge.autoClear = !this.bloomEnabled && (!this._hdr || !this.imageProcessing);
             }
             }
 
 
@@ -441,30 +502,32 @@
                     if (this.imageProcessing) {
                     if (this.imageProcessing) {
                         this.imageProcessing.shareOutputWith(this.pass);
                         this.imageProcessing.shareOutputWith(this.pass);
                         this.imageProcessing.autoClear = false;
                         this.imageProcessing.autoClear = false;
-                    } else if (this.fxaa) {
-                        this.fxaa.shareOutputWith(this.pass);
                     } else {
                     } else {
                         this.finalMerge.shareOutputWith(this.pass);
                         this.finalMerge.shareOutputWith(this.pass);
                     }
                     }
                 } else {
                 } else {
-                    if (this.fxaa) {
-                        this.fxaa.shareOutputWith(this.pass);
-                    } else {
-                        this.finalMerge.shareOutputWith(this.pass);
-                    }
+                    this.finalMerge.shareOutputWith(this.pass);
                 }
                 }
             }
             }
 
 
-            if (this.chromaticAberrationEnabled) {
-                this.chromaticAberration = new ChromaticAberrationPostProcess("ChromaticAberration", engine.getRenderWidth(), engine.getRenderHeight(), 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
-                this.addEffect(new PostProcessRenderEffect(engine, this.ChromaticAberrationPostProcessId, () => { return this.chromaticAberration; }, true));
+            if (this.fxaaEnabled) {
+                this.fxaa = new FxaaPostProcess("fxaa", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, engine, false, this._defaultPipelineTextureType);
+                this.addEffect(new PostProcessRenderEffect(engine, this.FxaaPostProcessId, () => { return this.fxaa; }, true));
+                this._setAutoClearAndTextureSharing(this.fxaa);
             }
             }
 
 
+            if (this.chromaticAberrationEnabled) {
+                if(!this.chromaticAberration.isReady()){
+                    this.chromaticAberration.updateEffect();
+                }
+                this.addEffect(this._chromaticAberrationEffect);
+                this._setAutoClearAndTextureSharing(this.chromaticAberration);
+            }
 
 
             if (this._cameras !== null) {
             if (this._cameras !== null) {
                 this._scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(this._name, this._cameras);
                 this._scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(this._name, this._cameras);
             }
             }
-            
+
             if(this.msaaEnabled){
             if(this.msaaEnabled){
                 if(!this._enableMSAAOnFirstPostProcess()){
                 if(!this._enableMSAAOnFirstPostProcess()){
                     BABYLON.Tools.Warn("MSAA failed to enable, MSAA is only supported in browsers that support webGL >= 2.0");
                     BABYLON.Tools.Warn("MSAA failed to enable, MSAA is only supported in browsers that support webGL >= 2.0");
@@ -472,14 +535,10 @@
             }
             }
         }
         }
 
 
-        private _disposePostProcesses(): void {
+        private _disposePostProcesses(disposeNonRecreated = false): void {
             for (var i = 0; i < this._cameras.length; i++) {
             for (var i = 0; i < this._cameras.length; i++) {
                 var camera = this._cameras[i];
                 var camera = this._cameras[i];
 
 
-                if (this.sharpen) {
-                    this.sharpen.dispose(camera);
-                }
-
                 if (this.pass) {
                 if (this.pass) {
                     this.pass.dispose(camera);
                     this.pass.dispose(camera);
                 }
                 }
@@ -512,16 +571,22 @@
                     this.finalMerge.dispose(camera);
                     this.finalMerge.dispose(camera);
                 }
                 }
 
 
-                if(this.depthOfField){
-                    this.depthOfField.disposeEffects(camera);
-                }
-
-                if(this.chromaticAberration){
-                    this.chromaticAberration.dispose(camera);
+                // These are created in the constructor and should not be disposed on every pipeline change
+                if(disposeNonRecreated){
+                    if (this.sharpen) {
+                        this.sharpen.dispose(camera);
+                    }
+    
+                    if(this.depthOfField){
+                        this.depthOfField.disposeEffects(camera);
+                    }
+    
+                    if(this.chromaticAberration){
+                        this.chromaticAberration.dispose(camera);
+                    }
                 }
                 }
             }
             }
 
 
-            (<any>this.sharpen) = null;
             (<any>this.pass) = null;
             (<any>this.pass) = null;
             (<any>this.highlights) = null;
             (<any>this.highlights) = null;
             (<any>this.blurX) = null;
             (<any>this.blurX) = null;
@@ -530,15 +595,19 @@
             (<any>this.imageProcessing) = null;
             (<any>this.imageProcessing) = null;
             (<any>this.fxaa) = null;
             (<any>this.fxaa) = null;
             (<any>this.finalMerge) = null;
             (<any>this.finalMerge) = null;
-            (<any>this.depthOfField) = null;
-            (<any>this.chromaticAberration) = null;
+
+            if(disposeNonRecreated){
+                (<any>this.sharpen) = null;
+                (<any>this.depthOfField) = null;
+                (<any>this.chromaticAberration) = null;
+            } 
         }
         }
 
 
         /**
         /**
          * Dispose of the pipeline and stop all post processes
          * Dispose of the pipeline and stop all post processes
          */
          */
         public dispose(): void {
         public dispose(): void {
-            this._disposePostProcesses();
+            this._disposePostProcesses(true);
 
 
             this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._cameras);
             this._scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(this._name, this._cameras);
 
 

+ 25 - 6
src/PostProcess/babylon.blurPostProcess.ts

@@ -19,7 +19,9 @@
 			v = Math.max(v, 1);
 			v = Math.max(v, 1);
 			this._idealKernel = v;
 			this._idealKernel = v;
 			this._kernel = this._nearestBestKernel(v);
 			this._kernel = this._nearestBestKernel(v);
-            this._updateParameters();
+			if(!this.blockCompilation){
+				this._updateParameters();
+			}
 		}
 		}
 
 
 		/**
 		/**
@@ -37,7 +39,9 @@
 				return;
 				return;
 			}
 			}
 			this._packedFloat = v;
 			this._packedFloat = v;
-            this._updateParameters();
+            if(!this.blockCompilation){
+				this._updateParameters();
+			}
 		}
 		}
 
 
 		/**
 		/**
@@ -58,8 +62,9 @@
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param textureType Type of textures used when performing the post process. (default: 0)
          * @param textureType Type of textures used when performing the post process. (default: 0)
+		 * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(name: string, /** The direction in which to blur the image. */ public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, defines = "") {
+        constructor(name: string, /** The direction in which to blur the image. */ public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, defines = "", private blockCompilation = false) {
 			super(name, "kernelBlur", ["delta", "direction", "cameraMinMaxZ"], ["circleOfConfusionSampler"], options, camera, samplingMode, engine, reusable, null, textureType, "kernelBlur", {varyingCount: 0, depCount: 0}, true);
 			super(name, "kernelBlur", ["delta", "direction", "cameraMinMaxZ"], ["circleOfConfusionSampler"], options, camera, samplingMode, engine, reusable, null, textureType, "kernelBlur", {varyingCount: 0, depCount: 0}, true);
 			this._staticDefines = defines;
 			this._staticDefines = defines;
 			this.onApplyObservable.add((effect: Effect) => {
 			this.onApplyObservable.add((effect: Effect) => {
@@ -69,7 +74,21 @@
             this.kernel = kernel;
             this.kernel = kernel;
         }
         }
 
 
-        protected _updateParameters(): void {
+		/**
+         * Updates the effect with the current post process compile time values and recompiles the shader.
+         * @param defines Define statements that should be added at the beginning of the shader. (default: null)
+         * @param uniforms Set of uniform variables that will be passed to the shader. (default: null)
+         * @param samplers Set of Texture2D variables that will be passed to the shader. (default: null)
+         * @param indexParameters The index parameters to be used for babylons include syntax "#include<kernelBlurVaryingDeclaration>[0..varyingCount]". (default: undefined) See usage in babylon.blurPostProcess.ts and kernelBlur.vertex.fx
+         * @param onCompiled Called when the shader has been compiled.
+         * @param onError Called if there is an error when compiling a shader.
+         */
+        public updateEffect(defines: Nullable<string> = null, uniforms: Nullable<string[]> = null, samplers: Nullable<string[]> = null, indexParameters?: any,
+            onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
+            this._updateParameters(onCompiled, onError);
+        }
+
+        protected _updateParameters(onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void): void {
             // Generate sampling offsets and weights
             // Generate sampling offsets and weights
 			let N = this._kernel;
 			let N = this._kernel;
 			let centerIndex = (N - 1) / 2;
 			let centerIndex = (N - 1) / 2;
@@ -155,10 +174,10 @@
 				defines += `#define PACKEDFLOAT 1`;
 				defines += `#define PACKEDFLOAT 1`;
 			}
 			}
 
 
-            this.updateEffect(defines, null, null, {
+            super.updateEffect(defines, null, null, {
 				varyingCount: varyingCount,
 				varyingCount: varyingCount,
 				depCount: depCount
 				depCount: depCount
-			});
+			}, onCompiled, onError);
         }
         }
 
 
         /**
         /**

+ 3 - 2
src/PostProcess/babylon.chromaticAberrationPostProcess.ts

@@ -34,9 +34,10 @@ module BABYLON {
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param textureType Type of textures used when performing the post process. (default: 0)
          * @param textureType Type of textures used when performing the post process. (default: 0)
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(name: string, screenWidth:number, screenHeight:number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, "chromaticAberration", ["chromatic_aberration", "screen_width", "screen_height", "direction", "radialIntensity", "centerPosition"], [], options, camera, samplingMode, engine, reusable, null, textureType);
+        constructor(name: string, screenWidth:number, screenHeight:number, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+            super(name, "chromaticAberration", ["chromatic_aberration", "screen_width", "screen_height", "direction", "radialIntensity", "centerPosition"], [], options, camera, samplingMode, engine, reusable, null, textureType, undefined, null, blockCompilation);
             this.onApplyObservable.add((effect: Effect) => {
             this.onApplyObservable.add((effect: Effect) => {
                 effect.setFloat('chromatic_aberration', this.aberrationAmount);
                 effect.setFloat('chromatic_aberration', this.aberrationAmount);
                 effect.setFloat('screen_width', screenWidth);
                 effect.setFloat('screen_width', screenWidth);

+ 23 - 9
src/PostProcess/babylon.circleOfConfusionPostProcess.ts

@@ -6,35 +6,42 @@ module BABYLON {
         /**
         /**
          * Max lens size in scene units/1000 (eg. millimeter). Standard cameras are 50mm. (default: 50) The diamater of the resulting aperture can be computed by lensSize/fStop.
          * Max lens size in scene units/1000 (eg. millimeter). Standard cameras are 50mm. (default: 50) The diamater of the resulting aperture can be computed by lensSize/fStop.
          */
          */
-        lensSize = 50
+        public lensSize = 50
         /**
         /**
          * F-Stop of the effect's camera. The diamater of the resulting aperture can be computed by lensSize/fStop. (default: 1.4)
          * F-Stop of the effect's camera. The diamater of the resulting aperture can be computed by lensSize/fStop. (default: 1.4)
          */
          */
-        fStop = 1.4;
+        public fStop = 1.4;
         /**
         /**
          * Distance away from the camera to focus on in scene units/1000 (eg. millimeter). (default: 2000)
          * Distance away from the camera to focus on in scene units/1000 (eg. millimeter). (default: 2000)
          */
          */
-        focusDistance = 2000;
+        public focusDistance = 2000;
         /**
         /**
          * Focal length of the effect's camera in scene units/1000 (eg. millimeter). (default: 50)
          * Focal length of the effect's camera in scene units/1000 (eg. millimeter). (default: 50)
          */
          */
-        focalLength = 50;
+        public focalLength = 50;
         
         
+        private _depthTexture:Nullable<RenderTargetTexture> = null;
         /**
         /**
          * Creates a new instance of @see CircleOfConfusionPostProcess
          * Creates a new instance of @see CircleOfConfusionPostProcess
          * @param name The name of the effect.
          * @param name The name of the effect.
-         * @param depthTexture The depth texture of the scene to compute the circle of confusion.
+         * @param depthTexture The depth texture of the scene to compute the circle of confusion. This must be set in order for this to function but may be set after initialization if needed.
          * @param options The required width/height ratio to downsize to before computing the render pass.
          * @param options The required width/height ratio to downsize to before computing the render pass.
          * @param camera The camera to apply the render pass to.
          * @param camera The camera to apply the render pass to.
          * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
          * @param samplingMode The sampling mode to be used when computing the pass. (default: 0)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param textureType Type of textures used when performing the post process. (default: 0)
          * @param textureType Type of textures used when performing the post process. (default: 0)
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(name: string, depthTexture: RenderTargetTexture, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, "circleOfConfusion", ["cameraMinMaxZ", "focusDistance", "cocPrecalculation"], ["depthSampler"], options, camera, samplingMode, engine, reusable, null, textureType);
+        constructor(name: string, depthTexture: Nullable<RenderTargetTexture>, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+            super(name, "circleOfConfusion", ["cameraMinMaxZ", "focusDistance", "cocPrecalculation"], ["depthSampler"], options, camera, samplingMode, engine, reusable, null, textureType, undefined, null, blockCompilation);
+            this._depthTexture = depthTexture;
             this.onApplyObservable.add((effect: Effect) => {
             this.onApplyObservable.add((effect: Effect) => {
-                effect.setTexture("depthSampler", depthTexture);
+                if(!this._depthTexture){
+                    BABYLON.Tools.Warn("No depth texture set on CircleOfConfusionPostProcess")
+                    return;
+                }
+                effect.setTexture("depthSampler", this._depthTexture);
                 
                 
                 // Circle of confusion calculation, See https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch23.html
                 // Circle of confusion calculation, See https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch23.html
                 var aperture = this.lensSize/this.fStop;
                 var aperture = this.lensSize/this.fStop;
@@ -42,8 +49,15 @@ module BABYLON {
                 
                 
                 effect.setFloat('focusDistance', this.focusDistance);
                 effect.setFloat('focusDistance', this.focusDistance);
                 effect.setFloat('cocPrecalculation', cocPrecalculation);
                 effect.setFloat('cocPrecalculation', cocPrecalculation);
-                effect.setFloat2('cameraMinMaxZ', depthTexture.activeCamera!.minZ, depthTexture.activeCamera!.maxZ);
+                effect.setFloat2('cameraMinMaxZ', this._depthTexture.activeCamera!.minZ, this._depthTexture.activeCamera!.maxZ);
             })
             })
         }
         }
+
+        /**
+         * Depth texture to be used to compute the circle of confusion. This must be set here or in the constructor in order for the post process to function.
+         */
+        public set depthTexture(value: RenderTargetTexture){
+            this._depthTexture = value;
+        }
     }
     }
 }
 }

+ 3 - 2
src/PostProcess/babylon.depthOfFieldBlurPostProcess.ts

@@ -20,9 +20,10 @@ module BABYLON {
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param textureType Type of textures used when performing the post process. (default: 0)
          * @param textureType Type of textures used when performing the post process. (default: 0)
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(name: string, scene: Scene, public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, circleOfConfusion:PostProcess, imageToBlur:Nullable<PostProcess> = null, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, direction, kernel, options, camera, samplingMode = Texture.BILINEAR_SAMPLINGMODE, engine, reusable, textureType = Engine.TEXTURETYPE_UNSIGNED_INT, `#define DOF 1\r\n`);
+        constructor(name: string, scene: Scene, public direction: Vector2, kernel: number, options: number | PostProcessOptions, camera: Nullable<Camera>, circleOfConfusion:PostProcess, imageToBlur:Nullable<PostProcess> = null, samplingMode: number = Texture.BILINEAR_SAMPLINGMODE, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+            super(name, direction, kernel, options, camera, samplingMode = Texture.BILINEAR_SAMPLINGMODE, engine, reusable, textureType = Engine.TEXTURETYPE_UNSIGNED_INT, `#define DOF 1\r\n`, blockCompilation);
 			
 			
 			this.onApplyObservable.add((effect: Effect) => {
 			this.onApplyObservable.add((effect: Effect) => {
                 if(imageToBlur != null){
                 if(imageToBlur != null){

+ 87 - 52
src/PostProcess/babylon.depthOfFieldEffect.ts

@@ -24,7 +24,12 @@ module BABYLON {
         private _circleOfConfusion: CircleOfConfusionPostProcess;
         private _circleOfConfusion: CircleOfConfusionPostProcess;
         private _depthOfFieldBlurX: Array<DepthOfFieldBlurPostProcess>;
         private _depthOfFieldBlurX: Array<DepthOfFieldBlurPostProcess>;
         private _depthOfFieldBlurY: Array<DepthOfFieldBlurPostProcess>;
         private _depthOfFieldBlurY: Array<DepthOfFieldBlurPostProcess>;
-        private _depthOfFieldMerge: DepthOfFieldMergePostProcess;
+        /**
+         * Private, last post process of dof
+         */
+        public _depthOfFieldMerge: DepthOfFieldMergePostProcess;
+
+        private _effects: Array<PostProcess> = [];
 
 
         /**
         /**
          * The focal the length of the camera used in the effect
          * The focal the length of the camera used in the effect
@@ -66,64 +71,72 @@ module BABYLON {
         /**
         /**
          * Creates a new instance of @see DepthOfFieldEffect
          * Creates a new instance of @see DepthOfFieldEffect
          * @param scene The scene the effect belongs to.
          * @param scene The scene the effect belongs to.
-         * @param depthTexture The depth texture of the scene to compute the circle of confusion.
+         * @param depthTexture The depth texture of the scene to compute the circle of confusion.This must be set in order for this to function but may be set after initialization if needed.
          * @param pipelineTextureType The type of texture to be used when performing the post processing.
          * @param pipelineTextureType The type of texture to be used when performing the post processing.
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(scene: Scene, depthTexture: RenderTargetTexture, blurLevel: DepthOfFieldEffectBlurLevel = DepthOfFieldEffectBlurLevel.Low, pipelineTextureType = 0) {
+        constructor(scene: Scene, depthTexture: Nullable<RenderTargetTexture>, blurLevel: DepthOfFieldEffectBlurLevel = DepthOfFieldEffectBlurLevel.Low, pipelineTextureType = 0, blockCompilation = false) {
             super(scene.getEngine(), "depth of field", ()=>{
             super(scene.getEngine(), "depth of field", ()=>{
-                // Circle of confusion value for each pixel is used to determine how much to blur that pixel
-                this._circleOfConfusion = new BABYLON.CircleOfConfusionPostProcess("circleOfConfusion", depthTexture, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-                // Capture circle of confusion texture
-                this._depthOfFieldPass = new PassPostProcess("depthOfFieldPass", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-                this._depthOfFieldPass.autoClear = false;
+                return this._effects;
+            }, true);
+            // Circle of confusion value for each pixel is used to determine how much to blur that pixel
+            this._circleOfConfusion = new BABYLON.CircleOfConfusionPostProcess("circleOfConfusion", depthTexture, 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType, blockCompilation);
+            // Capture circle of confusion texture
+            this._depthOfFieldPass = new PassPostProcess("depthOfFieldPass", 1.0, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType, blockCompilation);
+            this._depthOfFieldPass.autoClear = false;
 
 
-                // Create a pyramid of blurred images (eg. fullSize 1/4 blur, half size 1/2 blur, quarter size 3/4 blur, eith size 4/4 blur)
-                // Blur the image but do not blur on sharp far to near distance changes to avoid bleeding artifacts 
-                // See section 2.6.2 http://fileadmin.cs.lth.se/cs/education/edan35/lectures/12dof.pdf
-                this._depthOfFieldBlurY = []
-                this._depthOfFieldBlurX = []
-                var blurCount = 1;
-                var kernelSize = 15;
-                switch(blurLevel){
-                    case DepthOfFieldEffectBlurLevel.High: {
-                        blurCount = 3;
-                        kernelSize = 51;
-                        break;
-                    }
-                    case DepthOfFieldEffectBlurLevel.Medium: {
-                        blurCount = 2;
-                        kernelSize = 31;
-                        break;
-                    }
-                    default: {
-                        kernelSize = 15;
-                        blurCount = 1;
-                        break;
-                    }
+            // Create a pyramid of blurred images (eg. fullSize 1/4 blur, half size 1/2 blur, quarter size 3/4 blur, eith size 4/4 blur)
+            // Blur the image but do not blur on sharp far to near distance changes to avoid bleeding artifacts 
+            // See section 2.6.2 http://fileadmin.cs.lth.se/cs/education/edan35/lectures/12dof.pdf
+            this._depthOfFieldBlurY = []
+            this._depthOfFieldBlurX = []
+            var blurCount = 1;
+            var kernelSize = 15;
+            switch(blurLevel){
+                case DepthOfFieldEffectBlurLevel.High: {
+                    blurCount = 3;
+                    kernelSize = 51;
+                    break;
                 }
                 }
-                var adjustedKernelSize = kernelSize/Math.pow(2, blurCount-1);
-                for(var i = 0;i<blurCount;i++){
-                    var blurY = new DepthOfFieldBlurPostProcess("verticle blur", scene, new Vector2(0, 1.0), adjustedKernelSize, 1.0/Math.pow(2, i), null, this._depthOfFieldPass, i == 0 ? this._circleOfConfusion : null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-                    blurY.autoClear = false;
-                    var blurX = new DepthOfFieldBlurPostProcess("horizontal blur", scene, new Vector2(1.0, 0), adjustedKernelSize, 1.0/Math.pow(2, i), null,  this._depthOfFieldPass, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-                    blurX.autoClear = false;
-                    this._depthOfFieldBlurY.push(blurY);
-                    this._depthOfFieldBlurX.push(blurX);
+                case DepthOfFieldEffectBlurLevel.Medium: {
+                    blurCount = 2;
+                    kernelSize = 31;
+                    break;
                 }
                 }
-
-                // Merge blurred images with original image based on circleOfConfusion
-                this._depthOfFieldMerge = new DepthOfFieldMergePostProcess("depthOfFieldMerge", this._circleOfConfusion, this._depthOfFieldPass, this._depthOfFieldBlurY.slice(1), 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType);
-                this._depthOfFieldMerge.autoClear = false;
-                
-                // Set all post processes on the effect.
-                var effects= [this._circleOfConfusion, this._depthOfFieldPass];
-                for(var i=0;i<this._depthOfFieldBlurX.length;i++){
-                    effects.push(this._depthOfFieldBlurY[i]);
-                    effects.push(this._depthOfFieldBlurX[i]);
+                default: {
+                    kernelSize = 15;
+                    blurCount = 1;
+                    break;
                 }
                 }
-                effects.push(this._depthOfFieldMerge);
-                return effects;
-            }, true);
+            }
+            var adjustedKernelSize = kernelSize/Math.pow(2, blurCount-1);
+            for(var i = 0;i<blurCount;i++){
+                var blurY = new DepthOfFieldBlurPostProcess("verticle blur", scene, new Vector2(0, 1.0), adjustedKernelSize, 1.0/Math.pow(2, i), null, this._depthOfFieldPass, i == 0 ? this._circleOfConfusion : null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType, blockCompilation);
+                blurY.autoClear = false;
+                var blurX = new DepthOfFieldBlurPostProcess("horizontal blur", scene, new Vector2(1.0, 0), adjustedKernelSize, 1.0/Math.pow(2, i), null,  this._depthOfFieldPass, null, Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType, blockCompilation);
+                blurX.autoClear = false;
+                this._depthOfFieldBlurY.push(blurY);
+                this._depthOfFieldBlurX.push(blurX);
+            }
+
+            // Merge blurred images with original image based on circleOfConfusion
+            this._depthOfFieldMerge = new DepthOfFieldMergePostProcess("depthOfFieldMerge", this._circleOfConfusion, this._depthOfFieldPass, this._depthOfFieldBlurY.slice(1), 1, null, BABYLON.Texture.BILINEAR_SAMPLINGMODE, scene.getEngine(), false, pipelineTextureType, blockCompilation);
+            this._depthOfFieldMerge.autoClear = false;
+            
+            // Set all post processes on the effect.
+            this._effects= [this._circleOfConfusion, this._depthOfFieldPass];
+            for(var i=0;i<this._depthOfFieldBlurX.length;i++){
+                this._effects.push(this._depthOfFieldBlurY[i]);
+                this._effects.push(this._depthOfFieldBlurX[i]);
+            }
+            this._effects.push(this._depthOfFieldMerge);
+        }
+
+        /**
+         * Depth texture to be used to compute the circle of confusion. This must be set here or in the constructor in order for the post process to function.
+         */
+        public set depthTexture(value: RenderTargetTexture){
+            this._circleOfConfusion.depthTexture = value;
         }
         }
 
 
         /**
         /**
@@ -141,5 +154,27 @@ module BABYLON {
             });
             });
             this._depthOfFieldMerge.dispose(camera);
             this._depthOfFieldMerge.dispose(camera);
         }
         }
+
+        /**
+         * Internal
+         */
+        public _updateEffects(){
+            for(var effect in this._effects){
+                this._effects[effect].updateEffect();
+            }
+        }
+
+        /**
+         * Internal
+         * @returns if all the contained post processes are ready.
+         */
+        public _isReady(){
+            for(var effect in this._effects){
+                if(!this._effects[effect].isReady()){
+                    return false;
+                }
+            }
+            return true;
+        }
     }
     }
 }
 }

+ 21 - 2
src/PostProcess/babylon.depthOfFieldMergePostProcess.ts

@@ -15,9 +15,10 @@ module BABYLON {
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param engine The engine which the post process will be applied. (default: current engine)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param reusable If the post process can be reused on the same frame. (default: false)
          * @param textureType Type of textures used when performing the post process. (default: 0)
          * @param textureType Type of textures used when performing the post process. (default: 0)
+         * @param blockCompilation If compilation of the shader should not be done in the constructor. The updateEffect method can be used to compile the shader at a later time. (default: false)
          */
          */
-        constructor(name: string, original: PostProcess, circleOfConfusion: PostProcess, blurSteps: Array<PostProcess>, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT) {
-            super(name, "depthOfFieldMerge", [], ["circleOfConfusionSampler", "originalSampler", "blurStep1", "blurStep2"], options, camera, samplingMode, engine, reusable, "#define BLUR_LEVEL "+blurSteps.length+"\n", textureType);
+        constructor(name: string, original: PostProcess, circleOfConfusion: PostProcess, private blurSteps: Array<PostProcess>, options: number | PostProcessOptions, camera: Nullable<Camera>, samplingMode?: number, engine?: Engine, reusable?: boolean, textureType: number = Engine.TEXTURETYPE_UNSIGNED_INT, blockCompilation = false) {
+            super(name, "depthOfFieldMerge", [], ["circleOfConfusionSampler", "originalSampler", "blurStep1", "blurStep2"], options, camera, samplingMode, engine, reusable, null, textureType, undefined, null, true);
             this.onApplyObservable.add((effect: Effect) => {
             this.onApplyObservable.add((effect: Effect) => {
                 effect.setTextureFromPostProcess("circleOfConfusionSampler", circleOfConfusion);
                 effect.setTextureFromPostProcess("circleOfConfusionSampler", circleOfConfusion);
                 effect.setTextureFromPostProcess("originalSampler", original);
                 effect.setTextureFromPostProcess("originalSampler", original);
@@ -25,6 +26,24 @@ module BABYLON {
                     effect.setTextureFromPostProcess("blurStep"+(index+1), step);
                     effect.setTextureFromPostProcess("blurStep"+(index+1), step);
                 });
                 });
             });
             });
+
+            if(!blockCompilation){
+                this.updateEffect();
+            }
+        }
+
+        /**
+         * Updates the effect with the current post process compile time values and recompiles the shader.
+         * @param defines Define statements that should be added at the beginning of the shader. (default: null)
+         * @param uniforms Set of uniform variables that will be passed to the shader. (default: null)
+         * @param samplers Set of Texture2D variables that will be passed to the shader. (default: null)
+         * @param indexParameters The index parameters to be used for babylons include syntax "#include<kernelBlurVaryingDeclaration>[0..varyingCount]". (default: undefined) See usage in babylon.blurPostProcess.ts and kernelBlur.vertex.fx
+         * @param onCompiled Called when the shader has been compiled.
+         * @param onError Called if there is an error when compiling a shader.
+         */
+        public updateEffect(defines: Nullable<string> = null, uniforms: Nullable<string[]> = null, samplers: Nullable<string[]> = null, indexParameters?: any,
+            onCompiled?: (effect: Effect) => void, onError?: (effect: Effect, errors: string) => void) {
+            super.updateEffect(defines ? defines : "#define BLUR_LEVEL "+this.blurSteps.length+"\n", uniforms, samplers, indexParameters, onCompiled, onError);
         }
         }
     }
     }
 }
 }

+ 0 - 0
src/PostProcess/babylon.imageProcessingPostProcess.ts


Some files were not shown because too many files changed in this diff