//This assumes influence list has already been cleaned
        public static void AssignNodeIndices(ModelLinker linker)
        {
            MDL0Node model = linker.Model;
            int index = 0;

            int count = model._influences.Count + linker.BoneCache.Length;

            linker._nodeCount = count;
            linker.Model._numNodes = count;
            linker.NodeCache = new IMatrixNode[count];

            //Add referenced primaries
            foreach (MDL0BoneNode bone in linker.BoneCache)
            {
                if (bone.ReferenceCount > 0 || bone._infPolys.Count > 0)
                    linker.NodeCache[bone._nodeIndex = index++] = bone;
                else
                    bone._nodeIndex = -1;
                bone._weightCount = 0;
            }

            //Add weight groups
            foreach (Influence i in model._influences._influences)
            {
                linker.NodeCache[i._index = index++] = i;
                foreach (BoneWeight b in i._weights)
                    if (b.Bone != null)
                        b.Bone._weightCount++;
            }

            //Add remaining bones
            foreach (MDL0BoneNode bone in linker.BoneCache)
                if (bone._nodeIndex == -1)
                    linker.NodeCache[bone._nodeIndex = index++] = bone;
        }
Beispiel #2
0
 public void ShouldBindModelToTableModel()
 {
     ModelTest model = new ModelTest() { id = 1, name = "test"};
     TableModel tModel = new ModelLinker<ModelTest>(model).LinkModel();
     Assert.AreEqual("TAB_MODEL", tModel.GetTableName());
     Assert.AreEqual(1, tModel.GetColumnValue("MODEL_ID"));
     Assert.AreEqual("test", tModel.GetColumnValue("name"));
 }
Beispiel #3
0
        protected override int OnCalculateSize(bool force)
        {
            //Clean and sort influence list
            _influences.Clean();
            _influences.Sort();

            //Clean and sort texture list
            _textures.Clean();
            _textures.Sort();

            _linker = ModelLinker.Prepare(this);
            return(ModelEncoder.CalcSize(_linker));
        }
Beispiel #4
0
        protected override void OnPopulate()
        {
            InitGroups();
            _linker = new ModelLinker(Header);
            _assets = new AssetStorage(_linker);
            try
            {
                //Set def flags
                _hasMix = _hasOpa = _hasTree = _hasXlu = false;
                if (_linker.Defs != null)
                {
                    foreach (ResourcePair p in *_linker.Defs)
                    {
                        if (p.Name == "NodeTree")
                        {
                            _hasTree = true;
                        }
                        else if (p.Name == "NodeMix")
                        {
                            _hasMix = true;
                        }
                        else if (p.Name == "DrawOpa")
                        {
                            _hasOpa = true;
                        }
                        else if (p.Name == "DrawXlu")
                        {
                            _hasXlu = true;
                        }
                    }
                }

                _boneGroup.Parse(this);
                _matGroup.Parse(this);
                _shadGroup.Parse(this);
                _polyGroup.Parse(this);
                //Texture group doesn't need parsing
                //It's only used as a name reference/link and will be re-created on build.

                //Eliminate influences with no references?
            }
            finally
            {
                //Clean up!
                _assets.Dispose();
                _assets = null;
                _linker = null;
                CleanGroups();
            }
        }
Beispiel #5
0
        protected override bool OnInitialize()
        {
            MDL0Polygon *header = Header;
            int          nodeId = header->_nodeId;

            ModelLinker linker = Model._linker;

            //Attach single bind. Doesn't have to be bone node.
            if (nodeId >= 0)
            {
                Influence = linker.NodeCache[nodeId];
            }

            if (header->_defFlags != 0x80)
            {
                Console.WriteLine("OMG!");
            }
            if (header->_defSize != 0xE0)
            {
                Console.WriteLine("OMG!");
            }
            if (header->_dataLen1 != header->_dataLen2)
            {
                Console.WriteLine("DataLen deviation!");
            }
            if (header->_unk3 != 0)
            {
                Console.WriteLine("OMG!");
            }

            if (header != null)
            {
                //Conditional name assignment
                if ((_name == null) && (header->_stringOffset != 0))
                {
                    _name = header->ResourceString;
                }

                //Create primitive manager
                if (_parent != null)
                {
                    _manager = new PrimitiveManager(header, Model._assets, linker.NodeCache);
                }
            }

            return(false);
        }
        public AssetStorage(ModelLinker linker)
        {
            int index;

            //Vertices
            if (linker.Vertices != null)
            {
                Assets[0] = new UnsafeBuffer[linker.Vertices->_numEntries];
                index = 0;
                foreach (ResourcePair p in *linker.Vertices)
                    Assets[0][index++] = VertexCodec.Decode((MDL0VertexData*)p.Data);
            }

            //Normals
            if (linker.Normals != null)
            {
                Assets[1] = new UnsafeBuffer[linker.Normals->_numEntries];
                index = 0;
                foreach (ResourcePair p in *linker.Normals)
                    Assets[1][index++] = VertexCodec.Decode((MDL0NormalData*)p.Data);
            }

            //Colors
            if (linker.Colors != null)
            {
                Assets[2] = new UnsafeBuffer[linker.Colors->_numEntries];
                index = 0;
                foreach (ResourcePair p in *linker.Colors)
                    Assets[2][index++] = ColorCodec.Decode((MDL0ColorData*)p.Data);
            }

            //UVs
            if (linker.UVs != null)
            {
                Assets[3] = new UnsafeBuffer[linker.UVs->_numEntries];
                index = 0;
                foreach (ResourcePair p in *linker.UVs)
                    Assets[3][index++] = VertexCodec.Decode((MDL0UVData*)p.Data);
            }
        }
        //Write assets will only be used for model imports.
        private static void WriteAssets(Collada form, ModelLinker linker, ref byte* pData)
        {
            int index;
            MDL0Node model = linker.Model;

            if (linker._vertices != null && linker._vertices.Count != 0)
            {
                model.LinkGroup(new MDL0GroupNode(MDLResourceType.Vertices));
                model._vertGroup._parent = model;

                index = 0;
                foreach (VertexCodec c in linker._vertices)
                {
                    MDL0VertexNode node = new MDL0VertexNode();

                    node._name = model.Name + "_" + model._objList[index]._name;
                    if (((MDL0ObjectNode)model._objList[index])._opaMaterial != null)
                        node._name += "_" + ((MDL0ObjectNode)model._objList[index])._opaMaterial._name;

                    if (form != null)
                        form.Say("Writing Vertices - " + node.Name);

                    MDL0VertexData* header = (MDL0VertexData*)pData;
                    header->_dataLen = c._dataLen.Align(0x20) + 0x40;
                    header->_dataOffset = 0x40;
                    header->_index = index++;
                    header->_isXYZ = c._hasZ ? 1 : 0;
                    header->_type = (int)c._type;
                    header->_divisor = (byte)c._scale;
                    header->_entryStride = (byte)c._dstStride;
                    header->_numVertices = (short)c._dstCount;
                    header->_eMin = c._min;
                    header->_eMax = c._max;
                    header->_pad1 = header->_pad2 = 0;

                    c.Write(pData + 0x40);

                    node._replSrc = node._replUncompSrc = new DataSource(header, header->_dataLen);
                    model._vertGroup.AddChild(node, false);

                    pData += header->_dataLen;
                }
            }

            if (linker._normals != null && linker._normals.Count != 0)
            {
                model.LinkGroup(new MDL0GroupNode(MDLResourceType.Normals));
                model._normGroup._parent = model;

                index = 0;
                foreach (VertexCodec c in linker._normals)
                {
                    MDL0NormalNode node = new MDL0NormalNode();

                    node._name = model.Name + "_" + model._objList[index]._name;
                    if (((MDL0ObjectNode)model._objList[index])._opaMaterial != null)
                        node._name += "_" + ((MDL0ObjectNode)model._objList[index])._opaMaterial._name;

                    if (form != null)
                        form.Say("Writing Normals - " + node.Name);

                    MDL0NormalData* header = (MDL0NormalData*)pData;
                    header->_dataLen = c._dataLen.Align(0x20) + 0x20;
                    header->_dataOffset = 0x20;
                    header->_index = index++;
                    header->_isNBT = 0;
                    header->_type = (int)c._type;
                    header->_divisor = (byte)c._scale;
                    header->_entryStride = (byte)c._dstStride;
                    header->_numVertices = (ushort)c._dstCount;

                    c.Write(pData + 0x20);

                    node._replSrc = node._replUncompSrc = new DataSource(header, header->_dataLen);
                    model._normGroup.AddChild(node, false);

                    pData += header->_dataLen;
                }
            }

            if (linker._colors != null && linker._colors.Count != 0)
            {
                model.LinkGroup(new MDL0GroupNode(MDLResourceType.Colors));
                model._colorGroup._parent = model;

                index = 0;
                foreach (ColorCodec c in linker._colors)
                {
                    MDL0ColorNode node = new MDL0ColorNode();

                    node._name = model.Name + "_" + model._objList[index]._name;
                    if (((MDL0ObjectNode)model._objList[index])._opaMaterial != null)
                        node._name += "_" + ((MDL0ObjectNode)model._objList[index])._opaMaterial._name;

                    if (form != null)
                        form.Say("Writing Colors - " + node.Name);

                    MDL0ColorData* header = (MDL0ColorData*)pData;
                    header->_dataLen = c._dataLen.Align(0x20) + 0x20;
                    header->_dataOffset = 0x20;
                    header->_index = index++;
                    header->_isRGBA = c._hasAlpha ? 1 : 0;
                    header->_format = (int)c._outType;
                    header->_entryStride = (byte)c._dstStride;
                    header->_pad = 0;
                    header->_numEntries = (ushort)c._dstCount;

                    c.Write(pData + 0x20);

                    node._replSrc = node._replUncompSrc = new DataSource(header, header->_dataLen);
                    model._colorGroup.AddChild(node, false);

                    pData += header->_dataLen;
                }
            }

            if (linker._uvs != null && linker._uvs.Count != 0)
            {
                model.LinkGroup(new MDL0GroupNode(MDLResourceType.UVs));
                model._uvGroup._parent = model;

                index = 0;
                foreach (VertexCodec c in linker._uvs)
                {
                    MDL0UVNode node = new MDL0UVNode() { _name = "#" + index };

                    if (form != null)
                        form.Say("Writing UVs - " + node.Name);

                    MDL0UVData* header = (MDL0UVData*)pData;
                    header->_dataLen = c._dataLen.Align(0x20) + 0x40;
                    header->_dataOffset = 0x40;
                    header->_index = index++;
                    header->_format = (int)c._type;
                    header->_divisor = (byte)c._scale;
                    header->_isST = 1;
                    header->_entryStride = (byte)c._dstStride;
                    header->_numEntries = (ushort)c._dstCount;
                    header->_min = (Vector2)c._min;
                    header->_max = (Vector2)c._max;
                    header->_pad1 = header->_pad2 = header->_pad3 = header->_pad4 = 0;

                    c.Write(pData + 0x40);

                    node._replSrc = node._replUncompSrc = new DataSource(header, header->_dataLen);
                    model._uvGroup.AddChild(node, false);

                    pData += header->_dataLen;
                }
            }

            //Clean groups
            if (model._vertList != null && model._vertList.Count > 0)
            {
                model._children.Add(model._vertGroup);
                linker.Groups[(int)(MDLResourceType)Enum.Parse(typeof(MDLResourceType), model._vertGroup.Name)] = model._vertGroup;
            }
            else
                model.UnlinkGroup(model._vertGroup);

            if (model._normList != null && model._normList.Count > 0)
            {
                model._children.Add(model._normGroup);
                linker.Groups[(int)(MDLResourceType)Enum.Parse(typeof(MDLResourceType), model._normGroup.Name)] = model._normGroup;
            }
            else
                model.UnlinkGroup(model._normGroup);

            if (model._uvList != null && model._uvList.Count > 0)
            {
                model._children.Add(model._uvGroup);
                linker.Groups[(int)(MDLResourceType)Enum.Parse(typeof(MDLResourceType), model._uvGroup.Name)] = model._uvGroup;
            }
            else
                model.UnlinkGroup(model._uvGroup);

            if (model._colorList != null && model._colorList.Count > 0)
            {
                model._children.Add(model._colorGroup);
                linker.Groups[(int)(MDLResourceType)Enum.Parse(typeof(MDLResourceType), model._colorGroup.Name)] = model._colorGroup;
            }
            else
                model.UnlinkGroup(model._colorGroup);

            //Link sets
            if (model._objList != null)
            foreach (MDL0ObjectNode poly in model._objList)
            {
                if (poly._elementIndices[0] != -1 && model._vertList != null && model._vertList.Count > poly._elementIndices[0])
                    poly._vertexNode = (MDL0VertexNode)model._vertGroup._children[poly._elementIndices[0]];
                if (poly._elementIndices[1] != -1 && model._normList != null && model._normList.Count > poly._elementIndices[1])
                    poly._normalNode = (MDL0NormalNode)model._normGroup._children[poly._elementIndices[1]];
                for (int i = 2; i < 4; i++)
                    if (poly._elementIndices[i] != -1 && model._colorList != null && model._colorList.Count > poly._elementIndices[i])
                        poly._colorSet[i - 2] = (MDL0ColorNode)model._colorGroup._children[poly._elementIndices[i]];
                for (int i = 4; i < 12; i++)
                    if (poly._elementIndices[i] != -1 && model._uvList != null && model._uvList.Count > poly._elementIndices[i])
                        poly._uvSet[i - 4] = (MDL0UVNode)model._uvGroup._children[poly._elementIndices[i]];
            }
        }
 public static int CalcSize(ModelLinker linker)
 {
     return CalcSize(null, linker);
 }
        public override int OnCalculateSize(bool force)
        {
            //Clean and sort influence list
            _influences.Clean();
            //_influences.Sort();

            //Clean texture list
            CleanTextures();

            _linker = ModelLinker.Prepare(this);
            return ModelEncoder.CalcSize(_linker);
        }
        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 void WriteNodeTable(ModelLinker linker)
        {
            bint* ptr = (bint*)((byte*)linker.Header + linker._headerLen);
            int len = linker._nodeCount;
            int i = 0;

            //Set length
            *ptr++ = len;

            //Write indices
            while (i < len)
            {
                IMatrixNode n = linker.NodeCache[i++];
                if (n.IsPrimaryNode)
                    *ptr++ = ((MDL0BoneNode)n)._entryIndex;
                else
                    *ptr++ = -1;
            }
        }
Beispiel #12
0
        internal void Parse(MDL0Node model)
        {
            Influence   inf;
            ModelLinker linker = model._linker;

            switch (_type)
            {
            case MDLResourceType.Bones:
                //Break if there are no bones defined
                if (linker.Bones == null)
                {
                    break;
                }

                //Parse bones from raw data (flat list).
                //Bones re-assign parents in their Initialize block, so parents are true.
                ExtractGroup(linker.Bones, typeof(MDL0BoneNode));

                //Cache flat list
                linker.BoneCache = _children.ToArray();

                //Reset children so we can rebuild
                _children.Clear();

                //Populate node cache
                MDL0BoneNode bone = null;
                int          index;
                int          count = linker.BoneCache.Length;
                for (int i = 0; i < count; i++)
                {
                    //bone = linker.BoneCache[i] as MDL0BoneNode;
                    linker.NodeCache[(bone = linker.BoneCache[i] as MDL0BoneNode)._nodeIndex] = bone;
                }

                //Now that bones and primary influences have been cached, we can create weighted influences.

                foreach (ResourcePair p in *linker.Defs)
                {
                    if (p.Name == "NodeTree")
                    {
                        //Use node tree to rebuild bone heirarchy
                        byte *pData = (byte *)p.Data;

Top:
                        if (*pData == 2)
                        {
                            bone  = linker.BoneCache[*(bushort *)(pData + 1)] as MDL0BoneNode;
                            index = *(bushort *)(pData + 3);

                            if (bone.Header->_parentOffset == 0)
                            {
                                _children.Add(bone);
                            }
                            else
                            {
                                (bone._parent = linker.NodeCache[index] as ResourceNode)._children.Add(bone);
                            }

                            pData += 5;
                            goto Top;
                        }
                    }
                    else if (p.Name == "NodeMix")
                    {
                        //Use node mix to create weight groups
                        byte *pData = (byte *)p.Data;

Top:
                        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(count);
                            //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.
                            for (int i = 0; i < count; i++, nEntry++)
                            {
                                inf._weights[i] = new BoneWeight(linker.NodeCache[nEntry->_id] as MDL0BoneNode, nEntry->_value);
                            }

                            //Add influence to model object, while adding it to the cache.
                            linker.NodeCache[index] = model._influences.AddOrCreate(inf);

                            //Move data pointer to next entry
                            pData = (byte *)nEntry;
                            goto Top;

                        //Type 5 is for primary influences
                        case 5:
                            pData += 5;
                            goto Top;
                        }
                    }
                }

                break;

            case MDLResourceType.Materials:
                if (linker.Materials != null)
                {
                    ExtractGroup(linker.Materials, typeof(MDL0MaterialNode));
                }
                break;

            case MDLResourceType.Shaders:
                if (linker.Shaders != null)
                {
                    ExtractGroup(linker.Shaders, typeof(MDL0ShaderNode));
                }
                break;

            case MDLResourceType.Polygons:
                //Break if no polygons defined
                if (linker.Polygons == null)
                {
                    break;
                }

                //Extract
                ExtractGroup(linker.Polygons, typeof(MDL0PolygonNode));

                //Attach materials to polygons.
                //This assumes that materials have already been parsed.

                List <ResourceNode> matList = ((MDL0Node)_parent)._matList;
                MDL0PolygonNode     poly;
                MDL0MaterialNode    mat;

                //Find DrawOpa or DrawXlu entry in Definition list
                foreach (ResourcePair p in *linker.Defs)
                {
                    if ((p.Name == "DrawOpa") || (p.Name == "DrawXlu"))
                    {
                        byte *pData = (byte *)p.Data;
                        while (*pData++ == 4)
                        {
                            //Get polygon from index
                            poly = _children[*(bushort *)(pData + 2)] as MDL0PolygonNode;
                            //Get material from index
                            mat = matList[*(bushort *)pData] as MDL0MaterialNode;
                            //Assign material to polygon
                            poly._material = mat;
                            //Add polygon to material reference list
                            mat._polygons.Add(poly);
                            //Increment pointer
                            pData += 7;
                        }
                    }
                }
                break;
            }
        }
        internal void Parse(MDL0Node model)
        {
            Influence   inf;
            ModelLinker linker = model._linker;

            switch (_type)
            {
            case MDLResourceType.Definitions:
                if (linker.Defs != null)
                {
                    ExtractGroup(linker.Defs, typeof(MDL0DefNode));
                }
                break;

            case MDLResourceType.Bones:
                //Break if there are no bones defined
                if (linker.Bones == null)
                {
                    break;
                }

                //Parse bones from raw data (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.
                ExtractGroup(linker.Bones, typeof(MDL0BoneNode));

                //Cache flat list
                linker.BoneCache = _children.ToArray();

                //Make sure the node cache is the correct size
                int highest = 0;
                foreach (MDL0BoneNode b in linker.BoneCache)
                {
                    if (b._nodeIndex >= linker.NodeCache.Length && b._nodeIndex > highest)
                    {
                        highest = b._nodeIndex;
                    }
                }
                if (highest >= linker.NodeCache.Length)
                {
                    linker.NodeCache = new IMatrixNode[highest + 1];
                }

                //Reset children so we can rebuild
                _children.Clear();

                //Assign children using each bones' parent offset in case NodeTree is corrupted
                foreach (MDL0BoneNode b in linker.BoneCache)
                {
                    b._parent._children.Add(b);
                }

                //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;

Top:
                        if (*pData == 2)
                        {
                            bone  = linker.BoneCache[*(bushort *)(pData + 1)] as MDL0BoneNode;
                            index = *(bushort *)(pData + 3);

                            if (bone.Header->_parentOffset == 0)
                            {
                                if (!_children.Contains(bone))
                                {
                                    nodeTreeError = true;
                                    continue;
                                }
                            }
                            else
                            {
                                ResourceNode n = linker.NodeCache[index] as ResourceNode;
                                if (n == null || bone._parent != n || !n._children.Contains(bone))
                                {
                                    nodeTreeError = true;
                                    continue;
                                }
                            }
                            pData += 5;
                            goto Top;
                        }
                    }
                    else
                    if (p.Name == "NodeMix")
                    {
                        //Use node mix to create weight groups
                        byte *pData = (byte *)p.Data;

Top:
                        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._weights.Add(new BoneWeight(b, nEntry->_value));
                                }
                                else
                                {
                                    nullIndices.Add(i);
                                    nullCount++;
                                }
                            }

                            bool d = false;
                            if (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)
                                {
                                    d = true;
                                }
                                else
                                {
                                    inf._weights = newWeights;
                                }
                            }

                            //Add influence to model object, while adding it to the cache.
                            if (!d)
                            {
                                ((Influence)(linker.NodeCache[index] = model._influences.FindOrCreate(inf, true)))._index = index;
                            }

                            //Move data pointer to next entry
                            pData = (byte *)nEntry;
                            goto Top;

                        //Type 5 is for primary influences
                        case 5:
                            pData += 5;
                            goto Top;
                        }
                    }
                }

                if (nullCount > 0)
                {
                    model._errors.Add("There were " + nullCount + " null weights in NodeMix.");
                    SignalPropertyChange();
                }

                if (nodeTreeError)
                {
                    model._errors.Add("The NodeTree definition did not match the bone tree.");
                    SignalPropertyChange();
                }

                break;

            case MDLResourceType.Materials:
                if (linker.Materials != null)
                {
                    ExtractGroup(linker.Materials, typeof(MDL0MaterialNode));
                }
                break;

            case MDLResourceType.Shaders:
                if (linker.Shaders != null)
                {
                    ExtractGroup(linker.Shaders, typeof(MDL0ShaderNode));
                }
                break;

            case MDLResourceType.Vertices:
                if (linker.Vertices != null)
                {
                    ExtractGroup(linker.Vertices, typeof(MDL0VertexNode));
                }
                break;

            case MDLResourceType.Normals:
                if (linker.Normals != null)
                {
                    ExtractGroup(linker.Normals, typeof(MDL0NormalNode));
                }
                break;

            case MDLResourceType.UVs:
                if (linker.UVs != null)
                {
                    ExtractGroup(linker.UVs, typeof(MDL0UVNode));
                }
                break;

            case MDLResourceType.FurLayerCoords:
                if (linker.FurLayerCoords != null)
                {
                    ExtractGroup(linker.FurLayerCoords, typeof(MDL0FurPosNode));
                }
                break;

            case MDLResourceType.FurVectors:
                if (linker.FurVectors != null)
                {
                    ExtractGroup(linker.FurVectors, typeof(MDL0FurVecNode));
                }
                break;

            case MDLResourceType.Objects:
                //Break if no polygons defined
                if (linker.Polygons == null)
                {
                    break;
                }

                //Extract
                ExtractGroup(linker.Polygons, typeof(MDL0ObjectNode));

                //Attach materials to polygons.
                //This assumes that materials have already been parsed.

                List <ResourceNode> matList = ((MDL0Node)_parent)._matList;
                MDL0ObjectNode      poly;
                MDL0MaterialNode    mat;

                //Find DrawOpa or DrawXlu entry in Definition list
                foreach (ResourcePair p in *linker.Defs)
                {
                    if ((p.Name == "DrawOpa") || (p.Name == "DrawXlu"))
                    {
                        bool   opa    = p.Name == "DrawOpa";
                        ushort dIndex = 0;
                        byte * pData  = (byte *)p.Data;
                        while (*pData++ == 4)
                        {
                            //Get polygon from index
                            dIndex = *(bushort *)(pData + 2);
                            if (dIndex >= _children.Count || dIndex < 0)
                            {
                                model._errors.Add("Object index was greater than the actual object count.");
                                SignalPropertyChange();
                                dIndex = 0;
                            }
                            poly            = _children[dIndex] as MDL0ObjectNode;
                            poly._drawIndex = pData[6];
                            //Get material from index
                            mat = matList[*(bushort *)pData] as MDL0MaterialNode;
                            //Get bone from index and assign
                            int boneIndex = *(bushort *)(pData + 4);
                            if (linker.BoneCache != null && boneIndex >= 0 && boneIndex < linker.BoneCache.Length)
                            {
                                poly.BoneNode = linker.BoneCache[boneIndex] as MDL0BoneNode;
                            }
                            //Assign material to polygon
                            if (opa)
                            {
                                poly.OpaMaterialNode = mat;
                            }
                            else
                            {
                                poly.XluMaterialNode = mat;
                            }
                            //Increment pointer
                            pData += 7;
                        }
                    }
                }

                foreach (MDL0ObjectNode m in _children)
                {
                    int max = Maths.Max(
                        m.OpaMaterialNode != null ? m.OpaMaterialNode.Children.Count : 0,
                        m.XluMaterialNode != null ? m.XluMaterialNode.Children.Count : 0,
                        m.OpaMaterialNode != null && m.OpaMaterialNode.MetalMaterial != null ? m.OpaMaterialNode.MetalMaterial.Children.Count : 0,
                        m.XluMaterialNode != null && m.XluMaterialNode.MetalMaterial != null ? m.XluMaterialNode.MetalMaterial.Children.Count : 0);

                    bool hasUnused = false;
                    for (int i = max; i < 8; i++)
                    {
                        if (m.HasTextureMatrix[i])
                        {
                            m.HasTextureMatrix[i] = false;
                            m._rebuild            = true;
                            hasUnused             = true;
                        }
                    }
                    if (hasUnused)
                    {
                        ((MDL0Node)Parent)._errors.Add("Object " + m.Index + " has unused texture matrices.");
                        m.SignalPropertyChange();
                    }

                    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();
                    }
                }
                break;

            case MDLResourceType.Colors:
                if (linker.Colors != null)
                {
                    ExtractGroup(linker.Colors, typeof(MDL0ColorNode));
                }
                break;

            case MDLResourceType.Textures:
                if (linker.Textures != null)
                {
                    ExtractGroup(linker.Textures, typeof(MDL0TextureNode));
                }
                break;

            case MDLResourceType.Palettes:
                if (linker.Palettes != null)
                {
                    ExtractGroup(linker.Palettes, typeof(MDL0TextureNode));
                }
                break;
            }
        }
Beispiel #14
0
 protected internal override void OnRebuild(VoidPtr address, int length, bool force)
 {
     ModelEncoder.Build(_linker, (MDL0Header *)address, length, force);
     _linker = null;
     //ModelEncoder.Build(this, (MDL0Header*)address, length, force);
 }
        public static int CalcSize(Collada form, ModelLinker linker)
        {
            MDL0Node model = linker.Model;
            model._needsNrmMtxArray = model._needsTexMtxArray = false;
            model._numFacepoints = model._numFaces = 0;

            int headerLen,
                groupLen = 0,
                tableLen = 0,
                texLen = 0,
                boneLen = 0,
                dataLen = 0,
                defLen = 0,
                assetLen = 0,
                treeLen = 0,
                mixLen = 0,
                opaLen = 0,
                xluLen = 0;

            int aInd, aLen;

            //Get header length
            switch (linker.Version)
            {
                case 0x08:
                case 0x09: headerLen = 0x80; break;
                case 0x0A: headerLen = 0x88; break;
                case 0x0B: headerLen = 0x8C; break;
                default: headerLen = 0x80;
                    //Unsupported version. Change to 9 as default.
                    linker.Version = 9; break;
            }

            //Assign node indices
            AssignNodeIndices(linker);

            //Get table length
            tableLen = (linker._nodeCount + 1) << 2;

            //Get group/data length
            List<MDLResourceType> iList = ModelLinker.IndexBank[linker.Version];
            foreach (MDLResourceType resType in iList)
            {
                IEnumerable entryList = null;
                int entries = 0;

                switch (resType)
                {
                    case MDLResourceType.Definitions:

                        //NodeTree
                        treeLen = linker.BoneCache.Length * 5;

                        //NodeMix
                        foreach (Influence i in model._influences._influences)
                        {
                            mixLen += 4;
                            foreach (BoneWeight w in i._weights)
                                if (w.Bone != null && w.Weight != 0 && w.Bone._nodeIndex < linker.NodeCache.Length && w.Bone._nodeIndex >= 0 && linker.NodeCache[w.Bone._nodeIndex] is MDL0BoneNode)
                                    mixLen += 6;
                        }
                        foreach (MDL0BoneNode b in linker.BoneCache)
                            if (b._weightCount > 0)
                                mixLen += 5;

                        //DrawOpa and DrawXlu
                        //Get assigned materials and categorize
                        if (model._objList != null)
                            for (int i = 0; i < model._objList.Count; i++)
                            {
                                //Entries are ordered by material, not by polygon.
                                //Using the material's attached polygon list is untrustable if the definitions were corrupt on parse.
                                MDL0ObjectNode poly = model._objList[i] as MDL0ObjectNode;

                                model._numFaces += poly._numFaces;
                                model._numFacepoints += poly._numFacepoints;

                                if (poly.OpaMaterialNode != null)
                                    opaLen += 8;
                                if (poly.XluMaterialNode != null)
                                    xluLen += 8;
                            }

                        //Add terminate byte and set model def flags
                        if (model._hasTree = (treeLen > 0)) { treeLen++; entries++; }
                        if (model._hasMix = (mixLen > 0)) { mixLen++; entries++; }
                        if (model._hasOpa = (opaLen > 0)) { opaLen++; entries++; }
                        if (model._hasXlu = (xluLen > 0)) { xluLen++; entries++; }

                        //Align data
                        defLen += (treeLen + mixLen + opaLen + xluLen).Align(4);

                        break;

                    case MDLResourceType.Vertices:
                        if (model._vertList != null)
                        {
                            entryList = model._vertList;
                            break;
                        }
                        else
                        {
                            aInd = 0; //Set the ID
                            aLen = 1; //Offset count
                        }

                    EvalAssets:

                        List<ResourceNode> polyList = model._objList;
                        if (polyList == null)
                            break;

                        string str = "";

                        bool direct = linker._forceDirectAssets[aInd];

                        //Create asset lists
                        IList aList;
                        switch (aInd) //Switch by the set ID
                        {
                            case 0: aList = linker._vertices = new List<VertexCodec>(polyList.Count); str = "Vertices "; break;
                            case 1: aList = linker._normals = new List<VertexCodec>(polyList.Count); str = "Normals "; break;
                            case 2: aList = linker._colors = new List<ColorCodec>(polyList.Count); str = "Colors "; break;
                            default: aList = linker._uvs = new List<VertexCodec>(polyList.Count); str = "UVs "; break;
                        }

                        aLen += aInd;
                        for (int i = 0; i < polyList.Count; i++)
                        {
                            MDL0ObjectNode obj = polyList[i] as MDL0ObjectNode;
                            for (int x = aInd; x < aLen; x++)
                                if (obj._manager._faceData[x] != null)
                                {
                                    //Remap color nodes
                                    if ((x == 2 || x == 3))
                                    {
                                        if (Collada._importOptions._rmpClrs)
                                        {
                                            obj._elementIndices[x] = -1;
                                            foreach (MDL0ObjectNode thatObj in polyList.OrderBy(c => -((MDL0ObjectNode)c)._manager.GetColors(x - 2, false).Length))
                                            {
                                                //Only compare up to the current object
                                                if (thatObj == obj)
                                                    break;

                                                var thatArr = thatObj._manager.GetColors(x - 2, false);
                                                var thisArr = obj._manager.GetColors(x - 2, false);
                                                bool equals = true;
                                                if (thisArr.Length == thatArr.Length)
                                                {
                                                    for (int n = 0; n < thisArr.Length; n++)
                                                        if (thisArr[n] != thatArr[n])
                                                        {
                                                            equals = false;
                                                            break;
                                                        }
                                                }
                                                else
                                                {
                                                    foreach (RGBAPixel px in thisArr)
                                                    {
                                                        if (Array.IndexOf(thatArr, px) < 0)
                                                        {
                                                            equals = false;
                                                            break;
                                                        }
                                                    }
                                                }

                                                if (equals)
                                                {
                                                    //Found a match
                                                    obj._elementIndices[x] = thatObj._elementIndices[x];
                                                    obj._manager._newClrObj[x - 2] = thatObj.Index;
                                                    break;
                                                }
                                            }
                                            if (obj._elementIndices[x] != -1)
                                                continue;
                                        }
                                        else
                                            obj._manager._newClrObj[x - 2] = i;
                                    }

                                    obj._elementIndices[x] = (short)aList.Count;

                                    if (form != null)
                                        form.Say("Encoding " + str + (x - aInd) + " for Object " + i + ": " + obj.Name);

                                    VertexCodec vert;
                                    switch (aInd)
                                    {
                                        case 0:
                                            vert = new VertexCodec(obj._manager.GetVertices(false), false, Collada._importOptions._fltVerts);
                                            aList.Add(vert);
                                            if (!direct)
                                                assetLen += vert._dataLen.Align(0x20) + 0x40;
                                            break;
                                        case 1:
                                            vert = new VertexCodec(obj._manager.GetNormals(false), false, Collada._importOptions._fltNrms);
                                            aList.Add(vert);
                                            if (!direct)
                                                assetLen += vert._dataLen.Align(0x20) + 0x20;
                                            break;
                                        case 2:
                                            ColorCodec col = new ColorCodec(obj._manager.GetColors(x - 2, false));
                                            aList.Add(col);
                                            if (!direct)
                                                assetLen += col._dataLen.Align(0x20) + 0x20;
                                            break;
                                        default:
                                            vert = new VertexCodec(obj._manager.GetUVs(x - 4, false), Collada._importOptions._fltUVs);
                                            aList.Add(vert);
                                            if (!direct)
                                                assetLen += vert._dataLen.Align(0x20) + 0x40;
                                            break;
                                    }
                                }
                                else
                                    obj._elementIndices[x] = -1;
                        }
                        if (!direct)
                            entries = aList.Count;
                        break;
                    case MDLResourceType.Normals:
                        if (model._normList != null)
                            entryList = model._normList;
                        else
                        {
                            aInd = 1; //Set the ID
                            aLen = 1; //Offset count
                            goto EvalAssets;
                        }
                        break;
                    case MDLResourceType.Colors:
                        if (model._colorList != null)
                            entryList = model._colorList;
                        else
                        {
                            if (Collada._importOptions._useOneNode)
                            {
                                HashSet<RGBAPixel> pixels = new HashSet<RGBAPixel>();
                                foreach (MDL0ObjectNode obj in model._objList)
                                {
                                    for (int i = 0; i < 2; i++)
                                    {
                                        var arr = obj._manager.GetColors(i, false);
                                        if (arr.Length > 0)
                                        {
                                            obj._elementIndices[i + 2] = 0;
                                            foreach (RGBAPixel p in arr)
                                                pixels.Add(p);
                                        }
                                        else
                                            obj._elementIndices[i + 2] = -1;
                                    }
                                }
                                var le = pixels.ToList(); le.Sort();
                                Collada._importOptions._singleColorNodeEntries = le.ToArray();

                                ColorCodec col = new ColorCodec(Collada._importOptions._singleColorNodeEntries);
                                linker._colors = new List<ColorCodec>() { col };
                                assetLen += col._dataLen.Align(0x20) + 0x20;
                                entries = 1;
                            }
                            else
                            {
                                aInd = 2; //Set the ID
                                aLen = 2; //Offset count
                                goto EvalAssets;
                            }
                        }
                        break;
                    case MDLResourceType.UVs:
                        if (model._uvList != null)
                            entryList = model._uvList;
                        else
                        {
                            aInd = 4; //Set the ID
                            aLen = 8; //Offset count
                            goto EvalAssets;
                        }
                        break;

                    case MDLResourceType.Bones:
                        int index = 0;
                        foreach (MDL0BoneNode b in linker.BoneCache)
                        {
                            if (form != null)
                                form.Say("Calculating the size of the Bones - " + b.Name);

                            b._entryIndex = index++;
                            boneLen += b.CalculateSize(true);
                        }
                        entries = linker.BoneCache.Length;
                        break;

                    case MDLResourceType.Materials:
                        if (model._matList != null)
                            entries = model._matList.Count;
                        break;

                    case MDLResourceType.Objects:
                        if (model._objList != null)
                        {
                            entryList = model._objList;
                            if (model._objList.Count > 0)
                            {
                                model._needsNrmMtxArray = true;
                                foreach (MDL0ObjectNode n in model._objList)
                                    if (n.HasTexMtx)
                                        model._needsTexMtxArray = true;
                            }
                        }
                        break;

                    case MDLResourceType.Shaders:
                        if ((entryList = model.GetUsedShaders()) != null && model._matList != null)
                            entries = model._matList.Count;
                        break;

                    case MDLResourceType.Textures:
                        if (model._texList != null)
                        {
                            foreach (MDL0TextureNode tex in model._texList)
                                texLen += (tex._references.Count * 8) + 4;

                            linker._texCount = entries = model._texList.Count;
                        }
                        break;

                    case MDLResourceType.Palettes:
                        if (model._pltList != null)
                        {
                            foreach (MDL0TextureNode pal in model._pltList)
                                texLen += (pal._references.Count * 8) + 4;

                            linker._palCount = entries = model._pltList.Count;
                        }
                        break;
                }

                if (entryList != null)
                {
                    int index = 0;
                    foreach (MDL0EntryNode e in entryList)
                    {
                        if (form != null)
                            if (resType == MDLResourceType.Objects)
                                form.Say("Encoding the " + resType.ToString() + " - " + e.Name);
                            else
                                form.Say("Calculating the size of the " + resType.ToString() + " - " + e.Name);

                        e._entryIndex = index++;
                        dataLen += e.CalculateSize(true);
                    }
                    if (entries == 0)
                        entries = index;
                }

                if (entries > 0)
                    groupLen += (entries * 0x10) + 0x18;
            }

            //Align the materials perfectly using the data length
            int temp = 0;
            if (model._matList != null && iList.IndexOf(MDLResourceType.Materials) != -1)
            {
                int index = 0;
                MDL0MaterialNode prev = null;
                foreach (MDL0MaterialNode e in model._matList)
                {
                    if (form != null)
                        form.Say("Calculating the size of the Materials - " + e.Name);

                    e._entryIndex = index++;

                    if (index == 1)
                    {
                        if ((temp = (e._mdlOffset = headerLen + tableLen + groupLen + texLen + defLen + boneLen).Align(0x10)) != e._mdlOffset)
                            e._dataAlign = temp - e._mdlOffset;
                    }
                    else
                        e._mdlOffset = (prev = ((MDL0MaterialNode)model._matList[index - 1]))._mdlOffset + prev._calcSize;

                    dataLen += e.CalculateSize(true);
                }
            }

            return
            (linker._headerLen = headerLen) +
            (linker._tableLen = tableLen) +
            (linker._groupLen = groupLen) +
            (linker._texLen = texLen) +
            (linker._defLen = defLen) +
            (linker._boneLen = boneLen) +
            (linker._assetLen = assetLen) +
            (linker._dataLen = dataLen) +
            (linker.Version > 9 ? model._userEntries.GetSize() : 0);
        }
        private static void WriteDefs(ModelLinker linker, ref byte* pGroup, ref byte* pData)
        {
            MDL0Node mdl = linker.Model;

            //This should never happen!
            if (!mdl._hasMix && !mdl._hasOpa && !mdl._hasTree && !mdl._hasXlu)
                return;

            ResourceNode[] polyList = null;
            if (mdl._objList != null)
            {
                polyList = new ResourceNode[mdl._objList.Count];
                Array.Copy(mdl._objList.ToArray(), polyList, mdl._objList.Count);
            }
            MDL0ObjectNode poly;
            int entryCount = 0;
            byte* floor = pData;
            int dataLen;

            ResourceGroup* group = linker.Defs = (ResourceGroup*)pGroup;
            ResourceEntry* entry = &group->_first + 1;

            //NodeTree
            if (mdl._hasTree)
            {
                //Write group entry
                entry[entryCount++]._dataOffset = (int)(pData - pGroup);

                int bCount = linker.BoneCache.Length;
                for (int i = 0; i < bCount; i++)
                {
                    MDL0BoneNode bone = linker.BoneCache[i] as MDL0BoneNode;

                    *pData = 2; //Entry tag
                    *(bushort*)(pData + 1) = (ushort)bone._entryIndex;
                    *(bushort*)(pData + 3) = (ushort)(bone._parent is MDL0BoneNode ? ((MDL0BoneNode)bone._parent)._nodeIndex : 0);
                    pData += 5; //Advance
                }

                *pData++ = 1; //Terminate
            }

            //NodeMix
            //Only weight references go here.
            //First list bones used by weight groups, in bone order
            //Then list weight groups that use bones. Ordered by entry count.
            if (mdl._hasMix)
            {
                //Write group entry
                entry[entryCount++]._dataOffset = (int)(pData - pGroup);

                //Add bones first (using flat bone list)
                foreach (MDL0BoneNode b in linker.BoneCache)
                    if (b._weightCount > 0)
                    {
                        *pData = 5; //Tag
                        *(bushort*)(pData + 1) = (ushort)b._nodeIndex;
                        *(bushort*)(pData + 3) = (ushort)b._entryIndex;
                        pData += 5; //Advance
                    }

                //Add weight groups (using sorted influence list)
                foreach (Influence i in mdl._influences._influences)
                {
                    *pData = 3; //Tag
                    *(bushort*)&pData[1] = (ushort)i._index;
                    int g = 0;
                    foreach (BoneWeight w in i._weights)
                        if (w.Bone != null && w.Weight != 0 && w.Bone._nodeIndex < linker.NodeCache.Length && w.Bone._nodeIndex >= 0 && linker.NodeCache[w.Bone._nodeIndex] is MDL0BoneNode) g++;
                    pData[3] = (byte)g;
                    pData += 4; //Advance
                    foreach (BoneWeight w in i._weights)
                    {
                        if (w.Bone == null || w.Weight == 0 || w.Bone._nodeIndex >= linker.NodeCache.Length || w.Bone._nodeIndex < 0)
                            continue;

                        *(bushort*)pData = (ushort)w.Bone._nodeIndex;
                        *(bfloat*)(pData + 2) = w.Weight;
                        pData += 6; //Advance
                    }
                }

                *pData++ = 1; //Terminate
            }

            //DrawOpa
            if (mdl._hasOpa && polyList != null)
            {
                Array.Sort(polyList, MDL0ObjectNode.DrawCompareOpa);

                //Write group entry
                entry[entryCount++]._dataOffset = (int)(pData - pGroup);

                for (int i = 0; i < polyList.Length; i++)
                {
                    poly = polyList[i] as MDL0ObjectNode;
                    if (poly.OpaMaterialNode != null)
                    {
                        *pData = 4; //Tag
                        *(bushort*)(pData + 1) = (ushort)poly.OpaMaterialNode._entryIndex;
                        *(bushort*)(pData + 3) = (ushort)poly._entryIndex;
                        *(bushort*)(pData + 5) = (ushort)(poly.BoneNode != null ? poly.BoneNode.BoneIndex : 0);
                        pData[7] = poly.DrawPriority;
                        pData += 8; //Advance
                    }
                }

                *pData++ = 1; //Terminate
            }

            //DrawXlu
            if (mdl._hasXlu && polyList != null)
            {
                Array.Sort(polyList, MDL0ObjectNode.DrawCompareXlu);

                //Write group entry
                entry[entryCount++]._dataOffset = (int)(pData - pGroup);

                for (int i = 0; i < polyList.Length; i++)
                {
                    poly = polyList[i] as MDL0ObjectNode;
                    if (poly.XluMaterialNode != null)
                    {
                        *pData = 4; //Tag
                        *(bushort*)(pData + 1) = (ushort)poly.XluMaterialNode._entryIndex;
                        *(bushort*)(pData + 3) = (ushort)poly._entryIndex;
                        *(bushort*)(pData + 5) = (ushort)(poly.BoneNode != null ? poly.BoneNode.BoneIndex : 0);
                        pData[7] = poly.DrawPriority;
                        pData += 8; //Advance
                    }
                }

                *pData++ = 1; //Terminate
            }

            //Align data
            dataLen = (int)(pData - floor);
            while ((dataLen++ & 3) != 0)
                *pData++ = 0;

            //Set header
            *group = new ResourceGroup(entryCount);

            //Advance group poiner
            pGroup += group->_totalSize;
        }
 internal static unsafe void Build(ModelLinker linker, MDL0Header* header, int length, bool force)
 {
     Build(null, linker, header, length, force);
 }
        //Materials must already be written. Do this last!
        private static void WriteTextures(ModelLinker linker, ref byte* pGroup)
        {
            MDL0GroupNode texGrp = linker.Groups[(int)MDLResourceType.Textures];
            MDL0GroupNode palGrp = linker.Groups[(int)MDLResourceType.Palettes];

            if (texGrp == null) return;

            ResourceGroup* pTexGroup = null;
            ResourceEntry* pTexEntry = null;
            if (linker._texCount > 0)
            {
                linker.Textures = pTexGroup = (ResourceGroup*)pGroup;
                *pTexGroup = new ResourceGroup(linker._texCount);

                pTexEntry = &pTexGroup->_first + 1;
                pGroup += pTexGroup->_totalSize;
            }

            ResourceGroup* pDecGroup = null;
            ResourceEntry* pDecEntry = null;
            if (linker._palCount > 0)
            {
                linker.Palettes = pDecGroup = (ResourceGroup*)pGroup;
                *pDecGroup = new ResourceGroup(linker._palCount);
                pDecEntry = &pDecGroup->_first + 1;
                pGroup += pDecGroup->_totalSize;
            }

            bint* pData = (bint*)pGroup;
            int offset;

            //Textures
            List<ResourceNode> list = texGrp.Children;
            list.Sort(); //Alphabetical order
            if (pTexGroup != null)
                foreach (MDL0TextureNode t in list)
                    if (t._references.Count > 0)
                    {
                        offset = (int)pData;
                        (pTexEntry++)->_dataOffset = offset - (int)pTexGroup;
                        *pData++ = t._references.Count;
                        foreach (MDL0MaterialRefNode mat in t._references)
                        {
                            *pData++ = (int)mat.Material.WorkingUncompressed.Address - offset;
                            *pData++ = (int)mat.WorkingUncompressed.Address - offset;
                        }
                    }

            //Palettes
            if (palGrp == null) return;
            list = palGrp.Children;
            list.Sort(); //Alphabetical order
            if (pDecGroup != null)
                foreach (MDL0TextureNode t in list)
                    if (t._references.Count > 0)
                    {
                        offset = (int)pData;
                        (pDecEntry++)->_dataOffset = offset - (int)pDecGroup;
                        *pData++ = t._references.Count;
                        foreach (MDL0MaterialRefNode mat in t._references)
                        {
                            *pData++ = (int)mat.Material.WorkingUncompressed.Address - offset;
                            *pData++ = (int)mat.WorkingUncompressed.Address - offset;
                        }
                    }
        }
        internal static unsafe void Build(Collada form, ModelLinker linker, MDL0Header* header, int length, bool force)
        {
            byte* groupAddr = (byte*)header + linker._headerLen + linker._tableLen;
            byte* dataAddr = groupAddr + linker._groupLen + linker._texLen; //Definitions start here
            byte* assetAddr = dataAddr + linker._defLen + linker._boneLen + linker._dataLen;

            linker.Header = header;

            if (form != null)
                form.Say("Writing header...");

            //Create new model header
            *header = new MDL0Header(length, linker.Version);
            MDL0Props* props = header->Properties;

            if (form != null)
                form.Say("Writing node table...");

            //Write node table, assign node ids
            WriteNodeTable(linker);

            if (form != null)
                form.Say("Writing definitions...");

            //Write def table
            WriteDefs(linker, ref groupAddr, ref dataAddr);

            //Set format list for each polygon's UVAT groups
            SetFormatLists(linker);

            //Write assets first, but only if the model is an import
            if (linker.Model._isImport)
                WriteAssets(form, linker, ref assetAddr);

            //Write groups
            linker.Write(form, ref groupAddr, ref dataAddr, force);

            //Write user entries
            if (linker.Model._userEntries.Count > 0 && linker.Version > 9)
            {
                header->_userDataOffset = (int)dataAddr - (int)header;
                linker.Model._userEntries.Write(header->UserData);
            }
            else
                header->_userDataOffset = 0;

            //Write textures
            WriteTextures(linker, ref groupAddr);

            //Set box min and box max
            if (linker.Model._isImport)
                SetBox(linker);

            //Store group offsets
            linker.Finish();

            //Set new properties
            *props = new MDL0Props(linker.Version, linker.Model._numFacepoints, linker.Model._numFaces, linker.Model._numNodes, linker.Model._scalingRule, linker.Model._texMtxMode, linker.Model._needsNrmMtxArray, linker.Model._needsTexMtxArray, linker.Model._enableExtents, linker.Model._envMtxMode, linker.Model.BoxMin, linker.Model.BoxMax);
        }
        internal void Parse(MDL0Node model)
        {
            Influence   inf;
            ModelLinker linker = model._linker;

            switch (_type)
            {
            case MDLResourceType.Definitions:
                if (linker.Defs != null)
                {
                    ExtractGroup(linker.Defs, typeof(MDL0DefNode));
                }
                break;

            case MDLResourceType.Bones:
                //Break if there are no bones defined
                if (linker.Bones == null)
                {
                    break;
                }

                //Parse bones from raw data (flat list).
                //Bones re-assign parents in their Initialize block, so parents are true.
                ExtractGroup(linker.Bones, typeof(MDL0BoneNode));

                //Cache flat list
                linker.BoneCache = _children.ToArray();

                //Make sure the node cache is the correct size
                int highest = 0;
                foreach (MDL0BoneNode b in _children)
                {
                    if (b._nodeIndex >= linker.NodeCache.Length && b._nodeIndex > highest)
                    {
                        highest = b._nodeIndex;
                    }
                }
                if (highest >= linker.NodeCache.Length)
                {
                    linker.NodeCache = new IMatrixNode[highest + 1];
                }

                //Reset children so we can rebuild
                _children.Clear();

                //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;
                }

                //Now that bones and primary influences have been cached, we can create weighted influences.
                foreach (ResourcePair p in *linker.Defs)
                {
                    if (p.Name == "NodeTree")
                    {
                        //Use node tree to rebuild bone heirarchy
                        byte *pData = (byte *)p.Data;

Top:
                        if (*pData == 2)
                        {
                            bone  = linker.BoneCache[*(bushort *)(pData + 1)] as MDL0BoneNode;
                            index = *(bushort *)(pData + 3);

                            if (bone.Header->_parentOffset == 0)
                            {
                                _children.Add(bone);
                            }
                            else
                            {
                                (bone._parent = linker.NodeCache[index] as ResourceNode)._children.Add(bone);
                            }

                            pData += 5;
                            goto Top;
                        }
                    }
                    else if (p.Name == "NodeMix")
                    {
                        //Use node mix to create weight groups
                        byte *pData = (byte *)p.Data;

Top:
                        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(count);
                            //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.
                            for (int i = 0; i < count; i++, nEntry++)
                            {
                                if ((linker.NodeCache[nEntry->_id] as MDL0BoneNode) == null)
                                {
                                    Console.WriteLine("Null bone entry!");
                                }
                                else
                                {
                                    inf._weights[i] = new BoneWeight(linker.NodeCache[nEntry->_id] as MDL0BoneNode, nEntry->_value);
                                }
                            }

                            //Add influence to model object, while adding it to the cache.
                            linker.NodeCache[index] = model._influences.AddOrCreate(inf);

                            //Move data pointer to next entry
                            pData = (byte *)nEntry;
                            goto Top;

                        //Type 5 is for primary influences
                        case 5:
                            pData += 5;
                            goto Top;
                        }
                    }
                }
                int z = 0;
                foreach (IMatrixNode m in linker.NodeCache)
                {
                    if (!m.IsPrimaryNode)
                    {
                        ((Influence)m)._permanentID = z; break;
                    }
                    z++;
                }
                break;

            case MDLResourceType.Materials:
                if (linker.Materials != null)
                {
                    ExtractGroup(linker.Materials, typeof(MDL0MaterialNode));
                }
                break;

            case MDLResourceType.Shaders:
                if (linker.Shaders != null)
                {
                    ExtractGroup(linker.Shaders, typeof(MDL0ShaderNode));
                }
                break;

            case MDLResourceType.Vertices:
                if (linker.Vertices != null)
                {
                    ExtractGroup(linker.Vertices, typeof(MDL0VertexNode));
                }
                break;

            case MDLResourceType.Normals:
                if (linker.Normals != null)
                {
                    ExtractGroup(linker.Normals, typeof(MDL0NormalNode));
                }
                break;

            case MDLResourceType.UVs:
                if (linker.UVs != null)
                {
                    ExtractGroup(linker.UVs, typeof(MDL0UVNode));
                }
                break;

            case MDLResourceType.Objects:
                //Break if no polygons defined
                if (linker.Polygons == null)
                {
                    break;
                }

                //Extract
                ExtractGroup(linker.Polygons, typeof(MDL0PolygonNode));

                //Attach materials to polygons.
                //This assumes that materials have already been parsed.

                List <ResourceNode> matList = ((MDL0Node)_parent)._matList;
                MDL0PolygonNode     poly;
                MDL0MaterialNode    mat;

                //Find DrawOpa or DrawXlu entry in Definition list
                foreach (ResourcePair p in *linker.Defs)
                {
                    if ((p.Name == "DrawOpa") || (p.Name == "DrawXlu"))
                    {
                        ushort dIndex = 0;
                        byte * pData  = (byte *)p.Data;
                        while (*pData++ == 4)
                        {
                            //Get polygon from index
                            dIndex = *(bushort *)(pData + 2);
                            if (dIndex >= _children.Count || dIndex < 0)
                            {
                                ((MDL0Node)Parent)._errors.Add("Object index was greater than the actual object count.");
                                SignalPropertyChange();
                                dIndex = 0;
                            }
                            poly = _children[dIndex] as MDL0PolygonNode;
                            //Get material from index
                            mat = matList[*(bushort *)pData] as MDL0MaterialNode;
                            //Get bone from index and assign
                            int boneIndex = *(bushort *)(pData + 4);
                            if (linker.BoneCache != null && boneIndex >= 0 && boneIndex < linker.BoneCache.Length)
                            {
                                poly.BoneNode = linker.BoneCache[boneIndex] as MDL0BoneNode;
                            }
                            //Assign material to polygon and add polygon to material reference list
                            (poly._material = mat)._polygons.Add(poly);
                            //Increment pointer
                            pData += 7;
                        }
                    }
                }
                break;

            case MDLResourceType.Colors:
                if (linker.Colors != null)
                {
                    ExtractGroup(linker.Colors, typeof(MDL0ColorNode));
                }
                break;

            case MDLResourceType.Textures:
                if (linker.Textures != null)
                {
                    ExtractGroup(linker.Textures, typeof(MDL0TextureNode));
                }
                break;

            case MDLResourceType.Palettes:
                if (linker.Palettes != null)
                {
                    ExtractGroup(linker.Palettes, typeof(MDL0TextureNode));
                }
                break;
            }
        }
        private static void SetBox(ModelLinker linker)
        {
            Vector3 min, max;

            linker.Model.ApplyCHR(null, 0);
            linker.Model.GetBox(out min, out max);

            linker.Model._min = min;
            linker.Model._max = max;

            if (linker.Model._objList != null)
            {
                linker.Model._numFacepoints = 0;
                linker.Model._numFaces = 0;
                foreach (MDL0ObjectNode n in linker.Model._objList)
                {
                    linker.Model._numFacepoints += n._numFacepoints;
                    linker.Model._numFaces += n._numFaces;
                }
            }
        }
        public void BuildFromScratch(Collada form)
        {
            _isImport = true;

            _influences.Clean();
            _influences.Sort();

            CleanTextures();

            _linker = ModelLinker.Prepare(this);
            int size = ModelEncoder.CalcSize(form, _linker);

            FileMap uncompMap = FileMap.FromTempFile(size);

            ModelEncoder.Build(form, _linker, (MDL0Header*)uncompMap.Address, size, true);

            _replSrc.Close();
            _replUncompSrc.Close();
            _replSrc = _replUncompSrc = new DataSource(uncompMap.Address, size);
            _replSrc.Map = _replUncompSrc.Map = uncompMap;

            IsDirty = false;
            _reopen = true;
            _isImport = false;
        }
 private static void SetFormatLists(ModelLinker linker)
 {
     if (linker.Model._objList != null)
     for (int i = 0; i < linker.Model._objList.Count; i++)
     {
         MDL0ObjectNode poly = (MDL0ObjectNode)linker.Model._objList[i];
         poly._fmtList = poly._manager.SetFormatList(poly, linker);
     }
 }
        public override void OnPopulate()
        {
            InitGroups();
            _linker = new ModelLinker(Header);
            _assets = new AssetStorage(_linker);
            try
            {
                //Set def flags
                _hasMix = _hasOpa = _hasTree = _hasXlu = false;
                if (_linker.Defs != null)
                    foreach (ResourcePair p in *_linker.Defs)
                        if (p.Name == "NodeTree") _hasTree = true;
                        else if (p.Name == "NodeMix") _hasMix = true;
                        else if (p.Name == "DrawOpa") _hasOpa = true;
                        else if (p.Name == "DrawXlu") _hasXlu = true;

                //These cause some complications if not parsed...
                _texGroup.Parse(this);
                _pltGroup.Parse(this);

                _defGroup.Parse(this);
                _boneGroup.Parse(this);
                _matGroup.Parse(this);
                _shadGroup.Parse(this);
                _vertGroup.Parse(this);
                _normGroup.Parse(this);
                _uvGroup.Parse(this);
                _colorGroup.Parse(this);

                if (Version >= 10)
                {
                    _furVecGroup.Parse(this);
                    _furPosGroup.Parse(this);
                }

                _objGroup.Parse(this); //Parse objects last!

                _texList.Sort();
                _pltList.Sort();
            }
            finally //Clean up!
            {
                //We'll use the linker to access the bone cache
                //_linker = null;

                //Don't dispose assets, in case an object is replaced
                //_assets.Dispose();
                //_assets = null;

                CleanGroups();

                //Check for model errors
                if (_errors.Count > 0)
                {
                    string message = _errors.Count + (_errors.Count > 1 ? " errors have" : " error has") + " been found in the model " + _name + ".\n" + (_errors.Count > 1 ? "These errors" : "This error") + " will be fixed when you save:";
                    foreach (string s in _errors)
                        message += "\n - " + s;
                    MessageBox.Show(message);
                }
            }
        }
        public VertexAttributeFormat[] SetFormatList(MDL0ObjectNode polygon, ModelLinker linker)
        {
            List<VertexAttributeFormat> list = new List<VertexAttributeFormat>();
            VertexCodec vert = null;
            ColorCodec col = null;

            for (int i = 0; i < 12; i++)
            {
                if (polygon._manager._faceData[i] != null)
                    switch (i)
                    {
                        case 0: //Positions
                            if (linker._vertices != null && linker._vertices.Count != 0 && polygon._elementIndices[0] != -1)
                                if ((vert = linker._vertices[polygon._elementIndices[0]]) != null)
                                    list.Add(new VertexAttributeFormat(
                                        GXAttribute.Position,
                                        (GXCompType)vert._type,
                                        (GXCompCnt)(vert._hasZ ? 1 : 0),
                                        (byte)vert._scale));
                            break;
                        case 1: //Normals
                            vert = null;
                            if (linker._normals != null && linker._normals.Count != 0 && polygon._elementIndices[1] != -1)
                                if ((vert = linker._normals[polygon._elementIndices[1]]) != null)
                                    list.Add(new VertexAttributeFormat(
                                        GXAttribute.Normal,
                                        (GXCompType)vert._type,
                                        GXCompCnt.NrmXYZ,
                                        (byte)vert._scale));
                            break;
                        case 2: //Color 1
                        case 3: //Color 2
                            col = null;
                            if (linker._colors != null && linker._colors.Count != 0 && polygon._elementIndices[i] != -1 && (col = linker._colors[polygon._elementIndices[i]]) != null)
                                list.Add(new VertexAttributeFormat(
                                    (GXAttribute)((int)GXAttribute.Color0 + (i - 2)),
                                    (GXCompType)col._outType,
                                    (GXCompCnt)(col._hasAlpha ? 1 : 0),
                                    0));
                            break;
                        case 4: //Tex 1
                        case 5: //Tex 2
                        case 6: //Tex 3
                        case 7: //Tex 4
                        case 8: //Tex 5
                        case 9: //Tex 6
                        case 10: //Tex 7
                        case 11: //Tex 8
                            vert = null;
                            if (linker._uvs != null && linker._uvs.Count != 0 && polygon._elementIndices[i] != -1)
                                if ((vert = linker._uvs[polygon._elementIndices[i]]) != null)
                                    list.Add(new VertexAttributeFormat(
                                        (GXAttribute)((int)GXAttribute.Tex0 + (i - 4)),
                                        (GXCompType)vert._type,
                                        GXCompCnt.TexST,
                                        (byte)vert._scale));
                            break;
                    }
            }
            return list.ToArray();
        }