private GLTFNode ExportAbstractMesh(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.AbstractMesh | Export abstract mesh named: " + babylonAbstractMesh.name, 1);

            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = babylonAbstractMesh.name;
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonAbstractMesh.position;
            // TODO - Choose between this method and the extra root node
            // Switch from left to right handed coordinate system
            //gltfNode.translation[0] *= -1;
            if (babylonAbstractMesh.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonAbstractMesh.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonAbstractMesh.rotation[0],
                    Y = babylonAbstractMesh.rotation[1],
                    Z = babylonAbstractMesh.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }
            gltfNode.scale = babylonAbstractMesh.scaling;

            // Mesh
            var gltfMesh = gltf.MeshesList.Find(_gltfMesh => _gltfMesh.idGroupInstance == babylonAbstractMesh.idGroupInstance);

            if (gltfMesh != null)
            {
                gltfNode.mesh = gltfMesh.index;
            }

            return(gltfNode);
        }
Esempio n. 2
0
        /// <summary>
        /// Create a gltf node from the babylon node.
        /// </summary>
        /// <param name="babylonNode"></param>
        /// <param name="gltf"></param>
        /// <param name="babylonScene"></param>
        /// <param name="gltfParentNode">The parent of the glTF node that will be created.</param>
        /// <returns>The gltf node created.</returns>
        private GLTFNode ExportNode(BabylonNode babylonNode, GLTF gltf, BabylonScene babylonScene, GLTFNode gltfParentNode)
        {
            RaiseMessage($"GLTFExporter | ExportNode {babylonNode.name}", 1);
            GLTFNode gltfNode = null;
            var      type     = babylonNode.GetType();

            var nodeNodePair = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonNode.id));

            if (nodeNodePair.Key != null)
            {
                return(nodeNodePair.Value);
            }

            var boneNodePair = boneToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonNode.id));

            if (boneNodePair.Key != null)
            {
                return(boneNodePair.Value);
            }

            // Node
            gltfNode = new GLTFNode
            {
                name  = GetUniqueNodeName(babylonNode.name),
                index = gltf.NodesList.Count
            };
            gltf.NodesList.Add(gltfNode);                 // add the node to the gltf list
            nodeToGltfNodeMap.Add(babylonNode, gltfNode); // add the node to the global map

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Node| Add " + babylonNode.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Node | Add " + babylonNode.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            // Position
            gltfNode.translation = babylonNode.position;

            // Rotation
            if (type == typeof(BabylonAbstractMesh) || type.IsSubclassOf(typeof(BabylonAbstractMesh)) || type == typeof(BabylonCamera))
            {
                if (babylonNode.rotationQuaternion != null)
                {
                    gltfNode.rotation = babylonNode.rotationQuaternion;
                }
                else
                {
                    // Convert rotation vector to quaternion
                    BabylonVector3 rotationVector3 = new BabylonVector3
                    {
                        X = babylonNode.rotation[0],
                        Y = babylonNode.rotation[1],
                        Z = babylonNode.rotation[2]
                    };
                    gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
                }
            }
            else // Light
            {
                gltfNode.rotation = new float[4] {
                    0, 0, 0, 1
                };
            }

            // Scale
            if (type == typeof(BabylonAbstractMesh) || type.IsSubclassOf(typeof(BabylonAbstractMesh)))
            {
                gltfNode.scale = babylonNode.scaling;
            }
            else // Camera and light
            {
                gltfNode.scale = new float[3] {
                    1, 1, 1
                };
            }

            // Switch coordinate system at object level
            gltfNode.translation[2] *= -1;
            gltfNode.rotation[0]    *= -1;
            gltfNode.rotation[1]    *= -1;

            return(gltfNode);
        }
        private GLTFNode ExportAbstractMesh(BabylonAbstractMesh babylonAbstractMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
        {
            RaiseMessage("GLTFExporter.AbstractMesh | Export abstract mesh named: " + babylonAbstractMesh.name, 1);

            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = babylonAbstractMesh.name;
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.AbstractMesh | Add " + babylonAbstractMesh.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonAbstractMesh.position;
            if (babylonAbstractMesh.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonAbstractMesh.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonAbstractMesh.rotation[0],
                    Y = babylonAbstractMesh.rotation[1],
                    Z = babylonAbstractMesh.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }
            gltfNode.scale = babylonAbstractMesh.scaling;

            // Switch coordinate system at object level
            gltfNode.translation[2] *= -1;
            gltfNode.rotation[0]    *= -1;
            gltfNode.rotation[1]    *= -1;

            // Mesh
            var gltfMesh = gltf.MeshesList.Find(_gltfMesh => _gltfMesh.idGroupInstance == babylonAbstractMesh.idGroupInstance);

            if (gltfMesh != null)
            {
                gltfNode.mesh = gltfMesh.index;

                // Skin
                if (gltfMesh.idBabylonSkeleton.HasValue)
                {
                    var babylonSkeleton = babylonScene.skeletons[gltfMesh.idBabylonSkeleton.Value];
                    // Export a new skeleton if necessary and a new skin
                    var gltfSkin = ExportSkin(babylonSkeleton, gltf, gltfNode);
                    gltfNode.skin = gltfSkin.index;
                }
            }

            // Animations
            ExportNodeAnimation(babylonAbstractMesh, gltf, gltfNode, babylonScene);

            return(gltfNode);
        }
        private GLTFCamera ExportCamera(BabylonCamera babylonCamera, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.Camera | Export camera named: " + babylonCamera.name, 1);

            // --------------------------
            // ---------- Node ----------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | Node", 2);
            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = GetUniqueNodeName(babylonCamera.name);
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as child to " + gltfParentNode.name, 3);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as root node to scene", 3);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonCamera.position;
            if (babylonCamera.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonCamera.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonCamera.rotation[0],
                    Y = babylonCamera.rotation[1],
                    Z = babylonCamera.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }
            // No scaling defined for babylon camera. Use identity instead.
            gltfNode.scale = new float[3] {
                1, 1, 1
            };

            // Switch coordinate system at object level
            gltfNode.translation[2] *= -1;
            gltfNode.rotation[0]    *= -1;
            gltfNode.rotation[1]    *= -1;

            // Animations
            ExportNodeAnimation(babylonCamera, gltf, gltfNode);

            // --- prints ---
            #region prints

            RaiseVerbose("GLTFExporter.Camera | babylonCamera data", 2);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.type=" + babylonCamera.type, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.fov=" + babylonCamera.fov, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.maxZ=" + babylonCamera.maxZ, 3);
            RaiseVerbose("GLTFExporter.Camera | babylonCamera.minZ=" + babylonCamera.minZ, 3);
            #endregion


            // --------------------------
            // ------- gltfCamera -------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | create gltfCamera", 2);

            // Camera
            var gltfCamera = new GLTFCamera {
                name = babylonCamera.name
            };
            gltfCamera.index = gltf.CamerasList.Count;
            gltf.CamerasList.Add(gltfCamera);
            gltfNode.camera     = gltfCamera.index;
            gltfCamera.gltfNode = gltfNode;

            // Camera type
            switch (babylonCamera.mode)
            {
            case (BabylonCamera.CameraMode.ORTHOGRAPHIC_CAMERA):
                var gltfCameraOrthographic = new GLTFCameraOrthographic();
                gltfCameraOrthographic.xmag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.ymag  = 1;    // Do not bother about it - still mandatory
                gltfCameraOrthographic.zfar  = babylonCamera.maxZ;
                gltfCameraOrthographic.znear = babylonCamera.minZ;

                gltfCamera.type         = GLTFCamera.CameraType.orthographic.ToString();
                gltfCamera.orthographic = gltfCameraOrthographic;
                break;

            case (BabylonCamera.CameraMode.PERSPECTIVE_CAMERA):
                var gltfCameraPerspective = new GLTFCameraPerspective();
                gltfCameraPerspective.aspectRatio = null;              // Do not bother about it - use default glTF value
                gltfCameraPerspective.yfov        = babylonCamera.fov; // Babylon camera fov mode is assumed to be vertical (FOVMODE_VERTICAL_FIXED)
                gltfCameraPerspective.zfar        = babylonCamera.maxZ;
                gltfCameraPerspective.znear       = babylonCamera.minZ;

                gltfCamera.type        = GLTFCamera.CameraType.perspective.ToString();
                gltfCamera.perspective = gltfCameraPerspective;
                break;

            default:
                RaiseError("GLTFExporter.Camera | camera mode not found");
                break;
            }

            return(gltfCamera);
        }
Esempio n. 5
0
        /// <summary>
        /// Create a gltf node from the babylon node.
        /// </summary>
        /// <param name="babylonNode"></param>
        /// <param name="gltf"></param>
        /// <param name="babylonScene"></param>
        /// <param name="gltfParentNode">The parent of the glTF node that will be created.</param>
        /// <returns>The gltf node created.</returns>
        private GLTFNode ExportNode(BabylonNode babylonNode, GLTF gltf, BabylonScene babylonScene, GLTFNode gltfParentNode)
        {
            logger.RaiseMessage($"GLTFExporter | ExportNode {babylonNode.name}", 1);
            GLTFNode gltfNode = null;
            var      type     = babylonNode.GetType();

            var nodeNodePair = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonNode.id));

            if (nodeNodePair.Key != null)
            {
                return(nodeNodePair.Value);
            }

            // Node
            gltfNode = new GLTFNode
            {
                name  = GetUniqueNodeName(babylonNode.name),
                index = gltf.NodesList.Count
            };

            // User Custom Attributes
            if (babylonNode.metadata != null && babylonNode.metadata.Count != 0)
            {
                gltfNode.extras = babylonNode.metadata;
            }

            gltf.NodesList.Add(gltfNode);                 // add the node to the gltf list
            nodeToGltfNodeMap.Add(babylonNode, gltfNode); // add the node to the global map

            // Hierarchy
            if (gltfParentNode != null)
            {
                logger.RaiseMessage("GLTFExporter.Node| Add " + babylonNode.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                logger.RaiseMessage("GLTFExporter.Node | Add " + babylonNode.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // TRS
            if (exportParameters.exportAnimationsOnly == false)
            {
                // Position
                gltfNode.translation = babylonNode.position;

                // Rotation
                if (babylonNode.rotationQuaternion != null)
                {
                    gltfNode.rotation = babylonNode.rotationQuaternion;
                }
                else
                {
                    // Convert rotation vector to quaternion
                    BabylonVector3 rotationVector3 = new BabylonVector3
                    {
                        X = babylonNode.rotation[0],
                        Y = babylonNode.rotation[1],
                        Z = babylonNode.rotation[2]
                    };
                    gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
                }

                // Scale
                gltfNode.scale = babylonNode.scaling;

                // Switch coordinate system at object level
                gltfNode.translation[2] *= -1;
                gltfNode.rotation[0]    *= -1;
                gltfNode.rotation[1]    *= -1;
            }

            ExportGLTFExtension(babylonNode, ref gltfNode, gltf);

            return(gltfNode);
        }
Esempio n. 6
0
        private GLTFAnimation ExportBoneAnimation(BabylonBone babylonBone, GLTF gltf, GLTFNode gltfNode)
        {
            GLTFAnimation gltfAnimation = null;

            if (gltf.AnimationsList.Count > 0)
            {
                gltfAnimation = gltf.AnimationsList[0];
            }
            else
            {
                gltfAnimation = new GLTFAnimation();
                gltf.AnimationsList.Add(gltfAnimation);
            }

            var channelList = gltfAnimation.ChannelList;
            var samplerList = gltfAnimation.SamplerList;

            if (babylonBone.animation != null && babylonBone.animation.property == "_matrix")
            {
                RaiseMessage("GLTFExporter.Animation | Export animation of bone named: " + babylonBone.name, 2);

                var babylonAnimation = babylonBone.animation;

                // --- Input ---
                var accessorInput = _createAndPopulateInput(gltf, babylonAnimation);

                // --- Output ---
                var paths = new string[] { "translation", "rotation", "scale" };
                var accessorOutputByPath = new Dictionary <string, GLTFAccessor>();

                foreach (string path in paths)
                {
                    GLTFAccessor accessorOutput = _createAccessorOfPath(path, gltf);
                    accessorOutputByPath.Add(path, accessorOutput);
                }

                // Populate accessors
                foreach (var babylonAnimationKey in babylonAnimation.keys)
                {
                    var matrix = new BabylonMatrix();
                    matrix.m = babylonAnimationKey.values;

                    var translationBabylon  = new BabylonVector3();
                    var rotationQuatBabylon = new BabylonQuaternion();
                    var scaleBabylon        = new BabylonVector3();
                    matrix.decompose(scaleBabylon, rotationQuatBabylon, translationBabylon);

                    translationBabylon.Z *= -1;
                    BabylonVector3 rotationVector3 = rotationQuatBabylon.toEulerAngles();
                    rotationVector3.X  *= -1;
                    rotationVector3.Y  *= -1;
                    rotationQuatBabylon = rotationVector3.toQuaternion();

                    var outputValuesByPath = new Dictionary <string, float[]>();
                    outputValuesByPath.Add("translation", translationBabylon.ToArray());
                    outputValuesByPath.Add("rotation", rotationQuatBabylon.ToArray());
                    outputValuesByPath.Add("scale", scaleBabylon.ToArray());

                    // Store values as bytes
                    foreach (string path in paths)
                    {
                        var accessorOutput = accessorOutputByPath[path];
                        var outputValues   = outputValuesByPath[path];

                        foreach (var outputValue in outputValues)
                        {
                            accessorOutput.bytesList.AddRange(BitConverter.GetBytes(outputValue));
                        }
                        accessorOutput.count++;
                    }
                }
                ;

                foreach (string path in paths)
                {
                    var accessorOutput = accessorOutputByPath[path];

                    // Animation sampler
                    var gltfAnimationSampler = new GLTFAnimationSampler
                    {
                        input  = accessorInput.index,
                        output = accessorOutput.index
                    };
                    gltfAnimationSampler.index = samplerList.Count;
                    samplerList.Add(gltfAnimationSampler);

                    // Target
                    var gltfTarget = new GLTFChannelTarget
                    {
                        node = gltfNode.index
                    };
                    gltfTarget.path = path;

                    // Channel
                    var gltfChannel = new GLTFChannel
                    {
                        sampler = gltfAnimationSampler.index,
                        target  = gltfTarget
                    };
                    channelList.Add(gltfChannel);
                }
            }

            return(gltfAnimation);
        }
        /// <summary>
        /// Create a gltf node from the babylon node.
        /// </summary>
        /// <param name="babylonNode"></param>
        /// <param name="gltf"></param>
        /// <param name="babylonScene"></param>
        /// <param name="gltfParentNode">The parent of the glTF node that will be created.</param>
        /// <returns>The gltf node created.</returns>
        private GLTFNode ExportNode(BabylonNode babylonNode, GLTF gltf, BabylonScene babylonScene, GLTFNode gltfParentNode)
        {
            RaiseMessage($"GLTFExporter | ExportNode {babylonNode.name}", 1);
            GLTFNode gltfNode = null;
            var      type     = babylonNode.GetType();

            var nodeNodePair = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonNode.id));

            if (nodeNodePair.Key != null)
            {
                return(nodeNodePair.Value);
            }

            var boneNodePair = boneToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonNode.id));

            if (boneNodePair.Key != null)
            {
                return(boneNodePair.Value);
            }

            // Node
            gltfNode = new GLTFNode
            {
                name  = GetUniqueNodeName(babylonNode.name),
                index = gltf.NodesList.Count
            };
            gltf.NodesList.Add(gltfNode);                 // add the node to the gltf list
            nodeToGltfNodeMap.Add(babylonNode, gltfNode); // add the node to the global map

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Node| Add " + babylonNode.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
                gltfNode.parent = gltfParentNode;
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Node | Add " + babylonNode.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            // Position
            gltfNode.translation = babylonNode.position;

            // Rotation
            if (babylonNode.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonNode.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonNode.rotation[0],
                    Y = babylonNode.rotation[1],
                    Z = babylonNode.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }

            // Scale
            gltfNode.scale = babylonNode.scaling;

            // Switch coordinate system at object level
            gltfNode.translation[2] *= -1;
            gltfNode.rotation[0]    *= -1;
            gltfNode.rotation[1]    *= -1;

            // Kuesa layers
            if (exportParameters.kuesaExportLayers && babylonNode.kuesaLayers != null)
            {
                Dictionary <string, List <int> > layers = new Dictionary <string, List <int> >();
                List <int> indexes = babylonNode.kuesaLayers.ToList <int>();
                layers["layers"] = new List <int>();
                layers["layers"].AddRange(indexes);
                if (gltfNode.extensions == null)
                {
                    gltfNode.extensions = new GLTFExtensions();
                }
                gltfNode.extensions["KDAB_Kuesa_Layers"] = layers;
            }

            return(gltfNode);
        }
Esempio n. 8
0
        /// <summary>
        /// Get TRS and visiblity animations of the transform
        /// </summary>
        /// <param name="transform">Transform above mesh/camera/light</param>
        /// <returns></returns>
        private List <BabylonAnimation> GetAnimation(MFnTransform transform)
        {
            // Animations
            MPlugArray   connections  = new MPlugArray();
            MStringArray animCurvList = new MStringArray();
            MIntArray    keysTime     = new MIntArray();
            MDoubleArray keysValue    = new MDoubleArray();

            MFloatArray translateValues  = new MFloatArray();
            MFloatArray rotateValues     = new MFloatArray();
            MFloatArray scaleValues      = new MFloatArray();
            MFloatArray visibilityValues = new MFloatArray();
            MFloatArray keyTimes         = new MFloatArray();

            List <BabylonAnimationKey> keys             = new List <BabylonAnimationKey>();
            List <BabylonAnimation>    animationsObject = new List <BabylonAnimation>();

            //Get the animCurve
            MGlobal.executeCommand("listConnections -type \"animCurve\" " + transform.fullPathName + ";", animCurvList);

            List <AnimCurvData> animCurvesData = new List <AnimCurvData>();

            foreach (String animCurv in animCurvList)
            {
                AnimCurvData animCurvData = new AnimCurvData();
                animCurvesData.Add(animCurvData);

                animCurvData.animCurv = animCurv;

                //Get the key time for each curves
                MGlobal.executeCommand("keyframe -q " + animCurv + ";", keysTime);

                //Get the value for each curves
                MGlobal.executeCommand("keyframe - q -vc -absolute " + animCurv + ";", keysValue);

                if (animCurv.EndsWith("translateZ") || animCurv.EndsWith("rotateX") || animCurv.EndsWith("rotateY"))
                {
                    for (int index = 0; index < keysTime.Count; index++)
                    {
                        // Switch coordinate system at object level
                        animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index] * -1.0f);
                    }
                }
                else
                {
                    for (int index = 0; index < keysTime.Count; index++)
                    {
                        animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index]);
                    }
                }
            }

            string[] mayaAnimationProperties    = new string[] { "translate", "rotate", "scale" };
            string[] babylonAnimationProperties = new string[] { "position", "rotationQuaternion", "scaling" };
            string[] axis = new string[] { "X", "Y", "Z" };

            // Init TRS default values
            Dictionary <string, float> defaultValues = new Dictionary <string, float>();

            float[] position           = null;
            float[] rotationQuaternion = null;
            float[] rotation           = null;
            float[] scaling            = null;
            GetTransform(transform, ref position, ref rotationQuaternion, ref rotation, ref scaling); // coordinate system already switched
            defaultValues.Add("translateX", position[0]);
            defaultValues.Add("translateY", position[1]);
            defaultValues.Add("translateZ", position[2]);
            defaultValues.Add("rotateX", rotation[0]);
            defaultValues.Add("rotateY", rotation[1]);
            defaultValues.Add("rotateZ", rotation[2]);
            defaultValues.Add("scaleX", scaling[0]);
            defaultValues.Add("scaleY", scaling[1]);
            defaultValues.Add("scaleZ", scaling[2]);

            for (int indexAnimationProperty = 0; indexAnimationProperty < mayaAnimationProperties.Length; indexAnimationProperty++)
            {
                string mayaAnimationProperty = mayaAnimationProperties[indexAnimationProperty];

                // Retreive animation curves data for current animation property
                // Ex: all "translate" data are "translateX", "translateY", "translateZ"
                List <AnimCurvData> animDataProperty = animCurvesData.Where(data => data.animCurv.Contains(mayaAnimationProperty)).ToList();

                if (animDataProperty.Count == 0)
                {
                    // Property is not animated
                    continue;
                }

                // Get all frames for this property
                List <int> framesProperty = new List <int>();
                foreach (var animData in animDataProperty)
                {
                    framesProperty.AddRange(animData.valuePerFrame.Keys);
                }
                framesProperty = framesProperty.Distinct().ToList();
                framesProperty.Sort();

                // Get default values for this property
                BabylonAnimationKey lastBabylonAnimationKey = new BabylonAnimationKey();
                lastBabylonAnimationKey.frame  = 0;
                lastBabylonAnimationKey.values = new float[] { defaultValues[mayaAnimationProperty + "X"], defaultValues[mayaAnimationProperty + "Y"], defaultValues[mayaAnimationProperty + "Z"] };

                // Compute all values for this property
                List <BabylonAnimationKey> babylonAnimationKeys = new List <BabylonAnimationKey>();
                foreach (var frameProperty in framesProperty)
                {
                    BabylonAnimationKey babylonAnimationKey = new BabylonAnimationKey();
                    babylonAnimationKeys.Add(babylonAnimationKey);

                    // Frame
                    babylonAnimationKey.frame = frameProperty;

                    // Values
                    float[] valuesProperty = new float[3];
                    for (int indexAxis = 0; indexAxis < axis.Length; indexAxis++)
                    {
                        AnimCurvData animCurvDataAxis = animDataProperty.Find(data => data.animCurv.EndsWith(axis[indexAxis]));

                        float value;
                        if (animCurvDataAxis != null && animCurvDataAxis.valuePerFrame.ContainsKey(frameProperty))
                        {
                            value = animCurvDataAxis.valuePerFrame[frameProperty];
                        }
                        else
                        {
                            value = lastBabylonAnimationKey.values[indexAxis];
                        }
                        valuesProperty[indexAxis] = value;
                    }
                    babylonAnimationKey.values = valuesProperty.ToArray();

                    // Update last known values
                    lastBabylonAnimationKey = babylonAnimationKey;
                }

                // Convert euler to quaternion angles
                if (indexAnimationProperty == 1) // Rotation
                {
                    foreach (var babylonAnimationKey in babylonAnimationKeys)
                    {
                        BabylonVector3    eulerAngles      = BabylonVector3.FromArray(babylonAnimationKey.values);
                        BabylonQuaternion quaternionAngles = eulerAngles.toQuaternion();
                        babylonAnimationKey.values = quaternionAngles.ToArray();
                    }
                }

                var keysFull = new List <BabylonAnimationKey>(babylonAnimationKeys);

                // Optimization
                OptimizeAnimations(babylonAnimationKeys, true);

                // Ensure animation has at least 2 frames
                if (IsAnimationKeysRelevant(keys))
                {
                    // Create BabylonAnimation
                    string babylonAnimationProperty = babylonAnimationProperties[indexAnimationProperty];
                    animationsObject.Add(new BabylonAnimation()
                    {
                        dataType       = indexAnimationProperty == 1 ? (int)BabylonAnimation.DataType.Quaternion : (int)BabylonAnimation.DataType.Vector3,
                        name           = babylonAnimationProperty + " animation",
                        framePerSecond = 30,
                        loopBehavior   = (int)BabylonAnimation.LoopBehavior.Cycle,
                        property       = babylonAnimationProperty,
                        keys           = babylonAnimationKeys.ToArray(),
                        keysFull       = keysFull
                    });
                }
            }

            return(animationsObject);
        }
        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.Mesh | ExportMesh babylonMesh.name=" + babylonMesh.name, 1);

            // --------------------------
            // ---------- Node ----------
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | Node", 1);
            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = babylonMesh.name;
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as child to " + gltfParentNode.name, 2);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Mesh | Add " + babylonMesh.name + " as root node to scene", 2);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonMesh.position;
            if (babylonMesh.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonMesh.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                // TODO - Fix it
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonMesh.rotation[0],
                    Y = babylonMesh.rotation[1],
                    Z = babylonMesh.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();

                RaiseMessage("GLTFExporter.Mesh | rotationVector3=[" + rotationVector3.X + "; " + rotationVector3.Y + "; " + rotationVector3.Z + "]", 2);
                RaiseMessage("GLTFExporter.Mesh | gltfNode.rotation=[" + gltfNode.rotation[0] + "; " + gltfNode.rotation[1] + "; " + gltfNode.rotation[2] + "; " + gltfNode.rotation[3] + "]", 2);
            }
            gltfNode.scale = babylonMesh.scaling;


            // --------------------------
            // --- Mesh from babylon ----
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 1);
            // Retreive general data from babylon mesh
            int  nbVertices = babylonMesh.positions.Length / 3;
            bool hasUV      = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0;
            bool hasUV2     = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0;
            bool hasColor   = babylonMesh.colors != null && babylonMesh.colors.Length > 0;

            RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 2);
            RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 2);
            RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 2);
            RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 2);

            // Retreive vertices data from babylon mesh
            List <GLTFGlobalVertex> globalVertices = new List <GLTFGlobalVertex>();

            for (int i = 0; i < nbVertices; i++)
            {
                GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
                globalVertex.Position = createIPoint3(babylonMesh.positions, i);
                globalVertex.Normal   = createIPoint3(babylonMesh.normals, i);
                if (hasUV)
                {
                    globalVertex.UV = createIPoint2(babylonMesh.uvs, i);
                    // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                    // While for Babylon, it corresponds to the lower left corner of a texture image
                    globalVertex.UV.Y = 1 - globalVertex.UV.Y;
                }
                if (hasUV2)
                {
                    globalVertex.UV2 = createIPoint2(babylonMesh.uvs2, i);
                    // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image
                    // While for Babylon, it corresponds to the lower left corner of a texture image
                    globalVertex.UV2.Y = 1 - globalVertex.UV2.Y;
                }
                if (hasColor)
                {
                    globalVertex.Color = createIPoint4(babylonMesh.colors, i).ToArray();
                }

                globalVertices.Add(globalVertex);
            }

            // Retreive indices from babylon mesh
            List <ushort> indices = new List <ushort>();

            indices = babylonMesh.indices.ToList().ConvertAll(new Converter <int, ushort>(n => (ushort)n));
            // Swap face side
            for (int i = 0; i < indices.Count; i += 3)
            {
                var tmp = indices[i];
                indices[i]     = indices[i + 2];
                indices[i + 2] = tmp;
            }


            // --------------------------
            // ------- Init glTF --------
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | Init glTF", 1);
            // Mesh
            var gltfMesh = new GLTFMesh {
                name = babylonMesh.name
            };

            gltfMesh.index = gltf.MeshesList.Count;
            gltf.MeshesList.Add(gltfMesh);
            gltfNode.mesh     = gltfMesh.index;
            gltfMesh.gltfNode = gltfNode;

            // MeshPrimitive
            var meshPrimitives = new List <GLTFMeshPrimitive>();
            var meshPrimitive  = new GLTFMeshPrimitive
            {
                attributes = new Dictionary <string, int>(),
                mode       = GLTFMeshPrimitive.FillMode.TRIANGLES // TODO reteive info from babylon material
            };

            meshPrimitives.Add(meshPrimitive);

            // Buffer
            var buffer = new GLTFBuffer
            {
                uri = gltfMesh.name + ".bin"
            };

            buffer.index = gltf.BuffersList.Count;
            gltf.BuffersList.Add(buffer);

            // BufferView - Scalar
            var bufferViewScalar = new GLTFBufferView
            {
                name   = "bufferViewScalar",
                buffer = buffer.index,
                Buffer = buffer
            };

            bufferViewScalar.index = gltf.BufferViewsList.Count;
            gltf.BufferViewsList.Add(bufferViewScalar);

            // BufferView - Vector3
            var bufferViewFloatVec3 = new GLTFBufferView
            {
                name       = "bufferViewFloatVec3",
                buffer     = buffer.index,
                Buffer     = buffer,
                byteOffset = 0,
                byteStride = 12 // Field only defined for buffer views that contain vertex attributes. A vertex needs 3 * 4 bytes
            };

            bufferViewFloatVec3.index = gltf.BufferViewsList.Count;
            gltf.BufferViewsList.Add(bufferViewFloatVec3);

            // Accessor - Indices
            var accessorIndices = new GLTFAccessor
            {
                name          = "accessorIndices",
                bufferView    = bufferViewScalar.index,
                BufferView    = bufferViewScalar,
                componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                type          = GLTFAccessor.TypeEnum.SCALAR.ToString()
            };

            accessorIndices.index = gltf.AccessorsList.Count;
            gltf.AccessorsList.Add(accessorIndices);
            meshPrimitive.indices = accessorIndices.index;

            // Accessor - Positions
            var accessorPositions = new GLTFAccessor
            {
                name          = "accessorPositions",
                bufferView    = bufferViewFloatVec3.index,
                BufferView    = bufferViewFloatVec3,
                componentType = GLTFAccessor.ComponentType.FLOAT,
                type          = GLTFAccessor.TypeEnum.VEC3.ToString(),
                min           = new float[] { float.MaxValue, float.MaxValue, float.MaxValue },
                max           = new float[] { float.MinValue, float.MinValue, float.MinValue }
            };

            accessorPositions.index = gltf.AccessorsList.Count;
            gltf.AccessorsList.Add(accessorPositions);
            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);

            // Accessor - Normals
            var accessorNormals = new GLTFAccessor
            {
                name          = "accessorNormals",
                bufferView    = bufferViewFloatVec3.index,
                BufferView    = bufferViewFloatVec3,
                componentType = GLTFAccessor.ComponentType.FLOAT,
                type          = GLTFAccessor.TypeEnum.VEC3.ToString()
            };

            accessorNormals.index = gltf.AccessorsList.Count;
            gltf.AccessorsList.Add(accessorNormals);
            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);

            // BufferView - Vector4
            GLTFBufferView bufferViewFloatVec4 = null;
            // Accessor - Colors
            GLTFAccessor accessorColors = null;

            if (hasColor)
            {
                bufferViewFloatVec4 = new GLTFBufferView
                {
                    name       = "bufferViewFloatVec4",
                    buffer     = buffer.index,
                    Buffer     = buffer,
                    byteOffset = 0,
                    byteStride = 16 // Field only defined for buffer views that contain vertex attributes. A vertex needs 4 * 4 bytes
                };
                bufferViewFloatVec4.index = gltf.BufferViewsList.Count;
                gltf.BufferViewsList.Add(bufferViewFloatVec4);

                accessorColors = new GLTFAccessor
                {
                    name          = "accessorColors",
                    bufferView    = bufferViewFloatVec4.index,
                    BufferView    = bufferViewFloatVec4,
                    componentType = GLTFAccessor.ComponentType.FLOAT,
                    type          = GLTFAccessor.TypeEnum.VEC4.ToString()
                };
                accessorColors.index = gltf.AccessorsList.Count;
                gltf.AccessorsList.Add(accessorColors);
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
            }

            // BufferView - Vector2
            GLTFBufferView bufferViewFloatVec2 = null;

            if (hasUV || hasUV2)
            {
                bufferViewFloatVec2 = new GLTFBufferView
                {
                    name       = "bufferViewFloatVec2",
                    buffer     = buffer.index,
                    Buffer     = buffer,
                    byteStride = 8 // Field only defined for buffer views that contain vertex attributes. A vertex needs 2 * 4 bytes
                };
                bufferViewFloatVec2.index = gltf.BufferViewsList.Count;
                gltf.BufferViewsList.Add(bufferViewFloatVec2);
            }

            // Accessor - UV
            GLTFAccessor accessorUVs = null;

            if (hasUV)
            {
                accessorUVs = new GLTFAccessor
                {
                    name          = "accessorUVs",
                    bufferView    = bufferViewFloatVec2.index,
                    BufferView    = bufferViewFloatVec2,
                    componentType = GLTFAccessor.ComponentType.FLOAT,
                    type          = GLTFAccessor.TypeEnum.VEC2.ToString()
                };
                accessorUVs.index = gltf.AccessorsList.Count;
                gltf.AccessorsList.Add(accessorUVs);
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
            }

            // Accessor - UV2
            GLTFAccessor accessorUV2s = null;

            if (hasUV2)
            {
                accessorUV2s = new GLTFAccessor
                {
                    name          = "accessorUV2s",
                    bufferView    = bufferViewFloatVec2.index,
                    BufferView    = bufferViewFloatVec2,
                    componentType = GLTFAccessor.ComponentType.FLOAT,
                    type          = GLTFAccessor.TypeEnum.VEC2.ToString()
                };
                accessorUV2s.index = gltf.AccessorsList.Count;
                gltf.AccessorsList.Add(accessorUV2s);
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
            }


            // --------------------------
            // ------ Mesh as glTF ------
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 1);
            // Material
            //TODO - Handle multimaterials
            GLTFMaterial gltfMaterial = gltf.MaterialsList.Find(material => material.id == babylonMesh.materialId);

            if (gltfMaterial != null)
            {
                meshPrimitive.material = gltfMaterial.index;
            }

            // Update min and max vertex position for each component (X, Y, Z)
            globalVertices.ForEach((globalVertex) =>
            {
                var positionArray = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
                for (int indexComponent = 0; indexComponent < positionArray.Length; indexComponent++)
                {
                    if (positionArray[indexComponent] < accessorPositions.min[indexComponent])
                    {
                        accessorPositions.min[indexComponent] = positionArray[indexComponent];
                    }
                    if (positionArray[indexComponent] > accessorPositions.max[indexComponent])
                    {
                        accessorPositions.max[indexComponent] = positionArray[indexComponent];
                    }
                }
            });

            // Update byte length and count of accessors, bufferViews and buffers
            // Scalar
            AddElementsToAccessor(accessorIndices, indices.Count);
            // Vector3
            bufferViewFloatVec3.byteOffset = buffer.byteLength;
            AddElementsToAccessor(accessorPositions, globalVertices.Count);
            AddElementsToAccessor(accessorNormals, globalVertices.Count);
            // Vector4
            if (hasColor)
            {
                bufferViewFloatVec4.byteOffset = buffer.byteLength;
                AddElementsToAccessor(accessorColors, globalVertices.Count);
            }
            // Vector2
            if (hasUV || hasUV2)
            {
                bufferViewFloatVec2.byteOffset = buffer.byteLength;

                if (hasUV)
                {
                    AddElementsToAccessor(accessorUVs, globalVertices.Count);
                }
                if (hasUV2)
                {
                    AddElementsToAccessor(accessorUV2s, globalVertices.Count);
                }
            }


            // --------------------------
            // --------- Saving ---------
            // --------------------------

            string outputBinaryFile = Path.Combine(gltf.OutputPath, gltfMesh.name + ".bin");

            RaiseMessage("GLTFExporter.Mesh | Saving " + outputBinaryFile, 1);

            using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
            {
                // Binary arrays
                List <float> vertices = globalVertices.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
                List <float> normals  = globalVertices.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();

                List <float> colors = new List <float>();
                if (hasColor)
                {
                    colors = globalVertices.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                }

                List <float> uvs = new List <float>();
                if (hasUV)
                {
                    uvs = globalVertices.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList(); // No symetry required to perform 3dsMax => gltf conversion
                }

                List <float> uvs2 = new List <float>();
                if (hasUV2)
                {
                    uvs2 = globalVertices.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList(); // No symetry required to perform 3dsMax => gltf conversion
                }

                // Write data to binary file
                indices.ForEach(n => writer.Write(n));
                vertices.ForEach(n => writer.Write(n));
                normals.ForEach(n => writer.Write(n));
                colors.ForEach(n => writer.Write(n));
                uvs.ForEach(n => writer.Write(n));
            }

            gltfMesh.primitives = meshPrimitives.ToArray();

            return(gltfMesh);
        }
        // TODO - Test if ok with a gltf viewer working with custom camera (babylon loader/sandbox doesn't load them)
        private GLTFCamera ExportCamera(BabylonCamera babylonCamera, GLTF gltf, GLTFNode gltfParentNode)
        {
            RaiseMessage("GLTFExporter.Camera | Export camera named: " + babylonCamera.name, 1);

            // --------------------------
            // ---------- Node ----------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | Node", 2);
            // Node
            var gltfNode = new GLTFNode();

            gltfNode.name  = babylonCamera.name;
            gltfNode.index = gltf.NodesList.Count;
            gltf.NodesList.Add(gltfNode);

            // Hierarchy
            if (gltfParentNode != null)
            {
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as child to " + gltfParentNode.name, 3);
                gltfParentNode.ChildrenList.Add(gltfNode.index);
            }
            else
            {
                // It's a root node
                // Only root nodes are listed in a gltf scene
                RaiseMessage("GLTFExporter.Camera | Add " + babylonCamera.name + " as root node to scene", 3);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

            // Transform
            gltfNode.translation = babylonCamera.position;
            // Switch from left to right handed coordinate system
            //gltfNode.translation[0] *= -1;
            if (babylonCamera.rotationQuaternion != null)
            {
                gltfNode.rotation = babylonCamera.rotationQuaternion;
            }
            else
            {
                // Convert rotation vector to quaternion
                BabylonVector3 rotationVector3 = new BabylonVector3
                {
                    X = babylonCamera.rotation[0],
                    Y = babylonCamera.rotation[1],
                    Z = babylonCamera.rotation[2]
                };
                gltfNode.rotation = rotationVector3.toQuaternion().ToArray();
            }
            // No scaling defined for babylon camera. Use identity instead.
            gltfNode.scale = new float[3] {
                1, 1, 1
            };


            // --- prints ---

            RaiseMessage("GLTFExporter.Camera | babylonCamera data", 2);
            RaiseMessage("GLTFExporter.Camera | babylonCamera.type=" + babylonCamera.type, 3);
            RaiseMessage("GLTFExporter.Camera | babylonCamera.fov=" + babylonCamera.fov, 3);
            RaiseMessage("GLTFExporter.Camera | babylonCamera.maxZ=" + babylonCamera.maxZ, 3);
            RaiseMessage("GLTFExporter.Camera | babylonCamera.minZ=" + babylonCamera.minZ, 3);


            // --------------------------
            // ------- gltfCamera -------
            // --------------------------

            RaiseMessage("GLTFExporter.Camera | create gltfCamera", 2);

            // Camera
            var gltfCamera = new GLTFCamera {
                name = babylonCamera.name
            };

            gltfCamera.index = gltf.CamerasList.Count;
            gltf.CamerasList.Add(gltfCamera);
            gltfNode.camera     = gltfCamera.index;
            gltfCamera.gltfNode = gltfNode;

            // Camera type
            switch (babylonCamera.mode)
            {
            case (BabylonCamera.CameraMode.ORTHOGRAPHIC_CAMERA):
                var gltfCameraOrthographic = new GLTFCameraOrthographic();
                gltfCameraOrthographic.xmag  = 1;    // TODO - How to retreive value from babylon? xmag:The floating-point horizontal magnification of the view
                gltfCameraOrthographic.ymag  = 1;    // TODO - How to retreive value from babylon? ymag:The floating-point vertical magnification of the view
                gltfCameraOrthographic.zfar  = babylonCamera.maxZ;
                gltfCameraOrthographic.znear = babylonCamera.minZ;

                gltfCamera.type         = GLTFCamera.CameraType.orthographic.ToString();
                gltfCamera.orthographic = gltfCameraOrthographic;
                break;

            case (BabylonCamera.CameraMode.PERSPECTIVE_CAMERA):
                var gltfCameraPerspective = new GLTFCameraPerspective();
                gltfCameraPerspective.aspectRatio = null;              // 0.8f; // TODO - How to retreive value from babylon? The aspect ratio in babylon is computed based on the engine rather than set on a camera (aspectRatio = _gl.drawingBufferWidth / _gl.drawingBufferHeight)
                gltfCameraPerspective.yfov        = babylonCamera.fov; // WARNING - Babylon camera fov mode is assumed to be vertical (FOVMODE_VERTICAL_FIXED)
                gltfCameraPerspective.zfar        = babylonCamera.maxZ;
                gltfCameraPerspective.znear       = babylonCamera.minZ;

                gltfCamera.type        = GLTFCamera.CameraType.perspective.ToString();
                gltfCamera.perspective = gltfCameraPerspective;
                break;

            default:
                RaiseError("GLTFExporter.Camera | camera mode not found");
                break;
            }

            return(gltfCamera);
        }