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();
                    //}
                }
            }
        }
        private static PrimitiveManager DecodePrimitivesWeighted(
            Matrix bindMatrix,
            GeometryEntry geo,
            SkinEntry skin,
            SceneEntry scene,
            InfluenceManager infManager,
            Type boneType)
        {
            PrimitiveManager manager = DecodePrimitives(geo);

            IBoneNode[] boneList;
            IBoneNode   bone = null;
            int         boneCount;

            string[] jointStringArray = null;
            string   jointString      = null;

            byte *         pCmd = stackalloc byte[4];
            int            cmdCount = skin._weightInputs.Count;
            float          weight = 0;
            float *        pWeights = null;
            Vector3 *      pVert = null, pNorms = null;
            ushort *       pVInd    = (ushort *)manager._indices.Address;
            List <Vertex3> vertList = new List <Vertex3>(skin._weightCount);
            Matrix *       pMatrix  = null;

            UnsafeBuffer remap  = new UnsafeBuffer(skin._weightCount * 2);
            ushort *     pRemap = (ushort *)remap.Address;

            if (manager._faceData[1] != null)
            {
                pNorms = (Vector3 *)manager._faceData[1].Address;
            }

            manager._vertices = vertList;

            //Find vertex source
            foreach (SourceEntry s in geo._sources)
            {
                if (s._id == geo._verticesInput._source)
                {
                    pVert = (Vector3 *)((UnsafeBuffer)s._arrayData).Address;
                    break;
                }
            }

            //Find joint source
            foreach (InputEntry inp in skin._jointInputs)
            {
                if (inp._semantic == SemanticType.JOINT)
                {
                    foreach (SourceEntry src in skin._sources)
                    {
                        if (src._id == inp._source)
                        {
                            jointStringArray = src._arrayData as string[];
                            jointString      = src._arrayDataString;
                            break;
                        }
                    }
                }
                else if (inp._semantic == SemanticType.INV_BIND_MATRIX)
                {
                    foreach (SourceEntry src in skin._sources)
                    {
                        if (src._id == inp._source)
                        {
                            pMatrix = (Matrix *)((UnsafeBuffer)src._arrayData).Address;
                            break;
                        }
                    }
                }
            }

            Error = "There was a problem creating the list of bones for geometry entry " + geo._name;

            //Populate bone list
            boneCount = jointStringArray.Length;
            boneList  = new IBoneNode[boneCount];
            for (int i = 0; i < boneCount; i++)
            {
                NodeEntry entry = scene.FindNode(jointStringArray[i]);
                if (entry?._node != null)
                {
                    boneList[i] = entry._node as IBoneNode;
                }
                else
                {
                    //Search in reverse!
                    foreach (NodeEntry node in scene._nodes)
                    {
                        if ((entry = RecursiveTestNode(jointString, node)) != null)
                        {
                            if (entry._node != null)
                            {
                                boneList[i] = entry._node as IBoneNode;
                            }

                            break;
                        }
                    }

                    //Couldn't find the bone
                    if (boneList[i] == null)
                    {
                        boneList[i] = Activator.CreateInstance(boneType) as IBoneNode;
                    }
                }
            }

            //Build command list
            foreach (InputEntry inp in skin._weightInputs)
            {
                switch (inp._semantic)
                {
                case SemanticType.JOINT:
                    pCmd[inp._offset] = 1;
                    break;

                case SemanticType.WEIGHT:
                    pCmd[inp._offset] = 2;

                    //Get weight source
                    foreach (SourceEntry src in skin._sources)
                    {
                        if (src._id == inp._source)
                        {
                            pWeights = (float *)((UnsafeBuffer)src._arrayData).Address;
                            break;
                        }
                    }

                    break;

                default:
                    pCmd[inp._offset] = 0;
                    break;
                }
            }

            Error = "There was a problem creating vertex influences for geometry entry " + geo._name;

            //Build vertex list and remap table
            for (int i = 0; i < skin._weightCount; i++)
            {
                //Create influence
                int       iCount = skin._weights[i].Length / cmdCount;
                Influence inf    = new Influence();
                fixed(int *p = skin._weights[i])
                {
                    int *iPtr = p;

                    for (int x = 0; x < iCount; x++)
                    {
                        for (int z = 0; z < cmdCount; z++, iPtr++)
                        {
                            if (pCmd[z] == 1)
                            {
                                bone = boneList[*iPtr];
                            }
                            else if (pCmd[z] == 2)
                            {
                                weight = pWeights[*iPtr];
                            }
                        }

                        inf.AddWeight(new BoneWeight(bone, weight));
                    }
                }

                inf.CalcMatrix();

                Error = "There was a problem creating a vertex from the geometry entry " + geo._name +
                        ".\nMake sure that all the vertices are weighted properly.";

                Vector3 worldPos = bindMatrix * skin._bindMatrix * pVert[i];
                Vertex3 v;
                if (inf.Weights.Count > 1)
                {
                    //Match with manager
                    inf = infManager.FindOrCreate(inf);
                    v   = new Vertex3(worldPos, inf); //World position
                }
                else
                {
                    bone = inf.Weights[0].Bone;
                    v    = new Vertex3(bone.InverseBindMatrix * worldPos, bone); //Local position
                }

                ushort index = 0;
                while (index < vertList.Count)
                {
                    if (v.Equals(vertList[index]))
                    {
                        break;
                    }

                    index++;
                }

                if (index == vertList.Count)
                {
                    vertList.Add(v);
                }

                pRemap[i] = index;
            }

            Error = "There was a problem fixing normal rotations for geometry entry " + geo._name;

            //Remap vertex indices and fix normals
            for (int i = 0; i < manager._pointCount; i++, pVInd++)
            {
                *pVInd = pRemap[*pVInd];

                if (pNorms != null)
                {
                    Vertex3 v = null;
                    if (*pVInd < vertList.Count)
                    {
                        v = vertList[*pVInd];
                    }

                    if (v?.MatrixNode != null)
                    {
                        if (v.MatrixNode.Weights.Count > 1)
                        {
                            pNorms[i] =
                                (bindMatrix *
                                 skin._bindMatrix).GetRotationMatrix() *
                                pNorms[i];
                        }
                        else
                        {
                            pNorms[i] =
                                (v.MatrixNode.Weights[0].Bone.InverseBindMatrix *
                                 bindMatrix *
                                 skin._bindMatrix).GetRotationMatrix() *
                                pNorms[i];
                        }
                    }
                }
            }

            remap.Dispose();
            return(manager);
        }