Procházet zdrojové kódy

Merge pull request #4845 from bghgary/gltf-loader-extensions

Add support for registering custom glTF loader extensions
David Catuhe před 7 roky
rodič
revize
e3a43adbd9

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

@@ -100,6 +100,7 @@
 - Added debug logging and performance counters ([bghgary](http://www.github.com/bghgary))
 - Added support for EXT_lights_imageBased ([bghgary](http://www.github.com/bghgary))
 - Added support for MSFT_audio_emitter ([najadojo](http://www.github.com/najadojo))
+- Added support for custom loader extensions ([bghgary](http://www.github.com/bghgary))
 
 ### Viewer
 

+ 39 - 21
loaders/src/glTF/2.0/Extensions/EXT_lights_imageBased.ts

@@ -25,33 +25,51 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](TODO) (Experimental)
      */
-    export class EXT_lights_imageBased extends GLTFLoaderExtension {
+    export class EXT_lights_imageBased implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
         private _lights?: ILight[];
 
-        protected _onLoading(): void {
-            const extensions = this._loader._gltf.extensions;
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+            delete this._lights;
+        }
+
+        /** @hidden */
+        public onLoading(): void {
+            const extensions = this._loader.gltf.extensions;
             if (extensions && extensions[this.name]) {
                 const extension = extensions[this.name] as ILights;
                 this._lights = extension.lights;
             }
         }
 
-        protected _loadSceneAsync(context: string, scene: _ILoaderScene): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<ILightReference>(context, scene, (extensionContext, extension) => {
-                const promises = new Array<Promise<void>>();
+        /** @hidden */
+        public loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>> { 
+            return GLTFLoader.LoadExtensionAsync<ILightReference>(context, scene, this.name, (extensionContext, extension) => {
+                const promises = new Array<Promise<any>>();
 
-                promises.push(this._loader._loadSceneAsync(context, scene));
+                promises.push(this._loader.loadSceneAsync(context, scene));
 
-                this._loader._parent._logOpen(`${extensionContext}`);
+                this._loader.logOpen(`${extensionContext}`);
 
-                const light = GLTFLoader._GetProperty(`${extensionContext}/light`, this._lights, extension.light);
+                const light = ArrayItem.Get(`${extensionContext}/light`, this._lights, extension.light);
                 promises.push(this._loadLightAsync(`#/extensions/${this.name}/lights/${extension.light}`, light).then(texture => {
-                    this._loader._babylonScene.environmentTexture = texture;
+                    this._loader.babylonScene.environmentTexture = texture;
                 }));
 
-                this._loader._parent._logClose();
+                this._loader.logClose();
 
                 return Promise.all(promises).then(() => {});
             });
@@ -59,9 +77,9 @@ module BABYLON.GLTF2.Extensions {
 
         private _loadLightAsync(context: string, light: ILight): Promise<BaseTexture> {
             if (!light._loaded) {
-                const promises = new Array<Promise<void>>();
+                const promises = new Array<Promise<any>>();
 
-                this._loader._parent._logOpen(`${context}`);
+                this._loader.logOpen(`${context}`);
 
                 const imageData = new Array<Array<ArrayBufferView>>(light.specularImages.length);
                 for (let mipmap = 0; mipmap < light.specularImages.length; mipmap++) {
@@ -69,22 +87,22 @@ module BABYLON.GLTF2.Extensions {
                     imageData[mipmap] = new Array<ArrayBufferView>(faces.length);
                     for (let face = 0; face < faces.length; face++) {
                         const specularImageContext = `${context}/specularImages/${mipmap}/${face}`;
-                        this._loader._parent._logOpen(`${specularImageContext}`);
+                        this._loader.logOpen(`${specularImageContext}`);
 
                         const index = faces[face];
-                        const image = GLTFLoader._GetProperty(specularImageContext, this._loader._gltf.images, index);
-                        promises.push(this._loader._loadImageAsync(`#/images/${index}`, image).then(data => {
+                        const image = ArrayItem.Get(specularImageContext, this._loader.gltf.images, index);
+                        promises.push(this._loader.loadImageAsync(`#/images/${index}`, image).then(data => {
                             imageData[mipmap][face] = data;
                         }));
 
-                        this._loader._parent._logClose();
+                        this._loader.logClose();
                     }
                 }
 
-                this._loader._parent._logClose();
+                this._loader.logClose();
 
                 light._loaded = Promise.all(promises).then(() => {
-                    const babylonTexture = new RawCubeTexture(this._loader._babylonScene, null, light.specularImageSize);
+                    const babylonTexture = new RawCubeTexture(this._loader.babylonScene, null, light.specularImageSize);
                     light._babylonTexture = babylonTexture;
 
                     if (light.intensity != undefined) {
@@ -95,7 +113,7 @@ module BABYLON.GLTF2.Extensions {
                         let rotation = Quaternion.FromArray(light.rotation);
 
                         // Invert the rotation so that positive rotation is counter-clockwise.
-                        if (!this._loader._babylonScene.useRightHandedSystem) {
+                        if (!this._loader.babylonScene.useRightHandedSystem) {
                             rotation = Quaternion.Inverse(rotation);
                         }
 
@@ -120,5 +138,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new EXT_lights_imageBased(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new EXT_lights_imageBased(loader));
 }

+ 20 - 16
loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts

@@ -8,37 +8,41 @@ module BABYLON.GLTF2.Extensions {
         attributes: { [name: string]: number };
     }
 
-    interface ILoaderBufferViewDraco extends _ILoaderBufferView {
+    interface ILoaderBufferViewDraco extends ILoaderBufferView {
         _dracoBabylonGeometry?: Promise<Geometry>;
     }
 
     /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression)
      */
-    export class KHR_draco_mesh_compression extends GLTFLoaderExtension {
+    export class KHR_draco_mesh_compression implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
-        private _dracoCompression: Nullable<DracoCompression> = null;
+        /** Defines whether this extension is enabled. */
+        public enabled = DracoCompression.DecoderAvailable;
 
-        constructor(loader: GLTFLoader) {
-            super(loader);
+        private _loader: GLTFLoader;
+        private _dracoCompression?: DracoCompression;
 
-            // Disable extension if decoder is not available.
-            if (!DracoCompression.DecoderAvailable) {
-                this.enabled = false;
-            }
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
         }
 
+        /** @hidden */
         public dispose(): void {
             if (this._dracoCompression) {
                 this._dracoCompression.dispose();
+                delete this._dracoCompression;
             }
 
-            super.dispose();
+            delete this._loader;
         }
 
-        protected _loadVertexDataAsync(context: string, primitive: _ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>> {
-            return this._loadExtensionAsync<IKHRDracoMeshCompression, Geometry>(context, primitive, (extensionContext, extension) => {
+        /** @hidden */
+        public _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>> {
+            return GLTFLoader.LoadExtensionAsync<IKHRDracoMeshCompression, Geometry>(context, primitive, this.name, (extensionContext, extension) => {
                 if (primitive.mode != undefined) {
                     if (primitive.mode !== MeshPrimitiveMode.TRIANGLE_STRIP &&
                         primitive.mode !== MeshPrimitiveMode.TRIANGLES) {
@@ -75,15 +79,15 @@ module BABYLON.GLTF2.Extensions {
                 loadAttribute("WEIGHTS_0", VertexBuffer.MatricesWeightsKind);
                 loadAttribute("COLOR_0", VertexBuffer.ColorKind);
 
-                var bufferView = GLTFLoader._GetProperty(extensionContext, this._loader._gltf.bufferViews, extension.bufferView) as ILoaderBufferViewDraco;
+                var bufferView = ArrayItem.Get(extensionContext, this._loader.gltf.bufferViews, extension.bufferView) as ILoaderBufferViewDraco;
                 if (!bufferView._dracoBabylonGeometry) {
-                    bufferView._dracoBabylonGeometry = this._loader._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView).then(data => {
+                    bufferView._dracoBabylonGeometry = this._loader.loadBufferViewAsync(`#/bufferViews/${bufferView.index}`, bufferView).then(data => {
                         if (!this._dracoCompression) {
                             this._dracoCompression = new DracoCompression();
                         }
 
                         return this._dracoCompression.decodeMeshAsync(data, attributes).then(babylonVertexData => {
-                            const babylonGeometry = new Geometry(babylonMesh.name, this._loader._babylonScene);
+                            const babylonGeometry = new Geometry(babylonMesh.name, this._loader.babylonScene);
                             babylonVertexData.applyToGeometry(babylonGeometry);
                             return babylonGeometry;
                         }).catch(error => {
@@ -97,5 +101,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHR_draco_mesh_compression(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new KHR_draco_mesh_compression(loader));
 }

+ 62 - 43
loaders/src/glTF/2.0/Extensions/KHR_lights.ts

@@ -31,74 +31,93 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](https://github.com/MiiBond/glTF/tree/khr_lights_v1/extensions/Khronos/KHR_lights) (Experimental)
      */
-    export class KHR_lights extends GLTFLoaderExtension {
+    export class KHR_lights implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
         private _lights?: ILight[];
 
-        protected _onLoading(): void {
-            const extensions = this._loader._gltf.extensions;
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+            delete this._lights;
+        }
+
+        /** @hidden */
+        public onLoading(): void {
+            const extensions = this._loader.gltf.extensions;
             if (extensions && extensions[this.name]) {
                 const extension = extensions[this.name] as ILights;
                 this._lights = extension.lights;
             }
         }
 
-        protected _loadSceneAsync(context: string, scene: _ILoaderScene): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<ILightReference>(context, scene, (extensionContext, extension) => {
-                const promise = this._loader._loadSceneAsync(extensionContext, scene);
+        /** @hidden */
+        public loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>> { 
+            return GLTFLoader.LoadExtensionAsync<ILightReference>(context, scene, this.name, (extensionContext, extension) => {
+                const promise = this._loader.loadSceneAsync(context, scene);
 
-                const light = GLTFLoader._GetProperty(extensionContext, this._lights, extension.light);
+                const light = ArrayItem.Get(extensionContext, this._lights, extension.light);
                 if (light.type !== LightType.AMBIENT) {
                     throw new Error(`${extensionContext}: Only ambient lights are allowed on a scene`);
                 }
 
-                this._loader._babylonScene.ambientColor = light.color ? Color3.FromArray(light.color) : Color3.Black();
+                this._loader.babylonScene.ambientColor = light.color ? Color3.FromArray(light.color) : Color3.Black();
 
                 return promise;
             });
         }
 
-        protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<ILightReference>(context, node, (extensionContext, extension) => {
-                const promise = this._loader._loadNodeAsync(extensionContext, node);
-
-                let babylonLight: Light;
-
-                const light = GLTFLoader._GetProperty(extensionContext, this._lights, extension.light);
-                const name = node._babylonMesh!.name;
-                switch (light.type) {
-                    case LightType.AMBIENT: {
-                        throw new Error(`${extensionContext}: Ambient lights are not allowed on a node`);
+        /** @hidden */
+        public loadNodeAsync(context: string, node: ILoaderNode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> { 
+            return GLTFLoader.LoadExtensionAsync<ILightReference, Mesh>(context, node, this.name, (extensionContext, extension) => {
+                return this._loader.loadNodeAsync(context, node, babylonMesh => {
+                    let babylonLight: Light;
+
+                    const name = babylonMesh.name;
+                    const light = ArrayItem.Get(extensionContext, this._lights, extension.light);
+                    switch (light.type) {
+                        case LightType.AMBIENT: {
+                            throw new Error(`${extensionContext}: 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: {
+                            // TODO: support inner and outer cone angles
+                            //const innerConeAngle = spotLight.innerConeAngle || 0;
+                            const outerConeAngle = light.spot && light.spot.outerConeAngle || Math.PI / 4;
+                            babylonLight = new SpotLight(name, Vector3.Zero(), Vector3.Forward(), outerConeAngle, 2, this._loader.babylonScene);
+                            break;
+                        }
+                        default: {
+                            throw new Error(`${extensionContext}: Invalid light type (${light.type})`);
+                        }
                     }
-                    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: {
-                        // TODO: support inner and outer cone angles
-                        //const innerConeAngle = spotLight.innerConeAngle || 0;
-                        const outerConeAngle = light.spot && light.spot.outerConeAngle || Math.PI / 4;
-                        babylonLight = new SpotLight(name, Vector3.Zero(), Vector3.Forward(), outerConeAngle, 2, this._loader._babylonScene);
-                        break;
-                    }
-                    default: {
-                        throw new Error(`${extensionContext}: Invalid light type (${light.type})`);
-                    }
-                }
 
-                babylonLight.diffuse = light.color ? Color3.FromArray(light.color) : Color3.White();
-                babylonLight.intensity = light.intensity == undefined ? 1 : light.intensity;
-                babylonLight.parent = node._babylonMesh!;
+                    babylonLight.diffuse = light.color ? Color3.FromArray(light.color) : Color3.White();
+                    babylonLight.intensity = light.intensity == undefined ? 1 : light.intensity;
+                    babylonLight.parent = babylonMesh;
 
-                return promise;
+                    assign(babylonMesh);
+                });
             });
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHR_lights(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new KHR_lights(loader));
 }

+ 38 - 13
loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts

@@ -14,20 +14,45 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
      */
-    export class KHR_materials_pbrSpecularGlossiness extends GLTFLoaderExtension {
+    export class KHR_materials_pbrSpecularGlossiness implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
-        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
-            return this._loadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, (extensionContext, extension) => {
-                const promises = new Array<Promise<void>>();
-                promises.push(this._loader._loadMaterialBasePropertiesAsync(context, material, babylonMaterial as PBRMaterial));
-                promises.push(this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial as PBRMaterial));
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
+
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+        }
+
+        /** @hidden */
+        public loadMaterialPropertiesAsync(context: string, material: ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+            return GLTFLoader.LoadExtensionAsync<IKHRMaterialsPbrSpecularGlossiness>(context, material, this.name, (extensionContext, extension) => {
+                const promises = new Array<Promise<any>>();
+                promises.push(this._loader.loadMaterialBasePropertiesAsync(context, material, babylonMaterial));
+                promises.push(this._loadSpecularGlossinessPropertiesAsync(extensionContext, material, extension, babylonMaterial));
+                this._loader.loadMaterialAlphaProperties(context, material, babylonMaterial);
                 return Promise.all(promises).then(() => {});
             });
         }
 
-        private _loadSpecularGlossinessPropertiesAsync(context: string, material: _ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness, babylonMaterial: PBRMaterial): Promise<void> {
-            const promises = new Array<Promise<void>>();
+        private _loadSpecularGlossinessPropertiesAsync(context: string, material: ILoaderMaterial, properties: IKHRMaterialsPbrSpecularGlossiness, babylonMaterial: Material): Promise<void> {
+            if (!(babylonMaterial instanceof PBRMaterial)) {
+                throw new Error(`${context}: Material type not supported`);
+            }
+
+            const promises = new Array<Promise<any>>();
+
+            babylonMaterial.metallic = null;
+            babylonMaterial.roughness = null;
 
             if (properties.diffuseFactor) {
                 babylonMaterial.albedoColor = Color3.FromArray(properties.diffuseFactor);
@@ -41,25 +66,25 @@ module BABYLON.GLTF2.Extensions {
             babylonMaterial.microSurface = properties.glossinessFactor == undefined ? 1 : properties.glossinessFactor;
 
             if (properties.diffuseTexture) {
-                promises.push(this._loader._loadTextureInfoAsync(`${context}/diffuseTexture`, properties.diffuseTexture, texture => {
+                promises.push(this._loader.loadTextureInfoAsync(`${context}/diffuseTexture`, properties.diffuseTexture, texture => {
                     babylonMaterial.albedoTexture = texture;
+                    return Promise.resolve();
                 }));
             }
 
             if (properties.specularGlossinessTexture) {
-                promises.push(this._loader._loadTextureInfoAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, texture => {
+                promises.push(this._loader.loadTextureInfoAsync(`${context}/specularGlossinessTexture`, properties.specularGlossinessTexture, texture => {
                     babylonMaterial.reflectivityTexture = texture;
+                    return Promise.resolve();
                 }));
 
                 babylonMaterial.reflectivityTexture.hasAlpha = true;
                 babylonMaterial.useMicroSurfaceFromReflectivityMapAlpha = true;
             }
 
-            this._loader._loadMaterialAlphaProperties(context, material, babylonMaterial);
-
             return Promise.all(promises).then(() => {});
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHR_materials_pbrSpecularGlossiness(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new KHR_materials_pbrSpecularGlossiness(loader));
 }

+ 31 - 13
loaders/src/glTF/2.0/Extensions/KHR_materials_unlit.ts

@@ -6,24 +6,41 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit)
      */
-    export class KHR_materials_unlit extends GLTFLoaderExtension {
+    export class KHR_materials_unlit implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
-        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
-            return this._loadExtensionAsync<{}>(context, material, () => {
-                return this._loadUnlitPropertiesAsync(context, material, babylonMaterial as PBRMaterial);
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
+
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+        }
+
+        /** @hidden */
+        public loadMaterialPropertiesAsync(context: string, material: ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+            return GLTFLoader.LoadExtensionAsync(context, material, this.name, () => {
+                return this._loadUnlitPropertiesAsync(context, material, babylonMaterial);
             });
         }
 
-        private _loadUnlitPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: PBRMaterial): Promise<void> {
-            const promises = new Array<Promise<void>>();
+        private _loadUnlitPropertiesAsync(context: string, material: ILoaderMaterial, babylonMaterial: Material): Promise<void> {
+            if (!(babylonMaterial instanceof PBRMaterial)) {
+                throw new Error(`${context}: Material type not supported`);
+            }
+
+            const promises = new Array<Promise<any>>();
 
             babylonMaterial.unlit = true;
 
-            // Ensure metallic workflow
-            babylonMaterial.metallic = 1;
-            babylonMaterial.roughness = 1;
-
             const properties = material.pbrMetallicRoughness;
             if (properties) {
                 if (properties.baseColorFactor) {
@@ -35,8 +52,9 @@ module BABYLON.GLTF2.Extensions {
                 }
 
                 if (properties.baseColorTexture) {
-                    promises.push(this._loader._loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
+                    promises.push(this._loader.loadTextureInfoAsync(`${context}/baseColorTexture`, properties.baseColorTexture, texture => {
                         babylonMaterial.albedoTexture = texture;
+                        return Promise.resolve();
                     }));
                 }
             }
@@ -46,11 +64,11 @@ module BABYLON.GLTF2.Extensions {
                 babylonMaterial.twoSidedLighting = true;
             }
 
-            this._loader._loadMaterialAlphaProperties(context, material, babylonMaterial);
+            this._loader.loadMaterialAlphaProperties(context, material, babylonMaterial);
 
             return Promise.all(promises).then(() => {});
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHR_materials_unlit(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new KHR_materials_unlit(loader));
 }

+ 26 - 5
loaders/src/glTF/2.0/Extensions/KHR_texture_transform.ts

@@ -13,12 +13,33 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](https://github.com/AltspaceVR/glTF/blob/avr-sampler-offset-tile/extensions/2.0/Khronos/KHR_texture_transform/README.md) (Experimental)
      */
-    export class KHR_texture_transform extends GLTFLoaderExtension {
+    export class KHR_texture_transform implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
-        protected _loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
-            return this._loadExtensionAsync<IKHRTextureTransform>(context, textureInfo, (extensionContext, extension) => {
-                return this._loader._loadTextureInfoAsync(context, textureInfo, babylonTexture => {
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
+
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+        }
+
+        /** @hidden */
+        public loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>> {
+            return GLTFLoader.LoadExtensionAsync<IKHRTextureTransform, BaseTexture>(context, textureInfo, this.name, (extensionContext, extension) => {
+                return this._loader.loadTextureInfoAsync(context, textureInfo, babylonTexture => {
+                    if (!(babylonTexture instanceof Texture)) {
+                        throw new Error(`${extensionContext}: Texture type not supported`);
+                    }
+
                     if (extension.offset) {
                         babylonTexture.uOffset = extension.offset[0];
                         babylonTexture.vOffset = extension.offset[1];
@@ -47,5 +68,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new KHR_texture_transform(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new KHR_texture_transform(loader));
 }

+ 163 - 137
loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts

@@ -1,27 +1,26 @@
 /// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GLTF2.Extensions {
-
     const NAME = "MSFT_audio_emitter";
 
-    interface _IClipReference {
+    interface IClipReference {
         clip: number;
         weight?: number;
     }
 
-    interface _IEmittersReference {
+    interface IEmittersReference {
         emitters: number[];
     }
 
-    const enum _DistanceModel {
+    const enum DistanceModel {
         linear = "linear",
         inverse = "inverse",
         exponential = "exponential",
     }
 
-    interface _IEmitter {
+    interface IEmitter {
         name?: string;
-        distanceModel?: _DistanceModel;
+        distanceModel?: DistanceModel;
         refDistance?: number;
         maxDistance?: number;
         rolloffFactor?: number;
@@ -29,74 +28,178 @@ module BABYLON.GLTF2.Extensions {
         outerAngle?: number;
         loop?: boolean;
         volume?: number;
-        clips: _IClipReference[];
+        clips: IClipReference[];
     }
 
-    const enum _AudioMimeType {
+    const enum AudioMimeType {
         WAV = "audio/wav",
     }
 
-    interface _IClip {
+    interface IClip {
         uri?: string;
         bufferView?: number;
-        mimeType?: _AudioMimeType;
+        mimeType?: AudioMimeType;
     }
 
-    interface _ILoaderClip extends _IClip, _IArrayItem {
+    interface ILoaderClip extends IClip, IArrayItem {
         _objectURL?: Promise<string>;
     }
 
-    interface _ILoaderEmitter extends _IEmitter, _IArrayItem {
-        _babylonData?: { 
+    interface ILoaderEmitter extends IEmitter, IArrayItem {
+        _babylonData?: {
             sound?: WeightedSound;
             loaded: Promise<void>;
         };
         _babylonSounds: Sound[];
     }
 
-    interface _IMSFTAudioEmitter {
-        clips: _ILoaderClip[];
-        emitters: _ILoaderEmitter[];
+    interface IMSFTAudioEmitter {
+        clips: ILoaderClip[];
+        emitters: ILoaderEmitter[];
     }
 
-    const enum _AnimationEventAction {
+    const enum AnimationEventAction {
         play = "play",
         pause = "pause",
         stop = "stop",
     }
 
-    interface _IAnimationEvent {
-        action: _AnimationEventAction,
+    interface IAnimationEvent {
+        action: AnimationEventAction,
         emitter: number;
         time: number;
         startOffset?: number;
     }
 
-    interface _ILoaderAnimationEvent extends _IAnimationEvent, _IArrayItem {
+    interface ILoaderAnimationEvent extends IAnimationEvent, IArrayItem {
     }
 
-    interface _ILoaderAnimationEvents {
-        events: _ILoaderAnimationEvent[];
+    interface ILoaderAnimationEvents {
+        events: ILoaderAnimationEvent[];
     }
 
     /**
      * [Specification](https://github.com/najadojo/glTF/tree/MSFT_audio_emitter/extensions/2.0/Vendor/MSFT_audio_emitter)
      */
-    export class MSFT_audio_emitter extends GLTFLoaderExtension {
+    export class MSFT_audio_emitter implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
-        private _loadClipAsync(context: string, clip: _ILoaderClip): Promise<string> {
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
+        private _loader: GLTFLoader;
+        private _clips: Array<ILoaderClip>;
+        private _emitters: Array<ILoaderEmitter>;
+
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        /** @hidden */
+        public dispose() {
+            delete this._loader;
+            delete this._clips;
+            delete this._emitters;
+        }
+
+        /** @hidden */
+        public onLoading(): void {
+            const extensions = this._loader.gltf.extensions;
+            if (extensions && extensions[this.name]) {
+                const extension = extensions[this.name] as IMSFTAudioEmitter;
+
+                this._clips = extension.clips;
+                this._emitters = extension.emitters;
+
+                ArrayItem.Assign(this._clips);
+                ArrayItem.Assign(this._emitters);
+            }
+        }
+
+        /** @hidden */
+        public loadSceneAsync(context: string, scene: ILoaderScene): Nullable<Promise<void>> {
+            return GLTFLoader.LoadExtensionAsync<IEmittersReference>(context, scene, this.name, (extensionContext, extension) => {
+                const promises = new Array<Promise<any>>();
+
+                promises.push(this._loader.loadSceneAsync(context, scene));
+
+                for (const emitterIndex of extension.emitters) {
+                    const emitter = ArrayItem.Get(`${extensionContext}/emitters`, this._emitters, emitterIndex);
+                    if (emitter.refDistance != undefined || emitter.maxDistance != undefined || emitter.rolloffFactor != undefined ||
+                        emitter.distanceModel != undefined || emitter.innerAngle != undefined || emitter.outerAngle != undefined) {
+                        throw new Error(`${extensionContext}: Direction or Distance properties are not allowed on emitters attached to a scene`);
+                    }
+
+                    promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter.index}`, emitter));
+                }
+
+                return Promise.all(promises).then(() => {});
+            });
+        }
+
+        /** @hidden */
+        public loadNodeAsync(context: string, node: ILoaderNode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
+            return GLTFLoader.LoadExtensionAsync<IEmittersReference, Mesh>(context, node, this.name, (extensionContext, extension) => {
+                const promises = new Array<Promise<any>>();
+
+                return this._loader.loadNodeAsync(extensionContext, node, babylonMesh => {
+                    for (const emitterIndex of extension.emitters) {
+                        const emitter = ArrayItem.Get(`${extensionContext}/emitters`, this._emitters, emitterIndex);
+                        promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter.index}`, emitter).then(() => {
+                            for (const sound of emitter._babylonSounds) {
+                                sound.attachToMesh(babylonMesh);
+                                if (emitter.innerAngle != undefined || emitter.outerAngle != undefined) {
+                                    sound.setLocalDirectionToMesh(Vector3.Forward());
+                                    sound.setDirectionalCone(
+                                        2 * Tools.ToDegrees(emitter.innerAngle == undefined ? Math.PI : emitter.innerAngle),
+                                        2 * Tools.ToDegrees(emitter.outerAngle == undefined ? Math.PI : emitter.outerAngle),
+                                        0);
+                                }
+                            }
+                        }));
+                    }
+
+                    assign(babylonMesh);
+                }).then(babylonMesh => {
+                    return Promise.all(promises).then(() => {
+                        return babylonMesh;
+                    });
+                });
+            });
+        }
+
+        /** @hidden */
+        public loadAnimationAsync(context: string, animation: ILoaderAnimation): Nullable<Promise<AnimationGroup>> {
+            return GLTFLoader.LoadExtensionAsync<ILoaderAnimationEvents, AnimationGroup>(context, animation, this.name, (extensionContext, extension) => {
+                return this._loader.loadAnimationAsync(context, animation).then(babylonAnimationGroup => {
+                    const promises = new Array<Promise<any>>();
+
+                    ArrayItem.Assign(extension.events);
+                    for (const event of extension.events) {
+                        promises.push(this._loadAnimationEventAsync(`${extensionContext}/events/${event.index}`, context, animation, event, babylonAnimationGroup));
+                    }
+
+                    return Promise.all(promises).then(() => {
+                        return babylonAnimationGroup;
+                    });
+                });
+            });
+        }
+
+        private _loadClipAsync(context: string, clip: ILoaderClip): Promise<string> {
             if (clip._objectURL) {
                 return clip._objectURL;
             }
 
             let promise: Promise<ArrayBufferView>;
             if (clip.uri) {
-                promise = this._loader._loadUriAsync(context, clip.uri);
+                promise = this._loader.loadUriAsync(context, clip.uri);
             }
             else {
-                const bufferView = GLTFLoader._GetProperty(`${context}/bufferView`, this._loader._gltf.bufferViews, clip.bufferView);
-                promise = this._loader._loadBufferViewAsync(`#/bufferViews/${bufferView._index}`, bufferView);
+                const bufferView = ArrayItem.Get(`${context}/bufferView`, this._loader.gltf.bufferViews, clip.bufferView);
+                promise = this._loader.loadBufferViewAsync(`#/bufferViews/${bufferView.index}`, bufferView);
             }
 
             clip._objectURL = promise.then(data => {
@@ -106,23 +209,22 @@ module BABYLON.GLTF2.Extensions {
             return clip._objectURL;
         }
 
-        private _loadEmitterAsync(context: string, emitter: _ILoaderEmitter): Promise<void> {
+        private _loadEmitterAsync(context: string, emitter: ILoaderEmitter): Promise<void> {
             emitter._babylonSounds = emitter._babylonSounds || [];
             if (!emitter._babylonData) {
-                const clipPromises = new Array<Promise<void>>();
-                const name = emitter.name || `emitter${emitter._index}`;
+                const clipPromises = new Array<Promise<any>>();
+                const name = emitter.name || `emitter${emitter.index}`;
                 const options = {
                     loop: false,
                     autoplay: false,
                     volume: emitter.volume == undefined ? 1 : emitter.volume,
                 };
 
-                _ArrayItem.Assign(this._clips);
                 for (let i = 0; i < emitter.clips.length; i++) {
-                    const clipContext = `#/extensions/${NAME}/clips`;
-                    const clip = GLTFLoader._GetProperty(clipContext, this._clips, emitter.clips[i].clip);
+                    const clipContext = `#/extensions/${this.name}/clips`;
+                    const clip = ArrayItem.Get(clipContext, this._clips, emitter.clips[i].clip);
                     clipPromises.push(this._loadClipAsync(`${clipContext}/${emitter.clips[i].clip}`, clip).then((objectURL: string) => {
-                        const sound = emitter._babylonSounds[i] = new Sound(name, objectURL, this._loader._babylonScene, null, options);
+                        const sound = emitter._babylonSounds[i] = new Sound(name, objectURL, this._loader.babylonScene, null, options);
                         sound.refDistance = emitter.refDistance || 1;
                         sound.maxDistance = emitter.maxDistance || 256;
                         sound.rolloffFactor = emitter.rolloffFactor || 1;
@@ -148,129 +250,53 @@ module BABYLON.GLTF2.Extensions {
             return emitter._babylonData.loaded;
         }
 
-        protected _loadSceneAsync(context: string, scene: _ILoaderScene): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<_IEmittersReference>(context, scene, (extensionContext, extension) => {
-                return this._loader._loadSceneAsync(context, scene).then(() => {
-
-                    const promises = new Array<Promise<void>>();
-                    _ArrayItem.Assign(this._emitters);
-                    for (const emitterIndex of extension.emitters) {
-                        const emitter = GLTFLoader._GetProperty(`${extensionContext}/emitters`, this._emitters, emitterIndex);
-                        if (emitter.refDistance != undefined || emitter.maxDistance != undefined || emitter.rolloffFactor != undefined ||
-                            emitter.distanceModel != undefined || emitter.innerAngle != undefined || emitter.outerAngle != undefined) {
-                            throw new Error(`${extensionContext}: Direction or Distance properties are not allowed on emitters attached to a scene`);
-                        }
-
-                        promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter._index}`, emitter));
-                    }
-
-                    return Promise.all(promises).then(() => {});
-                });
-            });
-        }
-
-        protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<_IEmittersReference>(context, node, (extensionContext, extension) => {
-                return this._loader._loadNodeAsync(extensionContext, node).then(() => {
-
-                    const promises = new Array<Promise<void>>();
-                    _ArrayItem.Assign(this._emitters);
-                    for (const emitterIndex of extension.emitters) {
-                        const emitter = GLTFLoader._GetProperty(`${extensionContext}/emitters`, this._emitters, emitterIndex);
-                        promises.push(this._loadEmitterAsync(`${extensionContext}/emitters/${emitter._index}`, emitter).then(() => {
-                            if (node._babylonMesh) {
-                                for (const sound of emitter._babylonSounds) {
-                                    sound.attachToMesh(node._babylonMesh);
-                                    if (emitter.innerAngle != undefined || emitter.outerAngle != undefined) {
-                                        sound.setLocalDirectionToMesh(new Vector3(0, 0, 1));
-                                        sound.setDirectionalCone(2 * Tools.ToDegrees(emitter.innerAngle == undefined ? Math.PI : emitter.innerAngle),
-                                                                 2 * Tools.ToDegrees(emitter.outerAngle == undefined ? Math.PI : emitter.outerAngle), 0);
-                                    }
-                                }
-                            }
-                        }));
-                    }
-
-                    return Promise.all(promises).then(() => {});
-                });
-            });
-        }
-
-        protected _loadAnimationAsync(context: string, animation: _ILoaderAnimation): Nullable<Promise<void>> { 
-            return this._loadExtensionAsync<_ILoaderAnimationEvents>(context, animation, (extensionContext, extension) => {
-                return this._loader._loadAnimationAsync(extensionContext, animation).then(() => {
-                    const promises = new Array<Promise<void>>();
-                    const babylonAnimationGroup = animation._babylonAnimationGroup!;
-
-                    _ArrayItem.Assign(extension.events);
-                    for (const event of extension.events) {
-                        promises.push(this._loadAnimationEventAsync(`${extensionContext}/events/${event._index}`, context, animation, event, babylonAnimationGroup));
-                    }
-
-                    return Promise.all(promises).then(() => {});
-                });
-            });
-        }
-
-        private _getEventAction(context:string, sound: WeightedSound, action: _AnimationEventAction, time: number, startOffset?: number): (currentFrame: number) => void {
-            if (action == _AnimationEventAction.play) {
-                return (currentFrame: number) => {            
-                    const frameOffset = (startOffset || 0) + (currentFrame - time);
-                    sound.play(frameOffset);
-                };
-            } else if (action == _AnimationEventAction.stop) {
-                return (currentFrame: number) => {
-                    sound.stop();
-                };
-            } else if (action == _AnimationEventAction.pause) {
-                return (currentFrame: number) => {   
-                    sound.pause();
-                };
-            } else {
-                throw new Error(`${context}: Unsupported action ${action}`);
+        private _getEventAction(context: string, sound: WeightedSound, action: AnimationEventAction, time: number, startOffset?: number): (currentFrame: number) => void {
+            switch (action) {
+                case AnimationEventAction.play: {
+                    return (currentFrame: number) => {
+                        const frameOffset = (startOffset || 0) + (currentFrame - time);
+                        sound.play(frameOffset);
+                    };
+                }
+                case AnimationEventAction.stop: {
+                    return (currentFrame: number) => {
+                        sound.stop();
+                    };
+                }
+                case AnimationEventAction.pause: {
+                    return (currentFrame: number) => {
+                        sound.pause();
+                    };
+                }
+                default: {
+                    throw new Error(`${context}: Unsupported action ${action}`);
+                }
             }
         }
 
-        private _loadAnimationEventAsync(context: string, animationContext: string, animation: _ILoaderAnimation, event: _ILoaderAnimationEvent, babylonAnimationGroup: AnimationGroup): Promise<void> {
+        private _loadAnimationEventAsync(context: string, animationContext: string, animation: ILoaderAnimation, event: ILoaderAnimationEvent, babylonAnimationGroup: AnimationGroup): Promise<void> {
             if (babylonAnimationGroup.targetedAnimations.length == 0) {
                 return Promise.resolve();
             }
             const babylonAnimation = babylonAnimationGroup.targetedAnimations[0];
             const emitterIndex = event.emitter;
-            const emitter = GLTFLoader._GetProperty(`#/extensions/${NAME}/emitters`, this._emitters, emitterIndex);
-            return this._loadEmitterAsync(context, emitter).then(()=> {
+            const emitter = ArrayItem.Get(`#/extensions/${this.name}/emitters`, this._emitters, emitterIndex);
+            return this._loadEmitterAsync(context, emitter).then(() => {
                 const sound = emitter._babylonData!.sound;
                 if (sound) {
                     var babylonAnimationEvent = new AnimationEvent(event.time, this._getEventAction(context, sound, event.action, event.time, event.startOffset));
                     babylonAnimation.animation.addEvent(babylonAnimationEvent);
                     // Make sure all started audio stops when this animation is terminated.
-                    babylonAnimationGroup.onAnimationGroupEndObservable.add(() => { 
+                    babylonAnimationGroup.onAnimationGroupEndObservable.add(() => {
                         sound.stop();
                     });
-                    babylonAnimationGroup.onAnimationGroupPauseObservable.add(() => { 
+                    babylonAnimationGroup.onAnimationGroupPauseObservable.add(() => {
                         sound.pause();
                     });
                 }
             });
         }
-
-        private get _extension(): _IMSFTAudioEmitter {
-            const extensions = this._loader._gltf.extensions;
-            if (!extensions || !extensions[this.name]) {
-                throw new Error(`#/extensions: '${this.name}' not found`);
-            }
-
-            return extensions[this.name] as _IMSFTAudioEmitter;
-        }
-
-        private get _clips(): Array<_ILoaderClip> {
-            return this._extension.clips;
-        }
-
-        private get _emitters(): Array<_ILoaderEmitter> {
-            return this._extension.emitters;
-        }
     }
 
-    GLTFLoader._Register(NAME, loader => new MSFT_audio_emitter(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new MSFT_audio_emitter(loader));
 }

+ 61 - 35
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -10,9 +10,13 @@ module BABYLON.GLTF2.Extensions {
     /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod)
      */
-    export class MSFT_lod extends GLTFLoaderExtension {
+    export class MSFT_lod implements IGLTFLoaderExtension {
+        /** The name of this extension. */
         public readonly name = NAME;
 
+        /** Defines whether this extension is enabled. */
+        public enabled = true;
+
         /**
          * Maximum number of LODs to load, starting from the lowest LOD.
          */
@@ -32,16 +36,24 @@ module BABYLON.GLTF2.Extensions {
          */
         public onMaterialLODsLoadedObservable = new Observable<number>();
 
+        private _loader: GLTFLoader;
+
         private _nodeIndexLOD: Nullable<number> = null;
         private _nodeSignalLODs = new Array<Deferred<void>>();
-        private _nodePromiseLODs = new Array<Array<Promise<void>>>();
+        private _nodePromiseLODs = new Array<Array<Promise<any>>>();
 
         private _materialIndexLOD: Nullable<number> = null;
         private _materialSignalLODs = new Array<Deferred<void>>();
-        private _materialPromiseLODs = new Array<Array<Promise<void>>>();
+        private _materialPromiseLODs = new Array<Array<Promise<any>>>();
+
+        /** @hidden */
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
 
+        /** @hidden */
         public dispose() {
-            super.dispose();
+            delete this._loader;
 
             this._nodeIndexLOD = null;
             this._nodeSignalLODs.length = 0;
@@ -55,18 +67,19 @@ module BABYLON.GLTF2.Extensions {
             this.onNodeLODsLoadedObservable.clear();
         }
 
-        protected _onReady(): void {
+        /** @hidden */
+        public onReady(): void {
             for (let indexLOD = 0; indexLOD < this._nodePromiseLODs.length; indexLOD++) {
                 const promise = Promise.all(this._nodePromiseLODs[indexLOD]).then(() => {
                     if (indexLOD !== 0) {
-                        this._loader._parent._endPerformanceCounter(`Node LOD ${indexLOD}`);
+                        this._loader.endPerformanceCounter(`Node LOD ${indexLOD}`);
                     }
 
-                    this._loader._parent._log(`Loaded node LOD ${indexLOD}`);
+                    this._loader.log(`Loaded node LOD ${indexLOD}`);
                     this.onNodeLODsLoadedObservable.notifyObservers(indexLOD);
 
                     if (indexLOD !== this._nodePromiseLODs.length - 1) {
-                        this._loader._parent._startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
+                        this._loader.startPerformanceCounter(`Node LOD ${indexLOD + 1}`);
                         if (this._nodeSignalLODs[indexLOD]) {
                             this._nodeSignalLODs[indexLOD].resolve();
                         }
@@ -79,14 +92,14 @@ module BABYLON.GLTF2.Extensions {
             for (let indexLOD = 0; indexLOD < this._materialPromiseLODs.length; indexLOD++) {
                 const promise = Promise.all(this._materialPromiseLODs[indexLOD]).then(() => {
                     if (indexLOD !== 0) {
-                        this._loader._parent._endPerformanceCounter(`Material LOD ${indexLOD}`);
+                        this._loader.endPerformanceCounter(`Material LOD ${indexLOD}`);
                     }
 
-                    this._loader._parent._log(`Loaded material LOD ${indexLOD}`);
+                    this._loader.log(`Loaded material LOD ${indexLOD}`);
                     this.onMaterialLODsLoadedObservable.notifyObservers(indexLOD);
 
                     if (indexLOD !== this._materialPromiseLODs.length - 1) {
-                        this._loader._parent._startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
+                        this._loader.startPerformanceCounter(`Material LOD ${indexLOD + 1}`);
                         if (this._materialSignalLODs[indexLOD]) {
                             this._materialSignalLODs[indexLOD].resolve();
                         }
@@ -97,12 +110,13 @@ module BABYLON.GLTF2.Extensions {
             }
         }
 
-        protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable<Promise<void>> {
-            return this._loadExtensionAsync<IMSFTLOD>(context, node, (extensionContext, extension) => {
-                let firstPromise: Promise<void>;
+        /** @hidden */
+        public loadNodeAsync(context: string, node: ILoaderNode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
+            return GLTFLoader.LoadExtensionAsync<IMSFTLOD, Mesh>(context, node, this.name, (extensionContext, extension) => {
+                let firstPromise: Promise<Mesh>;
 
-                const nodeLODs = this._getLODs(extensionContext, node, this._loader._gltf.nodes, extension.ids);
-                this._loader._parent._logOpen(`${extensionContext}`);
+                const nodeLODs = this._getLODs(extensionContext, node, this._loader.gltf.nodes, extension.ids);
+                this._loader.logOpen(`${extensionContext}`);
 
                 for (let indexLOD = 0; indexLOD < nodeLODs.length; indexLOD++) {
                     const nodeLOD = nodeLODs[indexLOD];
@@ -112,8 +126,9 @@ module BABYLON.GLTF2.Extensions {
                         this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
                     }
 
-                    const promise = this._loader._loadNodeAsync(`#/nodes/${nodeLOD._index}`, nodeLOD).then(() => {
+                    const promise = this._loader.loadNodeAsync(`#/nodes/${nodeLOD.index}`, nodeLOD).then(babylonMesh => {
                         if (indexLOD !== 0) {
+                            // TODO: should not rely on _babylonMesh
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
                             if (previousNodeLOD._babylonMesh) {
                                 previousNodeLOD._babylonMesh.dispose();
@@ -121,6 +136,8 @@ module BABYLON.GLTF2.Extensions {
                                 this._disposeUnusedMaterials();
                             }
                         }
+
+                        return babylonMesh;
                     });
 
                     if (indexLOD === 0) {
@@ -134,22 +151,23 @@ module BABYLON.GLTF2.Extensions {
                     this._nodePromiseLODs[indexLOD].push(promise);
                 }
 
-                this._loader._parent._logClose();
+                this._loader.logClose();
                 return firstPromise!;
             });
         }
 
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
+        /** @hidden */
+        public _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>> {
             // Don't load material LODs if already loading a node LOD.
             if (this._nodeIndexLOD) {
                 return null;
             }
 
-            return this._loadExtensionAsync<IMSFTLOD>(context, material, (extensionContext, extension) => {
-                let firstPromise: Promise<void>;
+            return GLTFLoader.LoadExtensionAsync<IMSFTLOD, Material>(context, material, this.name, (extensionContext, extension) => {
+                let firstPromise: Promise<Material>;
 
-                const materialLODs = this._getLODs(extensionContext, material, this._loader._gltf.materials, extension.ids);
-                this._loader._parent._logOpen(`${extensionContext}`);
+                const materialLODs = this._getLODs(extensionContext, material, this._loader.gltf.materials, extension.ids);
+                this._loader.logOpen(`${extensionContext}`);
 
                 for (let indexLOD = 0; indexLOD < materialLODs.length; indexLOD++) {
                     const materialLOD = materialLODs[indexLOD];
@@ -158,17 +176,23 @@ module BABYLON.GLTF2.Extensions {
                         this._materialIndexLOD = indexLOD;
                     }
 
-                    const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD._index}`, materialLOD, mesh, babylonMesh, babylonDrawMode, indexLOD === 0 ? assign : () => {}).then(() => {
+                    const promise = this._loader._loadMaterialAsync(`#/materials/${materialLOD.index}`, materialLOD, babylonMesh, babylonDrawMode, babylonMaterial => {
+                        if (indexLOD === 0) {
+                            assign(babylonMaterial);
+                        }
+                    }).then(babylonMaterial => {
                         if (indexLOD !== 0) {
-                            const babylonDataLOD = materialLOD._babylonData!;
-                            assign(babylonDataLOD[babylonDrawMode].material);
+                            assign(babylonMaterial);
 
+                            // TODO: should not rely on _babylonData
                             const previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData!;
                             if (previousBabylonDataLOD[babylonDrawMode]) {
                                 previousBabylonDataLOD[babylonDrawMode].material.dispose();
                                 delete previousBabylonDataLOD[babylonDrawMode];
                             }
                         }
+
+                        return babylonMaterial;
                     });
 
                     if (indexLOD === 0) {
@@ -182,27 +206,28 @@ module BABYLON.GLTF2.Extensions {
                     this._materialPromiseLODs[indexLOD].push(promise);
                 }
 
-                this._loader._parent._logClose();
+                this._loader.logClose();
                 return firstPromise!;
             });
         }
 
-        protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
+        /** @hidden */
+        public _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
             // Defer the loading of uris if loading a material or node LOD.
             if (this._materialIndexLOD !== null) {
-                this._loader._parent._log(`deferred`);
+                this._loader.log(`deferred`);
                 const previousIndexLOD = this._materialIndexLOD - 1;
                 this._materialSignalLODs[previousIndexLOD] = this._materialSignalLODs[previousIndexLOD] || new Deferred<void>();
                 return this._materialSignalLODs[previousIndexLOD].promise.then(() => {
-                    return this._loader._loadUriAsync(context, uri);
+                    return this._loader.loadUriAsync(context, uri);
                 });
             }
             else if (this._nodeIndexLOD !== null) {
-                this._loader._parent._log(`deferred`);
+                this._loader.log(`deferred`);
                 const previousIndexLOD = this._nodeIndexLOD - 1;
                 this._nodeSignalLODs[previousIndexLOD] = this._nodeSignalLODs[previousIndexLOD] || new Deferred<void>();
                 return this._nodeSignalLODs[this._nodeIndexLOD - 1].promise.then(() => {
-                    return this._loader._loadUriAsync(context, uri);
+                    return this._loader.loadUriAsync(context, uri);
                 });
             }
 
@@ -220,7 +245,7 @@ module BABYLON.GLTF2.Extensions {
             const properties = new Array<T>();
 
             for (let i = ids.length - 1; i >= 0; i--) {
-                properties.push(GLTFLoader._GetProperty(`${context}/ids/${ids[i]}`, array, ids[i]));
+                properties.push(ArrayItem.Get(`${context}/ids/${ids[i]}`, array, ids[i]));
                 if (properties.length === this.maxLODsToLoad) {
                     return properties;
                 }
@@ -231,7 +256,8 @@ module BABYLON.GLTF2.Extensions {
         }
 
         private _disposeUnusedMaterials(): void {
-            const materials = this._loader._gltf.materials;
+            // TODO: should not rely on _babylonData
+            const materials = this._loader.gltf.materials;
             if (materials) {
                 for (const material of materials) {
                     if (material._babylonData) {
@@ -248,5 +274,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new MSFT_lod(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new MSFT_lod(loader));
 }

+ 29 - 14
loaders/src/glTF/2.0/Extensions/MSFT_minecraftMesh.ts

@@ -4,23 +4,38 @@ module BABYLON.GLTF2.Extensions {
     const NAME = "MSFT_minecraftMesh";
 
     /** @hidden */
-    export class MSFT_minecraftMesh extends GLTFLoaderExtension {
+    export class MSFT_minecraftMesh implements IGLTFLoaderExtension {
         public readonly name = NAME;
+        public enabled = true;
 
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
-            return this._loadExtrasValueAsync<boolean>(context, mesh, (extensionContext, value) => {
-                if (value) {
-                    return this._loader._loadMaterialAsync(context, material, mesh, babylonMesh, babylonDrawMode, (babylonMaterial: PBRMaterial) => {
-                        if (babylonMaterial.needAlphaBlending()) {
-                            babylonMaterial.forceDepthWrite = true;
-                            babylonMaterial.separateCullingPass = true;
-                        }
+        private _loader: GLTFLoader;
 
-                        babylonMaterial.backFaceCulling = babylonMaterial.forceDepthWrite;
-                        babylonMaterial.twoSidedLighting = true;
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        public dispose() {
+            delete this._loader;
+        }
+
+        public loadMaterialPropertiesAsync(context: string, material: ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+            return GLTFLoader.LoadExtraAsync<boolean>(context, material, this.name, (extraContext, extra) => {
+                if (extra) {
+                    if (!(babylonMaterial instanceof PBRMaterial)) {
+                        throw new Error(`${extraContext}: Material type not supported`);
+                    }
+
+                    const promise = this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial);
+
+                    if (babylonMaterial.needAlphaBlending()) {
+                        babylonMaterial.forceDepthWrite = true;
+                        babylonMaterial.separateCullingPass = true;
+                    }
+
+                    babylonMaterial.backFaceCulling = babylonMaterial.forceDepthWrite;
+                    babylonMaterial.twoSidedLighting = true;
 
-                        assign(babylonMaterial);
-                    });
+                    return promise;
                 }
 
                 return null;
@@ -28,5 +43,5 @@ module BABYLON.GLTF2.Extensions {
         }
     }
 
-    GLTFLoader._Register(NAME, loader => new MSFT_minecraftMesh(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new MSFT_minecraftMesh(loader));
 }

+ 30 - 17
loaders/src/glTF/2.0/Extensions/MSFT_sRGBFactors.ts

@@ -4,31 +4,44 @@ module BABYLON.GLTF2.Extensions {
     const NAME = "MSFT_sRGBFactors";
 
     /** @hidden */
-    export class MSFT_sRGBFactors extends GLTFLoaderExtension {
+    export class MSFT_sRGBFactors implements IGLTFLoaderExtension {
         public readonly name = NAME;
+        public enabled = true;
+
+        private _loader: GLTFLoader;
+
+        constructor(loader: GLTFLoader) {
+            this._loader = loader;
+        }
+
+        public dispose() {
+            delete this._loader;
+        }
+
+        public loadMaterialPropertiesAsync(context: string, material: ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
+            return GLTFLoader.LoadExtraAsync<boolean>(context, material, this.name, (extraContext, extra) => {
+                if (extra) {
+                    if (!(babylonMaterial instanceof PBRMaterial)) {
+                        throw new Error(`${extraContext}: Material type not supported`);
+                    }
+        
+                    const promise = this._loader.loadMaterialPropertiesAsync(context, material, babylonMaterial);
+
+                    if (!babylonMaterial.albedoTexture) {
+                        babylonMaterial.albedoColor.toLinearSpaceToRef(babylonMaterial.albedoColor);
+                    }
+
+                    if (!babylonMaterial.reflectivityTexture) {
+                        babylonMaterial.reflectivityColor.toLinearSpaceToRef(babylonMaterial.reflectivityColor);
+                    }
 
-        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
-            return this._loadExtrasValueAsync<boolean>(context, material, (extensionContext, value) => {
-                if (value) {
-                    const promise = this._loader._loadMaterialPropertiesAsync(context, material, babylonMaterial);
-                    this._convertColorsToLinear(babylonMaterial as PBRMaterial);
                     return promise;
                 }
 
                 return null;
             });
         }
-
-        private _convertColorsToLinear(babylonMaterial: PBRMaterial): void {
-            if (!babylonMaterial.albedoTexture) {
-                babylonMaterial.albedoColor.toLinearSpaceToRef(babylonMaterial.albedoColor);
-            }
-
-            if (!babylonMaterial.reflectivityTexture) {
-                babylonMaterial.reflectivityColor.toLinearSpaceToRef(babylonMaterial.reflectivityColor);
-            }
-        }
     }
 
-    GLTFLoader._Register(NAME, loader => new MSFT_sRGBFactors(loader));
+    GLTFLoader.RegisterExtension(NAME, loader => new MSFT_sRGBFactors(loader));
 }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 644 - 312
loaders/src/glTF/2.0/babylon.glTFLoader.ts


+ 63 - 203
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -2,249 +2,109 @@
 
 module BABYLON.GLTF2 {
     /**
-     * Abstract class that can be implemented to extend existing glTF loader behavior.
+     * Interface for a glTF loader extension.
      */
-    export abstract class GLTFLoaderExtension implements IGLTFLoaderExtension, IDisposable {
+    export interface IGLTFLoaderExtension extends BABYLON.IGLTFLoaderExtension, IDisposable {
         /**
-         * Gets or sets a boolean indicating if the extension is enabled
+         * Called after the loader state changes to LOADING.
          */
-        public enabled = true;
+        onLoading?(): void;
 
         /**
-         * Gets or sets extension name
+         * Called after the loader state changes to READY.
          */
-        public abstract readonly name: string;
-
-        protected _loader: GLTFLoader;
-
-        /**
-         * Creates new GLTFLoaderExtension
-         * @param loader defines the GLTFLoader to use
-         */
-        constructor(loader: GLTFLoader) {
-            this._loader = loader;
-        }
-
-        /**
-         * Release all resources
-         */
-        public dispose(): void {
-            delete this._loader;
-        }
-
-        // #region Overridable Methods
-
-        /**
-         * Override this method to do work after the state changes to LOADING.
-         */
-        protected _onLoading(): void {}
-
-        /**
-         * Override this method to do work after the state changes to READY.
-         */
-        protected _onReady(): void {}
-
-        /**
-         * Override this method to modify the default behavior for loading scenes.
-         * @hidden
-         */
-        protected _loadSceneAsync(context: string, node: _ILoaderScene): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading nodes.
-         * @hidden
-         */
-        protected _loadNodeAsync(context: string, node: _ILoaderNode): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading mesh primitive vertex data.
-         * @hidden
-         */
-        protected _loadVertexDataAsync(context: string, primitive: _ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading materials.
-         * @hidden
-         */
-        protected _loadMaterialAsync(context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading material properties.
-         * @hidden
-         */
-        protected _loadMaterialPropertiesAsync(context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading texture infos.
-         * @hidden
-         */
-        protected _loadTextureInfoAsync(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading textures.
-         * @hidden
-         */
-        protected _loadTextureAsync(context: string, texture: _ILoaderTexture, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> { return null; }
-
-        /**
-         * Override this method to modify the default behavior for loading uris.
-         * @hidden
-         */
-        protected _loadUriAsync(context: string, uri: string): Nullable<Promise<ArrayBufferView>> { return null; }
-
-        /** Override this method to modify the default behavior for loading animations. */
-        protected _loadAnimationAsync(context: string, animation: _ILoaderAnimation): Nullable<Promise<void>> { return null; }
-
-        // #endregion
-
-        /**
-         * Helper method called by a loader extension to load an glTF extension.
-         * @hidden
-         */
-        protected _loadExtensionAsync<TProperty, TResult = void>(context: string, property: IProperty, actionAsync: (extensionContext: string, extension: TProperty) => Nullable<Promise<TResult>>): Nullable<Promise<TResult>> {
-            if (!property.extensions) {
-                return null;
-            }
-
-            const extensions = property.extensions;
-
-            const extension = extensions[this.name] as TProperty;
-            if (!extension) {
-                return null;
-            }
-
-            // Clear out the extension before executing the action to avoid infinite recursion.
-            delete extensions[this.name];
-
-            try {
-                return actionAsync(`${context}/extensions/${this.name}`, extension);
-            }
-            finally {
-                // Restore the extension after executing the action.
-                extensions[this.name] = extension;
-            }
-        }
-
-        /**
-         * Helper method called by the loader to allow extensions to override loading scenes.
-         * @hidden
-         */
-        protected _loadExtrasValueAsync<TProperty, TResult = void>(context: string, property: IProperty, actionAsync: (extensionContext: string, value: TProperty) => Nullable<Promise<TResult>>): Nullable<Promise<TResult>> {
-            if (!property.extras) {
-                return null;
-            }
-
-            const extras = property.extras;
-
-            const value = extras[this.name] as TProperty;
-            if (value === undefined) {
-                return null;
-            }
-
-            // Clear out the extras value before executing the action to avoid infinite recursion.
-            delete extras[this.name];
-
-            try {
-                return actionAsync(`${context}/extras/${this.name}`, value);
-            }
-            finally {
-                // Restore the extras value after executing the action.
-                extras[this.name] = value;
-            }
-        }
+        onReady?(): void;
 
         /**
-         * Helper method called by the loader after the state changes to LOADING.
-         * @hidden
+         * Define this method to modify the default behavior when loading scenes.
+         * @param context The context when loading the asset
+         * @param scene The glTF scene property
+         * @returns A promise that resolves when the load is complete or null if not handled
          */
-        public static _OnLoading(loader: GLTFLoader): void {
-            loader._forEachExtensions(extension => extension._onLoading());
-        }
+        loadSceneAsync?(context: string, scene: ILoaderScene): Nullable<Promise<void>>;
 
         /**
-         * Helper method called by the loader after the state changes to READY.
-         * @hidden
+         * Define this method to modify the default behavior when loading nodes.
+         * @param context The context when loading the asset
+         * @param node The glTF node property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
          */
-        public static _OnReady(loader: GLTFLoader): void {
-            loader._forEachExtensions(extension => extension._onReady());
-        }
+        loadNodeAsync?(context: string, node: ILoaderNode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading scenes.
-         * @hidden
+         * Define this method to modify the default behavior when loading cameras.
+         * @param context The context when loading the asset
+         * @param camera The glTF camera property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded Babylon camera when the load is complete or null if not handled
          */
-        public static _LoadSceneAsync(loader: GLTFLoader, context: string, scene: _ILoaderScene): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadSceneAsync(context, scene));
-        }
+        loadCameraAsync?(context: string, camera: ILoaderCamera, assign: (babylonCamera: Camera) => void): Nullable<Promise<Camera>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading nodes.
-         * @hidden
+         * @hidden Define this method to modify the default behavior when loading vertex data for mesh primitives.
+         * @param context The context when loading the asset
+         * @param primitive The glTF mesh primitive property
+         * @returns A promise that resolves with the loaded geometry when the load is complete or null if not handled
          */
-        public static _LoadNodeAsync(loader: GLTFLoader, context: string, node: _ILoaderNode): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadNodeAsync(context, node));
-        }
+        _loadVertexDataAsync?(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading mesh primitive vertex data.
-         * @hidden
+         * @hidden Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
+         * @param context The context when loading the asset
+         * @param material The glTF material property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded Babylon material when the load is complete or null if not handled
          */
-        public static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: _ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<Geometry>> {
-            return loader._applyExtensions(extension => extension._loadVertexDataAsync(context, primitive, babylonMesh));
-        }
+        _loadMaterialAsync?(context: string, material: ILoaderMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading materials.
-         * @hidden
+         * Define this method to modify the default behavior when creating materials.
+         * @param context The context when loading the asset
+         * @param material The glTF material property
+         * @param babylonDrawMode The draw mode for the Babylon material
+         * @returns The Babylon material or null if not handled
          */
-        public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, mesh: _ILoaderMesh, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, mesh, babylonMesh, babylonDrawMode, assign));
-        }
+        createMaterial?(context: string, material: ILoaderMaterial, babylonDrawMode: number): Nullable<Material>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading material properties.
-         * @hidden
+         * Define this method to modify the default behavior when loading material properties.
+         * @param context The context when loading the asset
+         * @param material The glTF material property
+         * @param babylonMaterial The Babylon material
+         * @returns A promise that resolves when the load is complete or null if not handled
          */
-        public static _LoadMaterialPropertiesAsync(loader: GLTFLoader, context: string, material: _ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadMaterialPropertiesAsync(context, material, babylonMaterial));
-        }
+        loadMaterialPropertiesAsync?(context: string, material: ILoaderMaterial, babylonMaterial: Material): Nullable<Promise<void>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading texture infos.
-         * @hidden
+         * Define this method to modify the default behavior when loading texture infos.
+         * @param context The context when loading the asset
+         * @param textureInfo The glTF texture info property
+         * @param assign A function called synchronously after parsing the glTF properties
+         * @returns A promise that resolves with the loaded Babylon texture when the load is complete or null if not handled
          */
-        public static _LoadTextureInfoAsync(loader: GLTFLoader, context: string, textureInfo: ITextureInfo, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadTextureInfoAsync(context, textureInfo, assign));
-        }
+        loadTextureInfoAsync?(context: string, textureInfo: ITextureInfo, assign: (babylonTexture: BaseTexture) => void): Nullable<Promise<BaseTexture>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading textures.
-         * @hidden
+         * Define this method to modify the default behavior when loading animations.
+         * @param context The context when loading the asset
+         * @param animation The glTF animation property
+         * @returns A promise that resolves with the loaded Babylon animation group when the load is complete or null if not handled
          */
-        public static _LoadTextureAsync(loader: GLTFLoader, context: string, texture: _ILoaderTexture, assign: (babylonTexture: Texture) => void): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadTextureAsync(context, texture, assign));
-        }
+        loadAnimationAsync?(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
 
         /**
-         * Helper method called by the loader to allow extensions to override loading uris.
-         * @hidden
-         */
-        public static _LoadUriAsync(loader: GLTFLoader, context: string, uri: string): Nullable<Promise<ArrayBufferView>> {
-            return loader._applyExtensions(extension => extension._loadUriAsync(context, uri));
-        }
-
-        /** 
-         * Helper method called by the loader to allow extensions to override loading animations.
-         * @hidden
+         * Define this method to modify the default behavior when loading uris.
+         * @param context The context when loading the asset
+         * @param uri The uri to load
+         * @returns A promise that resolves with the loaded data when the load is complete or null if not handled
          */
-        public static _LoadAnimationAsync(loader: GLTFLoader, context: string, animation: _ILoaderAnimation): Nullable<Promise<void>> {
-            return loader._applyExtensions(extension => extension._loadAnimationAsync(context, animation));
-        }
+        _loadUriAsync?(context: string, uri: string): Nullable<Promise<ArrayBufferView>>;
     }
 }
 
 /**
- * Defines the module of the glTF 2.0 loader extensions.
+ * Defines the module for the built-in glTF 2.0 loader extensions.
  */
 module BABYLON.GLTF2.Extensions {
 }

+ 157 - 61
loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts

@@ -2,19 +2,31 @@
 /// <reference path="../../../../dist/preview release/glTF2Interface/babylon.glTF2Interface.d.ts"/>
 
 module BABYLON.GLTF2 {
-    /** @hidden */
-    export interface _IArrayItem {
-        _index: number;
-    }
-
-    /** @hidden */
-    export interface _ILoaderAccessor extends IAccessor, _IArrayItem {
+    /**
+     * Loader interface with an index field.
+     */
+    export interface IArrayItem {
+        /**
+         * The index of this item in the array.
+         */
+        index: number;
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderAccessor extends IAccessor, IArrayItem {
+        /** @hidden */
         _data?: Promise<ArrayBufferView>;
+
+        /** @hidden */
         _babylonVertexBuffer?: Promise<VertexBuffer>;
     }
 
-    /** @hidden */
-    export interface _ILoaderAnimationChannel extends IAnimationChannel, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderAnimationChannel extends IAnimationChannel, IArrayItem {
     }
 
     /** @hidden */
@@ -24,65 +36,129 @@ module BABYLON.GLTF2 {
         output: Float32Array;
     }
 
-    /** @hidden */
-    export interface _ILoaderAnimationSampler extends IAnimationSampler, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderAnimationSampler extends IAnimationSampler, IArrayItem {
+        /** @hidden */
         _data?: Promise<_ILoaderAnimationSamplerData>;
     }
 
-    /** @hidden */
-    export interface _ILoaderAnimation extends IAnimation, _IArrayItem {
-        channels: _ILoaderAnimationChannel[];
-        samplers: _ILoaderAnimationSampler[];
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderAnimation extends IAnimation, IArrayItem {
+        channels: ILoaderAnimationChannel[];
+        samplers: ILoaderAnimationSampler[];
 
+        /** @hidden */
         _babylonAnimationGroup?: AnimationGroup;
     }
 
-    /** @hidden */
-    export interface _ILoaderBuffer extends IBuffer, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderBuffer extends IBuffer, IArrayItem {
+        /** @hidden */
         _data?: Promise<ArrayBufferView>;
     }
 
-    /** @hidden */
-    export interface _ILoaderBufferView extends IBufferView, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderBufferView extends IBufferView, IArrayItem {
+        /** @hidden */
         _data?: Promise<ArrayBufferView>;
+
+        /** @hidden */
         _babylonBuffer?: Promise<Buffer>;
     }
 
-    /** @hidden */
-    export interface _ILoaderCamera extends ICamera, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderCamera extends ICamera, IArrayItem {
     }
 
-    /** @hidden */
-    export interface _ILoaderImage extends IImage, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderImage extends IImage, IArrayItem {
+        /** @hidden */
         _data?: Promise<ArrayBufferView>;
     }
 
-    /** @hidden */
-    export interface _ILoaderMaterial extends IMaterial, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMaterialNormalTextureInfo extends IMaterialNormalTextureInfo, ILoaderTextureInfo {
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMaterialOcclusionTextureInfo extends IMaterialOcclusionTextureInfo, ILoaderTextureInfo {
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMaterialPbrMetallicRoughness extends IMaterialPbrMetallicRoughness {
+        baseColorTexture?: ILoaderTextureInfo;
+        metallicRoughnessTexture?: ILoaderTextureInfo;
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMaterial extends IMaterial, IArrayItem {
+        pbrMetallicRoughness?: ILoaderMaterialPbrMetallicRoughness;
+        normalTexture?: ILoaderMaterialNormalTextureInfo;
+        occlusionTexture?: ILoaderMaterialOcclusionTextureInfo;
+        emissiveTexture?: ILoaderTextureInfo;
+
+        /** @hidden */
         _babylonData?: {
             [drawMode: number]: {
                 material: Material;
                 meshes: AbstractMesh[];
-                loaded: Promise<void>;
+                promise: Promise<void>;
             }
         };
     }
 
-    /** @hidden */
-    export interface _ILoaderMesh extends IMesh, _IArrayItem {
-        primitives: _ILoaderMeshPrimitive[];
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMesh extends IMesh, IArrayItem {
+        primitives: ILoaderMeshPrimitive[];
     }
 
-    /** @hidden */
-    export interface _ILoaderMeshPrimitive extends IMeshPrimitive, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderMeshPrimitive extends IMeshPrimitive, IArrayItem {
     }
 
-    /** @hidden */
-    export interface _ILoaderNode extends INode, _IArrayItem {
-        _parent?: _ILoaderNode;
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderNode extends INode, IArrayItem {
+        /**
+         * The parent glTF node.
+         */
+        parent?: ILoaderNode;
+
+        /** @hidden */
         _babylonMesh?: Mesh;
+
+        /** @hidden */
         _primitiveBabylonMeshes?: Mesh[];
+
+        /** @hidden */
         _babylonBones?: Bone[];
+
+        /** @hidden */
         _numMorphTargets?: number;
     }
 
@@ -94,39 +170,59 @@ module BABYLON.GLTF2 {
         wrapV: number;
     }
 
-    /** @hidden */
-    export interface _ILoaderSampler extends ISampler, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderSampler extends ISampler, IArrayItem {
+        /** @hidden */
         _data?: _ILoaderSamplerData;
     }
 
-    /** @hidden */
-    export interface _ILoaderScene extends IScene, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderScene extends IScene, IArrayItem {
     }
 
-    /** @hidden */
-    export interface _ILoaderSkin extends ISkin, _IArrayItem {
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderSkin extends ISkin, IArrayItem {
+        /** @hidden */
         _babylonSkeleton?: Skeleton;
-        _loaded?: Promise<void>;
-    }
 
-    /** @hidden */
-    export interface _ILoaderTexture extends ITexture, _IArrayItem {
-    }
-
-    /** @hidden */
-    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[];
+        /** @hidden */
+        _promise?: Promise<void>;
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderTexture extends ITexture, IArrayItem {
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    export interface ILoaderTextureInfo extends ITextureInfo {
+    }
+
+    /**
+     * Loader interface with additional members.
+     */
+    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[];
     }
 }

+ 8 - 8
serializers/src/glTF/2.0/babylon.glTFMaterialExporter.ts

@@ -32,11 +32,11 @@ module BABYLON.GLTF2 {
         /** 
          * Represents the metallness of the material
         */
-        metallic: number;
+        metallic: Nullable<number>;
         /** 
          * Represents the roughness of the material
         */
-        roughness: number;
+        roughness: Nullable<number>;
         /** 
          * The metallic roughness texture as a base64 string
         */
@@ -679,8 +679,8 @@ module BABYLON.GLTF2 {
                         maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
                         maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
                         maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
-                        maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
-                        maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
+                        maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic!);
+                        maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness!);
 
                         baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
                         baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
@@ -688,8 +688,8 @@ module BABYLON.GLTF2 {
                         baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] * 255 : 255;
 
                         metallicRoughnessBuffer[offset] = 0;
-                        metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
-                        metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
+                        metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness! * 255;
+                        metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic! * 255;
                         metallicRoughnessBuffer[offset + 3] = 255;
                     }
                 }
@@ -722,8 +722,8 @@ module BABYLON.GLTF2 {
                             writeOutBaseColorTexture = true;
                         }
 
-                        metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.roughness : 1;
-                        metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.metallic : 1;
+                        metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness! > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.roughness! : 1;
+                        metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic! > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.metallic! : 1;
 
                         const metallicRoughnessPixel = Color3.FromInts(255, metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
 

+ 2 - 2
src/Materials/PBR/babylon.pbrBaseMaterial.ts

@@ -266,13 +266,13 @@
          * Specifies the metallic scalar of the metallic/roughness workflow.
          * Can also be used to scale the metalness values of the metallic texture.
          */
-        protected _metallic: number;
+        protected _metallic: Nullable<number>;
 
         /**
          * Specifies the roughness scalar of the metallic/roughness workflow.
          * Can also be used to scale the roughness values of the metallic texture.
          */
-        protected _roughness: number;
+        protected _roughness: Nullable<number>;
 
         /**
          * Used to enable roughness/glossiness fetch from a separate chanel depending on the current mode.

+ 2 - 2
src/Materials/PBR/babylon.pbrMaterial.ts

@@ -129,7 +129,7 @@
          */
         @serialize()
         @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-        public metallic: number;
+        public metallic: Nullable<number>;
 
         /**
          * Specifies the roughness scalar of the metallic/roughness workflow.
@@ -137,7 +137,7 @@
          */
         @serialize()
         @expandToProperty("_markAllSubMeshesAsTexturesDirty")
-        public roughness: number;
+        public roughness: Nullable<number>;
 
         /**
          * Used to enable roughness/glossiness fetch from a separate chanel depending on the current mode.