ソースを参照

Initial MSFT_LOD implementation for node LODs

Gary Hsu 8 年 前
コミット
c8ae5a7eba

+ 7 - 15
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -14,21 +14,13 @@ module BABYLON.GLTF2.Extensions {
             return "KHR_materials_pbrSpecularGlossiness";
         }
 
-        protected loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            if (!material.extensions) {
-                return false;
-            }
-
-            var properties = material.extensions[this.name] as IKHRMaterialsPbrSpecularGlossiness;
-            if (!properties) {
-                return false;
-            }
-
-            loader._createPbrMaterial(material);
-            loader._loadMaterialBaseProperties(material);
-            this._loadSpecularGlossinessProperties(loader, material, properties);
-            assign(material.babylonMaterial, true);
-            return true;
+        protected _loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
+            return this._loadExtension<IKHRMaterialsPbrSpecularGlossiness>(material, (extension, onComplete) => {
+                loader._createPbrMaterial(material);
+                loader._loadMaterialBaseProperties(material);
+                this._loadSpecularGlossinessProperties(loader, material, extension);
+                assign(material.babylonMaterial, true);
+            });
         }
 
         private _loadSpecularGlossinessProperties(loader: GLTFLoader, material: IGLTFMaterial, properties: IKHRMaterialsPbrSpecularGlossiness): void {

+ 56 - 38
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -5,6 +5,7 @@ module BABYLON.GLTF2.Extensions {
         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)
@@ -15,52 +16,70 @@ module BABYLON.GLTF2.Extensions {
             return "MSFT_lod";
         }
 
-        protected loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            if (!material.extensions) {
-                return false;
-            }
+        protected _traverseNode(loader: GLTFLoader, index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean {
+            var node = loader._gltf.nodes[index];
 
-            var properties = material.extensions[this.name] as IMSFTLOD;
-            if (!properties) {
-                return false;
-            }
-
-            // Clear out the extension so that it won't get loaded again.
-            material.extensions[this.name] = undefined;
-
-            // Tell the loader not to clear its state until the highest LOD is loaded.
-            var materialLODs = [material.index, ...properties.ids];
+            return this._loadExtension<IMSFTLOD>(node, (extension, onComplete) => {
+                for (var i = extension.ids.length - 1; i >= 0; i--) {
+                    loader._traverseNode(extension.ids[i], action, parentNode);
+                }
 
-            loader._addLoaderPendingData(material);
-            for (var index = 0; index < materialLODs.length; index++) {
-                loader._addLoaderNonBlockingPendingData(index);
-            }
+                loader._traverseNode(index, action, parentNode);
+                onComplete();
+            });
+        }
 
-            // Start with the lowest quality LOD.
-            this.loadMaterialLOD(loader, material, materialLODs, materialLODs.length - 1, assign);
+        protected _loadNode(loader: GLTFLoader, node: IGLTFNode): boolean {
+            return this._loadExtension<IMSFTLOD>(node, (extension, onComplete) => {
+                var nodes = [node.index, ...extension.ids].map(index => loader._gltf.nodes[index]);
 
-            return true;
+                loader._addLoaderPendingData(node);
+                this._loadNodeLOD(loader, nodes, nodes.length - 1, () => {
+                    loader._removeLoaderPendingData(node);
+                    onComplete();
+                });
+            });
         }
 
-        private loadMaterialLOD(loader: GLTFLoader, material: IGLTFMaterial, materialLODs: number[], lod: number, assign: (babylonMaterial: Material, isNew: boolean) => void): void {
-            var materialLOD = loader._gltf.materials[materialLODs[lod]];
+        private _loadNodeLOD(loader: GLTFLoader, nodes: IGLTFNode[], index: number, onComplete: () => void): void {
+            loader._whenAction(() => {
+                loader._loadNode(nodes[index]);
+            }, () => {
+                if (index !== nodes.length - 1) {
+                    var previousNode = nodes[index + 1];
+                    previousNode.babylonMesh.setEnabled(false);
+                }
 
-            if (lod !== materialLODs.length - 1) {
-                loader._blockPendingTracking = true;
-            }
-            
-            loader._loadMaterial(materialLOD, (babylonMaterial, isNew) => {
-                assign(babylonMaterial, isNew);
+                if (index === 0) {
+                    onComplete();
+                    return;
+                }
 
-                loader._removeLoaderPendingData(lod);
+                setTimeout(() => {
+                    this._loadNodeLOD(loader, nodes, index - 1, onComplete);
+                }, MSFTLOD.MinimalLODDelay);
+            });
+        }
+
+        protected _loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
+            return this._loadExtension<IMSFTLOD>(material, (extension, onComplete) => {
+                var materials = [material.index, ...extension.ids].map(index => loader._gltf.materials[index]);
 
-                // Loading is considered complete if this is the lowest quality LOD.
-                if (lod === materialLODs.length - 1) {
+                loader._addLoaderPendingData(material);
+                this._loadMaterialLOD(loader, materials, materials.length - 1, assign, () => {
+                    material.extensions[this.name] = extension;
                     loader._removeLoaderPendingData(material);
-                }
+                    onComplete();
+                });
+            });
+        }
 
-                if (lod === 0) {
-                    loader._blockPendingTracking = false;
+        private _loadMaterialLOD(loader: GLTFLoader, materials: IGLTFMaterial[], index: number, assign: (babylonMaterial: Material, isNew: boolean) => void, onComplete: () => void): void {
+            loader._loadMaterial(materials[index], (babylonMaterial, isNew) => {
+                assign(babylonMaterial, isNew);
+
+                if (index === 0) {
+                    onComplete();
                     return;
                 }
 
@@ -68,13 +87,12 @@ module BABYLON.GLTF2.Extensions {
                 // all active material textures of the current LOD are loaded.
                 loader._executeWhenRenderReady(() => {
                     BaseTexture.WhenAllReady(babylonMaterial.getActiveTextures(), () => {
-                        setTimeout(()=> {
-                            this.loadMaterialLOD(loader, material, materialLODs, lod - 1, assign);
+                        setTimeout(() => {
+                            this._loadMaterialLOD(loader, materials, index - 1, assign, onComplete);
                         }, MSFTLOD.MinimalLODDelay);
                     });
                 });
             });
-
         }
     }
 

+ 146 - 129
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -1,10 +1,28 @@
 /// <reference path="../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2 {
+    class GLTFLoaderTracker {
+        private _pendingCount = 0;
+        private _callback: () => void;
+
+        constructor(onComplete: () => void) {
+            this._callback = onComplete;
+        }
+
+        public _addPendingData(data: any): void {
+            this._pendingCount++;
+        }
+
+        public _removePendingData(data: any): void {
+            if (--this._pendingCount === 0) {
+                this._callback();
+            }
+        }
+   }
+
     export class GLTFLoader implements IGLTFLoader, IDisposable {
         public _gltf: IGLTF;
         public _babylonScene: Scene;
-        public _blockPendingTracking = false;
 
         private _parent: GLTFFileLoader;
         private _rootUrl: string;
@@ -14,17 +32,16 @@ module BABYLON.GLTF2 {
         private _errorCallback: (message: string) => void;
         private _renderReady = false;
         private _disposed = false;
-        private _nonBlockingData: Array<any>;
-        private _rootMesh: Mesh;
 
-        // Observable with boolean indicating success or error.
         private _renderReadyObservable = new Observable<GLTFLoader>();
 
         // Count of pending work that needs to complete before the asset is rendered.
-        private _renderPendingCount: number = 0;
+        private _renderPendingCount = 0;
+
+        // Count of pending work that needs to complete before the loader is disposed.
+        private _loaderPendingCount = 0;
 
-        // Count of pending work that needs to complete before the loader is cleared.
-        private _loaderPendingCount: number = 0;
+        private _loaderTrackers = new Array<GLTFLoaderTracker>();
 
         public static Extensions: { [name: string]: GLTFLoaderExtension } = {};
 
@@ -101,14 +118,17 @@ module BABYLON.GLTF2 {
             if (this._errorCallback) {
                 this._errorCallback(message);
             }
+
             this.dispose();
         }
 
         private _onProgress(event: ProgressEvent): void {
-            this._progressCallback(event);
+            if (this._progressCallback) {
+                this._progressCallback(event);
+            }
         }
 
-        public _executeWhenRenderReady(func: () => void) {
+        public _executeWhenRenderReady(func: () => void): void {
             if (this._renderReady) {
                 func();
             }
@@ -118,39 +138,24 @@ module BABYLON.GLTF2 {
         }
 
         private _onRenderReady(): void {
-            switch (this._parent.coordinateSystemMode) {
-                case GLTFLoaderCoordinateSystemMode.AUTO:
-                    if (!this._babylonScene.useRightHandedSystem) {
-                        this._addRightHandToLeftHandRootTransform();
-                    }
-                    break;
-                case GLTFLoaderCoordinateSystemMode.PASS_THROUGH:
-                    // do nothing
-                    break;
-                case GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED:
-                    this._babylonScene.useRightHandedSystem = true;
-                    break;
-                default:
-                    Tools.Error("Invalid coordinate system mode (" + this._parent.coordinateSystemMode + ")");
-                    break;
-            }
+            var rootNode = this._gltf.nodes[this._gltf.nodes.length - 1];
+            rootNode.babylonMesh.setEnabled(true);
 
-            this._showMeshes();
             this._startAnimations();
             this._successCallback();
             this._renderReadyObservable.notifyObservers(this);
+
+            if (this._parent.onReady) {
+                this._parent.onReady();
+            }
         }
 
-        private _onLoaderComplete(): void {
+        private _onComplete(): void {
             if (this._parent.onComplete) {
                 this._parent.onComplete();
             }
-        }
 
-        private _onLoaderFirstLODComplete(): void {
-            if (this._parent.onFirstLODComplete) {
-                this._parent.onFirstLODComplete();
-            }
+            this.dispose();
         }
 
         private _loadData(data: IGLTFLoaderData): void {
@@ -176,30 +181,9 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _addRightHandToLeftHandRootTransform(): void {
-            this._rootMesh = new Mesh("root", this._babylonScene);
-            this._rootMesh.isVisible = false;
-            this._rootMesh.scaling = new Vector3(1, 1, -1);
-            this._rootMesh.rotation.y = Math.PI;
-
-            var nodes = this._gltf.nodes;
-            if (nodes) {
-                for (var i = 0; i < nodes.length; i++) {
-                    var mesh = nodes[i].babylonMesh;
-                    if (mesh && !mesh.parent) {
-                        mesh.parent = this._rootMesh;
-                    }
-                }
-            }
-        }
-
         private _getMeshes(): Mesh[] {
             var meshes = [];
 
-            if (this._rootMesh) {
-                meshes.push(this._rootMesh);
-            }
-
             var nodes = this._gltf.nodes;
             if (nodes) {
                 nodes.forEach(node => {
@@ -240,16 +224,38 @@ module BABYLON.GLTF2 {
             return targets;
         }
 
-        private _showMeshes(): void {
-            this._getMeshes().forEach(mesh => mesh.isVisible = true);
-        }
-
         private _startAnimations(): void {
             this._getAnimationTargets().forEach(target => this._babylonScene.beginAnimation(target, 0, Number.MAX_VALUE, true));
         }
 
         private _loadScene(nodeNames: any): void {
             var scene = this._gltf.scenes[this._gltf.scene || 0];
+
+            var rootNode = { name: "root", children: scene.nodes } as IGLTFNode;
+
+            switch (this._parent.coordinateSystemMode) {
+                case GLTFLoaderCoordinateSystemMode.AUTO:
+                    if (!this._babylonScene.useRightHandedSystem) {
+                        rootNode.rotation = [0, 1, 0, 0];
+                        rootNode.scale = [1, 1, -1];
+                    }
+                    break;
+                case GLTFLoaderCoordinateSystemMode.PASS_THROUGH:
+                    // do nothing
+                    break;
+                case GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED:
+                    this._babylonScene.useRightHandedSystem = true;
+                    break;
+                default:
+                    Tools.Error("Invalid coordinate system mode (" + this._parent.coordinateSystemMode + ")");
+                    break;
+            }
+
+            // Inject a root node into the scene.
+            this._gltf.nodes = this._gltf.nodes || [];
+            scene.nodes = [this._gltf.nodes.length];
+            this._gltf.nodes.push(rootNode);
+
             var nodeIndices = scene.nodes;
 
             this._traverseNodes(nodeIndices, (node, index, parentNode) => {
@@ -270,23 +276,31 @@ module BABYLON.GLTF2 {
 
                 var filteredNodeIndices = new Array<number>();
                 this._traverseNodes(nodeIndices, node => {
-                    if (nodeNames.indexOf(node.name) === -1) {
-                        return true;
+                    if (nodeNames.indexOf(node.name) !== -1) {
+                        filteredNodeIndices.push(node.index);
+                        return false;
                     }
 
-                    filteredNodeIndices.push(node.index);
-                    return false;
+                    return true;
                 });
 
                 nodeIndices = filteredNodeIndices;
             }
 
-            this._traverseNodes(nodeIndices, node => this._loadNode(node));
+            for (var i = 0; i < nodeIndices.length; i++) {
+                this._loadNode(this._gltf.nodes[nodeIndices[i]]);
+            }
+
+            // Disable the root mesh until the asset is ready to render.
+            rootNode.babylonMesh.setEnabled(false);
         }
 
-        private _loadNode(node: IGLTFNode): boolean {
+        public _loadNode(node: IGLTFNode): void {
+            if (GLTFLoaderExtension.LoadNode(this, node)) {
+                return;
+            }
+
             node.babylonMesh = new Mesh(node.name || "mesh" + node.index, this._babylonScene);
-            node.babylonMesh.isVisible = false;
 
             this._loadTransform(node);
 
@@ -310,7 +324,11 @@ module BABYLON.GLTF2 {
                 // TODO: handle cameras
             }
 
-            return true;
+            if (node.children) {
+                for (var i = 0; i < node.children.length; i++) {
+                    this._loadNode(this._gltf.nodes[node.children[i]]);
+                }
+            }
         }
 
         private _loadMesh(node: IGLTFNode, mesh: IGLTFMesh): void {
@@ -349,25 +367,25 @@ module BABYLON.GLTF2 {
                         loadMaterial: () => {
                             if (primitive.material === undefined) {
                                 babylonMultiMaterial.subMaterials[i] = this._getDefaultMaterial();
+                                return;
                             }
-                            else {
-                                var material = this._gltf.materials[primitive.material];
-                                this._loadMaterial(material, (babylonMaterial, isNew) => {
-                                    if (isNew && this._parent.onMaterialLoaded) {
-                                        this._parent.onMaterialLoaded(babylonMaterial);
-                                    }
-                                    
-                                    if (this._parent.onBeforeMaterialReadyAsync) {
-                                        this._addLoaderPendingData(material);
-                                        this._parent.onBeforeMaterialReadyAsync(babylonMaterial, node.babylonMesh, babylonMultiMaterial.subMaterials[i] != null, () => {
-                                            babylonMultiMaterial.subMaterials[i] = babylonMaterial;
-                                            this._removeLoaderPendingData(material);
-                                        });
-                                    } else {
+
+                            var material = this._gltf.materials[primitive.material];
+                            this._loadMaterial(material, (babylonMaterial, isNew) => {
+                                if (isNew && this._parent.onMaterialLoaded) {
+                                    this._parent.onMaterialLoaded(babylonMaterial);
+                                }
+
+                                if (this._parent.onBeforeMaterialReadyAsync) {
+                                    this._addLoaderPendingData(material);
+                                    this._parent.onBeforeMaterialReadyAsync(babylonMaterial, node.babylonMesh, babylonMultiMaterial.subMaterials[i] != null, () => {
                                         babylonMultiMaterial.subMaterials[i] = babylonMaterial;
-                                    }
-                                });
-                            }
+                                        this._removeLoaderPendingData(material);
+                                    });
+                                } else {
+                                    babylonMultiMaterial.subMaterials[i] = babylonMaterial;
+                                }
+                            });
                         }
                     });
 
@@ -449,7 +467,7 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _createMorphTargets(node: IGLTFNode, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive, babylonMesh: Mesh) {
+        private _createMorphTargets(node: IGLTFNode, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive, babylonMesh: Mesh): void {
             var targets = primitive.targets;
             if (!targets) {
                 return;
@@ -611,17 +629,18 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _traverseNode(index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode = null): void {
-            var node = this._gltf.nodes[index];
+        public _traverseNode(index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode = null): void {
+            if (GLTFLoaderExtension.TraverseNode(this, index, action, parentNode)) {
+                return;
+            }
 
+            var node = this._gltf.nodes[index];
             if (!action(node, index, parentNode)) {
                 return;
             }
 
             if (node.children) {
-                for (var i = 0; i < node.children.length; i++) {
-                    this._traverseNode(node.children[i], action, node);
-                }
+                this._traverseNodes(node.children, action, node);
             }
         }
 
@@ -754,14 +773,12 @@ module BABYLON.GLTF2 {
                 }
             };
 
-            this._loadAccessorAsync(this._gltf.accessors[sampler.input], data =>
-            {
+            this._loadAccessorAsync(this._gltf.accessors[sampler.input], data => {
                 inputData = <Float32Array>data;
                 checkSuccess();
             });
 
-            this._loadAccessorAsync(this._gltf.accessors[sampler.output], data =>
-            {
+            this._loadAccessorAsync(this._gltf.accessors[sampler.output], data => {
                 outputData = <Float32Array>data;
                 checkSuccess();
             });
@@ -825,7 +842,7 @@ module BABYLON.GLTF2 {
 
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
 
-            return targetBuffer;              
+            return targetBuffer;
         }
 
         private _buildUint8ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint8Array {
@@ -838,8 +855,8 @@ module BABYLON.GLTF2 {
 
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride, targetBuffer.length);
 
-            return targetBuffer;              
-        }        
+            return targetBuffer;
+        }
 
         private _buildInt16ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Int16Array {
             if (!byteStride) {
@@ -851,8 +868,8 @@ module BABYLON.GLTF2 {
 
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
 
-            return targetBuffer;             
-        }   
+            return targetBuffer;
+        }
 
         private _buildUint16ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint16Array {
             if (!byteStride) {
@@ -864,9 +881,9 @@ module BABYLON.GLTF2 {
 
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 2, targetBuffer.length);
 
-            return targetBuffer;             
-        }          
-        
+            return targetBuffer;
+        }
+
         private _buildUint32ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Uint32Array {
             if (!byteStride) {
                 return new Uint32Array(buffer, byteOffset, byteLength);
@@ -877,9 +894,9 @@ module BABYLON.GLTF2 {
 
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
 
-            return targetBuffer;            
-        }     
-        
+            return targetBuffer;
+        }
+
         private _buildFloat32ArrayBuffer(buffer: ArrayBuffer, byteOffset: number, byteLength: number, byteStride: number, bytePerComponent: number): Float32Array {
             if (!byteStride) {
                 return new Float32Array(buffer, byteOffset, byteLength);
@@ -891,11 +908,11 @@ module BABYLON.GLTF2 {
             this._extractInterleavedData(sourceBuffer, targetBuffer, bytePerComponent, byteStride / 4, targetBuffer.length);
 
             return targetBuffer;
-        }    
-        
+        }
+
         private _extractInterleavedData(sourceBuffer: ArrayBufferView, targetBuffer: ArrayBufferView, bytePerComponent: number, stride: number, length: number): void {
             let tempIndex = 0;
-            let sourceIndex = 0;            
+            let sourceIndex = 0;
             let storageSize = bytePerComponent;
 
             while (tempIndex < length) {
@@ -971,7 +988,7 @@ module BABYLON.GLTF2 {
             }
         }
 
-        public _addPendingData(data: any) {
+        public _addPendingData(data: any): void {
             if (!this._renderReady) {
                 this._renderPendingCount++;
             }
@@ -979,7 +996,7 @@ module BABYLON.GLTF2 {
             this._addLoaderPendingData(data);
         }
 
-        public _removePendingData(data: any) {
+        public _removePendingData(data: any): void {
             if (!this._renderReady) {
                 if (--this._renderPendingCount === 0) {
                     this._renderReady = true;
@@ -990,33 +1007,33 @@ module BABYLON.GLTF2 {
             this._removeLoaderPendingData(data);
         }
 
-        public _addLoaderNonBlockingPendingData(data: any): void {
-            if (!this._nonBlockingData) {
-                this._nonBlockingData = new Array<any>();
-            }
-            this._nonBlockingData.push(data);
+        public _addLoaderPendingData(data: any): void {
+            this._loaderPendingCount++;
+
+            this._loaderTrackers.forEach(tracker => tracker._addPendingData(data));
         }
 
-        public _addLoaderPendingData(data: any) {
-            if (this._blockPendingTracking) {
-                this._addLoaderNonBlockingPendingData(data);
-                return;
+        public _removeLoaderPendingData(data: any): void {
+            this._loaderTrackers.forEach(tracker => tracker._removePendingData(data));
+
+            if (--this._loaderPendingCount === 0) {
+                this._onComplete();
             }
-            this._loaderPendingCount++;
         }
 
-        public _removeLoaderPendingData(data: any) {
-            var indexInPending = this._nonBlockingData ? this._nonBlockingData.indexOf(data) : -1;
-            if (indexInPending !== -1) {
-                this._nonBlockingData.splice(indexInPending, 1);
-            } else if (--this._loaderPendingCount === 0) {
-                this._onLoaderFirstLODComplete();
-            }
+        public _whenAction(action: () => void, onComplete: () => void): void {
+            var tracker = new GLTFLoaderTracker(() => {
+                this._loaderTrackers.splice(this._loaderTrackers.indexOf(tracker));
+                onComplete();
+            });
 
-            if ((!this._nonBlockingData || this._nonBlockingData.length === 0) && this._loaderPendingCount === 0) {
-                this._onLoaderComplete();
-                this.dispose();
-            }
+            this._loaderTrackers.push(tracker);
+
+            this._addLoaderPendingData(tracker);
+
+            action();
+
+            this._removeLoaderPendingData(tracker);
         }
 
         private _getDefaultMaterial(): Material {

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

@@ -6,7 +6,32 @@ module BABYLON.GLTF2 {
 
         public abstract get name(): string;
 
-        protected loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean { return false; }
+        protected _traverseNode(loader: GLTFLoader, index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean { return false; }
+
+        protected _loadNode(loader: GLTFLoader, node: IGLTFNode): boolean { return false; }
+
+        protected _loadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean { return false; }
+
+        protected _loadExtension<T>(property: IGLTFProperty, action: (extension: T, onComplete: () => void) => void): boolean {
+            if (!property.extensions) {
+                return false;
+            }
+
+            var extension = property.extensions[this.name] as T;
+            if (!extension) {
+                return false;
+            }
+
+            // Clear out the extension before executing the action to avoid recursing into the same property.
+            property.extensions[this.name] = undefined;
+
+            action(extension, () => {
+                // Restore the extension after completing the action.
+                property.extensions[this.name] = extension;
+            });
+
+            return true;
+        }
 
         //
         // Utilities
@@ -14,8 +39,16 @@ module BABYLON.GLTF2 {
 
         public static _Extensions: GLTFLoaderExtension[] = [];
 
+        public static TraverseNode(loader: GLTFLoader, index: number, action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode): boolean {
+            return this._ApplyExtensions(extension => extension._traverseNode(loader, index, action, parentNode));
+        }
+
+        public static LoadNode(loader: GLTFLoader, node: IGLTFNode): boolean {
+            return this._ApplyExtensions(extension => extension._loadNode(loader, node));
+        }
+
         public static LoadMaterial(loader: GLTFLoader, material: IGLTFMaterial, assign: (babylonMaterial: Material, isNew: boolean) => void): boolean {
-            return this._ApplyExtensions(extension => extension.loadMaterial(loader, material, assign));
+            return this._ApplyExtensions(extension => extension._loadMaterial(loader, material, assign));
         }
 
         private static _ApplyExtensions(action: (extension: GLTFLoaderExtension) => boolean) {

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

@@ -38,18 +38,25 @@ module BABYLON {
         public coordinateSystemMode: GLTFLoaderCoordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
         public onTextureLoaded: (texture: BaseTexture) => void;
         public onMaterialLoaded: (material: Material) => void;
+
         /**
          * Let the user decides if he needs to process the material (like precompilation) before affecting it to meshes
          */
         public onBeforeMaterialReadyAsync: (material: Material, targetMesh: AbstractMesh, isLOD: boolean, callback: () => void) => void;
+
         /**
-         * Raised when all LODs are complete (or if there is no LOD and model is complete)
+         * Raised when the visible components (geometry, materials, textures, etc.) are first ready to be rendered.
+         * For assets with LODs, raised when the first LOD is complete.
+         * For assets without LODs, raised when the model is complete just before onComplete.
          */
-        public onComplete: () => void;
+        public onReady: () => void;
+
         /**
-         * Raised when first LOD complete (or if there is no LOD and model is complete)
+         * Raised when the asset is completely loaded, just before the loader is disposed.
+         * For assets with LODs, raised when all of the LODs are complete.
+         * For assets without LODs, raised when the model is complete just after onReady.
          */
-        public onFirstLODComplete: () => void;
+        public onComplete: () => void;
 
         public name = "gltf";