public static MDL Load(string path) { Logger.LogToFile(Logger.LogLevel.Info, "{0}", path); MDL mdl = new MDL { Name = Path.GetFileNameWithoutExtension(path) }; using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(path))) { mdl = Load(ms, Path.GetFileNameWithoutExtension(path)); } return(mdl); }
public static MDL Load(string path) { FileInfo fi = new FileInfo(path); Logger.LogToFile(Logger.LogLevel.Info, "{0}", path); MDL mdl = new MDL(); mdl.name = fi.Name.Replace(fi.Extension, ""); using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(path))) using (BinaryReader br = new BinaryReader(ms, Encoding.Default)) { if (br.ReadByte() != 0x45|| br.ReadByte() != 0x23) { Logger.LogToFile(Logger.LogLevel.Error, "{0} isn't a valid MDL file", path); return null; } byte minor = br.ReadByte(); byte major = br.ReadByte(); mdl.version = new Version(major, minor); if (!MDL.SupportedVersions.ContainsKey(mdl.version.ToString())) { Logger.LogToFile(Logger.LogLevel.Error, "Unsupported MDL version: v{0}", mdl.version.ToString()); return null; } Logger.LogToFile(Logger.LogLevel.Info, "MDL v{0}", mdl.version.ToString()); // TODO: v5.6 // Ref : Novadrome_Demo\WADs\data\DATA\SFX\CAR_EXPLOSION\DEBPOOL\DEBPOOL.MDL // Ref : Carmageddon Mobile\Data_IOS\DATA\CONTENT\SFX\SHRAPNEL.MDL // 01 00 00 00 EE 02 00 00 02 00 00 00 04 00 00 00 01 00 00 00 49 33 35 3F 89 41 00 BF 18 B7 51 BA 89 41 00 BF 00 00 00 3F 52 49 9D 3A 00 00 00 3F 00 12 03 BA 18 B7 51 39 00 12 03 BA 01 00 66 69 72 65 70 6F 6F 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 17 B7 51 39 17 B7 D1 38 00 00 80 3F 17 B7 D1 B8 00 00 00 00 03 00 00 00 02 00 00 00 01 00 00 00 17 B7 51 39 17 B7 D1 B8 00 00 80 3F 17 B7 D1 38 04 00 00 00 00 00 00 3F 17 B7 D1 38 00 00 00 BF 17 B7 D1 38 00 00 80 3F 17 B7 D1 B8 00 00 80 3F 00 00 80 3F 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 BF 17 B7 51 39 00 00 00 BF 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 3F 17 B7 51 39 00 00 00 3F 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 BF 17 B7 D1 38 00 00 00 3F 17 B7 D1 B8 00 00 80 3F 17 B7 D1 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 80 80 FF 01 00 00 00 00 00 17 B7 D1 38 00 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF mdl.checkSum = (int)br.ReadUInt32(); mdl.flags = (Flags)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "Flags {0}", (Flags)mdl.flags); mdl.prepDataSize = (int)br.ReadUInt32(); // PREP data size mdl.userFaceCount = (int)br.ReadUInt32(); mdl.userVertexCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "USER Faces: {0}", mdl.userFaceCount); Logger.LogToFile(Logger.LogLevel.Debug, "USER Verts: {0}", mdl.userVertexCount); mdl.fileSize = (int)br.ReadUInt32(); mdl.extents.Radius = br.ReadSingle(); mdl.extents.Min = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mdl.extents.Max = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); br.ReadBytes(12); // BoundingBox centre, Flummery auto calculates from min and max int materialCount = br.ReadInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "Material count: {0}", materialCount); for (int i = 0; i < materialCount; i++) { string materialName; int nameLength = -1; int padding = -1; if (mdl.version.Major < 6) { materialName = br.ReadBytes(32).ToName(); } else { nameLength = (int)br.ReadInt32(); padding = (((nameLength / 4) + (nameLength % 4 > 0 ? 1 : 0)) * 4) - nameLength + (mdl.version.Major == 6 && mdl.version.Minor > 0 ? 4 : 0); materialName = br.ReadString(nameLength); br.ReadBytes(padding); } mdl.meshes.Add(new MDLMaterialGroup(i, materialName)); } // START PREP DATA mdl.prepFaceCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Faces: {0}", mdl.prepFaceCount); for (int i = 0; i < mdl.prepFaceCount; i++) { var face = new MDLFace( br.ReadUInt16(), // Material index br.ReadUInt16(), // Material Flags (int)br.ReadUInt32(), // Vert index A (int)br.ReadUInt32(), // Vert index B (int)br.ReadUInt32() // Vert index C ); mdl.faces.Add(face); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1}", i, face); } mdl.prepVertexCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Verts: {0}", mdl.prepVertexCount); for (int i = 0; i < mdl.prepVertexCount; i++) { var vert = new MDLVertex( br.ReadSingle(), // X br.ReadSingle(), // Y br.ReadSingle(), // Z br.ReadSingle(), // N.X br.ReadSingle(), // N.Y br.ReadSingle(), // N.Z br.ReadSingle(), // U br.ReadSingle(), // V br.ReadSingle(), // U2 br.ReadSingle(), // V2 br.ReadByte(), // R br.ReadByte(), // G br.ReadByte(), // B br.ReadByte() // A ); mdl.verts.Add(vert); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1}", i, vert); } int materialGroups = br.ReadUInt16(); for (int i = 0; i < materialGroups; i++) { var mesh = mdl.meshes[i]; br.ReadBytes(12); // BoundingBox Centre, we recalculate it from Min and Max mesh.Extents.Radius = br.ReadSingle(); mesh.Extents.Min = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mesh.Extents.Max = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mesh.StripOffset = (int)br.ReadUInt32(); mesh.StripVertCount = (int)br.ReadUInt32(); int stripPointCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1} : {2}", mesh.StripOffset, mesh.StripVertCount, stripPointCount); for (int j = 0; j < stripPointCount; j++) { uint index = br.ReadUInt32(); bool bDegenerate = ((index & 0x80000000) != 0); index &= ~0x80000000; mesh.StripList.Add(new MDLPoint((int)index + mesh.StripOffset, bDegenerate)); Logger.LogToFile(Logger.LogLevel.Debug, "{0} ] {1} : {2}", j, index, bDegenerate); } mesh.TriListOffset = (int)br.ReadUInt32(); mesh.TriListVertCount = (int)br.ReadUInt32(); int listPointCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1} : {2}", mesh.TriListOffset, mesh.TriListVertCount, listPointCount); for (int j = 0; j < listPointCount; j++) { uint index = br.ReadUInt32(); mesh.TriList.Add(new MDLPoint((int)index + mesh.TriListOffset)); Logger.LogToFile(Logger.LogLevel.Debug, "{0} ] {1}", j, index); } } if (mdl.flags.HasFlag(Flags.PREPSkinData)) { Logger.LogToFile(Logger.LogLevel.Debug, "Processing PREP skin data"); int bodyPartCount = br.ReadUInt16(); int maxBonesPerVertex = br.ReadUInt16(); int rootBoneIndex = br.ReadUInt16(); var boneNames = br.ReadStrings(bodyPartCount); Logger.LogToFile(Logger.LogLevel.Debug, "Body Part Count: {0}. Max Bones per Vertex : {1}. Root Bone Index : {2}", bodyPartCount, maxBonesPerVertex, rootBoneIndex); for (int i = 0; i < bodyPartCount; i++) { var bone = new MDLBone(); bone.Name = boneNames[i]; bone.MinExtents = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); // Min and Max bone local space bone.MaxExtents = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); bone.Offset = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); // Offset is in parents local space bone.Parent = br.ReadByte(); bone.Child = br.ReadByte(); bone.Sibling = br.ReadByte(); mdl.prepBoneList.Add(bone); } for (int i = 0; i < bodyPartCount; i++) { mdl.prepBoneList[i].Rotation = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mdl.prepBoneList[i].Position = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); br.ReadBytes(4); Logger.LogToFile(Logger.LogLevel.Debug, "{0}) {1}", i, mdl.prepBoneList[i].Name); Logger.LogToFile(Logger.LogLevel.Debug, "P{0} C{1} S{2}", mdl.prepBoneList[i].Parent, mdl.prepBoneList[i].Child, mdl.prepBoneList[i].Sibling); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].MinExtents); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].MaxExtents); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Offset); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Rotation); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Position); Logger.LogToFile(Logger.LogLevel.Debug, ""); } Logger.LogToFile(Logger.LogLevel.Debug, "PREP skin vert weight table"); for (int i = 0; i < mdl.prepVertexCount; i++) { int weightCount = br.ReadUInt16(); br.ReadBytes(2); int weightOffset = (int)br.ReadUInt32(); mdl.prepVertSkinWeightLookup.Add(new MDLPrepSkinWeightLookup { Count = weightCount, Index = weightOffset }); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1} @ {2}", i, weightCount, weightOffset); } int prepSkinWeightCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Skin Weight Count: {0}", prepSkinWeightCount); for (int i = 0; i < prepSkinWeightCount; i++) { int boneIndex = br.ReadUInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1}", i, boneIndex); } for (int i = 0; i < prepSkinWeightCount; i++) { Single weight = br.ReadSingle(); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1}", i, weight); } } // END PREP DATA // START USER DATA if (mdl.flags.HasFlag(Flags.USERData)) { mdl.userFlags = (Flags)br.ReadUInt32(); // v5.6 successfully parses from this point down Logger.LogToFile(Logger.LogLevel.Debug, "USER vertex list with index count"); for (int i = 0; i < mdl.userVertexCount; i++) { mdl.userVertexList.Add(new MDLUserVertexEntry(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), (int)br.ReadUInt32())); } Logger.LogToFile(Logger.LogLevel.Debug, "USER face data"); for (int i = 0; i < mdl.userFaceCount; i++) { if (mdl.version.Major == 5 && mdl.version.Minor == 6) { br.ReadBytes(133); } else { mdl.userFaceList.Add( new MDLUserFaceEntry( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), // plane equation new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[0] normal new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[1] normal new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[2] normal (int)br.ReadUInt32(), // material index (int)br.ReadUInt32(), // smoothing group (int)br.ReadUInt32(), // vertex[0] (int)br.ReadUInt32(), // vertex[1] (int)br.ReadUInt32(), // vertex[2] br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[0] RGBA br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[1] RGBA br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[2] RGBA new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[0] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[0] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[1] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[1] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[2] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[2] br.ReadByte(), // flags (int)br.ReadUInt32() // application specific flags ) // 137 bytes ); } } Logger.LogToFile(Logger.LogLevel.Debug, "PREP to USER face lookup"); for (int i = 0; i < mdl.prepFaceCount; i++) { mdl.ptouFaceLookup.Add((int)br.ReadUInt32()); } int prepVertexMapCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP to USER vertex lookup"); for (int i = 0; i < prepVertexMapCount; i++) { mdl.ptouVertexLookup.Add((int)br.ReadUInt32()); } if (mdl.userFlags.HasFlag(Flags.USERSkinData)) { Logger.LogToFile(Logger.LogLevel.Debug, "Processing USER skin data"); int boneCount = br.ReadUInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "Bone count: {0}", boneCount); for (int i = 0; i < boneCount; i++) { string boneName = br.ReadString(32); short parentBoneIndex = br.ReadInt16(); Matrix3D boneTransform = new Matrix3D( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle() ); Logger.LogToFile(Logger.LogLevel.Debug, "{0}) {1}", i, boneName); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", parentBoneIndex); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", boneTransform); Logger.LogToFile(Logger.LogLevel.Debug, ""); } int userDataCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "{0} == {1}", userDataCount, mdl.userVertexCount); for (int i = 0; i < mdl.userVertexCount; i++) { int entryCount = br.ReadUInt16(); for (int j = 0; j < entryCount; j++) { int boneIndex = br.ReadUInt16(); Single weight = br.ReadSingle(); Vector3 vertexPosition = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); Logger.LogToFile(Logger.LogLevel.Debug, "{0}.{1}] {2,2} : {3,6:0.00}% : {4}", i, j, boneIndex, (weight * 100.0f), vertexPosition); } } } } else { Console.WriteLine("no user data"); } if (br.BaseStream.Position != br.BaseStream.Length) { Logger.LogToFile(Logger.LogLevel.Warning, "Still has data remaining (processed {0:x2} of {1:x2}", br.BaseStream.Position, br.BaseStream.Length); } } return mdl; }
public override void Export(Asset asset, string path) { var model = (asset as Model); int materialIndex = 1; ModelManipulator.PreProcess(model, PreProcessOptions.SplitMeshPart | PreProcessOptions.Dedupe | PreProcessOptions.ResolveNonManifold); foreach (var mesh in model.Meshes) { var mdl = new MDL(); //mdl.ModelFlags = MDL.Flags.USERData; materialIndex = 0; foreach (var meshpart in mesh.MeshParts.OrderByDescending(m => m.VertexCount).ToList()) { var mdlmesh = new MDLMaterialGroup(materialIndex, (meshpart.Material != null ? meshpart.Material.Name : "DEFAULT")); int masterVertOffset = mdl.Vertices.Count; foreach (var v in meshpart.VertexBuffer.Data) { mdl.Vertices.Add( new MDLVertex( v.Position.X, v.Position.Y, v.Position.Z, v.Normal.X, v.Normal.Y, v.Normal.Z, v.UV.X, v.UV.Y, v.UV.Z, v.UV.W, (byte)(v.Colour.R * 255), (byte)(v.Colour.G * 255), (byte)(v.Colour.B * 255), (byte)(v.Colour.A * 255) ) ); } for (int i = 0; i < meshpart.IndexBuffer.Data.Count; i += 3) { mdl.Faces.Add(new MDLFace(materialIndex, 0, masterVertOffset + meshpart.IndexBuffer.Data[i + 0], masterVertOffset + meshpart.IndexBuffer.Data[i + 1], masterVertOffset + meshpart.IndexBuffer.Data[i + 2])); } var stripper = new Stripper.Stripper(meshpart.IndexBuffer.Data.Count / 3, meshpart.IndexBuffer.Data.ToArray()); stripper.OneSided = true; stripper.ConnectAllStrips = true; stripper.ShakeItBaby(); if (stripper.Strips[0].Count > 3) { var strip = stripper.Strips[0]; if ((strip.Count & 1) == 1) { strip.Reverse(); } else { strip.Insert(0, strip[0]); } HashSet<int> uniqueVerts = new HashSet<int> { strip[0], strip[1] }; mdlmesh.StripOffset = masterVertOffset; mdlmesh.StripList.Add(new MDLPoint(strip[0], false)); mdlmesh.StripList.Add(new MDLPoint(strip[1], false)); for (int i = 2; i < strip.Count; i++) { uniqueVerts.Add(strip[i]); var point = new MDLPoint(strip[i], (strip.GetRange(i - 2, 3).Distinct().Count() != 3)); mdlmesh.StripList.Add(point); } mdlmesh.StripVertCount = uniqueVerts.Count; } int patchOffset = int.MaxValue; for (int i = 1; i < stripper.Strips.Count; i++) { patchOffset = Math.Min(patchOffset, stripper.Strips[i].Min()); } for (int i = 1; i < stripper.Strips.Count; i++) { var patch = stripper.Strips[i]; mdlmesh.TriListOffset = masterVertOffset + patchOffset; for (int j = 2; j >= 0; j--) { int index = patch[j] - patchOffset; mdlmesh.TriList.Add(new MDLPoint(index)); mdlmesh.TriListVertCount = Math.Max(mdlmesh.TriListVertCount, index + 1); } } mdlmesh.CalculateExtents(mdl.Vertices); mdl.Meshes.Add(mdlmesh); materialIndex++; Console.WriteLine("Saved mesh"); } mdl.Save(path + mesh.Name + ".mdl"); Console.WriteLine("Saved MDL"); } }
public static MDL Load(Stream stream, string name = null) { MDL mdl = new MDL { Name = name }; using (BinaryReader br = new BinaryReader(stream, Encoding.Default, true)) { if (br.ReadByte() != 0x45 || br.ReadByte() != 0x23) { Logger.LogToFile(Logger.LogLevel.Error, "This isn't a valid MDL file"); return(null); } byte minor = br.ReadByte(); byte major = br.ReadByte(); mdl.Version = new Version(major, minor); if (!SupportedVersions.Contains(mdl.Version)) { Logger.LogToFile(Logger.LogLevel.Error, "Unsupported MDL version: v{0}", mdl.Version.ToString()); return(null); } Logger.LogToFile(Logger.LogLevel.Info, "MDL v{0}", mdl.Version.ToString()); // TODO: v5.6 // Ref : Novadrome_Demo\WADs\data\DATA\SFX\CAR_EXPLOSION\DEBPOOL\DEBPOOL.MDL // Ref : Carmageddon Mobile\Data_IOS\DATA\CONTENT\SFX\SHRAPNEL.MDL // 01 00 00 00 EE 02 00 00 02 00 00 00 04 00 00 00 01 00 00 00 49 33 35 3F 89 41 00 BF 18 B7 51 BA 89 41 00 BF 00 00 00 3F 52 49 9D 3A 00 00 00 3F 00 12 03 BA 18 B7 51 39 00 12 03 BA 01 00 66 69 72 65 70 6F 6F 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 17 B7 51 39 17 B7 D1 38 00 00 80 3F 17 B7 D1 B8 00 00 00 00 03 00 00 00 02 00 00 00 01 00 00 00 17 B7 51 39 17 B7 D1 B8 00 00 80 3F 17 B7 D1 38 04 00 00 00 00 00 00 3F 17 B7 D1 38 00 00 00 BF 17 B7 D1 38 00 00 80 3F 17 B7 D1 B8 00 00 80 3F 00 00 80 3F 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 BF 17 B7 51 39 00 00 00 BF 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 3F 17 B7 51 39 00 00 00 3F 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 00 00 80 80 80 FF 00 00 00 BF 17 B7 D1 38 00 00 00 3F 17 B7 D1 B8 00 00 80 3F 17 B7 D1 38 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 80 80 FF 01 00 00 00 00 00 17 B7 D1 38 00 00 00 00 00 00 00 00 04 00 00 00 04 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF mdl.Checksum = (int)br.ReadUInt32(); mdl.ModelFlags = (Flags)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "Flags {0}", (Flags)mdl.ModelFlags); mdl.PrepDataSize = (int)br.ReadUInt32(); // PREP data size mdl.USERFaceCount = (int)br.ReadUInt32(); mdl.USERVertexCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "USER Faces: {0}", mdl.USERFaceCount); Logger.LogToFile(Logger.LogLevel.Debug, "USER Verts: {0}", mdl.USERVertexCount); mdl.FileSize = (int)br.ReadUInt32(); mdl.Extents.Radius = br.ReadSingle(); mdl.Extents.Min = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mdl.Extents.Max = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); br.ReadBytes(12); // BoundingBox centre, Flummery auto calculates from min and max int materialCount = br.ReadInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "Material count: {0}", materialCount); for (int i = 0; i < materialCount; i++) { string materialName; int nameLength; int padding; if (mdl.Version.Major < 6) { materialName = br.ReadBytes(32).ToName(); } else { nameLength = br.ReadInt32(); padding = (((nameLength / 4) + (nameLength % 4 > 0 ? 1 : 0)) * 4) - nameLength + (mdl.Version.Major == 6 && mdl.Version.Minor > 0 ? 4 : 0); materialName = br.ReadString(nameLength); br.ReadBytes(padding); } mdl.Meshes.Add(new MDLMaterialGroup(i, materialName)); } // START PREP DATA mdl.PREPFaceCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Faces: {0}", mdl.PREPFaceCount); for (int i = 0; i < mdl.PREPFaceCount; i++) { MDLFace face = new MDLFace( br.ReadUInt16(), // Material index br.ReadUInt16(), // Material Flags (int)br.ReadUInt32(), // Vert index A (int)br.ReadUInt32(), // Vert index B (int)br.ReadUInt32() // Vert index C ); mdl.Faces.Add(face); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1}", i, face); } mdl.PREPVertexCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Verts: {0}", mdl.PREPVertexCount); for (int i = 0; i < mdl.PREPVertexCount; i++) { MDLVertex vert = new MDLVertex( br.ReadSingle(), // X br.ReadSingle(), // Y br.ReadSingle(), // Z br.ReadSingle(), // N.X br.ReadSingle(), // N.Y br.ReadSingle(), // N.Z br.ReadSingle(), // U br.ReadSingle(), // V br.ReadSingle(), // U2 br.ReadSingle(), // V2 br.ReadByte(), // R br.ReadByte(), // G br.ReadByte(), // B br.ReadByte() // A ); mdl.Vertices.Add(vert); Logger.LogToFile(Logger.LogLevel.Debug, "{0} : {1}", i, vert); } int materialGroups = br.ReadUInt16(); for (int i = 0; i < materialGroups; i++) { MDLMaterialGroup mesh = mdl.Meshes[i]; br.ReadBytes(12); // BoundingBox Centre, we recalculate it from Min and Max mesh.Extents.Radius = br.ReadSingle(); mesh.Extents.Min = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mesh.Extents.Max = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mesh.StripOffset = (int)br.ReadUInt32(); mesh.StripVertCount = (int)br.ReadUInt32(); int stripPointCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Info, "{0} : {1} : {2}", mesh.StripOffset, mesh.StripVertCount, stripPointCount); for (int j = 0; j < stripPointCount; j++) { uint index = br.ReadUInt32(); bool bDegenerate = ((index & 0x80000000) != 0); index &= ~0x80000000; mesh.StripList.Add(new MDLPoint((int)index + mesh.StripOffset, bDegenerate)); Logger.LogToFile(Logger.LogLevel.Debug, "{0} ] {1} : {2}", j, index, bDegenerate); } mesh.TriListOffset = (int)br.ReadUInt32(); mesh.TriListVertCount = (int)br.ReadUInt32(); int listPointCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Info, "{0} : {1} : {2}", mesh.TriListOffset, mesh.TriListVertCount, listPointCount); for (int j = 0; j < listPointCount; j++) { uint index = br.ReadUInt32(); mesh.TriList.Add(new MDLPoint((int)index + mesh.TriListOffset)); Logger.LogToFile(Logger.LogLevel.Debug, "{0} ] {1}", j, index); } } if (mdl.ModelFlags.HasFlag(Flags.PREPSkinData)) { Logger.LogToFile(Logger.LogLevel.Debug, "Processing PREP skin data"); int bodyPartCount = br.ReadUInt16(); int maxBonesPerVertex = br.ReadUInt16(); int rootBoneIndex = br.ReadUInt16(); string[] boneNames = br.ReadStrings(bodyPartCount); Logger.LogToFile(Logger.LogLevel.Debug, "Body Part Count: {0}. Max Bones per Vertex : {1}. Root Bone Index : {2}", bodyPartCount, maxBonesPerVertex, rootBoneIndex); for (int i = 0; i < bodyPartCount; i++) { MDLBone bone = new MDLBone() { Name = boneNames[i], MinExtents = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // Min and Max bone local space MaxExtents = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), Offset = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // Offset is in parents local space Parent = br.ReadByte(), Child = br.ReadByte(), Sibling = br.ReadByte() }; mdl.prepBoneList.Add(bone); } for (int i = 0; i < bodyPartCount; i++) { mdl.prepBoneList[i].Rotation = new Vector4(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); mdl.prepBoneList[i].Position = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); br.ReadBytes(4); Logger.LogToFile(Logger.LogLevel.Debug, "{0}) {1}", i, mdl.prepBoneList[i].Name); Logger.LogToFile(Logger.LogLevel.Debug, "P{0} C{1} S{2}", mdl.prepBoneList[i].Parent, mdl.prepBoneList[i].Child, mdl.prepBoneList[i].Sibling); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].MinExtents); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].MaxExtents); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Offset); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Rotation); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", mdl.prepBoneList[i].Position); Logger.LogToFile(Logger.LogLevel.Debug, ""); } Logger.LogToFile(Logger.LogLevel.Debug, "PREP skin vert weight table"); for (int i = 0; i < mdl.PREPVertexCount; i++) { int weightCount = br.ReadUInt16(); br.ReadBytes(2); int weightOffset = (int)br.ReadUInt32(); mdl.prepVertSkinWeightLookup.Add(new MDLPrepSkinWeightLookup { Count = weightCount, Index = weightOffset }); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1} @ {2}", i, weightCount, weightOffset); } int prepSkinWeightCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP Skin Weight Count: {0}", prepSkinWeightCount); for (int i = 0; i < prepSkinWeightCount; i++) { int boneIndex = br.ReadUInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1}", i, boneIndex); } for (int i = 0; i < prepSkinWeightCount; i++) { float weight = br.ReadSingle(); Logger.LogToFile(Logger.LogLevel.Debug, "{0,5}] {1}", i, weight); } } // END PREP DATA if (mdl.ModelFlags.HasFlag(Flags.LODData)) { ushort lodLevel; do { lodLevel = br.ReadUInt16(); int nameLength = br.ReadInt32(); if (nameLength > 0) { int padding = (((nameLength / 4) + (nameLength % 4 > 0 ? 1 : 0)) * 4) - nameLength + (mdl.Version.Major == 6 && mdl.Version.Minor > 0 ? 4 : 0); string lodLevelName = br.ReadString(nameLength); br.ReadBytes(padding); mdl.LODs.Add(Load(br.BaseStream, lodLevelName)); } } while (lodLevel < 4); } // START USER DATA if (mdl.ModelFlags.HasFlag(Flags.USERData)) { mdl.UserFlags = (Flags)br.ReadUInt32(); // v5.6 successfully parses from this point down Logger.LogToFile(Logger.LogLevel.Debug, "USER vertex list with index count"); for (int i = 0; i < mdl.USERVertexCount; i++) { mdl.userVertexList.Add(new MDLUserVertexEntry(br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), (int)br.ReadUInt32())); } Logger.LogToFile(Logger.LogLevel.Debug, "USER face data"); for (int i = 0; i < mdl.USERFaceCount; i++) { if (mdl.Version.Major == 5 && mdl.Version.Minor == 6) { br.ReadBytes(133); } else { mdl.userFaceList.Add( new MDLUserFaceEntry( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), // plane equation new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[0] normal new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[1] normal new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()), // vertex[2] normal (int)br.ReadUInt32(), // material index (int)br.ReadUInt32(), // smoothing group (int)br.ReadUInt32(), // vertex[0] (int)br.ReadUInt32(), // vertex[1] (int)br.ReadUInt32(), // vertex[2] br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[0] RGBA br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[1] RGBA br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte(), // colour[2] RGBA new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[0] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[0] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[1] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[1] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv[2] new Vector2(br.ReadSingle(), br.ReadSingle()), // uv2[2] br.ReadByte(), // flags (int)br.ReadUInt32() // application specific flags ) // 137 bytes || 0x89 ); } } Logger.LogToFile(Logger.LogLevel.Debug, "PREP to USER face lookup"); for (int i = 0; i < mdl.PREPFaceCount; i++) { mdl.ptouFaceLookup.Add((int)br.ReadUInt32()); } int prepVertexMapCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "PREP to USER vertex lookup"); for (int i = 0; i < prepVertexMapCount; i++) { mdl.ptouVertexLookup.Add((int)br.ReadUInt32()); } if (mdl.UserFlags.HasFlag(Flags.USERSkinData)) { Logger.LogToFile(Logger.LogLevel.Debug, "Processing USER skin data"); int boneCount = br.ReadUInt16(); Logger.LogToFile(Logger.LogLevel.Debug, "Bone count: {0}", boneCount); for (int i = 0; i < boneCount; i++) { string boneName = br.ReadString(32); short parentBoneIndex = br.ReadInt16(); Matrix3D boneTransform = new Matrix3D( br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle(), br.ReadSingle() ); Logger.LogToFile(Logger.LogLevel.Debug, "{0}) {1}", i, boneName); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", parentBoneIndex); Logger.LogToFile(Logger.LogLevel.Debug, "{0}", boneTransform); Logger.LogToFile(Logger.LogLevel.Debug, ""); } int userDataCount = (int)br.ReadUInt32(); Logger.LogToFile(Logger.LogLevel.Debug, "{0} == {1}", userDataCount, mdl.USERVertexCount); for (int i = 0; i < mdl.USERVertexCount; i++) { int entryCount = br.ReadUInt16(); for (int j = 0; j < entryCount; j++) { int boneIndex = br.ReadUInt16(); float weight = br.ReadSingle(); Vector3 vertexPosition = new Vector3(br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); Logger.LogToFile(Logger.LogLevel.Debug, "{0}.{1}] {2,2} : {3,6:0.00}% : {4}", i, j, boneIndex, (weight * 100.0f), vertexPosition); } } } } if (br.BaseStream.Position != br.BaseStream.Length) { Logger.LogToFile(Logger.LogLevel.Warning, "Still has data remaining (processed {0:x2} of {1:x2}", br.BaseStream.Position, br.BaseStream.Length); } } return(mdl); }