Explorar o código

Merge pull request #3674 from bghgary/glTFLoader-refactor

glTF loader updates and related changes
David Catuhe %!s(int64=7) %!d(string=hai) anos
pai
achega
a86a04b640
Modificáronse 38 ficheiros con 2786 adicións e 2706 borrados
  1. 4 0
      Playground/scenes/BoomBox/BoomBox.gltf
  2. 3 2
      Tools/Gulp/config.json
  3. 2 2
      dist/babylon.glTF2Interface.d.ts
  4. 7 1
      dist/preview release/what's new.md
  5. 4 7
      loaders/src/OBJ/babylon.objFileLoader.ts
  6. 4 7
      loaders/src/STL/babylon.stlFileLoader.ts
  7. 42 12
      loaders/src/glTF/1.0/babylon.glTFLoader.ts
  8. 0 24
      loaders/src/glTF/1.0/babylon.glTFLoaderUtils.ts
  9. 78 90
      loaders/src/glTF/2.0/Extensions/KHR_lights.ts
  10. 55 29
      loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts
  11. 92 92
      loaders/src/glTF/2.0/Extensions/MSFT_lod.ts
  12. 876 1178
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  13. 41 49
      loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts
  14. 57 260
      loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts
  15. 21 0
      loaders/src/glTF/2.0/babylon.glTFLoaderUtilities.ts
  16. 0 36
      loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts
  17. 55 48
      loaders/src/glTF/babylon.glTFFileLoader.ts
  18. 0 4
      sandbox/index.js
  19. 7 2
      src/Engine/babylon.engine.ts
  20. 20 0
      src/Lights/Shadows/babylon.shadowGenerator.ts
  21. 292 295
      src/Loading/Plugins/babylon.babylonFileLoader.ts
  22. 224 33
      src/Loading/babylon.sceneLoader.ts
  23. 300 268
      src/Materials/PBR/babylon.pbrBaseMaterial.ts
  24. 16 2
      src/Materials/babylon.material.ts
  25. 8 3
      src/Materials/babylon.materialHelper.ts
  26. 75 38
      src/Mesh/babylon.mesh.vertexData.ts
  27. 33 30
      src/Mesh/babylon.vertexBuffer.ts
  28. 39 0
      src/Tools/babylon.deferred.ts
  29. 8 10
      src/Tools/babylon.filesInput.ts
  30. 20 20
      src/Tools/babylon.promise.ts
  31. 13 0
      src/Tools/babylon.tools.ts
  32. 12 0
      src/babylon.scene.ts
  33. 20 2
      tests/unit/babylon/tools/babylon.tools.tests.ts
  34. 0 91
      tests/unit/babylon/loading/babylon.sceneloader.tests.ts
  35. 218 0
      tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts
  36. 61 0
      tests/unit/babylon/src/Mesh/babylon.mesh.vertexData.tests.ts
  37. 79 71
      tests/unit/babylon/promises/babylon.promises.tests.ts
  38. BIN=BIN
      tests/validation/ReferenceImages/gltfPrimitiveAttribute.png

+ 4 - 0
Playground/scenes/BoomBox/BoomBox.gltf

@@ -158,15 +158,19 @@
   ],
   "textures": [
     {
+      "name": "baseColor",
       "source": 0
     },
     {
+      "name": "occlusionRoughnessMetallic",
       "source": 1
     },
     {
+      "name": "normal",
       "source": 2
     },
     {
+      "name": "emissive",
       "source": 3
     }
   ]

+ 3 - 2
Tools/Gulp/config.json

@@ -179,6 +179,7 @@
                 "../../src/babylon.mixins.js",
                 "../../src/Engine/babylon.webgl2.js",
                 "../../src/Tools/babylon.decorators.js",
+                "../../src/Tools/babylon.deferred.js",
                 "../../src/Tools/babylon.observable.js",
                 "../../src/Tools/babylon.smartArray.js",
                 "../../src/Tools/babylon.tools.js",
@@ -1471,9 +1472,9 @@
             {
                 "files": [
                     "../../loaders/src/glTF/babylon.glTFFileLoader.ts",
+                    "../../loaders/src/glTF/2.0/babylon.glTFLoaderUtilities.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoader.ts",
-                    "../../loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts",
                     "../../loaders/src/glTF/2.0/Extensions/MSFT_lod.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",
@@ -1491,9 +1492,9 @@
                     "../../loaders/src/glTF/1.0/babylon.glTFLoaderExtension.ts",
                     "../../loaders/src/glTF/1.0/babylon.glTFBinaryExtension.ts",
                     "../../loaders/src/glTF/1.0/babylon.glTFMaterialsCommonExtension.ts",
+                    "../../loaders/src/glTF/2.0/babylon.glTFLoaderUtilities.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoader.ts",
-                    "../../loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts",
                     "../../loaders/src/glTF/2.0/Extensions/MSFT_lod.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",

+ 2 - 2
dist/babylon.glTF2Interface.d.ts

@@ -22,7 +22,7 @@ declare module BABYLON.GLTF2 {
         SCALE = "scale",
         WEIGHTS = "weights",
     }
-    const enum AnimationInterpolation {
+    const enum AnimationSamplerInterpolation {
         LINEAR = "LINEAR",
         STEP = "STEP",
         CUBICSPLINE = "CUBICSPLINE",
@@ -110,7 +110,7 @@ declare module BABYLON.GLTF2 {
     }
     interface IAnimationSampler extends IProperty {
         input: number;
-        interpolation?: AnimationInterpolation;
+        interpolation?: AnimationSamplerInterpolation;
         output: number;
     }
     interface IAnimation extends IChildRootProperty {

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

@@ -53,15 +53,21 @@
 - (Gulp) extra/external declarations can be prepended to final NPM declarations during build. ([RaananW](https://github.com/RaananW))
 - Added FOV system to background material for zoom effects in skyboxes without adjusting camera FOV ([DavidHGillen](https://github.com/DavidHGillen))
 - Added VideoDomeHelper class to provide a template for a common scenario, and gave it FOV control ([DavidHGillen](https://github.com/DavidHGillen))
+- Improved glTF loader by using promises for asynchronous operations. ([bghgary](https://github.com/bghgary)]
+- Improved glTF loader performance by compiling materials in parallel with downloading external resources. ([bghgary](https://github.com/bghgary)]
+- Added unit tests for the glTF 2.0 loader. ([bghgary](https://github.com/bghgary)]
+- Added promise-based async functions to the SceneLoader, Scene.whenReadyAsync, and material.forceCompilationAsync. ([bghgary](https://github.com/bghgary)]
+- Added checks to VertexData.merge to ensure data is valid before merging. ([bghgary](https://github.com/bghgary)]
 
 ## Bug fixes
 
 - `setPivotMatrix` ws not setting pivot correctly. This is now fixed. We also introduced a new `setPreTransformMatrix` to reproduce the sometimes needed behavior of the previous `setPivotMatrix` function ([deltakosh](https://github.com/deltakosh))
 - 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))
-- Fixed a bug with merging vertex data ([bghgary](https://github.com/bghgary))
 - SPS internal temporary vector3 instead of Tmp.Vector3 to avoid possible concurrent uses ([jbousquie](https://github.com/jbousquie))
 
 ## Breaking changes
 
 - Removed 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)]
+- 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)]

+ 4 - 7
loaders/src/OBJ/babylon.objFileLoader.ts

@@ -264,14 +264,11 @@ module BABYLON {
             return this.importMesh(null, scene, data, rootUrl, null, null, null);
         }
 
-        public loadAssets(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void):Nullable<AssetContainer>{
+        public loadAssetContainer(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer {
             var container = new AssetContainer(scene);
-            var result = this.importMesh(null, scene, data, rootUrl, container.meshes, null, null);
-            if(result){
-                container.removeAllFromScene();
-                return container;
-            }
-            return null;
+            this.importMesh(null, scene, data, rootUrl, container.meshes, null, null);
+            container.removeAllFromScene();
+            return container;
         }
 
         /**

+ 4 - 7
loaders/src/STL/babylon.stlFileLoader.ts

@@ -86,14 +86,11 @@ module BABYLON {
             return result;
         }
 
-        public loadAssets(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void):Nullable<AssetContainer>{
+        public loadAssetContainer(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer {
             var container = new AssetContainer(scene);
-            var result = this.importMesh(null, scene, data, rootUrl, container.meshes, null, null);
-            if(result){
-                container.removeAllFromScene();
-                return container;
-            }
-            return null;
+            this.importMesh(null, scene, data, rootUrl, container.meshes, null, null);
+            container.removeAllFromScene();
+            return container;
         }
 
         private isBinary (data: any) {

+ 42 - 12
loaders/src/glTF/1.0/babylon.glTFLoader.ts

@@ -584,9 +584,7 @@ module BABYLON.GLTF1 {
 
         const subMaterials: Material[] = [];
 
-        var vertexData = new VertexData();
-        var geometry = new Geometry(id, gltfRuntime.scene, vertexData, false, newMesh);
-
+        var vertexData: Nullable<VertexData> = null;
         var verticesStarts = new Array<number>();
         var verticesCounts = new Array<number>();
         var indexStarts = new Array<number>();
@@ -684,7 +682,12 @@ module BABYLON.GLTF1 {
                     indexCounts.push(tempVertexData.indices.length);
                 }
 
-                vertexData.merge(tempVertexData);
+                if (!vertexData) {
+                    vertexData = tempVertexData;
+                }
+                else {
+                    vertexData.merge(tempVertexData);
+                }
 
                 // Sub material
                 let material = gltfRuntime.scene.getMaterialByID(primitive.material);
@@ -714,7 +717,7 @@ module BABYLON.GLTF1 {
         }
 
         // Apply geometry
-        geometry.setAllVerticesData(vertexData, false);
+        new Geometry(id, gltfRuntime.scene, vertexData!, false, newMesh);
         newMesh.computeWorldMatrix(true);
 
         // Apply submeshes
@@ -1292,8 +1295,8 @@ module BABYLON.GLTF1 {
         public static LoadBufferAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (buffer: ArrayBufferView) => void, onError: (message: string) => void, onProgress?: () => void): void {
             var buffer: IGLTFBuffer = gltfRuntime.buffers[id];
 
-            if (GLTFUtils.IsBase64(buffer.uri)) {
-                setTimeout(() => onSuccess(new Uint8Array(GLTFUtils.DecodeBase64(buffer.uri))));
+            if (Tools.IsBase64(buffer.uri)) {
+                setTimeout(() => onSuccess(new Uint8Array(Tools.DecodeBase64(buffer.uri))));
             }
             else {
                 Tools.LoadFile(gltfRuntime.rootUrl + buffer.uri, data => onSuccess(new Uint8Array(data as ArrayBuffer)), onProgress, undefined, true, request => {
@@ -1319,8 +1322,8 @@ module BABYLON.GLTF1 {
 
             var source: IGLTFImage = gltfRuntime.images[texture.source];
 
-            if (GLTFUtils.IsBase64(source.uri)) {
-                setTimeout(() => onSuccess(new Uint8Array(GLTFUtils.DecodeBase64(source.uri))));
+            if (Tools.IsBase64(source.uri)) {
+                setTimeout(() => onSuccess(new Uint8Array(Tools.DecodeBase64(source.uri))));
             }
             else {
                 Tools.LoadFile(gltfRuntime.rootUrl + source.uri, data => onSuccess(new Uint8Array(data as ArrayBuffer)), undefined, undefined, true, request => {
@@ -1368,7 +1371,7 @@ module BABYLON.GLTF1 {
         public static LoadShaderStringAsync(gltfRuntime: IGLTFRuntime, id: string, onSuccess: (shaderString: string) => void, onError: (message: string) => void): void {
             var shader: IGLTFShader = gltfRuntime.shaders[id];
 
-            if (GLTFUtils.IsBase64(shader.uri)) {
+            if (Tools.IsBase64(shader.uri)) {
                 var shaderString = atob(shader.uri.split(",")[1]);
                 onSuccess(shaderString);
             }
@@ -1570,10 +1573,13 @@ module BABYLON.GLTF1 {
         public onMaterialLoadedObservable = new Observable<Material>();
         public onCompleteObservable = new Observable<IGLTFLoader>();
 
+        public state: Nullable<GLTFLoaderState> = null;
+        public extensions: Nullable<IGLTFLoaderExtensions> = null;
+
         public dispose(): void {}
         // #endregion
 
-        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean {
+        private _importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean {
             scene.useRightHandedSystem = true;
 
             GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, gltfRuntime => {
@@ -1636,7 +1642,21 @@ module BABYLON.GLTF1 {
             return true;
         }
 
-        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void {
+        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress: (event: SceneLoaderProgressEvent) => void): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }> {
+            return new Promise((resolve, reject) => {
+                this._importMeshAsync(meshesNames, scene, data, rootUrl, (meshes, particleSystems, skeletons) => {
+                    resolve({
+                        meshes: meshes,
+                        particleSystems: particleSystems,
+                        skeletons: skeletons
+                    });
+                }, onProgress, message => {
+                    reject(new Error(message));
+                });
+            });
+        }
+
+        private _loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void {
             scene.useRightHandedSystem = true;
 
             GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, gltfRuntime => {
@@ -1664,6 +1684,16 @@ module BABYLON.GLTF1 {
             }, onError);
         }
 
+        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress: (event: SceneLoaderProgressEvent) => void): Promise<void> {
+            return new Promise((resolve, reject) => {
+                this._loadAsync(scene, data, rootUrl, () => {
+                    resolve();
+                }, onProgress, message => {
+                    reject(new Error(message));
+                });
+            });
+        }
+
         private _loadShadersAsync(gltfRuntime: IGLTFRuntime, onload: () => void): void {
             var hasShaders = false;
 

+ 0 - 24
loaders/src/glTF/1.0/babylon.glTFLoaderUtils.ts

@@ -84,30 +84,6 @@ module BABYLON.GLTF1 {
         }
 
         /**
-        * If the uri is a base64 string
-        * @param uri: the uri to test
-        */
-        public static IsBase64(uri: string): boolean {
-            return uri.length < 5 ? false : uri.substr(0, 5) === "data:";
-        }
-
-        /**
-        * Decode the base64 uri
-        * @param uri: the uri to decode
-        */
-        public static DecodeBase64(uri: string): ArrayBuffer {
-            var decodedString = atob(uri.split(",")[1]);
-            var bufferLength = decodedString.length;
-            var bufferView = new Uint8Array(new ArrayBuffer(bufferLength));
-
-            for (var i = 0; i < bufferLength; i++) {
-                bufferView[i] = decodedString.charCodeAt(i);
-            }
-
-            return bufferView.buffer;
-        }
-
-        /**
         * Returns the wrap mode of the texture
         * @param mode: the mode value
         */

+ 78 - 90
loaders/src/glTF/2.0/Extensions/KHR_lights.ts

@@ -1,119 +1,107 @@
 /// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2.Extensions {
-    interface IGLTFLight {
-        type: "directional" | "point" | "spot";
-        color: [number, number, number];
-        intensity: number;
-        // Runtime values
-        index: number;
-    }
+    // https://github.com/MiiBond/glTF/tree/khr_lights_v1/extensions/Khronos/KHR_lights
+
+    const NAME = "KHR_lights";
 
-    interface IKHRLights {
-        lights: IGLTFLight[];
+    enum LightType {
+        AMBIENT = "ambient",
+        DIRECTIONAL = "directional",
+        POINT = "point",
+        SPOT = "spot"
     }
 
-    interface IGLTFLightReference {
+    interface ILightReference {
         light: number;
-        // Runtime values
-        babylonLight: Light;
     }
 
-    export class KHRLights extends GLTFLoaderExtension {
-        public get name(): string {
-            return "KHR_lights";
-        }
+    interface ILight {
+        type: LightType;
+        color?: number[];
+        intensity?: number;
+    }
 
-        private applyCommonProperties(light: Light, lightInfo: IGLTFLight): void {
-            if (lightInfo.color) {
-                light.diffuse.copyFromFloats(lightInfo.color[0], lightInfo.color[1], lightInfo.color[2]);
-            } else {
-                light.diffuse.copyFromFloats(1, 1, 1);
-            }
+    interface ISpotLight extends ILight {
+        innerConeAngle?: number;
+        outerConeAngle?: number;
+    }
 
-            if (lightInfo.intensity !== undefined) {
-                light.intensity = lightInfo.intensity;
-            } else {
-                light.intensity = 1;
-            }
+    interface ILights {
+        lights: ILight[];
+    }
+
+    export class KHRLights extends GLTFLoaderExtension {
+        protected get _name(): string {
+            return NAME;
         }
 
-        protected _loadScene(loader: GLTFLoader, context: string, scene: IGLTFScene): boolean { 
-            return this._loadExtension<IGLTFLightReference>(context, scene, (context, extension, onComplete) => {
-                if (extension.light >= 0 && loader._gltf.extensions) {
-                    const lightInfo = loader._gltf.extensions.KHR_lights.lights[extension.light];
-                    if (lightInfo.type !== 'ambient') {
-                        return;
-                    }
+        protected _loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>> { 
+            return this._loadExtensionAsync<ILightReference>(context, scene, (context, extension) => {
+                const promise = this._loader._loadSceneAsync(context, scene);
 
-                    const lightColor = lightInfo.color ? lightInfo.color : [1, 1, 1];
-                    loader._babylonScene.ambientColor.copyFromFloats(lightColor[0], lightColor[1], lightColor[2]);
+                const light = GLTFLoader._GetProperty(context, this._lights, extension.light);
+                if (light.type !== LightType.AMBIENT) {
+                    throw new Error(context + ": Only ambient lights are allowed on a scene");
                 }
-                
-                onComplete();
+
+                this._loader._babylonScene.ambientColor = light.color ? Color3.FromArray(light.color) : Color3.Black();
+
+                return promise;
             });
         }
 
-        protected _loadNode(loader: GLTFLoader, context: string, node: IGLTFNode): boolean { 
-            return this._loadExtension<IGLTFLightReference>(context, node, (context, extension, onComplete) => {
-                if (extension.light >= 0 && loader._gltf.extensions) {
-                    const lightInfo = loader._gltf.extensions.KHR_lights.lights[extension.light];
-                    const name = node.name || 'Light';
-                    let matrix: Matrix;
-                    if (node.matrix) {
-                        matrix = Matrix.FromArray(node.matrix);
-                    } else {
-                        matrix = Matrix.Identity();
-                    }
+        protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>> { 
+            return this._loadExtensionAsync<ILightReference>(context, node, (context, extension) => {
+                const promise = this._loader._loadNodeAsync(context, node);
 
-                    const direction = new Vector3(0, 0, 1);
-                    if (lightInfo.type == 'directional' || lightInfo.type == 'spot') {
-                        const rotationMatrix = matrix.getRotationMatrix();
-                        Vector3.TransformCoordinatesToRef(direction, rotationMatrix, direction);
-                    }
+                let babylonLight: Light;
 
-                    let light: Light;
-                    if (lightInfo.type == 'directional') {
-                        light = new DirectionalLight(name, direction, loader._babylonScene);
-                    } else {
-                        const position = matrix.getTranslation();
-                        if (lightInfo.type == 'spot') {
-                            const angle = lightInfo.spot && lightInfo.spot.outerConeAngle ? lightInfo.spot.outerConeAngle : Math.PI / 2;
-                            light = new SpotLight(name, position, direction, angle, 2, loader._babylonScene);
-                        } else {
-                            light = new PointLight(name, position, loader._babylonScene);
-                        }
-                    } 
-
-                    this.applyCommonProperties(light, lightInfo);
-                    
-                    extension.babylonLight = light;
-                    extension.babylonLight.parent = node.parent ? node.parent.babylonMesh : null;
-                    
-                    if (node.children) {
-                        for (const index of node.children) {
-                            const childNode = GLTFLoader._GetProperty(loader._gltf.nodes, index);
-                            if (!childNode) {
-                                throw new Error(context + ": Failed to find child node " + index);
-                            }
-        
-                            loader._loadNode("#/nodes/" + index, childNode);
-                        }
+                const light = GLTFLoader._GetProperty(context, this._lights, extension.light);
+                const name = node._babylonMesh!.name;
+                switch (light.type) {
+                    case LightType.AMBIENT: {
+                        throw new Error(context + ": Ambient lights are not allowed on a node");
+                    }
+                    case LightType.DIRECTIONAL: {
+                        babylonLight = new DirectionalLight(name, Vector3.Forward(), this._loader._babylonScene);
+                        break;
+                    }
+                    case LightType.POINT: {
+                        babylonLight = new PointLight(name, Vector3.Zero(), this._loader._babylonScene);
+                        break;
+                    }
+                    case LightType.SPOT: {
+                        const spotLight = light as ISpotLight;
+                        // TODO: support inner and outer cone angles
+                        //const innerConeAngle = spotLight.innerConeAngle || 0;
+                        const outerConeAngle = spotLight.outerConeAngle || Math.PI / 4;
+                        babylonLight = new SpotLight(name, Vector3.Zero(), Vector3.Forward(), outerConeAngle, 2, this._loader._babylonScene);
+                        break;
+                    }
+                    default: {
+                        throw new Error(context + ": Invalid light type " + light.type);
                     }
                 }
-                onComplete();
+
+                babylonLight.diffuse = light.color ? Color3.FromArray(light.color) : Color3.White();
+                babylonLight.intensity = light.intensity == undefined ? 1 : light.intensity;
+                babylonLight.parent = node._babylonMesh!;
+
+                return promise;
             });
         }
 
-        protected _loadRoot(loader: GLTFLoader, context: string, root: BABYLON.GLTF2._IGLTF): boolean {
-            return this._loadExtension<IKHRLights>(context, root, (context, extension, onComplete) => {
-                extension.lights.forEach((light: IGLTFLight, idx: number) => {
-                    light.index = idx;
-                });
-                onComplete();
-            });
+        private get _lights(): Array<ILight> {
+            const extensions = this._loader._gltf.extensions;
+            if (!extensions || !extensions[this._name]) {
+                throw new Error("#/extensions: " + this._name + " not found");
+            }
+
+            const extension = extensions[this._name] as ILights;
+            return extension.lights;
         }
     }
 
-    GLTFLoader.RegisterExtension(new KHRLights());
+    GLTFLoader._Register(NAME, loader => new KHRLights(loader));
 }

+ 55 - 29
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -1,59 +1,85 @@
 /// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2.Extensions {
+    // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness
+
+    const NAME = "KHR_materials_pbrSpecularGlossiness";
+
     interface IKHRMaterialsPbrSpecularGlossiness {
         diffuseFactor: number[];
-        diffuseTexture: IGLTFTextureInfo;
+        diffuseTexture: ITextureInfo;
         specularFactor: number[];
         glossinessFactor: number;
-        specularGlossinessTexture: IGLTFTextureInfo;
+        specularGlossinessTexture: ITextureInfo;
     }
 
     export class KHRMaterialsPbrSpecularGlossiness extends GLTFLoaderExtension {
-        public get name(): string {
-            return "KHR_materials_pbrSpecularGlossiness";
+        protected get _name(): string {
+            return NAME;
         }
 
-        protected _loadMaterial(loader: GLTFLoader, context: string, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            return this._loadExtension<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension, onComplete) => {
-                loader._createPbrMaterial(material);
-                loader._loadMaterialBaseProperties(context, material);
-                this._loadSpecularGlossinessProperties(loader, context, material, extension);
-                assign(material.babylonMaterial, true);
-                onComplete();
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+            return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (context, extension) => {
+                material._babylonMeshes = material._babylonMeshes || [];
+                material._babylonMeshes.push(babylonMesh);
+
+                if (material._loaded) {
+                    babylonMesh.material = material._babylonMaterial!;
+                    return material._loaded;
+                }
+
+                const promises = new Array<Promise<void>>();
+
+                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));
+
+                this._loader.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+
+                babylonMesh.material = babylonMaterial;
+
+                return (material._loaded = Promise.all(promises).then(() => {}));
             });
         }
 
-        private _loadSpecularGlossinessProperties(loader: GLTFLoader, context: string, material: IGLTFMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): void {
-            const babylonMaterial = material.babylonMaterial as PBRMaterial;
+        private _loadSpecularGlossinessPropertiesAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): Promise<void> {
+            const promises = new Array<Promise<void>>();
 
-            babylonMaterial.albedoColor = properties.diffuseFactor ? Color3.FromArray(properties.diffuseFactor) : new Color3(1, 1, 1);
-            babylonMaterial.reflectivityColor = properties.specularFactor ? Color3.FromArray(properties.specularFactor) : new Color3(1, 1, 1);
-            babylonMaterial.microSurface = properties.glossinessFactor == null ? 1 : properties.glossinessFactor;
+            const babylonMaterial = material._babylonMaterial as PBRMaterial;
 
-            if (properties.diffuseTexture) {
-                const texture = GLTFLoader._GetProperty(loader._gltf.textures, properties.diffuseTexture.index);
-                if (!texture) {
-                    throw new Error(context + ": Failed to find diffuse texture " + properties.diffuseTexture.index);
-                }
+            if (properties.diffuseFactor) {
+                babylonMaterial.albedoColor = Color3.FromArray(properties.diffuseFactor);
+                babylonMaterial.alpha = properties.diffuseFactor[3];
+            }
+            else {
+                babylonMaterial.albedoColor = Color3.White();
+            }
 
-                babylonMaterial.albedoTexture = loader._loadTexture("textures[" + texture.index + "]", texture, properties.diffuseTexture.texCoord);
+            babylonMaterial.reflectivityColor = properties.specularFactor ? Color3.FromArray(properties.specularFactor) : Color3.White();
+            babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
+
+            if (properties.diffuseTexture) {
+                promises.push(loader._loadTextureAsync(context + "/diffuseTexture", properties.diffuseTexture, texture => {
+                    babylonMaterial.albedoTexture = texture;
+                }));
             }
 
             if (properties.specularGlossinessTexture) {
-                const texture = GLTFLoader._GetProperty(loader._gltf.textures, properties.specularGlossinessTexture.index);
-                if (!texture) {
-                    throw new Error(context + ": Failed to find diffuse texture " + properties.specularGlossinessTexture.index);
-                }
+                promises.push(loader._loadTextureAsync(context + "/specularGlossinessTexture", properties.specularGlossinessTexture, texture => {
+                    babylonMaterial.reflectivityTexture = texture;
+                }));
 
-                babylonMaterial.reflectivityTexture = loader._loadTexture("textures[" + texture.index + "]", texture, properties.specularGlossinessTexture.texCoord);
                 babylonMaterial.reflectivityTexture.hasAlpha = true;
                 babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
             }
 
-            loader._loadMaterialAlphaProperties(context, material, properties.diffuseFactor);
+            loader._loadMaterialAlphaProperties(context, material);
+
+            return Promise.all(promises).then(() => {});
         }
     }
 
-    GLTFLoader.RegisterExtension(new KHRMaterialsPbrSpecularGlossiness());
+    GLTFLoader._Register(NAME, loader => new KHRMaterialsPbrSpecularGlossiness(loader));
 }

+ 92 - 92
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -1,128 +1,128 @@
 /// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2.Extensions {
+    // https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod
+
+    const NAME = "MSFT_lod";
+
     interface IMSFTLOD {
         ids: number[];
     }
 
-    // See https://github.com/sbtron/glTF/tree/MSFT_lod/extensions/Vendor/MSFT_lod for more information about this extension.
     export class MSFTLOD extends GLTFLoaderExtension {
-        /**
-         * Specify the minimal delay between LODs in ms (default = 250)
-         */
-        public Delay = 250;
+        private _loadingNodeLOD: Nullable<ILoaderNode> = null;
+        private _loadNodeSignals: { [nodeIndex: number]: Deferred<void> } = {};
+
+        private _loadingMaterialLOD: Nullable<ILoaderMaterial> = null;
+        private _loadMaterialSignals: { [materialIndex: number]: Deferred<void> } = {};
 
-        public get name() {
-            return "MSFT_lod";
+        protected get _name() {
+            return NAME;
         }
 
-        protected _traverseNode(loader: GLTFLoader, context: string, node: IGLTFNode, action: (node: IGLTFNode, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean {
-            return this._loadExtension<IMSFTLOD>(context, node, (context, extension, onComplete) => {
-                for (let i = extension.ids.length - 1; i >= 0; i--) {
-                    const lodNode = GLTFLoader._GetProperty(loader._gltf.nodes, extension.ids[i]);
-                    if (!lodNode) {
-                        throw new Error(context + ": Failed to find node " + extension.ids[i]);
+        protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>> {
+            return this._loadExtensionAsync<IMSFTLOD>(context, node, (context, extension) => {
+                let firstPromise: Promise<void>;
+
+                const nodeLODs = MSFTLOD._GetLODs(context, node, this._loader._gltf.nodes, extension.ids);
+                for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
+                    const nodeLOD = nodeLODs[indexLOD];
+
+                    if (indexLOD !== 0) {
+                        this._loadingNodeLOD = nodeLOD;
+                        this._loadNodeSignals[nodeLOD._index] = new Deferred<void>();
                     }
 
-                    loader._traverseNode(context, lodNode, action, parentNode);
-                }
+                    const promise = this._loader._loadNodeAsync("#/nodes/" + nodeLOD._index, nodeLOD).then(() => {
+                        if (indexLOD !== 0) {
+                            const previousNodeLOD = nodeLODs[indexLOD - 1];
+                            previousNodeLOD._babylonMesh!.setEnabled(false);
+                        }
 
-                loader._traverseNode(context, node, action, parentNode);
-                onComplete();
-            });
-        }
+                        if (indexLOD !== nodeLODs.length - 1) {
+                            const nodeIndex = nodeLODs[indexLOD + 1]._index;
+                            this._loadNodeSignals[nodeIndex].resolve();
+                            delete this._loadNodeSignals[nodeIndex];
+                        }
+                    });
 
-        protected _loadNode(loader: GLTFLoader, context: string, node: IGLTFNode): boolean {
-            return this._loadExtension<IMSFTLOD>(context, node, (context, extension, onComplete) => {
-                const nodes = [node];
-                for (let index of extension.ids) {
-                    const lodNode = GLTFLoader._GetProperty(loader._gltf.nodes, index);
-                    if (!lodNode) {
-                        throw new Error(context + ": Failed to find node " + index);
+                    if (indexLOD === 0) {
+                        firstPromise = promise;
+                    }
+                    else {
+                        this._loader._completePromises.push(promise);
+                        this._loadingNodeLOD = null;
                     }
-
-                    nodes.push(lodNode);
                 }
 
-                loader._addLoaderPendingData(node);
-                this._loadNodeLOD(loader, context, nodes, nodes.length - 1, () => {
-                    loader._removeLoaderPendingData(node);
-                    onComplete();
-                });
+                return firstPromise!;
             });
         }
 
-        private _loadNodeLOD(loader: GLTFLoader, context: string, nodes: IGLTFNode[], index: number, onComplete: () => void): void {
-            loader._whenAction(() => {
-                loader._loadNode(context, nodes[index]);
-            }, () => {
-                if (index !== nodes.length - 1) {
-                    const previousNode = nodes[index + 1];
-                    previousNode.babylonMesh.setEnabled(false);
-                }
+        protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
+            return this._loadExtensionAsync<IMSFTLOD>(context, material, (context, extension) => {
+                let firstPromise: Promise<void>;
 
-                if (index === 0) {
-                    onComplete();
-                    return;
-                }
+                const materialLODs = MSFTLOD._GetLODs(context, material, this._loader._gltf.materials, extension.ids);
+                for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
+                    const materialLOD = materialLODs[indexLOD];
+
+                    if (indexLOD !== 0) {
+                        this._loadingMaterialLOD = materialLOD;
+                        this._loadMaterialSignals[materialLOD._index] = new Deferred<void>();
+                    }
 
-                setTimeout(() => {
-                    loader._tryCatchOnError(() => {
-                        this._loadNodeLOD(loader, context, nodes, index - 1, onComplete);
+                    const promise = this._loader._loadMaterialAsync("#/materials/" + materialLOD._index, materialLOD, babylonMesh).then(() => {
+                        if (indexLOD !== materialLODs.length - 1) {
+                            const materialIndex = materialLODs[indexLOD + 1]._index;
+                            this._loadMaterialSignals[materialIndex].resolve();
+                            delete this._loadMaterialSignals[materialIndex];
+                        }
                     });
-                }, this.Delay);
-            });
-        }
 
-        protected _loadMaterial(loader: GLTFLoader, context: string, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            return this._loadExtension<IMSFTLOD>(context, material, (context, extension, onComplete) => {
-                const materials = [material];
-                for (let index of extension.ids) {
-                    const lodMaterial = GLTFLoader._GetProperty(loader._gltf.materials, index);
-                    if (!lodMaterial) {
-                        throw new Error(context + ": Failed to find material " + index);
+                    if (indexLOD === 0) {
+                        firstPromise = promise;
+                    }
+                    else {
+                        this._loader._completePromises.push(promise);
+                        this._loadingMaterialLOD = null;
                     }
-
-                    materials.push(lodMaterial);
                 }
 
-                loader._addLoaderPendingData(material);
-                this._loadMaterialLOD(loader, context, materials, materials.length - 1, assign, () => {
-                    loader._removeLoaderPendingData(material);
-                    onComplete();
-                });
+                return firstPromise!;
             });
         }
 
-        private _loadMaterialLOD(loader: GLTFLoader, context: string, materials: IGLTFMaterial[], index: number, assign: (babylonMaterial: Material, isNew: boolean) => void, onComplete: () => void): void {
-            loader._loadMaterial(context, materials[index], (babylonMaterial, isNew) => {
-                if (index === materials.length - 1) {
-                    assign(babylonMaterial, isNew);
+        protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
+            // Defer the loading of uris if loading a material or node LOD.
+            if (this._loadingMaterialLOD) {
+                const index = this._loadingMaterialLOD._index;
+                return this._loadMaterialSignals[index].promise.then(() => {
+                    return this._loader._loadUriAsync(context, uri);
+                });
+            }
+            else if (this._loadingNodeLOD) {
+                const index = this._loadingNodeLOD._index;
+                return this._loadNodeSignals[index].promise.then(() => {
+                    return this._loader._loadUriAsync(context, uri);
+                });
+            }
 
-                    // Load the next LOD when the loader is ready to render.
-                    loader._executeWhenRenderReady(() => {
-                        this._loadMaterialLOD(loader, context, materials, index - 1, assign, onComplete);
-                    });
-                }
-                else {
-                    BaseTexture.WhenAllReady(babylonMaterial.getActiveTextures(), () => {
-                        assign(babylonMaterial, isNew);
+            return null;
+        }
 
-                        if (index === 0) {
-                            onComplete();
-                        }
-                        else {
-                            setTimeout(() => {
-                                loader._tryCatchOnError(() => {
-                                    this._loadMaterialLOD(loader, context, materials, index - 1, assign, onComplete);
-                                });
-                            }, this.Delay);
-                        }
-                    });
-                }
-            });
+        /**
+         * Gets an array of LOD properties from lowest to highest.
+         */
+        private static _GetLODs<T>(context: string, property: T, array: ArrayLike<T> | undefined, ids: number[]): T[] {
+            const properties = [property];
+            for (const id of ids) {
+                properties.push(GLTFLoader._GetProperty(context + "/ids/" + id, array, id));
+            }
+
+            return properties.reverse();
         }
     }
 
-    GLTFLoader.RegisterExtension(new MSFTLOD());
-}
+    GLTFLoader._Register(NAME, loader => new MSFTLOD(loader));
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 876 - 1178
loaders/src/glTF/2.0/babylon.glTFLoader.ts


+ 41 - 49
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -2,80 +2,72 @@
 
 module BABYLON.GLTF2 {
     export abstract class GLTFLoaderExtension {
-        public enabled: boolean = true;
+        public enabled = true;
 
-        public abstract get name(): string;
+        protected _loader: GLTFLoader;
 
-        protected _traverseNode(loader: GLTFLoader, context: string, node: IGLTFNode, action: (node: IGLTFNode, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean { return false; }
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        protected abstract get _name(): string;
+
+        // #region Overridable Methods
+
+        /** Override this method to modify the default behavior for loading scenes. */
+        protected _loadSceneAsync(context: string, node: ILoaderScene): Nullable<Promise<void>> { return null; }
 
-        protected _loadNode(loader: GLTFLoader, context: string, node: IGLTFNode): boolean { return false; }
+        /** Override this method to modify the default behavior for loading nodes. */
+        protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>> { return null; }
 
-        protected _loadRoot(loader: GLTFLoader, context: string, root: BABYLON.GLTF2._IGLTF): boolean { return false; }
+        /** 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 _loadScene(loader: GLTFLoader, context: string, scene: IGLTFScene): boolean { return false; }
+        /** Override this method to modify the default behavior for loading uris. */
+        protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> { return null; }
 
-        protected _loadMaterial(loader: GLTFLoader, context: string, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean { return false; }
+        // #endregion
 
-        protected _loadExtension<T>(context: string, property: IGLTFProperty, action: (context: string, extension: T, onComplete: () => void) => void): boolean {
+        /** Helper method called by a loader extension to load an glTF extension. */
+        protected _loadExtensionAsync<T>(context: string, property: IProperty, actionAsync: (context: string, extension: T) => Promise<void>): Nullable<Promise<void>> {
             if (!property.extensions) {
-                return false;
+                return null;
             }
 
-            const extension = property.extensions[this.name] as T;
+            const extensions = property.extensions;
+
+            const extension = extensions[this._name] as T;
             if (!extension) {
-                return false;
+                return null;
             }
 
             // Clear out the extension before executing the action to avoid recursing into the same property.
-            property.extensions[this.name] = undefined;
+            delete extensions[this._name];
 
-            action(context + "extensions/" + this.name, extension, () => {
+            return actionAsync(context + "extensions/" + this._name, extension).then(() => {
                 // Restore the extension after completing the action.
-                property.extensions![this.name] = extension;
+                extensions[this._name] = extension;
             });
-
-            return true;
-        }
-
-        //
-        // Utilities
-        //
-
-        public static _Extensions: GLTFLoaderExtension[] = [];
-
-        public static TraverseNode(loader: GLTFLoader, context: string, node: IGLTFNode, action: (node: IGLTFNode, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean {
-            return this._ApplyExtensions(extension => extension._traverseNode(loader, context, node, action, parentNode));
-        }
-
-        public static LoadRoot(loader: GLTFLoader, context: string, root: BABYLON.GLTF2._IGLTF): boolean {
-            return this._ApplyExtensions(extension => extension._loadRoot(loader, context, root));
         }
 
-        public static LoadScene(loader: GLTFLoader, context: string, scene: IGLTFScene): boolean {
-            return this._ApplyExtensions(extension => extension._loadScene(loader, context, scene));
+        /** Helper method called by the loader to allow extensions to override loading scenes. */
+        public static _LoadSceneAsync(loader: GLTFLoader, context: string, scene: ILoaderScene): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadSceneAsync(context, scene));
         }
 
-        public static LoadNode(loader: GLTFLoader, context: string, node: IGLTFNode): boolean {
-            return this._ApplyExtensions(extension => extension._loadNode(loader, context, node));
+        /** Helper method called by the loader to allow extensions to override loading nodes. */
+        public static _LoadNodeAsync(loader: GLTFLoader, context: string, node: ILoaderNode): Nullable<Promise<void>> {
+            return loader._applyExtensions(extension => extension._loadNodeAsync(context, node));
         }
 
-        public static LoadMaterial(loader: GLTFLoader, context: string, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            return this._ApplyExtensions(extension => extension._loadMaterial(loader, context, material, assign));
+        /** 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));
         }
 
-        private static _ApplyExtensions(action: (extension: GLTFLoaderExtension) => boolean) {
-            const extensions = GLTFLoaderExtension._Extensions;
-            if (!extensions) {
-                return false;
-            }
-
-            for (const extension of extensions) {
-                if (extension.enabled && action(extension)) {
-                    return true;
-                }
-            }
-
-            return false;
+        /** Helper method called by the loader to allow extensions to override loading uris. */
+        public static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
+            return loader._applyExtensions(extension => extension._loadUriAsync(context, uri));
         }
     }
 }

+ 57 - 260
loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts

@@ -1,306 +1,103 @@
 /// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+/// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
 
 module BABYLON.GLTF2 {
-    /**
-    * Enums
-    */
-    export enum EComponentType {
-        BYTE = 5120,
-        UNSIGNED_BYTE = 5121,
-        SHORT = 5122,
-        UNSIGNED_SHORT = 5123,
-        UNSIGNED_INT = 5125,
-        FLOAT = 5126
+    export interface ILoaderAccessor extends IAccessor, IArrayItem {
+        _data?: Promise<TypedArray>;
     }
 
-    export enum EMeshPrimitiveMode {
-        POINTS = 0,
-        LINES = 1,
-        LINE_LOOP = 2,
-        LINE_STRIP = 3,
-        TRIANGLES = 4,
-        TRIANGLE_STRIP = 5,
-        TRIANGLE_FAN = 6
+    export interface ILoaderAnimationChannel extends IAnimationChannel, IArrayItem {
+        _babylonAnimationGroup: AnimationGroup;
     }
 
-    export enum ETextureMagFilter {
-        NEAREST = 9728,
-        LINEAR = 9729,
+    export interface ILoaderAnimationSamplerData {
+        input: Float32Array;
+        interpolation: AnimationSamplerInterpolation;
+        output: Float32Array;
     }
 
-    export enum ETextureMinFilter {
-        NEAREST = 9728,
-        LINEAR = 9729,
-        NEAREST_MIPMAP_NEAREST = 9984,
-        LINEAR_MIPMAP_NEAREST = 9985,
-        NEAREST_MIPMAP_LINEAR = 9986,
-        LINEAR_MIPMAP_LINEAR = 9987
+    export interface ILoaderAnimationSampler extends IAnimationSampler, IArrayItem {
+        _data: Promise<ILoaderAnimationSamplerData>;
     }
 
-    export enum ETextureWrapMode {
-        CLAMP_TO_EDGE = 33071,
-        MIRRORED_REPEAT = 33648,
-        REPEAT = 10497
-    }
-
-    /**
-    * Interfaces
-    */
-    export interface IGLTFProperty {
-        extensions?: { [key: string]: any };
-        extras?: any;
-    }
-
-    export interface IGLTFChildRootProperty extends IGLTFProperty {
-        name?: string;
-    }
-
-    export interface IGLTFAccessorSparseIndices extends IGLTFProperty {
-        bufferView: number;
-        byteOffset?: number;
-        componentType: EComponentType;
-    }
-
-    export interface IGLTFAccessorSparseValues extends IGLTFProperty {
-        bufferView: number;
-        byteOffset?: number;
-    }
-
-    export interface IGLTFAccessorSparse extends IGLTFProperty {
-        count: number;
-        indices: IGLTFAccessorSparseIndices;
-        values: IGLTFAccessorSparseValues;
-    }
-
-    export interface IGLTFAccessor extends IGLTFChildRootProperty {
-        bufferView?: number;
-        byteOffset?: number;
-        componentType: EComponentType;
-        normalized?: boolean;
-        count: number;
-        type: string;
-        max: number[];
-        min: number[];
-        sparse?: IGLTFAccessorSparse;
-
-        // Runtime values
-        index: number;
-    }
-
-    export interface IGLTFAnimationChannel extends IGLTFProperty {
-        sampler: number;
-        target: IGLTFAnimationChannelTarget;
-    }
-
-    export interface IGLTFAnimationChannelTarget extends IGLTFProperty {
-        node: number;
-        path: string;
-    }
-
-    export interface IGLTFAnimationSampler extends IGLTFProperty {
-        input: number;
-        interpolation?: string;
-        output: number;
-    }
-
-    export interface IGLTFAnimation extends IGLTFChildRootProperty {
-        channels: IGLTFAnimationChannel[];
-        samplers: IGLTFAnimationSampler[];
-
-        // Runtime values
-        index: number;
-        babylonAnimationGroup: AnimationGroup;
-    }
-
-    export interface IGLTFAsset extends IGLTFChildRootProperty {
-        copyright?: string;
-        generator?: string;
-        version: string;
-        minVersion?: string;
-    }
-
-    export interface IGLTFBuffer extends IGLTFChildRootProperty {
-        uri?: string;
-        byteLength: number;
-
-        // Runtime values
-        index: number;
-        loadedData?: ArrayBufferView;
-        loadedObservable?: Observable<IGLTFBuffer>;
-    }
-
-    export interface IGLTFBufferView extends IGLTFChildRootProperty {
-        buffer: number;
-        byteOffset?: number;
-        byteLength: number;
-        byteStride?: number;
-
-        // Runtime values
-        index: number;
-    }
-
-    export interface IGLTFCameraOrthographic extends IGLTFProperty {
-        xmag: number;
-        ymag: number;
-        zfar: number;
-        znear: number;
-    }
+    export interface ILoaderAnimation extends IAnimation, IArrayItem {
+        channels: ILoaderAnimationChannel[];
+        samplers: ILoaderAnimationSampler[];
 
-    export interface IGLTFCameraPerspective extends IGLTFProperty {
-        aspectRatio: number;
-        yfov: number;
-        zfar: number;
-        znear: number;
+        _babylonAnimationGroup: Nullable<AnimationGroup>;
     }
 
-    export interface IGLTFCamera extends IGLTFChildRootProperty {
-        orthographic?: IGLTFCameraOrthographic;
-        perspective?: IGLTFCameraPerspective;
-        type: string;
+    export interface ILoaderBuffer extends IBuffer, IArrayItem {
+        _data?: Promise<ArrayBufferView>;
     }
 
-    export interface IGLTFImage extends IGLTFChildRootProperty {
-        uri?: string;
-        mimeType?: string;
-        bufferView?: number;
-
-        // Runtime values
-        index: number;
+    export interface ILoaderBufferView extends IBufferView, IArrayItem {
+        _data?: Promise<ArrayBufferView>;
     }
 
-    export interface IGLTFMaterialNormalTextureInfo extends IGLTFTextureInfo {
-        scale: number;
+    export interface ILoaderCamera extends ICamera, IArrayItem {
     }
 
-    export interface IGLTFMaterialOcclusionTextureInfo extends IGLTFTextureInfo {
-        strength: number;
+    export interface ILoaderImage extends IImage, IArrayItem {
+        _objectURL?: Promise<string>;
     }
 
-    export interface IGLTFMaterialPbrMetallicRoughness {
-        baseColorFactor: number[];
-        baseColorTexture: IGLTFTextureInfo;
-        metallicFactor: number;
-        roughnessFactor: number;
-        metallicRoughnessTexture: IGLTFTextureInfo;
+    export interface ILoaderMaterial extends IMaterial, IArrayItem {
+        _babylonMaterial?: Material;
+        _babylonMeshes?: AbstractMesh[];
+        _loaded?: Promise<void>;
     }
 
-    export interface IGLTFMaterial extends IGLTFChildRootProperty {
-        pbrMetallicRoughness?: IGLTFMaterialPbrMetallicRoughness;
-        normalTexture?: IGLTFMaterialNormalTextureInfo;
-        occlusionTexture?: IGLTFMaterialOcclusionTextureInfo;
-        emissiveTexture?: IGLTFTextureInfo;
-        emissiveFactor?: number[];
-        alphaMode?: string;
-        alphaCutoff: number;
-        doubleSided?: boolean;
-
-        // Runtime values
-        index: number;
-        babylonMaterial: Material;
+    export interface ILoaderMesh extends IMesh, IArrayItem {
+        primitives: ILoaderMeshPrimitive[];
     }
 
-    export interface IGLTFMeshPrimitive extends IGLTFProperty {
-        attributes: { [name: string]: number };
-        indices?: number;
-        material?: number;
-        mode?: EMeshPrimitiveMode;
-        targets?: { [name: string]: number }[];
-
-        // Runtime values
-        vertexData: VertexData;
-        targetsVertexData: VertexData[];
+    export interface ILoaderMeshPrimitive extends IMeshPrimitive, IArrayItem {
     }
 
-    export interface IGLTFMesh extends IGLTFChildRootProperty {
-        primitives: IGLTFMeshPrimitive[];
-        weights?: number[];
-
-        // Runtime values
-        index: number;
-        hasVertexAlpha: boolean;
+    export interface ILoaderNode extends INode, IArrayItem {
+        _parent: ILoaderNode;
+        _babylonMesh?: Mesh;
+        _primitiveBabylonMeshes?: Mesh[];
+        _babylonAnimationTargets?: Node[];
+        _numMorphTargets?: number;
     }
 
-    export interface IGLTFNode extends IGLTFChildRootProperty {
-        camera?: number;
-        children?: number[];
-        skin?: number;
-        matrix?: number[];
-        mesh?: number;
-        rotation?: number[];
-        scale?: number[];
-        translation?: number[];
-        weights?: number[];
-
-        // Runtime values
-        index: number;
-        parent: IGLTFNode;
-        babylonMesh: Mesh;
-        babylonAnimationTargets?: Node[];
-    }
-
-    export interface IGLTFSampler extends IGLTFChildRootProperty {
-        magFilter?: ETextureMagFilter;
-        minFilter?: ETextureMinFilter;
-        wrapS?: ETextureWrapMode;
-        wrapT?: ETextureWrapMode;
-
-        // Runtime values
-        index: number;
+    export interface ILoaderSamplerData {
         noMipMaps: boolean;
         samplingMode: number;
         wrapU: number;
         wrapV: number;
     }
 
-    export interface IGLTFScene extends IGLTFChildRootProperty {
-        nodes: number[];
-
-        // Runtime values
-        index: number;
+    export interface ILoaderSampler extends ISampler, IArrayItem {
+        _data?: ILoaderSamplerData;
     }
 
-    export interface IGLTFSkin extends IGLTFChildRootProperty {
-        inverseBindMatrices?: number;
-        skeleton?: number;
-        joints: number[];
-
-        // Runtime values
-        index: number;
-        babylonSkeleton: Skeleton;
+    export interface ILoaderScene extends IScene, IArrayItem {
     }
 
-    export interface IGLTFTexture extends IGLTFChildRootProperty {
-        sampler?: number;
-        source: number;
-
-        // Runtime values
-        index: number;
-        url?: string;
-        dataReadyObservable?: Observable<IGLTFTexture>;
+    export interface ILoaderSkin extends ISkin, IArrayItem {
+        _babylonSkeleton: Nullable<Skeleton>;
+        _loaded?: Promise<void>;
     }
 
-    export interface IGLTFTextureInfo {
-        index: number;
-        texCoord?: number;
+    export interface ILoaderTexture extends ITexture, IArrayItem {
     }
 
-    export interface _IGLTF extends IGLTFProperty {
-        accessors?: IGLTFAccessor[];
-        animations?: IGLTFAnimation[];
-        asset: IGLTFAsset;
-        buffers?: IGLTFBuffer[];
-        bufferViews?: IGLTFBufferView[];
-        cameras?: IGLTFCamera[];
-        extensionsUsed?: string[];
-        extensionsRequired?: string[];
-        images?: IGLTFImage[];
-        materials?: IGLTFMaterial[];
-        meshes?: IGLTFMesh[];
-        nodes?: IGLTFNode[];
-        samplers?: IGLTFSampler[];
-        scene?: number;
-        scenes?: IGLTFScene[];
-        skins?: IGLTFSkin[];
-        textures?: IGLTFTexture[];
+    export interface ILoaderGLTF extends IGLTF {
+        accessors?: ILoaderAccessor[];
+        animations?: ILoaderAnimation[];
+        buffers?: ILoaderBuffer[];
+        bufferViews?: ILoaderBufferView[];
+        cameras?: ILoaderCamera[];
+        images?: ILoaderImage[];
+        materials?: ILoaderMaterial[];
+        meshes?: ILoaderMesh[];
+        nodes?: ILoaderNode[];
+        samplers?: ILoaderSampler[];
+        scenes?: ILoaderScene[];
+        skins?: ILoaderSkin[];
+        textures?: ILoaderTexture[];
     }
 }

+ 21 - 0
loaders/src/glTF/2.0/babylon.glTFLoaderUtilities.ts

@@ -0,0 +1,21 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GLTF2 {
+    export interface TypedArray extends ArrayBufferView {
+        [index: number]: number;
+    }
+
+    export interface IArrayItem {
+        _index: number;
+    }
+
+    export class ArrayItem {
+        public static Assign(values?: IArrayItem[]): void {
+            if (values) {
+                for (let index = 0; index < values.length; index++) {
+                    values[index]._index = index;
+                }
+            }
+        }
+    }
+}

+ 0 - 36
loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts

@@ -1,36 +0,0 @@
-/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
-
-module BABYLON.GLTF2 {
-    /**
-    * Utils functions for GLTF
-    */
-    export class GLTFUtils {
-        /**
-        * If the uri is a base64 string
-        * @param uri: the uri to test
-        */
-        public static IsBase64(uri: string): boolean {
-            return uri.length < 5 ? false : uri.substr(0, 5) === "data:";
-        }
-
-        /**
-        * Decode the base64 uri
-        * @param uri: the uri to decode
-        */
-        public static DecodeBase64(uri: string): ArrayBuffer {
-            const decodedString = atob(uri.split(",")[1]);
-            const bufferLength = decodedString.length;
-            const bufferView = new Uint8Array(new ArrayBuffer(bufferLength));
-
-            for (let i = 0; i < bufferLength; i++) {
-                bufferView[i] = decodedString.charCodeAt(i);
-            }
-
-            return bufferView.buffer;
-        }
-
-        public static ValidateUri(uri: string): boolean {
-            return (uri.indexOf("..") === -1);
-        }
-    }
-}

+ 55 - 48
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -35,6 +35,20 @@ module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
 
+    export enum GLTFLoaderState {
+        Loading,
+        Ready,
+        Complete
+    }
+
+    export interface IGLTFLoaderExtension {
+        enabled: boolean;
+    }
+
+    export interface IGLTFLoaderExtensions {
+        [name: string]: IGLTFLoaderExtension;
+    }
+
     export interface IGLTFLoader extends IDisposable {
         coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
         animationStartMode: GLTFLoaderAnimationStartMode;
@@ -48,8 +62,11 @@ module BABYLON {
         onMaterialLoadedObservable: Observable<Material>;
         onCompleteObservable: Observable<IGLTFLoader>;
 
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        state: Nullable<GLTFLoaderState>;
+        extensions: Nullable<IGLTFLoaderExtensions>;
+
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }>;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void) => Promise<void>;
     }
 
     export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
@@ -113,7 +130,7 @@ module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
-        public onMeshLoadedObservable = new Observable<AbstractMesh>();
+        public readonly onMeshLoadedObservable = new Observable<AbstractMesh>();
 
         private _onMeshLoadedObserver: Nullable<Observer<AbstractMesh>>;
         public set onMeshLoaded(callback: (mesh: AbstractMesh) => void) {
@@ -126,7 +143,7 @@ module BABYLON {
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        public onTextureLoadedObservable = new Observable<BaseTexture>();
+        public readonly onTextureLoadedObservable = new Observable<BaseTexture>();
 
         private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
         public set onTextureLoaded(callback: (Texture: BaseTexture) => void) {
@@ -139,7 +156,7 @@ module BABYLON {
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        public onMaterialLoadedObservable = new Observable<Material>();
+        public readonly onMaterialLoadedObservable = new Observable<Material>();
 
         private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
         public set onMaterialLoaded(callback: (Material: Material) => void) {
@@ -154,7 +171,7 @@ module BABYLON {
          * 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.
          */
-        public onCompleteObservable = new Observable<GLTFFileLoader>();
+        public readonly onCompleteObservable = new Observable<GLTFFileLoader>();
 
         private _onCompleteObserver: Nullable<Observer<GLTFFileLoader>>;
         public set onComplete(callback: () => void) {
@@ -167,7 +184,7 @@ module BABYLON {
         /**
         * Raised when the loader is disposed.
         */
-        public onDisposeObservable = new Observable<GLTFFileLoader>();
+        public readonly onDisposeObservable = new Observable<GLTFFileLoader>();
 
         private _onDisposeObserver: Nullable<Observer<GLTFFileLoader>>;
         public set onDispose(callback: () => void) {
@@ -177,6 +194,20 @@ module BABYLON {
             this._onDisposeObserver = this.onDisposeObservable.add(callback);
         }
 
+        /**
+         * The loader state or null if not active.
+         */
+        public get loaderState(): Nullable<GLTFLoaderState> {
+            return this._loader ? this._loader.state : null;
+        }
+
+        /**
+         * The loader extensions or null if not active.
+         */
+        public get loaderExtensions(): Nullable<IGLTFLoaderExtensions> {
+            return this._loader ? this._loader.extensions : null;
+        }
+
         // #endregion
 
         private _loader: Nullable<IGLTFLoader> = null;
@@ -207,59 +238,35 @@ module BABYLON {
             this.onDisposeObservable.clear();
         }
 
-        public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
-            try {
+        public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }> {
+            return Promise.resolve().then(() => {
                 const loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
-                this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
-            }
-            catch (e) {
-                if (onError) {
-                    onError(e.message, e);
-                }
-                else {
-                    Tools.Error(e.message);
-                }
-            }
+                return this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onProgress);
+            });
         }
 
-        public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
-            try {
+        public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
+            return Promise.resolve().then(() => {
                 const loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
-                this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
-            }
-            catch (e) {
-                if (onError) {
-                    onError(e.message, e);
-                }
-                else {
-                    Tools.Error(e.message);
-                }
-            }
+                return this._loader.loadAsync(scene, loaderData, rootUrl, onProgress);
+            });
         }
 
-        public loadAssetsAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess: (assets:AssetContainer) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
-            try {
+        public loadAssetContainerAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<AssetContainer> {
+            return Promise.resolve().then(() => {
                 const loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
-                this._loader.importMeshAsync(null, scene, loaderData, rootUrl,  (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => {
+                return this._loader.importMeshAsync(null, scene, loaderData, rootUrl, onProgress).then(result => {
                     var container = new AssetContainer(scene);
-                    Array.prototype.push.apply(container.meshes, meshes)
-                    Array.prototype.push.apply(container.particleSystems, particleSystems)
-                    Array.prototype.push.apply(container.skeletons, skeletons)
+                    Array.prototype.push.apply(container.meshes, result.meshes);
+                    Array.prototype.push.apply(container.particleSystems, result.particleSystems);
+                    Array.prototype.push.apply(container.skeletons, result.skeletons);
                     container.removeAllFromScene();
-                    onSuccess(container)
-                }, onProgress, onError);
-            }
-            catch (e) {
-                if (onError) {
-                    onError(e.message, e);
-                }
-                else {
-                    Tools.Error(e.message);
-                }
-            }
+                    return container;
+                });
+            });
         }
 
         public canDirectLoad(data: string): boolean {

+ 0 - 4
sandbox/index.js

@@ -36,10 +36,6 @@ if (BABYLON.Engine.isSupported()) {
     BABYLON.GLTFFileLoader.IncrementalLoading = false;
     BABYLON.SceneLoader.OnPluginActivatedObservable.add(function (plugin) {
         currentPluginName = plugin.name;
-
-        if (plugin.name === "gltf" && plugin instanceof BABYLON.GLTFFileLoader) {
-            plugin.compileMaterials = true;
-        }
     });
 
     // Resize

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

@@ -2626,8 +2626,13 @@
             var name = vertex + "+" + fragment + "@" + (defines ? defines : (<EffectCreationOptions>attributesNamesOrOptions).defines);
             if (this._compiledEffects[name]) {
                 var compiledEffect = <Effect>this._compiledEffects[name];
-                if (onCompiled && compiledEffect.isReady()) {
-                    onCompiled(compiledEffect);
+                if (onCompiled) {
+                    if (compiledEffect.isReady()) {
+                        onCompiled(compiledEffect);
+                    }
+                    else {
+                        compiledEffect.onCompileObservable.add(onCompiled, undefined, undefined, true);
+                    }
                 }
                 return compiledEffect;
             }

+ 20 - 0
src/Lights/Shadows/babylon.shadowGenerator.ts

@@ -57,6 +57,13 @@
         forceCompilation(onCompiled?: (generator: ShadowGenerator) => void, options?: Partial<{ useInstances: boolean }>): void;
 
         /**
+         * Forces all the attached effect to compile to enable rendering only once ready vs. lazyly compiling effects.
+         * @param options Sets of optional options forcing the compilation with different modes 
+         * @returns A promise that resolves when the compilation completes
+         */
+        forceCompilationAsync(options?: Partial<{ useInstances: boolean }>): Promise<void>;
+
+        /**
          * Serializes the shadow generator setup to a json object.
          * @returns The serialized JSON object 
          */
@@ -846,6 +853,19 @@
         }
 
         /**
+         * Forces all the attached effect to compile to enable rendering only once ready vs. lazyly compiling effects.
+         * @param options Sets of optional options forcing the compilation with different modes 
+         * @returns A promise that resolves when the compilation completes
+         */
+        public forceCompilationAsync(options?: Partial<{ useInstances: boolean }>): Promise<void> {
+            return new Promise(resolve => {
+                this.forceCompilation(() => {
+                    resolve();
+                }, options);
+            });
+        }
+
+        /**
          * Determine wheter the shadow generator is ready or not (mainly all effects and related post processes needs to be ready).
          * @param subMesh The submesh we want to render in the shadow map
          * @param useInstances Defines wether will draw in the map using instances

+ 292 - 295
src/Loading/Plugins/babylon.babylonFileLoader.ts

@@ -28,355 +28,352 @@
         return operation + " of " + (producer ? producer.file + " from " + producer.name + " version: " + producer.version + ", exporter version: " + producer.exporter_version : "unknown");
     }
 
-    var loadAssets = (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void, addToScene = false):Nullable<AssetContainer> => {
+    var loadAssetContainer = (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void, addToScene = false): AssetContainer => {
         var container = new AssetContainer(scene);
 
-            // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
-            // when SceneLoader.debugLogging = true (default), or exception encountered.
-            // Everything stored in var log instead of writing separate lines to support only writing in exception,
-            // and avoid problems with multiple concurrent .babylon loads.
-            var log = "importScene has failed JSON parse";
-            try {
-                var parsedData = JSON.parse(data);
-                log = "";
-                var fullDetails = SceneLoader.loggingLevel === SceneLoader.DETAILED_LOGGING;               
-
-                var index: number;
-                var cache: number;
-                // Lights
-                if (parsedData.lights !== undefined && parsedData.lights !== null) {
-                    for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
-                        var parsedLight = parsedData.lights[index];
-                        var light = Light.Parse(parsedLight, scene);
-                        if (light) {
-                            container.lights.push(light);
-                            log += (index === 0 ? "\n\tLights:" : "");
-                            log += "\n\t\t" + light.toString(fullDetails);
-                        }
+        // Entire method running in try block, so ALWAYS logs as far as it got, only actually writes details
+        // when SceneLoader.debugLogging = true (default), or exception encountered.
+        // Everything stored in var log instead of writing separate lines to support only writing in exception,
+        // and avoid problems with multiple concurrent .babylon loads.
+        var log = "importScene has failed JSON parse";
+        try {
+            var parsedData = JSON.parse(data);
+            log = "";
+            var fullDetails = SceneLoader.loggingLevel === SceneLoader.DETAILED_LOGGING;
+
+            var index: number;
+            var cache: number;
+            // Lights
+            if (parsedData.lights !== undefined && parsedData.lights !== null) {
+                for (index = 0, cache = parsedData.lights.length; index < cache; index++) {
+                    var parsedLight = parsedData.lights[index];
+                    var light = Light.Parse(parsedLight, scene);
+                    if (light) {
+                        container.lights.push(light);
+                        log += (index === 0 ? "\n\tLights:" : "");
+                        log += "\n\t\t" + light.toString(fullDetails);
                     }
                 }
+            }
 
-                // Animations
-                if (parsedData.animations !== undefined && parsedData.animations !== null) {
-                    for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
-                        var parsedAnimation = parsedData.animations[index];
-                        var animation = Animation.Parse(parsedAnimation);
-                        scene.animations.push(animation);
-                        container.animations.push(animation);
-                        log += (index === 0 ? "\n\tAnimations:" : "");
-                        log += "\n\t\t" + animation.toString(fullDetails);
-                    }
+            // Animations
+            if (parsedData.animations !== undefined && parsedData.animations !== null) {
+                for (index = 0, cache = parsedData.animations.length; index < cache; index++) {
+                    var parsedAnimation = parsedData.animations[index];
+                    var animation = Animation.Parse(parsedAnimation);
+                    scene.animations.push(animation);
+                    container.animations.push(animation);
+                    log += (index === 0 ? "\n\tAnimations:" : "");
+                    log += "\n\t\t" + animation.toString(fullDetails);
                 }
+            }
 
-                // Materials
-                if (parsedData.materials !== undefined && parsedData.materials !== null) {
-                    for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
-                        var parsedMaterial = parsedData.materials[index];
-                        var mat = Material.Parse(parsedMaterial, scene, rootUrl);
-                        container.materials.push(mat);
-                        log += (index === 0 ? "\n\tMaterials:" : "");
-                        log += "\n\t\t" + mat.toString(fullDetails);
-                    }
+            // Materials
+            if (parsedData.materials !== undefined && parsedData.materials !== null) {
+                for (index = 0, cache = parsedData.materials.length; index < cache; index++) {
+                    var parsedMaterial = parsedData.materials[index];
+                    var mat = Material.Parse(parsedMaterial, scene, rootUrl);
+                    container.materials.push(mat);
+                    log += (index === 0 ? "\n\tMaterials:" : "");
+                    log += "\n\t\t" + mat.toString(fullDetails);
                 }
+            }
 
-                if (parsedData.multiMaterials !== undefined && parsedData.multiMaterials !== null) {
-                    for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
-                        var parsedMultiMaterial = parsedData.multiMaterials[index];
-                        var mmat = Material.ParseMultiMaterial(parsedMultiMaterial, scene);
-                        container.multiMaterials.push(mmat);
-                        log += (index === 0 ? "\n\tMultiMaterials:" : "");
-                        log += "\n\t\t" + mmat.toString(fullDetails);
-                    }
+            if (parsedData.multiMaterials !== undefined && parsedData.multiMaterials !== null) {
+                for (index = 0, cache = parsedData.multiMaterials.length; index < cache; index++) {
+                    var parsedMultiMaterial = parsedData.multiMaterials[index];
+                    var mmat = Material.ParseMultiMaterial(parsedMultiMaterial, scene);
+                    container.multiMaterials.push(mmat);
+                    log += (index === 0 ? "\n\tMultiMaterials:" : "");
+                    log += "\n\t\t" + mmat.toString(fullDetails);
                 }
+            }
 
-                // Morph targets
-                if (parsedData.morphTargetManagers !== undefined && parsedData.morphTargetManagers !== null) {
-                    for (var managerData of parsedData.morphTargetManagers) {
-                        container.morphTargetManagers.push(MorphTargetManager.Parse(managerData, scene));
-                    }
+            // Morph targets
+            if (parsedData.morphTargetManagers !== undefined && parsedData.morphTargetManagers !== null) {
+                for (var managerData of parsedData.morphTargetManagers) {
+                    container.morphTargetManagers.push(MorphTargetManager.Parse(managerData, scene));
                 }
+            }
 
-                // Skeletons
-                if (parsedData.skeletons !== undefined && parsedData.skeletons !== null) {
-                    for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
-                        var parsedSkeleton = parsedData.skeletons[index];
-                        var skeleton = Skeleton.Parse(parsedSkeleton, scene);
-                        container.skeletons.push(skeleton);
-                        log += (index === 0 ? "\n\tSkeletons:" : "");
-                        log += "\n\t\t" + skeleton.toString(fullDetails);
-                    }
+            // Skeletons
+            if (parsedData.skeletons !== undefined && parsedData.skeletons !== null) {
+                for (index = 0, cache = parsedData.skeletons.length; index < cache; index++) {
+                    var parsedSkeleton = parsedData.skeletons[index];
+                    var skeleton = Skeleton.Parse(parsedSkeleton, scene);
+                    container.skeletons.push(skeleton);
+                    log += (index === 0 ? "\n\tSkeletons:" : "");
+                    log += "\n\t\t" + skeleton.toString(fullDetails);
                 }
+            }
 
-                // Geometries
-                var geometries = parsedData.geometries;
-                if (geometries !== undefined && geometries !== null) {
-                    var addedGeometry = new Array<Nullable<Geometry>>();
-                    // Boxes
-                    var boxes = geometries.boxes;
-                    if (boxes !== undefined && boxes !== null) {
-                        for (index = 0, cache = boxes.length; index < cache; index++) {
-                            var parsedBox = boxes[index];
-                            addedGeometry.push(BoxGeometry.Parse(parsedBox, scene));
-                        }
+            // Geometries
+            var geometries = parsedData.geometries;
+            if (geometries !== undefined && geometries !== null) {
+                var addedGeometry = new Array<Nullable<Geometry>>();
+                // Boxes
+                var boxes = geometries.boxes;
+                if (boxes !== undefined && boxes !== null) {
+                    for (index = 0, cache = boxes.length; index < cache; index++) {
+                        var parsedBox = boxes[index];
+                        addedGeometry.push(BoxGeometry.Parse(parsedBox, scene));
                     }
+                }
 
-                    // Spheres
-                    var spheres = geometries.spheres;
-                    if (spheres !== undefined && spheres !== null) {
-                        for (index = 0, cache = spheres.length; index < cache; index++) {
-                            var parsedSphere = spheres[index];
-                            addedGeometry.push(SphereGeometry.Parse(parsedSphere, scene));
-                        }
+                // Spheres
+                var spheres = geometries.spheres;
+                if (spheres !== undefined && spheres !== null) {
+                    for (index = 0, cache = spheres.length; index < cache; index++) {
+                        var parsedSphere = spheres[index];
+                        addedGeometry.push(SphereGeometry.Parse(parsedSphere, scene));
                     }
+                }
 
-                    // Cylinders
-                    var cylinders = geometries.cylinders;
-                    if (cylinders !== undefined && cylinders !== null) {
-                        for (index = 0, cache = cylinders.length; index < cache; index++) {
-                            var parsedCylinder = cylinders[index];
-                            addedGeometry.push(CylinderGeometry.Parse(parsedCylinder, scene));
-                        }
+                // Cylinders
+                var cylinders = geometries.cylinders;
+                if (cylinders !== undefined && cylinders !== null) {
+                    for (index = 0, cache = cylinders.length; index < cache; index++) {
+                        var parsedCylinder = cylinders[index];
+                        addedGeometry.push(CylinderGeometry.Parse(parsedCylinder, scene));
                     }
+                }
 
-                    // Toruses
-                    var toruses = geometries.toruses;
-                    if (toruses !== undefined && toruses !== null) {
-                        for (index = 0, cache = toruses.length; index < cache; index++) {
-                            var parsedTorus = toruses[index];
-                            addedGeometry.push(TorusGeometry.Parse(parsedTorus, scene));
-                        }
+                // Toruses
+                var toruses = geometries.toruses;
+                if (toruses !== undefined && toruses !== null) {
+                    for (index = 0, cache = toruses.length; index < cache; index++) {
+                        var parsedTorus = toruses[index];
+                        addedGeometry.push(TorusGeometry.Parse(parsedTorus, scene));
                     }
+                }
 
-                    // Grounds
-                    var grounds = geometries.grounds;
-                    if (grounds !== undefined && grounds !== null) {
-                        for (index = 0, cache = grounds.length; index < cache; index++) {
-                            var parsedGround = grounds[index];
-                            addedGeometry.push(GroundGeometry.Parse(parsedGround, scene));
-                        }
+                // Grounds
+                var grounds = geometries.grounds;
+                if (grounds !== undefined && grounds !== null) {
+                    for (index = 0, cache = grounds.length; index < cache; index++) {
+                        var parsedGround = grounds[index];
+                        addedGeometry.push(GroundGeometry.Parse(parsedGround, scene));
                     }
+                }
 
-                    // Planes
-                    var planes = geometries.planes;
-                    if (planes !== undefined && planes !== null) {
-                        for (index = 0, cache = planes.length; index < cache; index++) {
-                            var parsedPlane = planes[index];
-                            addedGeometry.push(PlaneGeometry.Parse(parsedPlane, scene));
-                        }
+                // Planes
+                var planes = geometries.planes;
+                if (planes !== undefined && planes !== null) {
+                    for (index = 0, cache = planes.length; index < cache; index++) {
+                        var parsedPlane = planes[index];
+                        addedGeometry.push(PlaneGeometry.Parse(parsedPlane, scene));
                     }
+                }
 
-                    // TorusKnots
-                    var torusKnots = geometries.torusKnots;
-                    if (torusKnots !== undefined && torusKnots !== null) {
-                        for (index = 0, cache = torusKnots.length; index < cache; index++) {
-                            var parsedTorusKnot = torusKnots[index];
-                            addedGeometry.push(TorusKnotGeometry.Parse(parsedTorusKnot, scene));
-                        }
+                // TorusKnots
+                var torusKnots = geometries.torusKnots;
+                if (torusKnots !== undefined && torusKnots !== null) {
+                    for (index = 0, cache = torusKnots.length; index < cache; index++) {
+                        var parsedTorusKnot = torusKnots[index];
+                        addedGeometry.push(TorusKnotGeometry.Parse(parsedTorusKnot, scene));
                     }
+                }
 
-                    // VertexData
-                    var vertexData = geometries.vertexData;
-                    if (vertexData !== undefined && vertexData !== null) {
-                        for (index = 0, cache = vertexData.length; index < cache; index++) {
-                            var parsedVertexData = vertexData[index];
-                            addedGeometry.push(Geometry.Parse(parsedVertexData, scene, rootUrl));
-                        }
+                // VertexData
+                var vertexData = geometries.vertexData;
+                if (vertexData !== undefined && vertexData !== null) {
+                    for (index = 0, cache = vertexData.length; index < cache; index++) {
+                        var parsedVertexData = vertexData[index];
+                        addedGeometry.push(Geometry.Parse(parsedVertexData, scene, rootUrl));
                     }
+                }
 
-                    addedGeometry.forEach((g)=>{
-                        if(g){
-                            container.geometries.push(g);
-                        }
-                    })
-                }
-                
-                // Transform nodes
-                if (parsedData.transformNodes !== undefined && parsedData.transformNodes !== null) {
-                    for (index = 0, cache = parsedData.transformNodes.length; index < cache; index++) {
-                        var parsedTransformNode = parsedData.transformNodes[index];
-                        var node = TransformNode.Parse(parsedTransformNode, scene, rootUrl);
-                        container.transformNodes.push(node);
+                addedGeometry.forEach((g) => {
+                    if (g) {
+                        container.geometries.push(g);
                     }
-                }                
+                })
+            }
 
-                // Meshes
-                if (parsedData.meshes !== undefined && parsedData.meshes !== null) {
-                    for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
-                        var parsedMesh = parsedData.meshes[index];
-                        var mesh = <AbstractMesh>Mesh.Parse(parsedMesh, scene, rootUrl);
-                        container.meshes.push(mesh);
-                        log += (index === 0 ? "\n\tMeshes:" : "");
-                        log += "\n\t\t" + mesh.toString(fullDetails);
-                    }
+            // Transform nodes
+            if (parsedData.transformNodes !== undefined && parsedData.transformNodes !== null) {
+                for (index = 0, cache = parsedData.transformNodes.length; index < cache; index++) {
+                    var parsedTransformNode = parsedData.transformNodes[index];
+                    var node = TransformNode.Parse(parsedTransformNode, scene, rootUrl);
+                    container.transformNodes.push(node);
                 }
+            }
 
-                // Cameras
-                if (parsedData.cameras !== undefined && parsedData.cameras !== null) {
-                    for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
-                        var parsedCamera = parsedData.cameras[index];
-                        var camera = Camera.Parse(parsedCamera, scene);
-                        container.cameras.push(camera);
-                        log += (index === 0 ? "\n\tCameras:" : "");
-                        log += "\n\t\t" + camera.toString(fullDetails);
-                    }
+            // Meshes
+            if (parsedData.meshes !== undefined && parsedData.meshes !== null) {
+                for (index = 0, cache = parsedData.meshes.length; index < cache; index++) {
+                    var parsedMesh = parsedData.meshes[index];
+                    var mesh = <AbstractMesh>Mesh.Parse(parsedMesh, scene, rootUrl);
+                    container.meshes.push(mesh);
+                    log += (index === 0 ? "\n\tMeshes:" : "");
+                    log += "\n\t\t" + mesh.toString(fullDetails);
                 }
+            }
 
-                // Browsing all the graph to connect the dots
-                for (index = 0, cache = scene.cameras.length; index < cache; index++) {
-                    var camera = scene.cameras[index];
-                    if (camera._waitingParentId) {
-                        camera.parent = scene.getLastEntryByID(camera._waitingParentId);
-                        camera._waitingParentId = null;
-                    }
+            // Cameras
+            if (parsedData.cameras !== undefined && parsedData.cameras !== null) {
+                for (index = 0, cache = parsedData.cameras.length; index < cache; index++) {
+                    var parsedCamera = parsedData.cameras[index];
+                    var camera = Camera.Parse(parsedCamera, scene);
+                    container.cameras.push(camera);
+                    log += (index === 0 ? "\n\tCameras:" : "");
+                    log += "\n\t\t" + camera.toString(fullDetails);
                 }
+            }
 
-                for (index = 0, cache = scene.lights.length; index < cache; index++) {
-                    let light = scene.lights[index];
-                    if (light && light._waitingParentId) {
-                        light.parent = scene.getLastEntryByID(light._waitingParentId);
-                        light._waitingParentId = null;
-                    }
+            // Browsing all the graph to connect the dots
+            for (index = 0, cache = scene.cameras.length; index < cache; index++) {
+                var camera = scene.cameras[index];
+                if (camera._waitingParentId) {
+                    camera.parent = scene.getLastEntryByID(camera._waitingParentId);
+                    camera._waitingParentId = null;
                 }
+            }
 
-                // Sounds
-                // TODO: add sound
-                var loadedSounds: Sound[] = [];
-                var loadedSound: Sound;
-                if (AudioEngine && parsedData.sounds !== undefined && parsedData.sounds !== null) {
-                    for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
-                        var parsedSound = parsedData.sounds[index];
-                        if (Engine.audioEngine.canUseWebAudio) {
-                            if (!parsedSound.url) parsedSound.url = parsedSound.name;
-                            if (!loadedSounds[parsedSound.url]) {
-                                loadedSound = Sound.Parse(parsedSound, scene, rootUrl);
-                                loadedSounds[parsedSound.url] = loadedSound;
-                                container.sounds.push(loadedSound);
-                            }
-                            else {
-                                container.sounds.push(Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]));
-                            }
-                        } else {
-                            container.sounds.push(new Sound(parsedSound.name, null, scene));
+            for (index = 0, cache = scene.lights.length; index < cache; index++) {
+                let light = scene.lights[index];
+                if (light && light._waitingParentId) {
+                    light.parent = scene.getLastEntryByID(light._waitingParentId);
+                    light._waitingParentId = null;
+                }
+            }
+
+            // Sounds
+            // TODO: add sound
+            var loadedSounds: Sound[] = [];
+            var loadedSound: Sound;
+            if (AudioEngine && parsedData.sounds !== undefined && parsedData.sounds !== null) {
+                for (index = 0, cache = parsedData.sounds.length; index < cache; index++) {
+                    var parsedSound = parsedData.sounds[index];
+                    if (Engine.audioEngine.canUseWebAudio) {
+                        if (!parsedSound.url) parsedSound.url = parsedSound.name;
+                        if (!loadedSounds[parsedSound.url]) {
+                            loadedSound = Sound.Parse(parsedSound, scene, rootUrl);
+                            loadedSounds[parsedSound.url] = loadedSound;
+                            container.sounds.push(loadedSound);
                         }
+                        else {
+                            container.sounds.push(Sound.Parse(parsedSound, scene, rootUrl, loadedSounds[parsedSound.url]));
+                        }
+                    } else {
+                        container.sounds.push(new Sound(parsedSound.name, null, scene));
                     }
                 }
+            }
 
-                loadedSounds = [];
+            loadedSounds = [];
 
-                // Connect parents & children and parse actions
-                for (index = 0, cache = scene.transformNodes.length; index < cache; index++) {
-                    var transformNode = scene.transformNodes[index];
-                    if (transformNode._waitingParentId) {
-                        transformNode.parent = scene.getLastEntryByID(transformNode._waitingParentId);
-                        transformNode._waitingParentId = null;
-                    }
-                }                
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    var mesh = scene.meshes[index];
-                    if (mesh._waitingParentId) {
-                        mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
-                        mesh._waitingParentId = null;
-                    }
-                    if (mesh._waitingActions) {
-                        ActionManager.Parse(mesh._waitingActions, mesh, scene);
-                        mesh._waitingActions = null;
-                    }
+            // Connect parents & children and parse actions
+            for (index = 0, cache = scene.transformNodes.length; index < cache; index++) {
+                var transformNode = scene.transformNodes[index];
+                if (transformNode._waitingParentId) {
+                    transformNode.parent = scene.getLastEntryByID(transformNode._waitingParentId);
+                    transformNode._waitingParentId = null;
+                }
+            }
+            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                var mesh = scene.meshes[index];
+                if (mesh._waitingParentId) {
+                    mesh.parent = scene.getLastEntryByID(mesh._waitingParentId);
+                    mesh._waitingParentId = null;
+                }
+                if (mesh._waitingActions) {
+                    ActionManager.Parse(mesh._waitingActions, mesh, scene);
+                    mesh._waitingActions = null;
                 }
+            }
 
-                // freeze world matrix application
-                for (index = 0, cache = scene.meshes.length; index < cache; index++) {
-                    var currentMesh = scene.meshes[index];
-                    if (currentMesh._waitingFreezeWorldMatrix) {
-                        currentMesh.freezeWorldMatrix();
-                        currentMesh._waitingFreezeWorldMatrix = null;
-                    } else {
-                        currentMesh.computeWorldMatrix(true);
-                    }
+            // freeze world matrix application
+            for (index = 0, cache = scene.meshes.length; index < cache; index++) {
+                var currentMesh = scene.meshes[index];
+                if (currentMesh._waitingFreezeWorldMatrix) {
+                    currentMesh.freezeWorldMatrix();
+                    currentMesh._waitingFreezeWorldMatrix = null;
+                } else {
+                    currentMesh.computeWorldMatrix(true);
                 }
+            }
 
-                // Particles Systems
-                if (parsedData.particleSystems !== undefined && parsedData.particleSystems !== null) {
-                    for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
-                        var parsedParticleSystem = parsedData.particleSystems[index];
-                        var ps = ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
-                        container.particleSystems.push(ps);
-                    }
+            // Particles Systems
+            if (parsedData.particleSystems !== undefined && parsedData.particleSystems !== null) {
+                for (index = 0, cache = parsedData.particleSystems.length; index < cache; index++) {
+                    var parsedParticleSystem = parsedData.particleSystems[index];
+                    var ps = ParticleSystem.Parse(parsedParticleSystem, scene, rootUrl);
+                    container.particleSystems.push(ps);
                 }
+            }
 
-                // Lens flares
-                if (parsedData.lensFlareSystems !== undefined && parsedData.lensFlareSystems !== null) {
-                    for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
-                        var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
-                        var lf = LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
-                        container.lensFlareSystems.push(lf);
-                    }
+            // Lens flares
+            if (parsedData.lensFlareSystems !== undefined && parsedData.lensFlareSystems !== null) {
+                for (index = 0, cache = parsedData.lensFlareSystems.length; index < cache; index++) {
+                    var parsedLensFlareSystem = parsedData.lensFlareSystems[index];
+                    var lf = LensFlareSystem.Parse(parsedLensFlareSystem, scene, rootUrl);
+                    container.lensFlareSystems.push(lf);
                 }
+            }
 
-                // Shadows
-                if (parsedData.shadowGenerators !== undefined && parsedData.shadowGenerators !== null) {
-                    for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
-                        var parsedShadowGenerator = parsedData.shadowGenerators[index];
-                        var sg = ShadowGenerator.Parse(parsedShadowGenerator, scene);
-                        container.shadowGenerators.push(sg);
-                    }
+            // Shadows
+            if (parsedData.shadowGenerators !== undefined && parsedData.shadowGenerators !== null) {
+                for (index = 0, cache = parsedData.shadowGenerators.length; index < cache; index++) {
+                    var parsedShadowGenerator = parsedData.shadowGenerators[index];
+                    var sg = ShadowGenerator.Parse(parsedShadowGenerator, scene);
+                    container.shadowGenerators.push(sg);
                 }
+            }
 
-                // Lights exclusions / inclusions
-                for (index = 0, cache = scene.lights.length; index < cache; index++) {
-                    let light = scene.lights[index];
-                    // Excluded check
-                    if (light._excludedMeshesIds.length > 0) {
-                        for (var excludedIndex = 0; excludedIndex < light._excludedMeshesIds.length; excludedIndex++) {
-                            var excludedMesh = scene.getMeshByID(light._excludedMeshesIds[excludedIndex]);
+            // Lights exclusions / inclusions
+            for (index = 0, cache = scene.lights.length; index < cache; index++) {
+                let light = scene.lights[index];
+                // Excluded check
+                if (light._excludedMeshesIds.length > 0) {
+                    for (var excludedIndex = 0; excludedIndex < light._excludedMeshesIds.length; excludedIndex++) {
+                        var excludedMesh = scene.getMeshByID(light._excludedMeshesIds[excludedIndex]);
 
-                            if (excludedMesh) {
-                                light.excludedMeshes.push(excludedMesh);
-                            }
+                        if (excludedMesh) {
+                            light.excludedMeshes.push(excludedMesh);
                         }
-
-                        light._excludedMeshesIds = [];
                     }
 
-                    // Included check
-                    if (light._includedOnlyMeshesIds.length > 0) {
-                        for (var includedOnlyIndex = 0; includedOnlyIndex < light._includedOnlyMeshesIds.length; includedOnlyIndex++) {
-                            var includedOnlyMesh = scene.getMeshByID(light._includedOnlyMeshesIds[includedOnlyIndex]);
+                    light._excludedMeshesIds = [];
+                }
 
-                            if (includedOnlyMesh) {
-                                light.includedOnlyMeshes.push(includedOnlyMesh);
-                            }
-                        }
+                // Included check
+                if (light._includedOnlyMeshesIds.length > 0) {
+                    for (var includedOnlyIndex = 0; includedOnlyIndex < light._includedOnlyMeshesIds.length; includedOnlyIndex++) {
+                        var includedOnlyMesh = scene.getMeshByID(light._includedOnlyMeshesIds[includedOnlyIndex]);
 
-                        light._includedOnlyMeshesIds = [];
+                        if (includedOnlyMesh) {
+                            light.includedOnlyMeshes.push(includedOnlyMesh);
+                        }
                     }
-                }
 
-                // Actions (scene)
-                if (parsedData.actions !== undefined && parsedData.actions !== null) {
-                    ActionManager.Parse(parsedData.actions, null, scene);
-                }
-                
-                if(!addToScene){
-                    container.removeAllFromScene();
+                    light._includedOnlyMeshesIds = [];
                 }
+            }
 
-                return container;
+            // Actions (scene)
+            if (parsedData.actions !== undefined && parsedData.actions !== null) {
+                ActionManager.Parse(parsedData.actions, null, scene);
+            }
 
-            } catch (err) {
-                let msg = logOperation("loadAssts", parsedData ? parsedData.producer : "Unknown") + log;
-                if (onError) {
-                    onError(msg, err);
-                } else {
-                    Tools.Log(msg);
-                    throw err;
-                }
-            } finally {
-                if (log !== null && SceneLoader.loggingLevel !== SceneLoader.NO_LOGGING) {
-                    Tools.Log(logOperation("loadAssts", parsedData ? parsedData.producer : "Unknown") + (SceneLoader.loggingLevel !== SceneLoader.MINIMAL_LOGGING ? log : ""));
-                }
+            if (!addToScene) {
+                container.removeAllFromScene();
+            }
+        } catch (err) {
+            let msg = logOperation("loadAssts", parsedData ? parsedData.producer : "Unknown") + log;
+            if (onError) {
+                onError(msg, err);
+            } else {
+                Tools.Log(msg);
+                throw err;
             }
+        } finally {
+            if (log !== null && SceneLoader.loggingLevel !== SceneLoader.NO_LOGGING) {
+                Tools.Log(logOperation("loadAssts", parsedData ? parsedData.producer : "Unknown") + (SceneLoader.loggingLevel !== SceneLoader.MINIMAL_LOGGING ? log : ""));
+            }
+        }
 
-            return null;
+        return container;
     }
 
     SceneLoader.RegisterPlugin({
@@ -647,26 +644,26 @@
                 }
                 scene.workerCollisions = !!parsedData.workerCollisions;
 
-                var container = loadAssets(scene, data, rootUrl, onerror, true);
-                if(!container){
+                var container = loadAssetContainer(scene, data, rootUrl, onerror, true);
+                if (!container) {
                     return false;
                 }
-                
-                if (parsedData.autoAnimate) {		
-                    scene.beginAnimation(scene, parsedData.autoAnimateFrom, parsedData.autoAnimateTo, parsedData.autoAnimateLoop, parsedData.autoAnimateSpeed || 1.0);		
+
+                if (parsedData.autoAnimate) {
+                    scene.beginAnimation(scene, parsedData.autoAnimateFrom, parsedData.autoAnimateTo, parsedData.autoAnimateLoop, parsedData.autoAnimateSpeed || 1.0);
                 }
 
-                if (parsedData.activeCameraID !== undefined && parsedData.activeCameraID !== null) {		
-                    scene.setActiveCameraByID(parsedData.activeCameraID);		
+                if (parsedData.activeCameraID !== undefined && parsedData.activeCameraID !== null) {
+                    scene.setActiveCameraByID(parsedData.activeCameraID);
                 }
 
                 // Environment texture		
-                if (parsedData.environmentTexture !== undefined && parsedData.environmentTexture !== null) {		
-                    scene.environmentTexture = CubeTexture.CreateFromPrefilteredData(rootUrl + parsedData.environmentTexture, scene);		
-                    if (parsedData.createDefaultSkybox === true) {		
-                        var skyboxScale = (scene.activeCamera !== undefined && scene.activeCamera !== null) ? (scene.activeCamera.maxZ - scene.activeCamera.minZ) / 2 : 1000;		
-                        var skyboxBlurLevel = parsedData.skyboxBlurLevel || 0;		
-                        scene.createDefaultSkybox(undefined, true, skyboxScale, skyboxBlurLevel);		
+                if (parsedData.environmentTexture !== undefined && parsedData.environmentTexture !== null) {
+                    scene.environmentTexture = CubeTexture.CreateFromPrefilteredData(rootUrl + parsedData.environmentTexture, scene);
+                    if (parsedData.createDefaultSkybox === true) {
+                        var skyboxScale = (scene.activeCamera !== undefined && scene.activeCamera !== null) ? (scene.activeCamera.maxZ - scene.activeCamera.minZ) / 2 : 1000;
+                        var skyboxBlurLevel = parsedData.skyboxBlurLevel || 0;
+                        scene.createDefaultSkybox(undefined, true, skyboxScale, skyboxBlurLevel);
                     }
                 }
                 // Finish
@@ -686,8 +683,8 @@
             }
             return false;
         },
-        loadAssets: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): Nullable<AssetContainer> =>{
-            var container = loadAssets(scene, data, rootUrl, onerror);
+        loadAssetContainer: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer => {
+            var container = loadAssetContainer(scene, data, rootUrl, onerror);
             return container;
         }
     });

+ 224 - 33
src/Loading/babylon.sceneLoader.ts

@@ -21,23 +21,112 @@
     }
 
     export interface ISceneLoaderPlugin {
+        /**
+         * The friendly name of this plugin.
+         */
         name: string;
+
+        /**
+         * The file extensions supported by this plugin.
+         */
         extensions: string | ISceneLoaderPluginExtensions;
-        importMesh: (meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[], onError?: (message: string, exception?: any) => void) => boolean;
-        load: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void) => boolean;
+
+        /**
+         * Import meshes into a scene.
+         * @param meshesNames An array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
+         * @param scene The scene to import into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param meshes The meshes array to import into
+         * @param particleSystems The particle systems array to import into
+         * @param skeletons The skeletons array to import into
+         * @param onError The callback when import fails
+         * @returns True if successful or false otherwise
+         */
+        importMesh(meshesNames: any, scene: Scene, data: any, rootUrl: string, meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[], onError?: (message: string, exception?: any) => void): boolean;
+
+        /**
+         * Load into a scene.
+         * @param scene The scene to load into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param onError The callback when import fails
+         * @returns true if successful or false otherwise
+         */
+        load(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): boolean;
+
+        /**
+         * The callback that returns true if the data can be directly loaded.
+         */
         canDirectLoad?: (data: string) => boolean;
+
+        /**
+         * The callback that allows custom handling of the root url based on the response url.
+         */
         rewriteRootURL?: (rootUrl: string, responseURL?: string) => string;
-        loadAssets: (scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void) => Nullable<AssetContainer>;
+
+        /**
+         * Load into an asset container.
+         * @param scene The scene to load into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param onError The callback when import fails
+         * @returns The loaded asset container
+         */
+        loadAssetContainer(scene: Scene, data: string, rootUrl: string, onError?: (message: string, exception?: any) => void): AssetContainer;
     }
 
     export interface ISceneLoaderPluginAsync {
+        /**
+         * The friendly name of this plugin.
+         */
         name: string;
+
+        /**
+         * The file extensions supported by this plugin.
+         */
         extensions: string | ISceneLoaderPluginExtensions;
-        importMeshAsync: (meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: string, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+
+        /**
+         * Import meshes into a scene.
+         * @param meshesNames An array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported
+         * @param scene The scene to import into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param onProgress The callback when the load progresses
+         * @returns The loaded meshes, particle systems, and skeletons
+         */
+        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }>;
+
+        /**
+         * Load into a scene.
+         * @param scene The scene to load into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param onProgress The callback when the load progresses
+         * @returns Nothing
+         */
+        loadAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
+
+        /**
+         * The callback that returns true if the data can be directly loaded.
+         */
         canDirectLoad?: (data: string) => boolean;
+
+        /**
+         * The callback that allows custom handling of the root url based on the response url.
+         */
         rewriteRootURL?: (rootUrl: string, responseURL?: string) => string;
-        loadAssetsAsync: (scene: Scene, data: string, rootUrl: string, onSuccess?: (assets: Nullable<AssetContainer>) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+
+        /**
+         * Load into an asset container.
+         * @param scene The scene to load into
+         * @param data The data to import
+         * @param rootUrl The root url for scene and resources
+         * @param onProgress The callback when the load progresses
+         * @returns The loaded asset container
+         */
+        loadAssetContainerAsync(scene: Scene, data: string, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<AssetContainer>;
     }
 
     interface IRegisteredPlugin {
@@ -209,7 +298,7 @@
                 let url = rootUrl + sceneFilename;
                 request = Tools.LoadFile(url, dataCallback, onProgress ? event => {
                     onProgress(SceneLoaderProgressEvent.FromProgressEvent(event));
-                }: undefined, database, useArrayBuffer, (request, exception) => {
+                } : undefined, database, useArrayBuffer, (request, exception) => {
                     onError("Failed to load scene." + (exception ? "" : " " + exception.message), exception);
                 });
             };
@@ -272,15 +361,17 @@
         }
 
         /**
-        * Import meshes into a scene
-        * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported 
-        * @param rootUrl a string that defines the root url for scene and resources
-        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
-        * @param scene the instance of BABYLON.Scene to append to
-        * @param onSuccess a callback with a list of imported meshes, particleSystems, and skeletons when import succeeds
-        * @param onProgress a callback with a progress event for each file being loaded
-        * @param onError a callback with the scene, a message, and possibly an exception when import fails
-        */
+         * Import meshes into a scene
+         * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported 
+         * @param rootUrl a string that defines the root url for scene and resources
+         * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+         * @param scene the instance of BABYLON.Scene to append to
+         * @param onSuccess a callback with a list of imported meshes, particleSystems, and skeletons when import succeeds
+         * @param onProgress a callback with a progress event for each file being loaded
+         * @param onError a callback with the scene, a message, and possibly an exception when import fails
+         * @param pluginExtension the extension used to determine the plugin
+         * @returns The loaded plugin
+         */
         public static ImportMesh(meshNames: any, rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: Nullable<(meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
                 Tools.Error("Wrong sceneFilename parameter");
@@ -357,15 +448,41 @@
                 }
                 else {
                     var asyncedPlugin = <ISceneLoaderPluginAsync>plugin;
-                    asyncedPlugin.importMeshAsync(meshNames, scene, data, rootUrl, (meshes, particleSystems, skeletons) => {
+                    asyncedPlugin.importMeshAsync(meshNames, scene, data, rootUrl, progressHandler).then(result => {
                         scene.loadingPluginName = plugin.name;
-                        successHandler(meshes, particleSystems, skeletons);
-                    }, progressHandler, errorHandler);
+                        successHandler(result.meshes, result.particleSystems, result.skeletons);
+                    }).catch(error => {
+                        errorHandler(error.message, error);
+                    });
                 }
             }, progressHandler, errorHandler, disposeHandler, pluginExtension);
         }
 
         /**
+        * Import meshes into a scene
+        * @param meshNames an array of mesh names, a single mesh name, or empty string for all meshes that filter what meshes are imported 
+        * @param rootUrl a string that defines the root url for scene and resources
+        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+        * @param scene the instance of BABYLON.Scene to append to
+        * @param onProgress a callback with a progress event for each file being loaded
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded list of imported meshes, particleSystems, and skeletons
+        */
+        public static ImportMeshAsync(meshNames: any, rootUrl: string, sceneFilename: string, scene: Scene, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, pluginExtension: Nullable<string> = null): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[] }> {
+            return new Promise((resolve, reject) => {
+                SceneLoader.ImportMesh(meshNames, rootUrl, sceneFilename, scene, (meshes, particleSystems, skeletons) => {
+                    resolve({
+                        meshes: meshes,
+                        particleSystems: particleSystems,
+                        skeletons: skeletons
+                    });
+                }, onProgress, (scene, message, exception) => {
+                    reject(exception || new Error(message));
+                });
+            });
+        }
+
+        /**
         * Load a scene
         * @param rootUrl a string that defines the root url for scene and resources
         * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
@@ -373,12 +490,33 @@
         * @param onSuccess a callback with the scene when import succeeds
         * @param onProgress a callback with a progress event for each file being loaded
         * @param onError a callback with the scene, a message, and possibly an exception when import fails
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded plugin
         */
         public static Load(rootUrl: string, sceneFilename: any, engine: Engine, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             return SceneLoader.Append(rootUrl, sceneFilename, new Scene(engine), onSuccess, onProgress, onError, pluginExtension);
         }
 
         /**
+        * Load a scene
+        * @param rootUrl a string that defines the root url for scene and resources
+        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+        * @param engine is the instance of BABYLON.Engine to use to create the scene
+        * @param onProgress a callback with a progress event for each file being loaded
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded scene
+        */
+        public static LoadAsync(rootUrl: string, sceneFilename: any, engine: Engine, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, pluginExtension: Nullable<string> = null): Promise<Scene> {
+            return new Promise((resolve, reject) => {
+                SceneLoader.Load(rootUrl, sceneFilename, engine, scene => {
+                    resolve(scene);
+                }, onProgress, (scene, message, exception) => {
+                    reject(exception || new Error(message));
+                }, pluginExtension);
+            });
+        }
+
+        /**
         * Append a scene
         * @param rootUrl a string that defines the root url for scene and resources
         * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
@@ -386,6 +524,8 @@
         * @param onSuccess a callback with the scene when import succeeds
         * @param onProgress a callback with a progress event for each file being loaded
         * @param onError a callback with the scene, a message, and possibly an exception when import fails
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded plugin
         */
         public static Append(rootUrl: string, sceneFilename: any, scene: Scene, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
@@ -454,10 +594,12 @@
                     successHandler();
                 } else {
                     var asyncedPlugin = <ISceneLoaderPluginAsync>plugin;
-                    asyncedPlugin.loadAsync(scene, data, rootUrl, () => {
+                    asyncedPlugin.loadAsync(scene, data, rootUrl, progressHandler).then(() => {
                         scene.loadingPluginName = plugin.name;
                         successHandler();
-                    }, progressHandler, errorHandler);
+                    }).catch(error => {
+                        errorHandler(error.message, error);
+                    });
                 }
 
                 if (SceneLoader.ShowLoadingScreen) {
@@ -467,7 +609,37 @@
                 }
             }, progressHandler, errorHandler, disposeHandler, pluginExtension);
         }
-        
+
+        /**
+        * Append a scene
+        * @param rootUrl a string that defines the root url for scene and resources
+        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+        * @param scene is the instance of BABYLON.Scene to append to
+        * @param onProgress a callback with a progress event for each file being loaded
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The given scene
+        */
+        public static AppendAsync(rootUrl: string, sceneFilename: any, scene: Scene, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, pluginExtension: Nullable<string> = null): Promise<Scene> {
+            return new Promise((resolve, reject) => {
+                SceneLoader.Append(rootUrl, sceneFilename, scene, scene => {
+                    resolve(scene);
+                }, onProgress, (scene, message, exception) => {
+                    reject(exception || new Error(message));
+                }, pluginExtension);
+            });
+        }
+
+        /**
+        * Load a scene into an asset container
+        * @param rootUrl a string that defines the root url for scene and resources
+        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+        * @param scene is the instance of BABYLON.Scene to append to
+        * @param onSuccess a callback with the scene when import succeeds
+        * @param onProgress a callback with a progress event for each file being loaded
+        * @param onError a callback with the scene, a message, and possibly an exception when import fails
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded plugin
+        */
         public static LoadAssetContainer(
             rootUrl: string,
             sceneFilename: any,
@@ -524,25 +696,25 @@
             };
 
             return SceneLoader._loadData(rootUrl, sceneFilename, scene, (plugin, data, responseURL) => {
-                if ((<any>plugin).loadAssets) {
+                if ((<any>plugin).loadAssetContainer) {
                     var syncedPlugin = <ISceneLoaderPlugin>plugin;
-                    var assetContainer = syncedPlugin.loadAssets(scene, data, rootUrl, errorHandler);
+                    var assetContainer = syncedPlugin.loadAssetContainer(scene, data, rootUrl, errorHandler);
                     if (!assetContainer) {
                         return;
                     }
 
                     scene.loadingPluginName = plugin.name;
                     successHandler(assetContainer);
-                } else if ((<any>plugin).loadAssetsAsync) {
+                } else if ((<any>plugin).loadAssetContainerAsync) {
                     var asyncedPlugin = <ISceneLoaderPluginAsync>plugin;
-                    asyncedPlugin.loadAssetsAsync(scene, data, rootUrl, (assetContainer) => {
-                        if(assetContainer){
-                            scene.loadingPluginName = plugin.name;
-                            successHandler(assetContainer);
-                        }
-                    }, progressHandler, errorHandler);
-                }else{
-                    errorHandler("LoadAssetContainer is not supported by this plugin. Plugin did not provide a loadAssets or loadAssetsAsync method.")
+                    asyncedPlugin.loadAssetContainerAsync(scene, data, rootUrl, progressHandler).then(assetContainer => {
+                        scene.loadingPluginName = plugin.name;
+                        successHandler(assetContainer);
+                    }).catch(error => {
+                        errorHandler(error.message, error);
+                    });
+                } else {
+                    errorHandler("LoadAssetContainer is not supported by this plugin. Plugin did not provide a loadAssetContainer or loadAssetContainerAsync method.")
                 }
 
                 if (SceneLoader.ShowLoadingScreen) {
@@ -552,5 +724,24 @@
                 }
             }, progressHandler, errorHandler, disposeHandler, pluginExtension);
         }
+
+        /**
+        * Load a scene into an asset container
+        * @param rootUrl a string that defines the root url for scene and resources
+        * @param sceneFilename a string that defines the name of the scene file. can start with "data:" following by the stringified version of the scene
+        * @param scene is the instance of BABYLON.Scene to append to
+        * @param onProgress a callback with a progress event for each file being loaded
+        * @param pluginExtension the extension used to determine the plugin
+        * @returns The loaded asset container
+        */
+        public static LoadAssetContainerAsync(rootUrl: string, sceneFilename: any, scene: Scene, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, pluginExtension: Nullable<string> = null): Promise<AssetContainer> {
+            return new Promise((resolve, reject) => {
+                SceneLoader.LoadAssetContainer(rootUrl, sceneFilename, scene, assetContainer => {
+                    resolve(assetContainer);
+                }, onProgress, (scene, message, exception) => {
+                    reject(exception || new Error(message));
+                }, pluginExtension);
+            });
+        }
     };
 }

+ 300 - 268
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -686,15 +686,295 @@
                 subMesh._materialDefines = new PBRMaterialDefines();
             }
 
-            var scene = this.getScene();
-            var defines = <PBRMaterialDefines>subMesh._materialDefines;
+            const defines = <PBRMaterialDefines>subMesh._materialDefines;
             if (!this.checkReadyOnEveryCall && subMesh.effect) {
-                if (defines._renderId === scene.getRenderId()) {
+                if (defines._renderId === this.getScene().getRenderId()) {
                     return true;
                 }
             }
 
-            var engine = scene.getEngine();
+            const scene = this.getScene();
+            const engine = scene.getEngine();
+
+            if (defines._areTexturesDirty) {
+                if (scene.texturesEnabled) {
+                    if (this._albedoTexture && StandardMaterial.DiffuseTextureEnabled) {
+                        if (!this._albedoTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (this._ambientTexture && StandardMaterial.AmbientTextureEnabled) {
+                        if (!this._ambientTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (this._opacityTexture && StandardMaterial.OpacityTextureEnabled) {
+                        if (!this._opacityTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    var reflectionTexture = this._getReflectionTexture();
+                    if (reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
+                        if (!reflectionTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (this._lightmapTexture && StandardMaterial.LightmapTextureEnabled) {
+                        if (!this._lightmapTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (this._emissiveTexture && StandardMaterial.EmissiveTextureEnabled) {
+                        if (!this._emissiveTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (StandardMaterial.SpecularTextureEnabled) {
+                        if (this._metallicTexture) {
+                            if (!this._metallicTexture.isReadyOrNotBlocking()) {
+                                return false;
+                            }
+                        }
+                        else if (this._reflectivityTexture) {
+                            if (!this._reflectivityTexture.isReadyOrNotBlocking()) {
+                                return false;
+                            }
+                        }
+
+                        if (this._microSurfaceTexture) {
+                            if (!this._microSurfaceTexture.isReadyOrNotBlocking()) {
+                                return false;
+                            }
+                        }
+                    }
+
+                    if (engine.getCaps().standardDerivatives && this._bumpTexture && StandardMaterial.BumpTextureEnabled && !this._disableBumpMap) {
+                        // Bump texture cannot be not blocking.
+                        if (!this._bumpTexture.isReady()) {
+                            return false;
+                        }
+                    }
+
+                    var refractionTexture = this._getRefractionTexture();
+                    if (refractionTexture && StandardMaterial.RefractionTextureEnabled) {
+                        if (!refractionTexture.isReadyOrNotBlocking()) {
+                            return false;
+                        }
+                    }
+
+                    if (this._environmentBRDFTexture && StandardMaterial.ReflectionTextureEnabled) {
+                        // This is blocking.
+                        if (!this._environmentBRDFTexture.isReady()) {
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            if (defines._areImageProcessingDirty) {
+                if (!this._imageProcessingConfiguration.isReady()) {
+                    return false;
+                }
+            }
+
+            if (!engine.getCaps().standardDerivatives) {
+                let bufferMesh = null;
+                if (mesh.getClassName() === "InstancedMesh") {
+                    bufferMesh = (mesh as InstancedMesh).sourceMesh;
+                }
+                else if (mesh.getClassName() === "Mesh") {
+                    bufferMesh = mesh as Mesh;
+                }
+
+                if (bufferMesh && bufferMesh.geometry && bufferMesh.geometry.isReady() && !bufferMesh.geometry.isVerticesDataPresent(VertexBuffer.NormalKind)) {
+                    bufferMesh.createNormals(true);
+                    Tools.Warn("PBRMaterial: Normals have been created for the mesh: " + bufferMesh.name);
+                }
+            }
+
+            const effect = this._prepareEffect(mesh, defines, this.onCompiled, this.onError, useInstances);
+            if (effect) {
+                scene.resetCachedMaterial();
+                subMesh.setEffect(effect, defines);
+                this.buildUniformLayout();
+            }
+
+            if (!subMesh.effect || !subMesh.effect.isReady()) {
+                return false;
+            }
+
+            defines._renderId = scene.getRenderId();
+            this._wasPreviouslyReady = true;
+
+            return true;
+        }
+
+        private _prepareEffect(mesh: AbstractMesh, defines: PBRMaterialDefines, onCompiled: Nullable<(effect: Effect) => void> = null, onError: Nullable<(effect: Effect, errors: string) => void> = null, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null): Nullable<Effect> {
+            this._prepareDefines(mesh, defines, useInstances, useClipPlane);
+            if (!defines.isDirty) {
+                return null;
+            }
+
+            defines.markAsProcessed();
+
+            const scene = this.getScene();
+            const engine = scene.getEngine();
+
+            // Fallbacks
+            var fallbacks = new EffectFallbacks();
+            var fallbackRank = 0;
+            if (defines.USESPHERICALINVERTEX) {
+                fallbacks.addFallback(fallbackRank++, "USESPHERICALINVERTEX");
+            }
+
+            if (defines.FOG) {
+                fallbacks.addFallback(fallbackRank, "FOG");
+            }
+            if (defines.POINTSIZE) {
+                fallbacks.addFallback(fallbackRank, "POINTSIZE");
+            }
+            if (defines.LOGARITHMICDEPTH) {
+                fallbacks.addFallback(fallbackRank, "LOGARITHMICDEPTH");
+            }
+            if (defines.PARALLAX) {
+                fallbacks.addFallback(fallbackRank, "PARALLAX");
+            }
+            if (defines.PARALLAXOCCLUSION) {
+                fallbacks.addFallback(fallbackRank++, "PARALLAXOCCLUSION");
+            }
+
+            if (defines.ENVIRONMENTBRDF) {
+                fallbacks.addFallback(fallbackRank++, "ENVIRONMENTBRDF");
+            }
+
+            if (defines.TANGENT) {
+                fallbacks.addFallback(fallbackRank++, "TANGENT");
+            }
+
+            if (defines.BUMP) {
+                fallbacks.addFallback(fallbackRank++, "BUMP");
+            }
+
+            fallbackRank = MaterialHelper.HandleFallbacksForShadows(defines, fallbacks, this._maxSimultaneousLights, fallbackRank++);
+
+            if (defines.SPECULARTERM) {
+                fallbacks.addFallback(fallbackRank++, "SPECULARTERM");
+            }
+
+            if (defines.USESPHERICALFROMREFLECTIONMAP) {
+                fallbacks.addFallback(fallbackRank++, "USESPHERICALFROMREFLECTIONMAP");
+            }
+
+            if (defines.LIGHTMAP) {
+                fallbacks.addFallback(fallbackRank++, "LIGHTMAP");
+            }
+
+            if (defines.NORMAL) {
+                fallbacks.addFallback(fallbackRank++, "NORMAL");
+            }
+
+            if (defines.AMBIENT) {
+                fallbacks.addFallback(fallbackRank++, "AMBIENT");
+            }
+
+            if (defines.EMISSIVE) {
+                fallbacks.addFallback(fallbackRank++, "EMISSIVE");
+            }
+
+            if (defines.VERTEXCOLOR) {
+                fallbacks.addFallback(fallbackRank++, "VERTEXCOLOR");
+            }
+
+            if (defines.NUM_BONE_INFLUENCERS > 0) {
+                fallbacks.addCPUSkinningFallback(fallbackRank++, mesh);
+            }
+
+            if (defines.MORPHTARGETS) {
+                fallbacks.addFallback(fallbackRank++, "MORPHTARGETS");
+            }
+
+            //Attributes
+            var attribs = [VertexBuffer.PositionKind];
+
+            if (defines.NORMAL) {
+                attribs.push(VertexBuffer.NormalKind);
+            }
+
+            if (defines.TANGENT) {
+                attribs.push(VertexBuffer.TangentKind);
+            }
+
+            if (defines.UV1) {
+                attribs.push(VertexBuffer.UVKind);
+            }
+
+            if (defines.UV2) {
+                attribs.push(VertexBuffer.UV2Kind);
+            }
+
+            if (defines.VERTEXCOLOR) {
+                attribs.push(VertexBuffer.ColorKind);
+            }
+
+            MaterialHelper.PrepareAttributesForBones(attribs, mesh, defines, fallbacks);
+            MaterialHelper.PrepareAttributesForInstances(attribs, defines);
+            MaterialHelper.PrepareAttributesForMorphTargets(attribs, mesh, defines);
+
+            var uniforms = ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vEmissiveColor", "vReflectionColor",
+                "vFogInfos", "vFogColor", "pointSize",
+                "vAlbedoInfos", "vAmbientInfos", "vOpacityInfos", "vReflectionInfos", "vEmissiveInfos", "vReflectivityInfos", "vMicroSurfaceSamplerInfos", "vBumpInfos", "vLightmapInfos", "vRefractionInfos",
+                "mBones",
+                "vClipPlane", "albedoMatrix", "ambientMatrix", "opacityMatrix", "reflectionMatrix", "emissiveMatrix", "reflectivityMatrix", "microSurfaceSamplerMatrix", "bumpMatrix", "lightmapMatrix", "refractionMatrix",
+                "vLightingIntensity",
+                "logarithmicDepthConstant",
+                "vSphericalX", "vSphericalY", "vSphericalZ",
+                "vSphericalXX", "vSphericalYY", "vSphericalZZ",
+                "vSphericalXY", "vSphericalYZ", "vSphericalZX",
+                "vReflectionMicrosurfaceInfos", "vRefractionMicrosurfaceInfos",
+                "vTangentSpaceParams"
+            ];
+
+            var samplers = ["albedoSampler", "reflectivitySampler", "ambientSampler", "emissiveSampler",
+                "bumpSampler", "lightmapSampler", "opacitySampler",
+                "refractionSampler", "refractionSamplerLow", "refractionSamplerHigh",
+                "reflectionSampler", "reflectionSamplerLow", "reflectionSamplerHigh",
+                "microSurfaceSampler", "environmentBrdfSampler"];
+            var uniformBuffers = ["Material", "Scene"];
+
+            ImageProcessingConfiguration.PrepareUniforms(uniforms, defines);
+            ImageProcessingConfiguration.PrepareSamplers(samplers, defines);
+
+            MaterialHelper.PrepareUniformsAndSamplersList(<EffectCreationOptions>{
+                uniformsNames: uniforms,
+                uniformBuffersNames: uniformBuffers,
+                samplers: samplers,
+                defines: defines,
+                maxSimultaneousLights: this._maxSimultaneousLights
+            });
+
+            var join = defines.toString();
+            return engine.createEffect("pbr", <EffectCreationOptions>{
+                attributes: attribs,
+                uniformsNames: uniforms,
+                uniformBuffersNames: uniformBuffers,
+                samplers: samplers,
+                defines: join,
+                fallbacks: fallbacks,
+                onCompiled: onCompiled,
+                onError: onError,
+                indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
+            }, engine);
+        }
+
+        private _prepareDefines(mesh: AbstractMesh, defines: PBRMaterialDefines, useInstances: Nullable<boolean> = null, useClipPlane: Nullable<boolean> = null): void {
+            const scene = this.getScene();
+            const engine = scene.getEngine();
 
             // Lights
             MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, true, this._maxSimultaneousLights, this._disableLighting);
@@ -709,20 +989,12 @@
                     }
 
                     if (this._albedoTexture && StandardMaterial.DiffuseTextureEnabled) {
-                        if (!this._albedoTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._albedoTexture, defines, "ALBEDO");
                     } else {
                         defines.ALBEDO = false;
                     }
 
                     if (this._ambientTexture && StandardMaterial.AmbientTextureEnabled) {
-                        if (!this._ambientTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._ambientTexture, defines, "AMBIENT");
                         defines.AMBIENTINGRAYSCALE = this._useAmbientInGrayScale;
                     } else {
@@ -730,10 +1002,6 @@
                     }
 
                     if (this._opacityTexture && StandardMaterial.OpacityTextureEnabled) {
-                        if (!this._opacityTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._opacityTexture, defines, "OPACITY");
                         defines.OPACITYRGB = this._opacityTexture.getAlphaFromRGB;
                     } else {
@@ -742,10 +1010,6 @@
 
                     var reflectionTexture = this._getReflectionTexture();
                     if (reflectionTexture && StandardMaterial.ReflectionTextureEnabled) {
-                        if (!reflectionTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         defines.REFLECTION = true;
                         defines.GAMMAREFLECTION = reflectionTexture.gammaSpace;
                         defines.REFLECTIONMAP_OPPOSITEZ = this.getScene().useRightHandedSystem ? !reflectionTexture.invertZ : reflectionTexture.invertZ;
@@ -820,10 +1084,6 @@
                     }
 
                     if (this._lightmapTexture && StandardMaterial.LightmapTextureEnabled) {
-                        if (!this._lightmapTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._lightmapTexture, defines, "LIGHTMAP");
                         defines.USELIGHTMAPASSHADOWMAP = this._useLightmapAsShadowmap;
                     } else {
@@ -831,10 +1091,6 @@
                     }
 
                     if (this._emissiveTexture && StandardMaterial.EmissiveTextureEnabled) {
-                        if (!this._emissiveTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._emissiveTexture, defines, "EMISSIVE");
                     } else {
                         defines.EMISSIVE = false;
@@ -842,10 +1098,6 @@
 
                     if (StandardMaterial.SpecularTextureEnabled) {
                         if (this._metallicTexture) {
-                            if (!this._metallicTexture.isReadyOrNotBlocking()) {
-                                return false;
-                            }
-
                             MaterialHelper.PrepareDefinesForMergedUV(this._metallicTexture, defines, "REFLECTIVITY");
                             defines.METALLICWORKFLOW = true;
                             defines.ROUGHNESSSTOREINMETALMAPALPHA = this._useRoughnessFromMetallicTextureAlpha;
@@ -854,10 +1106,6 @@
                             defines.AOSTOREINMETALMAPRED = this._useAmbientOcclusionFromMetallicTextureRed;
                         }
                         else if (this._reflectivityTexture) {
-                            if (!this._reflectivityTexture.isReadyOrNotBlocking()) {
-                                return false;
-                            }
-
                             defines.METALLICWORKFLOW = false;
                             MaterialHelper.PrepareDefinesForMergedUV(this._reflectivityTexture, defines, "REFLECTIVITY");
                             defines.MICROSURFACEFROMREFLECTIVITYMAP = this._useMicroSurfaceFromReflectivityMapAlpha;
@@ -868,10 +1116,6 @@
                         }
 
                         if (this._microSurfaceTexture) {
-                            if (!this._microSurfaceTexture.isReadyOrNotBlocking()) {
-                                return false;
-                            }
-
                             MaterialHelper.PrepareDefinesForMergedUV(this._microSurfaceTexture, defines, "MICROSURFACEMAP");
                         } else {
                             defines.MICROSURFACEMAP = false;
@@ -882,11 +1126,6 @@
                     }
 
                     if (scene.getEngine().getCaps().standardDerivatives && this._bumpTexture && StandardMaterial.BumpTextureEnabled && !this._disableBumpMap) {
-                        // Bump texure can not be none blocking.
-                        if (!this._bumpTexture.isReady()) {
-                            return false;
-                        }
-
                         MaterialHelper.PrepareDefinesForMergedUV(this._bumpTexture, defines, "BUMP");
 
                         if (this._useParallax && this._albedoTexture && StandardMaterial.DiffuseTextureEnabled) {
@@ -902,10 +1141,6 @@
 
                     var refractionTexture = this._getRefractionTexture();
                     if (refractionTexture && StandardMaterial.RefractionTextureEnabled) {
-                        if (!refractionTexture.isReadyOrNotBlocking()) {
-                            return false;
-                        }
-
                         defines.REFRACTION = true;
                         defines.REFRACTIONMAP_3D = refractionTexture.isCube;
                         defines.GAMMAREFRACTION = refractionTexture.gammaSpace;
@@ -920,10 +1155,6 @@
                     }
 
                     if (this._environmentBRDFTexture && StandardMaterial.ReflectionTextureEnabled) {
-                        // This is blocking.
-                        if (!this._environmentBRDFTexture.isReady()) {
-                            return false;
-                        }
                         defines.ENVIRONMENTBRDF = true;
                     } else {
                         defines.ENVIRONMENTBRDF = false;
@@ -934,7 +1165,6 @@
                     } else {
                         defines.ALPHAFROMALBEDO = false;
                     }
-
                 }
 
                 defines.SPECULAROVERALPHA = this._useSpecularOverAlpha;
@@ -963,10 +1193,6 @@
             }
 
             if (defines._areImageProcessingDirty) {
-                if (!this._imageProcessingConfiguration.isReady()) {
-                    return false;
-                }
-
                 this._imageProcessingConfiguration.prepareDefines(defines);
             }
 
@@ -980,221 +1206,27 @@
             MaterialHelper.PrepareDefinesForMisc(mesh, scene, this._useLogarithmicDepth, this.pointsCloud, this.fogEnabled, this._shouldTurnAlphaTestOn(mesh) || this._forceAlphaTest, defines);
 
             // Values that need to be evaluated on every frame
-            MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false);
+            MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false, useClipPlane);
 
             // Attribs
-            if (MaterialHelper.PrepareDefinesForAttributes(mesh, defines, true, true, true, this._transparencyMode !== PBRMaterial.PBRMATERIAL_OPAQUE) && mesh) {
-                let bufferMesh = null;
-                if (mesh instanceof InstancedMesh) {
-                    bufferMesh = (mesh as InstancedMesh).sourceMesh;
-                }
-                else if (mesh instanceof Mesh) {
-                    bufferMesh = mesh as Mesh;
-                }
-
-                if (bufferMesh) {
-                    if (bufferMesh.isVerticesDataPresent(VertexBuffer.NormalKind)) {
-                        // If the first normal's components is the zero vector in one of the submeshes, we have invalid normals
-                        let normalVertexBuffer = bufferMesh.getVertexBuffer(VertexBuffer.NormalKind);
-                        let normals = normalVertexBuffer!.getData();
-                        let vertexBufferOffset = normalVertexBuffer!.getOffset();
-                        let strideSize = normalVertexBuffer!.getStrideSize();
-                        let offset = vertexBufferOffset + subMesh.indexStart * strideSize;
-
-                        if (normals![offset] === 0 && normals![offset + 1] === 0 && normals![offset + 2] === 0) {
-                            defines.NORMAL = false;
-                        }
-                        if (bufferMesh.isVerticesDataPresent(VertexBuffer.TangentKind)) {
-                            // If the first tangent's components is the zero vector in one of the submeshes, we have invalid tangents
-                            let tangentVertexBuffer = bufferMesh.getVertexBuffer(VertexBuffer.TangentKind);
-                            let tangents = tangentVertexBuffer!.getData();
-                            let vertexBufferOffset = tangentVertexBuffer!.getOffset();
-                            let strideSize = tangentVertexBuffer!.getStrideSize();
-                            let offset = vertexBufferOffset + subMesh.indexStart * strideSize;
-
-                            if (tangents![offset] === 0 && tangents![offset + 1] === 0 && tangents![offset + 2] === 0) {
-                                defines.TANGENT = false;
-                            }
-                        }
-                    }
-                    else {
-                        if (!scene.getEngine().getCaps().standardDerivatives) {
-                            bufferMesh.createNormals(true);
-                            Tools.Warn("PBRMaterial: Normals have been created for the mesh: " + bufferMesh.name);
-                        }
-                    }
-                }
-            }
-
-            // Get correct effect
-            if (defines.isDirty) {
-                defines.markAsProcessed();
-                scene.resetCachedMaterial();
-
-                // Fallbacks
-                var fallbacks = new EffectFallbacks();
-                var fallbackRank = 0;
-                if (defines.USESPHERICALINVERTEX) {
-                    fallbacks.addFallback(fallbackRank++, "USESPHERICALINVERTEX");
-                }
-
-                if (defines.FOG) {
-                    fallbacks.addFallback(fallbackRank, "FOG");
-                }
-                if (defines.POINTSIZE) {
-                    fallbacks.addFallback(fallbackRank, "POINTSIZE");
-                }
-                if (defines.LOGARITHMICDEPTH) {
-                    fallbacks.addFallback(fallbackRank, "LOGARITHMICDEPTH");
-                }
-                if (defines.PARALLAX) {
-                    fallbacks.addFallback(fallbackRank, "PARALLAX");
-                }
-                if (defines.PARALLAXOCCLUSION) {
-                    fallbacks.addFallback(fallbackRank++, "PARALLAXOCCLUSION");
-                }
-
-                if (defines.ENVIRONMENTBRDF) {
-                    fallbacks.addFallback(fallbackRank++, "ENVIRONMENTBRDF");
-                }
-
-                if (defines.TANGENT) {
-                    fallbacks.addFallback(fallbackRank++, "TANGENT");
-                }
-
-                if (defines.BUMP) {
-                    fallbacks.addFallback(fallbackRank++, "BUMP");
-                }
-
-                fallbackRank = MaterialHelper.HandleFallbacksForShadows(defines, fallbacks, this._maxSimultaneousLights, fallbackRank++);
-
-                if (defines.SPECULARTERM) {
-                    fallbacks.addFallback(fallbackRank++, "SPECULARTERM");
-                }
-
-                if (defines.USESPHERICALFROMREFLECTIONMAP) {
-                    fallbacks.addFallback(fallbackRank++, "USESPHERICALFROMREFLECTIONMAP");
-                }
-
-                if (defines.LIGHTMAP) {
-                    fallbacks.addFallback(fallbackRank++, "LIGHTMAP");
-                }
-
-                if (defines.NORMAL) {
-                    fallbacks.addFallback(fallbackRank++, "NORMAL");
-                }
-
-                if (defines.AMBIENT) {
-                    fallbacks.addFallback(fallbackRank++, "AMBIENT");
-                }
-
-                if (defines.EMISSIVE) {
-                    fallbacks.addFallback(fallbackRank++, "EMISSIVE");
-                }
-
-                if (defines.VERTEXCOLOR) {
-                    fallbacks.addFallback(fallbackRank++, "VERTEXCOLOR");
-                }
-
-                if (defines.NUM_BONE_INFLUENCERS > 0) {
-                    fallbacks.addCPUSkinningFallback(fallbackRank++, mesh);
-                }
-
-                if (defines.MORPHTARGETS) {
-                    fallbacks.addFallback(fallbackRank++, "MORPHTARGETS");
-                }
-
-                //Attributes
-                var attribs = [VertexBuffer.PositionKind];
-
-                if (defines.NORMAL) {
-                    attribs.push(VertexBuffer.NormalKind);
-                }
-
-                if (defines.TANGENT) {
-                    attribs.push(VertexBuffer.TangentKind);
-                }
-
-                if (defines.UV1) {
-                    attribs.push(VertexBuffer.UVKind);
-                }
+            MaterialHelper.PrepareDefinesForAttributes(mesh, defines, true, true, true, this._transparencyMode !== PBRMaterial.PBRMATERIAL_OPAQUE);
+        }
 
-                if (defines.UV2) {
-                    attribs.push(VertexBuffer.UV2Kind);
-                }
+        /**
+         * Force shader compilation
+         */
+        public forceCompilation(mesh: AbstractMesh, onCompiled?: (material: Material) => void, options?: Partial<{ clipPlane: boolean }>): void {
+            let localOptions = {
+                clipPlane: false,
+                ...options
+            };
 
-                if (defines.VERTEXCOLOR) {
-                    attribs.push(VertexBuffer.ColorKind);
+            const defines = new PBRMaterialDefines();
+            this._prepareEffect(mesh, defines, () => {
+                if (onCompiled) {
+                    onCompiled(this);
                 }
-
-                MaterialHelper.PrepareAttributesForBones(attribs, mesh, defines, fallbacks);
-                MaterialHelper.PrepareAttributesForInstances(attribs, defines);
-                MaterialHelper.PrepareAttributesForMorphTargets(attribs, mesh, defines);
-
-                var uniforms = ["world", "view", "viewProjection", "vEyePosition", "vLightsType", "vAmbientColor", "vAlbedoColor", "vReflectivityColor", "vEmissiveColor", "vReflectionColor",
-                    "vFogInfos", "vFogColor", "pointSize",
-                    "vAlbedoInfos", "vAmbientInfos", "vOpacityInfos", "vReflectionInfos", "vEmissiveInfos", "vReflectivityInfos", "vMicroSurfaceSamplerInfos", "vBumpInfos", "vLightmapInfos", "vRefractionInfos",
-                    "mBones",
-                    "vClipPlane", "albedoMatrix", "ambientMatrix", "opacityMatrix", "reflectionMatrix", "emissiveMatrix", "reflectivityMatrix", "microSurfaceSamplerMatrix", "bumpMatrix", "lightmapMatrix", "refractionMatrix",
-                    "vLightingIntensity",
-                    "logarithmicDepthConstant",
-                    "vSphericalX", "vSphericalY", "vSphericalZ",
-                    "vSphericalXX", "vSphericalYY", "vSphericalZZ",
-                    "vSphericalXY", "vSphericalYZ", "vSphericalZX",
-                    "vReflectionMicrosurfaceInfos", "vRefractionMicrosurfaceInfos",
-                    "vTangentSpaceParams"
-                ];
-
-                var samplers = ["albedoSampler", "reflectivitySampler", "ambientSampler", "emissiveSampler",
-                    "bumpSampler", "lightmapSampler", "opacitySampler",
-                    "refractionSampler", "refractionSamplerLow", "refractionSamplerHigh",
-                    "reflectionSampler", "reflectionSamplerLow", "reflectionSamplerHigh",
-                    "microSurfaceSampler", "environmentBrdfSampler"];
-                var uniformBuffers = ["Material", "Scene"];
-
-                ImageProcessingConfiguration.PrepareUniforms(uniforms, defines);
-                ImageProcessingConfiguration.PrepareSamplers(samplers, defines);
-
-                MaterialHelper.PrepareUniformsAndSamplersList(<EffectCreationOptions>{
-                    uniformsNames: uniforms,
-                    uniformBuffersNames: uniformBuffers,
-                    samplers: samplers,
-                    defines: defines,
-                    maxSimultaneousLights: this._maxSimultaneousLights
-                });
-
-                var onCompiled = (effect: Effect) => {
-                    if (this.onCompiled) {
-                        this.onCompiled(effect);
-                    }
-
-                    this.bindSceneUniformBuffer(effect, scene.getSceneUniformBuffer());
-                };
-
-                var join = defines.toString();
-                subMesh.setEffect(scene.getEngine().createEffect("pbr", <EffectCreationOptions>{
-                    attributes: attribs,
-                    uniformsNames: uniforms,
-                    uniformBuffersNames: uniformBuffers,
-                    samplers: samplers,
-                    defines: join,
-                    fallbacks: fallbacks,
-                    onCompiled: onCompiled,
-                    onError: this.onError,
-                    indexParameters: { maxSimultaneousLights: this._maxSimultaneousLights, maxSimultaneousMorphTargets: defines.NUM_MORPH_INFLUENCERS }
-                }, engine), defines);
-
-                this.buildUniformLayout();
-            }
-
-            if (!subMesh.effect || !subMesh.effect.isReady()) {
-                return false;
-            }
-
-            defines._renderId = scene.getRenderId();
-            this._wasPreviouslyReady = true;
-
-            return true;
+            }, undefined, undefined, localOptions.clipPlane);
         }
 
         /**

+ 16 - 2
src/Materials/babylon.material.ts

@@ -1020,7 +1020,7 @@
         }
 
         /**
-         * Force shader compilation including textures ready check
+         * Force shader compilation
          * @param mesh - BJS mesh.
          * @param onCompiled - function to execute once the material is compiled.
          * @param options - options to pass to this function.
@@ -1069,7 +1069,7 @@
                     }
                 }
 
-                if (options && options.clipPlane) {
+                if (localOptions.clipPlane) {
                     scene.clipPlane = clipPlaneState;
                 }
             };
@@ -1078,6 +1078,20 @@
         }
 
         /**
+         * Force shader compilation.
+         * @param mesh The mesh that will use this material
+         * @param options Additional options for compiling the shaders
+         * @returns A promise that resolves when the compilation completes
+         */
+        public forceCompilationAsync(mesh: AbstractMesh, options?: Partial<{ clipPlane: boolean }>): Promise<void> {
+            return new Promise(resolve => {
+                this.forceCompilation(mesh, () => {
+                    resolve();
+                }, options);
+            });
+        }
+
+        /**
          * Marks a define in the material to indicate that it needs to be re-computed.
          * @param flag - Material define flag.
          */

+ 8 - 3
src/Materials/babylon.materialHelper.ts

@@ -84,12 +84,17 @@ module BABYLON {
          * @param engine defines the current engine
          * @param defines specifies the list of active defines
          * @param useInstances defines if instances have to be turned on
+         * @param useClipPlane defines if clip plane have to be turned on
          */
-        public static PrepareDefinesForFrameBoundValues(scene: Scene, engine: Engine, defines: any, useInstances: boolean): void {
+        public static PrepareDefinesForFrameBoundValues(scene: Scene, engine: Engine, defines: any, useInstances: boolean, useClipPlane: Nullable<boolean> = null): void {
             var changed = false;
 
-            if (defines["CLIPPLANE"] !== (scene.clipPlane !== undefined && scene.clipPlane !== null)) {
-                defines["CLIPPLANE"] = !defines["CLIPPLANE"];
+            if (useClipPlane == null) {
+                useClipPlane = (scene.clipPlane !== undefined && scene.clipPlane !== null);
+            }
+
+            if (defines["CLIPPLANE"] !== useClipPlane) {
+                defines["CLIPPLANE"] = useClipPlane;
                 changed = true;
             }
 

+ 75 - 38
src/Mesh/babylon.mesh.vertexData.ts

@@ -291,8 +291,26 @@
          * Merges the passed VertexData into the current one.  
          * Returns the modified VertexData.  
          */
-        public merge(other: VertexData, options?: { tangentLength?: number }): VertexData {
-            options = options || {};
+        public merge(other: VertexData): VertexData {
+            this._validate();
+            other._validate();
+
+            if (!this.normals !== !other.normals ||
+                !this.tangents !== !other.tangents ||
+                !this.uvs !== !other.uvs ||
+                !this.uvs2 !== !other.uvs2 ||
+                !this.uvs3 !== !other.uvs3 ||
+                !this.uvs4 !== !other.uvs4 ||
+                !this.uvs5 !== !other.uvs5 ||
+                !this.uvs6 !== !other.uvs6 ||
+                !this.colors !== !other.colors ||
+                !this.matricesIndices !== !other.matricesIndices ||
+                !this.matricesWeights !== !other.matricesWeights ||
+                !this.matricesIndicesExtra !== !other.matricesIndicesExtra ||
+                !this.matricesWeightsExtra !== !other.matricesWeightsExtra)
+            {
+                throw new Error("Cannot merge vertex data that do not have the same set of attributes");
+            }
 
             if (other.indices) {
                 if (!this.indices) {
@@ -307,48 +325,29 @@
             }
 
             this.positions = this._mergeElement(this.positions, other.positions);
-
-            if (!this.positions) {
-                return this;
-            }
-
-            var count = this.positions.length / 3;
-
-            this.normals = this._mergeElement(this.normals, other.normals, count * 3);
-            this.tangents = this._mergeElement(this.tangents, other.tangents, count * (options.tangentLength || 4));
-            this.uvs = this._mergeElement(this.uvs, other.uvs, count * 2);
-            this.uvs2 = this._mergeElement(this.uvs2, other.uvs2, count * 2);
-            this.uvs3 = this._mergeElement(this.uvs3, other.uvs3, count * 2);
-            this.uvs4 = this._mergeElement(this.uvs4, other.uvs4, count * 2);
-            this.uvs5 = this._mergeElement(this.uvs5, other.uvs5, count * 2);
-            this.uvs6 = this._mergeElement(this.uvs6, other.uvs6, count * 2);
-            this.colors = this._mergeElement(this.colors, other.colors, count * 4, 1);
-            this.matricesIndices = this._mergeElement(this.matricesIndices, other.matricesIndices, count * 4);
-            this.matricesWeights = this._mergeElement(this.matricesWeights, other.matricesWeights, count * 4);
-            this.matricesIndicesExtra = this._mergeElement(this.matricesIndicesExtra, other.matricesIndicesExtra, count * 4);
-            this.matricesWeightsExtra = this._mergeElement(this.matricesWeightsExtra, other.matricesWeightsExtra, count * 4);
+            this.normals = this._mergeElement(this.normals, other.normals);
+            this.tangents = this._mergeElement(this.tangents, other.tangents);
+            this.uvs = this._mergeElement(this.uvs, other.uvs);
+            this.uvs2 = this._mergeElement(this.uvs2, other.uvs2);
+            this.uvs3 = this._mergeElement(this.uvs3, other.uvs3);
+            this.uvs4 = this._mergeElement(this.uvs4, other.uvs4);
+            this.uvs5 = this._mergeElement(this.uvs5, other.uvs5);
+            this.uvs6 = this._mergeElement(this.uvs6, other.uvs6);
+            this.colors = this._mergeElement(this.colors, other.colors);
+            this.matricesIndices = this._mergeElement(this.matricesIndices, other.matricesIndices);
+            this.matricesWeights = this._mergeElement(this.matricesWeights, other.matricesWeights);
+            this.matricesIndicesExtra = this._mergeElement(this.matricesIndicesExtra, other.matricesIndicesExtra);
+            this.matricesWeightsExtra = this._mergeElement(this.matricesWeightsExtra, other.matricesWeightsExtra);
             return this;
         }
 
-        private _mergeElement(source: Nullable<FloatArray>, other: Nullable<FloatArray>, length = 0, defaultValue = 0): Nullable<FloatArray> {
-            if (!other && !source) {
-                return null;
+        private _mergeElement(source: Nullable<FloatArray>, other: Nullable<FloatArray>): Nullable<FloatArray> {
+            if (!source) {
+                return other;
             }
 
             if (!other) {
-                var padding = new Float32Array((<FloatArray>source).length);
-                padding.fill(defaultValue);
-                return this._mergeElement(source, padding, length);
-            }
-
-            if (!source) {
-                if (length === 0 || length === other.length) {
-                    return other;
-                }
-
-                var padding = new Float32Array(length - other.length);
-                padding.fill(defaultValue);
-                return this._mergeElement(padding, other, length);
+                return source;
             }
 
             var len = other.length + source.length;
@@ -376,6 +375,44 @@
             }
         }
 
+        private _validate(): void {
+            if (!this.positions) {
+                throw new Error("Positions are required");
+            }
+
+            const getElementCount = (kind: string, values: FloatArray) => {
+                const stride = VertexBuffer.DeduceStride(kind);
+                if ((values.length % stride) !== 0) {
+                    throw new Error("The " + kind + "s array count must be a multiple of " + stride);
+                }
+
+                return values.length / stride;
+            };
+
+            const positionsElementCount = getElementCount(VertexBuffer.PositionKind, this.positions);
+
+            const validateElementCount = (kind: string, values: FloatArray) => {
+                const elementCount = getElementCount(kind, values);
+                if (elementCount !== positionsElementCount) {
+                    throw new Error("The " + kind + "s element count (" + elementCount + ") does not match the positions count (" + positionsElementCount + ")");
+                }
+            };
+
+            if (this.normals) validateElementCount(VertexBuffer.NormalKind, this.normals);
+            if (this.tangents) validateElementCount(VertexBuffer.TangentKind, this.tangents);
+            if (this.uvs) validateElementCount(VertexBuffer.UVKind, this.uvs);
+            if (this.uvs2) validateElementCount(VertexBuffer.UV2Kind, this.uvs2);
+            if (this.uvs3) validateElementCount(VertexBuffer.UV3Kind, this.uvs3);
+            if (this.uvs4) validateElementCount(VertexBuffer.UV4Kind, this.uvs4);
+            if (this.uvs5) validateElementCount(VertexBuffer.UV5Kind, this.uvs5);
+            if (this.uvs6) validateElementCount(VertexBuffer.UV6Kind, this.uvs6);
+            if (this.colors) validateElementCount(VertexBuffer.ColorKind, this.colors);
+            if (this.matricesIndices) validateElementCount(VertexBuffer.MatricesIndicesKind, this.matricesIndices);
+            if (this.matricesWeights) validateElementCount(VertexBuffer.MatricesWeightsKind, this.matricesWeights);
+            if (this.matricesIndicesExtra) validateElementCount(VertexBuffer.MatricesIndicesExtraKind, this.matricesIndicesExtra);
+            if (this.matricesWeightsExtra) validateElementCount(VertexBuffer.MatricesWeightsExtraKind, this.matricesWeightsExtra);
+        }
+
         /**
          * Serializes the VertexData.  
          * Returns a serialized object.  

+ 33 - 30
src/Mesh/babylon.vertexBuffer.ts

@@ -9,36 +9,7 @@
 
         constructor(engine: any, data: FloatArray | Buffer, kind: string, updatable: boolean, postponeInternalCreation?: boolean, stride?: number, instanced?: boolean, offset?: number, size?: number) {
             if (!stride) {
-                // Deduce stride from kind
-                switch (kind) {
-                    case VertexBuffer.PositionKind:
-                        stride = 3;
-                        break;
-                    case VertexBuffer.NormalKind:
-                        stride = 3;
-                        break;
-                    case VertexBuffer.UVKind:
-                    case VertexBuffer.UV2Kind:
-                    case VertexBuffer.UV3Kind:
-                    case VertexBuffer.UV4Kind:
-                    case VertexBuffer.UV5Kind:
-                    case VertexBuffer.UV6Kind:
-                        stride = 2;
-                        break;
-                    case VertexBuffer.TangentKind:
-                    case VertexBuffer.ColorKind:
-                        stride = 4;
-                        break;
-                    case VertexBuffer.MatricesIndicesKind:
-                    case VertexBuffer.MatricesIndicesExtraKind:
-                        stride = 4;
-                        break;
-                    case VertexBuffer.MatricesWeightsKind:
-                    case VertexBuffer.MatricesWeightsExtraKind:
-                    default:
-                        stride = 4;
-                        break;
-                }
+                stride = VertexBuffer.DeduceStride(kind);
             }
 
             if (data instanceof Buffer) {
@@ -240,5 +211,37 @@
         public static get MatricesWeightsExtraKind(): string {
             return VertexBuffer._MatricesWeightsExtraKind;
         }
+
+        /**
+         * Deduces the stride given a kind.
+         * @param kind The kind string to deduce
+         * @returns The deduced stride
+         */
+        public static DeduceStride(kind: string): number {
+            switch (kind) {
+                case VertexBuffer.PositionKind:
+                    return 3;
+                case VertexBuffer.NormalKind:
+                    return 3;
+                case VertexBuffer.UVKind:
+                case VertexBuffer.UV2Kind:
+                case VertexBuffer.UV3Kind:
+                case VertexBuffer.UV4Kind:
+                case VertexBuffer.UV5Kind:
+                case VertexBuffer.UV6Kind:
+                    return 2;
+                case VertexBuffer.TangentKind:
+                case VertexBuffer.ColorKind:
+                    return 4;
+                case VertexBuffer.MatricesIndicesKind:
+                case VertexBuffer.MatricesIndicesExtraKind:
+                    return 4;
+                case VertexBuffer.MatricesWeightsKind:
+                case VertexBuffer.MatricesWeightsExtraKind:
+                    return 4;
+                default:
+                    throw new Error("Invalid kind '" + kind + "'");
+            }
+        }
     }
 } 

+ 39 - 0
src/Tools/babylon.deferred.ts

@@ -0,0 +1,39 @@
+module BABYLON {
+    /**
+     * Wrapper class for promise with external resolve and reject.
+     */
+    export class Deferred<T> {
+        /**
+         * The promise associated with this deferred object.
+         */
+        public readonly promise: Promise<T>;
+
+        private _resolve: (value?: T | PromiseLike<T>) => void;
+        private _reject: (reason?: any) => void;
+
+        /**
+         * The resolve method of the promise associated with this deferred object.
+         */
+        public get resolve() {
+            return this._resolve;
+        }
+
+        /**
+         * The reject method of the promise associated with this deferred object.
+         */
+        public get reject() {
+            return this._reject;
+        }
+
+        /**
+         * Constructor for this deferred object.
+         */
+        constructor()
+        {
+            this.promise = new Promise((resolve, reject) => {
+                this._resolve = resolve;
+                this._reject = reject;
+            });
+        }
+    }
+}

+ 8 - 10
src/Tools/babylon.filesInput.ts

@@ -220,8 +220,12 @@
                     this._currentScene.dispose();
                 }
 
-                SceneLoader.Load("file:", this._sceneFileToLoad, this._engine, (newScene) => {
-                    this._currentScene = newScene;
+                SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, progress => {
+                    if (this._progressCallback) {
+                        this._progressCallback(progress);
+                    }
+                }).then(scene => {
+                    this._currentScene = scene;
 
                     if (this._sceneLoadedCallback) {
                         this._sceneLoadedCallback(this._sceneFileToLoad, this._currentScene);
@@ -233,15 +237,9 @@
                             this.renderFunction();
                         });
                     });
-                }, progress => {
-                    if (this._progressCallback) {
-                        this._progressCallback(progress);
-                    }
-                }, (scene, message) => {
-                    this._currentScene = scene;
-
+                }).catch(error => {
                     if (this._errorCallback) {
-                        this._errorCallback(this._sceneFileToLoad, this._currentScene, message);
+                        this._errorCallback(this._sceneFileToLoad, this._currentScene, error.message);
                     }
                 });
             }

+ 20 - 20
src/Tools/babylon.promise.ts

@@ -10,7 +10,7 @@ module BABYLON {
         public count = 0;
         public target = 0;
         public rootPromise: InternalPromise<T>;
-        public results:any[] = [];
+        public results: any[] = [];
     }
 
     class InternalPromise<T> {
@@ -28,32 +28,32 @@ module BABYLON {
 
         public get isFulfilled(): boolean {
             return this._state === PromiseStates.Fulfilled;
-        }            
+        }
 
         public get isRejected(): boolean {
             return this._state === PromiseStates.Rejected;
         }
-    
+
         public get isPending(): boolean {
             return this._state === PromiseStates.Pending;
         }
-    
+
         public value(): Nullable<T> | undefined {
             if (!this.isFulfilled) {
                 throw new Error("Promise is not fulfilled");
             }
             return this._result;
-        }     
-        
+        }
+
         public reason(): any {
             if (!this.isRejected) {
                 throw new Error("Promise is not rejected");
             }
             return this._reason;
-        }            
+        }
 
         public constructor(resolver?: (
-            resolve:(value?: Nullable<T>) => void, 
+            resolve: (value?: Nullable<T>) => void,
             reject: (reason: any) => void
         ) => void) {
 
@@ -67,8 +67,8 @@ module BABYLON {
                 }, (reason: any) => {
                     this._reject(reason);
                 });
-            } catch(e) {
-                this._reject((<Error>e).message);
+            } catch (e) {
+                this._reject(e);
             }
         }
 
@@ -103,7 +103,7 @@ module BABYLON {
             }
 
             return newPromise;
-        }       
+        }
 
         private _moveChildren(children: InternalPromise<T>[]): void {
             this._children.push(...children.splice(0, children.length));
@@ -111,14 +111,14 @@ module BABYLON {
             if (this.isFulfilled) {
                 for (var child of this._children) {
                     child._resolve(this._result);
-                } 
+                }
             } else if (this.isRejected) {
                 for (var child of this._children) {
                     child._reject(this._reason);
-                }                 
+                }
             }
         }
-        
+
         private _resolve(value?: Nullable<T>): Nullable<InternalPromise<T>> | T {
             try {
                 this._state = PromiseStates.Fulfilled;
@@ -133,7 +133,7 @@ module BABYLON {
                     if ((<InternalPromise<T>>returnedValue)._state !== undefined) {
                         // Transmit children
                         let returnedPromise = returnedValue as InternalPromise<T>;
-                        
+
                         returnedPromise._moveChildren(this._children);
                     } else {
                         value = <T>returnedValue;
@@ -142,11 +142,11 @@ module BABYLON {
 
                 for (var child of this._children) {
                     child._resolve(value);
-                }                
+                }
 
                 return returnedValue;
-            } catch(e) {
-                this._reject((<Error>e).message);
+            } catch (e) {
+                this._reject(e);
             }
 
             return null;
@@ -167,7 +167,7 @@ module BABYLON {
                 } else {
                     child._reject(reason);
                 }
-            }                
+            }
         }
 
         public static resolve<T>(value: T): InternalPromise<T> {
@@ -224,7 +224,7 @@ module BABYLON {
          */
         public static Apply(force = false): void {
             if (force || typeof Promise === 'undefined') {
-                let root:any = window;
+                let root: any = window;
                 root.Promise = InternalPromise;
             }
         }

+ 13 - 0
src/Tools/babylon.tools.ts

@@ -1526,6 +1526,19 @@
             }
             return hash;
         }
+
+        /**
+         * Returns a promise that resolves after the given amount of time.
+         * @param delay Number of milliseconds to delay
+         * @returns Promise that resolves after the given amount of time
+         */
+        public static DelayAsync(delay: number): Promise<void> {
+            return new Promise(resolve => {
+                setTimeout(() => {
+                    resolve();
+                }, delay);
+            });
+        }
     }
 
     /**

+ 12 - 0
src/babylon.scene.ts

@@ -2019,6 +2019,18 @@
             }, 150);
         }
 
+        /**
+         * Returns a promise that resolves when the scene is ready.
+         * @returns A promise that resolves when the scene is ready.
+         */
+        public whenReadyAsync(): Promise<void> {
+            return new Promise(resolve => {
+                this.executeWhenReady(() => {
+                    resolve();
+                });
+            });
+        }
+
         public _checkIsReady() {
             if (this.isReady()) {
                 this.onReadyObservable.notifyObservers(this);

+ 20 - 2
tests/unit/babylon/tools/babylon.tools.tests.ts

@@ -1,7 +1,14 @@
 /**
  * Describes the test suite.
  */
-describe('Babylon Tools', () => {
+describe('Example', function () {
+    /**
+     * Sets the timeout of all the tests to 10 seconds.
+     * Note the JavaScript function syntax in the describe callback.
+     * See https://mochajs.org/#arrow-functions
+     */
+    this.timeout(10000);
+
     /**
      * Loads the dependencies.
      */
@@ -10,6 +17,8 @@ describe('Babylon Tools', () => {
         (BABYLONDEVTOOLS).Loader
             .useDist()
             .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
                 done();
             });
     });
@@ -22,7 +31,7 @@ describe('Babylon Tools', () => {
         it('should be expoent of two', () => {
             var result : boolean = BABYLON.Tools.IsExponentOfTwo(2);
             expect(result).to.be.true; 
-            
+
             result = BABYLON.Tools.IsExponentOfTwo(4);
             result.should.be.true; 
             
@@ -41,4 +50,13 @@ describe('Babylon Tools', () => {
             assert.isFalse(result);
         });
     });
+
+    /**
+     * This test shows how to return an asynchronous operation with promises.
+     */
+    describe('#Promise', () => {
+        it('delay', () => {
+            return BABYLON.Tools.DelayAsync(100);
+        });
+    });
 });

+ 0 - 91
tests/unit/babylon/loading/babylon.sceneloader.tests.ts

@@ -1,91 +0,0 @@
-/**
- * Describes the test suite.
- */
-describe('Babylon SceneLoader', () => {
-    var subject : BABYLON.Engine;
-
-    /**
-     * Loads the dependencies.
-     */
-    before(function (done) {
-        this.timeout(180000);
-        (BABYLONDEVTOOLS).Loader
-            .useDist()
-            .load(function () {
-                done();
-            });
-    });
-
-    /**
-     * Create a nu engine subject before each test.
-     */
-    beforeEach(function () {
-        subject = new BABYLON.NullEngine({
-            renderHeight: 256,
-            renderWidth: 256,
-            textureSize: 256,
-            deterministicLockstep: false,
-            lockstepMaxSteps: 1
-        });
-    });
-
-    /**
-     * This test is more an integration test than a regular unit test but highlights how to rely
-     * on the BABYLON.NullEngine in order to create complex test cases.
-     */
-    describe('#GLTF', () => {
-        it('should load BoomBox GLTF', (done) => {
-            mocha.timeout(10000);
-
-            var scene = new BABYLON.Scene(subject);
-            BABYLON.SceneLoader.Append("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene, function () {
-
-                scene.meshes.length.should.be.equal(2);
-                scene.materials.length.should.be.equal(1);
-                scene.multiMaterials.length.should.be.equal(0);
-
-                done();
-            });
-        });
-    });
-
-    describe('#AssetContainer', () => {
-        it('should be loaded from BoomBox GLTF', (done) => {
-            var scene = new BABYLON.Scene(subject);
-            BABYLON.SceneLoader.LoadAssetContainer("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene, function (container) {
-                expect(container.meshes.length).to.eq(2);
-                done();
-            });
-        });
-        it('should be adding and removing objects from scene', () => {
-            // Create a scene with some assets
-            var scene = new BABYLON.Scene(subject);
-            var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
-            var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
-            var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
-            var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
-
-            // Move all the assets from the scene into a container
-            var container = new BABYLON.AssetContainer(scene);
-            var keepAssets = new BABYLON.KeepAssets();
-            keepAssets.cameras.push(camera)
-            container.moveAllFromScene(keepAssets)
-            expect(scene.cameras.length).to.eq(1)
-            expect(scene.meshes.length).to.eq(0)
-            expect(scene.lights.length).to.eq(0)
-            expect(container.cameras.length).to.eq(0)
-            expect(container.meshes.length).to.eq(2)
-            expect(container.lights.length).to.eq(1)
-
-            // Add them back and then remove again
-            container.addAllToScene();
-            expect(scene.cameras.length).to.eq(1)
-            expect(scene.meshes.length).to.eq(2)
-            expect(scene.lights.length).to.eq(1)
-            container.removeAllFromScene();
-            expect(scene.cameras.length).to.eq(1)
-            expect(scene.meshes.length).to.eq(0)
-            expect(scene.lights.length).to.eq(0)
-        });
-    });
-});

+ 218 - 0
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -0,0 +1,218 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon Scene Loader', function () {
+    let subject: BABYLON.Engine;
+
+    this.timeout(10000);
+
+    /**
+     * Loads the dependencies.
+     */
+    before(function (done) {
+        this.timeout(180000);
+        (BABYLONDEVTOOLS).Loader
+            .useDist()
+            .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
+                done();
+            });
+    });
+
+    /**
+     * Create a new engine subject before each test.
+     */
+    beforeEach(function () {
+        subject = new BABYLON.NullEngine({
+            renderHeight: 256,
+            renderWidth: 256,
+            textureSize: 256,
+            deterministicLockstep: false,
+            lockstepMaxSteps: 1
+        });
+    });
+
+    /**
+     * Integration tests for loading glTF assets.
+     */
+    describe('#glTF', () => {
+        it('Load BoomBox', () => {
+            const scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene);
+        });
+
+        it('Load BoomBox GLB', () => {
+            const scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.AppendAsync("/Playground/scenes/", "BoomBox.glb", scene);
+        });
+
+        it('Load BoomBox with callbacks', () => {
+            let parsedCount = 0;
+            let primaryMeshLoadCount = 0;
+            let primaryMaterialLoadCount = 0;
+            let textureLoadCounts: { [name: string]: number } = {};
+            let ready = false;
+
+            const deferred = new BABYLON.Deferred();
+            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+                loader.onParsed = data => {
+                    parsedCount++;
+                };
+
+                loader.onMeshLoaded = mesh => {
+                    if (mesh.name === "BoomBox") {
+                        primaryMeshLoadCount++;
+                    }
+                };
+                loader.onMaterialLoaded = material => {
+                    if (material.name === "BoomBox_Mat") {
+                        primaryMaterialLoadCount++;
+                    }
+                };
+                loader.onTextureLoaded = texture => {
+                    textureLoadCounts[texture.name] = textureLoadCounts[texture.name] || 0;
+                    textureLoadCounts[texture.name]++;
+                };
+
+                loader.onComplete = () => {
+                    try {
+                        expect(ready, "ready").to.be.true;
+                        deferred.resolve();
+                    }
+                    catch (e) {
+                        deferred.reject(e);
+                    }
+                };
+            }, undefined, undefined, undefined, true);
+
+            const scene = new BABYLON.Scene(subject);
+            const promise = BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
+                ready = true;
+
+                expect(parsedCount, "parsedCount").to.equal(1);
+                expect(primaryMeshLoadCount, "primaryMeshLoadCount").to.equal(1);
+                expect(primaryMaterialLoadCount, "primaryMaterialLoadCount").to.equal(1);
+
+                const expectedTextureLoadCounts = {
+                    "baseColor": 1,
+                    "occlusionRoughnessMetallic": 2,
+                    "normal": 1,
+                    "emissive": 1
+                };
+                expect(Object.keys(textureLoadCounts), "Object.keys(textureLoadCounts)").to.have.lengthOf(Object.keys(expectedTextureLoadCounts).length);
+                for (const textureName in expectedTextureLoadCounts) {
+                    expect(textureLoadCounts, "textureLoadCounts").to.have.property(textureName, expectedTextureLoadCounts[textureName]);
+                }
+            });
+
+            return Promise.all([promise, deferred.promise]);
+        });
+
+        it('Load BoomBox with dispose', () => {
+            let ready = false;
+            let disposed = false;
+
+            const deferred = new BABYLON.Deferred<void>();
+            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+                loader.onDispose = () => {
+                    disposed = true;
+                };
+
+                BABYLON.Tools.DelayAsync(50).then(() => {
+                    loader.dispose();
+                    expect(ready, "ready").to.be.false;
+                    expect(disposed, "disposed").to.be.true;
+                    deferred.resolve();
+                }).catch(error => {
+                    deferred.reject(error);
+                });
+            }, undefined, undefined, undefined, true);
+
+            const scene = new BABYLON.Scene(subject);
+            BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox2.gltf", scene).then(() => {
+                ready = true;
+            }).catch(error => {
+                // Cannot rely on the typical error handling of promises since the AppendAsync is not
+                // supposed to complete because the loader is being disposed.
+                deferred.reject(error);
+            });
+
+            return deferred.promise;
+        });
+
+        it('Load BoomBox with compileMaterials', () => {
+            let createShaderProgramSpy: sinon.SinonSpy;
+
+            const deferred = new BABYLON.Deferred();
+            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+                loader.compileMaterials = true;
+
+                loader.onComplete = () => {
+                    try {
+                        expect(createShaderProgramSpy.called, "createShaderProgramSpy.called").to.be.false;
+                        deferred.resolve();
+                    }
+                    catch (e) {
+                        deferred.reject(e);
+                    }
+                    finally {
+                        createShaderProgramSpy.restore();
+                    }
+                };
+            }, undefined, undefined, undefined, true);
+
+            const scene = new BABYLON.Scene(subject);
+            const promise = BABYLON.SceneLoader.AppendAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(() => {
+                createShaderProgramSpy = sinon.spy(subject, "createShaderProgram");
+            });
+
+            return Promise.all([promise, deferred.promise, scene.whenReadyAsync()]);
+        });
+
+        // TODO: test material instancing
+        // TODO: test ImportMesh with specific node name
+        // TODO: test KHR_materials_pbrSpecularGlossiness
+        // TODO: test MSFT_lod
+        // TODO: test KHR_lights
+    });
+
+    describe('#AssetContainer', () => {
+        it('should be loaded from BoomBox GLTF', () => {
+            var scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.LoadAssetContainerAsync("/Playground/scenes/BoomBox/", "BoomBox.gltf", scene).then(container => {
+                expect(container.meshes.length).to.eq(2);
+            });
+        });
+        it('should be adding and removing objects from scene', () => {
+            // Create a scene with some assets
+            var scene = new BABYLON.Scene(subject);
+            var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);
+            var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
+            var sphere = BABYLON.Mesh.CreateSphere("sphere1", 16, 2, scene);
+            var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
+
+            // Move all the assets from the scene into a container
+            var container = new BABYLON.AssetContainer(scene);
+            var keepAssets = new BABYLON.KeepAssets();
+            keepAssets.cameras.push(camera);
+            container.moveAllFromScene(keepAssets);
+            expect(scene.cameras.length).to.eq(1);
+            expect(scene.meshes.length).to.eq(0);
+            expect(scene.lights.length).to.eq(0);
+            expect(container.cameras.length).to.eq(0);
+            expect(container.meshes.length).to.eq(2);
+            expect(container.lights.length).to.eq(1);
+
+            // Add them back and then remove again
+            container.addAllToScene();
+            expect(scene.cameras.length).to.eq(1);
+            expect(scene.meshes.length).to.eq(2);
+            expect(scene.lights.length).to.eq(1);
+            container.removeAllFromScene();
+            expect(scene.cameras.length).to.eq(1);
+            expect(scene.meshes.length).to.eq(0);
+            expect(scene.lights.length).to.eq(0);
+        });
+    });
+});

+ 61 - 0
tests/unit/babylon/src/Mesh/babylon.mesh.vertexData.tests.ts

@@ -0,0 +1,61 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon Mesh Vertex Data', () => {
+    /**
+     * Loads the dependencies.
+     */
+    before(function (done) {
+        this.timeout(180000);
+        (BABYLONDEVTOOLS).Loader
+            .useDist()
+            .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
+                done();
+            });
+    });
+
+    describe('#Mesh Vertex Data Merge', () => {
+        it('should be able to merge data', () => {
+            const foo = new BABYLON.VertexData();
+            foo.positions = [0, 0, 0];
+            foo.normals = [0, 0, 1];
+
+            const bar = new BABYLON.VertexData();
+            bar.positions = [0, 0, 1];
+            bar.normals = [0, 1, 0];
+
+            const merged = foo.merge(bar);
+            expect(merged.positions).to.eql([0, 0, 0, 0, 0, 1]);
+            expect(merged.normals).to.eql([0, 0, 1, 0, 1, 0]);
+        });
+
+        it('should not be able to merge data that are not valid', () => {
+            const foo = new BABYLON.VertexData();
+            foo.positions = [0, 0, 0];
+            foo.normals = [0];
+
+            const bar = new BABYLON.VertexData();
+            bar.positions = [0, 0, 1];
+            bar.normals = [0];
+
+            expect(() => {
+                foo.merge(bar);
+            }).to.throw(Error);
+        });
+
+        it('should not be able to merge data with different attributes', () => {
+            const foo = new BABYLON.VertexData();
+            foo.positions = [0, 0, 0];
+            foo.normals = [0, 0, 1];
+
+            const bar = new BABYLON.VertexData();
+            bar.positions = [0, 0, 1];
+
+            expect(() => {
+                foo.merge(bar);
+            }).to.throw(Error);
+        });
+    });
+});

+ 79 - 71
tests/unit/babylon/promises/babylon.promises.tests.ts

@@ -1,24 +1,26 @@
 /**
  * Describes the test suite.
  */
-describe('Babylon.Promise', () => {
-    var subject : BABYLON.Engine;
+describe('Babylon.Promise', function () {
+    var subject: BABYLON.Engine;
+
+    this.timeout(10000);
 
     /**
      * Loads the dependencies.
      */
     before(function (done) {
-        this.timeout(180000);
         (BABYLONDEVTOOLS).Loader
             .useDist()
             .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
                 BABYLON.PromisePolyfill.Apply(true);
                 done();
             });
     });
 
     /**
-     * Create a nu engine subject before each test.
+     * Create a new engine subject before each test.
      */
     beforeEach(function () {
         subject = new BABYLON.NullEngine({
@@ -32,23 +34,17 @@ describe('Babylon.Promise', () => {
 
     describe('#Composition', () => {
         it('should chain promises correctly #1', (done) => {
-            mocha.timeout(10000);
             var tempString = "";
             var p1 = new Promise((resolve, reject) => {
                 tempString = "Initial";
-            
                 resolve();
-            })
-            .then(() => {
+            }).then(() => {
                 tempString += " message";
-            })
-            .then(() => {
+            }).then(() => {
                 throw new Error('Something failed');
-            })
-            .catch(() => {
+            }).catch(() => {
                 tempString += " to check promises";
-            })
-            .then(() => {
+            }).then(() => {
                 expect(tempString).to.eq("Initial message to check promises");
                 done();
             });
@@ -57,32 +53,25 @@ describe('Babylon.Promise', () => {
 
     describe('#Composition', () => {
         it('should chain promises correctly #2', (done) => {
-            mocha.timeout(10000);
             var tempString = "";
             var p1 = new Promise((resolve, reject) => {
                 tempString = "Initial";
-            
                 resolve();
-            })
-            .then(() => {
+            }).then(() => {
                 tempString += " message";
-            })
-            .then(() => {
+            }).then(() => {
                 tempString += " to check promises";
-            })
-            .catch(() => {
+            }).catch(() => {
                 tempString += " wrong!";
-            })
-            .then(() => {
+            }).then(() => {
                 expect(tempString).to.eq("Initial message to check promises");
                 done();
             });
         });
-    });    
+    });
 
     describe('#Delayed', () => {
         it('should chain promises correctly #3', (done) => {
-            mocha.timeout(10000);
             var tempString = "";
             function resolveLater(resolve, reject) {
                 setTimeout(function () {
@@ -94,19 +83,19 @@ describe('Babylon.Promise', () => {
                     reject(20);
                 }, 1000);
             }
-        
+
             var p1 = (<any>Promise).resolve('foo');
             var p2 = p1.then(function () {
                 // Return promise here, that will be resolved to 10 after 1 second
                 return new Promise(resolveLater);
             });
             p2.then(function (v) {
-                tempString += 'resolved '+ v;  // "resolved", 10
+                tempString += 'resolved ' + v;  // "resolved", 10
             }, function (e) {
                 // not called
                 tempString += 'rejected' + e;
             });
-        
+
             var p3 = p1.then(function () {
                 // Return promise here, that will be rejected with 20 after 1 second
                 return new Promise(rejectLater);
@@ -120,83 +109,102 @@ describe('Babylon.Promise', () => {
                 done();
             });
         });
-    });    
-    
+    });
+
     describe('#Promise.all', () => {
         it('should agregate promises correctly', (done) => {
-            mocha.timeout(10000);
             var promise1 = Promise.resolve(3);
-            var promise2 = new Promise(function(resolve, reject) {
+            var promise2 = new Promise(function (resolve, reject) {
                 setTimeout(resolve, 100, 'foo');
             });
             var promise3 = Promise.resolve(42);
-        
-            Promise.all([promise1, promise2, promise3]).then(function(values) {
+
+            Promise.all([promise1, promise2, promise3]).then(function (values) {
                 values.should.deep.equal([3, "foo", 42]);
                 done();
             });
         });
-    });   
+    });
 
     describe('#Returning value', () => {
         it('should correctly handle returned values', (done) => {
-            mocha.timeout(10000);
             Promise.resolve(1)
-            .then(number => { return number + 1; })
-            .then(number => { return number + 1; })
-            .then(number => { 
-                number.should.be.equal(3);
-                done();
-             });
+                .then(number => { return number + 1; })
+                .then(number => { return number + 1; })
+                .then(number => {
+                    number.should.be.equal(3);
+                    done();
+                });
         });
-    });     
-    
+    });
+
     describe('#Multiple children', () => {
-        it('should correctly handle multiple independant "then"', (done) => {
-            mocha.timeout(10000);
-            var promise1 = new Promise(function(resolve, reject) {
-                setTimeout(function() {
-                    resolve('Success!');
-                }, 500);
-            });
+        it('should correctly handle multiple independent "then"', (done) => {
+            var successValue = 'Success!';
+            var promise1 = BABYLON.Tools.DelayAsync(500).then(() => successValue);
+
             var sum = 0;
-            promise1.then(function(value) {
+            promise1.then(function (value) {
                 sum++;
                 if (sum === 2) {
+                    expect(value).to.equal(successValue);
                     done();
                 }
             });
-            
-            promise1.then(function(value) {
+
+            promise1.then(function (value) {
                 sum++;
                 if (sum === 2) {
+                    expect(value).to.equal(successValue);
                     done();
                 }
             });
         });
-    });     
+    });
 
     describe('#All and then', () => {
         it('should correctly handle chaining a returning then after a all', (done) => {
-            mocha.timeout(10000);
-            var delayAsync = function (timeout) {
-                return new Promise(function (resolve) {
-                    setTimeout(function () {
-                        resolve(1);
-                    }, timeout);
-                });
-            };
-            
-            var promise = Promise.all([delayAsync(100), delayAsync(200)]).then(function () {
-                return 2;
+            var promise = Promise.all([BABYLON.Tools.DelayAsync(100), BABYLON.Tools.DelayAsync(200)]).then(function () {
+                return 1;
             });
-            
+
             promise.then(function (value) {
-                value.should.be.equal(2);
+                expect(value).to.equal(1);
                 done();
-            }); 
+            });
         });
-    });      
+    });
 
+    describe('#Move children', () => {
+        it('should correctly handle moving children', (done) => {
+            var callback1Count = 0;
+            var callback2Count = 0;
+            Promise.resolve().then(function () {
+                var promise = Promise.all([BABYLON.Tools.DelayAsync(100), BABYLON.Tools.DelayAsync(200)]).then(function () {
+                    callback1Count++;
+                });
+                Promise.all([promise]).then(function () {
+                    callback2Count++;
+                });
+                return promise;
+            }).then(function () {
+                expect(callback1Count).to.equal(1);
+                expect(callback2Count).to.equal(1);
+                done();
+            });
+        });
+    });
 
+    describe('#Error handling', () => {
+        it('should correctly handle exceptions', (done) => {
+            var errorValue = 'Failed!';
+            var promise = new Promise((resolve, reject) => {
+                throw new Error(errorValue);
+            }).catch(error => {
+                expect(error.constructor).to.equal(Error);
+                expect(error.message).to.equal(errorValue);
+                done();
+            });
+        });
+    });
 });

BIN=BIN
tests/validation/ReferenceImages/gltfPrimitiveAttribute.png