private GLTFNode ExportMesh(BabylonMesh babylonMesh, GLTF gltf, GLTFNode gltfParentNode, BabylonScene babylonScene)
        {
            RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);

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

            RaiseMessage("GLTFExporter.Mesh | Node", 2);
            // 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, 3);
                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", 3);
                gltf.scenes[0].NodesList.Add(gltfNode.index);
            }

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


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

            if (babylonMesh.positions == null)
            {
                RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
                return(gltfNode);
            }

            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
            // 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, 3);
            RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
            RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
            RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);

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

            for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++)
            {
                GLTFGlobalVertex globalVertex = new GLTFGlobalVertex();
                globalVertex.Position = createIPoint3(babylonMesh.positions, indexVertex);
                // Switch from left to right handed coordinate system
                //globalVertex.Position.X *= -1;
                globalVertex.Normal = createIPoint3(babylonMesh.normals, indexVertex);
                if (hasUV)
                {
                    globalVertex.UV = createIPoint2(babylonMesh.uvs, indexVertex);
                    // 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, indexVertex);
                    // 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, indexVertex).ToArray();
                }

                globalVertices.Add(globalVertex);
            }

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

            babylonIndices = babylonMesh.indices.ToList().ConvertAll(new Converter <int, ushort>(n => (ushort)n));
            // For triangle primitives in gltf, the front face has a counter-clockwise (CCW) winding order
            // Swap face side
            //for (int i = 0; i < babylonIndices.Count; i += 3)
            //{
            //    var tmp = babylonIndices[i];
            //    babylonIndices[i] = babylonIndices[i + 2];
            //    babylonIndices[i + 2] = tmp;
            //}


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

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

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

            // 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);

            // BufferView - Vector4
            GLTFBufferView bufferViewFloatVec4 = 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);
            }

            // 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);
            }

            // --------------------------
            // ---- glTF primitives -----
            // --------------------------

            RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2);
            var meshPrimitives = new List <GLTFMeshPrimitive>();

            // Global vertices are sorted per submesh
            var globalVerticesSubMeshes = new List <List <GLTFGlobalVertex> >();

            // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0)
            // Thus, the gltf indices list is a concatenation of sub lists all 0-based
            // Example for 2 triangles, each being a submesh:
            //      babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2}
            var gltfIndices = new List <ushort>();

            foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
            {
                // --------------------------
                // ------ SubMesh data ------
                // --------------------------

                List <GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount);
                globalVerticesSubMeshes.Add(globalVerticesSubMesh);

                List <ushort> _indices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
                // Indices of this submesh / primitive are updated to be 0-based
                var minIndiceValue = _indices.Min(); // Should be equal to babylonSubMesh.indexStart
                for (int indexIndice = 0; indexIndice < _indices.Count; indexIndice++)
                {
                    _indices[indexIndice] -= minIndiceValue;
                }
                gltfIndices.AddRange(_indices);

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

                // MeshPrimitive
                var meshPrimitive = new GLTFMeshPrimitive
                {
                    attributes = new Dictionary <string, int>()
                };
                meshPrimitives.Add(meshPrimitive);

                // 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);

                // Accessor - Colors
                GLTFAccessor accessorColors = null;
                if (hasColor)
                {
                    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);
                }

                // 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);
                }


                // --------------------------
                // - Update glTF primitive --
                // --------------------------

                RaiseMessage("GLTFExporter.Mesh | Mesh as glTF", 3);

                // Material
                if (babylonMesh.materialId != null)
                {
                    // Retreive the babylon material
                    var babylonMaterialId = babylonMesh.materialId;
                    var babylonMaterials  = new List <BabylonMaterial>(babylonScene.materials);
                    var babylonMaterial   = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
                    if (babylonMaterial == null)
                    {
                        // It's a multi material
                        var babylonMultiMaterials = new List <BabylonMultiMaterial>(babylonScene.multiMaterials);
                        var babylonMultiMaterial  = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMesh.materialId);
                        babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
                        babylonMaterial   = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);
                    }

                    // Update primitive material index
                    var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
                    if (indexMaterial == -1)
                    {
                        // Store material for exportation
                        indexMaterial = babylonMaterialsToExport.Count;
                        babylonMaterialsToExport.Add(babylonMaterial);
                    }
                    meshPrimitive.material = indexMaterial;

                    // TODO - Add and retreive info from babylon material
                    meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES;
                }

                // Update min and max vertex position for each component (X, Y, Z)
                globalVerticesSubMesh.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
                AddElementsToAccessor(accessorPositions, globalVerticesSubMesh.Count);
                AddElementsToAccessor(accessorNormals, globalVerticesSubMesh.Count);
                // Vector4
                if (hasColor)
                {
                    AddElementsToAccessor(accessorColors, globalVerticesSubMesh.Count);
                }
                // Vector2
                if (hasUV)
                {
                    AddElementsToAccessor(accessorUVs, globalVerticesSubMesh.Count);
                }
                if (hasUV2)
                {
                    AddElementsToAccessor(accessorUV2s, globalVerticesSubMesh.Count);
                }
            }
            gltfMesh.primitives = meshPrimitives.ToArray();

            // Update byte offset of bufferViews
            GLTFBufferView lastBufferView = null;

            gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
            {
                if (lastBufferView != null)
                {
                    bufferView.byteOffset = lastBufferView.byteOffset + lastBufferView.byteLength;
                }
                lastBufferView = bufferView;
            });


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

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

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

            // Write data to binary file
            using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
            {
                // BufferView - Scalar
                gltfIndices.ForEach(n => writer.Write(n));

                // BufferView - Vector3
                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
                {
                    List <float> vertices = globalVerticesSubMesh.SelectMany(v => new[] { v.Position.X, v.Position.Y, v.Position.Z }).ToList();
                    vertices.ForEach(n => writer.Write(n));

                    List <float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
                    normals.ForEach(n => writer.Write(n));
                });

                // BufferView - Vector4
                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
                {
                    if (hasColor)
                    {
                        List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                        colors.ForEach(n => writer.Write(n));
                    }
                });

                // BufferView - Vector2
                globalVerticesSubMeshes.ForEach(globalVerticesSubMesh =>
                {
                    if (hasUV)
                    {
                        List <float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
                        uvs.ForEach(n => writer.Write(n));
                    }

                    if (hasUV2)
                    {
                        List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
                        uvs2.ForEach(n => writer.Write(n));
                    }
                });
            }

            return(gltfNode);
        }
        // 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.toQuaternionGltf().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);
        }