private static void LoadSourceMeshData(ModelData modelData, IFile file) { modelData.Meshes = new List<VTXModel>(); // In Source the vertices are saved to the VVD file // The vertex windings are saved in the VTX file var vvd = file.GetRelatedFile("vvd"); var vtx = file.GetRelatedFile("vtx"); if (vvd == null) throw new ProviderException("Unable to locate " + file.NameWithoutExtension + ".vvd"); if (vtx == null) throw new ProviderException("Unable to locate " + file.NameWithoutExtension + ".vtx"); var vertices = new List<VVDPoint>(); using (var fs = vvd.Open()) { using (var vbr = new BinaryReader(fs)) { var magicString = vbr.ReadFixedLengthString(Encoding.UTF8, 4); if (magicString != MagicStringIDSV) { throw new ProviderException("Bad magic number for vertex file. Expected IDSV, got: " + magicString); } var version = vbr.ReadInt32(); if (version != VVDVersionSource) { throw new ProviderException("Bad version number for vertex file. Expected 4, got: " + version); } long checksum = vbr.ReadInt32(); var numLods = vbr.ReadInt32(); var numLodVertices = vbr.ReadIntArray(8); var numFixups = vbr.ReadInt32(); var fixupTableStart = vbr.ReadInt32(); var vertexDataStart = vbr.ReadInt32(); var tangentDataStart = vbr.ReadInt32(); vbr.BaseStream.Position = vertexDataStart; // Read all the vertices from LOD 0 (this should contain the vertices for all LODs) for (var i = 0; i < numLodVertices[0]; i++) { var boneWeights = vbr.ReadSingleArray(3); var bones = vbr.ReadBytes(3); var numBones = vbr.ReadByte(); var position = vbr.ReadCoordinateF(); var normal = vbr.ReadCoordinateF(); var textureS = vbr.ReadSingle(); var textureT = vbr.ReadSingle(); vertices.Add(new VVDPoint(boneWeights, bones, numBones, position, normal, textureS, textureT)); } // Apply the fixup table, this re-orders the indices in reverse LOD order for performance reasons if (numFixups > 0) { vbr.BaseStream.Position = fixupTableStart; var newVerts = new List<VVDPoint>(); for (var i = 0; i < numFixups; i++) { var fuLod = vbr.ReadInt32(); var fuvertid = vbr.ReadInt32(); var funumverts = vbr.ReadInt32(); newVerts.AddRange(vertices.GetRange(fuvertid, funumverts)); } vertices.Clear(); vertices.AddRange(newVerts); } modelData.Vertices = vertices; } } using (var fs = vtx.Open()) { using (var vbr = new BinaryReader(fs)) { var version = vbr.ReadInt32(); // 7 if (version != VTXVersionSource) { throw new ProviderException("Bad version number for vertex file. Expected 7, got: " + version); } var vertCacheSize = vbr.ReadInt32(); var maxBonesPerStrip = vbr.ReadUInt16(); var maxBonesPerTri = vbr.ReadUInt16(); var maxBonesPerVert = vbr.ReadInt32(); long checksum = vbr.ReadInt32(); var numLods = vbr.ReadInt32(); var materialReplacementListOffset = vbr.ReadInt32(); var numBodyParts = vbr.ReadInt32(); var bodyPartOffset = vbr.ReadInt32(); // BODY PARTS long posbp = bodyPartOffset; for (var bp = 0; bp < numBodyParts; bp++) { vbr.BaseStream.Position = posbp; var numModels = vbr.ReadInt32(); var modelOffset = vbr.ReadInt32(); var posmdl = posbp + modelOffset; posbp = vbr.BaseStream.Position; // MODELS for (var mdl = 0; mdl < numModels; mdl++) { vbr.BaseStream.Position = posmdl; var numLod = vbr.ReadInt32(); var lodOffset = vbr.ReadInt32(); var poslod = posmdl + lodOffset; posmdl = vbr.BaseStream.Position; // LODS for (var lod = 0; lod < numLod; lod++) { vbr.BaseStream.Position = poslod; var meshNum = vbr.ReadInt32(); var meshOffset = vbr.ReadInt32(); var switchPoint = vbr.ReadSingle(); var posmesh = poslod + meshOffset; poslod = vbr.BaseStream.Position; // MESHES for (var msh = 0; msh < meshNum; msh++) { vbr.BaseStream.Position = posmesh; var sgNum = vbr.ReadInt32(); var sgOffset = vbr.ReadInt32(); var meshFlags = vbr.ReadByte(); var possg = posmesh + sgOffset; posmesh = vbr.BaseStream.Position; var mesh = new VTXModel(bp, mdl, lod, msh); // STRIP GROUPS for (var sg = 0; sg < sgNum; sg++) { vbr.BaseStream.Position = possg; var vertNum = vbr.ReadInt32(); var vertOffset = vbr.ReadInt32(); var indexNum = vbr.ReadInt32(); var indexOffset = vbr.ReadInt32(); var stripNum = vbr.ReadInt32(); var stripOffset = vbr.ReadInt32(); var sgFlags = vbr.ReadByte(); // vbr.ReadIntArray(2); //TODO FIXME Newer model format 49's (DOTA2, CSGO) have two extra integers here, (num + offset, purpose unknown) var posvert = possg + vertOffset; var posidx = possg + indexOffset; var posstrip = possg + stripOffset; possg = vbr.BaseStream.Position; var vertinfo = new List<VTXPoint>(); vbr.BaseStream.Position = posvert; for (var vert = 0; vert < vertNum; vert++) { var boneWeightIndices = vbr.ReadBytes(3); var numBones = vbr.ReadByte(); var meshVertex = vbr.ReadInt16(); var boneIDs = vbr.ReadBytes(3); vertinfo.Add(new VTXPoint(boneWeightIndices, numBones, meshVertex, boneIDs)); } vbr.BaseStream.Position = posidx; var indices = vbr.ReadShortArray(indexNum); // The strips hold info about whether this is a triangle strip or just a list vbr.BaseStream.Position = posstrip; for (var st = 0; st < stripNum; st++) { var numStIndices = vbr.ReadInt32(); var stIndexOffset = vbr.ReadInt32(); var numStVerts = vbr.ReadInt32(); var stVertOffset = vbr.ReadInt32(); var numStBones = vbr.ReadInt16(); var stFlags = vbr.ReadByte(); var numStBoneStateChanges = vbr.ReadInt32(); var stBoneStateChangeOffset = vbr.ReadInt32(); // vbr.ReadIntArray(2); //TODO FIXME Newer model format 49's (DOTA2, CSGO) have two extra integers here, (num + offset, purpose unknown) if ((stFlags & VTXStripGroupTriListFlag) > 0) { for (var j = stIndexOffset; j < stIndexOffset + numStIndices; j++) { mesh.Mesh.Points.Add(vertinfo[indices[j]]); //mesh.Vertices.Add(vertices[vertinfo[indices[j]]]); } } else if ((stFlags & VTXStripGroupTriStripFlag) > 0) { for (var j = stIndexOffset; j < stIndexOffset + numStIndices - 2; j++) { var add = j % 2 == 1 ? new[] { j + 1, j, j + 2 } : new[] { j, j + 1, j + 2 }; foreach (var idx in add) { mesh.Mesh.Points.Add(vertinfo[indices[idx]]); //mesh.Vertices.Add(vertices[vertinfo[indices[idx]]]); } } } } // Strips } // Strip Groups modelData.Meshes.Add(mesh); } // Meshes } // LODs } // Models } // Body Parts } // using (var br) } // using (var fs) }