private static void LoadSourceAnimationData(BinaryReader br, ModelData modelData, int numAnim, int animIndex) { modelData.SourceAnimations = new List<SourceAnimation>(); var restorePoint = br.BaseStream.Position; br.BaseStream.Position = animIndex; for (var i = 0; i < numAnim; i++) { var animStartPos = br.BaseStream.Position; var basePointer = br.ReadInt32(); var szNameIndex = br.ReadInt32(); var fps = br.ReadSingle(); var animDescFlags = br.ReadInt32(); var numframes = br.ReadInt32(); var numMovements = br.ReadInt32(); var movementIndex = br.ReadInt32(); br.ReadCoordinateFArray(2); // bounding box; unused var ablock = br.ReadInt32(); var aindex = br.ReadInt32(); var numIkRules = br.ReadInt32(); var ikRuleIndex = br.ReadInt32(); var animBlockIkRuleIndex = br.ReadInt32(); br.ReadIntArray(7); // Unused var animEndPos = br.BaseStream.Position; var sourceAnim = new SourceAnimation(i, numframes, fps, animDescFlags, numMovements, movementIndex, ablock, aindex, numIkRules, ikRuleIndex, animBlockIkRuleIndex); var currentOffset = aindex; short nextOffset; do { br.BaseStream.Position = animStartPos + currentOffset; var animBone = br.ReadByte(); var animFlags = br.ReadByte(); nextOffset = br.ReadInt16(); currentOffset += nextOffset; var aniBone = new SourceAnimationBone(animBone, animFlags, numframes); aniBone.ReadData(br); sourceAnim.AnimationBones.Add(aniBone); } while (nextOffset != 0); modelData.SourceAnimations.Add(sourceAnim); br.BaseStream.Position = animEndPos; } br.BaseStream.Position = restorePoint; }
private static void ReadStudioModel(BinaryReader br, string groupName, int bodyPartIndex, int modelIndex, ModelData data, DataStructures.Models.Model model) { var startModelPos = br.BaseStream.Position; var name = br.ReadFixedLengthString(Encoding.ASCII, 64); var type = br.ReadInt32(); var radius = br.ReadSingle(); var numMesh = br.ReadInt32(); var meshIndex = br.ReadInt32(); var numVerts = br.ReadInt32(); var vertInfoIndex = 0; if (data.Version == MDLVersionGoldsource) { vertInfoIndex = br.ReadInt32(); } var vertIndex = br.ReadInt32(); int numNorms = 0, normInfoIndex = 0, normIndex = 0; if (data.Version == MDLVersionGoldsource) { numNorms = br.ReadInt32(); normInfoIndex = br.ReadInt32(); normIndex = br.ReadInt32(); } else if (data.Version >= MDLVersionSource2006) { var tangentsIndex = br.ReadInt32(); } var numGroups = br.ReadInt32(); // Attachments in source var groupIndex = br.ReadInt32(); // Attachments if (data.Version >= MDLVersionSource2006) { var numEyeballs = br.ReadInt32(); var eyeballIndex = br.ReadInt32(); var vertexDataPointer = br.ReadInt32(); var tangentDataPointer = br.ReadInt32(); br.ReadIntArray(8); // Unused } var endPos = br.BaseStream.Position; if (data.Version == MDLVersionGoldsource) { ReadVerticesGoldSource(br, groupName, modelIndex, model, numVerts, vertInfoIndex, vertIndex, numMesh, meshIndex, numNorms, normInfoIndex, normIndex); } else if (data.Version >= MDLVersionSource2006) { ReadVerticesSource(br, groupName, bodyPartIndex, modelIndex, data, model, numMesh, startModelPos + meshIndex); } br.BaseStream.Position = endPos; }
private static void ReadSequence(BinaryReader br, int index, ModelData data, DataStructures.Models.Model model, IList<SequenceGroup> groups) { var startReadIndex = br.BaseStream.Position; var name = ""; var fps = 0f; if (data.Version == MDLVersionGoldsource) { name = br.ReadFixedLengthString(Encoding.ASCII, 32); fps = br.ReadSingle(); } else if (data.Version >= MDLVersionSource2006) { var baseIndex = br.ReadInt32(); var labelIndex = br.ReadInt32(); var activityNameIndex = br.ReadInt32(); } var flags = br.ReadInt32(); var activity = br.ReadInt32(); var actweight = br.ReadInt32(); var numevents = br.ReadInt32(); var eventindex = br.ReadInt32(); var numframes = 0; if (data.Version == MDLVersionGoldsource) { numframes = br.ReadInt32(); var numpivots = br.ReadInt32(); var pivotindex = br.ReadInt32(); var motiontype = br.ReadInt32(); var motionbone = br.ReadInt32(); var linearmovement = br.ReadCoordinateF(); var automoveposindex = br.ReadInt32(); var automoveangleindex = br.ReadInt32(); } var bbmin = br.ReadCoordinateF(); var bbmax = br.ReadCoordinateF(); var numblends = br.ReadInt32(); var animindex = br.ReadInt32(); var groupsize = new int[0]; if (data.Version >= MDLVersionSource2006) { var movementindex = br.ReadInt32(); groupsize = br.ReadIntArray(2); } var blendtype = br.ReadIntArray(2); // paramindex in source var blendstart = br.ReadSingleArray(2); // paramstart var blendend = br.ReadSingleArray(2); // paramend var blendparent = br.ReadInt32(); // paramparent var seqgroup = 0; if (data.Version == MDLVersionGoldsource) { seqgroup = br.ReadInt32(); } if (data.Version >= MDLVersionSource2006) { var fadeintime = br.ReadSingle(); var fadeouttime = br.ReadSingle(); } var entrynode = br.ReadInt32(); var exitnode = br.ReadInt32(); var nodeflags = br.ReadInt32(); if (data.Version >= MDLVersionSource2006) { var entryphase = br.ReadSingle(); var exitphase = br.ReadSingle(); var lastframe = br.ReadSingle(); } var nextseq = br.ReadInt32(); if (data.Version >= MDLVersionSource2006) { var pose = br.ReadInt32(); var numikrules = br.ReadInt32(); var numautolayers = br.ReadInt32(); var autolayerindex = br.ReadInt32(); var weightlistindex = br.ReadInt32(); var wlpos = br.BaseStream.Position; br.BaseStream.Position = startReadIndex + weightlistindex; var weightList = br.ReadSingleArray(model.Bones.Count); br.BaseStream.Position = wlpos; var posekeyindex = br.ReadInt32(); var numiklocks = br.ReadInt32(); var iklockindex = br.ReadInt32(); var keyvalueindex = br.ReadInt32(); var keyvaluesize = br.ReadInt32(); var cycleposeindex = br.ReadInt32(); br.ReadIntArray(7); // Unused } // Load animtion values var pos = br.BaseStream.Position; if (data.Version == MDLVersionGoldsource) { if (seqgroup > 0) { //TODO: load animations from other files return; // sub out br for another br against the new SG file // br = new BinaryReader(file....etc) // br.BaseStream.Position = animindex; } br.BaseStream.Position = groups[seqgroup].GroupZeroDataIndex + animindex; ReadAnimationGoldsource(br, model, numframes); } else if (data.Version >= MDLVersionSource2006) { br.BaseStream.Position = startReadIndex + animindex; ReadAnimationSource(br, data, model, groupsize); } br.BaseStream.Position = pos; }
private static DataStructures.Models.Model ReadModel(BinaryReader br, IFile file, ModelLoadItems loadItems) { // int id - Not really an int. This is a magic string, either "IDST" or "IDSQ". var magicString = br.ReadFixedLengthString(Encoding.UTF8, 4); if (magicString != MagicStringIDST && magicString != MagicStringIDSQ) { throw new ProviderException("Bad magic number for model. Expected [IDST,IDSQ], got: " + magicString); } // int version - Half-life 1 models are version 10. var version = br.ReadInt32(); if (version != MDLVersionGoldsource && version != MDLVersionSource2006 && version != MDLVersionSourceEpisode2 && version != MDLVersionSourcePortal && version != MDLVersionSource2007 && version != MDLVersionSource2012) { throw new ProviderException("Bad version number for model. Expected [10,44,45,46,48,49], got: " + version); } var modelData = new ModelData {Version = version}; if (version >= MDLVersionSource2006) { if (loadItems.HasFlag(ModelLoadItems.Meshes)) { // Source vertex and mesh info is stored in flat file structures, preload these separately. LoadSourceMeshData(modelData, file); } } long checksum = 0; if (version >= MDLVersionSource2006) { checksum = br.ReadInt32(); // This is a long in the headers but is only written to the file in 4 bytes. Why? I don't know. } // char name[64] - The name of the model (file path) var path = br.ReadFixedLengthString(Encoding.UTF8, 64); // int length - The size of the model file in bytes var fileSize = br.ReadInt32(); var eyePosition = br.ReadCoordinateF(); var illumPosition = CoordinateF.Zero; if (version >= MDLVersionSource2006) { illumPosition = br.ReadCoordinateF(); } var hullMin = br.ReadCoordinateF(); var hullMax = br.ReadCoordinateF(); var bbMin = br.ReadCoordinateF(); var bbMax = br.ReadCoordinateF(); // int flags - Unknown. var flags = br.ReadInt32(); var numBones = br.ReadInt32(); var boneIndex = br.ReadInt32(); var numBoneControllers = br.ReadInt32(); var boneControllerIndex = br.ReadInt32(); var numHitBoxes = br.ReadInt32(); var hitboxIndex = br.ReadInt32(); if (version >= MDLVersionSource2006) { var numAnim = br.ReadInt32(); var animIndex = br.ReadInt32(); if (loadItems.HasFlag(ModelLoadItems.Animations)) { // Source animation data is stored on their own instead of inside the sequence LoadSourceAnimationData(br, modelData, numAnim, animIndex); } } var numSeq = br.ReadInt32(); var seqIndex = br.ReadInt32(); int numSeqGroups = 0, seqGroupIndex = 0, activitylistversion = 0, eventsindexed = 0; if (version >= MDLVersionSource2006) { activitylistversion = br.ReadInt32(); eventsindexed = br.ReadInt32(); } else if (version == MDLVersionGoldsource) { numSeqGroups = br.ReadInt32(); seqGroupIndex = br.ReadInt32(); } var numTextures = br.ReadInt32(); var textureIndex = br.ReadInt32(); var textureDataIndex = 0; if (version == MDLVersionGoldsource) { textureDataIndex = br.ReadInt32(); } if (version >= MDLVersionSource2006) { var numcdtextures = br.ReadInt32(); var cdtextureindex = br.ReadInt32(); } var numSkinRef = br.ReadInt32(); var numSkinFamilies = br.ReadInt32(); var skinIndex = br.ReadInt32(); var numBodyParts = br.ReadInt32(); var bodyPartIndex = br.ReadInt32(); var numAttachments = br.ReadInt32(); var attachmentIndex = br.ReadInt32(); if (version >= MDLVersionSource2006) { var numlocalnodes = br.ReadInt32(); var localnodeindex = br.ReadInt32(); var localnodenameindex = br.ReadInt32(); var numflexdesc = br.ReadInt32(); var flexdescindex = br.ReadInt32(); var numflexcontrollers = br.ReadInt32(); var flexcontrollerindex = br.ReadInt32(); var numflexrules = br.ReadInt32(); var flexruleindex = br.ReadInt32(); var numikchains = br.ReadInt32(); var ikchainindex = br.ReadInt32(); var nummouths = br.ReadInt32(); var mouthindex = br.ReadInt32(); var numlocalposeparameters = br.ReadInt32(); var localposeparamindex = br.ReadInt32(); var surfacepropindex = br.ReadInt32(); var keyvalueindex = br.ReadInt32(); var keyvaluesize = br.ReadInt32(); var numlocalikautoplaylocks = br.ReadInt32(); var localikautoplaylockindex = br.ReadInt32(); var mass = br.ReadSingle(); var contents = br.ReadInt32(); var numincludemodels = br.ReadInt32(); var includemodelindex = br.ReadInt32(); var virtualModelPointer = br.ReadInt32(); var szanimblocknameindex = br.ReadInt32(); var numanimblocks = br.ReadInt32(); var animblockindex = br.ReadInt32(); var animblockModelPointer = br.ReadInt32(); var bonetablebynameindex = br.ReadInt32(); var pVertexBasePointer = br.ReadInt32(); var pIndexBasePointer = br.ReadInt32(); var constdirectionallightdot = br.ReadByte(); var rootLod = br.ReadByte(); var numAllowedRootLods = br.ReadByte(); // Unused in Source2006 br.ReadByte(); // Unused var zeroframecacheindex = br.ReadInt32(); // Unused in Source2007 if (version == MDLVersionSource2006) { br.ReadBytes(6); // Unused } else if (version == MDLVersionSource2007) { var numflexcontrollerui = br.ReadInt32(); var flexcontrolleruiindex = br.ReadInt32(); br.ReadIntArray(2); // Unused var studiohdr2Index = br.ReadInt32(); br.ReadInt32(); // Unused } } else if (version == MDLVersionGoldsource) { var soundTable = br.ReadInt32(); var soundIndex = br.ReadInt32(); var soundGroups = br.ReadInt32(); var soundGroupIndex = br.ReadInt32(); var numTransitions = br.ReadInt32(); var transitionIndex = br.ReadInt32(); } var model = new DataStructures.Models.Model(); model.Name = file.NameWithoutExtension; model.BonesTransformMesh = modelData.Version == MDLVersionGoldsource; if (loadItems.HasFlag(ModelLoadItems.Bones)) { // Bones br.BaseStream.Position = boneIndex; for (var i = 0; i < numBones; i++) ReadBone(br, i, modelData, model); } // Controllers // TODO // Attachments // TODO // Hitboxes // TODO if (loadItems.HasFlag(ModelLoadItems.Animations)) { if (version >= MDLVersionSource2006) { throw new ProviderException("Source animations are currently not supported."); } // Sequence Groups var groups = new List<SequenceGroup>(); br.BaseStream.Position = seqGroupIndex; for (var i = 0; i < numSeqGroups; i++) groups.Add(ReadSequenceGroup(br, modelData)); // Sequences br.BaseStream.Position = seqIndex; for (var i = 0; i < numSeq; i++) ReadSequence(br, i, modelData, model, groups); } // Transitions // TODO if (loadItems.HasFlag(ModelLoadItems.Meshes)) { // Body parts br.BaseStream.Position = bodyPartIndex; for (var i = 0; i < numBodyParts; i++) ReadBodyPart(br, i, modelData, model); } // Texture Info if (loadItems.HasFlag(ModelLoadItems.TextureInfo) || loadItems.HasFlag(ModelLoadItems.TextureData)) { ReadTextureInfo(file, br, modelData, model, numTextures, textureIndex); } // Textures return model; }
private static void ReadBone(BinaryReader br, int index, ModelData data, DataStructures.Models.Model model) { var name = ""; var nameIndex = 0; if (data.Version >= MDLVersionSource2006) { nameIndex = br.ReadInt32(); } else if (data.Version == MDLVersionGoldsource) { name = br.ReadFixedLengthString(Encoding.UTF8, 32); } var parent = br.ReadInt32(); int flags = 0; if (data.Version == MDLVersionGoldsource) { flags = br.ReadInt32(); } var boneController = br.ReadIntArray(6); // 3 pos, 3 rot var defPos = br.ReadCoordinateF(); QuaternionF quat = null; if (data.Version >= MDLVersionSource2006) { // quaternion quat = new QuaternionF(br.ReadCoordinateF(), br.ReadSingle()); } var defAng = br.ReadCoordinateF(); var defPosScale = br.ReadCoordinateF(); var defAngScale = br.ReadCoordinateF(); if (data.Version >= MDLVersionSource2006) { var poseToBone = br.ReadIntArray(12); // 3x4 matrix var qAlignment = new QuaternionF(br.ReadCoordinateF(), br.ReadSingle()); flags = br.ReadInt32(); var proctype = br.ReadInt32(); var procindex = br.ReadInt32(); var physicsbone = br.ReadInt32(); var surfacepropidx = br.ReadInt32(); var contents = br.ReadInt32(); br.ReadIntArray(8); // Unused } var parentBone = parent < 0 ? null : model.Bones[parent]; model.Bones.Add(new Bone(index, parent, parentBone, name, defPos, defAng, defPosScale, defAngScale)); }
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) }
private static void ReadVerticesSource(BinaryReader br, string groupName, int bodyPartIndex, int modelIndex, ModelData modelData, DataStructures.Models.Model model, int numMesh, long meshIndex) { br.BaseStream.Position = meshIndex; for (var i = 0; i < numMesh; i++) { var material = br.ReadInt32(); var modelOffset = br.ReadInt32(); var numVerts = br.ReadInt32(); var vertexOffset = br.ReadInt32(); var numFlexes = br.ReadInt32(); var flexIndex = br.ReadInt32(); var materialType = br.ReadInt32(); var materialParam = br.ReadInt32(); var meshId = br.ReadInt32(); var center = br.ReadCoordinateF(); var modelVertexDataPointer = br.ReadInt32(); var numLODVertices = br.ReadIntArray(8); br.ReadIntArray(8); // Unused foreach (var mm in modelData.Meshes.Where(mm => mm.BodyPart == bodyPartIndex && mm.Model == modelIndex && mm.LOD == 0 && mm.MeshIndex == meshId)) { var mesh = new Mesh(mm.LOD); foreach (var point in mm.Mesh.Points) { var vert = modelData.Vertices[point.VertexIndex + vertexOffset]; var boneWeights = new List<BoneWeighting>(); for (var j = 0; j < vert.NumBones; j++) { boneWeights.Add(new BoneWeighting(model.Bones[vert.Bones[j]], vert.BoneWeights[j])); } var mv = new MeshVertex(vert.Position, vert.Normal, boneWeights, vert.TextureS, vert.TextureT); mesh.Vertices.Add(mv); } model.AddMesh(groupName, modelIndex, mesh); } } }