//Initialize should only be called from parent group during parse. //Bones need not be imported/exported anyways protected override bool OnInitialize() { MDL0Bone *header = Header; SetSizeInternal(header->_headerLen); //Assign true parent using parent header offset int offset = header->_parentOffset; //Offsets are always < 0, because parent entries are listed before children if (offset < 0) { //Get address of parent header MDL0Bone *pHeader = (MDL0Bone *)((byte *)header + offset); //Search bone list for matching header foreach (MDL0BoneNode bone in _parent._children) { if (pHeader == bone.Header) { _parent = bone; break; } //Assign parent and break } } //Conditional name assignment if ((_name == null) && (header->_stringOffset != 0)) { _name = header->ResourceString; } //Assign fields _flags = (BoneFlags)(uint)header->_flags; _nodeIndex = header->_nodeId; _boneIndex = header->_index; _permanentID = header->_index; _bindState = _frameState = new FrameState(header->_scale, header->_rotation, header->_translation); (_bindState._quaternion = new Quaternion()).FromEuler(header->_rotation); _bindMatrix = _frameMatrix = header->_transform; _inverseBindMatrix = _inverseFrameMatrix = header->_transformInv; _bMin = header->_boxMin; _bMax = header->_boxMax; if (header->_part2Offset != 0) { Part2Data * part2 = (Part2Data *)((byte *)header + header->_part2Offset); ResourceGroup *group = part2->Group; ResourceEntry *pEntry = &group->_first + 1; int count = group->_numEntries; for (int i = 0; i < count; i++) { _entries.Add(new String((sbyte *)group + (pEntry++)->_stringOffset)); } } //We don't want to process children because not all have been parsed yet. //Child assigning will be handled by the parent group. return(false); }
public void CalculateOffsets() { MDL0Bone *header = (MDL0Bone *)WorkingUncompressed.Address; //Set first child header->_firstChildOffset = _children.Count > 0 ? (int)Children[0].WorkingUncompressed.Address - (int)header : 0; if (Parent != null) { int index = Index; //Parent header->_parentOffset = Parent is MDL0BoneNode ? (int)Parent.WorkingUncompressed.Address - (int)header : 0; //Previous sibling header->_prevOffset = index == 0 ? 0 : (int)Parent._children[index - 1].WorkingUncompressed.Address - (int)header; //Next sibling header->_nextOffset = index == (Parent._children.Count - 1) ? 0 : (int)Parent._children[index + 1].WorkingUncompressed.Address - (int)header; } }
public override void OnRebuild(VoidPtr address, int length, bool force) { MDL0Bone *header = (MDL0Bone *)address; RecalcOffsets(header, address, length); if (_refCount > 0 || _weightCount > 0 || InfluencedObjects.Length > 0) { _flags1 |= BoneFlags.HasGeometry; } else { _flags1 &= ~BoneFlags.HasGeometry; } header->_headerLen = length; header->_index = _boneIndex = _entryIndex; header->_nodeId = _nodeIndex; header->_flags = (uint)_flags1; header->_bbFlags = (uint)_flags2; header->_bbNodeId = _bbNodeId; header->_scale = _bindState._scale; header->_rotation = _bindState._rotate; header->_translation = _bindState._translate; header->_boxMin = _bMin; header->_boxMax = _bMax; header->_transform = (bMatrix43)_bindMatrix; header->_transformInv = (bMatrix43)_inverseBindMatrix; _moved = false; }
protected internal override void PostProcess(VoidPtr mdlAddress, VoidPtr dataAddress, StringTable stringTable) { MDL0Bone *header = (MDL0Bone *)dataAddress; header->MDL0Address = mdlAddress; header->ResourceStringAddress = stringTable[Name] + 4; _userEntries.PostProcess(dataAddress + 0xD0, stringTable); }
protected internal override void OnRebuild(VoidPtr address, int length, bool force) { MDL0Bone *header = (MDL0Bone *)address; RecalcOffsets(header, address, length); if (_flags == 0 || flagsChanged) { CalcFlags(); } header->_headerLen = length; //if (!_override) header->_index = _boneIndex = _entryIndex; // - Model._overrideCount; //else // header->_index = _boneIndex = Model._linker.BoneCache.Length - 1 + Model._overrideCount++; header->_nodeId = _nodeIndex; header->_flags = (uint)_flags; header->_pad1 = 0; header->_pad2 = 0; header->_scale = _bindState._scale; header->_rotation = _bindState._rotate; header->_translation = _bindState._translate; header->_boxMin = _bMin; header->_boxMax = _bMax; //if (_bindMatrix != _frameMatrix) //{ // header->_transform = (bMatrix43)_frameMatrix; // header->_transformInv = (bMatrix43)_inverseFrameMatrix; //} //else //{ header->_transform = (bMatrix43)_bindMatrix; header->_transformInv = (bMatrix43)_inverseBindMatrix; //} _moved = false; }
protected internal override void PostProcess(VoidPtr mdlAddress, VoidPtr dataAddress, StringTable stringTable) { MDL0Bone *header = (MDL0Bone *)dataAddress; header->_mdl0Offset = (int)mdlAddress - (int)dataAddress; header->_stringOffset = (int)stringTable[Name] + 4 - (int)dataAddress; //Entry strings if (_entries.Count > 0) { ResourceGroup *pGroup = (ResourceGroup *)((byte *)header + header->_part2Offset + 4); ResourceEntry *pEntry = &pGroup->_first; int count = pGroup->_numEntries; (*pEntry++) = new ResourceEntry(0xFFFF, 0, 0, 0, 0); for (int i = 0; i < count; i++) { Part2DataEntry *entry = (Part2DataEntry *)((byte *)pGroup + (pEntry++)->_dataOffset); entry->_stringOffset = (int)stringTable[_entries[i]] + 4 - ((int)entry + (int)dataAddress); ResourceEntry.Build(pGroup, i + 1, entry, (BRESString *)stringTable[_entries[i]]); } } }
//Initialize should only be called from parent group during parse. //Bones need not be imported/exported anyways public override bool OnInitialize() { MDL0Bone *header = Header; SetSizeInternal(header->_headerLen); //Conditional name assignment if (_name == null && header->_stringOffset != 0) { _name = header->ResourceString; } //Assign fields _boneFlags = (BoneFlags)(uint)header->_flags; _billboardFlags = (BillboardFlags)(uint)header->_bbFlags; _nodeIndex = header->_nodeId; _entryIndex = header->_index; _bbRefNode = !_replaced && _boneFlags.HasFlag(BoneFlags.HasBillboardParent) ? Model._linker.NodeCache[header->_bbNodeId] as MDL0BoneNode : null; if (_billboardFlags != BillboardFlags.Off) { Model._billboardBones.Add(this); //Update mesh in T-Pose } _bindState = _frameState = new FrameState(header->_scale, (Vector3)header->_rotation, header->_translation); _bindMatrix = _frameMatrix = header->_transform; _inverseBindMatrix = _inverseFrameMatrix = header->_transformInv; _extents = header->_extents; (_userEntries = new UserDataCollection()).Read(header->UserDataAddress); //We don't want to process children because not all have been parsed yet. //Child assignments will be handled by the parent group. return(false); }
public override void OnRebuild(VoidPtr address, int length, bool force) { MDL0Bone *header = (MDL0Bone *)address; if (Users.Count > 0 || SingleBindObjects.Length > 0) { _boneFlags |= BoneFlags.HasGeometry; } else { _boneFlags &= ~BoneFlags.HasGeometry; } header->_headerLen = length; header->_index = _entryIndex; header->_nodeId = _nodeIndex; header->_flags = (uint)_boneFlags; header->_bbFlags = (uint)_billboardFlags; header->_bbIndex = _bbRefNode == null ? 0 : (uint)_bbRefNode._entryIndex; header->_scale = _bindState._scale; header->_rotation = _bindState._rotate; header->_translation = _bindState._translate; header->_extents = _extents; header->_transform = (bMatrix43)_bindMatrix; header->_transformInv = (bMatrix43)_inverseBindMatrix; if (_userEntries.Count > 0) { header->_userDataOffset = 0xD0; _userEntries.Write(address + 0xD0); } else { header->_userDataOffset = 0; } }
protected internal override void OnRebuild(VoidPtr address, int length, bool force) { MDL0Bone * header = (MDL0Bone *)address; MDL0BoneNode bone; int index = 0, offset; header->_headerLen = length; header->_index = _entryIndex; header->_nodeId = _nodeIndex; header->_flags = (uint)_flags; header->_pad1 = 0; header->_pad2 = 0; header->_scale = _bindState._scale; header->_rotation = _bindState._rotate; header->_translation = _bindState._translate; header->_boxMin = _bMin; header->_boxMax = _bMax; header->_transform = _bindMatrix; header->_transformInv = _inverseBindMatrix; //Sub-entries if (_entries.Count > 0) { header->_part2Offset = 0xD0; *(bint *)((byte *)address + 0xD0) = 0x1C + (_entries.Count * 0x2C); ResourceGroup *pGroup = (ResourceGroup *)((byte *)address + 0xD4); ResourceEntry *pEntry = &pGroup->_first + 1; byte * pData = (byte *)pGroup + pGroup->_totalSize; *pGroup = new ResourceGroup(_entries.Count); foreach (string s in _entries) { (pEntry++)->_dataOffset = (int)pData - (int)pGroup; MDL0Data7Part4Entry *p = (MDL0Data7Part4Entry *)pData; *p = new MDL0Data7Part4Entry(1); pData += 0x1C; } } else { header->_part2Offset = 0; } //Set first child if (_children.Count > 0) { header->_firstChildOffset = length; } else { header->_firstChildOffset = 0; } if (_parent != null) { index = _parent._children.IndexOf(this); //Parent if (_parent is MDL0BoneNode) { header->_parentOffset = (int)_parent.WorkingUncompressed.Address - (int)address; } else { header->_parentOffset = 0; } //Prev if (index == 0) { header->_prevOffset = 0; } else { //Link to prev bone = _parent._children[index - 1] as MDL0BoneNode; offset = (int)bone.Header - (int)address; header->_prevOffset = offset; bone.Header->_nextOffset = -offset; } //Next if (index == (_parent._children.Count - 1)) { header->_nextOffset = 0; } } }
public Bone(MDL0Bone *pBone) { }
internal void Parse(MDL0Node model) { Influence inf; ModelLinker linker = model._linker; int typeIndex = (int)_type; fixed(ResourceGroup **gList = &linker.Defs) if (gList[typeIndex] != null) ExtractGroup(gList[typeIndex], ModelLinker.TypeBank[typeIndex]); else return; //Nothing to read //Special handling for bones and objects if (_type == MDLResourceType.Bones) { //Bones have been parsed from raw data as a flat list. //Bones re-assign parents in their Initialize block, so parents are true. //Parents must be assigned now as bones will be moved in memory when assigned as children. //Cache flat list linker.BoneCache = _children.Select(x => x as MDL0BoneNode).ToArray(); //Reset children so we can rebuild _children.Clear(); //Assign children using each bones' parent offset in case NodeTree is corrupted. //Bone parents are assigned when they are initialized in a flat array. foreach (MDL0BoneNode b in linker.BoneCache) { MDL0Bone *header = b.Header; //Assign true parent using parent header offset int offset = header->_parentOffset; if (offset != 0) { //Get address of parent header MDL0Bone *pHeader = (MDL0Bone *)((byte *)header + offset); //Search bone list for matching header foreach (MDL0BoneNode b2 in linker.BoneCache) { if (pHeader == b2.Header) { b._parent = b2; break; } } } if (b._boneFlags.HasFlag(BoneFlags.HasBillboardParent)) { b._bbRefNode = model._linker.BoneCache[header->_bbIndex] as MDL0BoneNode; } } //Make sure the node cache is the correct size int highest = 0; //Add bones to their parent's child lists and find highest node id foreach (MDL0BoneNode b in linker.BoneCache) { b._parent._children.Add(b); if (b._nodeIndex >= linker.NodeCache.Length && b._nodeIndex > highest) { highest = b._nodeIndex; } } if (highest >= linker.NodeCache.Length) { linker.NodeCache = new IMatrixNode[highest + 1]; } //Populate node cache MDL0BoneNode bone = null; int index; int count = linker.BoneCache.Length; for (int i = 0; i < count; i++) { linker.NodeCache[(bone = linker.BoneCache[i] as MDL0BoneNode)._nodeIndex] = bone; } int nullCount = 0; bool nodeTreeError = false; //Now that bones and primary influences have been cached, we can create weighted influences. foreach (ResourcePair p in *linker.Defs) { if (p.Name == "NodeTree") { //Double check bone tree using the NodeTree definition. //If the NodeTree is corrupt, the user will be informed that it needs to be rebuilt. byte *pData = (byte *)p.Data; bool fixCS0159 = false; List <MDL0BoneNode> bones = linker.BoneCache.ToList(); STop: if (*pData == 2) { bone = linker.BoneCache[*(bushort *)(pData + 1)] as MDL0BoneNode; index = *(bushort *)(pData + 3); //Parent bone node index if (bone.Header->_parentOffset == 0) { if (!_children.Contains(bone)) { nodeTreeError = true; continue; } else { bones.Remove(bone); } } else { MDL0BoneNode parent = linker.NodeCache[index] as MDL0BoneNode; if (parent == null || bone._parent != parent || !parent._children.Contains(bone)) { nodeTreeError = true; continue; } else { bones.Remove(bone); } } pData += 5; fixCS0159 = true; } if (fixCS0159) { fixCS0159 = false; goto STop; } if (bones.Count > 0) { nodeTreeError = true; } } else if (p.Name == "NodeMix") { //Use node mix to create weight groups byte *pData = (byte *)p.Data; bool fixCS0159 = false; TTop: switch (*pData) { //Type 3 is for weighted influences case 3: //Get index/count fields index = *(bushort *)(pData + 1); count = pData[3]; //Get data pointer (offset of 4) MDL0NodeType3Entry *nEntry = (MDL0NodeType3Entry *)(pData + 4); //Create influence with specified count inf = new Influence(); //Iterate through weights, adding each to the influence //Here, we are referring back to the NodeCache to grab the bone. //Note that the weights do not reference other influences, only bones. There is a good reason for this. MDL0BoneNode b = null; List <int> nullIndices = new List <int>(); for (int i = 0; i < count; i++, nEntry++) { if (nEntry->_id < linker.NodeCache.Length && (b = (linker.NodeCache[nEntry->_id] as MDL0BoneNode)) != null) { inf.AddWeight(new BoneWeight(b, nEntry->_value)); } else { nullIndices.Add(i); } } bool noWeights = false; if ((nullCount = nullIndices.Count) > 0) { List <BoneWeight> newWeights = new List <BoneWeight>(); for (int i = 0; i < inf.Weights.Count; i++) { if (!nullIndices.Contains(i)) { newWeights.Add(inf.Weights[i]); } } if (newWeights.Count == 0) { noWeights = true; } else { inf.SetWeights(newWeights); } } //Add influence to model object, while adding it to the cache. //Don't add user references here, they will be added during each object's initialization if (!noWeights) { ((Influence)(linker.NodeCache[index] = model._influences.FindOrCreate(inf)))._index = index; } //Move data pointer to next entry pData = (byte *)nEntry; fixCS0159 = true; break; //Type 5 is for primary influences case 5: pData += 5; fixCS0159 = true; break; } if (fixCS0159) { fixCS0159 = false; goto TTop; } } } if (nullCount > 0) { model._errors.Add("There were " + nullCount + " null weights in NodeMix."); } if (nodeTreeError) { model._errors.Add("The NodeTree definition did not match the bone tree."); } } else if (_type == MDLResourceType.Objects) { //Attach materials to polygons. //This assumes that materials have already been parsed. List <ResourceNode> matList = ((MDL0Node)_parent)._matList; MDL0ObjectNode obj; MDL0MaterialNode mat; //Find DrawOpa or DrawXlu entry in Definition list foreach (ResourcePair p in *linker.Defs) { if ((p.Name == "DrawOpa") || (p.Name == "DrawXlu")) { bool isXLU = p.Name == "DrawXlu"; ushort objectIndex = 0; byte * pData = (byte *)p.Data; while (*pData++ == 4) { //Get object with index objectIndex = *(bushort *)(pData + 2); if (objectIndex >= _children.Count || objectIndex < 0) { model._errors.Add("Object index was greater than the actual object count."); objectIndex = 0; } obj = _children[objectIndex] as MDL0ObjectNode; //Get material with index mat = matList[*(bushort *)pData] as MDL0MaterialNode; //Get bone with index int boneIndex = *(bushort *)(pData + 4); MDL0BoneNode visBone = null; if (linker.BoneCache != null && boneIndex >= 0 && boneIndex < linker.BoneCache.Length) { visBone = linker.BoneCache[boneIndex] as MDL0BoneNode; } obj._drawCalls.Add(new DrawCall(obj) { _drawOrder = pData[6], _isXLU = isXLU, MaterialNode = mat, VisibilityBoneNode = visBone, }); //Increment pointer pData += 7; } } } foreach (MDL0ObjectNode m in _children) { int max = 0; foreach (DrawCall c in m._drawCalls) { max = Maths.Max(max, c.MaterialNode.Children.Count); if (c.MaterialNode.MetalMaterial != null) { max = Maths.Max(max, c.MaterialNode.MetalMaterial.Children.Count); } } bool hasUnused = false; if (m._manager != null) { for (int i = max; i < 8; i++) { if (m._manager.HasTextureMatrix[i]) { m._manager.HasTextureMatrix[i] = false; m._forceRebuild = true; hasUnused = true; } } } if (hasUnused) { ((MDL0Node)Parent)._errors.Add("Object " + m.Index + " has unused texture matrices."); } //This error doesn't seem to always be true for factory models... //if (m.HasTexMtx && m.HasNonFloatVertices) //{ // ((MDL0Node)Parent)._errors.Add("Object " + m.Index + " has texture matrices and non-float vertices, meaning it will explode in-game."); // m.SignalPropertyChange(); //} } } }
public void Write(Collada form, ref byte *pGroup, ref byte *pData, bool force) { MDL0GroupNode group; ResourceGroup *pGrp; ResourceEntry *pEntry; int len; //Write data in the order it appears foreach (MDLResourceType resType in OrderBank) { if (((group = Groups[(int)resType]) == null) || SpecialRebuildData((int)resType)) { continue; } if (resType == MDLResourceType.Bones) { if (form != null) { form.Say("Writing Bones"); } MDL0Bone *pBone = (MDL0Bone *)pData; foreach (MDL0BoneNode e in BoneCache) { len = e._calcSize; e.Rebuild(pData, len, true); pData += len; } //Loop through after all bones are written //and set header offsets to related bones foreach (MDL0BoneNode e in BoneCache) { e.CalculateOffsets(); } } else if (resType == MDLResourceType.Shaders) { MDL0GroupNode mats = Groups[(int)MDLResourceType.Materials]; MDL0Material *mHeader; if (form != null) { form.Say("Writing Shaders"); } //Write data without headers foreach (ResourceNode e in group.Children) { if (((MDL0ShaderNode)e)._materials.Count > 0) { len = e._calcSize; e.Rebuild(pData, len, force); pData += len; } } //Write one header for each material, using same order. if (mats != null) { foreach (MDL0MaterialNode mat in mats.Children) { mHeader = mat.Header; if (mat._shader != null) { len = (int)mat._shader.Header; mHeader->_shaderOffset = len - (int)mHeader; } else { mHeader->_shaderOffset = 0; } } } } else if (resType == MDLResourceType.Objects || resType == MDLResourceType.Materials) { foreach (ResourceNode r in group.Children) { if (form != null) { form.Say("Writing " + resType.ToString() + " - " + r.Name); } len = r._calcSize; r.Rebuild(pData, len, true); //Forced to fix object node ids and align materials pData += len; } } else { bool rebuild = true; if (Model._isImport) { if (group._name == "Vertices" || group._name == "Normals" || group._name == "UVs" || group._name == "Colors") { rebuild = false; //The data has already been written! } } if (rebuild) { foreach (ResourceNode e in group.Children) { //Console.WriteLine("Rebuilding the " + group.Name); if (form != null) { form.Say("Writing the " + resType.ToString() + " - " + e.Name); } len = e._calcSize; e.Rebuild(pData, len, true); //Forced just in case we need to convert to float. pData += len; } } } } //Write relocation offsets in the order of the header fixed(ResourceGroup **pOut = &Defs) foreach (MDLResourceType resType in IndexBank[Version]) { if (((group = Groups[(int)resType]) == null) || SpecialRebuildData((int)resType)) { continue; } pOut[(int)resType] = pGrp = (ResourceGroup *)pGroup; pEntry = &pGrp->_first + 1; if (resType == MDLResourceType.Bones) { *pGrp = new ResourceGroup(BoneCache.Length); foreach (ResourceNode e in BoneCache) { (pEntry++)->_dataOffset = (int)((byte *)(e.WorkingUncompressed.Address) - pGroup); } } else if (resType == MDLResourceType.Shaders) { MDL0GroupNode mats = Groups[(int)MDLResourceType.Materials]; if (mats != null) { //Create a material group with the amount of entries *pGrp = new ResourceGroup(mats.Children.Count); foreach (MDL0MaterialNode mat in mats.Children) { (pEntry++)->_dataOffset = (int)mat._shader.Header - (int)pGrp; } } } else { *pGrp = new ResourceGroup(group.Children.Count); foreach (ResourceNode e in group.Children) { (pEntry++)->_dataOffset = (int)((byte *)(e.WorkingUncompressed.Address) - pGroup); } } pGroup += pGrp->_totalSize; } }
public void RecalcOffsets(MDL0Bone *header, VoidPtr address, int length) { MDL0BoneNode bone; int index = 0, offset; //Sub-entries if (_entries.Count > 0) { header->_part2Offset = 0xD0; *(bint *)((byte *)address + 0xD0) = 0x1C + (_entries.Count * 0x2C); ResourceGroup *pGroup = (ResourceGroup *)((byte *)address + 0xD4); ResourceEntry *pEntry = &pGroup->_first + 1; byte * pData = (byte *)pGroup + pGroup->_totalSize; *pGroup = new ResourceGroup(_entries.Count); foreach (string s in _entries) { (pEntry++)->_dataOffset = (int)pData - (int)pGroup; Part2DataEntry *p = (Part2DataEntry *)pData; *p = new Part2DataEntry(1); pData += 0x1C; } } else { header->_part2Offset = 0; } //Set first child if (_children.Count > 0) { header->_firstChildOffset = length; } else { header->_firstChildOffset = 0; } if (_parent != null) { index = _parent._children.IndexOf(this); //Parent if (_parent is MDL0BoneNode) { header->_parentOffset = (int)_parent.WorkingUncompressed.Address - (int)address; } else { header->_parentOffset = 0; } //Prev if (index == 0) { header->_prevOffset = 0; } else { //Link to prev bone = _parent._children[index - 1] as MDL0BoneNode; offset = (int)bone.Header - (int)address; header->_prevOffset = offset; bone.Header->_nextOffset = -offset; } //Next if (index == (_parent._children.Count - 1)) { header->_nextOffset = 0; } } }
public void RecalcOffsets(MDL0Bone *header, VoidPtr address, int length) { MDL0BoneNode bone; int index = 0, offset; //Sub-entries if (_userEntries.Count > 0) { header->_userDataOffset = 0xD0; _userEntries.Write(address + 0xD0); } else { header->_userDataOffset = 0; } //Set first child if (_children.Count > 0) { header->_firstChildOffset = length; } else { header->_firstChildOffset = 0; } if (_parent != null) { index = Parent._children.IndexOf(this); //Parent if (Parent is MDL0BoneNode) { header->_parentOffset = (int)Parent.WorkingUncompressed.Address - (int)address; } else { header->_parentOffset = 0; } //Prev if (index == 0) { header->_prevOffset = 0; } else { //Link to prev bone = Parent._children[index - 1] as MDL0BoneNode; offset = (int)bone.Header - (int)address; header->_prevOffset = offset; bone.Header->_nextOffset = -offset; } //Next if (index == (Parent._children.Count - 1)) { header->_nextOffset = 0; } } }
//Initialize should only be called from parent group during parse. //Bones need not be imported/exported anyways public override bool OnInitialize() { MDL0Bone *header = Header; SetSizeInternal(header->_headerLen); //Assign true parent using parent header offset int offset = header->_parentOffset; //Offsets are always < 0, because parent entries are listed before children if (offset < 0) { //Get address of parent header MDL0Bone *pHeader = (MDL0Bone *)((byte *)header + offset); //Search bone list for matching header foreach (MDL0BoneNode bone in Parent._children) { if (pHeader == bone.Header) { _parent = bone; break; } } } //Conditional name assignment if ((_name == null) && (header->_stringOffset != 0)) { _name = header->ResourceString; } //Assign fields _flags1 = (BoneFlags)(uint)header->_flags; _flags2 = (BillboardFlags)(uint)header->_bbFlags; _bbNodeId = header->_bbNodeId; _nodeIndex = header->_nodeId; _boneIndex = header->_index; _headerLen = header->_headerLen; _mdl0Offset = header->_mdl0Offset; _stringOffset = header->_stringOffset; _parentOffset = header->_parentOffset; _firstChildOffset = header->_firstChildOffset; _nextOffset = header->_nextOffset; _prevOffset = header->_prevOffset; _userDataOffset = header->_userDataOffset; if (_flags2 != 0 && _flags1.HasFlag(BoneFlags.HasGeometry)) { Model._billboardBones.Add(this); //Update mesh in T-Pose } _bindState = _frameState = new FrameState(header->_scale, (Vector3)header->_rotation, header->_translation); _bindMatrix = _frameMatrix = header->_transform; _inverseBindMatrix = _inverseFrameMatrix = header->_transformInv; _bMin = header->_boxMin; _bMax = header->_boxMax; (_userEntries = new UserDataCollection()).Read(header->UserDataAddress); //We don't want to process children because not all have been parsed yet. //Child assigning will be handled by the parent group. return(false); }