public GLTFBufferView GetBufferViewAnimationFloatVec4(GLTF gltf, GLTFBuffer buffer) { if (gltf.bufferViewAnimationFloatVec4 == null) { var bufferView = CreateBufferView(gltf, buffer, "bufferViewAnimationFloatVec4"); gltf.bufferViewAnimationFloatVec4 = bufferView; } return(gltf.bufferViewAnimationFloatVec4); }
public GLTFBufferView GetBufferViewFloatMat4(GLTF gltf, GLTFBuffer buffer) { if (gltf.bufferViewFloatMat4 == null) { var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatMat4"); gltf.bufferViewFloatMat4 = bufferView; } return(gltf.bufferViewFloatMat4); }
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(); }
public GLTFBufferView GetBufferViewFloatVec4(GLTF gltf, GLTFBuffer buffer) { if (gltf.bufferViewFloatVec4 == null) { var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatVec4"); bufferView.byteStride = 16; gltf.bufferViewFloatVec4 = bufferView; } return(gltf.bufferViewFloatVec4); }
public GLTFBufferView GetBufferViewFloatVec2(GLTF gltf, GLTFBuffer buffer) { if (gltf.bufferViewFloatVec2 == null) { var bufferView = CreateBufferView(gltf, buffer, "bufferViewFloatVec2"); bufferView.byteStride = 8; gltf.bufferViewFloatVec2 = bufferView; } return(gltf.bufferViewFloatVec2); }
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); }
public GLTFBuffer GetBuffer(GLTF gltf) { var buffer = gltf.buffer; if (buffer == null) { buffer = new GLTFBuffer { uri = gltf.OutputFile + ".bin" }; buffer.index = gltf.BuffersList.Count; gltf.BuffersList.Add(buffer); gltf.buffer = buffer; } return(buffer); }
private void _exportMorphTargets(BabylonMesh babylonMesh, BabylonSubMesh babylonSubMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive) { var gltfMorphTargets = new List <GLTFMorphTarget>(); foreach (var babylonMorphTarget in babylonMorphTargetManager.targets) { var gltfMorphTarget = new GLTFMorphTarget(); // Positions if (babylonMorphTarget.positions != null) { var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorTargetPositions", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index); // Populate accessor int nbComponents = 3; // Vector3 int startIndex = babylonSubMesh.verticesStart * nbComponents; int endIndex = startIndex + babylonSubMesh.verticesCount * nbComponents; accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue }; accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue }; for (int indexPosition = startIndex; indexPosition < endIndex; indexPosition += 3) { var positionTarget = Tools.SubArray(babylonMorphTarget.positions, indexPosition, 3); // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive var positionMesh = Tools.SubArray(babylonMesh.positions, indexPosition, 3); for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++) { positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate]; } positionTarget[2] *= -1; // Store values as bytes foreach (var coordinate in positionTarget) { accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate)); } // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget); } accessorTargetPositions.count = babylonSubMesh.verticesCount; } // Normals if ((babylonMorphTarget.normals != null) && _exportMorphNormal) { var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorTargetNormals", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index); // Populate accessor int nbComponents = 3; // Vector3 int startIndex = babylonSubMesh.verticesStart * nbComponents; int endIndex = startIndex + babylonSubMesh.verticesCount * nbComponents; for (int indexNormal = startIndex; indexNormal < endIndex; indexNormal += 3) { var normalTarget = Tools.SubArray(babylonMorphTarget.normals, indexNormal, 3); // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive var normalMesh = Tools.SubArray(babylonMesh.normals, indexNormal, 3); for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++) { normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate]; } normalTarget[2] *= -1; // Store values as bytes foreach (var coordinate in normalTarget) { accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate)); } } accessorTargetNormals.count = babylonSubMesh.verticesCount; } // Tangents if ((babylonMorphTarget.tangents != null) && _exportMorphTangent) { var accessorTargetTangents = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorTargetTangents", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); gltfMorphTarget.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTargetTangents.index); // Populate accessor // Note that the w component for handedness is omitted when targeting TANGENT data since handedness cannot be displaced. int nbComponents = 4; // Vector4 int startIndex = babylonSubMesh.verticesStart * nbComponents; int endIndex = startIndex + babylonSubMesh.verticesCount * nbComponents; for (int indexTangent = startIndex; indexTangent < endIndex; indexTangent += 4) { var tangentTarget = Tools.SubArray(babylonMorphTarget.tangents, indexTangent, 3); // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive var tangentMesh = Tools.SubArray(babylonMesh.tangents, indexTangent, 3); for (int indexCoordinate = 0; indexCoordinate < tangentTarget.Length; indexCoordinate++) { tangentTarget[indexCoordinate] = tangentTarget[indexCoordinate] - tangentMesh[indexCoordinate]; } tangentTarget[2] *= -1; // Store values as bytes foreach (var coordinate in tangentTarget) { accessorTargetTangents.bytesList.AddRange(BitConverter.GetBytes(coordinate)); } } accessorTargetTangents.count = babylonSubMesh.verticesCount; } gltfMorphTargets.Add(gltfMorphTarget); } if (gltfMorphTargets.Count > 0) { meshPrimitive.targets = gltfMorphTargets.ToArray(); } }
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 void _exportMorphTargets(BabylonMesh babylonMesh, BabylonMorphTargetManager babylonMorphTargetManager, GLTF gltf, GLTFBuffer buffer, GLTFMeshPrimitive meshPrimitive, List <float> weights) { var gltfMorphTargets = new List <GLTFMorphTarget>(); foreach (var babylonMorphTarget in babylonMorphTargetManager.targets) { var gltfMorphTarget = new GLTFMorphTarget(); // Positions if (babylonMorphTarget.positions != null) { var accessorTargetPositions = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorTargetPositions", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); gltfMorphTarget.Add(GLTFMorphTarget.Attribute.POSITION.ToString(), accessorTargetPositions.index); // Populate accessor accessorTargetPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue }; accessorTargetPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue }; for (int indexPosition = 0; indexPosition < babylonMorphTarget.positions.Length; indexPosition += 3) { var positionTarget = Tools.SubArray(babylonMorphTarget.positions, indexPosition, 3); // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive var positionMesh = Tools.SubArray(babylonMesh.positions, indexPosition, 3); for (int indexCoordinate = 0; indexCoordinate < positionTarget.Length; indexCoordinate++) { positionTarget[indexCoordinate] = positionTarget[indexCoordinate] - positionMesh[indexCoordinate]; } // Store values as bytes foreach (var coordinate in positionTarget) { accessorTargetPositions.bytesList.AddRange(BitConverter.GetBytes(coordinate)); } // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorTargetPositions, positionTarget); } accessorTargetPositions.count = babylonMorphTarget.positions.Length / 3; } // Normals if (babylonMorphTarget.normals != null) { var accessorTargetNormals = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorTargetNormals", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); gltfMorphTarget.Add(GLTFMorphTarget.Attribute.NORMAL.ToString(), accessorTargetNormals.index); // Populate accessor for (int indexNormal = 0; indexNormal < babylonMorphTarget.positions.Length; indexNormal += 3) { var normalTarget = Tools.SubArray(babylonMorphTarget.normals, indexNormal, 3); // Babylon stores morph target information as final data while glTF expects deltas from mesh primitive var normalMesh = Tools.SubArray(babylonMesh.normals, indexNormal, 3); for (int indexCoordinate = 0; indexCoordinate < normalTarget.Length; indexCoordinate++) { normalTarget[indexCoordinate] = normalTarget[indexCoordinate] - normalMesh[indexCoordinate]; } // Store values as bytes foreach (var coordinate in normalTarget) { accessorTargetNormals.bytesList.AddRange(BitConverter.GetBytes(coordinate)); } } accessorTargetNormals.count = babylonMorphTarget.normals.Length / 3; } if (gltfMorphTarget.Count > 0) { gltfMorphTargets.Add(gltfMorphTarget); weights.Add(babylonMorphTarget.influence); } } if (gltfMorphTargets.Count > 0) { meshPrimitive.targets = gltfMorphTargets.ToArray(); } }
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); }