瀏覽代碼

fixes for glTF export; handedness conversions and primitive modes

Kacey Coley 7 年之前
父節點
當前提交
23d4ea9104

+ 146 - 73
serializers/src/glTF/2.0/babylon.glTFAnimation.ts

@@ -66,16 +66,17 @@ module BABYLON.GLTF2 {
      */
     export class _GLTFAnimation {
         /**
+         * @ignore
          * 
          * Creates glTF channel animation from BabylonJS animation.
-         * @param babylonMesh - BabylonJS mesh.
+         * @param babylonTransformNode - BabylonJS mesh.
          * @param animation - animation.
          * @param animationChannelTargetPath - The target animation channel. 
          * @param convertToRightHandedSystem - Specifies if the values should be converted to right-handed.
          * @param useQuaternion - Specifies if quaternions are used.
          * @returns nullable IAnimationData
          */
-        public static _CreateNodeAnimation(babylonMesh: BABYLON.Mesh, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number): Nullable<_IAnimationData> {
+        public static _CreateNodeAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number): Nullable<_IAnimationData> {
             const inputs: number[] = [];
             const outputs: number[][] = [];
             const keyFrames = animation.getKeys();
@@ -87,18 +88,18 @@ module BABYLON.GLTF2 {
             const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation;
 
             if (shouldBakeAnimation) {
-                _GLTFAnimation._CreateBakedAnimation(babylonMesh, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
+                _GLTFAnimation._CreateBakedAnimation(babylonTransformNode, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
             }
             else {
                 if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) {
-                    _GLTFAnimation._CreateLinearOrStepAnimation(babylonMesh, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
+                    _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
 
                 }
                 else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
-                    _GLTFAnimation._CreateCubicSplineAnimation(babylonMesh, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
+                    _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, frameDelta, inputs, outputs, convertToRightHandedSystem, useQuaternion);
                 }
                 else {
-                    _GLTFAnimation._CreateBakedAnimation(babylonMesh, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
+                    _GLTFAnimation._CreateBakedAnimation(babylonTransformNode, animation, animationChannelTargetPath, minMaxKeyFrames.min, minMaxKeyFrames.max, animation.framePerSecond, animationSampleRate, inputs, outputs, minMaxKeyFrames, convertToRightHandedSystem, useQuaternion);
                 }
             }
 
@@ -156,8 +157,9 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * 
-         * @param babylonMesh 
+         * @ignore
+         * Create node animations from the transform node animations
+         * @param babylonTransformNode 
          * @param runtimeGLTFAnimation 
          * @param idleGLTFAnimations 
          * @param nodeMap 
@@ -167,10 +169,10 @@ module BABYLON.GLTF2 {
          * @param accessors 
          * @param convertToRightHandedSystem 
          */
-        public static _CreateNodeAnimationFromMeshAnimations(babylonMesh: Mesh, runtimeGLTFAnimation: IAnimation, idleGLTFAnimations: IAnimation[], nodeMap: { [key: number]: number }, nodes: INode[], binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, animationSampleRate: number) {
+        public static _CreateNodeAnimationFromTransformNodeAnimations(babylonTransformNode: TransformNode, runtimeGLTFAnimation: IAnimation, idleGLTFAnimations: IAnimation[], nodeMap: { [key: number]: number }, nodes: INode[], binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, animationSampleRate: number) {
             let glTFAnimation: IAnimation;
-            if (babylonMesh.animations) {
-                babylonMesh.animations.forEach(function (animation) {
+            if (babylonTransformNode.animations) {
+                for (let animation of babylonTransformNode.animations) {
                     let animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation);
                     if (animationInfo) {
                         glTFAnimation = {
@@ -180,7 +182,7 @@ module BABYLON.GLTF2 {
                         }
                         _GLTFAnimation.AddAnimation(`${animation.name}`,
                             animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation,
-                            babylonMesh,
+                            babylonTransformNode,
                             animation,
                             animationInfo.dataAccessorType,
                             animationInfo.animationChannelTargetPath,
@@ -196,12 +198,13 @@ module BABYLON.GLTF2 {
                             idleGLTFAnimations.push(glTFAnimation);
                         }
                     }
-                });
+                };
             }
         }
 
         /**
-         * 
+         * @ignore
+         * Create node animations from the animation groups
          * @param babylonScene 
          * @param glTFAnimations 
          * @param nodeMap 
@@ -216,19 +219,19 @@ module BABYLON.GLTF2 {
             if (babylonScene.animationGroups) {
                 let animationGroups = babylonScene.animationGroups;
 
-                animationGroups.forEach(function (animationGroup) {
+                for (let animationGroup of animationGroups) {
                     glTFAnimation = {
                         name: animationGroup.name,
                         channels: [],
                         samplers: []
                     }
-                    animationGroup.targetedAnimations.forEach(function (targetAnimation) {
+                    for (let targetAnimation of animationGroup.targetedAnimations) {
                         let target = targetAnimation.target;
                         let animation = targetAnimation.animation;
-                        if (target instanceof Mesh) {
+                        if (target instanceof Mesh || target.length === 1 && target[0] instanceof Mesh) { // TODO: Update to support bones
                             let animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation);
                             if (animationInfo) {
-                                let babylonMesh = target as Mesh;
+                                let babylonMesh = target instanceof Mesh ? target : target[0] as Mesh;
                                 _GLTFAnimation.AddAnimation(`${animation.name}`,
                                     glTFAnimation,
                                     babylonMesh,
@@ -245,16 +248,16 @@ module BABYLON.GLTF2 {
                                 );
                             }
                         }
-                    });
+                    };
                     if (glTFAnimation.channels.length && glTFAnimation.samplers.length) {
                         glTFAnimations.push(glTFAnimation);
                     }
-                });
+                };
             }
         }
 
-        private static AddAnimation(name: string, glTFAnimation: IAnimation, babylonMesh: Mesh, animation: Animation, dataAccessorType: AccessorType, animationChannelTargetPath: AnimationChannelTargetPath, nodeMap: { [key: number]: number }, binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number) {
-            let animationData = _GLTFAnimation._CreateNodeAnimation(babylonMesh, animation, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion, animationSampleRate);
+        private static AddAnimation(name: string, glTFAnimation: IAnimation, babylonTransformNode: TransformNode, animation: Animation, dataAccessorType: AccessorType, animationChannelTargetPath: AnimationChannelTargetPath, nodeMap: { [key: number]: number }, binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, useQuaternion: boolean, animationSampleRate: number) {
+            let animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion, animationSampleRate);
             let bufferView: IBufferView;
             let accessor: IAccessor;
             let keyframeAccessorIndex: number;
@@ -264,7 +267,7 @@ module BABYLON.GLTF2 {
             let animationChannel: IAnimationChannel;
 
             if (animationData) {
-                let nodeIndex = nodeMap[babylonMesh.uniqueId];
+                let nodeIndex = nodeMap[babylonTransformNode.uniqueId];
 
                 // Creates buffer view and accessor for key frames.
                 let byteLength = animationData.inputs.length * 4;
@@ -319,7 +322,7 @@ module BABYLON.GLTF2 {
 
         /**
          * Create a baked animation
-         * @param babylonMesh BabylonJS mesh
+         * @param babylonTransformNode BabylonJS mesh
          * @param animation BabylonJS animation corresponding to the BabylonJS mesh
          * @param animationChannelTargetPath animation target channel
          * @param minFrame minimum animation frame
@@ -330,7 +333,7 @@ module BABYLON.GLTF2 {
          * @param convertToRightHandedSystem converts the values to right-handed
          * @param useQuaternion specifies if quaternions should be used
          */
-        private static _CreateBakedAnimation(babylonMesh: Mesh, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, minFrame: number, maxFrame: number, fps: number, sampleRate: number, inputs: number[], outputs: number[][], minMaxFrames: { min: number, max: number }, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+        private static _CreateBakedAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, minFrame: number, maxFrame: number, fps: number, sampleRate: number, inputs: number[], outputs: number[][], minMaxFrames: { min: number, max: number }, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
             let value: number | Vector3 | Quaternion;
             let quaternionCache: Quaternion = Quaternion.Identity();
             let previousTime: Nullable<number> = null;
@@ -382,7 +385,7 @@ module BABYLON.GLTF2 {
                         maxUsedFrame = time;
                         value = animation._interpolate(f, 0, undefined, animation.loopMode);
 
-                        _GLTFAnimation._SetInterpolatedValue(babylonMesh, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
+                        _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
                     }
                 }
             }
@@ -391,24 +394,27 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private static _ConvertFactorToVector3OrQuaternion(factor: number, babylonMesh: Mesh, animation: Animation, animationType: number, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean): Nullable<Vector3 | Quaternion> {
+        private static _ConvertFactorToVector3OrQuaternion(factor: number, babylonTransformNode: TransformNode, animation: Animation, animationType: number, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean): Nullable<Vector3 | Quaternion> {
             let property: string[];
             let componentName: string;
             let value: Nullable<Quaternion | Vector3> = null;
-            const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonMesh, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
+            const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
             if (animationType === Animation.ANIMATIONTYPE_FLOAT) { // handles single component x, y, z or w component animation by using a base property and animating over a component.
                 property = animation.targetProperty.split('.');
                 componentName = property ? property[1] : ''; // x, y, or z component
                 value = useQuaternion ? BABYLON.Quaternion.FromArray(basePositionRotationOrScale).normalize() : BABYLON.Vector3.FromArray(basePositionRotationOrScale);
 
                 switch (componentName) {
-                    case 'x':
+                    case 'x': {
+                        value[componentName] = (convertToRightHandedSystem && useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
+                        break;
+                    }
                     case 'y': {
-                        value[componentName] = (convertToRightHandedSystem && useQuaternion) ? -factor : factor;
+                        value[componentName] = (convertToRightHandedSystem && useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
                         break;
                     }
                     case 'z': {
-                        value[componentName] = (convertToRightHandedSystem && !useQuaternion && !(animationChannelTargetPath === AnimationChannelTargetPath.SCALE)) ? -factor : factor;
+                        value[componentName] = (convertToRightHandedSystem && !useQuaternion && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) ? -factor : factor;
                         break;
                     }
                     case 'w': {
@@ -424,12 +430,12 @@ module BABYLON.GLTF2 {
             return value;
         }
 
-        private static _SetInterpolatedValue(babylonMesh: Mesh, value: Nullable<number | Vector3 | Quaternion>, time: number, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, quaternionCache: Quaternion, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+        private static _SetInterpolatedValue(babylonTransformNode: TransformNode, value: Nullable<number | Vector3 | Quaternion>, time: number, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, quaternionCache: Quaternion, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
             const animationType = animation.dataType;
             let cacheValue: Vector3 | Quaternion;
             inputs.push(time);
             if (typeof value === "number") {
-                value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonMesh, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
+                value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
             }
             if (value) {
                 if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
@@ -441,15 +447,22 @@ module BABYLON.GLTF2 {
                         Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache);
                     }
                     if (convertToRightHandedSystem) {
-                        quaternionCache.x *= -1;
-                        quaternionCache.y *= -1;
-                        outputs.push(quaternionCache.asArray());
+                        _GLTFUtilities.GetRightHandedQuaternionFromRef(quaternionCache);
+
+                        if (!babylonTransformNode.parent) {
+                            quaternionCache = Quaternion.FromArray([0, 1, 0, 0]).multiply(quaternionCache);
+                        }
                     }
+                    outputs.push(quaternionCache.asArray());
                 }
                 else {
                     cacheValue = value as Vector3;
                     if (convertToRightHandedSystem && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) {
-                        cacheValue.z *= -1;
+                        _GLTFUtilities.GetRightHandedPositionVector3FromRef(cacheValue);
+                        if (!babylonTransformNode.parent) {
+                            cacheValue.x *= -1;
+                            cacheValue.z *= -1;
+                        }
                     }
 
                     outputs.push(cacheValue.asArray());
@@ -459,7 +472,7 @@ module BABYLON.GLTF2 {
 
         /**
          * Creates linear animation from the animation key frames
-         * @param babylonMesh BabylonJS mesh
+         * @param babylonTransformNode BabylonJS mesh
          * @param animation BabylonJS animation
          * @param animationChannelTargetPath The target animation channel
          * @param frameDelta The difference between the last and first frame of the animation
@@ -468,16 +481,16 @@ module BABYLON.GLTF2 {
          * @param convertToRightHandedSystem Specifies if the position data should be converted to right handed
          * @param useQuaternion Specifies if quaternions are used in the animation
          */
-        private static _CreateLinearOrStepAnimation(babylonMesh: Mesh, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
-            animation.getKeys().forEach(function (keyFrame) {
+        private static _CreateLinearOrStepAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+            for (let keyFrame of animation.getKeys()) {
                 inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
-                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonMesh, convertToRightHandedSystem, useQuaternion);
-            });
+                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, convertToRightHandedSystem, useQuaternion);
+            };
         }
 
         /**
          * Creates cubic spline animation from the animation key frames
-         * @param babylonMesh BabylonJS mesh
+         * @param babylonTransformNode BabylonJS mesh
          * @param animation BabylonJS animation
          * @param animationChannelTargetPath The target animation channel
          * @param frameDelta The difference between the last and first frame of the animation
@@ -486,10 +499,11 @@ module BABYLON.GLTF2 {
          * @param convertToRightHandedSystem Specifies if the position data should be converted to right handed
          * @param useQuaternion Specifies if quaternions are used in the animation
          */
-        private static _CreateCubicSplineAnimation(babylonMesh: Mesh, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+        private static _CreateCubicSplineAnimation(babylonTransformNode: TransformNode, animation: Animation, animationChannelTargetPath: AnimationChannelTargetPath, frameDelta: number, inputs: number[], outputs: number[][], convertToRightHandedSystem: boolean, useQuaternion: boolean) {
             animation.getKeys().forEach(function (keyFrame) {
                 inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
                 _GLTFAnimation.AddSplineTangent(
+                    babylonTransformNode,
                     _TangentType.INTANGENT,
                     outputs,
                     animationChannelTargetPath,
@@ -499,9 +513,10 @@ module BABYLON.GLTF2 {
                     useQuaternion,
                     convertToRightHandedSystem
                 );
-                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonMesh, convertToRightHandedSystem, useQuaternion);
+                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, convertToRightHandedSystem, useQuaternion);
 
                 _GLTFAnimation.AddSplineTangent(
+                    babylonTransformNode,
                     _TangentType.OUTTANGENT,
                     outputs,
                     animationChannelTargetPath,
@@ -514,15 +529,17 @@ module BABYLON.GLTF2 {
             });
         }
 
-        private static _GetBasePositionRotationOrScale(babylonMesh: Mesh, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+        private static _GetBasePositionRotationOrScale(babylonTransformNode: TransformNode, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
             let basePositionRotationOrScale: number[];
             if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
                 if (useQuaternion) {
-                    if (babylonMesh.rotationQuaternion) {
-                        basePositionRotationOrScale = babylonMesh.rotationQuaternion.asArray();
+                    if (babylonTransformNode.rotationQuaternion) {
+                        basePositionRotationOrScale = babylonTransformNode.rotationQuaternion.asArray();
                         if (convertToRightHandedSystem) {
-                            basePositionRotationOrScale[0] *= -1;
-                            basePositionRotationOrScale[1] *= -1;
+                            _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(basePositionRotationOrScale);
+                            if (!babylonTransformNode.parent) {
+                                basePositionRotationOrScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(basePositionRotationOrScale)).asArray();
+                            }
                         }
                     }
                     else {
@@ -530,18 +547,18 @@ module BABYLON.GLTF2 {
                     }
                 }
                 else {
-                    basePositionRotationOrScale = babylonMesh.rotation.asArray();
-                    basePositionRotationOrScale[2] *= -1;
+                    basePositionRotationOrScale = babylonTransformNode.rotation.asArray();
+                    _GLTFUtilities.GetRightHandedNormalArray3FromRef(basePositionRotationOrScale);
                 }
             }
             else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
-                basePositionRotationOrScale = babylonMesh.position.asArray();
+                basePositionRotationOrScale = babylonTransformNode.position.asArray();
                 if (convertToRightHandedSystem) {
-                    basePositionRotationOrScale[2] *= -1;
+                    _GLTFUtilities.GetRightHandedPositionArray3FromRef(basePositionRotationOrScale);
                 }
             }
             else { // scale
-                basePositionRotationOrScale = babylonMesh.scaling.asArray();
+                basePositionRotationOrScale = babylonTransformNode.scaling.asArray();
             }
             return basePositionRotationOrScale;
         }
@@ -556,40 +573,74 @@ module BABYLON.GLTF2 {
          * @param convertToRightHandedSystem 
          * @param useQuaternion 
          */
-        private static _AddKeyframeValue(keyFrame: IAnimationKey, animation: Animation, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, babylonMesh: Mesh, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+        private static _AddKeyframeValue(keyFrame: IAnimationKey, animation: Animation, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, babylonTransformNode: TransformNode, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
             let value: number[];
             let newPositionRotationOrScale: Nullable<Vector3 | Quaternion>;
             const animationType = animation.dataType;
             if (animationType === Animation.ANIMATIONTYPE_VECTOR3) {
                 value = keyFrame.value.asArray();
-                if (convertToRightHandedSystem && !(animationChannelTargetPath === AnimationChannelTargetPath.SCALE)) {
-                    value[2] *= -1;
-                }
                 if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
-                    outputs.push(Vector3.FromArray(value).toQuaternion().asArray());
+                    const array = Vector3.FromArray(value);
+                    let rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z);
+                    if (convertToRightHandedSystem) {
+                        _GLTFUtilities.GetRightHandedQuaternionFromRef(rotationQuaternion);
+
+                        if (!babylonTransformNode.parent) {
+                            rotationQuaternion = Quaternion.FromArray([0, 1, 0, 0]).multiply(rotationQuaternion);
+                        }
+                    }
+                    value = rotationQuaternion.asArray();
                 }
-                else {
-                    outputs.push(value); // scale or position vector.
+                else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
+                    if (convertToRightHandedSystem) {
+                        _GLTFUtilities.GetRightHandedNormalArray3FromRef(value);
+                        if (!babylonTransformNode.parent) {
+                            value[0] *= -1;
+                            value[2] *= -1;
+                        }
+                    }
                 }
+                outputs.push(value); // scale  vector.
+
             }
             else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { // handles single component x, y, z or w component animation by using a base property and animating over a component.
-                newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion(keyFrame.value as number, babylonMesh, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion)
+                newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion(keyFrame.value as number, babylonTransformNode, animation, animationType, animationChannelTargetPath, convertToRightHandedSystem, useQuaternion);
                 if (newPositionRotationOrScale) {
                     if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
-                        useQuaternion ? outputs.push(newPositionRotationOrScale.normalize().asArray()) : outputs.push((newPositionRotationOrScale as Vector3).toQuaternion().normalize().asArray());
+                        let posRotScale = useQuaternion ? newPositionRotationOrScale as Quaternion : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize();
+                        if (convertToRightHandedSystem) {
+                            _GLTFUtilities.GetRightHandedQuaternionFromRef(posRotScale);
+
+                            if (!babylonTransformNode.parent) {
+                                posRotScale = Quaternion.FromArray([0, 1, 0, 0]).multiply(posRotScale);
+                            }
+                        }
+                        outputs.push(posRotScale.asArray());
                     }
-                    else {
-                        outputs.push(newPositionRotationOrScale.asArray());
+                    else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
+                        if (convertToRightHandedSystem) {
+                            _GLTFUtilities.GetRightHandedNormalVector3FromRef(newPositionRotationOrScale as Vector3);
+
+                            if (!babylonTransformNode.parent) {
+                                newPositionRotationOrScale.x *= -1;
+                                newPositionRotationOrScale.z *= -1;
+                            }
+                        }
                     }
+                    outputs.push(newPositionRotationOrScale.asArray());
                 }
             }
             else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) {
                 value = (keyFrame.value as Quaternion).normalize().asArray();
 
                 if (convertToRightHandedSystem) {
-                    value[0] *= -1;
-                    value[1] *= -1;
+                    _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(value);
+
+                    if (!babylonTransformNode.parent) {
+                        value = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(value)).asArray();
+                    }
                 }
+
                 outputs.push(value);
             }
             else {
@@ -597,6 +648,12 @@ module BABYLON.GLTF2 {
             }
         }
 
+        /**
+         * Determine the interpolation based on the key frames
+         * @param keyFrames 
+         * @param animationChannelTargetPath 
+         * @param useQuaternion 
+         */
         private static _DeduceInterpolation(keyFrames: IAnimationKey[], animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean): { interpolationType: AnimationSamplerInterpolation, shouldBakeAnimation: boolean } {
             let interpolationType: AnimationSamplerInterpolation | undefined;
             let shouldBakeAnimation = false;
@@ -658,17 +715,27 @@ module BABYLON.GLTF2 {
          * @param useQuaternion Specifies if quaternions are used
          * @param convertToRightHandedSystem Specifies if the values should be converted to right-handed
          */
-        private static AddSplineTangent(tangentType: _TangentType, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, interpolation: AnimationSamplerInterpolation, keyFrame: IAnimationKey, frameDelta: number, useQuaternion: boolean, convertToRightHandedSystem: boolean) {
+        private static AddSplineTangent(babylonTransformNode: TransformNode, tangentType: _TangentType, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, interpolation: AnimationSamplerInterpolation, keyFrame: IAnimationKey, frameDelta: number, useQuaternion: boolean, convertToRightHandedSystem: boolean) {
             let tangent: number[];
             let tangentValue: Vector3 | Quaternion = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent;
             if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
                 if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
                     if (tangentValue) {
-                        tangent = useQuaternion ? (tangentValue as Quaternion).scale(frameDelta).asArray() : (tangentValue as Vector3).scale(frameDelta).toQuaternion().asArray();
+                        if (useQuaternion) {
+                            tangent = (tangentValue as Quaternion).scale(frameDelta).asArray();
+                        }
+                        else {
+                            const array = (tangentValue as Vector3).scale(frameDelta);
+                            tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray();
+                        }
+
                         if (convertToRightHandedSystem) {
-                            tangent[0] *= -1;
-                            tangent[1] *= -1;
+                            _GLTFUtilities.GetRightHandedQuaternionArrayFromRef(tangent);
+                            if (!babylonTransformNode.parent) {
+                                tangent = Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(tangent)).asArray();
+                            }
                         }
+
                     }
                     else {
                         tangent = [0, 0, 0, 0];
@@ -678,7 +745,13 @@ module BABYLON.GLTF2 {
                     if (tangentValue) {
                         tangent = (tangentValue as Vector3).scale(frameDelta).asArray();
                         if (convertToRightHandedSystem) {
-                            tangent[2] *= -1;
+                            if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
+                                _GLTFUtilities.GetRightHandedPositionArray3FromRef(tangent);
+                                if (!babylonTransformNode.parent) {
+                                    tangent[0] *= -1; // x
+                                    tangent[2] *= -1; // z
+                                }
+                            }
                         }
                     }
                     else {

File diff suppressed because it is too large
+ 750 - 192
serializers/src/glTF/2.0/babylon.glTFExporter.ts


+ 193 - 52
serializers/src/glTF/2.0/babylon.glTFMaterial.ts

@@ -92,17 +92,16 @@ module BABYLON.GLTF2 {
          * @param imageData mapping of texture names to base64 textures
          * @param hasTextureCoords specifies if texture coordinates are present on the material
          */
-        public static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
-            for (let i = 0; i < babylonMaterials.length; ++i) {
-                const babylonMaterial = babylonMaterials[i];
+        public static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+            for (let babylonMaterial of babylonMaterials) {
                 if (babylonMaterial instanceof StandardMaterial) {
-                    _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
                 }
                 else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
-                    _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
                 }
                 else if (babylonMaterial instanceof PBRMaterial) {
-                    _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
+                    _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
                 }
                 else {
                     Tools.Error("Unsupported material type: " + babylonMaterial.name);
@@ -313,7 +312,7 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             const glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
 
             const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
@@ -325,26 +324,29 @@ module BABYLON.GLTF2 {
             }
             if (hasTextureCoords) {
                 if (babylonStandardMaterial.diffuseTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture != null) {
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                 }
                 if (babylonStandardMaterial.bumpTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, imageData)
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, samplers, imageData)
                     if (glTFTexture) {
                         glTFMaterial.normalTexture = glTFTexture;
+                        if (babylonStandardMaterial.bumpTexture.level !== 1) {
+                            glTFMaterial.normalTexture.scale = babylonStandardMaterial.bumpTexture.level;
+                        }
                     }
                 }
                 if (babylonStandardMaterial.emissiveTexture) {
-                    const glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, imageData)
+                    const glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData)
                     if (glTFEmissiveTexture) {
                         glTFMaterial.emissiveTexture = glTFEmissiveTexture;
                     }
                     glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
                 }
                 if (babylonStandardMaterial.ambientTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture) {
                         const occlusionTexture: IMaterialOcclusionTextureInfo = {
                             index: glTFTexture.index
@@ -383,7 +385,7 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
 
             if (babylonPBRMetalRoughMaterial.baseColor) {
@@ -411,19 +413,22 @@ module BABYLON.GLTF2 {
 
             if (hasTextureCoords) {
                 if (babylonPBRMetalRoughMaterial.baseTexture != null) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture != null) {
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                 }
                 if (babylonPBRMetalRoughMaterial.normalTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture) {
                         glTFMaterial.normalTexture = glTFTexture;
+                        if (babylonPBRMetalRoughMaterial.normalTexture.level !== 1) {
+                            glTFMaterial.normalTexture.scale = babylonPBRMetalRoughMaterial.normalTexture.level;
+                        }
                     }
                 }
                 if (babylonPBRMetalRoughMaterial.occlusionTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture) {
                         glTFMaterial.occlusionTexture = glTFTexture;
                         if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
@@ -432,7 +437,7 @@ module BABYLON.GLTF2 {
                     }
                 }
                 if (babylonPBRMetalRoughMaterial.emissiveTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture != null) {
                         glTFMaterial.emissiveTexture = glTFTexture;
                     }
@@ -447,7 +452,7 @@ module BABYLON.GLTF2 {
                 if (alphaMode) {
                     if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
                         glTFMaterial.alphaMode = alphaMode;
-                        if (alphaMode === MaterialAlphaMode.BLEND) {
+                        if (alphaMode === MaterialAlphaMode.MASK) {
                             glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
                         }
                     }
@@ -469,10 +474,11 @@ module BABYLON.GLTF2 {
          */
         private static _CreateBase64FromCanvas(buffer: Uint8ClampedArray | Float32Array, width: number, height: number, mimeType: ImageMimeType): string {
             const imageCanvas = document.createElement('canvas');
-            imageCanvas.id = "WriteCanvas";
-            const ctx = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
             imageCanvas.width = width;
             imageCanvas.height = height;
+            imageCanvas.id = "WriteCanvas";
+ 
+            const ctx = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
 
             const imgData = ctx.createImageData(width, height);
 
@@ -492,8 +498,8 @@ module BABYLON.GLTF2 {
         private static _CreateWhiteTexture(width: number, height: number, scene: Scene): Texture {
             const data = new Uint8Array(width * height * 4);
 
-            for (let i = 0; i < data.length; ++i) {
-                data[i] = 255;
+            for (let i = 0; i < data.length; i = i + 4) {
+                data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0xFF;
             }
 
             const rawTexture = RawTexture.CreateRGBATexture(data, width, height, scene);
@@ -745,7 +751,7 @@ module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): _IPBRMetallicRoughness {
+        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): _IPBRMetallicRoughness {
             const metallicRoughness = {
                 baseColor: babylonPBRMaterial.albedoColor,
                 metallic: babylonPBRMaterial.metallic,
@@ -754,13 +760,13 @@ module BABYLON.GLTF2 {
 
             if (hasTextureCoords) {
                 if (babylonPBRMaterial.albedoTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture) {
                         glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
                     }
                 }
                 if (babylonPBRMaterial.metallicTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, imageData);
+                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, samplers, imageData);
                     if (glTFTexture != null) {
                         glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
                     }
@@ -769,6 +775,107 @@ module BABYLON.GLTF2 {
             return metallicRoughness;
         }
 
+        private static _GetGLTFTextureSampler(texture: BaseTexture): ISampler {
+            const sampler = _GLTFMaterial._GetGLTFTextureWrapModesSampler(texture);
+
+            let samplingMode = texture instanceof Texture ? (texture as Texture).samplingMode : null;
+            if (samplingMode != null) {
+                switch (samplingMode) {
+                    case Texture.LINEAR_LINEAR: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.LINEAR;
+                        break;
+                    }
+                    case Texture.LINEAR_NEAREST: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.NEAREST;
+                        break;
+                    }
+                    case Texture.NEAREST_LINEAR: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.LINEAR;
+                        break;
+                    }
+                    case Texture.NEAREST_LINEAR_MIPLINEAR: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_LINEAR;
+                        break;
+                    }
+                    case Texture.NEAREST_NEAREST: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.NEAREST;
+                        break;
+                    }
+                    case Texture.NEAREST_LINEAR_MIPNEAREST: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_NEAREST;
+                        break;
+                    }
+                    case Texture.LINEAR_NEAREST_MIPNEAREST: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_NEAREST;
+                        break;
+                    }
+                    case Texture.LINEAR_NEAREST_MIPLINEAR: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_LINEAR;
+                        break;
+                    }
+                    case Texture.NEAREST_NEAREST_MIPLINEAR: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_LINEAR;
+                        break;
+                    }
+                    case Texture.LINEAR_LINEAR_MIPLINEAR: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_LINEAR;
+                        break;
+                    }
+                    case Texture.LINEAR_LINEAR_MIPNEAREST: {
+                        sampler.magFilter = TextureMagFilter.LINEAR;
+                        sampler.minFilter = TextureMinFilter.LINEAR_MIPMAP_NEAREST;
+                        break;
+                    }
+                    case Texture.NEAREST_NEAREST_MIPNEAREST: {
+                        sampler.magFilter = TextureMagFilter.NEAREST;
+                        sampler.minFilter = TextureMinFilter.NEAREST_MIPMAP_NEAREST;
+                        break;
+                    }
+                }
+            }
+            return sampler;
+        }
+
+        private static _GetGLTFTextureWrapMode(wrapMode: number): TextureWrapMode {
+            switch (wrapMode) {
+                case Texture.WRAP_ADDRESSMODE: {
+                    return TextureWrapMode.REPEAT;
+                }
+                case Texture.CLAMP_ADDRESSMODE: {
+                    return TextureWrapMode.CLAMP_TO_EDGE;
+                }
+                case Texture.MIRROR_ADDRESSMODE: {
+                    return TextureWrapMode.MIRRORED_REPEAT;
+                }
+                default: {
+                    Tools.Error(`Unsupported Texture Wrap Mode ${wrapMode}!`);
+                    return TextureWrapMode.REPEAT;
+                }
+            }
+        }
+
+        private static _GetGLTFTextureWrapModesSampler(texture: BaseTexture): ISampler {
+            let wrapS = _GLTFMaterial._GetGLTFTextureWrapMode(texture instanceof Texture ? (texture as Texture).wrapU : Texture.WRAP_ADDRESSMODE);
+            let wrapT = _GLTFMaterial._GetGLTFTextureWrapMode(texture instanceof Texture ? (texture as Texture).wrapV : Texture.WRAP_ADDRESSMODE);
+
+            if (wrapS === TextureWrapMode.REPEAT && wrapT === TextureWrapMode.REPEAT) { // default wrapping mode in glTF, so omitting
+                return {};
+            }
+
+
+            return { wrapS: wrapS, wrapT: wrapT };
+        }
+
         /**
          * Convert a PBRMaterial (Specular/Glossiness) to Metallic Roughness factors
          * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
@@ -780,12 +887,18 @@ module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Nullable<_IPBRMetallicRoughness> {
+        private static _ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Nullable<_IPBRMetallicRoughness> {
             const specGloss: _IPBRSpecularGlossiness = {
                 diffuseColor: babylonPBRMaterial.albedoColor || Color3.White(),
                 specularColor: babylonPBRMaterial.reflectivityColor || Color3.White(),
                 glossiness: babylonPBRMaterial.microSurface || 1,
             };
+            let samplerIndex: Nullable<number> = null;
+            const sampler = this._GetGLTFTextureSampler(babylonPBRMaterial.albedoTexture);
+            if (sampler.magFilter != null && sampler.minFilter != null && sampler.wrapS != null && sampler.wrapT != null) {
+                samplers.push(sampler);
+                samplerIndex = samplers.length - 1;
+            }
             if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
                 Tools.Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
                 return null;
@@ -793,19 +906,20 @@ module BABYLON.GLTF2 {
 
             let metallicRoughnessFactors = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
 
+
             if (!metallicRoughnessFactors) {
                 metallicRoughnessFactors = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
             }
             else {
                 if (hasTextureCoords) {
                     if (metallicRoughnessFactors.baseColorTextureBase64) {
-                        const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                        const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.albedoTexture.coordinatesIndex, samplerIndex, imageData);
                         if (glTFBaseColorTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
                         }
                     }
                     if (metallicRoughnessFactors.metallicRoughnessTextureBase64) {
-                        const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, imageData);
+                        const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.reflectivityTexture.coordinatesIndex, samplerIndex, imageData);
                         if (glTFMRColorTexture != null) {
                             glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
                         }
@@ -825,7 +939,7 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
             let metallicRoughness: Nullable<_IPBRMetallicRoughness>;
             const glTFMaterial: IMaterial = {
@@ -834,10 +948,10 @@ module BABYLON.GLTF2 {
             const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
 
             if (useMetallicRoughness) {
-                metallicRoughness = this._ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+                metallicRoughness = this._ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
             }
             else {
-                metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+                metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
             }
             if (metallicRoughness) {
                 if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
@@ -864,13 +978,17 @@ module BABYLON.GLTF2 {
                 }
                 if (hasTextureCoords) {
                     if (babylonPBRMaterial.bumpTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, samplers, imageData);
                         if (glTFTexture) {
                             glTFMaterial.normalTexture = glTFTexture;
+                            if (babylonPBRMaterial.bumpTexture.level !== 1) {
+                                glTFMaterial.normalTexture.scale = babylonPBRMaterial.bumpTexture.level;
+                            }
                         }
+
                     }
                     if (babylonPBRMaterial.ambientTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
                         if (glTFTexture) {
                             let occlusionTexture: IMaterialOcclusionTextureInfo = {
                                 index: glTFTexture.index
@@ -884,7 +1002,7 @@ module BABYLON.GLTF2 {
                         }
                     }
                     if (babylonPBRMaterial.emissiveTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
                         if (glTFTexture != null) {
                             glTFMaterial.emissiveTexture = glTFTexture;
                         }
@@ -898,7 +1016,7 @@ module BABYLON.GLTF2 {
                     if (alphaMode) {
                         if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
                             glTFMaterial.alphaMode = alphaMode;
-                            if (alphaMode === MaterialAlphaMode.BLEND) {
+                            if (alphaMode === MaterialAlphaMode.MASK) {
                                 glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
                             }
                         }
@@ -915,6 +1033,7 @@ module BABYLON.GLTF2 {
             return pixels;
         }
 
+
         /**
          * Extracts a texture from a Babylon texture into file data and glTF data
          * @param babylonTexture Babylon texture to extract
@@ -924,7 +1043,28 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name and data
          * @return glTF texture info, or null if the texture format is not supported
          */
-        private static _ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+        private static _ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+            const sampler = _GLTFMaterial._GetGLTFTextureSampler(babylonTexture);
+            let samplerIndex: Nullable<number> = null;
+
+            //  if a pre-existing sampler with identical parameters exists, then reuse the previous sampler
+            let foundSamplerIndex: Nullable<number> = null;
+            for (let i = 0; i < samplers.length; ++i) {
+                let s = samplers[i];
+                if (s.minFilter === sampler.minFilter && s.magFilter === sampler.magFilter &&
+                    s.wrapS === sampler.wrapS && s.wrapT === sampler.wrapT) {
+                    foundSamplerIndex = i;
+                    break;
+                }
+            }
+            if (foundSamplerIndex == null) {
+                samplers.push(sampler);
+                samplerIndex = samplers.length - 1;
+            }
+            else {
+                samplerIndex = foundSamplerIndex;
+            }
+
             let textureName = "texture_" + (textures.length - 1).toString();
             let textureData = babylonTexture.getInternalTexture();
 
@@ -951,12 +1091,10 @@ module BABYLON.GLTF2 {
 
 
             const pixels = _GLTFMaterial.GetPixelsFromTexture(babylonTexture as Texture);
-
             const size = babylonTexture.getSize();
-
             const base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
 
-            return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, imageData);
+            return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, babylonTexture.coordinatesIndex, samplerIndex, imageData);
         }
 
         /**
@@ -969,12 +1107,16 @@ module BABYLON.GLTF2 {
          * @param imageData map of image data
          * @returns glTF texture info, or null if the texture format is not supported
          */
-        private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+        private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], texCoordIndex: number, samplerIndex: Nullable<number>, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
             let textureInfo: Nullable<ITextureInfo> = null;
 
-            const glTFTexture = {
-                source: images.length
+            const glTFTexture: ITexture = {
+                source: images.length,
+                name: textureName
             };
+            if (samplerIndex != null) {
+                glTFTexture.sampler = samplerIndex;
+            }
 
             const binStr = atob(base64Texture.split(',')[1]);
             let arrBuff = new ArrayBuffer(binStr.length);
@@ -989,30 +1131,29 @@ module BABYLON.GLTF2 {
                 const glTFImage: IImage = {
                     uri: textureName
                 }
-                let foundIndex: number = -1;
+                let foundIndex: Nullable<number> = null;
                 for (let i = 0; i < images.length; ++i) {
                     if (images[i].uri === textureName) {
                         foundIndex = i;
                         break;
                     }
                 }
-                if (foundIndex === -1) {
+                if (foundIndex == null) {
                     images.push(glTFImage);
                     glTFTexture.source = images.length - 1;
-                    textures.push({
-                        source: images.length - 1
-                    });
 
-                    textureInfo = {
-                        index: images.length - 1
-                    }
+
                 }
                 else {
                     glTFTexture.source = foundIndex;
 
-                    textureInfo = {
-                        index: foundIndex
-                    }
+                }
+                textures.push(glTFTexture);
+                textureInfo = {
+                    index: textures.length - 1
+                }
+                if (texCoordIndex) {
+                    textureInfo.texCoord = texCoordIndex;
                 }
             }
 

+ 42 - 15
serializers/src/glTF/2.0/babylon.glTFSerializer.ts

@@ -4,13 +4,13 @@ module BABYLON {
     /**
      * Holds a collection of exporter options and parameters
      */
-    export interface IExporterOptions {
+    export interface IExportOptions {
         /**
          * Function which indicates whether a babylon mesh should be exported or not
          * @param mesh source Babylon mesh. It is used to check whether it should be exported to glTF or not
          * @returns boolean, which indicates whether the mesh should be exported (true) or not (false)
          */
-        shouldExportMesh?(mesh: AbstractMesh): boolean;
+        shouldExportTransformNode?(mesh: TransformNode): boolean;
         /**
          * The sample rate to bake animation curves
          */
@@ -22,42 +22,69 @@ module BABYLON {
      */
     export class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format
+         * 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
          */
-        public static GLTF(scene: Scene, filePrefix: string, options?: IExporterOptions): Nullable<GLTFData> {
+        public static GLTF(scene: Scene, filePrefix: string, options?: IExportOptions): Nullable<GLTFData> {
+            if (scene.isReady()) {
             const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
             const gltfGenerator = new GLTF2._Exporter(scene, options);
-            if (scene.isReady) {
-                return gltfGenerator._generateGLTF(glTFPrefix);
+            return gltfGenerator._generateGLTF(glTFPrefix);
             }
             else {
-                Tools.Error("glTF Serializer: Scene is not ready!");
+                Tools.Error('glTF Serializer: Scene is not ready!');
                 return null;
-            } 
+            }
+        }
+
+        /**
+         * 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
+         * @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
+         */
+        public static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<Nullable<GLTFData>> {
+            return Promise.resolve(scene.whenReadyAsync()).then(() => {
+                return GLTF2Export.GLTF(scene, filePrefix, options);
+            });
         }
 
         /**
-         * Exports the geometry of the scene to .glb file format
+         * 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
          */
-        public static GLB(scene: Scene, filePrefix: string, options?: IExporterOptions): Nullable<GLTFData> {
-            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");        
+        public static GLB(scene: Scene, filePrefix: string, options?: IExportOptions): Nullable<GLTFData> {
+            if (scene.isReady()) {
+            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
             const gltfGenerator = new GLTF2._Exporter(scene, options);
-            if (scene.isReady) {
-                return gltfGenerator._generateGLB(glTFPrefix);
+            return gltfGenerator._generateGLB(glTFPrefix);
             }
             else {
-                Tools.Error("glTF Serializer: Scene is not ready!");
+                Tools.Error('glTF Serializer: Scene is not ready!');
                 return null;
-            }  
+            }
+        }
+
+        /**
+         * 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
+         * @param options Exporter options
+         * @returns Returns an object with a .glb filename as key and data as value
+         */
+        public static GLBAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<Nullable<GLTFData>> {
+            return Promise.resolve(scene.whenReadyAsync()).then(() => {
+                return GLTF2Export.GLB(scene, filePrefix, options);
+            });
         }
     }
 }

+ 59 - 8
serializers/src/glTF/2.0/babylon.glTFUtilities.ts

@@ -44,13 +44,13 @@ module BABYLON.GLTF2 {
         public static CreateAccessor(bufferviewIndex: number, name: string, type: AccessorType, componentType: AccessorComponentType, count: number, byteOffset: Nullable<number>, min: Nullable<number[]>, max: Nullable<number[]>): IAccessor {
             let accessor: IAccessor = { name: name, bufferView: bufferviewIndex, componentType: componentType, count: count, type: type };
 
-            if (min) {
+            if (min != null) {
                 accessor.min = min;
             }
-            if (max) {
+            if (max != null) {
                 accessor.max = max;
             }
-            if (byteOffset) {
+            if (byteOffset != null) {
                 accessor.byteOffset = byteOffset;
             }
 
@@ -78,7 +78,7 @@ module BABYLON.GLTF2 {
 
                     position = Vector3.FromArray(positions, indexOffset);
                     if (convertToRightHandedSystem) {
-                        _GLTFUtilities.GetRightHandedVector3FromRef(position);
+                        _GLTFUtilities.GetRightHandedPositionVector3FromRef(position);
                     }
                     vector = position.asArray();
 
@@ -102,7 +102,32 @@ module BABYLON.GLTF2 {
          * @param vector vector3 array
          * @returns right-handed Vector3
          */
-        public static GetRightHandedVector3(vector: Vector3): Vector3 {
+        public static GetRightHandedPositionVector3(vector: Vector3): Vector3 {
+            return new Vector3(vector.x, vector.y, -vector.z);
+        }
+
+        /**
+         * Converts a Vector3 to right-handed
+         * @param vector Vector3 to convert to right-handed
+         */
+        public static GetRightHandedPositionVector3FromRef(vector: Vector3) {
+            vector.z *= -1;
+        }
+
+        /**
+         * Converts a three element number array to right-handed
+         * @param vector number array to convert to right-handed
+         */
+        public static GetRightHandedPositionArray3FromRef(vector: number[]) {
+            vector[2] *= -1;
+        }
+
+        /**
+         * Converts a new right-handed Vector3
+         * @param vector vector3 array
+         * @returns right-handed Vector3
+         */
+        public static GetRightHandedNormalVector3(vector: Vector3): Vector3 {
             return new Vector3(vector.x, vector.y, -vector.z);
         }
 
@@ -110,11 +135,19 @@ module BABYLON.GLTF2 {
          * Converts a Vector3 to right-handed
          * @param vector Vector3 to convert to right-handed
          */
-        public static GetRightHandedVector3FromRef(vector: Vector3) {
+        public static GetRightHandedNormalVector3FromRef(vector: Vector3) {
             vector.z *= -1;
         }
 
         /**
+         * Converts a three element number array to right-handed
+         * @param vector number array to convert to right-handed
+         */
+        public static GetRightHandedNormalArray3FromRef(vector: number[]) {
+            vector[2] *= -1;
+        }
+
+        /**
          * Converts a Vector4 to right-handed
          * @param vector Vector4 to convert to right-handed
          */
@@ -124,12 +157,30 @@ module BABYLON.GLTF2 {
         }
 
         /**
+         * Converts a Vector4 to right-handed
+         * @param vector Vector4 to convert to right-handed
+         */
+        public static GetRightHandedArray4FromRef(vector: number[]) {
+            vector[2] *= -1;
+            vector[3] *= -1;
+        }
+
+        /**
          * Converts a Quaternion to right-handed
          * @param quaternion Source quaternion to convert to right-handed
          */
         public static GetRightHandedQuaternionFromRef(quaternion: Quaternion) {
-            quaternion.x *= -1;
-            quaternion.y *= -1;
+             quaternion.x *= -1;
+             quaternion.y *= -1;
+        }
+
+        /**
+         * Converts a Quaternion to right-handed
+         * @param quaternion Source quaternion to convert to right-handed
+         */
+        public static GetRightHandedQuaternionArrayFromRef(quaternion: number[]) {
+             quaternion[0] *= -1;
+             quaternion[1] *= -1;
         }
     }
 }

+ 1 - 0
serializers/src/tsconfig.json

@@ -3,6 +3,7 @@
     "experimentalDecorators": true,
     "module": "commonjs",
     "target": "es5",
+    "lib": ["dom", "es2015.promise", "es5"],
     "noImplicitAny": true,
     "noImplicitReturns": true,
     "noImplicitThis": true,

+ 2 - 2
tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts

@@ -139,7 +139,7 @@ describe('Babylon glTF Serializer', () => {
 
             const plane = BABYLON.Mesh.CreatePlane('plane', 120, scene);
             const babylonPBRMetalRoughMaterial = new BABYLON.PBRMetallicRoughnessMaterial('metalRoughMat', scene);
-            babylonPBRMetalRoughMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND;
+            babylonPBRMetalRoughMaterial.transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHATEST;
             const alphaCutoff = 0.8;
             babylonPBRMetalRoughMaterial.alphaCutOff = alphaCutoff;
 
@@ -156,7 +156,7 @@ describe('Babylon glTF Serializer', () => {
 
                 jsonData.materials.length.should.be.equal(2);
 
-                jsonData.materials[0].alphaMode.should.be.equal('BLEND');
+                jsonData.materials[0].alphaMode.should.be.equal('MASK');
 
                 jsonData.materials[0].alphaCutoff.should.be.equal(alphaCutoff);