private void AddElementsToAccessor(GLTFAccessor accessor, int count) { GLTFBufferView bufferView = accessor.BufferView; GLTFBuffer buffer = bufferView.Buffer; accessor.byteOffset = bufferView.byteLength; accessor.count += count; bufferView.byteLength += accessor.getByteLength(); buffer.byteLength += accessor.getByteLength(); }
private GLTFBufferView CreateBufferView(GLTF gltf, GLTFBuffer buffer, string name) { var bufferView = new GLTFBufferView { name = name, buffer = buffer.index, Buffer = buffer }; bufferView.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(bufferView); buffer.BufferViews.Add(bufferView); return(bufferView); }
private List <GLTFBufferView> SwitchImagesFromUriToBinary(GLTF gltf) { var imageBufferViews = new List <GLTFBufferView>(); foreach (GLTFImage gltfImage in gltf.ImagesList) { var path = Path.Combine(gltf.OutputFolder, gltfImage.uri); using (Image image = Image.FromFile(path)) { using (MemoryStream m = new MemoryStream()) { var imageFormat = gltfImage.FileExtension == "jpeg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png; image.Save(m, imageFormat); byte[] imageBytes = m.ToArray(); // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements var nbSpaceToAdd = imageBytes.Length % 4 == 0 ? 0 : (4 - imageBytes.Length % 4); var imageBytesList = new List <byte>(imageBytes); for (int i = 0; i < nbSpaceToAdd; i++) { imageBytesList.Add(0x00); } imageBytes = imageBytesList.ToArray(); // BufferView - Image var buffer = gltf.buffer; var bufferViewImage = new GLTFBufferView { name = "bufferViewImage", buffer = buffer.index, Buffer = buffer, byteOffset = buffer.byteLength }; bufferViewImage.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(bufferViewImage); imageBufferViews.Add(bufferViewImage); gltfImage.uri = null; gltfImage.bufferView = bufferViewImage.index; gltfImage.mimeType = "image/" + gltfImage.FileExtension; bufferViewImage.bytesList.AddRange(imageBytes); bufferViewImage.byteLength += imageBytes.Length; bufferViewImage.Buffer.byteLength += imageBytes.Length; } } } return(imageBufferViews); }
public GLTFAccessor CreateAccessor(GLTF gltf, GLTFBufferView bufferView, string name, GLTFAccessor.ComponentType componentType, GLTFAccessor.TypeEnum type) { var accessor = new GLTFAccessor { name = name, bufferView = bufferView.index, BufferView = bufferView, componentType = componentType, type = type.ToString() }; accessor.index = gltf.AccessorsList.Count; gltf.AccessorsList.Add(accessor); bufferView.Accessors.Add(accessor); return(accessor); }
private GLTFBufferView WriteImageToGltfBuffer(GLTF gltf, GLTFImage gltfImage, string imageSourcePath = null, Bitmap imageBitmap = null, long textureQuality = 100) { byte[] imageBytes = null; if (imageBitmap != null) { // try our best to get extension - default will be png which is the looseless format. var extension = gltfImage.FileExtension ?? (gltfImage.uri != null ? Path.GetExtension(gltfImage.uri) : null); var outputFormat = extension != null?TextureUtilities.GetImageFormat(gltfImage.FileExtension) : ImageFormat.Png; using (MemoryStream m = new MemoryStream()) { // this use the SAME method for GLTF TextureUtilities.SaveBitmap(m, imageBitmap, outputFormat, textureQuality); imageBytes = m.ToArray(); } } else { imageBytes = File.ReadAllBytes(imageSourcePath); } // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements imageBytes = padChunk(imageBytes, 4, 0x00); // BufferView - Image var buffer = gltf.buffer; var imageBufferView = new GLTFBufferView { name = "bufferViewImage", buffer = buffer.index, Buffer = buffer, byteOffset = buffer.byteLength }; imageBufferView.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(imageBufferView); imageBufferView.bytesList.AddRange(imageBytes); imageBufferView.byteLength += imageBytes.Length; imageBufferView.Buffer.byteLength += imageBytes.Length; imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList); return(imageBufferView); }
private List <GLTFBufferView> SwitchImagesFromUriToBinary(GLTF gltf) { var imageBufferViews = new List <GLTFBufferView>(); foreach (GLTFImage gltfImage in gltf.ImagesList) { var path = Path.Combine(gltf.OutputFolder, gltfImage.uri); byte[] imageBytes = File.ReadAllBytes(path); // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements imageBytes = padChunk(imageBytes, 4, 0x00); // BufferView - Image var buffer = gltf.buffer; var bufferViewImage = new GLTFBufferView { name = "bufferViewImage", buffer = buffer.index, Buffer = buffer, byteOffset = buffer.byteLength }; bufferViewImage.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(bufferViewImage); imageBufferViews.Add(bufferViewImage); gltfImage.uri = null; gltfImage.bufferView = bufferViewImage.index; gltfImage.mimeType = "image/" + gltfImage.FileExtension; bufferViewImage.bytesList.AddRange(imageBytes); bufferViewImage.byteLength += imageBytes.Length; bufferViewImage.Buffer.byteLength += imageBytes.Length; } return(imageBufferViews); }
private GLTFBufferView WriteImageToGltfBuffer(GLTF gltf, GLTFImage gltfImage, string imageSourcePath = null, Bitmap imageBitmap = null) { byte[] imageBytes = null; if (imageBitmap != null) { ImageConverter converter = new ImageConverter(); imageBytes = (byte[])converter.ConvertTo(imageBitmap, typeof(byte[])); } else { imageBytes = File.ReadAllBytes(imageSourcePath); } // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements imageBytes = padChunk(imageBytes, 4, 0x00); // BufferView - Image var buffer = gltf.buffer; var imageBufferView = new GLTFBufferView { name = "bufferViewImage", buffer = buffer.index, Buffer = buffer, byteOffset = buffer.byteLength }; imageBufferView.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(imageBufferView); imageBufferView.bytesList.AddRange(imageBytes); imageBufferView.byteLength += imageBytes.Length; imageBufferView.Buffer.byteLength += imageBytes.Length; imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList); return(imageBufferView); }
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, 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); }