Browse Source

Adding fragment shader to prevent premultiplication of alpha when exporting to glTF; Use the babylon default material instead of omitting and using the glTF default material; Refactored to use promises in more functions to support the async shader call

Kacey Coley 7 years ago
parent
commit
b54f2e27ae

+ 3 - 0
Tools/Gulp/config.json

@@ -1601,6 +1601,9 @@
                     "../../serializers/src/glTF/2.0/babylon.glTFAnimation.ts",
                     "../../serializers/src/glTF/2.0/babylon.glTFUtilities.ts"
                 ],
+                "shaderFiles": [
+                    "../../serializers/src/glTF/2.0/shaders/setAlphaToOne.fragment.fx"
+                ],
                 "output": "babylon.glTF2Serializer.js"
             }
         ],

+ 189 - 195
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -110,6 +110,9 @@ module BABYLON.GLTF2 {
          */
         private animationSampleRate: number;
 
+        /**
+         * Callback which specifies if a transform node should be exported or not
+         */
         private shouldExportTransformNode: ((babylonTransformNode: TransformNode) => boolean);
 
         /**
@@ -591,36 +594,43 @@ module BABYLON.GLTF2 {
          * @param glTFPrefix Text to use when prefixing a glTF file
          * @returns GLTFData with glTF file data
          */
-        public _generateGLTF(glTFPrefix: string): GLTFData {
-            const binaryBuffer = this.generateBinary();
-            const jsonText = this.generateJSON(false, glTFPrefix, true);
-            const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
+        public _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData> {
+            return new Promise((resolve, reject) => {
+                this.generateBinaryAsync().then(binaryBuffer => {
+                    const jsonText = this.generateJSON(false, glTFPrefix, true);
+                    const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
 
-            const glTFFileName = glTFPrefix + '.gltf';
-            const glTFBinFile = glTFPrefix + '.bin';
+                    const glTFFileName = glTFPrefix + '.gltf';
+                    const glTFBinFile = glTFPrefix + '.bin';
 
-            const container = new GLTFData();
+                    const container = new GLTFData();
 
-            container.glTFFiles[glTFFileName] = jsonText;
-            container.glTFFiles[glTFBinFile] = bin;
+                    container.glTFFiles[glTFFileName] = jsonText;
+                    container.glTFFiles[glTFBinFile] = bin;
 
-            if (this.imageData) {
-                for (let image in this.imageData) {
-                    container.glTFFiles[image] = new Blob([this.imageData[image].data], { type: this.imageData[image].mimeType });
-                }
-            }
+                    if (this.imageData) {
+                        for (let image in this.imageData) {
+                            container.glTFFiles[image] = new Blob([this.imageData[image].data], { type: this.imageData[image].mimeType });
+                        }
+                    }
+
+                    resolve(container);
+                });
+            });
 
-            return container;
         }
 
         /**
          * Creates a binary buffer for glTF
          * @returns array buffer for binary data
          */
-        private generateBinary(): ArrayBuffer {
-            let binaryWriter = new _BinaryWriter(4);
-            this.createScene(this.babylonScene, binaryWriter);
-            return binaryWriter.getArrayBuffer();
+        private generateBinaryAsync(): Promise<ArrayBuffer> {
+            return new Promise((resolve, reject) => {
+                let binaryWriter = new _BinaryWriter(4);
+                this.createSceneAsync(this.babylonScene, binaryWriter).then(() => {
+                    resolve(binaryWriter.getArrayBuffer());
+                });
+            });
         }
 
         /**
@@ -641,84 +651,88 @@ module BABYLON.GLTF2 {
          * @param glTFPrefix 
          * @returns object with glb filename as key and data as value
          */
-        public _generateGLB(glTFPrefix: string): GLTFData {
-            const binaryBuffer = this.generateBinary();
-            const jsonText = this.generateJSON(true);
-            const glbFileName = glTFPrefix + '.glb';
-            const headerLength = 12;
-            const chunkLengthPrefix = 8;
-            const jsonLength = jsonText.length;
-            let imageByteLength = 0;
-
-            for (let key in this.imageData) {
-                imageByteLength += this.imageData[key].data.byteLength;
-            }
-            const jsonPadding = this._getPadding(jsonLength);
-            const binPadding = this._getPadding(binaryBuffer.byteLength);
-            const imagePadding = this._getPadding(imageByteLength);
-
-            const byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding;
-
-            //header
-            const headerBuffer = new ArrayBuffer(headerLength);
-            const headerBufferView = new DataView(headerBuffer);
-            headerBufferView.setUint32(0, 0x46546C67, true); //glTF
-            headerBufferView.setUint32(4, 2, true); // version
-            headerBufferView.setUint32(8, byteLength, true); // total bytes in file
-
-            //json chunk
-            const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
-            const jsonChunkBufferView = new DataView(jsonChunkBuffer);
-            jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
-            jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
-
-            //json chunk bytes
-            const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
-            for (let i = 0; i < jsonLength; ++i) {
-                jsonData[i] = jsonText.charCodeAt(i);
-            }
+        public _generateGLBAsync(glTFPrefix: string): Promise<GLTFData> {
+            return new Promise((resolve, reject) => {
+                this.generateBinaryAsync().then(binaryBuffer => {
+                    const jsonText = this.generateJSON(true);
+                    const glbFileName = glTFPrefix + '.glb';
+                    const headerLength = 12;
+                    const chunkLengthPrefix = 8;
+                    const jsonLength = jsonText.length;
+                    let imageByteLength = 0;
+
+                    for (let key in this.imageData) {
+                        imageByteLength += this.imageData[key].data.byteLength;
+                    }
+                    const jsonPadding = this._getPadding(jsonLength);
+                    const binPadding = this._getPadding(binaryBuffer.byteLength);
+                    const imagePadding = this._getPadding(imageByteLength);
+
+                    const byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding;
+
+                    //header
+                    const headerBuffer = new ArrayBuffer(headerLength);
+                    const headerBufferView = new DataView(headerBuffer);
+                    headerBufferView.setUint32(0, 0x46546C67, true); //glTF
+                    headerBufferView.setUint32(4, 2, true); // version
+                    headerBufferView.setUint32(8, byteLength, true); // total bytes in file
+
+                    //json chunk
+                    const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
+                    const jsonChunkBufferView = new DataView(jsonChunkBuffer);
+                    jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
+                    jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
+
+                    //json chunk bytes
+                    const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
+                    for (let i = 0; i < jsonLength; ++i) {
+                        jsonData[i] = jsonText.charCodeAt(i);
+                    }
 
-            //json padding
-            const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
-            for (let i = 0; i < jsonPadding; ++i) {
-                jsonPaddingView[i] = 0x20;
-            }
+                    //json padding
+                    const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
+                    for (let i = 0; i < jsonPadding; ++i) {
+                        jsonPaddingView[i] = 0x20;
+                    }
 
-            //binary chunk
-            const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
-            const binaryChunkBufferView = new DataView(binaryChunkBuffer);
-            binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true);
-            binaryChunkBufferView.setUint32(4, 0x004E4942, true);
-
-            // binary padding
-            const binPaddingBuffer = new ArrayBuffer(binPadding);
-            const binPaddingView = new Uint8Array(binPaddingBuffer);
-            for (let i = 0; i < binPadding; ++i) {
-                binPaddingView[i] = 0;
-            }
+                    //binary chunk
+                    const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
+                    const binaryChunkBufferView = new DataView(binaryChunkBuffer);
+                    binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true);
+                    binaryChunkBufferView.setUint32(4, 0x004E4942, true);
+
+                    // binary padding
+                    const binPaddingBuffer = new ArrayBuffer(binPadding);
+                    const binPaddingView = new Uint8Array(binPaddingBuffer);
+                    for (let i = 0; i < binPadding; ++i) {
+                        binPaddingView[i] = 0;
+                    }
 
-            const imagePaddingBuffer = new ArrayBuffer(imagePadding);
-            const imagePaddingView = new Uint8Array(imagePaddingBuffer);
-            for (let i = 0; i < imagePadding; ++i) {
-                imagePaddingView[i] = 0;
-            }
+                    const imagePaddingBuffer = new ArrayBuffer(imagePadding);
+                    const imagePaddingView = new Uint8Array(imagePaddingBuffer);
+                    for (let i = 0; i < imagePadding; ++i) {
+                        imagePaddingView[i] = 0;
+                    }
 
-            const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer];
+                    const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer];
 
-            // binary data
-            for (let key in this.imageData) {
-                glbData.push(this.imageData[key].data.buffer);
-            }
-            glbData.push(binPaddingBuffer);
+                    // binary data
+                    for (let key in this.imageData) {
+                        glbData.push(this.imageData[key].data.buffer);
+                    }
+                    glbData.push(binPaddingBuffer);
 
-            glbData.push(imagePaddingBuffer);
+                    glbData.push(imagePaddingBuffer);
 
-            const glbFile = new Blob(glbData, { type: 'application/octet-stream' });
+                    const glbFile = new Blob(glbData, { type: 'application/octet-stream' });
 
-            const container = new GLTFData();
-            container.glTFFiles[glbFileName] = glbFile;
+                    const container = new GLTFData();
+                    container.glTFFiles[glbFileName] = glbFile;
 
-            return container;
+                    resolve(container);
+                });
+
+            });
         }
 
         /**
@@ -792,7 +806,7 @@ module BABYLON.GLTF2 {
          * @param babylonMesh The BabylonJS mesh
          */
         private getMeshPrimitiveMode(babylonMesh: AbstractMesh): number {
-            return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFanDrawMode;
+            return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFillMode;
         }
 
         /**
@@ -937,13 +951,36 @@ module BABYLON.GLTF2 {
                 }
 
                 if (bufferMesh.subMeshes) {
-                    uvCoordsPresent = false;
                     // go through all mesh primitives (submeshes)
                     for (const submesh of bufferMesh.subMeshes) {
+                        uvCoordsPresent = false;
+                        let babylonMaterial = submesh.getMaterial();
+
+                        let materialIndex: Nullable<number> = null;
+                        if (babylonMaterial) {
+                            if (babylonMaterial instanceof MultiMaterial) {
+                                const babylonMultiMaterial = babylonMaterial as MultiMaterial;
+                                babylonMaterial = babylonMultiMaterial.subMaterials[submesh.materialIndex];
+                                if (babylonMaterial) {
+                                    materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
+                                }
+                            }
+                            else {
+                                materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
+                            }
+                        }
+
+                        let glTFMaterial: Nullable<IMaterial> = materialIndex != null ? this.materials[materialIndex] : null;
+
                         const meshPrimitive: IMeshPrimitive = { attributes: {} };
 
                         for (const attribute of attributeData) {
                             const attributeKind = attribute.kind;
+                            if (attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) {
+                                if (glTFMaterial && !_GLTFMaterial._HasTexturesPresent(glTFMaterial)) {
+                                    continue;
+                                }
+                            }
                             let vertexData = bufferMesh.getVerticesData(attributeKind);
                             if (vertexData) {
                                 const vertexBuffer = this.getVertexBufferFromMesh(attributeKind, bufferMesh);
@@ -971,26 +1008,9 @@ module BABYLON.GLTF2 {
                             this.accessors.push(accessor);
                             meshPrimitive.indices = this.accessors.length - 1;
                         }
-                        if (bufferMesh.material) {
-                            let materialIndex: Nullable<number> = null;
-                            if (bufferMesh.material instanceof StandardMaterial || bufferMesh.material instanceof PBRMetallicRoughnessMaterial || bufferMesh.material instanceof PBRMaterial) {
-                                materialIndex = babylonTransformNode.getScene().materials.indexOf(bufferMesh.material);
-                            }
-                            else if (bufferMesh.material instanceof MultiMaterial) {
-                                const babylonMultiMaterial = bufferMesh.material as MultiMaterial;
-                                const material = babylonMultiMaterial.subMaterials[submesh.materialIndex];
-
-                                if (material) {
-                                    materialIndex = babylonTransformNode.getScene().materials.indexOf(material);
-                                }
-                            }
-                            else {
-                                Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
-                            }
-
+                        if (babylonMaterial) {
                             if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
                                 let sideOrientation = this.babylonScene.materials[materialIndex].sideOrientation;
-
                                 this.setPrimitiveMode(meshPrimitive, primitiveMode);
 
                                 if (this.convertToRightHandedSystem && sideOrientation === Material.ClockWiseSideOrientation) {
@@ -1002,7 +1022,7 @@ module BABYLON.GLTF2 {
                                         babylonIndices = bufferMesh.getIndices();
                                     }
                                     if (babylonIndices) {
-                                        this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);    
+                                        this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);
                                     }
                                     else {
                                         for (let attribute of attributeData) {
@@ -1018,49 +1038,13 @@ module BABYLON.GLTF2 {
                                     }
                                 }
 
-                                if (uvCoordsPresent) {
-                                    if (!_GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
-                                        delete meshPrimitive.attributes.TEXCOORD_0;
-                                        delete meshPrimitive.attributes.TEXCOORD_1;
-                                    }
-                                    meshPrimitive.material = materialIndex;
-                                }
-                                else {
-                                    if (_GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
-                                        const newMat = _GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
-                                        this.materials.push(newMat);
-                                        meshPrimitive.material = this.materials.length - 1;
-                                    }
-                                    else {
-                                        meshPrimitive.material = materialIndex;
-                                    }
-                                }
-                            }
-                        }
-                        else {
-                            const sideOrientation = this.babylonScene.defaultMaterial.sideOrientation;
-                            let byteOffset = indexBufferViewIndex != null ? this.bufferViews[indexBufferViewIndex].byteOffset : null;
-                            if (byteOffset == null) { byteOffset = 0; }
-                            let babylonIndices: Nullable<IndicesArray> = null;
-                            if (indexBufferViewIndex != null) {
-                                babylonIndices = bufferMesh.getIndices();
-                            }
-                            if (babylonIndices) {
-                                if (sideOrientation === Material.ClockWiseSideOrientation) {
-                                    this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);
-                                }
-                            }
-                            else {
-                                for (let attribute of attributeData) {
-                                    let vertexData = bufferMesh.getVerticesData(attribute.kind);
-                                    if (vertexData) {
-                                        let byteOffset = this.bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset;
-                                        if (!byteOffset) {
-                                            byteOffset = 0
-                                        }
-                                        this.reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, sideOrientation, attribute.kind, vertexData, byteOffset, binaryWriter);
-                                    }
+                                if (!uvCoordsPresent && _GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
+                                    const newMat = _GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
+                                    this.materials.push(newMat);
+                                    materialIndex = this.materials.length - 1;
                                 }
+
+                                meshPrimitive.material = materialIndex;
                             }
                         }
                         mesh.primitives.push(meshPrimitive);
@@ -1075,62 +1059,72 @@ module BABYLON.GLTF2 {
          * @param babylonScene Babylon scene to get the mesh data from
          * @param binaryWriter Buffer to write binary data to
          */
-        private createScene(babylonScene: Scene, binaryWriter: _BinaryWriter): void {
-            if (this.setNodeTransformation.length) {
-                const scene: IScene = { nodes: [] };
-                let glTFNodeIndex: number;
-                let glTFNode: INode;
-                let directDescendents: Node[];
-                const nodes = [...babylonScene.transformNodes, ...babylonScene.meshes];
-
-                _GLTFMaterial._ConvertMaterialsToGLTF(babylonScene.materials, ImageMimeType.PNG, this.images, this.textures, this.samplers, this.materials, this.imageData, true);
-                this.nodeMap = this.createNodeMapAndAnimations(babylonScene, nodes, this.shouldExportTransformNode, binaryWriter);
-
-                this.totalByteLength = binaryWriter.getByteOffset();
-
-
-                // Build Hierarchy with the node map.
-                for (let babylonTransformNode of nodes) {
-                    glTFNodeIndex = this.nodeMap[babylonTransformNode.uniqueId];
-                    if (glTFNodeIndex != null) {
-                        glTFNode = this.nodes[glTFNodeIndex];
-                        if (!babylonTransformNode.parent) {
-                            if (!this.shouldExportTransformNode(babylonTransformNode)) {
-                                Tools.Log("Omitting " + babylonTransformNode.name + " from scene.");
-                            }
-                            else {
-                                if (this.convertToRightHandedSystem) {
-                                        if (glTFNode.translation) {
-                                            glTFNode.translation[2] *= -1;
-                                            glTFNode.translation[0] *= -1;
+        private createSceneAsync(babylonScene: Scene, binaryWriter: _BinaryWriter): Promise<void> {
+            return new Promise((resolve, reject) => {
+                let promises = [];
+                if (this.setNodeTransformation.length) {
+                    const scene: IScene = { nodes: [] };
+                    let glTFNodeIndex: number;
+                    let glTFNode: INode;
+                    let directDescendents: Node[];
+                    const nodes = [...babylonScene.transformNodes, ...babylonScene.meshes];
+
+                    let promise = _GLTFMaterial._ConvertMaterialsToGLTFAsync(babylonScene.materials, ImageMimeType.PNG, this.images, this.textures, this.samplers, this.materials, this.imageData, true).then(() => {
+                        this.nodeMap = this.createNodeMapAndAnimations(babylonScene, nodes, this.shouldExportTransformNode, binaryWriter);
+
+                        this.totalByteLength = binaryWriter.getByteOffset();
+
+
+                        // Build Hierarchy with the node map.
+                        for (let babylonTransformNode of nodes) {
+                            glTFNodeIndex = this.nodeMap[babylonTransformNode.uniqueId];
+                            if (glTFNodeIndex != null) {
+                                glTFNode = this.nodes[glTFNodeIndex];
+                                if (!babylonTransformNode.parent) {
+                                    if (!this.shouldExportTransformNode(babylonTransformNode)) {
+                                        Tools.Log("Omitting " + babylonTransformNode.name + " from scene.");
+                                    }
+                                    else {
+                                        if (this.convertToRightHandedSystem) {
+                                            if (glTFNode.translation) {
+                                                glTFNode.translation[2] *= -1;
+                                                glTFNode.translation[0] *= -1;
+                                            }
+                                            glTFNode.rotation = glTFNode.rotation ? Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(glTFNode.rotation)).asArray() : (Quaternion.FromArray([0, 1, 0, 0])).asArray();
                                         }
-                                        glTFNode.rotation = glTFNode.rotation ? Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(glTFNode.rotation)).asArray() : (Quaternion.FromArray([0, 1, 0, 0])).asArray();
-                                }
 
-                                scene.nodes.push(glTFNodeIndex);
-                            }
-                        }
+                                        scene.nodes.push(glTFNodeIndex);
+                                    }
+                                }
 
-                        directDescendents = babylonTransformNode.getDescendants(true);
-                        if (!glTFNode.children && directDescendents && directDescendents.length) {
-                            glTFNode.children = [];
-                            for (let descendent of directDescendents) {
-                                if (this.nodeMap[descendent.uniqueId] != null) {
-                                    glTFNode.children.push(this.nodeMap[descendent.uniqueId]);
+                                directDescendents = babylonTransformNode.getDescendants(true);
+                                if (!glTFNode.children && directDescendents && directDescendents.length) {
+                                    glTFNode.children = [];
+                                    for (let descendent of directDescendents) {
+                                        if (this.nodeMap[descendent.uniqueId] != null) {
+                                            glTFNode.children.push(this.nodeMap[descendent.uniqueId]);
+                                        }
+                                    }
                                 }
                             }
+                        };
+                        if (scene.nodes.length) {
+                            this.scenes.push(scene);
                         }
-                    }
-                };
-                if (scene.nodes.length) {
-                    this.scenes.push(scene);
+                    });
+                    promises.push(promise);
                 }
-            }
+                Promise.all(promises).then(() => {
+                    resolve();
+                });
+            });
         }
 
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
+         * @param nodes Babylon transform nodes
+         * @param shouldExportTransformNode Callback specifying if a transform node should be exported
          * @param binaryWriter Buffer to write binary data to
          * @returns Node mapping of unique id to index
          */

File diff suppressed because it is too large
+ 617 - 470
serializers/src/glTF/2.0/babylon.glTFMaterial.ts


+ 8 - 31
serializers/src/glTF/2.0/babylon.glTFSerializer.ts

@@ -22,20 +22,6 @@ module BABYLON {
      */
     export class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating the glTF file
-         * @param options Exporter options
-         * @returns Returns an object with a .gltf file and associates texture names
-         * as keys and their data and paths as values
-         */
-        private static GLTF(scene: Scene, filePrefix: string, options?: IExportOptions): GLTFData {
-            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
-            const gltfGenerator = new GLTF2._Exporter(scene, options);
-            return gltfGenerator._generateGLTF(glTFPrefix);
-        }
-
-        /**
          * Exports the geometry of the scene to .gltf file format asynchronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating the glTF file
@@ -44,25 +30,14 @@ module BABYLON {
          * as keys and their data and paths as values
          */
         public static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData> {
-            return Promise.resolve(scene.whenReadyAsync()).then(() => {
-                return GLTF2Export.GLTF(scene, filePrefix, options);
+            return scene.whenReadyAsync().then(() => {
+                const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
+                const gltfGenerator = new GLTF2._Exporter(scene, options);
+                return gltfGenerator._generateGLTFAsync(glTFPrefix);
             });
         }
 
         /**
-         * Exports the geometry of the scene to .glb file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating glb file
-         * @param options Exporter options
-         * @returns Returns an object with a .glb filename as key and data as value
-         */
-        private static GLB(scene: Scene, filePrefix: string, options?: IExportOptions): GLTFData {
-            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
-            const gltfGenerator = new GLTF2._Exporter(scene, options);
-            return gltfGenerator._generateGLB(glTFPrefix);
-        }
-
-        /**
          * Exports the geometry of the scene to .glb file format asychronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating glb file
@@ -70,8 +45,10 @@ module BABYLON {
          * @returns Returns an object with a .glb filename as key and data as value
          */
         public static GLBAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData> {
-            return Promise.resolve(scene.whenReadyAsync()).then(() => {
-                return GLTF2Export.GLB(scene, filePrefix, options);
+            return scene.whenReadyAsync().then(() => {
+                const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
+                const gltfGenerator = new GLTF2._Exporter(scene, options);
+                return gltfGenerator._generateGLBAsync(glTFPrefix);
             });
         }
     }

+ 10 - 0
serializers/src/glTF/2.0/shaders/setAlphaToOne.fragment.fx

@@ -0,0 +1,10 @@
+precision highp float;
+
+uniform sampler2D textureSampler;
+
+varying vec2 vUV;
+
+void main(void) {
+    vec4 color = texture2D(textureSampler, vUV);
+    gl_FragColor = vec4(color.rgb, 1.0);
+}

+ 217 - 244
tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts

@@ -12,6 +12,8 @@ describe('Babylon glTF Serializer', () => {
         (BABYLONDEVTOOLS).Loader
             .useDist()
             .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
                 done();
             });
     });
@@ -78,63 +80,53 @@ describe('Babylon glTF Serializer', () => {
             BABYLON.GLTF2._GLTFMaterial._SolveMetallic(0.0, 1.0, 1.0).should.be.approximately(1, 1e-6);
         });
 
-        it('should serialize empty Babylon scene to glTF with only asset property', (done) => {
-            mocha.timeout(10000);
-
+        it('should serialize empty Babylon scene to glTF with only asset property', () => {
             const scene = new BABYLON.Scene(subject);
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
                 Object.keys(jsonData).length.should.be.equal(1);
                 jsonData.asset.version.should.be.equal("2.0");
                 jsonData.asset.generator.should.be.equal("BabylonJS");
-
-                done();
             });
         });
 
-        it('should serialize sphere geometry in scene to glTF', (done) => {
-            mocha.timeout(10000);
+        it('should serialize sphere geometry in scene to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             BABYLON.Mesh.CreateSphere('sphere', 16, 2, scene);
 
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
-                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-                const jsonData = JSON.parse(jsonString);
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test')
+                .then(glTFData => {
+                    const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                    const jsonData = JSON.parse(jsonString);
 
-                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
-                Object.keys(jsonData).length.should.be.equal(9);
+                    // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
+                    Object.keys(jsonData).length.should.be.equal(9);
 
-                // positions, normals, texture coords, indices
-                jsonData.accessors.length.should.be.equal(4);
+                    // positions, normals, indices
+                    jsonData.accessors.length.should.be.equal(3);
 
-                // generator, version
-                Object.keys(jsonData.asset).length.should.be.equal(2);
+                    // generator, version
+                    Object.keys(jsonData.asset).length.should.be.equal(2);
 
-                jsonData.buffers.length.should.be.equal(1);
+                    jsonData.buffers.length.should.be.equal(1);
 
-                // positions, normals, texture coords, indices
-                jsonData.bufferViews.length.should.be.equal(4);
+                    // positions, normals, texture coords, indices
+                    jsonData.bufferViews.length.should.be.equal(4);
 
-                jsonData.meshes.length.should.be.equal(1);
+                    jsonData.meshes.length.should.be.equal(1);
 
-                jsonData.nodes.length.should.be.equal(1);
+                    jsonData.nodes.length.should.be.equal(1);
 
-                jsonData.scenes.length.should.be.equal(1);
-
-                jsonData.scene.should.be.equal(0);
+                    jsonData.scenes.length.should.be.equal(1);
 
-                done();
-            });
+                    jsonData.scene.should.be.equal(0);
+                });
         });
 
-        it('should serialize alpha mode and cutoff', (done) => {
-            mocha.timeout(10000);
+        it('should serialize alpha mode and cutoff', () => {
             const scene = new BABYLON.Scene(subject);
 
             const plane = BABYLON.Mesh.CreatePlane('plane', 120, scene);
@@ -145,9 +137,8 @@ describe('Babylon glTF Serializer', () => {
 
             plane.material = babylonPBRMetalRoughMaterial;
 
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
@@ -159,243 +150,228 @@ describe('Babylon glTF Serializer', () => {
                 jsonData.materials[0].alphaMode.should.be.equal('MASK');
 
                 jsonData.materials[0].alphaCutoff.should.be.equal(alphaCutoff);
-
-                done();
             });
         });
-        it('should serialize single component translation animation to glTF', (done) => {
-            mocha.timeout(10000);
+        it('should serialize single component translation animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: 1
+                frame: 0,
+                value: 1
             });
             keys.push({
-            frame: 20,
-            value: 0.2
+                frame: 20,
+                value: 0.2
             });
             keys.push({
-            frame: 40,
-            value: 1
+                frame: 40,
+                value: 1
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'position.y', 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('translation');
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('translation');
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize translation animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize translation animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'position', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('translation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('translation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize scale animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize scale animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'scaling', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('scale');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('scale');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize rotation quaternion animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize rotation quaternion animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 0,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Quaternion.Identity()
+                frame: 20,
+                value: BABYLON.Quaternion.Identity()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 40,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'rotationQuaternion', 30, BABYLON.Animation.ANIMATIONTYPE_QUATERNION, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('rotation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('rotation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize combined animations to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize combined animations to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             const rotationKeyFrames: BABYLON.IAnimationKey[] = [];
             rotationKeyFrames.push({
-            frame: 0,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 0,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             rotationKeyFrames.push({
-            frame: 20,
-            value: BABYLON.Quaternion.Identity()
+                frame: 20,
+                value: BABYLON.Quaternion.Identity()
             });
             rotationKeyFrames.push({
-            frame: 40,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 40,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             const scaleKeyFrames: BABYLON.IAnimationKey[] = [];
             scaleKeyFrames.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             scaleKeyFrames.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             scaleKeyFrames.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let rotationAnimationBox = new BABYLON.Animation('boxAnimation_rotation', 'rotationQuaternion', 30, BABYLON.Animation.ANIMATIONTYPE_QUATERNION, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             rotationAnimationBox.setKeys(rotationKeyFrames);
@@ -403,46 +379,43 @@ describe('Babylon glTF Serializer', () => {
             let scaleAnimationBox = new BABYLON.Animation('boxAnimation_scale', 'scaling', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             scaleAnimationBox.setKeys(scaleKeyFrames);
             box.animations.push(scaleAnimationBox);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(2);
-            
-            let animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('rotation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            animation = jsonData.animations[1];
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('scale');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(6);
-            animation.samplers[0].output.should.be.equal(7);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data
-            jsonData.accessors.length.should.be.equal(8);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data 
-            jsonData.bufferViews.length.should.be.equal(8);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(2);
+
+                let animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('rotation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                animation = jsonData.animations[1];
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('scale');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(5);
+                animation.samplers[0].output.should.be.equal(6);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data
+                jsonData.accessors.length.should.be.equal(7);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data 
+                jsonData.bufferViews.length.should.be.equal(8);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            
+        });
     });
 });