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);
        }
        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
        {
            RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);

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

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

            RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
            // Retreive general data from babylon mesh
            int  nbVertices    = babylonMesh.positions.Length / 3;
            bool hasTangents   = babylonMesh.tangents != null && babylonMesh.tangents.Length > 0;
            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;
            bool hasBones      = babylonMesh.matricesIndices != null && babylonMesh.matricesIndices.Length > 0;
            bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.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);
            RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3);
            RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 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 = BabylonVector3.FromArray(babylonMesh.positions, indexVertex);
                globalVertex.Normal   = BabylonVector3.FromArray(babylonMesh.normals, indexVertex);
                if (hasTangents)
                {
                    globalVertex.Tangent = BabylonQuaternion.FromArray(babylonMesh.tangents, indexVertex);

                    // Switch coordinate system at object level
                    globalVertex.Tangent.Z *= -1;

                    // Invert W to switch to right handed system
                    globalVertex.Tangent.W *= -1;
                }

                // Switch coordinate system at object level
                globalVertex.Position.Z *= -1;
                globalVertex.Normal.Z   *= -1;

                if (hasUV)
                {
                    globalVertex.UV = BabylonVector2.FromArray(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 = BabylonVector2.FromArray(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 = Tools.SubArrayFromEntity(babylonMesh.colors, indexVertex, 4);
                }
                if (hasBones)
                {
                    // In babylon, the 4 bones indices are stored in a single int
                    // Each bone index is 8-bit offset from the next
                    int bonesIndicesMerged = babylonMesh.matricesIndices[indexVertex];
                    int bone3 = bonesIndicesMerged >> 24;
                    bonesIndicesMerged -= bone3 << 24;
                    int bone2 = bonesIndicesMerged >> 16;
                    bonesIndicesMerged -= bone2 << 16;
                    int bone1 = bonesIndicesMerged >> 8;
                    bonesIndicesMerged -= bone1 << 8;
                    int bone0 = bonesIndicesMerged >> 0;
                    bonesIndicesMerged -= bone0 << 0;
                    var bonesIndicesArray = new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 };
                    globalVertex.BonesIndices = bonesIndicesArray;
                    globalVertex.BonesWeights = Tools.SubArrayFromEntity(babylonMesh.matricesWeights, indexVertex, 4);
                }

                globalVertices.Add(globalVertex);
            }

            var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);

            // Retrieve indices from babylon mesh
            List <int> babylonIndices = babylonMesh.indices.ToList();

            // --------------------------
            // ------- 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);
            gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
            if (hasBones)
            {
                gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId;
            }

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

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

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

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

                var gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
                // 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 minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
                for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
                {
                    gltfIndices[indexIndice] -= minIndiceValue;
                }

                // --------------------------
                // ----- Mesh primitive -----
                // --------------------------

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

                // Material
                if (babylonMesh.materialId != null)
                {
                    RaiseMessage("GLTFExporter.Mesh | Material", 3);
                    // Retreive the babylon material
                    BabylonMaterial babylonMaterial;
                    var             babylonMaterialId = babylonMesh.materialId;
                    // From multi materials first, if any
                    // Loop recursively even though it shouldn't be a real use case
                    var babylonMultiMaterials = new List <BabylonMultiMaterial>(babylonScene.multiMaterials);
                    BabylonMultiMaterial babylonMultiMaterial;
                    do
                    {
                        babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMaterialId);
                        if (babylonMultiMaterial != null)
                        {
                            babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex];
                        }
                    }while (babylonMultiMaterial != null);
                    // Then from materials
                    var babylonMaterials = new List <BabylonMaterial>(babylonScene.materials);
                    babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId);

                    // If babylon material was exported successfully
                    if (babylonMaterial != null)
                    {
                        // Update primitive material index
                        var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial);
                        if (indexMaterial == -1)
                        {
                            // Store material for export
                            indexMaterial = babylonMaterialsToExport.Count;
                            babylonMaterialsToExport.Add(babylonMaterial);
                        }
                        meshPrimitive.material = indexMaterial;
                    }

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

                // --------------------------
                // ------- Accessors --------
                // --------------------------

                RaiseMessage("GLTFExporter.Mesh | Geometry", 3);

                // Buffer
                var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

                // --- Indices ---
                var componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT;
                if (nbVertices >= 65536)
                {
                    componentType = GLTFAccessor.ComponentType.UNSIGNED_INT;
                }
                var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
                    "accessorIndices",
                    componentType,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                meshPrimitive.indices = accessorIndices.index;
                // Populate accessor
                if (componentType == GLTFAccessor.ComponentType.UNSIGNED_INT)
                {
                    gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                }
                else
                {
                    var gltfIndicesShort = gltfIndices.ConvertAll(new Converter <int, ushort>(n => (ushort)n));
                    gltfIndicesShort.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                }
                accessorIndices.count = gltfIndices.Count;

                // --- Positions ---
                var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorPositions",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
                // Populate accessor
                accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                globalVerticesSubMesh.ForEach((globalVertex) =>
                {
                    var positions = globalVertex.Position.ToArray();
                    // Store values as bytes
                    foreach (var position in positions)
                    {
                        accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
                    }
                    // Update min and max values
                    GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
                });
                accessorPositions.count = globalVerticesSubMesh.Count;

                // --- Normals ---
                var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorNormals",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
                // Populate accessor
                List <float> normals = globalVerticesSubMesh.SelectMany(v => v.Normal.ToArray()).ToList();
                normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorNormals.count = globalVerticesSubMesh.Count;

                // --- Tangents ---
                if (hasTangents)
                {
                    var accessorTangents = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorTangents",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTangents.index);
                    // Populate accessor
                    List <float> tangents = globalVerticesSubMesh.SelectMany(v => v.Tangent.ToArray()).ToList();
                    tangents.ForEach(n => accessorTangents.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorTangents.count = globalVerticesSubMesh.Count;
                }

                // --- Colors ---
                if (hasColor)
                {
                    var accessorColors = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorColors",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
                    // Populate accessor
                    List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                    colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorColors.count = globalVerticesSubMesh.Count;
                }

                // --- UV ---
                if (hasUV)
                {
                    var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUVs",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
                    // Populate accessor
                    List <float> uvs = globalVerticesSubMesh.SelectMany(v => v.UV.ToArray()).ToList();
                    uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUVs.count = globalVerticesSubMesh.Count;
                }

                // --- UV2 ---
                if (hasUV2)
                {
                    var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUV2s",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
                    // Populate accessor
                    List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => v.UV2.ToArray()).ToList();
                    uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUV2s.count = globalVerticesSubMesh.Count;
                }

                // --- Bones ---
                if (hasBones)
                {
                    RaiseMessage("GLTFExporter.Mesh | Bones", 3);
                    // --- Joints ---
                    var accessorJoints = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
                        "accessorJoints",
                        GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index);
                    // Populate accessor
                    List <ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList();
                    joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorJoints.count = globalVerticesSubMesh.Count;

                    // --- Weights ---
                    var accessorWeights = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorWeights",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index);
                    // Populate accessor
                    List <float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList();
                    weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorWeights.count = globalVerticesSubMesh.Count;
                }

                if (hasBonesExtra)
                {
                    RaiseWarning("Too many bones influences per vertex. glTF only support up to 4 bones influences per vertex. The result may not be as expected.", 3);
                }

                // Morph targets positions and normals
                if (babylonMorphTargetManager != null)
                {
                    RaiseMessage("GLTFExporter.Mesh | Morph targets", 3);
                    _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive);
                }
            }
            gltfMesh.primitives = meshPrimitives.ToArray();

            // Morph targets weights
            if (babylonMorphTargetManager != null)
            {
                var weights = new List <float>();
                foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets)
                {
                    weights.Add(babylonMorphTarget.influence);
                }
                gltfMesh.weights = weights.ToArray();
            }

            return(gltfMesh);
        }
Пример #3
0
        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
        {
            RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);

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

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

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


            // --------------------------
            // ------- 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);
            gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
            var weights = new List <float>();

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

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

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

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

                List <ushort> gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
                // 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 minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
                for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
                {
                    gltfIndices[indexIndice] -= minIndiceValue;
                }

                // --------------------------
                // ----- Mesh primitive -----
                // --------------------------

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

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

                // --------------------------
                // ------- Accessors --------
                // --------------------------

                // Buffer
                var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

                // --- Indices ---
                var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
                    "accessorIndices",
                    GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                    GLTFAccessor.TypeEnum.SCALAR
                    );
                meshPrimitive.indices = accessorIndices.index;
                // Populate accessor
                gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorIndices.count = gltfIndices.Count;

                // --- Positions ---
                var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorPositions",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
                // Populate accessor
                accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                globalVerticesSubMesh.ForEach((globalVertex) =>
                {
                    var positions = new float[] { globalVertex.Position.X, globalVertex.Position.Y, globalVertex.Position.Z };
                    // Store values as bytes
                    foreach (var position in positions)
                    {
                        accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
                    }
                    // Update min and max values
                    GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
                });
                accessorPositions.count = globalVerticesSubMesh.Count;

                // --- Normals ---
                var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
                    gltf,
                    GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                    "accessorNormals",
                    GLTFAccessor.ComponentType.FLOAT,
                    GLTFAccessor.TypeEnum.VEC3
                    );
                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);
                // Populate accessor
                List <float> normals = globalVerticesSubMesh.SelectMany(v => new[] { v.Normal.X, v.Normal.Y, v.Normal.Z }).ToList();
                normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorNormals.count = globalVerticesSubMesh.Count;

                // --- Colors ---
                if (hasColor)
                {
                    var accessorColors = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                        "accessorColors",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC4
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
                    // Populate accessor
                    List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                    colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorColors.count = globalVerticesSubMesh.Count;
                }

                // --- UV ---
                if (hasUV)
                {
                    var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUVs",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
                    // Populate accessor
                    List <float> uvs = globalVerticesSubMesh.SelectMany(v => new[] { v.UV.X, v.UV.Y }).ToList();
                    uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUVs.count = globalVerticesSubMesh.Count;
                }

                // --- UV2 ---
                if (hasUV2)
                {
                    var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                        "accessorUV2s",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC2
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
                    // Populate accessor
                    List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => new[] { v.UV2.X, v.UV2.Y }).ToList();
                    uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
                    accessorUV2s.count = globalVerticesSubMesh.Count;
                }

                // Morph targets
                var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);
                if (babylonMorphTargetManager != null)
                {
                    _exportMorphTargets(babylonMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive, weights);
                }
            }
            gltfMesh.primitives = meshPrimitives.ToArray();
            if (weights.Count > 0)
            {
                gltfMesh.weights = weights.ToArray();
            }

            return(gltfMesh);
        }
Пример #4
0
        private GLTFSkin ExportSkin(BabylonSkeleton babylonSkeleton, GLTF gltf, GLTFNode gltfNode, GLTFMesh gltfMesh)
        {
            logger.RaiseMessage("GLTFExporter.Skin | Export skin of node '" + gltfNode.name + "' based on skeleton '" + babylonSkeleton.name + "'", 2);

            // Retreive gltf skeleton data if babylon skeleton has already been exported
            if (!alreadyExportedSkeletons.ContainsKey(babylonSkeleton))
            {
                alreadyExportedSkeletons.Add(babylonSkeleton, new BabylonSkeletonExportData());

                // Switch coordinate system at object level
                foreach (var babylonBone in babylonSkeleton.bones)
                {
                    var boneLocalMatrix = new BabylonMatrix();
                    boneLocalMatrix.m = babylonBone.matrix;

                    var translationBabylon  = new BabylonVector3();
                    var rotationQuatBabylon = new BabylonQuaternion();
                    var scale = new BabylonVector3();
                    boneLocalMatrix.decompose(scale, rotationQuatBabylon, translationBabylon);
                    translationBabylon    *= exportParameters.scaleFactor;
                    translationBabylon.Z  *= -1;
                    rotationQuatBabylon.X *= -1;
                    rotationQuatBabylon.Y *= -1;
                    boneLocalMatrix        = BabylonMatrix.Compose(scale, rotationQuatBabylon, translationBabylon);

                    babylonBone.matrix = boneLocalMatrix.m;
                }
            }
            var babylonSkeletonExportData = alreadyExportedSkeletons[babylonSkeleton];

            // Skin

            // if this mesh is sharing a skin with another mesh, use the already exported skin
            var sharedSkinnedMeshesByOriginalPair = sharedSkinnedMeshesByOriginal.Where(skinSharingMeshPair => skinSharingMeshPair.Value.Contains(gltfMesh)).Select(kvp => (KeyValuePair <GLTFMesh, List <GLTFMesh> >?)kvp).FirstOrDefault();

            if (sharedSkinnedMeshesByOriginalPair != null)
            {
                logger.RaiseMessage("GLTFExporter.Skin | Sharing skinning information from mesh '" + sharedSkinnedMeshesByOriginalPair.Value.Key.name + "'", 3);
                var skeletonExportData = alreadyExportedSkeletons[babylonSkeleton];
                gltfNode.skin = skeletonExportData.skinIndex;
                return(gltf.SkinsList[(int)gltfNode.skin]);
            }

            // otherwise create a new GLTFSkin
            var      nameSuffix = babylonSkeletonExportData.nb != 0 ? "_" + babylonSkeletonExportData.nb : "";
            GLTFSkin gltfSkin   = new GLTFSkin
            {
                name = babylonSkeleton.name + nameSuffix
            };

            gltfSkin.index = gltf.SkinsList.Count;
            gltf.SkinsList.Add(gltfSkin);
            babylonSkeletonExportData.nb++;
            babylonSkeletonExportData.skinIndex = gltfSkin.index;

            var bones = new List <BabylonBone>(babylonSkeleton.bones);

            // Compute and store world matrix of each bone
            var bonesWorldMatrices = new Dictionary <int, BabylonMatrix>();

            foreach (var babylonBone in babylonSkeleton.bones)
            {
                if (!bonesWorldMatrices.ContainsKey(babylonBone.index))
                {
                    var           nodePair        = nodeToGltfNodeMap.First(pair => pair.Key.id.Equals(babylonBone.id));
                    BabylonMatrix boneWorldMatrix = _getNodeWorldMatrix(nodePair.Value);
                    bonesWorldMatrices.Add(babylonBone.index, boneWorldMatrix);
                }
            }

            // Buffer
            var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

            // Accessor - InverseBindMatrices
            var accessorInverseBindMatrices = GLTFBufferService.Instance.CreateAccessor(
                gltf,
                GLTFBufferService.Instance.GetBufferViewFloatMat4(gltf, buffer),
                "accessorInverseBindMatrices",
                GLTFAccessor.ComponentType.FLOAT,
                GLTFAccessor.TypeEnum.MAT4
                );

            gltfSkin.inverseBindMatrices = accessorInverseBindMatrices.index;

            // World matrix of the node
            var nodeWorldMatrix = _getNodeWorldMatrix(gltfNode);

            var gltfJoints = new List <int>();

            foreach (var babylonBone in babylonSkeleton.bones)
            {
                GLTFNode gltfBoneNode = null;
                if (!babylonSkeletonExportData.nodeByBone.ContainsKey(babylonBone))
                {
                    // Export bone as a new node
                    gltfBoneNode = nodeToGltfNodeMap.FirstOrDefault(pair => pair.Key.id.Equals(babylonBone.id)).Value;//_exportBone(babylonBone, gltf, babylonSkeleton, bones);
                    babylonSkeletonExportData.nodeByBone.Add(babylonBone, gltfBoneNode);
                }
                gltfBoneNode = babylonSkeletonExportData.nodeByBone[babylonBone];

                gltfJoints.Add(gltfBoneNode.index);

                // Set this bone as skeleton if it is a root
                // Meaning of 'skeleton' here is the top root bone
                if (babylonBone.parentBoneIndex == -1)
                {
                    gltfSkin.skeleton = gltfBoneNode.index;
                }

                // Compute inverseBindMatrice for this bone when attached to this node
                var boneLocalMatrix = new BabylonMatrix();
                boneLocalMatrix.m = babylonBone.matrix;
                //printMatrix("boneLocalMatrix[" + babylonBone.name + "]", boneLocalMatrix);

                BabylonMatrix boneWorldMatrix = null;
                if (babylonBone.parentBoneIndex == -1)
                {
                    boneWorldMatrix = boneLocalMatrix;
                }
                else
                {
                    var parentWorldMatrix = bonesWorldMatrices[babylonBone.parentBoneIndex];
                    // Remove scale of parent
                    // This actually enable to take into account the scale of the bones, except for the root one
                    parentWorldMatrix = _removeScale(parentWorldMatrix);

                    boneWorldMatrix = boneLocalMatrix * parentWorldMatrix;
                }
                //printMatrix("boneWorldMatrix[" + babylonBone.name + "]", boneWorldMatrix);

                var inverseBindMatrices = nodeWorldMatrix * BabylonMatrix.Invert(boneWorldMatrix);

                // Populate accessor
                List <float> matrix = new List <float>(inverseBindMatrices.m);
                matrix.ForEach(n => accessorInverseBindMatrices.bytesList.AddRange(BitConverter.GetBytes(n)));
                accessorInverseBindMatrices.count++;
            }
            gltfSkin.joints = gltfJoints.ToArray();

            ExportGLTFExtension(babylonSkeleton, ref gltfNode, gltf);

            return(gltfSkin);
        }
Пример #5
0
        public static GLTFRoot ExportModel(string path)
        {
            using var stream = File.OpenRead(path);
            var pokemonModel   = PokemonModel.GetRootAsPokemonModel(stream.ToByteBuffer());
            var bufferRoot     = Path.GetDirectoryName(path);
            var bufferRootFile = Path.Combine(bufferRoot, $"{Path.GetFileNameWithoutExtension(path)}.mdlbin");

            using var buffer = File.OpenWrite(bufferRootFile);
            buffer.SetLength(0);

            var gltfRoot = new GLTFRoot
            {
                Asset = new GLTFAsset
                {
                    Generator = "GFLX",
                    Copyright = "2019 GameFreak - Pokemon Company",
                    Version   = "2.0"
                }
            };
            var gltfBuffer = gltfRoot.Buffers.Count;
            var materials  = DeconstructMaterials(pokemonModel, gltfRoot);

            var scene = new GLTFScene();

            for (var i = 0; i < pokemonModel.VisualGroupLength; ++i)
            {
                var group      = pokemonModel.VisualGroup(i).GetValueOrDefault();
                var mesh       = pokemonModel.Meshes((int)group.MeshId).GetValueOrDefault();
                var bone       = pokemonModel.Bones((int)group.BoneId).GetValueOrDefault();
                var attributes = DeconstructVbo(mesh, gltfRoot, buffer, gltfBuffer);
                var node       = new GLTFMesh
                {
                    Name = bone.Name
                };
                for (var j = 0; j < mesh.IndiceBufferLength; ++j)
                {
                    var primitive = mesh.IndiceBuffer(j).GetValueOrDefault();
                    node.Primitives.Add(new GLTFMeshPrimitive
                    {
                        Attributes = attributes,
                        Indices    = gltfRoot.Accessors.Count,
                        Mode       = GLTFDrawMode.Triangles,
                        Material   = materials[primitive.MaterialId]
                    });
                    gltfRoot.Accessors.Add(new GLTFAccessor
                    {
                        BufferView    = gltfRoot.BufferViews.Count,
                        ByteOffset    = 0,
                        ComponentType = GLTFComponentType.UnsignedShort,
                        Count         = (uint)primitive.IndicesLength,
                        Type          = GLTFAccessorAttributeType.SCALAR
                    });
                    var targetBuffer = MemoryMarshal.Cast <ushort, byte>(primitive.GetIndicesBytes());
                    gltfRoot.BufferViews.Add(new GLTFBufferView
                    {
                        Buffer     = gltfBuffer,
                        ByteOffset = (uint)buffer.Position,
                        ByteLength = (uint)targetBuffer.Length
                    });
                    buffer.Write(targetBuffer);
                }

                scene.Nodes.Add(gltfRoot.Nodes.Count);
                gltfRoot.Nodes.Add(new GLTFNode
                {
                    Mesh = gltfRoot.Meshes.Count,
                    Name = node.Name
                });
                gltfRoot.Meshes.Add(node);
            }

            gltfRoot.Scenes.Add(scene);
            gltfRoot.Buffers.Add(new GLTFBuffer
            {
                Uri        = Path.GetFileName(bufferRootFile),
                ByteLength = (uint)buffer.Length
            });

            return(gltfRoot);
        }
Пример #6
0
        private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene)
        {
            logger.RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1);

            // --------------------------
            // --- Mesh from babylon ----
            // --------------------------
            // get direct access to mesh data or use geometryId to do so
            IBabylonMeshData meshData = babylonMesh.geometryId == null ? babylonMesh : (IBabylonMeshData)babylonScene.geometries.Get(babylonMesh.geometryId) ?? babylonMesh;

            if (meshData.positions == null || meshData.positions.Length == 0)
            {
                logger.RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2);
                return(null);
            }

            logger.RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2);
            // Retreive general data from babylon mesh
            int  nbVertices  = meshData.positions.Length / 3;
            bool hasUV       = meshData.uvs != null && meshData.uvs.Length > 0;
            bool hasUV2      = meshData.uvs2 != null && meshData.uvs2.Length > 0;
            bool hasColor    = meshData.colors != null && meshData.colors.Length > 0;
            bool hasBones    = meshData.matricesIndices != null && meshData.matricesIndices.Length > 0;
            bool hasTangents = meshData.tangents != null && meshData.tangents.Length > 0;
            bool hasNormals  = meshData.normals != null && meshData.normals.Length > 0;

            bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.Length > 0;
            bool hasMetadata   = babylonMesh.metadata != null && babylonMesh.metadata.Count > 0;

            logger.RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasMetadata=" + hasMetadata, 3);
            logger.RaiseMessage("GLTFExporter.Mesh | hasNormals=" + hasNormals, 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 = BabylonVector3.FromArray(meshData.positions, indexVertex);

                // Switch coordinate system at object level
                globalVertex.Position.Z *= -1;
                globalVertex.Position   *= exportParameters.scaleFactor;

                if (hasNormals)
                {
                    globalVertex.Normal    = BabylonVector3.FromArray(meshData.normals, indexVertex);
                    globalVertex.Normal.Z *= -1;
                }

                if (hasTangents)
                {
                    globalVertex.Tangent = BabylonQuaternion.FromArray(meshData.tangents, indexVertex);

                    // Switch coordinate system at object level
                    globalVertex.Tangent.Z *= -1;

                    // Invert W to switch to right handed system
                    globalVertex.Tangent.W *= -1;
                }

                if (hasUV)
                {
                    globalVertex.UV = BabylonVector2.FromArray(meshData.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 = BabylonVector2.FromArray(meshData.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 = ArrayExtension.SubArrayFromEntity(meshData.colors, indexVertex, 4);
                }
                if (hasBones)
                {
                    // In babylon, the 4 bones indices are stored in a single int
                    // Each bone index is 8-bit offset from the next
                    ushort[] unpackBabylonBonesToArray(uint babylonBoneIndices)
                    {
                        uint bone3 = babylonBoneIndices >> 24;
                        uint bone2 = (babylonBoneIndices << 8) >> 24;
                        uint bone1 = (babylonBoneIndices << 16) >> 24;
                        uint bone0 = (babylonBoneIndices << 24) >> 24;

                        babylonBoneIndices -= bone0 << 0;
                        return(new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 });
                    }

                    uint bonesIndicesMerged = (uint)meshData.matricesIndices[indexVertex];
                    globalVertex.BonesIndices = unpackBabylonBonesToArray(bonesIndicesMerged);
                    globalVertex.BonesWeights = ArrayExtension.SubArrayFromEntity(meshData.matricesWeights, indexVertex, 4);
                    void clearBoneUnusedIndices(ushort[] indices, float[] weights)
                    {
                        for (int i = 0; i < indices.Length; ++i)
                        {
                            // Zero out indices of unused joint weights to avoid ACCESSOR_JOINTS_USED_ZERO_WEIGHT.
                            if (MathUtilities.IsAlmostEqualTo(weights[i], 0, float.Epsilon))
                            {
                                indices[i] = 0;
                            }
                        }
                    }

                    clearBoneUnusedIndices(globalVertex.BonesIndices, globalVertex.BonesWeights);

                    if (hasBonesExtra)
                    {
                        uint bonesIndicesExtraMerged = (uint)meshData.matricesIndicesExtra[indexVertex];
                        globalVertex.BonesIndicesExtra = unpackBabylonBonesToArray(bonesIndicesExtraMerged);
                        globalVertex.BonesWeightsExtra = ArrayExtension.SubArrayFromEntity(meshData.matricesWeightsExtra, indexVertex, 4);

                        clearBoneUnusedIndices(globalVertex.BonesIndicesExtra, globalVertex.BonesWeightsExtra);
                    }
                }

                globalVertices.Add(globalVertex);
            }

            var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh);

            // Retreive indices from babylon mesh
            List <int> babylonIndices = meshData.indices.ToList();

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

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

            gltfMesh.index = gltf.MeshesList.Count;
            gltf.MeshesList.Add(gltfMesh);
            gltfMesh.idGroupInstance = babylonMesh.idGroupInstance;
            if (hasBones)
            {
                gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId;
            }

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

            if (meshData != babylonMesh && primitivesCache.TryGetValue(babylonMesh.geometryId, out List <GLTFMeshPrimitive> result))
            {
                // this is a clone, indexing the same geometry
                // so we just need to retreive the primitive object and copy attributes and indices
                meshPrimitives.AddRange(result.Select((p, i) =>
                {
                    var meshPrimitive = new GLTFMeshPrimitive()
                    {
                        indices    = p.indices,
                        attributes = p.attributes
                    };

                    // Material
                    if (babylonMesh.materialId != null)
                    {
                        logger.RaiseMessage("GLTFExporter.Mesh | Material", 3);
                        // according we have a clone, then submeshes are of the same count and order.
                        SetBabylonMaterial(babylonMesh, babylonMesh.subMeshes[i], meshPrimitive);
                    }
                    return(meshPrimitive);
                }));
            }
            else
            {
                foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes)
                {
                    // --------------------------
                    // ------ SubMesh data ------
                    // --------------------------

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

                    var gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount);
                    // 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 minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart
                    if (minIndiceValue != 0)
                    {
                        for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++)
                        {
                            gltfIndices[indexIndice] -= minIndiceValue;
                        }
                    }

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

                    // Material
                    if (babylonMesh.materialId != null)
                    {
                        logger.RaiseMessage("GLTFExporter.Mesh | Material", 3);
                        SetBabylonMaterial(babylonMesh, babylonSubMesh, meshPrimitive);
                    }

                    // --------------------------
                    // ------- Accessors --------
                    // --------------------------

                    logger.RaiseMessage("GLTFExporter.Mesh | Geometry", 3);

                    // Buffer
                    var buffer = GLTFBufferService.Instance.GetBuffer(gltf);

                    // --- Indices ---
                    var componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT;
                    if (nbVertices >= 65536)
                    {
                        componentType = GLTFAccessor.ComponentType.UNSIGNED_INT;
                    }
                    var accessorIndices = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer),
                        "accessorIndices",
                        componentType,
                        GLTFAccessor.TypeEnum.SCALAR
                        );
                    meshPrimitive.indices = accessorIndices.index;
                    // Populate accessor
                    if (componentType == GLTFAccessor.ComponentType.UNSIGNED_INT)
                    {
                        gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                    }
                    else
                    {
                        var gltfIndicesShort = gltfIndices.ConvertAll(new Converter <int, ushort>(n => (ushort)n));
                        gltfIndicesShort.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n)));
                    }
                    accessorIndices.count = gltfIndices.Count;

                    // --- Positions ---
                    var accessorPositions = GLTFBufferService.Instance.CreateAccessor(
                        gltf,
                        GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                        "accessorPositions",
                        GLTFAccessor.ComponentType.FLOAT,
                        GLTFAccessor.TypeEnum.VEC3
                        );
                    meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index);
                    // Populate accessor
                    accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue };
                    accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue };
                    globalVerticesSubMesh.ForEach((globalVertex) =>
                    {
                        var positions = globalVertex.Position.ToArray();
                        // Store values as bytes
                        foreach (var position in positions)
                        {
                            accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position));
                        }
                        // Update min and max values
                        GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions);
                    });
                    accessorPositions.count = globalVerticesSubMesh.Count;

                    // --- Tangents ---
                    if (hasTangents)
                    {
                        var accessorTangents = GLTFBufferService.Instance.CreateAccessor(
                            gltf,
                            GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                            "accessorTangents",
                            GLTFAccessor.ComponentType.FLOAT,
                            GLTFAccessor.TypeEnum.VEC4
                            );
                        meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTangents.index);
                        // Populate accessor
                        List <float> tangents = globalVerticesSubMesh.SelectMany(v => v.Tangent.ToArray()).ToList();
                        tangents.ForEach(n => accessorTangents.bytesList.AddRange(BitConverter.GetBytes(n)));
                        accessorTangents.count = globalVerticesSubMesh.Count;
                    }

                    // --- Normals ---
                    if (hasNormals)
                    {
                        var accessorNormals = GLTFBufferService.Instance.CreateAccessor(
                            gltf,
                            GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer),
                            "accessorNormals",
                            GLTFAccessor.ComponentType.FLOAT,
                            GLTFAccessor.TypeEnum.VEC3
                            );
                        meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index);

                        // Populate accessor
                        List <float> normals = globalVerticesSubMesh.SelectMany(v => v.Normal.ToArray()).ToList();
                        normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n)));
                        accessorNormals.count = globalVerticesSubMesh.Count;
                    }

                    // --- Colors ---
                    if (hasColor)
                    {
                        var accessorColors = GLTFBufferService.Instance.CreateAccessor(
                            gltf,
                            GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                            "accessorColors",
                            GLTFAccessor.ComponentType.FLOAT,
                            GLTFAccessor.TypeEnum.VEC4
                            );
                        meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index);
                        // Populate accessor
                        List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList();
                        colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n)));
                        accessorColors.count = globalVerticesSubMesh.Count;
                    }

                    // --- UV ---
                    if (hasUV)
                    {
                        var accessorUVs = GLTFBufferService.Instance.CreateAccessor(
                            gltf,
                            GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                            "accessorUVs",
                            GLTFAccessor.ComponentType.FLOAT,
                            GLTFAccessor.TypeEnum.VEC2
                            );
                        meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index);
                        // Populate accessor
                        List <float> uvs = globalVerticesSubMesh.SelectMany(v => v.UV.ToArray()).ToList();
                        uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n)));
                        accessorUVs.count = globalVerticesSubMesh.Count;
                    }

                    // --- UV2 ---
                    if (hasUV2)
                    {
                        var accessorUV2s = GLTFBufferService.Instance.CreateAccessor(
                            gltf,
                            GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer),
                            "accessorUV2s",
                            GLTFAccessor.ComponentType.FLOAT,
                            GLTFAccessor.TypeEnum.VEC2
                            );
                        meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index);
                        // Populate accessor
                        List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => v.UV2.ToArray()).ToList();
                        uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n)));
                        accessorUV2s.count = globalVerticesSubMesh.Count;
                    }

                    // --- Bones ---
                    if (hasBones)
                    {
                        logger.RaiseMessage("GLTFExporter.Mesh | Bones", 3);

                        // if we've already exported this mesh's skeleton, check if the skins match,
                        // if so then export this mesh primitive to share joint and weight accessors.
                        var matchingSkinnedMesh = alreadyExportedSkinnedMeshes.FirstOrDefault(skinnedMesh => skinnedMesh.skeletonId == babylonMesh.skeletonId);
                        if (matchingSkinnedMesh != null && BabylonMesh.MeshesShareSkin(matchingSkinnedMesh, babylonMesh))
                        {
                            var tmpGltfMesh          = gltf.MeshesList.FirstOrDefault(mesh => matchingSkinnedMesh.name == mesh.name);
                            var tmpGltfMeshPrimitive = tmpGltfMesh.primitives.First();

                            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_0.ToString()]);
                            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString()]);
                            if (hasBonesExtra)
                            {
                                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_1.ToString()]);
                                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString()]);
                            }
                            sharedSkinnedMeshesByOriginal[tmpGltfMesh].Add(gltfMesh);
                        }
                        else
                        {
                            // Create new joint and weight accessors for this mesh's skinning.
                            // --- Joints ---
                            sharedSkinnedMeshesByOriginal[gltfMesh] = new List <GLTFMesh>();
                            var accessorJoints = GLTFBufferService.Instance.CreateAccessor(
                                gltf,
                                GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
                                "accessorJoints",
                                GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                                GLTFAccessor.TypeEnum.VEC4
                                );
                            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index);
                            // Populate accessor
                            List <ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList();
                            joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n)));
                            accessorJoints.count = globalVerticesSubMesh.Count;

                            if (hasBonesExtra)
                            {
                                // --- Joints Extra ---
                                var accessorJointsExtra = GLTFBufferService.Instance.CreateAccessor(
                                    gltf,
                                    GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer),
                                    "accessorJointsExtra",
                                    GLTFAccessor.ComponentType.UNSIGNED_SHORT,
                                    GLTFAccessor.TypeEnum.VEC4
                                    );
                                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), accessorJointsExtra.index);
                                // Populate accessor
                                List <ushort> jointsExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndicesExtra[0], v.BonesIndicesExtra[1], v.BonesIndicesExtra[2], v.BonesIndicesExtra[3] }).ToList();
                                jointsExtra.ForEach(n => accessorJointsExtra.bytesList.AddRange(BitConverter.GetBytes(n)));
                                accessorJointsExtra.count = globalVerticesSubMesh.Count;
                            }

                            // --- Weights ---
                            var accessorWeights = GLTFBufferService.Instance.CreateAccessor(
                                gltf,
                                GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                                "accessorWeights",
                                GLTFAccessor.ComponentType.FLOAT,
                                GLTFAccessor.TypeEnum.VEC4
                                );
                            meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index);
                            // Populate accessor
                            List <float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList();
                            weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n)));
                            accessorWeights.count = globalVerticesSubMesh.Count;

                            if (hasBonesExtra)
                            {
                                // --- Weights Extra ---
                                var accessorWeightsExtra = GLTFBufferService.Instance.CreateAccessor(
                                    gltf,
                                    GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer),
                                    "accessorWeightsExtra",
                                    GLTFAccessor.ComponentType.FLOAT,
                                    GLTFAccessor.TypeEnum.VEC4
                                    );
                                meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), accessorWeightsExtra.index);
                                // Populate accessor
                                List <float> weightBonesExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeightsExtra[0], v.BonesWeightsExtra[1], v.BonesWeightsExtra[2], v.BonesWeightsExtra[3] }).ToList();
                                weightBonesExtra.ForEach(n => accessorWeightsExtra.bytesList.AddRange(BitConverter.GetBytes(n)));
                                accessorWeightsExtra.count = globalVerticesSubMesh.Count;
                            }
                        }
                    }

                    // Morph targets positions and normals
                    if (babylonMorphTargetManager != null)
                    {
                        logger.RaiseMessage("GLTFExporter.Mesh | Morph targets", 3);
                        _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive);
                    }
                }

                // this is a "master" mesh so save the primitives into dictionary
                if (babylonMesh.geometryId != null && meshPrimitives != null)
                {
                    primitivesCache.Add(babylonMesh.geometryId, meshPrimitives);
                }
            }
            gltfMesh.primitives = meshPrimitives.ToArray();

            // Morph targets weights and names
            if (babylonMorphTargetManager != null && babylonMorphTargetManager.targets != null)
            {
                var weights     = new List <float>();
                var targetNames = new List <String>();
                foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets)
                {
                    weights.Add(babylonMorphTarget.influence);
                    targetNames.Add(babylonMorphTarget.name);
                }
                gltfMesh.weights = weights.ToArray();

                if (gltfMesh.extras == null)
                {
                    gltfMesh.extras = new Dictionary <string, object>();
                }
                gltfMesh.extras["targetNames"] = targetNames.ToArray();
            }

            if (hasBones)
            {
                alreadyExportedSkinnedMeshes.Add(babylonMesh);
            }

            ExportGLTFExtension(babylonMesh, ref gltfMesh, gltf);

            return(gltfMesh);
        }
        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);
        }