private BabylonMatrix _getNodeLocalMatrix(GLTFNode gltfNode) { return(BabylonMatrix.Compose( BabylonVector3.FromArray(gltfNode.scale), BabylonQuaternion.FromArray(gltfNode.rotation), BabylonVector3.FromArray(gltfNode.translation) )); }
private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene) { RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1); // -------------------------- // --- Mesh from babylon ---- // -------------------------- if (babylonMesh.positions == null || babylonMesh.positions.Length == 0) { RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2); return(null); } RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2); // Retreive general data from babylon mesh int nbVertices = babylonMesh.positions.Length / 3; bool hasTangents = babylonMesh.tangents != null && babylonMesh.tangents.Length > 0; bool hasUV = babylonMesh.uvs != null && babylonMesh.uvs.Length > 0; bool hasUV2 = babylonMesh.uvs2 != null && babylonMesh.uvs2.Length > 0; bool hasColor = babylonMesh.colors != null && babylonMesh.colors.Length > 0; bool hasBones = babylonMesh.matricesIndices != null && babylonMesh.matricesIndices.Length > 0; bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.Length > 0; RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3); RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3); RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3); RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3); RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3); RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 3); // Retreive vertices data from babylon mesh List <GLTFGlobalVertex> globalVertices = new List <GLTFGlobalVertex>(); for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++) { GLTFGlobalVertex globalVertex = new GLTFGlobalVertex(); globalVertex.Position = BabylonVector3.FromArray(babylonMesh.positions, indexVertex); globalVertex.Normal = BabylonVector3.FromArray(babylonMesh.normals, indexVertex); if (hasTangents) { globalVertex.Tangent = BabylonQuaternion.FromArray(babylonMesh.tangents, indexVertex); // Switch coordinate system at object level globalVertex.Tangent.Z *= -1; // Invert W to switch to right handed system globalVertex.Tangent.W *= -1; } // Switch coordinate system at object level globalVertex.Position.Z *= -1; globalVertex.Normal.Z *= -1; if (hasUV) { globalVertex.UV = BabylonVector2.FromArray(babylonMesh.uvs, indexVertex); // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image // While for Babylon, it corresponds to the lower left corner of a texture image globalVertex.UV.Y = 1 - globalVertex.UV.Y; } if (hasUV2) { globalVertex.UV2 = BabylonVector2.FromArray(babylonMesh.uvs2, indexVertex); // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image // While for Babylon, it corresponds to the lower left corner of a texture image globalVertex.UV2.Y = 1 - globalVertex.UV2.Y; } if (hasColor) { globalVertex.Color = Tools.SubArrayFromEntity(babylonMesh.colors, indexVertex, 4); } if (hasBones) { // In babylon, the 4 bones indices are stored in a single int // Each bone index is 8-bit offset from the next int bonesIndicesMerged = babylonMesh.matricesIndices[indexVertex]; int bone3 = bonesIndicesMerged >> 24; bonesIndicesMerged -= bone3 << 24; int bone2 = bonesIndicesMerged >> 16; bonesIndicesMerged -= bone2 << 16; int bone1 = bonesIndicesMerged >> 8; bonesIndicesMerged -= bone1 << 8; int bone0 = bonesIndicesMerged >> 0; bonesIndicesMerged -= bone0 << 0; var bonesIndicesArray = new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 }; globalVertex.BonesIndices = bonesIndicesArray; globalVertex.BonesWeights = Tools.SubArrayFromEntity(babylonMesh.matricesWeights, indexVertex, 4); } globalVertices.Add(globalVertex); } var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh); // Retrieve indices from babylon mesh List <int> babylonIndices = babylonMesh.indices.ToList(); // -------------------------- // ------- Init glTF -------- // -------------------------- RaiseMessage("GLTFExporter.Mesh | Init glTF", 2); // Mesh var gltfMesh = new GLTFMesh { name = babylonMesh.name }; gltfMesh.index = gltf.MeshesList.Count; gltf.MeshesList.Add(gltfMesh); gltfMesh.idGroupInstance = babylonMesh.idGroupInstance; if (hasBones) { gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId; } // -------------------------- // ---- glTF primitives ----- // -------------------------- RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2); var meshPrimitives = new List <GLTFMeshPrimitive>(); foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes) { // -------------------------- // ------ SubMesh data ------ // -------------------------- List <GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount); var gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount); // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0) // Thus, the gltf indices list is a concatenation of sub lists all 0-based // Example for 2 triangles, each being a submesh: // babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2} var minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++) { gltfIndices[indexIndice] -= minIndiceValue; } // -------------------------- // ----- Mesh primitive ----- // -------------------------- // MeshPrimitive var meshPrimitive = new GLTFMeshPrimitive { attributes = new Dictionary <string, int>() }; meshPrimitives.Add(meshPrimitive); // Material if (babylonMesh.materialId != null) { RaiseMessage("GLTFExporter.Mesh | Material", 3); // Retreive the babylon material BabylonMaterial babylonMaterial; var babylonMaterialId = babylonMesh.materialId; // From multi materials first, if any // Loop recursively even though it shouldn't be a real use case var babylonMultiMaterials = new List <BabylonMultiMaterial>(babylonScene.multiMaterials); BabylonMultiMaterial babylonMultiMaterial; do { babylonMultiMaterial = babylonMultiMaterials.Find(_babylonMultiMaterial => _babylonMultiMaterial.id == babylonMaterialId); if (babylonMultiMaterial != null) { babylonMaterialId = babylonMultiMaterial.materials[babylonSubMesh.materialIndex]; } }while (babylonMultiMaterial != null); // Then from materials var babylonMaterials = new List <BabylonMaterial>(babylonScene.materials); babylonMaterial = babylonMaterials.Find(_babylonMaterial => _babylonMaterial.id == babylonMaterialId); // If babylon material was exported successfully if (babylonMaterial != null) { // Update primitive material index var indexMaterial = babylonMaterialsToExport.FindIndex(_babylonMaterial => _babylonMaterial == babylonMaterial); if (indexMaterial == -1) { // Store material for export indexMaterial = babylonMaterialsToExport.Count; babylonMaterialsToExport.Add(babylonMaterial); } meshPrimitive.material = indexMaterial; } // TODO - Add and retreive info from babylon material meshPrimitive.mode = GLTFMeshPrimitive.FillMode.TRIANGLES; } // -------------------------- // ------- Accessors -------- // -------------------------- RaiseMessage("GLTFExporter.Mesh | Geometry", 3); // Buffer var buffer = GLTFBufferService.Instance.GetBuffer(gltf); // --- Indices --- var componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT; if (nbVertices >= 65536) { componentType = GLTFAccessor.ComponentType.UNSIGNED_INT; } var accessorIndices = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer), "accessorIndices", componentType, GLTFAccessor.TypeEnum.SCALAR ); meshPrimitive.indices = accessorIndices.index; // Populate accessor if (componentType == GLTFAccessor.ComponentType.UNSIGNED_INT) { gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n))); } else { var gltfIndicesShort = gltfIndices.ConvertAll(new Converter <int, ushort>(n => (ushort)n)); gltfIndicesShort.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n))); } accessorIndices.count = gltfIndices.Count; // --- Positions --- var accessorPositions = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorPositions", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index); // Populate accessor accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue }; accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue }; globalVerticesSubMesh.ForEach((globalVertex) => { var positions = globalVertex.Position.ToArray(); // Store values as bytes foreach (var position in positions) { accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position)); } // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions); }); accessorPositions.count = globalVerticesSubMesh.Count; // --- Normals --- var accessorNormals = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorNormals", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index); // Populate accessor List <float> normals = globalVerticesSubMesh.SelectMany(v => v.Normal.ToArray()).ToList(); normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n))); accessorNormals.count = globalVerticesSubMesh.Count; // --- Tangents --- if (hasTangents) { var accessorTangents = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorTangents", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTangents.index); // Populate accessor List <float> tangents = globalVerticesSubMesh.SelectMany(v => v.Tangent.ToArray()).ToList(); tangents.ForEach(n => accessorTangents.bytesList.AddRange(BitConverter.GetBytes(n))); accessorTangents.count = globalVerticesSubMesh.Count; } // --- Colors --- if (hasColor) { var accessorColors = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorColors", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index); // Populate accessor List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList(); colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n))); accessorColors.count = globalVerticesSubMesh.Count; } // --- UV --- if (hasUV) { var accessorUVs = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer), "accessorUVs", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC2 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index); // Populate accessor List <float> uvs = globalVerticesSubMesh.SelectMany(v => v.UV.ToArray()).ToList(); uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n))); accessorUVs.count = globalVerticesSubMesh.Count; } // --- UV2 --- if (hasUV2) { var accessorUV2s = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer), "accessorUV2s", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC2 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index); // Populate accessor List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => v.UV2.ToArray()).ToList(); uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n))); accessorUV2s.count = globalVerticesSubMesh.Count; } // --- Bones --- if (hasBones) { RaiseMessage("GLTFExporter.Mesh | Bones", 3); // --- Joints --- var accessorJoints = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer), "accessorJoints", GLTFAccessor.ComponentType.UNSIGNED_SHORT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index); // Populate accessor List <ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList(); joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n))); accessorJoints.count = globalVerticesSubMesh.Count; // --- Weights --- var accessorWeights = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorWeights", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index); // Populate accessor List <float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList(); weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n))); accessorWeights.count = globalVerticesSubMesh.Count; } if (hasBonesExtra) { RaiseWarning("Too many bones influences per vertex. glTF only support up to 4 bones influences per vertex. The result may not be as expected.", 3); } // Morph targets positions and normals if (babylonMorphTargetManager != null) { RaiseMessage("GLTFExporter.Mesh | Morph targets", 3); _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive); } } gltfMesh.primitives = meshPrimitives.ToArray(); // Morph targets weights if (babylonMorphTargetManager != null) { var weights = new List <float>(); foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets) { weights.Add(babylonMorphTarget.influence); } gltfMesh.weights = weights.ToArray(); } return(gltfMesh); }
int CreateGlobalVertex(IIGameNode meshNode, IIGameMesh mesh, BabylonAbstractMesh babylonAbstractMesh, IMatrix3 invertedWorldMatrix, IFaceEx face, int facePart, List <GlobalVertex> vertices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List <GlobalVertex>[] verticesAlreadyExported, IIGameSkin skin, List <int> boneIds) { var vertexIndex = (int)face.Vert[facePart]; // Position can by retreived in world space or object space // Unfortunately, this value can't be retreived in local space var vertex = new GlobalVertex { BaseIndex = vertexIndex, Position = mesh.GetVertex(vertexIndex, false), // world space Normal = mesh.GetNormal((int)face.Norm[facePart], false) // world space }; if (exportParameters.exportTangents) { int indexTangentBinormal = mesh.GetFaceVertexTangentBinormal(face.MeshFaceIndex, facePart, 1); IPoint3 normal = vertex.Normal.Normalize; IPoint3 tangent = mesh.GetTangent(indexTangentBinormal, 1).Normalize; IPoint3 bitangent = mesh.GetBinormal(indexTangentBinormal, 1).Normalize; int w = GetW(normal, tangent, bitangent); vertex.Tangent = new float[] { tangent.X, tangent.Y, tangent.Z, w }; } // Convert position and normal to local space vertex.Position = invertedWorldMatrix.PointTransform(vertex.Position); vertex.Normal = invertedWorldMatrix.VectorTransform(vertex.Normal); // 1. scale normals with node scales var nodeScaling = BabylonVector3.FromArray(babylonAbstractMesh.scaling); vertex.Normal = vertex.Normal.Multiply(Loader.Global.Point3.Create(Math.Abs(nodeScaling.X), Math.Abs(nodeScaling.Y), Math.Abs(nodeScaling.Z))); // 2. scale normals with objectOffsetScales (unrotate by objectOffsetRot, then scale, then rotate again) // note: LH coordinate system => flip y and z var objOffsetScale = Loader.Global.Point3.Create(meshNode.MaxNode.ObjOffsetScale.S); var scaleX = Math.Abs(objOffsetScale.X); var scaleY = Math.Abs(objOffsetScale.Y); var scaleZ = Math.Abs(objOffsetScale.Z); var objOffsetScaleFlipYZInv = Loader.Global.Point3.Create(1 / scaleX, 1 / scaleZ, 1 / scaleY); var objOffsetQuat = meshNode.MaxNode.ObjOffsetRot; var qFlippedYZ = objOffsetQuat; var tmpSwap = objOffsetQuat.Y; qFlippedYZ.Y = objOffsetQuat.Z; qFlippedYZ.Z = tmpSwap; var nUnrotated = RotateVectorByQuaternion(vertex.Normal, qFlippedYZ); var nUnrotatedScaled = nUnrotated.Multiply(objOffsetScaleFlipYZInv); nUnrotatedScaled = nUnrotatedScaled.Normalize; var nRerotatedScaled = RotateVectorByQuaternion(nUnrotatedScaled, qFlippedYZ.Conjugate); vertex.Normal = nRerotatedScaled; if (hasUV) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(1, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(1, indices[facePart]); vertex.UV = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasUV2) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(2, indices[facePart]); vertex.UV2 = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasColor) { var vertexColorIndex = (int)face.Color[facePart]; var vertexColor = mesh.GetColorVertex(vertexColorIndex); float alpha = 1; if (hasAlpha) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(-2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var color = mesh.GetMapVertex(-2, indices[facePart]); alpha = color.X; } vertex.Color = new[] { vertexColor.X, vertexColor.Y, vertexColor.Z, alpha }; } if (skin != null) { float weight0 = 0; float weight1 = 0; float weight2 = 0; float weight3 = 0; int bone0 = bonesCount; int bone1 = bonesCount; int bone2 = bonesCount; int bone3 = bonesCount; var nbBones = skin.GetNumberOfBones(vertexIndex); if (nbBones > 0) { bone0 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 0).NodeID); weight0 = skin.GetWeight(vertexIndex, 0); } if (nbBones > 1) { bone1 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 1).NodeID); weight1 = skin.GetWeight(vertexIndex, 1); } if (nbBones > 2) { bone2 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 2).NodeID); weight2 = skin.GetWeight(vertexIndex, 2); } if (nbBones > 3) { bone3 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 3).NodeID); weight3 = skin.GetWeight(vertexIndex, 3); } if (nbBones == 0) { weight0 = 1.0f; bone0 = bonesCount; } vertex.Weights = Loader.Global.Point4.Create(weight0, weight1, weight2, weight3); vertex.BonesIndices = (bone3 << 24) | (bone2 << 16) | (bone1 << 8) | bone0; if (nbBones > 4) { bone0 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 4).NodeID); weight0 = skin.GetWeight(vertexIndex, 4); weight1 = 0; weight2 = 0; weight3 = 0; if (nbBones > 5) { bone1 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 5).NodeID); weight1 = skin.GetWeight(vertexIndex, 5); } if (nbBones > 6) { bone2 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 6).NodeID); weight2 = skin.GetWeight(vertexIndex, 6); } if (nbBones > 7) { bone3 = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, 7).NodeID); weight3 = skin.GetWeight(vertexIndex, 7); } vertex.WeightsExtra = Loader.Global.Point4.Create(weight0, weight1, weight2, weight3); vertex.BonesIndicesExtra = (bone3 << 24) | (bone2 << 16) | (bone1 << 8) | bone0; if (nbBones > 8) { RaiseError("Too many bones influences per vertex: " + nbBones + ". Babylon.js only support 8 bones influences per vertex.", 2); } } } if (verticesAlreadyExported != null) { if (verticesAlreadyExported[vertexIndex] != null) { var index = verticesAlreadyExported[vertexIndex].IndexOf(vertex); if (index > -1) { return(verticesAlreadyExported[vertexIndex][index].CurrentIndex); } } else { verticesAlreadyExported[vertexIndex] = new List <GlobalVertex>(); } vertex.CurrentIndex = vertices.Count; verticesAlreadyExported[vertexIndex].Add(vertex); } vertices.Add(vertex); return(vertices.Count - 1); }
int CreateGlobalVertex(IIGameNode meshNode, IIGameMesh mesh, BabylonAbstractMesh babylonAbstractMesh, IMatrix3 invertedWorldMatrix, IFaceEx face, int facePart, List <GlobalVertex> vertices, bool hasUV, bool hasUV2, bool hasColor, bool hasAlpha, List <GlobalVertex>[] verticesAlreadyExported, IIGameSkin skin, List <int> boneIds) { var vertexIndex = (int)face.Vert[facePart]; // Position can by retreived in world space or object space // Unfortunately, this value can't be retreived in local space var vertex = new GlobalVertex { BaseIndex = vertexIndex, Position = mesh.GetVertex(vertexIndex, false), // world space Normal = mesh.GetNormal((int)face.Norm[facePart], false) // world space }; if (exportParameters.exportTangents) { int indexTangentBinormal = mesh.GetFaceVertexTangentBinormal(face.MeshFaceIndex, facePart, 1); IPoint3 normal = vertex.Normal.Normalize; IPoint3 tangent = mesh.GetTangent(indexTangentBinormal, 1).Normalize; IPoint3 bitangent = mesh.GetBinormal(indexTangentBinormal, 1).Normalize; int w = GetW(normal, tangent, bitangent); vertex.Tangent = new float[] { tangent.X, tangent.Y, tangent.Z, w }; } // Convert position and normal to local space vertex.Position = invertedWorldMatrix.PointTransform(vertex.Position); vertex.Normal = invertedWorldMatrix.VectorTransform(vertex.Normal); // 1. scale normals with node scales var nodeScaling = BabylonVector3.FromArray(babylonAbstractMesh.scaling); vertex.Normal = vertex.Normal.Multiply(Loader.Global.Point3.Create(Math.Abs(nodeScaling.X), Math.Abs(nodeScaling.Y), Math.Abs(nodeScaling.Z))); // 2. scale normals with objectOffsetScales (unrotate by objectOffsetRot, then scale, then rotate again) // note: LH coordinate system => flip y and z var objOffsetScale = Loader.Global.Point3.Create(meshNode.MaxNode.ObjOffsetScale.S); var scaleX = Math.Abs(objOffsetScale.X); var scaleY = Math.Abs(objOffsetScale.Y); var scaleZ = Math.Abs(objOffsetScale.Z); var objOffsetScaleFlipYZInv = Loader.Global.Point3.Create(1 / scaleX, 1 / scaleZ, 1 / scaleY); var objOffsetQuat = meshNode.MaxNode.ObjOffsetRot; var qFlippedYZ = objOffsetQuat; var tmpSwap = objOffsetQuat.Y; qFlippedYZ.Y = objOffsetQuat.Z; qFlippedYZ.Z = tmpSwap; var nUnrotated = RotateVectorByQuaternion(vertex.Normal, qFlippedYZ); var nUnrotatedScaled = nUnrotated.Multiply(objOffsetScaleFlipYZInv); nUnrotatedScaled = nUnrotatedScaled.Normalize; var nRerotatedScaled = RotateVectorByQuaternion(nUnrotatedScaled, qFlippedYZ.Conjugate); vertex.Normal = nRerotatedScaled; if (hasUV) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(1, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(1, indices[facePart]); vertex.UV = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasUV2) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var texCoord = mesh.GetMapVertex(2, indices[facePart]); vertex.UV2 = Loader.Global.Point2.Create(texCoord.X, -texCoord.Y); } if (hasColor) { var vertexColorIndex = (int)face.Color[facePart]; var vertexColor = mesh.GetColorVertex(vertexColorIndex); float alpha = 1; if (hasAlpha) { var indices = new int[3]; unsafe { fixed(int *indicesPtr = indices) { mesh.GetMapFaceIndex(-2, face.MeshFaceIndex, new IntPtr(indicesPtr)); } } var color = mesh.GetMapVertex(-2, indices[facePart]); alpha = color.X; } vertex.Color = new[] { vertexColor.X, vertexColor.Y, vertexColor.Z, alpha }; } if (skin != null) { float[] weight = new float[4] { 0, 0, 0, 0 }; int[] bone = new int[4] { bonesCount, bonesCount, bonesCount, bonesCount }; var nbBones = skin.GetNumberOfBones(vertexIndex); int currentVtxBone = 0; int currentSkinBone = 0; // process skin bones until we have 4 bones for this vertex or we run out of skin bones for (currentSkinBone = 0; currentSkinBone < nbBones && currentVtxBone < 4; ++currentSkinBone) { float boneWeight = skin.GetWeight(vertexIndex, currentSkinBone); if (boneWeight <= 0) { continue; } bone[currentVtxBone] = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, currentSkinBone).NodeID); weight[currentVtxBone] = skin.GetWeight(vertexIndex, currentSkinBone); ++currentVtxBone; } // if we didnt have any bones with a weight > 0 if (currentVtxBone == 0) { weight[0] = 1.0f; bone[0] = bonesCount; } vertex.Weights = Loader.Global.Point4.Create(weight); vertex.BonesIndices = (bone[3] << 24) | (bone[2] << 16) | (bone[1] << 8) | bone[0]; if (currentVtxBone >= 4 && currentSkinBone < nbBones) { weight = new float[4] { 0, 0, 0, 0 }; bone = new int[4] { bonesCount, bonesCount, bonesCount, bonesCount }; // process remaining skin bones until we have a total of 8 bones for this vertex or we run out of skin bones for (; currentSkinBone < nbBones && currentVtxBone < 8; ++currentSkinBone) { float boneWeight = skin.GetWeight(vertexIndex, currentSkinBone); if (boneWeight <= 0) { continue; } if (isGltfExported) { RaiseError("Too many bone influences per vertex for vertexIndex: " + vertexIndex + ". glTF only supports up to 4 bone influences per vertex.", 2); break; } bone[currentVtxBone - 4] = boneIds.IndexOf(skin.GetIGameBone(vertexIndex, currentSkinBone).NodeID); weight[currentVtxBone - 4] = skin.GetWeight(vertexIndex, currentSkinBone); ++currentVtxBone; } // if we have any extra bone weights if (currentVtxBone > 4) { vertex.WeightsExtra = Loader.Global.Point4.Create(weight); vertex.BonesIndicesExtra = (bone[3] << 24) | (bone[2] << 16) | (bone[1] << 8) | bone[0]; if (currentSkinBone < nbBones) { // if we have more skin bones left, this means we have used up all our bones for this vertex // check if any of the remaining bones has a weight > 0 for (; currentSkinBone < nbBones; ++currentSkinBone) { float boneWeight = skin.GetWeight(vertexIndex, currentSkinBone); if (boneWeight <= 0) { continue; } RaiseError("Too many bone influences per vertex for vertexIndex: " + vertexIndex + ". Babylon.js only supports up to 8 bone influences per vertex.", 2); break; } } } } } if (verticesAlreadyExported != null) { if (verticesAlreadyExported[vertexIndex] != null) { var index = verticesAlreadyExported[vertexIndex].IndexOf(vertex); if (index > -1) { return(verticesAlreadyExported[vertexIndex][index].CurrentIndex); } } else { verticesAlreadyExported[vertexIndex] = new List <GlobalVertex>(); } vertex.CurrentIndex = vertices.Count; verticesAlreadyExported[vertexIndex].Add(vertex); } vertices.Add(vertex); return(vertices.Count - 1); }
/// <summary> /// Get TRS and visiblity animations of the transform /// </summary> /// <param name="transform">Transform above mesh/camera/light</param> /// <returns></returns> private List <BabylonAnimation> GetAnimation(MFnTransform transform) { // Animations MPlugArray connections = new MPlugArray(); MStringArray animCurvList = new MStringArray(); MIntArray keysTime = new MIntArray(); MDoubleArray keysValue = new MDoubleArray(); MFloatArray translateValues = new MFloatArray(); MFloatArray rotateValues = new MFloatArray(); MFloatArray scaleValues = new MFloatArray(); MFloatArray visibilityValues = new MFloatArray(); MFloatArray keyTimes = new MFloatArray(); List <BabylonAnimationKey> keys = new List <BabylonAnimationKey>(); List <BabylonAnimation> animationsObject = new List <BabylonAnimation>(); //Get the animCurve MGlobal.executeCommand("listConnections -type \"animCurve\" " + transform.fullPathName + ";", animCurvList); List <AnimCurvData> animCurvesData = new List <AnimCurvData>(); foreach (String animCurv in animCurvList) { AnimCurvData animCurvData = new AnimCurvData(); animCurvesData.Add(animCurvData); animCurvData.animCurv = animCurv; //Get the key time for each curves MGlobal.executeCommand("keyframe -q " + animCurv + ";", keysTime); //Get the value for each curves MGlobal.executeCommand("keyframe - q -vc -absolute " + animCurv + ";", keysValue); if (animCurv.EndsWith("translateZ") || animCurv.EndsWith("rotateX") || animCurv.EndsWith("rotateY")) { for (int index = 0; index < keysTime.Count; index++) { // Switch coordinate system at object level animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index] * -1.0f); } } else { for (int index = 0; index < keysTime.Count; index++) { animCurvData.valuePerFrame.Add(keysTime[index], (float)keysValue[index]); } } } string[] mayaAnimationProperties = new string[] { "translate", "rotate", "scale" }; string[] babylonAnimationProperties = new string[] { "position", "rotationQuaternion", "scaling" }; string[] axis = new string[] { "X", "Y", "Z" }; // Init TRS default values Dictionary <string, float> defaultValues = new Dictionary <string, float>(); float[] position = null; float[] rotationQuaternion = null; float[] rotation = null; float[] scaling = null; GetTransform(transform, ref position, ref rotationQuaternion, ref rotation, ref scaling); // coordinate system already switched defaultValues.Add("translateX", position[0]); defaultValues.Add("translateY", position[1]); defaultValues.Add("translateZ", position[2]); defaultValues.Add("rotateX", rotation[0]); defaultValues.Add("rotateY", rotation[1]); defaultValues.Add("rotateZ", rotation[2]); defaultValues.Add("scaleX", scaling[0]); defaultValues.Add("scaleY", scaling[1]); defaultValues.Add("scaleZ", scaling[2]); for (int indexAnimationProperty = 0; indexAnimationProperty < mayaAnimationProperties.Length; indexAnimationProperty++) { string mayaAnimationProperty = mayaAnimationProperties[indexAnimationProperty]; // Retreive animation curves data for current animation property // Ex: all "translate" data are "translateX", "translateY", "translateZ" List <AnimCurvData> animDataProperty = animCurvesData.Where(data => data.animCurv.Contains(mayaAnimationProperty)).ToList(); if (animDataProperty.Count == 0) { // Property is not animated continue; } // Get all frames for this property List <int> framesProperty = new List <int>(); foreach (var animData in animDataProperty) { framesProperty.AddRange(animData.valuePerFrame.Keys); } framesProperty = framesProperty.Distinct().ToList(); framesProperty.Sort(); // Get default values for this property BabylonAnimationKey lastBabylonAnimationKey = new BabylonAnimationKey(); lastBabylonAnimationKey.frame = 0; lastBabylonAnimationKey.values = new float[] { defaultValues[mayaAnimationProperty + "X"], defaultValues[mayaAnimationProperty + "Y"], defaultValues[mayaAnimationProperty + "Z"] }; // Compute all values for this property List <BabylonAnimationKey> babylonAnimationKeys = new List <BabylonAnimationKey>(); foreach (var frameProperty in framesProperty) { BabylonAnimationKey babylonAnimationKey = new BabylonAnimationKey(); babylonAnimationKeys.Add(babylonAnimationKey); // Frame babylonAnimationKey.frame = frameProperty; // Values float[] valuesProperty = new float[3]; for (int indexAxis = 0; indexAxis < axis.Length; indexAxis++) { AnimCurvData animCurvDataAxis = animDataProperty.Find(data => data.animCurv.EndsWith(axis[indexAxis])); float value; if (animCurvDataAxis != null && animCurvDataAxis.valuePerFrame.ContainsKey(frameProperty)) { value = animCurvDataAxis.valuePerFrame[frameProperty]; } else { value = lastBabylonAnimationKey.values[indexAxis]; } valuesProperty[indexAxis] = value; } babylonAnimationKey.values = valuesProperty.ToArray(); // Update last known values lastBabylonAnimationKey = babylonAnimationKey; } // Convert euler to quaternion angles if (indexAnimationProperty == 1) // Rotation { foreach (var babylonAnimationKey in babylonAnimationKeys) { BabylonVector3 eulerAngles = BabylonVector3.FromArray(babylonAnimationKey.values); BabylonQuaternion quaternionAngles = eulerAngles.toQuaternion(); babylonAnimationKey.values = quaternionAngles.ToArray(); } } var keysFull = new List <BabylonAnimationKey>(babylonAnimationKeys); // Optimization OptimizeAnimations(babylonAnimationKeys, true); // Ensure animation has at least 2 frames if (IsAnimationKeysRelevant(keys)) { // Create BabylonAnimation string babylonAnimationProperty = babylonAnimationProperties[indexAnimationProperty]; animationsObject.Add(new BabylonAnimation() { dataType = indexAnimationProperty == 1 ? (int)BabylonAnimation.DataType.Quaternion : (int)BabylonAnimation.DataType.Vector3, name = babylonAnimationProperty + " animation", framePerSecond = 30, loopBehavior = (int)BabylonAnimation.LoopBehavior.Cycle, property = babylonAnimationProperty, keys = babylonAnimationKeys.ToArray(), keysFull = keysFull }); } } return(animationsObject); }
private GLTFMesh ExportMesh(BabylonMesh babylonMesh, GLTF gltf, BabylonScene babylonScene) { logger.RaiseMessage("GLTFExporter.Mesh | Export mesh named: " + babylonMesh.name, 1); // -------------------------- // --- Mesh from babylon ---- // -------------------------- // get direct access to mesh data or use geometryId to do so IBabylonMeshData meshData = babylonMesh.geometryId == null ? babylonMesh : (IBabylonMeshData)babylonScene.geometries.Get(babylonMesh.geometryId) ?? babylonMesh; if (meshData.positions == null || meshData.positions.Length == 0) { logger.RaiseMessage("GLTFExporter.Mesh | Mesh is a dummy", 2); return(null); } logger.RaiseMessage("GLTFExporter.Mesh | Mesh from babylon", 2); // Retreive general data from babylon mesh int nbVertices = meshData.positions.Length / 3; bool hasUV = meshData.uvs != null && meshData.uvs.Length > 0; bool hasUV2 = meshData.uvs2 != null && meshData.uvs2.Length > 0; bool hasColor = meshData.colors != null && meshData.colors.Length > 0; bool hasBones = meshData.matricesIndices != null && meshData.matricesIndices.Length > 0; bool hasTangents = meshData.tangents != null && meshData.tangents.Length > 0; bool hasNormals = meshData.normals != null && meshData.normals.Length > 0; bool hasBonesExtra = babylonMesh.matricesIndicesExtra != null && babylonMesh.matricesIndicesExtra.Length > 0; bool hasMetadata = babylonMesh.metadata != null && babylonMesh.metadata.Count > 0; logger.RaiseMessage("GLTFExporter.Mesh | nbVertices=" + nbVertices, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasUV=" + hasUV, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasUV2=" + hasUV2, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasColor=" + hasColor, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasBones=" + hasBones, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasBonesExtra=" + hasBonesExtra, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasMetadata=" + hasMetadata, 3); logger.RaiseMessage("GLTFExporter.Mesh | hasNormals=" + hasNormals, 3); // Retreive vertices data from babylon mesh List <GLTFGlobalVertex> globalVertices = new List <GLTFGlobalVertex>(); for (int indexVertex = 0; indexVertex < nbVertices; indexVertex++) { GLTFGlobalVertex globalVertex = new GLTFGlobalVertex(); globalVertex.Position = BabylonVector3.FromArray(meshData.positions, indexVertex); // Switch coordinate system at object level globalVertex.Position.Z *= -1; globalVertex.Position *= exportParameters.scaleFactor; if (hasNormals) { globalVertex.Normal = BabylonVector3.FromArray(meshData.normals, indexVertex); globalVertex.Normal.Z *= -1; } if (hasTangents) { globalVertex.Tangent = BabylonQuaternion.FromArray(meshData.tangents, indexVertex); // Switch coordinate system at object level globalVertex.Tangent.Z *= -1; // Invert W to switch to right handed system globalVertex.Tangent.W *= -1; } if (hasUV) { globalVertex.UV = BabylonVector2.FromArray(meshData.uvs, indexVertex); // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image // While for Babylon, it corresponds to the lower left corner of a texture image globalVertex.UV.Y = 1 - globalVertex.UV.Y; } if (hasUV2) { globalVertex.UV2 = BabylonVector2.FromArray(meshData.uvs2, indexVertex); // For glTF, the origin of the UV coordinates (0, 0) corresponds to the upper left corner of a texture image // While for Babylon, it corresponds to the lower left corner of a texture image globalVertex.UV2.Y = 1 - globalVertex.UV2.Y; } if (hasColor) { globalVertex.Color = ArrayExtension.SubArrayFromEntity(meshData.colors, indexVertex, 4); } if (hasBones) { // In babylon, the 4 bones indices are stored in a single int // Each bone index is 8-bit offset from the next ushort[] unpackBabylonBonesToArray(uint babylonBoneIndices) { uint bone3 = babylonBoneIndices >> 24; uint bone2 = (babylonBoneIndices << 8) >> 24; uint bone1 = (babylonBoneIndices << 16) >> 24; uint bone0 = (babylonBoneIndices << 24) >> 24; babylonBoneIndices -= bone0 << 0; return(new ushort[] { (ushort)bone0, (ushort)bone1, (ushort)bone2, (ushort)bone3 }); } uint bonesIndicesMerged = (uint)meshData.matricesIndices[indexVertex]; globalVertex.BonesIndices = unpackBabylonBonesToArray(bonesIndicesMerged); globalVertex.BonesWeights = ArrayExtension.SubArrayFromEntity(meshData.matricesWeights, indexVertex, 4); void clearBoneUnusedIndices(ushort[] indices, float[] weights) { for (int i = 0; i < indices.Length; ++i) { // Zero out indices of unused joint weights to avoid ACCESSOR_JOINTS_USED_ZERO_WEIGHT. if (MathUtilities.IsAlmostEqualTo(weights[i], 0, float.Epsilon)) { indices[i] = 0; } } } clearBoneUnusedIndices(globalVertex.BonesIndices, globalVertex.BonesWeights); if (hasBonesExtra) { uint bonesIndicesExtraMerged = (uint)meshData.matricesIndicesExtra[indexVertex]; globalVertex.BonesIndicesExtra = unpackBabylonBonesToArray(bonesIndicesExtraMerged); globalVertex.BonesWeightsExtra = ArrayExtension.SubArrayFromEntity(meshData.matricesWeightsExtra, indexVertex, 4); clearBoneUnusedIndices(globalVertex.BonesIndicesExtra, globalVertex.BonesWeightsExtra); } } globalVertices.Add(globalVertex); } var babylonMorphTargetManager = GetBabylonMorphTargetManager(babylonScene, babylonMesh); // Retreive indices from babylon mesh List <int> babylonIndices = meshData.indices.ToList(); // -------------------------- // ------- Init glTF -------- // -------------------------- logger.RaiseMessage("GLTFExporter.Mesh | Init glTF", 2); // Mesh var gltfMesh = new GLTFMesh { name = babylonMesh.name }; gltfMesh.index = gltf.MeshesList.Count; gltf.MeshesList.Add(gltfMesh); gltfMesh.idGroupInstance = babylonMesh.idGroupInstance; if (hasBones) { gltfMesh.idBabylonSkeleton = babylonMesh.skeletonId; } // -------------------------- // ---- glTF primitives ----- // -------------------------- logger.RaiseMessage("GLTFExporter.Mesh | glTF primitives", 2); List <GLTFMeshPrimitive> meshPrimitives = new List <GLTFMeshPrimitive>(); if (meshData != babylonMesh && primitivesCache.TryGetValue(babylonMesh.geometryId, out List <GLTFMeshPrimitive> result)) { // this is a clone, indexing the same geometry // so we just need to retreive the primitive object and copy attributes and indices meshPrimitives.AddRange(result.Select((p, i) => { var meshPrimitive = new GLTFMeshPrimitive() { indices = p.indices, attributes = p.attributes }; // Material if (babylonMesh.materialId != null) { logger.RaiseMessage("GLTFExporter.Mesh | Material", 3); // according we have a clone, then submeshes are of the same count and order. SetBabylonMaterial(babylonMesh, babylonMesh.subMeshes[i], meshPrimitive); } return(meshPrimitive); })); } else { foreach (BabylonSubMesh babylonSubMesh in babylonMesh.subMeshes) { // -------------------------- // ------ SubMesh data ------ // -------------------------- List <GLTFGlobalVertex> globalVerticesSubMesh = globalVertices.GetRange(babylonSubMesh.verticesStart, babylonSubMesh.verticesCount); var gltfIndices = babylonIndices.GetRange(babylonSubMesh.indexStart, babylonSubMesh.indexCount); // In gltf, indices of each mesh primitive are 0-based (ie: min value is 0) // Thus, the gltf indices list is a concatenation of sub lists all 0-based // Example for 2 triangles, each being a submesh: // babylonIndices = {0,1,2, 3,4,5} gives as result gltfIndicies = {0,1,2, 0,1,2} var minIndiceValue = gltfIndices.Min(); // Should be equal to babylonSubMesh.indexStart if (minIndiceValue != 0) { for (int indexIndice = 0; indexIndice < gltfIndices.Count; indexIndice++) { gltfIndices[indexIndice] -= minIndiceValue; } } // -------------------------- // ----- Mesh primitive ----- // -------------------------- // MeshPrimitive var meshPrimitive = new GLTFMeshPrimitive { attributes = new Dictionary <string, int>() }; meshPrimitives.Add(meshPrimitive); // Material if (babylonMesh.materialId != null) { logger.RaiseMessage("GLTFExporter.Mesh | Material", 3); SetBabylonMaterial(babylonMesh, babylonSubMesh, meshPrimitive); } // -------------------------- // ------- Accessors -------- // -------------------------- logger.RaiseMessage("GLTFExporter.Mesh | Geometry", 3); // Buffer var buffer = GLTFBufferService.Instance.GetBuffer(gltf); // --- Indices --- var componentType = GLTFAccessor.ComponentType.UNSIGNED_SHORT; if (nbVertices >= 65536) { componentType = GLTFAccessor.ComponentType.UNSIGNED_INT; } var accessorIndices = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewScalar(gltf, buffer), "accessorIndices", componentType, GLTFAccessor.TypeEnum.SCALAR ); meshPrimitive.indices = accessorIndices.index; // Populate accessor if (componentType == GLTFAccessor.ComponentType.UNSIGNED_INT) { gltfIndices.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n))); } else { var gltfIndicesShort = gltfIndices.ConvertAll(new Converter <int, ushort>(n => (ushort)n)); gltfIndicesShort.ForEach(n => accessorIndices.bytesList.AddRange(BitConverter.GetBytes(n))); } accessorIndices.count = gltfIndices.Count; // --- Positions --- var accessorPositions = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorPositions", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.POSITION.ToString(), accessorPositions.index); // Populate accessor accessorPositions.min = new float[] { float.MaxValue, float.MaxValue, float.MaxValue }; accessorPositions.max = new float[] { float.MinValue, float.MinValue, float.MinValue }; globalVerticesSubMesh.ForEach((globalVertex) => { var positions = globalVertex.Position.ToArray(); // Store values as bytes foreach (var position in positions) { accessorPositions.bytesList.AddRange(BitConverter.GetBytes(position)); } // Update min and max values GLTFBufferService.UpdateMinMaxAccessor(accessorPositions, positions); }); accessorPositions.count = globalVerticesSubMesh.Count; // --- Tangents --- if (hasTangents) { var accessorTangents = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorTangents", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TANGENT.ToString(), accessorTangents.index); // Populate accessor List <float> tangents = globalVerticesSubMesh.SelectMany(v => v.Tangent.ToArray()).ToList(); tangents.ForEach(n => accessorTangents.bytesList.AddRange(BitConverter.GetBytes(n))); accessorTangents.count = globalVerticesSubMesh.Count; } // --- Normals --- if (hasNormals) { var accessorNormals = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec3(gltf, buffer), "accessorNormals", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC3 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.NORMAL.ToString(), accessorNormals.index); // Populate accessor List <float> normals = globalVerticesSubMesh.SelectMany(v => v.Normal.ToArray()).ToList(); normals.ForEach(n => accessorNormals.bytesList.AddRange(BitConverter.GetBytes(n))); accessorNormals.count = globalVerticesSubMesh.Count; } // --- Colors --- if (hasColor) { var accessorColors = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorColors", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.COLOR_0.ToString(), accessorColors.index); // Populate accessor List <float> colors = globalVerticesSubMesh.SelectMany(v => new[] { v.Color[0], v.Color[1], v.Color[2], v.Color[3] }).ToList(); colors.ForEach(n => accessorColors.bytesList.AddRange(BitConverter.GetBytes(n))); accessorColors.count = globalVerticesSubMesh.Count; } // --- UV --- if (hasUV) { var accessorUVs = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer), "accessorUVs", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC2 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_0.ToString(), accessorUVs.index); // Populate accessor List <float> uvs = globalVerticesSubMesh.SelectMany(v => v.UV.ToArray()).ToList(); uvs.ForEach(n => accessorUVs.bytesList.AddRange(BitConverter.GetBytes(n))); accessorUVs.count = globalVerticesSubMesh.Count; } // --- UV2 --- if (hasUV2) { var accessorUV2s = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec2(gltf, buffer), "accessorUV2s", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC2 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.TEXCOORD_1.ToString(), accessorUV2s.index); // Populate accessor List <float> uvs2 = globalVerticesSubMesh.SelectMany(v => v.UV2.ToArray()).ToList(); uvs2.ForEach(n => accessorUV2s.bytesList.AddRange(BitConverter.GetBytes(n))); accessorUV2s.count = globalVerticesSubMesh.Count; } // --- Bones --- if (hasBones) { logger.RaiseMessage("GLTFExporter.Mesh | Bones", 3); // if we've already exported this mesh's skeleton, check if the skins match, // if so then export this mesh primitive to share joint and weight accessors. var matchingSkinnedMesh = alreadyExportedSkinnedMeshes.FirstOrDefault(skinnedMesh => skinnedMesh.skeletonId == babylonMesh.skeletonId); if (matchingSkinnedMesh != null && BabylonMesh.MeshesShareSkin(matchingSkinnedMesh, babylonMesh)) { var tmpGltfMesh = gltf.MeshesList.FirstOrDefault(mesh => matchingSkinnedMesh.name == mesh.name); var tmpGltfMeshPrimitive = tmpGltfMesh.primitives.First(); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_0.ToString()]); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString()]); if (hasBonesExtra) { meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.JOINTS_1.ToString()]); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), tmpGltfMeshPrimitive.attributes[GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString()]); } sharedSkinnedMeshesByOriginal[tmpGltfMesh].Add(gltfMesh); } else { // Create new joint and weight accessors for this mesh's skinning. // --- Joints --- sharedSkinnedMeshesByOriginal[gltfMesh] = new List <GLTFMesh>(); var accessorJoints = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer), "accessorJoints", GLTFAccessor.ComponentType.UNSIGNED_SHORT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_0.ToString(), accessorJoints.index); // Populate accessor List <ushort> joints = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndices[0], v.BonesIndices[1], v.BonesIndices[2], v.BonesIndices[3] }).ToList(); joints.ForEach(n => accessorJoints.bytesList.AddRange(BitConverter.GetBytes(n))); accessorJoints.count = globalVerticesSubMesh.Count; if (hasBonesExtra) { // --- Joints Extra --- var accessorJointsExtra = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewUnsignedShortVec4(gltf, buffer), "accessorJointsExtra", GLTFAccessor.ComponentType.UNSIGNED_SHORT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.JOINTS_1.ToString(), accessorJointsExtra.index); // Populate accessor List <ushort> jointsExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesIndicesExtra[0], v.BonesIndicesExtra[1], v.BonesIndicesExtra[2], v.BonesIndicesExtra[3] }).ToList(); jointsExtra.ForEach(n => accessorJointsExtra.bytesList.AddRange(BitConverter.GetBytes(n))); accessorJointsExtra.count = globalVerticesSubMesh.Count; } // --- Weights --- var accessorWeights = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorWeights", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_0.ToString(), accessorWeights.index); // Populate accessor List <float> weightBones = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeights[0], v.BonesWeights[1], v.BonesWeights[2], v.BonesWeights[3] }).ToList(); weightBones.ForEach(n => accessorWeights.bytesList.AddRange(BitConverter.GetBytes(n))); accessorWeights.count = globalVerticesSubMesh.Count; if (hasBonesExtra) { // --- Weights Extra --- var accessorWeightsExtra = GLTFBufferService.Instance.CreateAccessor( gltf, GLTFBufferService.Instance.GetBufferViewFloatVec4(gltf, buffer), "accessorWeightsExtra", GLTFAccessor.ComponentType.FLOAT, GLTFAccessor.TypeEnum.VEC4 ); meshPrimitive.attributes.Add(GLTFMeshPrimitive.Attribute.WEIGHTS_1.ToString(), accessorWeightsExtra.index); // Populate accessor List <float> weightBonesExtra = globalVerticesSubMesh.SelectMany(v => new[] { v.BonesWeightsExtra[0], v.BonesWeightsExtra[1], v.BonesWeightsExtra[2], v.BonesWeightsExtra[3] }).ToList(); weightBonesExtra.ForEach(n => accessorWeightsExtra.bytesList.AddRange(BitConverter.GetBytes(n))); accessorWeightsExtra.count = globalVerticesSubMesh.Count; } } } // Morph targets positions and normals if (babylonMorphTargetManager != null) { logger.RaiseMessage("GLTFExporter.Mesh | Morph targets", 3); _exportMorphTargets(babylonMesh, babylonSubMesh, babylonMorphTargetManager, gltf, buffer, meshPrimitive); } } // this is a "master" mesh so save the primitives into dictionary if (babylonMesh.geometryId != null && meshPrimitives != null) { primitivesCache.Add(babylonMesh.geometryId, meshPrimitives); } } gltfMesh.primitives = meshPrimitives.ToArray(); // Morph targets weights and names if (babylonMorphTargetManager != null && babylonMorphTargetManager.targets != null) { var weights = new List <float>(); var targetNames = new List <String>(); foreach (BabylonMorphTarget babylonMorphTarget in babylonMorphTargetManager.targets) { weights.Add(babylonMorphTarget.influence); targetNames.Add(babylonMorphTarget.name); } gltfMesh.weights = weights.ToArray(); if (gltfMesh.extras == null) { gltfMesh.extras = new Dictionary <string, object>(); } gltfMesh.extras["targetNames"] = targetNames.ToArray(); } if (hasBones) { alreadyExportedSkinnedMeshes.Add(babylonMesh); } ExportGLTFExtension(babylonMesh, ref gltfMesh, gltf); return(gltfMesh); }