Przeglądaj źródła

Merge pull request #2897 from bghgary/error-handling

glTF loader and sandbox error handling
sebavan 7 lat temu
rodzic
commit
b045d03796

+ 4 - 1
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -17,7 +17,10 @@ module BABYLON.GLTF2.Extensions {
         }
 
         protected _traverseNode(loader: GLTFLoader, index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean {
-            var node = loader._gltf.nodes[index];
+            var node = loader._getArrayItem(loader._gltf.nodes, index, "Node");
+            if (!node) {
+                return true;
+            }
 
             return this._loadExtension<IMSFTLOD>(node, (extension, onComplete) => {
                 for (var i = extension.ids.length - 1; i >= 0; i--) {

+ 216 - 113
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -27,6 +27,7 @@ module BABYLON.GLTF2 {
         private _parent: GLTFFileLoader;
         private _rootUrl: string;
         private _defaultMaterial: PBRMaterial;
+        private _rootNode: IGLTFNode;
         private _successCallback: () => void;
         private _progressCallback: (event: ProgressEvent) => void;
         private _errorCallback: (message: string) => void;
@@ -100,21 +101,32 @@ module BABYLON.GLTF2 {
         }
 
         private _loadAsync(nodeNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void {
-            this._loadData(data);
-            this._babylonScene = scene;
-            this._rootUrl = rootUrl;
-
-            this._successCallback = onSuccess;
-            this._progressCallback = onProgress;
-            this._errorCallback = onError;
-
-            this._addPendingData(this);
-            this._loadScene(nodeNames);
-            this._loadAnimations();
-            this._removePendingData(this);
+            try {
+                this._loadData(data);
+                this._babylonScene = scene;
+                this._rootUrl = rootUrl;
+
+                this._successCallback = onSuccess;
+                this._progressCallback = onProgress;
+                this._errorCallback = onError;
+
+                this._addPendingData(this);
+                this._loadScene(nodeNames);
+                this._loadAnimations();
+                this._removePendingData(this);
+            }
+            catch (e) {
+                this._onError(e.message);
+            }
         }
 
-        private _onError(message: string): void {
+        public _onError(message: string): void {
+            if (this._disposed) {
+                return;
+            }
+
+            Tools.Error("glTF Loader Error: " + message);
+
             if (this._errorCallback) {
                 this._errorCallback(message);
             }
@@ -123,6 +135,10 @@ module BABYLON.GLTF2 {
         }
 
         private _onProgress(event: ProgressEvent): void {
+            if (this._disposed) {
+                return;
+            }
+
             if (this._progressCallback) {
                 this._progressCallback(event);
             }
@@ -138,8 +154,7 @@ module BABYLON.GLTF2 {
         }
 
         private _onRenderReady(): void {
-            var rootNode = this._gltf.nodes[this._gltf.nodes.length - 1];
-            rootNode.babylonMesh.setEnabled(true);
+            this._rootNode.babylonMesh.setEnabled(true);
 
             this._startAnimations();
             this._successCallback();
@@ -161,28 +176,24 @@ module BABYLON.GLTF2 {
         private _loadData(data: IGLTFLoaderData): void {
             this._gltf = <IGLTF>data.json;
 
-            var binaryBuffer: IGLTFBuffer;
-            var buffers = this._gltf.buffers;
-            if (buffers && buffers[0].uri === undefined) {
-                binaryBuffer = buffers[0];
-            }
-
             if (data.bin) {
-                if (binaryBuffer) {
+                var buffers = this._gltf.buffers;
+                if (buffers && buffers[0] && !buffers[0].uri) {
+                    var binaryBuffer = buffers[0];
                     if (binaryBuffer.byteLength != data.bin.byteLength) {
                         Tools.Warn("Binary buffer length (" + binaryBuffer.byteLength + ") from JSON does not match chunk length (" + data.bin.byteLength + ")");
                     }
+
+                    binaryBuffer.loadedData = data.bin;
                 }
                 else {
                     Tools.Warn("Unexpected BIN chunk");
                 }
-
-                binaryBuffer.loadedData = data.bin;
             }
         }
 
         private _getMeshes(): Mesh[] {
-            var meshes = [];
+            var meshes = [this._rootNode.babylonMesh];
 
             var nodes = this._gltf.nodes;
             if (nodes) {
@@ -229,15 +240,18 @@ module BABYLON.GLTF2 {
         }
 
         private _loadScene(nodeNames: any): void {
-            var scene = this._gltf.scenes[this._gltf.scene || 0];
+            var scene = this._getArrayItem(this._gltf.scenes, this._gltf.scene || 0, "Scene");
+            if (!scene) {
+                return;
+            }
 
-            var rootNode = { name: "root", children: scene.nodes } as IGLTFNode;
+            this._rootNode = { name: "__root__" };
 
             switch (this._parent.coordinateSystemMode) {
                 case GLTFLoaderCoordinateSystemMode.AUTO:
                     if (!this._babylonScene.useRightHandedSystem) {
-                        rootNode.rotation = [0, 1, 0, 0];
-                        rootNode.scale = [1, 1, -1];
+                        this._rootNode.rotation = [0, 1, 0, 0];
+                        this._rootNode.scale = [1, 1, -1];
                     }
                     break;
                 case GLTFLoaderCoordinateSystemMode.PASS_THROUGH:
@@ -248,13 +262,10 @@ module BABYLON.GLTF2 {
                     break;
                 default:
                     Tools.Error("Invalid coordinate system mode (" + this._parent.coordinateSystemMode + ")");
-                    break;
+                    return;
             }
 
-            // Inject a root node into the scene.
-            this._gltf.nodes = this._gltf.nodes || [];
-            scene.nodes = [this._gltf.nodes.length];
-            this._gltf.nodes.push(rootNode);
+            this._loadNode(this._rootNode);
 
             var nodeIndices = scene.nodes;
 
@@ -262,13 +273,18 @@ module BABYLON.GLTF2 {
                 node.index = index;
                 node.parent = parentNode;
                 return true;
-            });
+            }, this._rootNode);
 
             var materials = this._gltf.materials;
             if (materials) {
                 materials.forEach((material, index) => material.index = index);
             }
 
+            var skins = this._gltf.skins;
+            if (skins) {
+                skins.forEach((skin, index) => skin.index = index);
+            }
+
             if (nodeNames) {
                 if (!(nodeNames instanceof Array)) {
                     nodeNames = [nodeNames];
@@ -282,17 +298,22 @@ module BABYLON.GLTF2 {
                     }
 
                     return true;
-                });
+                }, this._rootNode);
 
                 nodeIndices = filteredNodeIndices;
             }
 
             for (var i = 0; i < nodeIndices.length; i++) {
-                this._loadNode(this._gltf.nodes[nodeIndices[i]]);
+                var node = this._getArrayItem(this._gltf.nodes, nodeIndices[i], "Node");
+                if (!node) {
+                    return;
+                }
+
+                this._loadNode(node);
             }
 
             // Disable the root mesh until the asset is ready to render.
-            rootNode.babylonMesh.setEnabled(false);
+            this._rootNode.babylonMesh.setEnabled(false);
         }
 
         public _loadNode(node: IGLTFNode): void {
@@ -305,7 +326,11 @@ module BABYLON.GLTF2 {
             this._loadTransform(node);
 
             if (node.mesh !== undefined) {
-                var mesh = this._gltf.meshes[node.mesh];
+                var mesh = this._getArrayItem(this._gltf.meshes, node.mesh, "Mesh");
+                if (!mesh) {
+                    return;
+                }
+
                 this._loadMesh(node, mesh);
             }
 
@@ -315,8 +340,11 @@ module BABYLON.GLTF2 {
             node.babylonAnimationTargets.push(node.babylonMesh);
 
             if (node.skin !== undefined) {
-                var skin = this._gltf.skins[node.skin];
-                skin.index = node.skin;
+                var skin = this._getArrayItem(this._gltf.skins, node.skin, "Skin");
+                if (!skin) {
+                    return;
+                }
+
                 node.babylonMesh.skeleton = this._loadSkin(skin);
             }
 
@@ -326,7 +354,12 @@ module BABYLON.GLTF2 {
 
             if (node.children) {
                 for (var i = 0; i < node.children.length; i++) {
-                    this._loadNode(this._gltf.nodes[node.children[i]]);
+                    var childNode = this._getArrayItem(this._gltf.nodes, node.children[i], "Node");
+                    if (!childNode) {
+                        return;
+                    }
+
+                    this._loadNode(childNode);
                 }
             }
         }
@@ -365,12 +398,16 @@ module BABYLON.GLTF2 {
                         indicesStart: vertexData.indices.length,
                         indicesCount: subVertexData.indices.length,
                         loadMaterial: () => {
-                            if (primitive.material === undefined) {
+                            if (primitive.material == null) {
                                 babylonMultiMaterial.subMaterials[i] = this._getDefaultMaterial();
                                 return;
                             }
 
-                            var material = this._gltf.materials[primitive.material];
+                            var material = this._getArrayItem(this._gltf.materials, primitive.material, "Material");
+                            if (!material) {
+                                return;
+                            }
+
                             this._loadMaterial(material, (babylonMaterial, isNew) => {
                                 if (isNew && this._parent.onMaterialLoaded) {
                                     this._parent.onMaterialLoaded(babylonMaterial);
@@ -416,10 +453,14 @@ module BABYLON.GLTF2 {
 
             var loadedAttributes = 0;
             var totalAttributes = Object.keys(attributes).length;
-            for (let semantic in attributes) {
-                var accessor = this._gltf.accessors[attributes[semantic]];
+            for (let attribute in attributes) {
+                var accessor = this._getArrayItem(this._gltf.accessors, attributes[attribute], "Mesh primitive attribute '" + attribute + "' accessor");
+                if (!accessor) {
+                    return;
+                }
+
                 this._loadAccessorAsync(accessor, data => {
-                    switch (semantic) {
+                    switch (attribute) {
                         case "NORMAL":
                             vertexData.normals = <Float32Array>data;
                             break;
@@ -445,23 +486,27 @@ module BABYLON.GLTF2 {
                             vertexData.colors = <Float32Array>data;
                             break;
                         default:
-                            Tools.Warn("Ignoring unrecognized semantic '" + semantic + "'");
+                            Tools.Warn("Ignoring unrecognized attribute '" + attribute + "'");
                             break;
                     }
 
                     if (++loadedAttributes === totalAttributes) {
-                        var indicesAccessor = this._gltf.accessors[primitive.indices];
-                        if (indicesAccessor) {
+                        if (primitive.indices == null) {
+                            vertexData.indices = new Uint32Array(vertexData.positions.length / 3);
+                            vertexData.indices.forEach((v, i) => vertexData.indices[i] = i);
+                            onSuccess(vertexData);
+                        }
+                        else {
+                            var indicesAccessor = this._getArrayItem(this._gltf.accessors, primitive.indices, "Mesh primitive 'indices' accessor");
+                            if (!indicesAccessor) {
+                                return;
+                            }
+
                             this._loadAccessorAsync(indicesAccessor, data => {
                                 vertexData.indices = <IndicesArray>data;
                                 onSuccess(vertexData);
                             });
                         }
-                        else {
-                            vertexData.indices = new Uint32Array(vertexData.positions.length / 3);
-                            vertexData.indices.forEach((v, i) => vertexData.indices[i] = i);
-                            onSuccess(vertexData);
-                        }
                     }
                 });
             }
@@ -492,8 +537,12 @@ module BABYLON.GLTF2 {
             for (var index = 0; index < targets.length; index++) {
                 let babylonMorphTarget = babylonMesh.morphTargetManager.getTarget(index);
                 var attributes = targets[index];
-                for (let semantic in attributes) {
-                    var accessor = this._gltf.accessors[attributes[semantic]];
+                for (let attribute in attributes) {
+                    var accessor = this._getArrayItem(this._gltf.accessors, attributes[attribute], "Mesh primitive morph target attribute '" + attribute + "' accessor");
+                    if (!accessor) {
+                        return;
+                    }
+
                     this._loadAccessorAsync(accessor, data => {
                         if (accessor.name) {
                             babylonMorphTarget.name = accessor.name;
@@ -502,7 +551,7 @@ module BABYLON.GLTF2 {
                         // glTF stores morph target information as deltas while babylon.js expects the final data.
                         // As a result we have to add the original data to the delta to calculate the final data.
                         var values = <Float32Array>data;
-                        switch (semantic) {
+                        switch (attribute) {
                             case "NORMAL":
                                 GLTFUtils.ForEach(values, (v, i) => values[i] += vertexData.normals[i]);
                                 babylonMorphTarget.setNormals(values);
@@ -524,7 +573,7 @@ module BABYLON.GLTF2 {
                                 babylonMorphTarget.setTangents(values);
                                 break;
                             default:
-                                Tools.Warn("Ignoring unrecognized semantic '" + semantic + "'");
+                                Tools.Warn("Ignoring unrecognized attribute '" + attribute + "'");
                                 break;
                         }
                     });
@@ -556,11 +605,15 @@ module BABYLON.GLTF2 {
             var skeletonId = "skeleton" + skin.index;
             skin.babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
 
-            if (skin.inverseBindMatrices === undefined) {
+            if (skin.inverseBindMatrices == null) {
                 this._loadBones(skin, null);
             }
             else {
-                var accessor = this._gltf.accessors[skin.inverseBindMatrices];
+                var accessor = this._getArrayItem(this._gltf.accessors, skin.inverseBindMatrices, "Skin (" + skin.index + ") inverse bind matrices attribute accessor");
+                if (!accessor) {
+                    return;
+                }
+
                 this._loadAccessorAsync(accessor, data => {
                     this._loadBones(skin, <Float32Array>data);
                 });
@@ -584,7 +637,11 @@ module BABYLON.GLTF2 {
         private _loadBones(skin: IGLTFSkin, inverseBindMatrixData: Float32Array): void {
             var babylonBones: { [index: number]: Bone } = {};
             for (var i = 0; i < skin.joints.length; i++) {
-                var node = this._gltf.nodes[skin.joints[i]];
+                var node = this._getArrayItem(this._gltf.nodes, skin.joints[i], "Skin (" + skin.index + ") joint");
+                if (!node) {
+                    return;
+                }
+
                 this._loadBone(node, skin, inverseBindMatrixData, babylonBones);
             }
         }
@@ -634,7 +691,11 @@ module BABYLON.GLTF2 {
                 return;
             }
 
-            var node = this._gltf.nodes[index];
+            var node = this._getArrayItem(this._gltf.nodes, index, "Node");
+            if (!node) {
+                return;
+            }
+
             if (!action(node, index, parentNode)) {
                 return;
             }
@@ -646,7 +707,7 @@ module BABYLON.GLTF2 {
 
         private _loadAnimations(): void {
             var animations = this._gltf.animations;
-            if (!animations || animations.length === 0) {
+            if (!animations) {
                 return;
             }
 
@@ -663,9 +724,8 @@ module BABYLON.GLTF2 {
             var samplerIndex = channel.sampler;
             var sampler = animation.samplers[samplerIndex];
 
-            var targetNode = this._gltf.nodes[channel.target.node];
+            var targetNode = this._getArrayItem(this._gltf.nodes, channel.target.node, "Animation channel target");
             if (!targetNode) {
-                Tools.Warn("Animation channel target node (" + channel.target.node + ") does not exist");
                 return;
             }
 
@@ -676,7 +736,7 @@ module BABYLON.GLTF2 {
                 "weights": "influence"
             }[channel.target.path];
             if (!targetPath) {
-                Tools.Warn("Animation channel target path '" + channel.target.path + "' is not valid");
+                this._onError("Invalid animation channel target path '" + channel.target.path + "'");
                 return;
             }
 
@@ -773,19 +833,37 @@ module BABYLON.GLTF2 {
                 }
             };
 
-            this._loadAccessorAsync(this._gltf.accessors[sampler.input], data => {
+            var inputAccessor = this._getArrayItem(this._gltf.accessors, sampler.input, "Animation sampler input accessor");
+            if (!inputAccessor) {
+                return;
+            }
+
+            this._loadAccessorAsync(inputAccessor, data => {
                 inputData = <Float32Array>data;
                 checkSuccess();
             });
 
-            this._loadAccessorAsync(this._gltf.accessors[sampler.output], data => {
+            var outputAccessor = this._getArrayItem(this._gltf.accessors, sampler.output, "Animation sampler output accessor");
+            if (!outputAccessor) {
+                return;
+            }
+
+            this._loadAccessorAsync(outputAccessor, data => {
                 outputData = <Float32Array>data;
                 checkSuccess();
             });
         }
 
-        private _loadBufferAsync(index: number, onSuccess: (data: ArrayBufferView) => void): void {
-            var buffer = this._gltf.buffers[index];
+        private _validateUri(uri: string): boolean {
+            if (!uri) {
+                this._onError("Uri is missing");
+                return false;
+            }
+
+            return true;
+        }
+
+        private _loadBufferAsync(buffer: IGLTFBuffer, onSuccess: (data: ArrayBufferView) => void): void {
             this._addPendingData(buffer);
 
             if (buffer.loadedData) {
@@ -794,41 +872,38 @@ module BABYLON.GLTF2 {
                     this._removePendingData(buffer);
                 });
             }
-            else if (GLTFUtils.IsBase64(buffer.uri)) {
-                var data = GLTFUtils.DecodeBase64(buffer.uri);
-                buffer.loadedData = new Uint8Array(data);
-                setTimeout(() => {
-                    onSuccess(buffer.loadedData);
-                    this._removePendingData(buffer);
-                });
-            }
             else if (buffer.loadedObservable) {
                 buffer.loadedObservable.add(buffer => {
                     onSuccess(buffer.loadedData);
                     this._removePendingData(buffer);
                 });
             }
-            else {
-                buffer.loadedObservable = new Observable<IGLTFBuffer>();
-                buffer.loadedObservable.add(buffer => {
-                    onSuccess(buffer.loadedData);
-                    this._removePendingData(buffer);
-                });
-
-                Tools.LoadFile(this._rootUrl + buffer.uri, data => {
+            else if (this._validateUri(buffer.uri)) {
+                if (GLTFUtils.IsBase64(buffer.uri)) {
+                    var data = GLTFUtils.DecodeBase64(buffer.uri);
                     buffer.loadedData = new Uint8Array(data);
-                    buffer.loadedObservable.notifyObservers(buffer);
-                    buffer.loadedObservable = null;
-                }, event => {
-                    if (!this._disposed) {
-                        this._onProgress(event);
-                    }
-                }, this._babylonScene.database, true, request => {
-                    if (!this._disposed) {
-                        this._onError("Failed to load file '" + buffer.uri + "': " + request.status + " " + request.statusText);
+                    setTimeout(() => {
+                        onSuccess(buffer.loadedData);
                         this._removePendingData(buffer);
-                    }
-                });
+                    });
+                }
+                else {
+                    buffer.loadedObservable = new Observable<IGLTFBuffer>();
+                    buffer.loadedObservable.add(buffer => {
+                        onSuccess(buffer.loadedData);
+                        this._removePendingData(buffer);
+                    });
+
+                    Tools.LoadFile(this._rootUrl + buffer.uri, data => {
+                        buffer.loadedData = new Uint8Array(data);
+                        buffer.loadedObservable.notifyObservers(buffer);
+                        buffer.loadedObservable = null;
+                    }, event => {
+                        this._onProgress(event);
+                    }, this._babylonScene.database, true, request => {
+                        this._onError("Failed to load file '" + buffer.uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""));
+                    });
+                }
             }
         }
 
@@ -927,7 +1002,12 @@ module BABYLON.GLTF2 {
         private _loadBufferViewAsync(bufferView: IGLTFBufferView, byteOffset: number, byteLength: number, bytePerComponent: number, componentType: EComponentType, onSuccess: (data: ArrayBufferView) => void): void {
             byteOffset += (bufferView.byteOffset || 0);
 
-            this._loadBufferAsync(bufferView.buffer, bufferData => {
+            var buffer = this._getArrayItem(this._gltf.buffers, bufferView.buffer, "Buffer");
+            if (!buffer) {
+                return;
+            }
+
+            this._loadBufferAsync(buffer, bufferData => {
                 if (byteOffset + byteLength > bufferData.byteLength) {
                     this._onError("Buffer access is out of range");
                     return;
@@ -966,7 +1046,11 @@ module BABYLON.GLTF2 {
         }
 
         private _loadAccessorAsync(accessor: IGLTFAccessor, onSuccess: (data: ArrayBufferView) => void): void {
-            var bufferView = this._gltf.bufferViews[accessor.bufferView];
+            var bufferView = this._getArrayItem(this._gltf.bufferViews, accessor.bufferView, "Buffer view");
+            if (!bufferView) {
+                return;
+            }
+
             var byteOffset = accessor.byteOffset || 0;
             let bytePerComponent = this._getByteStrideFromType(accessor);
             var byteLength = accessor.count * bytePerComponent;
@@ -1066,8 +1150,8 @@ module BABYLON.GLTF2 {
             }
 
             babylonMaterial.albedoColor = properties.baseColorFactor ? Color3.FromArray(properties.baseColorFactor) : new Color3(1, 1, 1);
-            babylonMaterial.metallic = properties.metallicFactor === undefined ? 1 : properties.metallicFactor;
-            babylonMaterial.roughness = properties.roughnessFactor === undefined ? 1 : properties.roughnessFactor;
+            babylonMaterial.metallic = properties.metallicFactor == null ? 1 : properties.metallicFactor;
+            babylonMaterial.roughness = properties.roughnessFactor == null ? 1 : properties.roughnessFactor;
 
             if (properties.baseColorTexture) {
                 babylonMaterial.albedoTexture = this._loadTexture(properties.baseColorTexture);
@@ -1156,23 +1240,31 @@ module BABYLON.GLTF2 {
                     }
                     break;
                 default:
-                    Tools.Warn("Invalid alpha mode '" + material.alphaMode + "'");
-                    break;
+                    this._onError("Invalid alpha mode '" + material.alphaMode + "'");
+                    return;
             }
 
-            babylonMaterial.alphaCutOff = material.alphaCutoff === undefined ? 0.5 : material.alphaCutoff;
+            babylonMaterial.alphaCutOff = material.alphaCutoff == null ? 0.5 : material.alphaCutoff;
         }
 
         public _loadTexture(textureInfo: IGLTFTextureInfo): Texture {
-            var texture = this._gltf.textures[textureInfo.index];
+            var texture = this._getArrayItem(this._gltf.textures, textureInfo.index, "Texture");
+            if (!texture) {
+                return null;
+            }
+
             var texCoord = textureInfo.texCoord || 0;
 
-            if (!texture || texture.source === undefined) {
+            var source = this._getArrayItem(this._gltf.images, texture.source, "Texture (" + textureInfo.index + ") source");
+            if (!source) {
                 return null;
             }
 
-            var source = this._gltf.images[texture.source];
-            var sampler = (texture.sampler === undefined ? <IGLTFSampler>{} : this._gltf.samplers[texture.sampler]);
+            var sampler = (texture.sampler == null ? <IGLTFSampler>{} : this._getArrayItem(this._gltf.samplers, texture.sampler, "Texture (" + textureInfo.index + ") sampler"));
+            if (!sampler) {
+                return;
+            }
+
             var noMipMaps = (sampler.minFilter === ETextureMinFilter.NEAREST || sampler.minFilter === ETextureMinFilter.LINEAR);
             var samplingMode = GLTFUtils.GetTextureSamplingMode(sampler.magFilter, sampler.minFilter);
 
@@ -1208,7 +1300,11 @@ module BABYLON.GLTF2 {
                 };
 
                 if (!source.uri) {
-                    var bufferView = this._gltf.bufferViews[source.bufferView];
+                    var bufferView = this._getArrayItem(this._gltf.bufferViews, source.bufferView, "Texture (" + textureInfo.index + ") source (" + texture.source + ") buffer view");
+                    if (!bufferView) {
+                        return;
+                    }
+
                     this._loadBufferViewAsync(bufferView, 0, bufferView.byteLength, 1, EComponentType.UNSIGNED_BYTE, setTextureData);
                 }
                 else if (GLTFUtils.IsBase64(source.uri)) {
@@ -1216,9 +1312,7 @@ module BABYLON.GLTF2 {
                 }
                 else {
                     Tools.LoadFile(this._rootUrl + source.uri, setTextureData, event => {
-                        if (!this._disposed) {
-                            this._onProgress(event);
-                        }
+                        this._onProgress(event);
                     }, this._babylonScene.database, true, request => {
                         this._onError("Failed to load file '" + source.uri + "': " + request.status + " " + request.statusText);
                     });
@@ -1236,6 +1330,15 @@ module BABYLON.GLTF2 {
 
             return babylonTexture;
         }
+
+        public _getArrayItem<T>(array: ArrayLike<T>, index: number, name: string): T {
+            if (!array || !array[index]) {
+                this._onError(name + " index (" + index + ") was not found");
+                return null;
+            }
+
+            return array[index];
+        }
     }
 
     BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = parent => new GLTFLoader(parent);

+ 4 - 4
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -106,11 +106,11 @@ module BABYLON {
         }
 
         private static _parse(data: string | ArrayBuffer, onError: (message: string) => void): IGLTFLoaderData {
-            if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data, onError);
-            }
-
             try {
+                if (data instanceof ArrayBuffer) {
+                    return GLTFFileLoader._parseBinary(data, onError);
+                }
+
                 return {
                     json: JSON.parse(data),
                     bin: null

+ 1 - 0
sandbox/index-local.html

@@ -48,6 +48,7 @@
         </div>
     </div>
     <div id="loadingText" class="loadingText"></div>
+    <div id="errorZone"></div>
     <script>
         BABYLONDEVTOOLS.Loader.require('index.js')
             .load();

+ 28 - 1
sandbox/index.css

@@ -261,4 +261,31 @@ li {
 #btnFullscreen:hover, #btnPerf:hover, #btnFiles:hover {
     -webkit-transform: scale(0.9);
     transform: scale(0.9);
-}
+}
+
+#errorZone {
+    display:none;
+    position: absolute;
+    width: 50%;
+    left: 25%;
+    bottom: 80px;
+    background-color: #C73228;
+    padding:20px;
+    border-radius: 5px;
+    color:white;
+    font-family: 'Inconsolata';
+}
+#errorZone button {
+    position: absolute;
+    top: 3px;
+    right: 10px;
+    padding: 0;
+    cursor: pointer;
+    background: transparent;
+    border: 0;
+    -webkit-appearance: none;
+    color: #000;
+    text-shadow: 0 1px 0 #fff;
+    opacity: .4;
+    font-size: 1.8em;
+}

+ 1 - 0
sandbox/index.html

@@ -67,6 +67,7 @@
         </div>
     </div>
     <div id="loadingText" class="loadingText"></div>
+    <div id="errorZone"></div>
     <script src="index.js"></script>
 </body>
 </html>

+ 27 - 4
sandbox/index.js

@@ -1,4 +1,6 @@
-if (BABYLON.Engine.isSupported()) {
+/// <reference path="../dist/preview release/babylon.d.ts" />
+
+if (BABYLON.Engine.isSupported()) {
     var canvas = document.getElementById("renderCanvas");
     var engine = new BABYLON.Engine(canvas, true);
     var divFps = document.getElementById("fps");
@@ -60,16 +62,21 @@
             enableDebugLayer = false;
             currentScene.debugLayer.hide();
         };
+
         if (enableDebugLayer) {
             hideDebugLayerAndLogs();
         }
+
+        // Clear the error
+        document.getElementById("errorZone").style.display = 'none';
+
         currentScene = babylonScene;
         document.title = "BabylonJS - " + sceneFile.name;
         // Fix for IE, otherwise it will change the default filter for files selection after first use
         htmlInput.value = "";
 
         // Attach camera to canvas inputs
-        if (!currentScene.activeCamera || currentScene.lights.length === 0) {     
+        if (!currentScene.activeCamera || currentScene.lights.length === 0) {
             currentScene.createDefaultCameraOrLight(true);
             // Enable camera's behaviors
             currentScene.activeCamera.useBouncingBehavior = true;
@@ -80,7 +87,7 @@
             framingBehavior.elevationReturnTime = -1;
 
             var bouncingBehavior = currentScene.activeCamera.getBehaviorByName("Bouncing");
-            bouncingBehavior.autoTransitionRange = true;        
+            bouncingBehavior.autoTransitionRange = true;
 
             if (currentScene.meshes.length) {
                 var worldExtends = currentScene.getWorldExtends();
@@ -120,7 +127,23 @@
         }
     };
 
-    filesInput = new BABYLON.FilesInput(engine, null, sceneLoaded);
+    var sceneError = function (sceneFile, babylonScene, message) {
+        document.title = "BabylonJS - " + sceneFile.name;
+        document.getElementById("logo").className = "";
+        canvas.style.opacity = 0;
+
+        var errorContent = '<div class="alert alert-error"><button type="button" class="close" data-dismiss="alert">&times;</button>' + message.replace("file:[object File]", "'" + sceneFile.name + "'") + '</div>';
+
+        document.getElementById("errorZone").style.display = 'block';
+        document.getElementById("errorZone").innerHTML = errorContent;
+
+        // Close button error
+        document.getElementById("errorZone").querySelector('.close').addEventListener('click', function () {
+            document.getElementById("errorZone").style.display = 'none';
+        });
+    };
+
+    filesInput = new BABYLON.FilesInput(engine, null, sceneLoaded, null, null, null, function () { BABYLON.Tools.ClearLogCache() }, null, sceneError);
     filesInput.onProcessFileCallback = (function (file, name, extension) {
         if (extension === "dds") {
             BABYLON.FilesInput.FilesToLoad[name] = file;

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

@@ -12,13 +12,14 @@
         private _textureLoadingCallback: (remaining: number) => void;
         private _startingProcessingFilesCallback: () => void;
         private _onReloadCallback: (sceneFile: File) => void;
+        private _errorCallback: (sceneFile: File, scene: Scene, message: string) => void;
         private _elementToMonitor: HTMLElement;
 
         private _sceneFileToLoad: File;
         private _filesToLoad: File[];
 
         constructor(engine: Engine, scene: Scene, sceneLoadedCallback: (sceneFile: File, scene: Scene) => void, progressCallback: (progress: ProgressEvent) => void, additionalRenderLoopLogicCallback: () => void, 
-                    textureLoadingCallback: (remaining: number) => void, startingProcessingFilesCallback: () => void, onReloadCallback: (sceneFile: File) => void) {
+                    textureLoadingCallback: (remaining: number) => void, startingProcessingFilesCallback: () => void, onReloadCallback: (sceneFile: File) => void, errorCallback: (sceneFile: File, scene: Scene, message: string) => void) {
             this._engine = engine;
             this._currentScene = scene;
 
@@ -28,6 +29,7 @@
             this._textureLoadingCallback = textureLoadingCallback;
             this._startingProcessingFilesCallback = startingProcessingFilesCallback;
             this._onReloadCallback = onReloadCallback;
+            this._errorCallback = errorCallback;
         }
 
         private _dragEnterHandler: (any) => void;
@@ -229,15 +231,22 @@
                     }
 
                     // Wait for textures and shaders to be ready
-                    this._currentScene.executeWhenReady(() => {                       
-                        this._engine.runRenderLoop(() => { 
-                            this.renderFunction(); });
+                    this._currentScene.executeWhenReady(() => {
+                        this._engine.runRenderLoop(() => {
+                            this.renderFunction();
                         });
-                }, progress => {
-                        if (this._progressCallback) {
-                            this._progressCallback(progress);
-                        }
                     });
+                }, progress => {
+                    if (this._progressCallback) {
+                        this._progressCallback(progress);
+                    }
+                }, (scene, message) => {
+                    this._currentScene = scene;
+
+                    if (this._errorCallback) {
+                        this._errorCallback(this._sceneFileToLoad, this._currentScene, message);
+                    }
+                });
             }
             else {
                 Tools.Error("Please provide a valid .babylon file.");