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) { 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); }
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 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; bool hasTangents = babylonMesh.tangents != null && babylonMesh.tangents.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); // Retreive 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 exportation 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; // --- 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 --- 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) { 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); }
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); }