internal void Read(EndianBinaryReader reader, MeshSection section = null) { reader.SeekCurrent(4); BoundingSphere = reader.ReadBoundingSphere(); int indexTableCount = reader.ReadInt32(); long indexTablesOffset = reader.ReadOffset(); var attributes = ( VertexFormatAttribute )reader.ReadUInt32(); int stride = reader.ReadInt32(); int vertexCount = reader.ReadInt32(); var elemItems = reader.ReadUInt32s(section?.Format == BinaryFormat.X ? 49 : 28); Name = reader.ReadString(StringBinaryFormat.FixedLength, 64); IndexTables.Capacity = indexTableCount; for (int i = 0; i < indexTableCount; i++) { reader.ReadAtOffset(indexTablesOffset + (i * IndexTable.GetByteSize(section?.Format ?? BinaryFormat.DT)), () => { var indexTable = new IndexTable(); indexTable.Read(reader, section); IndexTables.Add(indexTable); }); } // Modern Format if (section != null) { ReadVertexAttributesModern(); } else { ReadVertexAttributesClassic(); } void ReadVertexAttributesClassic() { Vector4[] boneWeights = null; Vector4[] boneIndices = null; for (int i = 0; i < 28; i++) { var attribute = ( VertexFormatAttribute )(1 << i); reader.ReadAtOffsetIf((attributes & attribute) != 0, elemItems[i], () => { switch (attribute) { case VertexFormatAttribute.Vertex: Vertices = reader.ReadVector3s(vertexCount); break; case VertexFormatAttribute.Normal: Normals = reader.ReadVector3s(vertexCount); break; case VertexFormatAttribute.Tangent: Tangents = reader.ReadVector4s(vertexCount); break; case VertexFormatAttribute.UVChannel1: UVChannel1 = reader.ReadVector2s(vertexCount); break; case VertexFormatAttribute.UVChannel2: UVChannel2 = reader.ReadVector2s(vertexCount); break; case VertexFormatAttribute.Color: Colors = reader.ReadColors(vertexCount); break; case VertexFormatAttribute.BoneWeight: boneWeights = reader.ReadVector4s(vertexCount); break; case VertexFormatAttribute.BoneIndex: boneIndices = reader.ReadVector4s(vertexCount); break; default: Console.WriteLine("Unhandled vertex format element: {0}", attribute); break; } }); } if (boneWeights != null && boneIndices != null) { BoneWeights = new BoneWeight[vertexCount]; for (int i = 0; i < vertexCount; i++) { // So apparently, FT uses -1 instead of 255 for weights that aren't used, // and index tables can use bones more than 85 (85*3=255) // For that reason, weight == 255 check won't and shouldn't be done anymore. Vector4 weight4 = boneWeights[i]; Vector4 index4 = Vector4.Divide(boneIndices[i], 3); var boneWeight = new BoneWeight { Weight1 = weight4.X, Weight2 = weight4.Y, Weight3 = weight4.Z, Weight4 = weight4.W, Index1 = ( int )index4.X, Index2 = ( int )index4.Y, Index3 = ( int )index4.Z, Index4 = ( int )index4.W, }; boneWeight.Validate(); BoneWeights[i] = boneWeight; } } } void ReadVertexAttributesModern() { uint dataOffset = elemItems[section.Format == BinaryFormat.X ? 27 : 13]; uint attributeFlags = elemItems[section.Format == BinaryFormat.X ? 42 : 21]; if (attributeFlags == 2 || attributeFlags == 4) { Vertices = new Vector3[vertexCount]; Normals = new Vector3[vertexCount]; Tangents = new Vector4[vertexCount]; UVChannel1 = new Vector2[vertexCount]; UVChannel2 = new Vector2[vertexCount]; Colors = new Color[vertexCount]; if (attributeFlags == 4) { BoneWeights = new BoneWeight[vertexCount]; } bool hasTangents = false; bool hasUVChannel2 = false; bool hasColors = false; var vertexReader = section.VertexData.Reader; for (int i = 0; i < vertexCount; i++) { vertexReader.SeekBegin(section.VertexData.DataOffset + dataOffset + (stride * i)); Vertices[i] = vertexReader.ReadVector3(); Normals[i] = vertexReader.ReadVector3(VectorBinaryFormat.Int16); vertexReader.SeekCurrent(2); Tangents[i] = vertexReader.ReadVector4(VectorBinaryFormat.Int16); UVChannel1[i] = vertexReader.ReadVector2(VectorBinaryFormat.Half); UVChannel2[i] = vertexReader.ReadVector2(VectorBinaryFormat.Half); Colors[i] = vertexReader.ReadColor(VectorBinaryFormat.Half); if (attributeFlags == 4) { var boneWeight = new BoneWeight { Weight1 = vertexReader.ReadUInt16() / 32768f, Weight2 = vertexReader.ReadUInt16() / 32768f, Weight3 = vertexReader.ReadUInt16() / 32768f, Weight4 = vertexReader.ReadUInt16() / 32768f, Index1 = vertexReader.ReadByte() / 3, Index2 = vertexReader.ReadByte() / 3, Index3 = vertexReader.ReadByte() / 3, Index4 = vertexReader.ReadByte() / 3, }; boneWeight.Validate(); BoneWeights[i] = boneWeight; } // Normalize normal because precision Normals[i] = Vector3.Normalize(Normals[i]); // Checks to get rid of useless data after reading if (Tangents[i] != Vector4.Zero) { hasTangents = true; } if (UVChannel1[i] != UVChannel2[i]) { hasUVChannel2 = true; } if (!Colors[i].Equals(Color.White)) { hasColors = true; } } if (!hasTangents) { Tangents = null; } if (!hasUVChannel2) { UVChannel2 = null; } if (!hasColors) { Colors = null; } } if (Tangents != null) { for (int i = 0; i < Tangents.Length; i++) { float direction = Tangents[i].W < 0.0f ? -1.0f : 1.0f; Vector3 tangent = Vector3.Normalize(new Vector3(Tangents[i].X, Tangents[i].Y, Tangents[i].Z)); Tangents[i] = new Vector4(tangent, direction); } } } }
private static SubMesh ConvertSubMeshFromAiNode(Ai.Node aiNode, Ai.Scene aiScene, Matrix4x4 parentTransformation, Dictionary <string, int> boneMap, List <Bone> bones, Dictionary <string, int> materialMap, List <Material> materials, string texturesDirectory, TextureSet textureSet) { if (!aiNode.HasMeshes) { return(null); } // Select meshes that have triangles var aiMeshes = aiNode.MeshIndices.Select(x => aiScene.Meshes[x]).Where(x => x.PrimitiveType == Ai.PrimitiveType.Triangle && x.Faces.Any(y => y.IndexCount == 3)).ToList(); if (aiMeshes.Count == 0) { return(null); } var transformation = parentTransformation * GetMatrix4x4FromAiMatrix4x4(aiNode.Transform); int vertexCount = aiMeshes.Sum(x => x.VertexCount); var subMesh = new SubMesh { Name = aiNode.Name, Vertices = new Vector3[vertexCount], }; int vertexOffset = 0; foreach (var aiMesh in aiMeshes) { for (int i = 0; i < aiMesh.Vertices.Count; i++) { subMesh.Vertices[vertexOffset + i] = Vector3.Transform(new Vector3(aiMesh.Vertices[i].X, aiMesh.Vertices[i].Y, aiMesh.Vertices[i].Z), transformation); } if (aiMesh.HasNormals) { if (subMesh.Normals == null) { subMesh.Normals = new Vector3[vertexCount]; } for (int i = 0; i < aiMesh.Normals.Count; i++) { subMesh.Normals[vertexOffset + i] = Vector3.Normalize(Vector3.TransformNormal(new Vector3(aiMesh.Normals[i].X, aiMesh.Normals[i].Y, aiMesh.Normals[i].Z), transformation)); } } if (aiMesh.HasTangentBasis) { if (subMesh.Tangents == null) { subMesh.Tangents = new Vector4[vertexCount]; } for (int i = 0; i < aiMesh.Tangents.Count; i++) { Vector3 tangent = Vector3.TransformNormal(new Vector3(aiMesh.Tangents[i].X, aiMesh.Tangents[i].Y, aiMesh.Tangents[i].Z), transformation); Vector3 bitangent = Vector3.TransformNormal(new Vector3(aiMesh.BiTangents[i].X, aiMesh.BiTangents[i].Y, aiMesh.BiTangents[i].Z), transformation); float direction = Vector3.Dot(bitangent, Vector3.Cross(subMesh.Normals[vertexOffset + i], tangent)) > 0 ? 1.0f : -1.0f; subMesh.Tangents[vertexOffset + i] = new Vector4(tangent, direction); } } if (aiMesh.HasTextureCoords(0)) { if (subMesh.UVChannel1 == null) { subMesh.UVChannel1 = new Vector2[vertexCount]; } for (int i = 0; i < aiMesh.TextureCoordinateChannels[0].Count; i++) { subMesh.UVChannel1[vertexOffset + i] = ClampTextureCoordinates(new Vector2(aiMesh.TextureCoordinateChannels[0][i].X, 1f - aiMesh.TextureCoordinateChannels[0][i].Y)); } } if (aiMesh.HasTextureCoords(1)) { if (subMesh.UVChannel2 == null) { subMesh.UVChannel2 = new Vector2[vertexCount]; } for (int i = 0; i < aiMesh.TextureCoordinateChannels[1].Count; i++) { subMesh.UVChannel2[vertexOffset + i] = ClampTextureCoordinates(new Vector2(aiMesh.TextureCoordinateChannels[1][i].X, 1f - aiMesh.TextureCoordinateChannels[1][i].Y)); } } if (aiMesh.HasVertexColors(0)) { if (subMesh.Colors == null) { subMesh.Colors = Enumerable.Repeat(Color.White, vertexCount).ToArray(); } for (int i = 0; i < aiMesh.VertexColorChannels[0].Count; i++) { subMesh.Colors[vertexOffset + i] = new Color(aiMesh.VertexColorChannels[0][i].R, aiMesh.VertexColorChannels[0][i].G, aiMesh.VertexColorChannels[0][i].B, aiMesh.VertexColorChannels[0][i].A); } } var indexTable = new IndexTable(); if (aiMesh.HasBones) { if (subMesh.BoneWeights == null) { subMesh.BoneWeights = Enumerable.Repeat(BoneWeight.Empty, vertexCount).ToArray(); } indexTable.BoneIndices = new ushort[aiMesh.Bones.Count]; for (int i = 0; i < aiMesh.Bones.Count; i++) { var aiBone = aiMesh.Bones[i]; if (!boneMap.TryGetValue(aiBone.Name, out int boneIndex)) { boneIndex = bones.Count; boneMap[aiBone.Name] = boneIndex; bones.Add(ConvertBoneFromAiBone(aiBone, aiScene, boneIndex)); } indexTable.BoneIndices[i] = ( ushort )boneIndex; foreach (var aiWeight in aiBone.VertexWeights) { subMesh.BoneWeights[vertexOffset + aiWeight.VertexID].AddWeight(i, aiWeight.Weight); } } } indexTable.Indices = aiMesh.Faces.Where(x => x.IndexCount == 3).SelectMany(x => x.Indices).Select(x => ( ushort )(vertexOffset + x)).ToArray(); ushort[] triangleStrip = TriangleStripUtilities.GenerateStrips(indexTable.Indices); if (triangleStrip != null) { indexTable.PrimitiveType = IndexTablePrimitiveType.TriangleStrip; indexTable.Indices = triangleStrip; } var aiMaterial = aiScene.Materials[aiMesh.MaterialIndex]; if (!materialMap.TryGetValue(aiMaterial.Name, out int materialIndex)) { materialIndex = materials.Count; materialMap[aiMaterial.Name] = materialIndex; materials.Add(ConvertMaterialFromAiMaterial(aiMaterial, texturesDirectory, textureSet)); } indexTable.MaterialIndex = materialIndex; var axisAlignedBoundingBox = new AxisAlignedBoundingBox(subMesh.Vertices.Skip(vertexOffset).Take(aiMesh.Vertices.Count)); indexTable.BoundingSphere = axisAlignedBoundingBox.ToBoundingSphere(); indexTable.BoundingBox = axisAlignedBoundingBox.ToBoundingBox(); subMesh.IndexTables.Add(indexTable); vertexOffset += aiMesh.VertexCount; } subMesh.BoundingSphere = new AxisAlignedBoundingBox(subMesh.Vertices).ToBoundingSphere(); return(subMesh); }