浏览代码

Merge pull request #4128 from kcoley/serializerAnimation

Added TRS animation support to glTF serializer; refactorings
David Catuhe 7 年之前
父节点
当前提交
c15a27b284

+ 4 - 1
Tools/Gulp/config.json

@@ -13,6 +13,7 @@
             "../../dist/preview release/babylon.d.ts",
             "../../dist/preview release/gui/babylon.gui.d.ts",
             "../../dist/preview release/loaders/babylon.glTF2FileLoader.d.ts",
+            "../../dist/preview release/serializers/babylon.glTF2Serializer.d.ts",
             "../../dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts"
         ],
         "outputCustomConfigurationsDirectory": "../../dist/preview release/customConfigurations",
@@ -1592,7 +1593,9 @@
                     "../../serializers/src/glTF/2.0/babylon.glTFSerializer.ts",
                     "../../serializers/src/glTF/2.0/babylon.glTFExporter.ts",
                     "../../serializers/src/glTF/2.0/babylon.glTFData.ts",
-                    "../../serializers/src/glTF/2.0/babylon.glTFMaterial.ts"
+                    "../../serializers/src/glTF/2.0/babylon.glTFMaterial.ts",
+                    "../../serializers/src/glTF/2.0/babylon.glTFAnimation.ts",
+                    "../../serializers/src/glTF/2.0/babylon.glTFUtilities.ts"
                 ],
                 "output": "babylon.glTF2Serializer.js"
             }

+ 1 - 12
Tools/Gulp/gulp-validateTypedoc.js

@@ -274,9 +274,6 @@ Validate.prototype.validateTypedocNamespaces = function (namespaces) {
     for (var a in namespace.children) {
         containerNode = namespace.children[a];
 
-        // If comment contains @ignore then skip validation completely
-        if (Validate.hasTag(containerNode, 'ignore')) continue;
-
         // Account for undefined access modifiers.
         if (!containerNode.flags.isPublic &&
             !containerNode.flags.isPrivate &&
@@ -298,12 +295,10 @@ Validate.prototype.validateTypedocNamespaces = function (namespaces) {
                 "Missing text for " + containerNode.kindString + " : " + containerNode.name + " (id: " + containerNode.id + ")", Validate.position(containerNode));
         }
 
-        //if comment contains tag @ignoreChildren, then don't validate children
-        var validateChildren = !Validate.hasTag(containerNode, 'ignoreChildren');
         children = containerNode.children;
 
         //Validate Properties
-        if (validateChildren && children) {
+        if (children) {
             for (var b in children) {
                 childNode = children[b];
 
@@ -318,9 +313,6 @@ Validate.prototype.validateTypedocNamespaces = function (namespaces) {
                 // Validate Naming.
                 this.validateNaming(containerNode, childNode);
 
-                //if comment contains @ignore then skip validation completely
-                if (Validate.hasTag(childNode, 'ignore')) continue;
-
                 if (isPublic) {
                     tags = this.validateTags(childNode);
                     if (tags) {
@@ -340,9 +332,6 @@ Validate.prototype.validateTypedocNamespaces = function (namespaces) {
                         for (var c in signatures) {
                             signatureNode = signatures[c];
 
-                            //if node contains @ignore then skip validation completely
-                            if (Validate.hasTag(signatureNode, 'ignore')) continue;
-
                             if (isPublic) {
                                 if (!this.validateComment(signatureNode)) {
                                     this.errorCallback(containerNode.name,

+ 4 - 1
Tools/Gulp/gulpfile.js

@@ -884,7 +884,10 @@ gulp.task("modules", ["prepare-dependency-tree"], function () {
  */
 gulp.task("typedoc-generate", function () {
     return gulp
-        .src(["../../dist/preview release/babylon.d.ts", "../../dist/preview release/loaders/babylon.glTF2FileLoader.d.ts", "../../dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts"])
+        .src(["../../dist/preview release/babylon.d.ts", 
+            "../../dist/preview release/loaders/babylon.glTF2FileLoader.d.ts", 
+            "../../dist/preview release/serializers/babylon.glTF2Serializer.d.ts",
+            "../../dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts"])
         .pipe(typedoc({
             // TypeScript options (see typescript docs)
             mode: "modules",

+ 605 - 2
dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts

@@ -2,251 +2,854 @@
  * @ignoreChildren
  * @ignore
  */
+declare module "babylonjs-gltf2interface" {
+    export = BABYLON.GLTF2;
+}
+/**
+ * Moduel for glTF 2.0 Interface
+ */
 declare module BABYLON.GLTF2 {
+    /**
+     * The datatype of the components in the attribute
+     */
     const enum AccessorComponentType {
+        /**
+         * Byte
+         */
         BYTE = 5120,
+        /**
+         * Unsigned Byte
+         */
         UNSIGNED_BYTE = 5121,
+        /**
+         * Short
+         */
         SHORT = 5122,
+        /**
+         * Unsigned Short
+         */
         UNSIGNED_SHORT = 5123,
+        /**
+         * Unsigned Int
+         */
         UNSIGNED_INT = 5125,
+        /**
+         * Float
+         */
         FLOAT = 5126,
     }
+    /**
+     * Specifies if the attirbute is a scalar, vector, or matrix
+     */
     const enum AccessorType {
+        /**
+         * Scalar
+         */
         SCALAR = "SCALAR",
+        /**
+         * Vector2
+         */
         VEC2 = "VEC2",
+        /**
+         * Vector3
+         */
         VEC3 = "VEC3",
+        /**
+         * Vector4
+         */
         VEC4 = "VEC4",
+        /**
+         * Matrix2x2
+         */
         MAT2 = "MAT2",
+        /**
+         * Matrix3x3
+         */
         MAT3 = "MAT3",
+        /**
+         * Matrix4x4
+         */
         MAT4 = "MAT4",
     }
+    /**
+     * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates
+     */
     const enum AnimationChannelTargetPath {
+        /**
+         * Translation
+         */
         TRANSLATION = "translation",
+        /**
+         * Rotation
+         */
         ROTATION = "rotation",
+        /**
+         * Scale
+         */
         SCALE = "scale",
+        /**
+         * Weights
+         */
         WEIGHTS = "weights",
     }
+    /**
+     * Interpolation algorithm
+     */
     const enum AnimationSamplerInterpolation {
+        /**
+         * The animated values are linearly interpolated between keyframes
+         */
         LINEAR = "LINEAR",
+        /**
+         * The animated values remain constant to the output of the first keyframe, until the next keyframe
+         */
         STEP = "STEP",
+        /**
+         * The animation's interpolation is computed using a cubic spline with specified tangents
+         */
         CUBICSPLINE = "CUBICSPLINE",
     }
+    /**
+     * A camera's projection.  A node can reference a camera to apply a transform to place the camera in the scene
+     */
     const enum CameraType {
+        /**
+         * A perspective camera containing properties to create a perspective projection matrix
+         */
         PERSPECTIVE = "perspective",
+        /**
+         * An orthographic camera containing properties to create an orthographic projection matrix
+         */
         ORTHOGRAPHIC = "orthographic",
     }
+    /**
+     * The mime-type of the image
+     */
     const enum ImageMimeType {
+        /**
+         * JPEG Mime-type
+         */
         JPEG = "image/jpeg",
+        /**
+         * PNG Mime-type
+         */
         PNG = "image/png",
     }
+    /**
+     * The alpha rendering mode of the material
+     */
     const enum MaterialAlphaMode {
+        /**
+         * The alpha value is ignored and the rendered output is fully opaque
+         */
         OPAQUE = "OPAQUE",
+        /**
+         * The rendered output is either fully opaque or fully transparent depending on the alpha value and the specified alpha cutoff value
+         */
         MASK = "MASK",
+        /**
+         * The alpha value is used to composite the source and destination areas. The rendered output is combined with the background using the normal painting operation (i.e. the Porter and Duff over operator)
+         */
         BLEND = "BLEND",
     }
+    /**
+     * The type of the primitives to render
+     */
     const enum MeshPrimitiveMode {
+        /**
+         * Points
+         */
         POINTS = 0,
+        /**
+         * Lines
+         */
         LINES = 1,
+        /**
+         * Line Loop
+         */
         LINE_LOOP = 2,
+        /**
+         * Line Strip
+         */
         LINE_STRIP = 3,
+        /**
+         * Triangles
+         */
         TRIANGLES = 4,
+        /**
+         * Triangle Strip
+         */
         TRIANGLE_STRIP = 5,
+        /**
+         * Triangle Fan
+         */
         TRIANGLE_FAN = 6,
     }
+    /**
+     * Magnification filter.  Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR)
+     */
     const enum TextureMagFilter {
+        /**
+         * Nearest
+         */
         NEAREST = 9728,
+        /**
+         * Linear
+         */
         LINEAR = 9729,
     }
+    /**
+     * Minification filter.  All valid values correspond to WebGL enums
+     */
     const enum TextureMinFilter {
+        /**
+         * Nearest
+         */
         NEAREST = 9728,
+        /**
+         * Linear
+         */
         LINEAR = 9729,
+        /**
+         * Nearest Mip-Map Nearest
+         */
         NEAREST_MIPMAP_NEAREST = 9984,
+        /**
+         * Linear Mipmap Nearest
+         */
         LINEAR_MIPMAP_NEAREST = 9985,
+        /**
+         * Nearest Mipmap Linear
+         */
         NEAREST_MIPMAP_LINEAR = 9986,
+        /**
+         * Linear Mipmap Linear
+         */
         LINEAR_MIPMAP_LINEAR = 9987,
     }
+    /**
+     * S (U) wrapping mode.  All valid values correspond to WebGL enums
+     */
     const enum TextureWrapMode {
+        /**
+         * Clamp to Edge
+         */
         CLAMP_TO_EDGE = 33071,
+        /**
+         * Mirrored Repeat
+         */
         MIRRORED_REPEAT = 33648,
+        /**
+         * Repeat
+         */
         REPEAT = 10497,
     }
+    /**
+     * glTF Property
+     */
     interface IProperty {
+        /**
+         * Dictionary object with extension-specific objects
+         */
         extensions?: {
             [key: string]: any;
         };
+        /**
+         * Application-Specific data 
+         */
         extras?: any;
     }
+    /**
+     * glTF Child of Root Property
+     */
     interface IChildRootProperty extends IProperty {
+        /**
+         * The user-defined name of this object
+         */
         name?: string;
     }
+    /**
+     * Indices of those attributes that deviate from their initialization value
+     */
     interface IAccessorSparseIndices extends IProperty {
+        /**
+         * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target
+         */
         bufferView: number;
+        /**
+         * The offset relative to the start of the bufferView in bytes. Must be aligned
+         */
         byteOffset?: number;
+        /**
+         * The indices data type.  Valid values correspond to WebGL enums: 5121 (UNSIGNED_BYTE), 5123 (UNSIGNED_SHORT), 5125 (UNSIGNED_INT)
+         */
         componentType: AccessorComponentType;
     }
+    /**
+     * Array of size accessor.sparse.count times number of components storing the displaced accessor attributes pointed by accessor.sparse.indices
+     */
     interface IAccessorSparseValues extends IProperty {
+        /**
+         * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target
+         */
         bufferView: number;
+        /**
+         * The offset relative to the start of the bufferView in bytes. Must be aligned
+         */
         byteOffset?: number;
     }
+    /**
+     * Sparse storage of attributes that deviate from their initialization value
+     */
     interface IAccessorSparse extends IProperty {
+        /**
+         * The number of attributes encoded in this sparse accessor
+         */
         count: number;
+        /**
+         * Index array of size count that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase
+         */
         indices: IAccessorSparseIndices;
+        /**
+         * Array of size count times number of components, storing the displaced accessor attributes pointed by indices. Substituted values must have the same componentType and number of components as the base accessor
+         */
         values: IAccessorSparseValues;
     }
+    /**
+     * A typed view into a bufferView.  A bufferView contains raw binary data.  An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's vertexAttribPointer() defines an attribute in a buffer
+     */
     interface IAccessor extends IChildRootProperty {
+        /**
+         * The index of the bufferview
+         */
         bufferView?: number;
+        /**
+         * The offset relative to the start of the bufferView in bytes
+         */
         byteOffset?: number;
+        /**
+         * The datatype of components in the attribute
+         */
         componentType: AccessorComponentType;
+        /**
+         * Specifies whether integer data values should be normalized
+         */
         normalized?: boolean;
+        /**
+         * The number of attributes referenced by this accessor
+         */
         count: number;
+        /**
+         * Specifies if the attribute is a scalar, vector, or matrix
+         */
         type: AccessorType;
+        /**
+         * Maximum value of each component in this attribute
+         */
         max?: number[];
+        /**
+         * Minimum value of each component in this attribute
+         */
         min?: number[];
+        /**
+         * Sparse storage of attributes that deviate from their initialization value
+         */
         sparse?: IAccessorSparse;
     }
+    /**
+     * Targets an animation's sampler at a node's property
+     */
     interface IAnimationChannel extends IProperty {
+        /**
+         * The index of a sampler in this animation used to compute the value for the target
+         */
         sampler: number;
+        /**
+         * The index of the node and TRS property to target
+         */
         target: IAnimationChannelTarget;
     }
+    /**
+     * The index of the node and TRS property that an animation channel targets
+     */
     interface IAnimationChannelTarget extends IProperty {
+        /**
+         * The index of the node to target
+         */
         node: number;
+        /**
+         * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates
+         */
         path: AnimationChannelTargetPath;
     }
+    /**
+     * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target)
+     */
     interface IAnimationSampler extends IProperty {
+        /**
+         * The index of an accessor containing keyframe input values, e.g., time
+         */
         input: number;
+        /**
+         * Interpolation algorithm
+         */
         interpolation?: AnimationSamplerInterpolation;
+        /**
+         * The index of an accessor, containing keyframe output values
+         */
         output: number;
     }
+    /**
+     * A keyframe animation
+     */
     interface IAnimation extends IChildRootProperty {
+        /**
+         * An array of channels, each of which targets an animation's sampler at a node's property
+         */
         channels: IAnimationChannel[];
+        /**
+         * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target)
+         */
         samplers: IAnimationSampler[];
     }
+    /**
+     * Metadata about the glTF asset
+     */
     interface IAsset extends IChildRootProperty {
+        /**
+         * A copyright message suitable for display to credit the content creator
+         */
         copyright?: string;
+        /**
+         * Tool that generated this glTF model.  Useful for debugging
+         */
         generator?: string;
+        /**
+         * The glTF version that this asset targets
+         */
         version: string;
+        /**
+         * The minimum glTF version that this asset targets
+         */
         minVersion?: string;
     }
+    /**
+     * A buffer points to binary geometry, animation, or skins
+     */
     interface IBuffer extends IChildRootProperty {
+        /**
+         * The uri of the buffer.  Relative paths are relative to the .gltf file.  Instead of referencing an external file, the uri can also be a data-uri
+         */
         uri?: string;
+        /**
+         * The length of the buffer in bytes
+         */
         byteLength: number;
     }
+    /**
+     * A view into a buffer generally representing a subset of the buffer
+     */
     interface IBufferView extends IChildRootProperty {
+        /**
+         * The index of the buffer
+         */
         buffer: number;
+        /**
+         * The offset into the buffer in bytes
+         */
         byteOffset?: number;
+        /**
+         * The lenth of the bufferView in bytes
+         */
         byteLength: number;
+        /**
+         * The stride, in bytes
+         */
         byteStride?: number;
     }
+    /**
+     * An orthographic camera containing properties to create an orthographic projection matrix
+     */
     interface ICameraOrthographic extends IProperty {
+        /**
+         * The floating-point horizontal magnification of the view. Must not be zero
+         */
         xmag: number;
+        /**
+         * The floating-point vertical magnification of the view. Must not be zero
+         */
         ymag: number;
+        /**
+         * The floating-point distance to the far clipping plane. zfar must be greater than znear
+         */
         zfar: number;
+        /**
+         * The floating-point distance to the near clipping plane
+         */
         znear: number;
     }
+    /**
+     * A perspective camera containing properties to create a perspective projection matrix
+     */
     interface ICameraPerspective extends IProperty {
-        aspectRatio: number;
+        /**
+         * The floating-point aspect ratio of the field of view
+         */
+        aspectRatio?: number;
+        /**
+         * The floating-point vertical field of view in radians
+         */
         yfov: number;
-        zfar: number;
+        /**
+         * The floating-point distance to the far clipping plane
+         */
+        zfar?: number;
+        /**
+         * The floating-point distance to the near clipping plane
+         */
         znear: number;
     }
+    /**
+     * A camera's projection.  A node can reference a camera to apply a transform to place the camera in the scene
+     */
     interface ICamera extends IChildRootProperty {
+        /**
+         * An orthographic camera containing properties to create an orthographic projection matrix
+         */
         orthographic?: ICameraOrthographic;
+        /**
+         * A perspective camera containing properties to create a perspective projection matrix
+         */
         perspective?: ICameraPerspective;
+        /**
+         * Specifies if the camera uses a perspective or orthographic projection
+         */
         type: CameraType;
     }
+    /**
+     * Image data used to create a texture. Image can be referenced by URI or bufferView index. mimeType is required in the latter case
+     */
     interface IImage extends IChildRootProperty {
+        /**
+         * The uri of the image.  Relative paths are relative to the .gltf file.  Instead of referencing an external file, the uri can also be a data-uri.  The image format must be jpg or png
+         */
         uri?: string;
+        /**
+         * The image's MIME type
+         */
         mimeType?: ImageMimeType;
+        /**
+         * The index of the bufferView that contains the image. Use this instead of the image's uri property
+         */
         bufferView?: number;
     }
+    /**
+     * Material Normal Texture Info
+     */
     interface IMaterialNormalTextureInfo extends ITextureInfo {
+        /**
+         * The scalar multiplier applied to each normal vector of the normal texture
+         */
         scale?: number;
     }
+    /**
+     * Material Occlusion Texture Info
+     */
     interface IMaterialOcclusionTextureInfo extends ITextureInfo {
+        /**
+         * A scalar multiplier controlling the amount of occlusion applied
+         */
         strength?: number;
     }
+    /**
+     * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology
+     */
     interface IMaterialPbrMetallicRoughness {
+        /**
+         * The material's base color factor
+         */
         baseColorFactor?: number[];
+        /**
+         * The base color texture
+         */
         baseColorTexture?: ITextureInfo;
+        /**
+         * The metalness of the material
+         */
         metallicFactor?: number;
+        /**
+         * The roughness of the material
+         */
         roughnessFactor?: number;
+        /**
+         * The metallic-roughness texture
+         */
         metallicRoughnessTexture?: ITextureInfo;
     }
+    /**
+     * The material appearance of a primitive
+     */
     interface IMaterial extends IChildRootProperty {
+        /**
+         * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of pbrMetallicRoughness apply
+         */
         pbrMetallicRoughness?: IMaterialPbrMetallicRoughness;
+        /**
+         * The normal map texture
+         */
         normalTexture?: IMaterialNormalTextureInfo;
+        /**
+         * The occlusion map texture
+         */
         occlusionTexture?: IMaterialOcclusionTextureInfo;
+        /**
+         * The emissive map texture
+         */
         emissiveTexture?: ITextureInfo;
+        /**
+         * The RGB components of the emissive color of the material. These values are linear. If an emissiveTexture is specified, this value is multiplied with the texel values
+         */
         emissiveFactor?: number[];
+        /**
+         * The alpha rendering mode of the material
+         */
         alphaMode?: MaterialAlphaMode;
+        /**
+         * The alpha cutoff value of the material
+         */
         alphaCutoff?: number;
+        /**
+         * Specifies whether the material is double sided
+         */
         doubleSided?: boolean;
     }
+    /**
+     * Geometry to be rendered with the given material
+     */
     interface IMeshPrimitive extends IProperty {
+        /**
+         * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data
+         */
         attributes: {
             [name: string]: number;
         };
+        /**
+         * The index of the accessor that contains the indices
+         */
         indices?: number;
+        /**
+         * The index of the material to apply to this primitive when rendering
+         */
         material?: number;
+        /**
+         * The type of primitives to render. All valid values correspond to WebGL enums
+         */
         mode?: MeshPrimitiveMode;
+        /**
+         * An array of Morph Targets, each  Morph Target is a dictionary mapping attributes (only POSITION, NORMAL, and TANGENT supported) to their deviations in the Morph Target
+         */
         targets?: {
             [name: string]: number;
         }[];
     }
+    /**
+     * A set of primitives to be rendered.  A node can contain one mesh.  A node's transform places the mesh in the scene
+     */
     interface IMesh extends IChildRootProperty {
+        /**
+         * An array of primitives, each defining geometry to be rendered with a material
+         */
         primitives: IMeshPrimitive[];
+        /**
+         * Array of weights to be applied to the Morph Targets
+         */
         weights?: number[];
     }
+    /**
+     * A node in the node hierarchy
+     */
     interface INode extends IChildRootProperty {
+        /**
+         * The index of the camera referenced by this node
+         */
         camera?: number;
+        /**
+         * The indices of this node's children
+         */
         children?: number[];
+        /**
+         * The index of the skin referenced by this node
+         */
         skin?: number;
+        /**
+         * A floating-point 4x4 transformation matrix stored in column-major order
+         */
         matrix?: number[];
+        /**
+         * The index of the mesh in this node
+         */
         mesh?: number;
+        /**
+         * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar
+         */
         rotation?: number[];
+        /**
+         * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes
+         */
         scale?: number[];
+        /**
+         * The node's translation along the x, y, and z axes
+         */
         translation?: number[];
+        /**
+         * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh
+         */
         weights?: number[];
     }
+    /**
+     * Texture sampler properties for filtering and wrapping modes
+     */
     interface ISampler extends IChildRootProperty {
+        /**
+         * Magnification filter.  Valid values correspond to WebGL enums: 9728 (NEAREST) and 9729 (LINEAR)
+         */
         magFilter?: TextureMagFilter;
+        /**
+         * Minification filter.  All valid values correspond to WebGL enums
+         */
         minFilter?: TextureMinFilter;
+        /**
+         * S (U) wrapping mode.  All valid values correspond to WebGL enums
+         */
         wrapS?: TextureWrapMode;
+        /**
+         * T (V) wrapping mode.  All valid values correspond to WebGL enums
+         */
         wrapT?: TextureWrapMode;
     }
+    /**
+     * The root nodes of a scene
+     */
     interface IScene extends IChildRootProperty {
+        /**
+         * The indices of each root node
+         */
         nodes: number[];
     }
+    /**
+     * Joints and matrices defining a skin
+     */
     interface ISkin extends IChildRootProperty {
+        /**
+         * The index of the accessor containing the floating-point 4x4 inverse-bind matrices.  The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied
+         */
         inverseBindMatrices?: number;
+        /**
+         * The index of the node used as a skeleton root. When undefined, joints transforms resolve to scene root
+         */
         skeleton?: number;
+        /**
+         * Indices of skeleton nodes, used as joints in this skin.  The array length must be the same as the count property of the inverseBindMatrices accessor (when defined)
+         */
         joints: number[];
     }
+    /**
+     * A texture and its sampler
+     */
     interface ITexture extends IChildRootProperty {
+        /**
+         * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used
+         */
         sampler?: number;
+        /**
+         * The index of the image used by this texture
+         */
         source: number;
     }
+    /**
+     * Reference to a texture
+     */
     interface ITextureInfo {
+        /**
+         * The index of the texture
+         */
         index: number;
+        /**
+         * The set index of texture's TEXCOORD attribute used for texture coordinate mapping
+         */
         texCoord?: number;
     }
+    /**
+     * The root object for a glTF asset
+     */
     interface IGLTF extends IProperty {
+        /**
+         * An array of accessors. An accessor is a typed view into a bufferView
+         */
         accessors?: IAccessor[];
+        /**
+         * An array of keyframe animations
+         */
         animations?: IAnimation[];
+        /**
+         * Metadata about the glTF asset
+         */
         asset: IAsset;
+        /**
+         * An array of buffers.  A buffer points to binary geometry, animation, or skins
+         */
         buffers?: IBuffer[];
+        /**
+         * An array of bufferViews.  A bufferView is a view into a buffer generally representing a subset of the buffer
+         */
         bufferViews?: IBufferView[];
+        /**
+         * An array of cameras
+         */
         cameras?: ICamera[];
+        /**
+         * Names of glTF extensions used somewhere in this asset
+         */
         extensionsUsed?: string[];
+        /**
+         * Names of glTF extensions required to properly load this asset
+         */
         extensionsRequired?: string[];
+        /**
+         * An array of images.  An image defines data used to create a texture
+         */
         images?: IImage[];
+        /**
+         * An array of materials.  A material defines the appearance of a primitive
+         */
         materials?: IMaterial[];
+        /**
+         * An array of meshes.  A mesh is a set of primitives to be rendered
+         */
         meshes?: IMesh[];
+        /**
+         * An array of nodes
+         */
         nodes?: INode[];
+        /**
+         * An array of samplers.  A sampler contains properties for texture filtering and wrapping modes
+         */
         samplers?: ISampler[];
+        /**
+         * The index of the default scene
+         */
         scene?: number;
+        /**
+         * An array of scenes
+         */
         scenes?: IScene[];
+        /**
+         * An array of skins.  A skin is defined by joints and matrices
+         */
         skins?: ISkin[];
+        /**
+         * An array of textures
+         */
         textures?: ITexture[];
     }
 }

+ 710 - 0
serializers/src/glTF/2.0/babylon.glTFAnimation.ts

@@ -0,0 +1,710 @@
+/// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
+
+module BABYLON.GLTF2 {
+    /**
+     * @hidden
+     * Interface to store animation data.
+     */
+    export interface _IAnimationData {
+        /**
+         * Keyframe data.
+         */
+        inputs: number[],
+        /**
+         * Value data.
+         */
+        outputs: number[][],
+        /**
+         * Animation interpolation data.
+         */
+        samplerInterpolation: AnimationSamplerInterpolation,
+        /**
+         * Minimum keyframe value.
+         */
+        inputsMin: number,
+        /**
+         * Maximum keyframe value.
+         */
+        inputsMax: number,
+    }
+
+    /**
+     * @hidden
+     */
+    export interface _IAnimationInfo {
+        /**
+         * The target channel for the animation
+         */
+        animationChannelTargetPath: AnimationChannelTargetPath,
+        /**
+         * The glTF accessor type for the data.
+         */
+        dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4,
+        /**
+         * Specifies if quaternions should be used.
+         */
+        useQuaternion: boolean
+    }
+
+    /**
+     * @hidden
+     * Enum for handling in tangent and out tangent.
+     */
+    enum _TangentType {
+        /**
+         * Specifies that input tangents are used.
+         */
+        INTANGENT,
+        /**
+         * Specifies that output tangents are used.
+         */
+        OUTTANGENT
+    }
+    /**
+     * @hidden
+     * Utility class for generating glTF animation data from BabylonJS.
+     */
+    export class _GLTFAnimation {
+        /**
+         * 
+         * Creates glTF channel animation from BabylonJS animation.
+         * @param babylonMesh - 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> {
+            const inputs: number[] = [];
+            const outputs: number[][] = [];
+            const keyFrames = animation.getKeys();
+            const minMaxKeyFrames = _GLTFAnimation.calculateMinMaxKeyFrames(keyFrames);
+            const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion);
+            const frameDelta = minMaxKeyFrames.max - minMaxKeyFrames.min;
+
+            const interpolation = interpolationOrBake.interpolationType;
+            const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation;
+
+            if (shouldBakeAnimation) {
+                _GLTFAnimation._CreateBakedAnimation(babylonMesh, 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);
+
+                }
+                else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) {
+                    _GLTFAnimation._CreateCubicSplineAnimation(babylonMesh, 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);
+                }
+            }
+
+            if (inputs.length && outputs.length) {
+                const result: _IAnimationData = {
+                    inputs: inputs,
+                    outputs: outputs,
+                    samplerInterpolation: interpolation,
+                    inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond),
+                    inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond)
+                }
+
+                return result;
+            }
+
+            return null;
+        }
+
+        private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> {
+            let animationChannelTargetPath: Nullable<AnimationChannelTargetPath> = null;
+            let dataAccessorType = AccessorType.VEC3;
+            let useQuaternion: boolean = false;
+            let property = animation.targetProperty.split('.');
+            switch (property[0]) {
+                case 'scaling': {
+                    animationChannelTargetPath = AnimationChannelTargetPath.SCALE;
+                    break;
+                }
+                case 'position': {
+                    animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION;
+                    break;
+                }
+                case 'rotation': {
+                    dataAccessorType = AccessorType.VEC4;
+                    animationChannelTargetPath = AnimationChannelTargetPath.ROTATION;
+                    break;
+                }
+                case 'rotationQuaternion': {
+                    dataAccessorType = AccessorType.VEC4;
+                    useQuaternion = true;
+                    animationChannelTargetPath = AnimationChannelTargetPath.ROTATION;
+                    break;
+                }
+                default: {
+                    Tools.Error(`Unsupported animatable property ${property[0]}`);
+                }
+            }
+            if (animationChannelTargetPath) {
+                return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion };
+            }
+            else {
+                Tools.Error('animation channel target path and data accessor type could be deduced');
+            }
+            return null;
+        }
+
+        /**
+         * 
+         * @param babylonMesh 
+         * @param runtimeGLTFAnimation 
+         * @param idleGLTFAnimations 
+         * @param nodeMap 
+         * @param nodes 
+         * @param binaryWriter 
+         * @param bufferViews 
+         * @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) {
+            let glTFAnimation: IAnimation;
+            if (babylonMesh.animations) {
+                babylonMesh.animations.forEach(function (animation) {
+                    let animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation);
+                    if (animationInfo) {
+                        glTFAnimation = {
+                            name: animation.name,
+                            samplers: [],
+                            channels: []
+                        }
+                        _GLTFAnimation.AddAnimation(`${animation.name}`,
+                            animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation,
+                            babylonMesh,
+                            animation,
+                            animationInfo.dataAccessorType,
+                            animationInfo.animationChannelTargetPath,
+                            nodeMap,
+                            binaryWriter,
+                            bufferViews,
+                            accessors,
+                            convertToRightHandedSystem,
+                            animationInfo.useQuaternion,
+                            animationSampleRate
+                        );
+                        if (glTFAnimation.samplers.length && glTFAnimation.channels.length) {
+                            idleGLTFAnimations.push(glTFAnimation);
+                        }
+                    }
+                });
+            }
+        }
+
+        /**
+         * 
+         * @param babylonScene 
+         * @param glTFAnimations 
+         * @param nodeMap 
+         * @param nodes 
+         * @param binaryWriter 
+         * @param bufferViews 
+         * @param accessors 
+         * @param convertToRightHandedSystem 
+         */
+        public static _CreateNodeAnimationFromAnimationGroups(babylonScene: Scene, glTFAnimations: IAnimation[], nodeMap: { [key: number]: number }, nodes: INode[], binaryWriter: _BinaryWriter, bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHandedSystem: boolean, animationSampleRate: number) {
+            let glTFAnimation: IAnimation;
+            if (babylonScene.animationGroups) {
+                let animationGroups = babylonScene.animationGroups;
+
+                animationGroups.forEach(function (animationGroup) {
+                    glTFAnimation = {
+                        name: animationGroup.name,
+                        channels: [],
+                        samplers: []
+                    }
+                    animationGroup.targetedAnimations.forEach(function (targetAnimation) {
+                        let target = targetAnimation.target;
+                        let animation = targetAnimation.animation;
+                        if (target instanceof Mesh) {
+                            let animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation);
+                            if (animationInfo) {
+                                let babylonMesh = target as Mesh;
+                                _GLTFAnimation.AddAnimation(`${animation.name}`,
+                                    glTFAnimation,
+                                    babylonMesh,
+                                    animation,
+                                    animationInfo.dataAccessorType,
+                                    animationInfo.animationChannelTargetPath,
+                                    nodeMap,
+                                    binaryWriter,
+                                    bufferViews,
+                                    accessors,
+                                    convertToRightHandedSystem,
+                                    animationInfo.useQuaternion,
+                                    animationSampleRate
+                                );
+                            }
+                        }
+                    });
+                    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);
+            let bufferView: IBufferView;
+            let accessor: IAccessor;
+            let keyframeAccessorIndex: number;
+            let dataAccessorIndex: number;
+            let outputLength: number;
+            let animationSampler: IAnimationSampler;
+            let animationChannel: IAnimationChannel;
+
+            if (animationData) {
+                let nodeIndex = nodeMap[babylonMesh.uniqueId];
+
+                // Creates buffer view and accessor for key frames.
+                let byteLength = animationData.inputs.length * 4;
+                bufferView = _GLTFUtilities.CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name}  keyframe data view`);
+                bufferViews.push(bufferView);
+
+                animationData.inputs.forEach(function (input) {
+                    binaryWriter.setFloat32(input);
+                });
+
+                accessor = _GLTFUtilities.CreateAccessor(bufferViews.length - 1, `${name}  keyframes`, AccessorType.SCALAR, AccessorComponentType.FLOAT, animationData.inputs.length, null, [animationData.inputsMin], [animationData.inputsMax]);
+                accessors.push(accessor);
+                keyframeAccessorIndex = accessors.length - 1;
+
+                // create bufferview and accessor for keyed values.
+                outputLength = animationData.outputs.length;
+                byteLength = dataAccessorType === AccessorType.VEC3 ? animationData.outputs.length * 12 : animationData.outputs.length * 16;
+
+                // check for in and out tangents
+                bufferView = _GLTFUtilities.CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name}  data view`);
+                bufferViews.push(bufferView);
+
+                animationData.outputs.forEach(function (output) {
+                    output.forEach(function (entry) {
+                        binaryWriter.setFloat32(entry);
+                    });
+                });
+
+                accessor = _GLTFUtilities.CreateAccessor(bufferViews.length - 1, `${name}  data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null);
+                accessors.push(accessor);
+                dataAccessorIndex = accessors.length - 1;
+
+                // create sampler
+                animationSampler = {
+                    interpolation: animationData.samplerInterpolation,
+                    input: keyframeAccessorIndex,
+                    output: dataAccessorIndex
+                }
+                glTFAnimation.samplers.push(animationSampler);
+
+                // create channel
+                animationChannel = {
+                    sampler: glTFAnimation.samplers.length - 1,
+                    target: {
+                        node: nodeIndex,
+                        path: animationChannelTargetPath
+                    }
+                }
+                glTFAnimation.channels.push(animationChannel);
+            }
+        }
+
+        /**
+         * Create a baked animation
+         * @param babylonMesh BabylonJS mesh
+         * @param animation BabylonJS animation corresponding to the BabylonJS mesh
+         * @param animationChannelTargetPath animation target channel
+         * @param minFrame minimum animation frame
+         * @param maxFrame maximum animation frame
+         * @param fps frames per second of the animation
+         * @param inputs input key frames of the animation
+         * @param outputs output key frame data of the animation
+         * @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) {
+            let value: number | Vector3 | Quaternion;
+            let quaternionCache: Quaternion = Quaternion.Identity();
+            let previousTime: Nullable<number> = null;
+            let time: number;
+            let maxUsedFrame: Nullable<number> = null;
+            let currKeyFrame: Nullable<IAnimationKey> = null;
+            let nextKeyFrame: Nullable<IAnimationKey> = null;
+            let prevKeyFrame: Nullable<IAnimationKey> = null;
+            let endFrame: Nullable<number> = null;
+            minMaxFrames.min = Tools.FloatRound(minFrame / fps);
+
+            let keyFrames = animation.getKeys();
+
+            for (let i = 0, length = keyFrames.length; i < length; ++i) {
+                endFrame = null;
+                currKeyFrame = keyFrames[i];
+
+                if (i + 1 < length) {
+                    nextKeyFrame = keyFrames[i + 1];
+                    if (currKeyFrame.value.equals(nextKeyFrame.value)) {
+                        if (i === 0) { // set the first frame to itself
+                            endFrame = currKeyFrame.frame;
+                        }
+                        else {
+                            continue;
+                        }
+                    }
+                    else {
+                        endFrame = nextKeyFrame.frame;
+                    }
+                }
+                else {
+                    // at the last key frame
+                    prevKeyFrame = keyFrames[i - 1];
+                    if (currKeyFrame.value.equals(prevKeyFrame.value)) {
+                        continue;
+                    }
+                    else {
+                        endFrame = maxFrame;
+                    }
+                }
+                if (endFrame) {
+                    for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) {
+                        time = Tools.FloatRound(f / fps);
+                        if (time === previousTime) {
+                            continue;
+                        }
+                        previousTime = time;
+                        maxUsedFrame = time;
+                        value = animation._interpolate(f, 0, undefined, animation.loopMode);
+
+                        _GLTFAnimation._SetInterpolatedValue(babylonMesh, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, convertToRightHandedSystem, useQuaternion);
+                    }
+                }
+            }
+            if (maxUsedFrame) {
+                minMaxFrames.max = maxUsedFrame;
+            }
+        }
+
+        private static _ConvertFactorToVector3OrQuaternion(factor: number, babylonMesh: Mesh, 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);
+            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 'y': {
+                        value[componentName] = (convertToRightHandedSystem && useQuaternion) ? -factor : factor;
+                        break;
+                    }
+                    case 'z': {
+                        value[componentName] = (convertToRightHandedSystem && !useQuaternion && !(animationChannelTargetPath === AnimationChannelTargetPath.SCALE)) ? -factor : factor;
+                        break;
+                    }
+                    case 'w': {
+                        (value as Quaternion).w = factor;
+                        break;
+                    }
+                    default: {
+                        Tools.Error(`glTFAnimation: Unsupported component type "${componentName}" for scale animation!`);
+                    }
+                }
+            }
+
+            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) {
+            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);
+            }
+            if (value) {
+                if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
+                    if (useQuaternion) {
+                        quaternionCache = value as Quaternion;
+                    }
+                    else {
+                        cacheValue = value as Vector3;
+                        Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache);
+                    }
+                    if (convertToRightHandedSystem) {
+                        quaternionCache.x *= -1;
+                        quaternionCache.y *= -1;
+                        outputs.push(quaternionCache.asArray());
+                    }
+                }
+                else {
+                    cacheValue = value as Vector3;
+                    if (convertToRightHandedSystem && (animationChannelTargetPath !== AnimationChannelTargetPath.SCALE)) {
+                        cacheValue.z *= -1;
+                    }
+
+                    outputs.push(cacheValue.asArray());
+                }
+            }
+        }
+
+        /**
+         * Creates linear animation from the animation key frames
+         * @param babylonMesh 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
+         * @param inputs Array to store the key frame times
+         * @param outputs Array to store the key frame data
+         * @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) {
+                inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
+                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonMesh, convertToRightHandedSystem, useQuaternion);
+            });
+        }
+
+        /**
+         * Creates cubic spline animation from the animation key frames
+         * @param babylonMesh 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
+         * @param inputs Array to store the key frame times
+         * @param outputs Array to store the key frame data
+         * @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) {
+            animation.getKeys().forEach(function (keyFrame) {
+                inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds.
+                _GLTFAnimation.AddSplineTangent(
+                    _TangentType.INTANGENT,
+                    outputs,
+                    animationChannelTargetPath,
+                    AnimationSamplerInterpolation.CUBICSPLINE,
+                    keyFrame,
+                    frameDelta,
+                    useQuaternion,
+                    convertToRightHandedSystem
+                );
+                _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonMesh, convertToRightHandedSystem, useQuaternion);
+
+                _GLTFAnimation.AddSplineTangent(
+                    _TangentType.OUTTANGENT,
+                    outputs,
+                    animationChannelTargetPath,
+                    AnimationSamplerInterpolation.CUBICSPLINE,
+                    keyFrame,
+                    frameDelta,
+                    useQuaternion,
+                    convertToRightHandedSystem
+                );
+            });
+        }
+
+        private static _GetBasePositionRotationOrScale(babylonMesh: Mesh, animationChannelTargetPath: AnimationChannelTargetPath, convertToRightHandedSystem: boolean, useQuaternion: boolean) {
+            let basePositionRotationOrScale: number[];
+            if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
+                if (useQuaternion) {
+                    if (babylonMesh.rotationQuaternion) {
+                        basePositionRotationOrScale = babylonMesh.rotationQuaternion.asArray();
+                        if (convertToRightHandedSystem) {
+                            basePositionRotationOrScale[0] *= -1;
+                            basePositionRotationOrScale[1] *= -1;
+                        }
+                    }
+                    else {
+                        basePositionRotationOrScale = BABYLON.Quaternion.Identity().asArray();
+                    }
+                }
+                else {
+                    basePositionRotationOrScale = babylonMesh.rotation.asArray();
+                    basePositionRotationOrScale[2] *= -1;
+                }
+            }
+            else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) {
+                basePositionRotationOrScale = babylonMesh.position.asArray();
+                if (convertToRightHandedSystem) {
+                    basePositionRotationOrScale[2] *= -1;
+                }
+            }
+            else { // scale
+                basePositionRotationOrScale = babylonMesh.scaling.asArray();
+            }
+            return basePositionRotationOrScale;
+        }
+
+        /**
+         * Adds a key frame value
+         * @param keyFrame 
+         * @param animation 
+         * @param outputs 
+         * @param animationChannelTargetPath 
+         * @param basePositionRotationOrScale 
+         * @param convertToRightHandedSystem 
+         * @param useQuaternion 
+         */
+        private static _AddKeyframeValue(keyFrame: IAnimationKey, animation: Animation, outputs: number[][], animationChannelTargetPath: AnimationChannelTargetPath, babylonMesh: Mesh, 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());
+                }
+                else {
+                    outputs.push(value); // scale or position 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)
+                if (newPositionRotationOrScale) {
+                    if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) {
+                        useQuaternion ? outputs.push(newPositionRotationOrScale.normalize().asArray()) : outputs.push((newPositionRotationOrScale as Vector3).toQuaternion().normalize().asArray());
+                    }
+                    else {
+                        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;
+                }
+                outputs.push(value);
+            }
+            else {
+                Tools.Error('glTFAnimation: Unsupported key frame values for animation!');
+            }
+        }
+
+        private static _DeduceInterpolation(keyFrames: IAnimationKey[], animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean): { interpolationType: AnimationSamplerInterpolation, shouldBakeAnimation: boolean } {
+            let interpolationType: AnimationSamplerInterpolation | undefined;
+            let shouldBakeAnimation = false;
+            let key: IAnimationKey;
+
+            if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) {
+                return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true };
+            }
+
+            for (let i = 0, length = keyFrames.length; i < length; ++i) {
+                key = keyFrames[i];
+                if (key.inTangent || key.outTangent) {
+                    if (interpolationType) {
+                        if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) {
+                            interpolationType = AnimationSamplerInterpolation.LINEAR;
+                            shouldBakeAnimation = true;
+                            break;
+                        }
+                    }
+                    else {
+                        interpolationType = AnimationSamplerInterpolation.CUBICSPLINE;
+                    }
+                }
+                else {
+                    if (interpolationType) {
+                        if (interpolationType === AnimationSamplerInterpolation.CUBICSPLINE ||
+                            (key.interpolation && (key.interpolation === AnimationKeyInterpolation.STEP) && interpolationType !== AnimationSamplerInterpolation.STEP)) {
+                            interpolationType = AnimationSamplerInterpolation.LINEAR;
+                            shouldBakeAnimation = true;
+                            break;
+                        }
+                    }
+                    else {
+                        if (key.interpolation && (key.interpolation === AnimationKeyInterpolation.STEP)) {
+                            interpolationType = AnimationSamplerInterpolation.STEP;
+                        }
+                        else {
+                            interpolationType = AnimationSamplerInterpolation.LINEAR;
+                        }
+                    }
+                }
+            }
+            if (!interpolationType) {
+                interpolationType = AnimationSamplerInterpolation.LINEAR;
+            }
+
+            return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation };
+        }
+
+        /**
+         * Adds an input tangent or output tangent to the output data
+         * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion
+         * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent)
+         * @param outputs The animation data by keyframe
+         * @param animationChannelTargetPath The target animation channel
+         * @param interpolation The interpolation type
+         * @param keyFrame The key frame with the animation data
+         * @param frameDelta Time difference between two frames used to scale the tangent by the frame delta
+         * @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) {
+            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 (convertToRightHandedSystem) {
+                            tangent[0] *= -1;
+                            tangent[1] *= -1;
+                        }
+                    }
+                    else {
+                        tangent = [0, 0, 0, 0];
+                    }
+                }
+                else {
+                    if (tangentValue) {
+                        tangent = (tangentValue as Vector3).scale(frameDelta).asArray();
+                        if (convertToRightHandedSystem) {
+                            tangent[2] *= -1;
+                        }
+                    }
+                    else {
+                        tangent = [0, 0, 0];
+                    }
+                }
+
+                outputs.push(tangent);
+            }
+        }
+
+        /**
+         * Get the minimum and maximum key frames' frame values
+         * @param keyFrames animation key frames
+         * @returns the minimum and maximum key frame value
+         */
+        private static calculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number, max: number } {
+            let min: number = Infinity;
+            let max: number = -Infinity;
+            keyFrames.forEach(function (keyFrame) {
+                min = Math.min(min, keyFrame.frame);
+                max = Math.max(max, keyFrame.frame);
+            });
+
+            return { min: min, max: max };
+
+        }
+    }
+}

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

@@ -4,28 +4,28 @@ module BABYLON {
     /**
      * Class for holding and downloading glTF file data
      */
-    export class _GLTFData {
+    export class GLTFData {
         /**
-         * Object which contains the file name as the key and its data as the value.
+         * Object which contains the file name as the key and its data as the value
          */
         glTFFiles: { [fileName: string]: string | Blob };
 
         /**
-         * Initializes the glTF file object.
+         * Initializes the glTF file object
          */
         public constructor() {
             this.glTFFiles = {};
         }
 
         /**
-         * Downloads the glTF data as files based on their names and data.
+         * Downloads the glTF data as files based on their names and data
          */
         public downloadFiles(): void {
             /**
-            * Checks for a matching suffix at the end of a string (for ES5 and lower).
-            * @param str - Source string.
-            * @param suffix - Suffix to search for in the source string.
-            * @returns - Boolean indicating whether the suffix was found (true) or not (false).
+            * Checks for a matching suffix at the end of a string (for ES5 and lower)
+            * @param str Source string
+            * @param suffix Suffix to search for in the source string
+            * @returns Boolean indicating whether the suffix was found (true) or not (false)
             */
             function endsWith(str: string, suffix: string): boolean {
                 return str.indexOf(suffix, str.length - suffix.length) !== -1;

文件差异内容过多而无法显示
+ 399 - 389
serializers/src/glTF/2.0/babylon.glTFExporter.ts


+ 229 - 210
serializers/src/glTF/2.0/babylon.glTFMaterial.ts

@@ -2,63 +2,66 @@
 
 module BABYLON.GLTF2 {
     /** 
-     * Interface for storing specular glossiness factors.
+     * Interface for storing specular glossiness factors
      * @hidden
      */
+
     interface _IPBRSpecularGlossiness {
         /** 
-         * Represents the linear diffuse factors of the material.
+         * Represents the linear diffuse factors of the material
         */
         diffuseColor: BABYLON.Color3;
         /** 
-         * Represents the linear specular factors of the material.
+         * Represents the linear specular factors of the material
         */
         specularColor: BABYLON.Color3;
         /** 
-         * Represents the smoothness of the material.
+         * Represents the smoothness of the material
         */
         glossiness: number;
     }
 
     /** 
-     * Interface for storing metallic roughness factors.
+     * Interface for storing metallic roughness factors
      * @hidden
      */
+
     interface _IPBRMetallicRoughness {
         /** 
-         * Represents the albedo color of the material.
+         * Represents the albedo color of the material
         */
         baseColor: BABYLON.Color3;
         /** 
-         * Represents the metallness of the material.
+         * Represents the metallness of the material
         */
         metallic: number;
         /** 
-         * Represents the roughness of the material.
+         * Represents the roughness of the material
         */
         roughness: number;
         /** 
-         * The metallic roughness texture as a base64 string.
+         * The metallic roughness texture as a base64 string
         */
         metallicRoughnessTextureBase64?: Nullable<string>;
         /** 
-         * The base color texture as a base64 string.
+         * The base color texture as a base64 string
         */
         baseColorTextureBase64?: Nullable<string>;
     }
 
     /**
-     * Utility methods for working with glTF material conversion properties.  This class should only be used internally.
+     * Utility methods for working with glTF material conversion properties.  This class should only be used internally
      * @hidden
+
      */
     export class _GLTFMaterial {
         /**
-         * Represents the dielectric specular values for R, G and B.
+         * Represents the dielectric specular values for R, G and B
          */
         private static readonly _dielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04);
 
         /**
-         * Allows the maximum specular power to be defined for material calculations.
+         * Allows the maximum specular power to be defined for material calculations
          */
         private static _maxSpecularPower = 1024;
 
@@ -68,10 +71,10 @@ module BABYLON.GLTF2 {
         private static _epsilon = 1e-6;
 
         /**
-         * Specifies if two colors are approximately equal in value.
-         * @param color1 - first color to compare to.
-         * @param color2 - second color to compare to.
-         * @param epsilon - threshold value
+         * Specifies if two colors are approximately equal in value
+         * @param color1 first color to compare to
+         * @param color2 second color to compare to
+         * @param epsilon threshold value
          */
         private static FuzzyEquals(color1: Color3, color2: Color3, epsilon: number): boolean {
             return Scalar.WithinEpsilon(color1.r, color2.r, epsilon) &&
@@ -80,14 +83,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Gets the materials from a Babylon scene and converts them to glTF materials.
-         * @param scene - babylonjs scene.
-         * @param mimeType - texture mime type.
-         * @param images - array of images.
-         * @param textures - array of textures.
-         * @param materials - array of materials.
-         * @param imageData - mapping of texture names to base64 textures
-         * @param hasTextureCoords - specifies if texture coordinates are present on the material.
+         * Gets the materials from a Babylon scene and converts them to glTF materials
+         * @param scene babylonjs scene
+         * @param mimeType texture mime type
+         * @param images array of images
+         * @param textures array of textures
+         * @param materials array of materials
+         * @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) {
@@ -102,14 +105,14 @@ module BABYLON.GLTF2 {
                     _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, materials, imageData, hasTextureCoords);
                 }
                 else {
-                    throw new Error("Unsupported material type: " + babylonMaterial.name);
+                    Tools.Error("Unsupported material type: " + babylonMaterial.name);
                 }
             }
         }
 
         /**
-         * Makes a copy of the glTF material without the texture parameters.
-         * @param originalMaterial - original glTF material.
+         * Makes a copy of the glTF material without the texture parameters
+         * @param originalMaterial original glTF material
          * @returns glTF material without texture parameters
          */
         public static _StripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial {
@@ -132,8 +135,8 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Specifies if the material has any texture parameters present.
-         * @param material - glTF Material.
+         * Specifies if the material has any texture parameters present
+         * @param material glTF Material
          * @returns boolean specifying if texture parameters are present
          */
         public static _HasTexturesPresent(material: IMaterial): boolean {
@@ -151,9 +154,9 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material.
+         * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material
          * @param babylonStandardMaterial 
-         * @returns - glTF Metallic Roughness Material representation
+         * @returns glTF Metallic Roughness Material representation
          */
         public static _ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness {
             const P0 = new BABYLON.Vector2(0, 1);
@@ -162,13 +165,13 @@ module BABYLON.GLTF2 {
             const P3 = new BABYLON.Vector2(1300, 0.1);
 
             /**
-             * Given the control points, solve for x based on a given t for a cubic bezier curve.
-             * @param t - a value between 0 and 1.
-             * @param p0 - first control point.
-             * @param p1 - second control point.
-             * @param p2 - third control point.
-             * @param p3 - fourth control point.
-             * @returns - number result of cubic bezier curve at the specified t.
+             * Given the control points, solve for x based on a given t for a cubic bezier curve
+             * @param t a value between 0 and 1
+             * @param p0 first control point
+             * @param p1 second control point
+             * @param p2 third control point
+             * @param p3 fourth control point
+             * @returns number result of cubic bezier curve at the specified t
              */
             function _cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number {
                 return (
@@ -182,9 +185,9 @@ module BABYLON.GLTF2 {
             /**
              * Evaluates a specified specular power value to determine the appropriate roughness value, 
              * based on a pre-defined cubic bezier curve with specular on the abscissa axis (x-axis) 
-             * and roughness on the ordinant axis (y-axis).
-             * @param specularPower - specular power of standard material.
-             * @returns - Number representing the roughness value.
+             * and roughness on the ordinant axis (y-axis)
+             * @param specularPower specular power of standard material
+             * @returns Number representing the roughness value
              */
             function _solveForRoughness(specularPower: number): number {
                 var t = Math.pow(specularPower / P3.x, 0.333333);
@@ -213,10 +216,10 @@ module BABYLON.GLTF2 {
 
         /**
          * Computes the metallic factor
-         * @param diffuse - diffused value
-         * @param specular - specular value
-         * @param oneMinusSpecularStrength - one minus the specular strength
-         * @returns - metallic value
+         * @param diffuse diffused value
+         * @param specular specular value
+         * @param oneMinusSpecularStrength one minus the specular strength
+         * @returns metallic value
          */
         public static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number {
             if (specular < _GLTFMaterial._dielectricSpecular.r) {
@@ -233,10 +236,10 @@ module BABYLON.GLTF2 {
 
         /**
          * Gets the glTF alpha mode from the Babylon Material
-         * @param babylonMaterial - Babylon Material
-         * @returns - The Babylon alpha mode value
+         * @param babylonMaterial Babylon Material
+         * @returns The Babylon alpha mode value
          */
-        public static _GetAlphaMode(babylonMaterial: Material): MaterialAlphaMode {
+        public static _GetAlphaMode(babylonMaterial: Material): Nullable<MaterialAlphaMode> {
             if (babylonMaterial instanceof StandardMaterial) {
                 const babylonStandardMaterial = babylonMaterial as StandardMaterial;
                 if ((babylonStandardMaterial.alpha != 1.0) ||
@@ -266,7 +269,8 @@ module BABYLON.GLTF2 {
                         return MaterialAlphaMode.BLEND;
                     }
                     default: {
-                        throw new Error("Unsupported alpha mode " + babylonPBRMetallicRoughness.transparencyMode);
+                        Tools.Error("Unsupported alpha mode " + babylonPBRMetallicRoughness.transparencyMode);
+                        return null;
                     }
                 }
             }
@@ -288,27 +292,28 @@ module BABYLON.GLTF2 {
                         return MaterialAlphaMode.BLEND;
                     }
                     default: {
-                        throw new Error("Unsupported alpha mode " + babylonPBRMaterial.transparencyMode);
+                        Tools.Error("Unsupported alpha mode " + babylonPBRMaterial.transparencyMode);
+                        return null;
                     }
                 }
             }
             else {
-                throw new Error("Unsupported Babylon material type");
+                Tools.Error("Unsupported Babylon material type");
+                return null;
             }
         }
 
         /**
-         * Converts a Babylon Standard Material to a glTF Material.
-         * @param babylonStandardMaterial - BJS Standard Material.
-         * @param mimeType - mime type to use for the textures.
-         * @param images - array of glTF image interfaces.
-         * @param textures - array of glTF texture interfaces.
-         * @param materials - array of glTF material interfaces.
-         * @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.
+         * Converts a Babylon Standard Material to a glTF Material
+         * @param babylonStandardMaterial BJS Standard Material
+         * @param mimeType mime type to use for the textures
+         * @param images array of glTF image interfaces
+         * @param textures array of glTF texture interfaces
+         * @param materials array of glTF material interfaces
+         * @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) {
-            Tools.Warn(babylonStandardMaterial.name + ": Standard Material is currently not fully supported/implemented in glTF serializer");
             const glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
 
             const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
@@ -369,14 +374,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
-         * @param babylonPBRMetalRoughMaterial - BJS PBR Metallic Roughness Material.
-         * @param mimeType - mime type to use for the textures.
-         * @param images - array of glTF image interfaces.
-         * @param textures - array of glTF texture interfaces.
-         * @param materials - array of glTF material interfaces.
-         * @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.
+         * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
+         * @param babylonPBRMetalRoughMaterial BJS PBR Metallic Roughness Material
+         * @param mimeType mime type to use for the textures
+         * @param images array of glTF image interfaces
+         * @param textures array of glTF texture interfaces
+         * @param materials array of glTF material interfaces
+         * @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) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
@@ -439,11 +444,12 @@ module BABYLON.GLTF2 {
             }
             if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
                 const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
-
-                if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                    glTFMaterial.alphaMode = alphaMode;
-                    if (alphaMode === MaterialAlphaMode.BLEND) {
-                        glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
+                if (alphaMode) {
+                    if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                        glTFMaterial.alphaMode = alphaMode;
+                        if (alphaMode === MaterialAlphaMode.BLEND) {
+                            glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
+                        }
                     }
                 }
             }
@@ -454,14 +460,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Converts an image typed array buffer to a base64 image.
-         * @param buffer - typed array buffer.
-         * @param width - width of the image.
-         * @param height - height of the image.
-         * @param mimeType - mimetype of the image.
-         * @returns - base64 image string.
+         * Converts an image typed array buffer to a base64 image
+         * @param buffer typed array buffer
+         * @param width width of the image
+         * @param height height of the image
+         * @param mimeType mimetype of the image
+         * @returns base64 image string
          */
-        private static _CreateBase64FromCanvas(buffer: Uint8ClampedArray, width: number, height: number, mimeType: ImageMimeType): string {
+        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;
@@ -477,11 +483,11 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Generates a white texture based on the specified width and height.
-         * @param width - width of the texture in pixels.
-         * @param height - height of the texture in pixels.
-         * @param scene - babylonjs scene.
-         * @returns - white texture.
+         * Generates a white texture based on the specified width and height
+         * @param width width of the texture in pixels
+         * @param height height of the texture in pixels
+         * @param scene babylonjs scene
+         * @returns white texture
          */
         private static _CreateWhiteTexture(width: number, height: number, scene: Scene): Texture {
             const data = new Uint8Array(width * height * 4);
@@ -496,11 +502,11 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null.
-         * @param texture1 - first texture to resize.
-         * @param texture2 - second texture to resize.
-         * @param scene - babylonjs scene.
-         * @returns resized textures or null.
+         * Resizes the two source textures to the same dimensions.  If a texture is null, a default white texture is generated.  If both textures are null, returns null
+         * @param texture1 first texture to resize
+         * @param texture2 second texture to resize
+         * @param scene babylonjs scene
+         * @returns resized textures or null
          */
         private static _ResizeTexturesToSameDimensions(texture1: BaseTexture, texture2: BaseTexture, scene: Scene): { "texture1": BaseTexture, "texture2": BaseTexture } {
             let texture1Size = texture1 ? texture1.getSize() : { width: 0, height: 0 };
@@ -538,14 +544,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Convert Specular Glossiness Textures to Metallic Roughness.
+         * Convert Specular Glossiness Textures to Metallic Roughness
          * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness
          * @link https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js
-         * @param diffuseTexture - texture used to store diffuse information.
-         * @param specularGlossinessTexture - texture used to store specular and glossiness information.
-         * @param factors - specular glossiness material factors.
-         * @param mimeType - the mime type to use for the texture.
-         * @returns pbr metallic roughness interface or null.
+         * @param diffuseTexture texture used to store diffuse information
+         * @param specularGlossinessTexture texture used to store specular and glossiness information
+         * @param factors specular glossiness material factors
+         * @param mimeType the mime type to use for the texture
+         * @returns pbr metallic roughness interface or null
          */
         private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Nullable<_IPBRMetallicRoughness> {
             if (!(diffuseTexture || specularGlossinessTexture)) {
@@ -554,7 +560,8 @@ module BABYLON.GLTF2 {
 
             const scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
             if (!scene) {
-                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
+                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
+                return null;
             }
 
             const resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
@@ -572,7 +579,8 @@ module BABYLON.GLTF2 {
                 diffuseBuffer = (resizedTextures.texture1.readPixels()) as Uint8Array;
             }
             else {
-                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+                return null;
             }
             pixels = resizedTextures.texture2.readPixels();
 
@@ -580,7 +588,8 @@ module BABYLON.GLTF2 {
                 specularGlossinessBuffer = (resizedTextures.texture2.readPixels()) as Uint8Array;
             }
             else {
-                throw new Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
+                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
+                return null;
             }
 
             const byteLength = specularGlossinessBuffer.byteLength;
@@ -678,9 +687,9 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Converts specular glossiness material properties to metallic roughness.
-         * @param specularGlossiness - interface with specular glossiness material properties.
-         * @returns - interface with metallic roughness material properties.
+         * Converts specular glossiness material properties to metallic roughness
+         * @param specularGlossiness interface with specular glossiness material properties
+         * @returns interface with metallic roughness material properties
          */
         private static _ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness: _IPBRSpecularGlossiness): _IPBRMetallicRoughness {
             const diffusePerceivedBrightness = _GLTFMaterial._GetPerceivedBrightness(specularGlossiness.diffuseColor);
@@ -702,9 +711,9 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Calculates the surface reflectance, independent of lighting conditions.
-         * @param color - Color source to calculate brightness from.
-         * @returns number representing the perceived brightness, or zero if color is undefined. 
+         * Calculates the surface reflectance, independent of lighting conditions
+         * @param color Color source to calculate brightness from
+         * @returns number representing the perceived brightness, or zero if color is undefined
          */
         private static _GetPerceivedBrightness(color: Color3): number {
             if (color) {
@@ -714,9 +723,9 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Returns the maximum color component value.
+         * Returns the maximum color component value
          * @param color 
-         * @returns maximum color component value, or zero if color is null or undefined.
+         * @returns maximum color component value, or zero if color is null or undefined
          */
         private static _GetMaxComponent(color: Color3): number {
             if (color) {
@@ -726,15 +735,15 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Convert a PBRMaterial (Metallic/Roughness) to Metallic Roughness factors.
-         * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
-         * @param mimeType - mime type to use for the textures.
-         * @param images - array of glTF image interfaces.
-         * @param textures - array of glTF texture interfaces.
-         * @param glTFPbrMetallicRoughness - glTF PBR Metallic Roughness interface.
-         * @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.
-         * @returns - glTF PBR Metallic Roughness factors.
+         * Convert a PBRMaterial (Metallic/Roughness) to Metallic Roughness factors
+         * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
+         * @param mimeType mime type to use for the textures
+         * @param images array of glTF image interfaces
+         * @param textures array of glTF texture interfaces
+         * @param glTFPbrMetallicRoughness glTF PBR Metallic Roughness interface
+         * @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
+         * @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 {
             const metallicRoughness = {
@@ -761,24 +770,25 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Convert a PBRMaterial (Specular/Glossiness) to Metallic Roughness factors.
-         * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
-         * @param mimeType - mime type to use for the textures.
-         * @param images - array of glTF image interfaces.
-         * @param textures - array of glTF texture interfaces.
-         * @param glTFPbrMetallicRoughness - glTF PBR Metallic Roughness interface.
-         * @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.
-         * @returns - glTF PBR Metallic Roughness factors.
+         * Convert a PBRMaterial (Specular/Glossiness) to Metallic Roughness factors
+         * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
+         * @param mimeType mime type to use for the textures
+         * @param images array of glTF image interfaces
+         * @param textures array of glTF texture interfaces
+         * @param glTFPbrMetallicRoughness glTF PBR Metallic Roughness interface
+         * @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
+         * @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): _IPBRMetallicRoughness {
+        private static _ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], 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,
             };
             if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
-                throw new Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                Tools.Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                return null;
             }
 
             let metallicRoughnessFactors = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
@@ -806,14 +816,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Converts a Babylon PBR Metallic Roughness Material to a glTF Material.
-         * @param babylonPBRMaterial - BJS PBR Metallic Roughness Material.
-         * @param mimeType - mime type to use for the textures.
-         * @param images - array of glTF image interfaces.
-         * @param textures - array of glTF texture interfaces.
-         * @param materials - array of glTF material interfaces.
-         * @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.
+         * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
+         * @param babylonPBRMaterial BJS PBR Metallic Roughness Material
+         * @param mimeType mime type to use for the textures
+         * @param images array of glTF image interfaces
+         * @param textures array of glTF texture interfaces
+         * @param materials array of glTF material interfaces
+         * @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) {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
@@ -829,83 +839,90 @@ module BABYLON.GLTF2 {
             else {
                 metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
             }
+            if (metallicRoughness) {
+                if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
+                    glTFPbrMetallicRoughness.baseColorFactor = [
+                        metallicRoughness.baseColor.r,
+                        metallicRoughness.baseColor.g,
+                        metallicRoughness.baseColor.b,
+                        babylonPBRMaterial.alpha
+                    ];
+                }
 
-            if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
-                glTFPbrMetallicRoughness.baseColorFactor = [
-                    metallicRoughness.baseColor.r,
-                    metallicRoughness.baseColor.g,
-                    metallicRoughness.baseColor.b,
-                    babylonPBRMaterial.alpha
-                ];
-            }
-
-            if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
-                glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
-            }
-            if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
-                glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
-            }
-
-            if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
-                if (!babylonPBRMaterial.twoSidedLighting) {
-                    Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
+                if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) {
+                    glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic;
                 }
-                glTFMaterial.doubleSided = true;
-            }
-            if (hasTextureCoords) {
-                if (babylonPBRMaterial.bumpTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
-                    if (glTFTexture) {
-                        glTFMaterial.normalTexture = glTFTexture;
+                if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) {
+                    glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness;
+                }
+
+                if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) {
+                    if (!babylonPBRMaterial.twoSidedLighting) {
+                        Tools.Warn(babylonPBRMaterial.name + ": Back-face culling enabled and two-sided lighting disabled is not supported in glTF.");
                     }
+                    glTFMaterial.doubleSided = true;
                 }
-                if (babylonPBRMaterial.ambientTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
-                    if (glTFTexture) {
-                        let occlusionTexture: IMaterialOcclusionTextureInfo = {
-                            index: glTFTexture.index
-                        };
+                if (hasTextureCoords) {
+                    if (babylonPBRMaterial.bumpTexture) {
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            glTFMaterial.normalTexture = glTFTexture;
+                        }
+                    }
+                    if (babylonPBRMaterial.ambientTexture) {
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture) {
+                            let occlusionTexture: IMaterialOcclusionTextureInfo = {
+                                index: glTFTexture.index
+                            };
 
-                        glTFMaterial.occlusionTexture = occlusionTexture;
+                            glTFMaterial.occlusionTexture = occlusionTexture;
 
-                        if (babylonPBRMaterial.ambientTextureStrength) {
-                            occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
+                            if (babylonPBRMaterial.ambientTextureStrength) {
+                                occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
+                            }
                         }
                     }
-                }
-                if (babylonPBRMaterial.emissiveTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
-                    if (glTFTexture != null) {
-                        glTFMaterial.emissiveTexture = glTFTexture;
+                    if (babylonPBRMaterial.emissiveTexture) {
+                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, imageData);
+                        if (glTFTexture != null) {
+                            glTFMaterial.emissiveTexture = glTFTexture;
+                        }
                     }
-                }       
-            }
-            if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
-                glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
-            }
-            if (babylonPBRMaterial.transparencyMode != null) {
-                const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
-
-                if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                    glTFMaterial.alphaMode = alphaMode;
-                    if (alphaMode === MaterialAlphaMode.BLEND) {
-                        glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
+                }
+                if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
+                    glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
+                }
+                if (babylonPBRMaterial.transparencyMode != null) {
+                    const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
+                    if (alphaMode) {
+                        if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                            glTFMaterial.alphaMode = alphaMode;
+                            if (alphaMode === MaterialAlphaMode.BLEND) {
+                                glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
+                            }
+                        }
                     }
                 }
+
+                glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
+                materials.push(glTFMaterial);
             }
+        }
 
-            glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
-            materials.push(glTFMaterial);
+        private static GetPixelsFromTexture(babylonTexture: Texture): Uint8Array | Float32Array {
+            let pixels = babylonTexture.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as Uint8Array : babylonTexture.readPixels() as Float32Array;
+            return pixels;
         }
 
         /**
-         * Extracts a texture from a Babylon texture into file data and glTF data.
-         * @param babylonTexture - Babylon texture to extract.
-         * @param mimeType - Mime Type of the babylonTexture.
-         * @param images - Array of glTF images.
-         * @param textures - Array of glTF textures.
-         * @param imageData - map of image file name and data.
-         * @return - glTF texture info, or null if the texture format is not supported.
+         * Extracts a texture from a Babylon texture into file data and glTF data
+         * @param babylonTexture Babylon texture to extract
+         * @param mimeType Mime Type of the babylonTexture
+         * @param images Array of glTF images
+         * @param textures Array of glTF textures
+         * @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> {
             let textureName = "texture_" + (textures.length - 1).toString();
@@ -927,12 +944,13 @@ module BABYLON.GLTF2 {
                 extension = ".png";
             }
             else {
-                throw new Error("Unsupported mime type " + mimeType);
+                Tools.Error("Unsupported mime type " + mimeType);
+                return null;
             }
             textureName = baseFile + extension;
 
 
-            const pixels = babylonTexture.readPixels() as Uint8Array;
+            const pixels = _GLTFMaterial.GetPixelsFromTexture(babylonTexture as Texture);
 
             const size = babylonTexture.getSize();
 
@@ -942,14 +960,14 @@ module BABYLON.GLTF2 {
         }
 
         /**
-         * Builds a texture from base64 string.
-         * @param base64Texture - base64 texture string.
-         * @param textureName - Name to use for the texture.
-         * @param mimeType - image mime type for the texture.
-         * @param images - array of images.
-         * @param textures - array of textures.
-         * @param imageData - map of image data.
-         * @returns - glTF texture info, or null if the texture format is not supported.
+         * Builds a texture from base64 string
+         * @param base64Texture base64 texture string
+         * @param textureName Name to use for the texture
+         * @param mimeType image mime type for the texture
+         * @param images array of images
+         * @param textures array of textures
+         * @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> {
             let textureInfo: Nullable<ITextureInfo> = null;
@@ -959,8 +977,9 @@ module BABYLON.GLTF2 {
             };
 
             const binStr = atob(base64Texture.split(',')[1]);
-            const arr = new Uint8Array(binStr.length);
-            for (let i = 0; i < binStr.length; ++i) {
+            let arrBuff = new ArrayBuffer(binStr.length);
+            const arr = new Uint8Array(arrBuff);
+            for (let i = 0, length = binStr.length; i < length; ++i) {
                 arr[i] = binStr.charCodeAt(i);
             }
             const imageValues = { data: arr, mimeType: mimeType };
@@ -970,7 +989,7 @@ module BABYLON.GLTF2 {
                 const glTFImage: IImage = {
                     uri: textureName
                 }
-                let foundIndex = -1;
+                let foundIndex: number = -1;
                 for (let i = 0; i < images.length; ++i) {
                     if (images[i].uri === textureName) {
                         foundIndex = i;

+ 23 - 18
serializers/src/glTF/2.0/babylon.glTFSerializer.ts

@@ -6,12 +6,15 @@ module BABYLON {
      */
     export interface IExporterOptions {
         /**
-         * 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.
+         * 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;
+        /**
+         * The sample rate to bake animation curves
+         */
+        animationSampleRate?: number;
     };
 
     /**
@@ -19,39 +22,41 @@ module BABYLON {
      */
     export class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format.
-         * @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.
+         * Exports the geometry of the scene to .gltf file format
+         * @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): _GLTFData {
+        public static GLTF(scene: Scene, filePrefix: string, options?: IExporterOptions): Nullable<GLTFData> {
             const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
             const gltfGenerator = new GLTF2._Exporter(scene, options);
             if (scene.isReady) {
                 return gltfGenerator._generateGLTF(glTFPrefix);
             }
             else {
-                throw new 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.
-         * @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
+         * Exports the geometry of the scene to .glb file format
+         * @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): _GLTFData {
+        public static GLB(scene: Scene, filePrefix: string, options?: IExporterOptions): Nullable<GLTFData> {
             const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");        
             const gltfGenerator = new GLTF2._Exporter(scene, options);
             if (scene.isReady) {
                 return gltfGenerator._generateGLB(glTFPrefix);
             }
             else {
-                throw new Error("glTF Serializer: Scene is not ready!");
+                Tools.Error("glTF Serializer: Scene is not ready!");
+                return null;
             }  
         }
     }

+ 135 - 0
serializers/src/glTF/2.0/babylon.glTFUtilities.ts

@@ -0,0 +1,135 @@
+/// <reference path="../../../../dist/babylon.glTF2Interface.d.ts"/>
+
+module BABYLON.GLTF2 {
+    /**
+     * @hidden
+     */
+    export class _GLTFUtilities {
+        /**
+         * Creates a buffer view based on the supplied arguments
+         * @param bufferIndex index value of the specified buffer
+         * @param byteOffset byte offset value
+         * @param byteLength byte length of the bufferView
+         * @param byteStride byte distance between conequential elements
+         * @param name name of the buffer view
+         * @returns bufferView for glTF
+         */
+        public static CreateBufferView(bufferIndex: number, byteOffset: number, byteLength: number, byteStride?: number, name?: string): IBufferView {
+            let bufferview: IBufferView = { buffer: bufferIndex, byteLength: byteLength };
+            if (byteOffset) {
+                bufferview.byteOffset = byteOffset;
+            }
+            if (name) {
+                bufferview.name = name;
+            }
+            if (byteStride) {
+                bufferview.byteStride = byteStride;
+            }
+
+            return bufferview;
+        }
+
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex The index of the bufferview referenced by this accessor
+         * @param name The name of the accessor
+         * @param type The type of the accessor
+         * @param componentType The datatype of components in the attribute
+         * @param count The number of attributes referenced by this accessor
+         * @param byteOffset The offset relative to the start of the bufferView in bytes
+         * @param min Minimum value of each component in this attribute
+         * @param max Maximum value of each component in this attribute
+         * @returns accessor for glTF 
+         */
+        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) {
+                accessor.min = min;
+            }
+            if (max) {
+                accessor.max = max;
+            }
+            if (byteOffset) {
+                accessor.byteOffset = byteOffset;
+            }
+
+            return accessor;
+        }
+
+        /**
+         * Calculates the minimum and maximum values of an array of position floats
+         * @param positions Positions array of a mesh
+         * @param vertexStart Starting vertex offset to calculate min and max values
+         * @param vertexCount Number of vertices to check for min and max values
+         * @returns min number array and max number array
+         */
+        public static CalculateMinMaxPositions(positions: FloatArray, vertexStart: number, vertexCount: number, convertToRightHandedSystem: boolean): { min: number[], max: number[] } {
+            const min = [Infinity, Infinity, Infinity];
+            const max = [-Infinity, -Infinity, -Infinity];
+            const positionStrideSize = 3;
+            let indexOffset: number;
+            let position: Vector3;
+            let vector: number[];
+
+            if (vertexCount) {
+                for (let i = vertexStart, length = vertexStart + vertexCount; i < length; ++i) {
+                    indexOffset = positionStrideSize * i;
+
+                    position = Vector3.FromArray(positions, indexOffset);
+                    if (convertToRightHandedSystem) {
+                        _GLTFUtilities.GetRightHandedVector3FromRef(position);
+                    }
+                    vector = position.asArray();
+
+                    for (let j = 0; j < positionStrideSize; ++j) {
+                        let num = vector[j];
+                        if (num < min[j]) {
+                            min[j] = num;
+                        }
+                        if (num > max[j]) {
+                            max[j] = num;
+                        }
+                        ++indexOffset;
+                    }
+                }
+            }
+            return { min, max };
+        }
+
+        /**
+         * Converts a new right-handed Vector3
+         * @param vector vector3 array
+         * @returns right-handed Vector3
+         */
+        public static GetRightHandedVector3(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 GetRightHandedVector3FromRef(vector: Vector3) {
+            vector.z *= -1;
+        }
+
+        /**
+         * Converts a Vector4 to right-handed
+         * @param vector Vector4 to convert to right-handed
+         */
+        public static GetRightHandedVector4FromRef(vector: Vector4) {
+            vector.z *= -1;
+            vector.w *= -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;
+        }
+    }
+}

+ 16 - 2
src/Tools/babylon.tools.ts

@@ -146,6 +146,21 @@
             return count === value;
         }
 
+        private static _tmpFloatArray = new Float32Array(1);
+        /**
+         * Returns the nearest 32-bit single precision float representation of a Number
+         * @param value A Number.  If the parameter is of a different type, it will get converted
+         * to a number or to NaN if it cannot be converted
+         * @returns number
+         */
+        public static FloatRound(value: number): number {
+            if (Math.fround) {
+                return Math.fround(value);
+            }
+
+            return (Tools._tmpFloatArray[0] = value);
+        }
+
 		/**
 		 * Find the next highest power of two.
 		 * @param x Number to start search from.
@@ -824,8 +839,7 @@
                     continue;
                 }
 
-                try
-                {
+                try {
                     if (typeOfSourceValue === "object") {
                         if (sourceValue instanceof Array) {
                             destination[prop] = [];

+ 281 - 0
tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts

@@ -163,5 +163,286 @@ describe('Babylon glTF Serializer', () => {
                 done();
             });
         });
+        it('should serialize single component translation animation to glTF', (done) => {
+            mocha.timeout(10000);
+            const scene = new BABYLON.Scene(subject);
+            const box = BABYLON.Mesh.CreateBox('box', 1, scene);
+            let keys: BABYLON.IAnimationKey[] = [];
+            keys.push({
+            frame: 0,
+            value: 1
+            });
+            keys.push({
+            frame: 20,
+            value: 0.2
+            });
+            keys.push({
+            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();
+            });
+            });
+            it('should serialize translation animation to glTF', (done) => {
+            mocha.timeout(10000);
+            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)
+            });
+            keys.push({
+            frame: 20,
+            value: BABYLON.Vector3.One()
+            });
+            keys.push({
+            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();
+            });
+            });
+            it('should serialize scale animation to glTF', (done) => {
+            mocha.timeout(10000);
+            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)
+            });
+            keys.push({
+            frame: 20,
+            value: BABYLON.Vector3.One()
+            });
+            keys.push({
+            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();
+            });
+            });
+            it('should serialize rotation quaternion animation to glTF', (done) => {
+            mocha.timeout(10000);
+            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)
+            });
+            keys.push({
+            frame: 20,
+            value: BABYLON.Quaternion.Identity()
+            });
+            keys.push({
+            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();
+            });
+            });
+            it('should serialize combined animations to glTF', (done) => {
+            mocha.timeout(10000);
+            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)
+            });
+            rotationKeyFrames.push({
+            frame: 20,
+            value: BABYLON.Quaternion.Identity()
+            });
+            rotationKeyFrames.push({
+            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)
+            });
+            scaleKeyFrames.push({
+            frame: 20,
+            value: BABYLON.Vector3.One()
+            });
+            scaleKeyFrames.push({
+            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);
+            box.animations.push(rotationAnimationBox);
+            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();
+            });
+            });
+            
     });
 });