using SharpDX;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace CodeWalker.GameFiles
{

    [TypeConverter(typeof(ExpandableObjectConverter))] public class ShaderGroup : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 64; }
        }

        // structure data
        public uint VFT { get; set; } = 1080113136;
        public uint Unknown_4h = 1; // 0x00000001
        public ulong TextureDictionaryPointer { get; set; }
        public ulong ShadersPointer { get; set; }
        public ushort ShadersCount1 { get; set; }
        public ushort ShadersCount2 { get; set; }
        public uint Unknown_1Ch; // 0x00000000
        public ulong Unknown_20h; // 0x0000000000000000
        public ulong Unknown_28h; // 0x0000000000000000
        public uint Unknown_30h { get; set; }//wtf is this?? (shadercount-1)*3+8 ..?
        public uint Unknown_34h; // 0x00000000
        public ulong Unknown_38h; // 0x0000000000000000

        // reference data
        public TextureDictionary TextureDictionary { get; set; }
        public ResourcePointerArray64<ShaderFX> Shaders { get; set; }


        public int TotalParameters
        {
            get
            {
                int c = 0;
                if (Shaders?.data_items != null)
                {
                    foreach (var s in Shaders.data_items)
                    {
                        c += s.ParameterCount;
                    }
                }
                return c;
            }
        }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.TextureDictionaryPointer = reader.ReadUInt64();
            this.ShadersPointer = reader.ReadUInt64();
            this.ShadersCount1 = reader.ReadUInt16();
            this.ShadersCount2 = reader.ReadUInt16();
            this.Unknown_1Ch = reader.ReadUInt32();
            this.Unknown_20h = reader.ReadUInt64();
            this.Unknown_28h = reader.ReadUInt64();
            this.Unknown_30h = reader.ReadUInt32();
            this.Unknown_34h = reader.ReadUInt32();
            this.Unknown_38h = reader.ReadUInt64();

            // read reference data
            this.TextureDictionary = reader.ReadBlockAt<TextureDictionary>(
                this.TextureDictionaryPointer // offset
            );
            this.Shaders = reader.ReadBlockAt<ResourcePointerArray64<ShaderFX>>(
                this.ShadersPointer, // offset
                this.ShadersCount1
            );


            // wtf is Unknown_30h ???
            //switch (ShadersCount1)
            //{
            //    case 1: if ((Unknown_30h != 8) && (Unknown_30h != 25))
            //        { }
            //        break;
            //    case 2: if ((Unknown_30h != 11) && (Unknown_30h != 61) && (Unknown_30h != 50) && (Unknown_30h != 51))
            //        { }
            //        break;
            //    case 3: if ((Unknown_30h != 15) && (Unknown_30h != 78))
            //        { }
            //        break;
            //    case 4: if ((Unknown_30h != 18) && (Unknown_30h != 108))
            //        { }
            //        break;
            //    case 5: if ((Unknown_30h != 22) && (Unknown_30h != 135) && (Unknown_30h != 137))
            //        { }
            //        break;
            //    case 6: if (Unknown_30h != 25)
            //        { }
            //        break;
            //    case 7: if (Unknown_30h != 29)
            //        { }
            //        break;
            //    case 8: if (Unknown_30h != 32)
            //        { }
            //        break;
            //    case 9: if (Unknown_30h != 36)
            //        { }
            //        break;
            //    case 10: if (Unknown_30h != 39)
            //        { }
            //        break;
            //    case 11: if (Unknown_30h != 43)
            //        { }
            //        break;
            //    case 12: if (Unknown_30h != 46)
            //        { }
            //        break;
            //    case 13: if (Unknown_30h != 50)
            //        { }
            //        break;
            //    case 14: if (Unknown_30h != 53)
            //        { }
            //        break;
            //    case 15: if (Unknown_30h != 57)
            //        { }
            //        break;
            //    case 16: if (Unknown_30h != 60)
            //        { }
            //        break;
            //    case 17: if (Unknown_30h != 64)
            //        { }
            //        break;
            //    case 18: if (Unknown_30h != 67)
            //        { }
            //        break;
            //    case 19: if (Unknown_30h != 71)
            //        { }
            //        break;
            //    case 20: if (Unknown_30h != 74)
            //        { }
            //        break;
            //    default:
            //        break;
            //}

            //var cnt = 8 + ((ShadersCount1 > 0) ? ShadersCount1-1 : 0) * 3;
            //if (cnt != Unknown_30h)
            //{ }

            //if (Unknown_4h != 1)
            //{ }
            //if (Unknown_1Ch != 0)
            //{ }
            //if (Unknown_20h != 0)
            //{ }
            //if (Unknown_28h != 0)
            //{ }
            //if (Unknown_34h != 0)
            //{ }
            //if (Unknown_38h != 0)
            //{ }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.TextureDictionaryPointer = (ulong)(this.TextureDictionary != null ? this.TextureDictionary.FilePosition : 0);
            this.ShadersPointer = (ulong)(this.Shaders != null ? this.Shaders.FilePosition : 0);
            this.ShadersCount1 = (ushort)(this.Shaders != null ? this.Shaders.Count : 0);
            this.ShadersCount2 = this.ShadersCount1;

            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.TextureDictionaryPointer);
            writer.Write(this.ShadersPointer);
            writer.Write(this.ShadersCount1);
            writer.Write(this.ShadersCount2);
            writer.Write(this.Unknown_1Ch);
            writer.Write(this.Unknown_20h);
            writer.Write(this.Unknown_28h);
            writer.Write(this.Unknown_30h);
            writer.Write(this.Unknown_34h);
            writer.Write(this.Unknown_38h);
        }
        public void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            YdrXml.ValueTag(sb, indent, "Unknown30", Unknown_30h.ToString());
            if (TextureDictionary != null)
            {
                TextureDictionary.WriteXmlNode(TextureDictionary, sb, indent, ddsfolder, "TextureDictionary");
            }
            YdrXml.WriteItemArray(sb, Shaders?.data_items, indent, "Shaders");
        }
        public void ReadXml(XmlNode node, string ddsfolder)
        {
            Unknown_30h = Xml.GetChildUIntAttribute(node, "Unknown30", "value");
            var tnode = node.SelectSingleNode("TextureDictionary");
            if (tnode != null)
            {
                TextureDictionary = TextureDictionary.ReadXmlNode(tnode, ddsfolder);
            }
            var shaders = XmlMeta.ReadItemArray<ShaderFX>(node, "Shaders");
            if (shaders != null)
            {
                Shaders = new ResourcePointerArray64<ShaderFX>();
                Shaders.data_items = shaders;
            }


            if ((shaders != null) && (TextureDictionary != null))
            {
                foreach (var shader in shaders)
                {
                    var sparams = shader?.ParametersList?.Parameters;
                    if (sparams != null)
                    {
                        foreach (var sparam in sparams)
                        {
                            if (sparam.Data is TextureBase tex)
                            {
                                var tex2 = TextureDictionary.Lookup(tex.NameHash);
                                if (tex2 != null)
                                {
                                    sparam.Data = tex2;//swap the parameter out for the embedded texture
                                }
                            }
                        }
                    }
                }
            }
        }


        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (TextureDictionary != null) list.Add(TextureDictionary);
            if (Shaders != null) list.Add(Shaders);
            return list.ToArray();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class ShaderFX : ResourceSystemBlock, IMetaXmlItem
    {
        public override long BlockLength
        {
            get { return 48; }
        }

        // structure data
        public ulong ParametersPointer { get; set; }
        public MetaHash Name { get; set; } //decal_emissive_only, emissive, spec
        public uint Unknown_Ch; // 0x00000000
        public byte ParameterCount { get; set; }
        public byte RenderBucket { get; set; } // 2, 0, 
        public ushort Unknown_12h { get; set; } = 32768; // 32768    HasComment?
        public ushort ParameterSize { get; set; } //112, 208, 320    (with 16h) 10485872, 17826000, 26214720
        public ushort ParameterDataSize { get; set; } //160, 272, 400 
        public MetaHash FileName { get; set; } //decal_emissive_only.sps, emissive.sps, spec.sps
        public uint Unknown_1Ch; // 0x00000000
        public uint RenderBucketMask { get; set; } //65284, 65281  DrawBucketMask?   (1<<bucket) | 0xFF00
        public ushort Unknown_24h; // 0x0000
        public byte Unknown_26h; // 0x00
        public byte TextureParametersCount { get; set; }
        public ulong Unknown_28h; // 0x0000000000000000

        // reference data
        //public ResourceSimpleArray<ShaderParameter_GTA5_pc> Parameters { get; set; }
        //public SimpleArrayOFFSET<uint_r> ParameterHashes { get; set; }
        public ShaderParametersBlock ParametersList { get; set; }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.ParametersPointer = reader.ReadUInt64();
            this.Name = new MetaHash(reader.ReadUInt32());
            this.Unknown_Ch = reader.ReadUInt32();
            this.ParameterCount = reader.ReadByte();
            this.RenderBucket = reader.ReadByte();
            this.Unknown_12h = reader.ReadUInt16();
            this.ParameterSize = reader.ReadUInt16();
            this.ParameterDataSize = reader.ReadUInt16();
            this.FileName = new MetaHash(reader.ReadUInt32());
            this.Unknown_1Ch = reader.ReadUInt32();
            this.RenderBucketMask = reader.ReadUInt32();
            this.Unknown_24h = reader.ReadUInt16();
            this.Unknown_26h = reader.ReadByte();
            this.TextureParametersCount = reader.ReadByte();
            this.Unknown_28h = reader.ReadUInt64();

            // read reference data
            this.ParametersList = reader.ReadBlockAt<ShaderParametersBlock>(
                this.ParametersPointer, // offset
                this.ParameterCount,
                this
            );

            //// just testing...
            //if (Unknown_12h != 32768)
            //{
            //    if (Unknown_12h != 0)//des_aquaduct_root, rig_root_skin.... destructions?
            //    { }//no hit
            //}
            //if (RenderBucketMask != ((1 << RenderBucket) | 0xFF00))
            //{ }//no hit
            //if (ParameterSize != ParametersList?.ParametersSize)
            //{ }//no hit
            ////if (ParameterDataSize != ParametersList?.ParametersDataSize)
            //{
            //    var diff = ParameterDataSize - (ParametersList?.BlockLength ?? 0);
            //    switch (diff)
            //    {
            //        case 32:
            //        case 36:
            //        case 40:
            //        case 44:
            //            break;
            //        default:
            //            break;//no hit
            //    }
            //}
            //if (Unknown_24h != 0)
            //{ }//no hit
            //if (Unknown_26h != 0)
            //{ }//no hit
            //if (Unknown_Ch != 0)
            //{ }//no hit
            //if (Unknown_1Ch != 0)
            //{ }//no hit
            //if (Unknown_28h != 0)
            //{ }//no hit

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.ParametersPointer = (ulong)(this.ParametersList != null ? this.ParametersList.FilePosition : 0);
            this.ParameterCount = (byte)(this.ParametersList != null ? this.ParametersList.Count : 0);

            // write structure data
            writer.Write(this.ParametersPointer);
            writer.Write(this.Name.Hash);
            writer.Write(this.Unknown_Ch);
            writer.Write(this.ParameterCount);
            writer.Write(this.RenderBucket);
            writer.Write(this.Unknown_12h);
            writer.Write(this.ParameterSize);
            writer.Write(this.ParameterDataSize);
            writer.Write(this.FileName.Hash);
            writer.Write(this.Unknown_1Ch);
            writer.Write(this.RenderBucketMask);
            writer.Write(this.Unknown_24h);
            writer.Write(this.Unknown_26h);
            writer.Write(this.TextureParametersCount);
            writer.Write(this.Unknown_28h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.StringTag(sb, indent, "Name", YdrXml.HashString(Name));
            YdrXml.StringTag(sb, indent, "FileName", YdrXml.HashString(FileName));
            YdrXml.ValueTag(sb, indent, "RenderBucket", RenderBucket.ToString());
            if (ParametersList != null)
            {
                YdrXml.OpenTag(sb, indent, "Parameters");
                ParametersList.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "Parameters");
            }
        }
        public void ReadXml(XmlNode node)
        {
            Name = XmlMeta.GetHash(Xml.GetChildInnerText(node, "Name"));
            FileName = XmlMeta.GetHash(Xml.GetChildInnerText(node, "FileName"));
            RenderBucket = (byte)Xml.GetChildUIntAttribute(node, "RenderBucket", "value");
            RenderBucketMask = ((1u << RenderBucket) | 0xFF00u);
            var pnode = node.SelectSingleNode("Parameters");
            if (pnode != null)
            {
                ParametersList = new ShaderParametersBlock();
                ParametersList.Owner = this;
                ParametersList.ReadXml(pnode);

                ParameterCount = (byte)ParametersList.Count;
                ParameterSize = ParametersList.ParametersSize;
                ParameterDataSize = ParametersList.ParametersDataSize;//is it right?
                TextureParametersCount = ParametersList.TextureParamsCount;
            }
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (ParametersList != null) list.Add(ParametersList);
            return list.ToArray();
        }


        public override string ToString()
        {
            return Name.ToString() + " (" + FileName.ToString() + ")";
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class ShaderParameter
    {
        public byte DataType { get; set; } //0: texture, 1: vector4
        public byte Unknown_1h { get; set; }
        public ushort Unknown_2h; // 0x0000
        public uint Unknown_4h; // 0x00000000
        public ulong DataPointer { get; set; }

        public object Data { get; set; }

        public void Read(ResourceDataReader reader)
        {
            this.DataType = reader.ReadByte();
            this.Unknown_1h = reader.ReadByte();
            this.Unknown_2h = reader.ReadUInt16();
            this.Unknown_4h = reader.ReadUInt32();
            this.DataPointer = reader.ReadUInt64();
        }
        public void Write(ResourceDataWriter writer)
        {
            writer.Write(this.DataType);
            writer.Write(this.Unknown_1h);
            writer.Write(this.Unknown_2h);
            writer.Write(this.Unknown_4h);
            writer.Write(this.DataPointer);
        }

        public override string ToString()
        {
            return (Data != null) ? Data.ToString() : (DataType.ToString() + ": " + DataPointer.ToString());
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class ShaderParametersBlock : ResourceSystemBlock
    {

        public override long BlockLength
        {
            get
            {
                var bsize = BaseSize;
                var psize = ParametersDataSize;
                return bsize + psize*4;
            }
        }

        public long BaseSize
        {
            get
            {
                long offset = 32;
                foreach (var x in Parameters)
                {
                    offset += 16;
                    offset += 16 * x.DataType;
                }

                offset += Parameters.Length * 4;
                return offset;
            }
        }
        public ushort ParametersSize
        {
            get
            {
                ushort size = (ushort)((Parameters?.Length??0) * 16);
                foreach (var x in Parameters)
                {
                    size += (ushort)(16 * x.DataType);
                }
                return size;
            }
        }
        public ushort ParametersDataSize
        {
            get
            {
                var size = BaseSize;
                if ((size % 16) != 0) size += (16 - (size % 16));
                return (ushort)size;
            }
        }

        public byte TextureParamsCount
        {
            get
            {
                byte c = 0;
                foreach (var x in Parameters)
                {
                    if (x.DataType == 0) c++;
                }
                return c;
            }
        }

        public ShaderParameter[] Parameters { get; set; }
        public MetaName[] Hashes { get; set; }
        public int Count { get; set; }

        public ShaderFX Owner { get; set; }

        private ResourceSystemStructBlock<Vector4>[] ParameterDataBlocks = null;

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            Count = Convert.ToInt32(parameters[0]);
            Owner = parameters[1] as ShaderFX;

            var paras = new List<ShaderParameter>();
            for (int i = 0; i < Count; i++)
            {
                var p = new ShaderParameter();
                p.Read(reader);
                paras.Add(p);
            }

            int offset = 0;
            for (int i = 0; i < Count; i++)
            {
                var p = paras[i];

                // read reference data
                switch (p.DataType)
                {
                    case 0:
                        offset += 0;
                        p.Data = reader.ReadBlockAt<TextureBase>(p.DataPointer);
                        break;
                    case 1:
                        offset += 16;
                        p.Data = reader.ReadStructAt<Vector4>((long)p.DataPointer);
                        break;
                    default:
                        offset += 16 * p.DataType;
                        p.Data = reader.ReadStructsAt<Vector4>(p.DataPointer, p.DataType, false);
                        break;
                }
            }


            reader.Position += offset; //Vector4 data gets embedded here... but why pointers in params also???

            var hashes = new List<MetaName>();
            for (int i = 0; i < Count; i++)
            {
                hashes.Add((MetaName)reader.ReadUInt32());
            }

            Parameters = paras.ToArray();
            Hashes = hashes.ToArray();


            ////testing padding area at the end of the block...
            //var psiz1 = Owner.ParameterDataSize;
            //var psiz2 = ParametersDataSize;
            //if (psiz1 != psiz2)
            //{ }//no hit
            //var unk0 = reader.ReadStructs<MetaHash>(8);
            //foreach (var u0i in unk0)
            //{
            //    if (u0i != 0)
            //    { }//no hit
            //}
            //if (Owner.Unknown_12h != 0)
            //{
            //    var unk1 = reader.ReadStructs<MetaHash>(psiz1);
            //    foreach (var u1i in unk1)
            //    {
            //        if (u1i != 0)
            //        { break; }//no hit
            //    }
            //}


            //// just testing...
            //for (int i = 0; i < Parameters.Length; i++)
            //{
            //    var param = Parameters[i];
            //    if (param.DataType == 0)
            //    {
            //        if (param.Unknown_1h != ((param.Data == null) ? 10 : (i + 2)))
            //        { }
            //    }
            //    else
            //    {
            //        if (param.Unknown_1h != (160 + ((Parameters.Length - 1) - i)))
            //        { }
            //    }
            //}
            //if (Parameters.Length > 0)
            //{
            //    var lparam = Parameters[Parameters.Length - 1];
            //    switch(lparam.Unknown_1h)
            //    {
            //        case 192:
            //        case 160:
            //        case 177:
            //        case 161:
            //        case 156:
            //        case 162:
            //        case 157:
            //        case 149:
            //        case 178:
            //        case 72:
            //        case 153:
            //        case 133:
            //            break;
            //        case 64://in ydd's
            //        case 130:
            //        case 180:
            //            break;
            //        default:
            //            break;
            //    }
            //}


        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {

            // update pointers...
            for (int i = 0; i < Parameters.Length; i++)
            {
                var param = Parameters[i];
                if (param.DataType == 0)
                {
                    param.DataPointer = (ulong)((param.Data as TextureBase)?.FilePosition ?? 0);
                }
                else
                {
                    var block = (i < ParameterDataBlocks?.Length) ? ParameterDataBlocks[i] : null;
                    if (block != null)
                    {
                        param.DataPointer = (ulong)block.FilePosition;
                    }
                    else
                    {
                        param.DataPointer = 0;//shouldn't happen!
                    }
                }
            }



            // write parameter infos
            foreach (var f in Parameters)
            {
                f.Write(writer);
            }

            // write vector data
            for (int i = 0; i < Parameters.Length; i++)
            {
                var param = Parameters[i];
                if (param.DataType != 0)
                {
                    var block = (i < ParameterDataBlocks?.Length) ? ParameterDataBlocks[i] : null;
                    if (block != null)
                    {
                        writer.WriteBlock(block);
                    }
                    else
                    { } //shouldn't happen!
                }
            }

            // write hashes
            foreach (var h in Hashes)
            {
                writer.Write((uint)h);
            }


            //write end padding stuff
            var psiz = ParametersDataSize;
            writer.Write(new byte[32 + psiz*4]);

        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            var cind = indent + 1;
            for (int i = 0; i < Count; i++)
            {
                var param = Parameters[i];
                var name = (ShaderParamNames)Hashes[i];
                var typestr = "";
                if (param.DataType == 0) typestr = "Texture";
                else if (param.DataType == 1) typestr = "Vector";
                else if (param.DataType > 1) typestr = "Array";
                var otstr = "Item name=\"" + name.ToString() + "\" type=\"" + typestr + "\"";

                if (param.DataType == 0)
                {
                    if (param.Data is TextureBase tex)
                    {
                        YdrXml.OpenTag(sb, indent, otstr);
                        YdrXml.StringTag(sb, cind, "Name", YdrXml.XmlEscape(tex.Name));
                        YdrXml.CloseTag(sb, indent, "Item");
                    }
                    else
                    {
                        YdrXml.SelfClosingTag(sb, indent, otstr);
                    }
                }
                else if (param.DataType == 1)
                {
                    if (param.Data is Vector4 vec)
                    {
                        YdrXml.SelfClosingTag(sb, indent, otstr + " " + FloatUtil.GetVector4XmlString(vec));
                    }
                    else
                    {
                        YdrXml.SelfClosingTag(sb, indent, otstr);
                    }
                }
                else
                {
                    if (param.Data is Vector4[] arr)
                    {
                        YdrXml.OpenTag(sb, indent, otstr);
                        foreach (var vec in arr)
                        {
                            YdrXml.SelfClosingTag(sb, cind, "Value " + FloatUtil.GetVector4XmlString(vec));
                        }
                        YdrXml.CloseTag(sb, indent, "Item");
                    }
                    else
                    {
                        YdrXml.SelfClosingTag(sb, indent, otstr);
                    }
                }
            }
        }
        public void ReadXml(XmlNode node)
        {
            var plist = new List<ShaderParameter>();
            var hlist = new List<MetaName>();
            var pnodes = node.SelectNodes("Item");
            foreach (XmlNode pnode in pnodes)
            {
                var p = new ShaderParameter();
                var h = (MetaName)(uint)XmlMeta.GetHash(Xml.GetStringAttribute(pnode, "name")?.ToLowerInvariant());
                var type = Xml.GetStringAttribute(pnode, "type");
                if (type == "Texture")
                {
                    p.DataType = 0;
                    if (pnode.SelectSingleNode("Name") != null)
                    {
                        var tex = new TextureBase();
                        tex.ReadXml(pnode, null);//embedded textures will get replaced in ShaderFX ReadXML
                        tex.Unknown_32h = 2;
                        p.Data = tex;
                    }
                }
                else if (type == "Vector")
                {
                    p.DataType = 1;
                    float fx = Xml.GetFloatAttribute(pnode, "x");
                    float fy = Xml.GetFloatAttribute(pnode, "y");
                    float fz = Xml.GetFloatAttribute(pnode, "z");
                    float fw = Xml.GetFloatAttribute(pnode, "w");
                    p.Data = new Vector4(fx, fy, fz, fw);
                }
                else if (type == "Array")
                {
                    var vecs = new List<Vector4>();
                    var inodes = pnode.SelectNodes("Value");
                    foreach (XmlNode inode in inodes)
                    {
                        float fx = Xml.GetFloatAttribute(inode, "x");
                        float fy = Xml.GetFloatAttribute(inode, "y");
                        float fz = Xml.GetFloatAttribute(inode, "z");
                        float fw = Xml.GetFloatAttribute(inode, "w");
                        vecs.Add(new Vector4(fx, fy, fz, fw));
                    }
                    p.Data = vecs.ToArray();
                    p.DataType = (byte)vecs.Count;
                }
                plist.Add(p);
                hlist.Add(h);
            }

            Parameters = plist.ToArray();
            Hashes = hlist.ToArray();
            Count = plist.Count;

            for (int i = 0; i < Parameters.Length; i++)
            {
                var param = Parameters[i];
                if (param.DataType == 0)
                {
                    param.Unknown_1h = (byte)(i + 2);//wtf and why
                }
            }
            var offset = 160;
            for (int i = Parameters.Length - 1; i >= 0; i--)
            {
                var param = Parameters[i];
                if (param.DataType != 0)
                {
                    param.Unknown_1h = (byte)offset;//wtf and why
                    offset += param.DataType;
                }
            }

        }




        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            list.AddRange(base.GetReferences());

            foreach (var x in Parameters)
            {
                if (x.DataType == 0)
                {
                    list.Add(x.Data as TextureBase);
                }
            }

            return list.ToArray();
        }

        public override Tuple<long, IResourceBlock>[] GetParts()
        {
            var list = new List<Tuple<long, IResourceBlock>>();
            list.AddRange(base.GetParts());

            long offset = Parameters.Length * 16;

            var blist = new List<ResourceSystemStructBlock<Vector4>>();
            foreach (var x in Parameters)
            {
                if (x.DataType != 0)
                {
                    var vecs = x.Data as Vector4[];
                    if (vecs == null)
                    {
                        vecs = new[] { (Vector4)x.Data };
                    }
                    if (vecs == null)
                    { }
                    var block = new ResourceSystemStructBlock<Vector4>(vecs);
                    list.Add(new Tuple<long, IResourceBlock>(offset, block));
                    blist.Add(block);
                }
                else
                {
                    blist.Add(null);
                }
                offset += 16 * x.DataType;
            }
            ParameterDataBlocks = blist.ToArray();

            return list.ToArray();
        }
    }



    [TypeConverter(typeof(ExpandableObjectConverter))] public class Skeleton : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 112; }
        }

        // structure data
        public uint VFT { get; set; } = 1080114336;
        public uint Unknown_4h { get; set; } = 1; // 0x00000001
        public ulong Unknown_8h; // 0x0000000000000000
        public ulong BoneTagsPointer { get; set; }
        public ushort BoneTagsCapacity { get; set; }
        public ushort BoneTagsCount { get; set; }
        public FlagsUint Unknown_1Ch { get; set; }
        public ulong BonesPointer { get; set; }
        public ulong TransformationsInvertedPointer { get; set; }
        public ulong TransformationsPointer { get; set; }
        public ulong ParentIndicesPointer { get; set; }
        public ulong ChildIndicesPointer { get; set; }
        public ulong Unknown_48h; // 0x0000000000000000
        public MetaHash Unknown_50h { get; set; }
        public MetaHash Unknown_54h { get; set; }
        public MetaHash Unknown_58h { get; set; }
        public ushort Unknown_5Ch { get; set; } = 1; // 0x0001
        public ushort BonesCount { get; set; }
        public ushort ChildIndicesCount { get; set; }
        public ushort Unknown_62h; // 0x0000
        public uint Unknown_64h; // 0x00000000
        public ulong Unknown_68h; // 0x0000000000000000

        // reference data
        public ResourcePointerArray64<SkeletonBoneTag> BoneTags { get; set; }
        public SkeletonBonesBlock Bones { get; set; }

        public Matrix[] TransformationsInverted { get; set; }
        public Matrix[] Transformations { get; set; }
        public short[] ParentIndices { get; set; }
        public short[] ChildIndices { get; set; }//mapping child->parent indices, first child index, then parent

        private ResourceSystemStructBlock<Matrix> TransformationsInvertedBlock = null;//for saving only
        private ResourceSystemStructBlock<Matrix> TransformationsBlock = null;
        private ResourceSystemStructBlock<short> ParentIndicesBlock = null;
        private ResourceSystemStructBlock<short> ChildIndicesBlock = null;


        public Dictionary<ushort, Bone> BonesMap { get; set; }//for convienience finding bones by tag


        public Matrix3_s[] BoneTransforms; //for rendering


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.Unknown_8h = reader.ReadUInt64();
            this.BoneTagsPointer = reader.ReadUInt64();
            this.BoneTagsCapacity = reader.ReadUInt16();
            this.BoneTagsCount = reader.ReadUInt16();
            this.Unknown_1Ch = reader.ReadUInt32();
            this.BonesPointer = reader.ReadUInt64();
            this.TransformationsInvertedPointer = reader.ReadUInt64();
            this.TransformationsPointer = reader.ReadUInt64();
            this.ParentIndicesPointer = reader.ReadUInt64();
            this.ChildIndicesPointer = reader.ReadUInt64();
            this.Unknown_48h = reader.ReadUInt64();
            this.Unknown_50h = new MetaHash(reader.ReadUInt32());
            this.Unknown_54h = new MetaHash(reader.ReadUInt32());
            this.Unknown_58h = new MetaHash(reader.ReadUInt32());
            this.Unknown_5Ch = reader.ReadUInt16();
            this.BonesCount = reader.ReadUInt16();
            this.ChildIndicesCount = reader.ReadUInt16();
            this.Unknown_62h = reader.ReadUInt16();
            this.Unknown_64h = reader.ReadUInt32();
            this.Unknown_68h = reader.ReadUInt64();

            // read reference data
            this.BoneTags = reader.ReadBlockAt<ResourcePointerArray64<SkeletonBoneTag>>(this.BoneTagsPointer, this.BoneTagsCapacity);
            this.Bones = reader.ReadBlockAt<SkeletonBonesBlock>((this.BonesPointer != 0) ? (BonesPointer - 16) : 0, (uint)this.BonesCount);
            this.TransformationsInverted = reader.ReadStructsAt<Matrix>(this.TransformationsInvertedPointer, this.BonesCount);
            this.Transformations = reader.ReadStructsAt<Matrix>(this.TransformationsPointer, this.BonesCount);
            this.ParentIndices = reader.ReadShortsAt(this.ParentIndicesPointer, this.BonesCount);
            this.ChildIndices = reader.ReadShortsAt(this.ChildIndicesPointer, this.ChildIndicesCount);


            AssignBoneParents();

            BuildBonesMap();

            //BuildIndices();//testing!
            //BuildBoneTags();//testing!
            //BuildTransformations();//testing!
            //if (BoneTagsCount != Math.Min(BonesCount, BoneTagsCapacity))
            //{ }//no hits

            //if (BonesPointer != 0)
            //{
            //    var bhdr = reader.ReadStructAt<ResourcePointerListHeader>((long)BonesPointer - 16);
            //    if (bhdr.Pointer != BonesCount)
            //    { }//no hit
            //    if ((bhdr.Count != 0) || (bhdr.Capacity != 0) || (bhdr.Unknown != 0))
            //    { }//no hit
            //}

            //if (Unknown_8h != 0)
            //{ }
            //if (Unknown_48h != 0)
            //{ }
            //if (Unknown_62h != 0)
            //{ }
            //if (Unknown_64h != 0)
            //{ }
            //if (Unknown_68h != 0)
            //{ }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.BoneTagsPointer = (ulong)(this.BoneTags != null ? this.BoneTags.FilePosition : 0);
            this.BoneTagsCapacity = (ushort)(this.BoneTags != null ? this.BoneTags.Count : 0);
            this.BonesPointer = (ulong)(this.Bones != null ? this.Bones.FilePosition+16 : 0);
            this.TransformationsInvertedPointer = (ulong)(this.TransformationsInvertedBlock != null ? this.TransformationsInvertedBlock.FilePosition : 0);
            this.TransformationsPointer = (ulong)(this.TransformationsBlock != null ? this.TransformationsBlock.FilePosition : 0);
            this.ParentIndicesPointer = (ulong)(this.ParentIndicesBlock != null ? this.ParentIndicesBlock.FilePosition : 0);
            this.ChildIndicesPointer = (ulong)(this.ChildIndicesBlock != null ? this.ChildIndicesBlock.FilePosition : 0);
            this.BonesCount = (ushort)(this.Bones?.Items != null ? this.Bones.Items.Length : 0);
            this.ChildIndicesCount = (ushort)(this.ChildIndicesBlock != null ? this.ChildIndicesBlock.ItemCount : 0);
            this.BoneTagsCount = Math.Min(BonesCount, BoneTagsCapacity);


            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.Unknown_8h);
            writer.Write(this.BoneTagsPointer);
            writer.Write(this.BoneTagsCapacity);
            writer.Write(this.BoneTagsCount);
            writer.Write(this.Unknown_1Ch);
            writer.Write(this.BonesPointer);
            writer.Write(this.TransformationsInvertedPointer);
            writer.Write(this.TransformationsPointer);
            writer.Write(this.ParentIndicesPointer);
            writer.Write(this.ChildIndicesPointer);
            writer.Write(this.Unknown_48h);
            writer.Write(this.Unknown_50h);
            writer.Write(this.Unknown_54h);
            writer.Write(this.Unknown_58h);
            writer.Write(this.Unknown_5Ch);
            writer.Write(this.BonesCount);
            writer.Write(this.ChildIndicesCount);
            writer.Write(this.Unknown_62h);
            writer.Write(this.Unknown_64h);
            writer.Write(this.Unknown_68h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "Unknown1C", Unknown_1Ch.Value.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown50", Unknown_50h.Hash.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown54", Unknown_54h.Hash.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown58", Unknown_58h.Hash.ToString());

            if (Bones?.Items != null)
            {
                YdrXml.WriteItemArray(sb, Bones.Items, indent, "Bones");
            }

        }
        public void ReadXml(XmlNode node)
        {
            Unknown_1Ch = Xml.GetChildUIntAttribute(node, "Unknown1C", "value");
            Unknown_50h = Xml.GetChildUIntAttribute(node, "Unknown50", "value");
            Unknown_54h = Xml.GetChildUIntAttribute(node, "Unknown54", "value");
            Unknown_58h = Xml.GetChildUIntAttribute(node, "Unknown58", "value");

            var bones = XmlMeta.ReadItemArray<Bone>(node, "Bones");
            if (bones != null)
            {
                Bones = new SkeletonBonesBlock();
                Bones.Items = bones;
            }

            BuildIndices();
            BuildBoneTags();
            AssignBoneParents();
            BuildTransformations();
            BuildBonesMap();
        }

        public override IResourceBlock[] GetReferences()
        {
            BuildTransformations();

            var list = new List<IResourceBlock>();
            if (BoneTags != null) list.Add(BoneTags);
            if (Bones != null) list.Add(Bones);
            if (TransformationsInverted != null)
            {
                TransformationsInvertedBlock = new ResourceSystemStructBlock<Matrix>(TransformationsInverted);
                list.Add(TransformationsInvertedBlock);
            }
            if (Transformations != null)
            {
                TransformationsBlock = new ResourceSystemStructBlock<Matrix>(Transformations);
                list.Add(TransformationsBlock);
            }
            if (ParentIndices != null)
            {
                ParentIndicesBlock = new ResourceSystemStructBlock<short>(ParentIndices);
                list.Add(ParentIndicesBlock);
            }
            if (ChildIndices != null)
            {
                ChildIndicesBlock = new ResourceSystemStructBlock<short>(ChildIndices);
                list.Add(ChildIndicesBlock);
            }
            return list.ToArray();
        }






        public void AssignBoneParents()
        {
            if ((Bones?.Items != null) && (ParentIndices != null))
            {
                var maxcnt = Math.Min(Bones.Items.Length, ParentIndices.Length);
                for (int i = 0; i < maxcnt; i++)
                {
                    var bone = Bones.Items[i];
                    var pind = ParentIndices[i];
                    if ((pind >= 0) && (pind < Bones.Items.Length))
                    {
                        bone.Parent = Bones.Items[pind];
                    }
                }
            }
        }

        public void BuildBonesMap()
        {
            BonesMap = new Dictionary<ushort, Bone>();
            if (Bones?.Items != null)
            {
                for (int i = 0; i < Bones.Items.Length; i++)
                {
                    var bone = Bones.Items[i];
                    BonesMap[bone.Tag] = bone;

                    bone.UpdateAnimTransform();
                    bone.AbsTransform = bone.AnimTransform;
                    bone.BindTransformInv = (i < (TransformationsInverted?.Length ?? 0)) ? TransformationsInverted[i] : Matrix.Invert(bone.AnimTransform);
                    bone.BindTransformInv.M44 = 1.0f;
                    bone.UpdateSkinTransform();
                    bone.TransformUnk = (i < (Transformations?.Length ?? 0)) ? Transformations[i].Column4 : Vector4.Zero;//still dont know what this is
                }
            }
        }

        public void BuildIndices()
        {
            var parents = new List<short>();
            var childs = new List<short>();
            if (Bones?.Items != null)
            {

                //crazy breadth-wise limited to 4 algorithm for generating the ChildIndices

                var tbones = Bones.Items.ToList();
                var rootbones = tbones.Where(b => (b.ParentIndex < 0)).ToList();
                for (int i = 0; i < tbones.Count; i++)
                {
                    var bone = Bones.Items[i];
                    var pind = bone.ParentIndex;
                    parents.Add(pind);
                }

                List<Bone> getChildren(Bone b)
                {
                    var r = new List<Bone>();
                    if (b == null) return r;
                    for (int i = 0; i < tbones.Count; i++)
                    {
                        var tb = tbones[i];
                        if (tb.ParentIndex == b.Index)
                        {
                            r.Add(tb);
                        }
                    }
                    return r;
                }
                List<Bone> getAllChildren(List<Bone> bones)
                {
                    var l = new List<Bone>();
                    foreach (var b in bones)
                    {
                        var children = getChildren(b);
                        l.AddRange(children);
                    }
                    return l;
                }
                
                var layers = new List<List<Bone>>();
                var layer = getAllChildren(rootbones);
                while (layer.Count > 0)
                {
                    var numbones = Math.Min(layer.Count, 4);
                    var inslayer = layer.GetRange(0, numbones);
                    var extlayer = getAllChildren(inslayer);
                    layers.Add(inslayer);
                    layer.RemoveRange(0, numbones);
                    layer.InsertRange(0, extlayer);
                }



                foreach (var l in layers)
                {
                    Bone lastbone = null;
                    foreach (var b in l)
                    {
                        childs.Add(b.Index);
                        childs.Add(b.ParentIndex);
                        lastbone = b;
                    }
                    if (lastbone != null)
                    {
                        var npad = 8 - (childs.Count % 8);
                        if (npad < 8)
                        {
                            for (int i = 0; i < npad; i += 2)
                            {
                                childs.Add(lastbone.Index);
                                childs.Add(lastbone.ParentIndex);
                            }
                        }
                    }
                }





                //////just testing
                //var numchilds = ChildIndices?.Length ?? 0;
                //int diffstart = -1;
                //int diffend = -1;
                //int ndiff = Math.Abs(numchilds - childs.Count);
                //int maxchilds = Math.Min(numchilds, childs.Count);
                //for (int i = 0; i < maxchilds; i++)
                //{
                //    var oc = ChildIndices[i];
                //    var nc = childs[i];
                //    if (nc != oc)
                //    {
                //        if (diffstart < 0) diffstart = i;
                //        diffend = i;
                //        ndiff++; 
                //    }
                //}
                //if (ndiff > 0)
                //{
                //    var difffrac = ((float)ndiff) / ((float)numchilds);
                //}
                //if (numchilds != childs.Count)
                //{ }




                //var numbones = Bones.Items.Length;
                //var numchilds = ChildIndices?.Length ?? 0;
                //for (int i = 0; i < numchilds; i += 2)
                //{
                //    var bind = ChildIndices[i];
                //    var pind = ChildIndices[i + 1];
                //    if (bind > numbones)
                //    { continue; }//shouldn't happen
                //    var bone = Bones.Items[bind];
                //    if (bone == null)
                //    { continue; }//shouldn't happen
                //    if (pind != bone.ParentIndex)
                //    { }//shouldn't happen?
                //}




            }

            ParentIndices = (parents.Count > 0) ? parents.ToArray() : null;
            ChildIndices = (childs.Count > 0) ? childs.ToArray() : null;

        }

        public void BuildBoneTags()
        {
            var tags = new List<SkeletonBoneTag>();
            if (Bones?.Items != null)
            {
                for (int i = 0; i < Bones.Items.Length; i++)
                {
                    var bone = Bones.Items[i];
                    var tag = new SkeletonBoneTag();
                    tag.BoneTag = bone.Tag;
                    tag.BoneIndex = (uint)i;
                    tags.Add(tag);
                }
            }

            if (tags.Count < 2)
            {
                if (BoneTags != null)
                { }
                BoneTags = null;
                return;
            }

            var numbuckets = GetNumHashBuckets(tags.Count);

            var buckets = new List<SkeletonBoneTag>[numbuckets];
            foreach (var tag in tags)
            {
                var b = tag.BoneTag % numbuckets;
                var bucket = buckets[b];
                if (bucket == null)
                {
                    bucket = new List<SkeletonBoneTag>();
                    buckets[b] = bucket;
                }
                bucket.Add(tag);
            }

            var newtags = new List<SkeletonBoneTag>();
            foreach (var b in buckets)
            {
                if ((b?.Count ?? 0) == 0) newtags.Add(null);
                else
                {
                    b.Reverse();
                    newtags.Add(b[0]);
                    var p = b[0];
                    for (int i = 1; i < b.Count; i++)
                    {
                        var c = b[i];
                        c.Next = null;
                        p.Next = c;
                        p = c;
                    }
                }
            }


            //if (BoneTags?.data_items != null) //just testing - all ok
            //{
            //    var numtags = BoneTags.data_items.Length;
            //    if (numbuckets != numtags)
            //    { }
            //    else
            //    {
            //        for (int i = 0; i < numtags; i++)
            //        {
            //            var ot = BoneTags.data_items[i];
            //            var nt = newtags[i];
            //            if ((ot == null) != (nt == null))
            //            { }
            //            else if (ot != null)
            //            {
            //                if (ot.BoneIndex != nt.BoneIndex)
            //                { }
            //                if (ot.BoneTag != nt.BoneTag)
            //                { }
            //            }
            //        }
            //    }
            //}


            BoneTags = new ResourcePointerArray64<SkeletonBoneTag>();
            BoneTags.data_items = newtags.ToArray();


        }

        public void BuildTransformations()
        {
            var transforms = new List<Matrix>();
            var transformsinv = new List<Matrix>();
            if (Bones?.Items != null)
            {
                foreach (var bone in Bones.Items)
                {
                    var pos = bone.Translation;
                    var ori = bone.Rotation;
                    var sca = bone.Scale;
                    var m = Matrix.AffineTransformation(1.0f, ori, pos);//(local transform)
                    m.ScaleVector *= sca;
                    m.Column4 = bone.TransformUnk;// new Vector4(0, 4, -3, 0);//???

                    var pbone = bone.Parent;
                    while (pbone != null)
                    {
                        pos = pbone.Rotation.Multiply(pos /** pbone.Scale*/) + pbone.Translation;
                        ori = pbone.Rotation * ori;
                        pbone = pbone.Parent;
                    }
                    var m2 = Matrix.AffineTransformation(1.0f, ori, pos);//(global transform)
                    var mi = Matrix.Invert(m2);
                    mi.Column4 = Vector4.Zero;

                    transforms.Add(m);
                    transformsinv.Add(mi);
                }
            }

            //if (Transformations != null) //just testing! - all ok
            //{
            //    if (Transformations.Length != transforms.Count)
            //    { }
            //    else
            //    {
            //        for (int i = 0; i < Transformations.Length; i++)
            //        {
            //            if (Transformations[i].Column1 != transforms[i].Column1)
            //            { }
            //            if (Transformations[i].Column2 != transforms[i].Column2)
            //            { }
            //            if (Transformations[i].Column3 != transforms[i].Column3)
            //            { }
            //            if (Transformations[i].Column4 != transforms[i].Column4)
            //            { }
            //        }
            //    }
            //    if (TransformationsInverted.Length != transformsinv.Count)
            //    { }
            //    else
            //    {
            //        for (int i = 0; i < TransformationsInverted.Length; i++)
            //        {
            //            if (TransformationsInverted[i].Column4 != transformsinv[i].Column4)
            //            { }
            //        }
            //    }
            //}

            Transformations = (transforms.Count > 0) ? transforms.ToArray() : null;
            TransformationsInverted = (transformsinv.Count > 0) ? transformsinv.ToArray() : null;

        }


        public static uint GetNumHashBuckets(int nHashes)
        {
            //todo: refactor with same in Clip.cs?
            if (nHashes < 11) return 11;
            else if (nHashes < 29) return 29;
            else if (nHashes < 59) return 59;
            else if (nHashes < 107) return 107;
            else if (nHashes < 191) return 191;
            else if (nHashes < 331) return 331;
            else if (nHashes < 563) return 563;
            else if (nHashes < 953) return 953;
            else if (nHashes < 1609) return 1609;
            else if (nHashes < 2729) return 2729;
            else if (nHashes < 4621) return 4621;
            else if (nHashes < 7841) return 7841;
            else if (nHashes < 13297) return 13297;
            else if (nHashes < 22571) return 22571;
            else if (nHashes < 38351) return 38351;
            else if (nHashes < 65167) return 65167;
            else /*if (nHashes < 65521)*/ return 65521;
            //return ((uint)nHashes / 4) * 4 + 3;
        }



        public void ResetBoneTransforms()
        {
            if (Bones?.Items == null) return;
            foreach (var bone in Bones.Items)
            {
                bone.ResetAnimTransform();
            }
            UpdateBoneTransforms();
        }
        public void UpdateBoneTransforms()
        {
            if (Bones?.Items == null) return;
            if ((BoneTransforms == null) || (BoneTransforms.Length != Bones.Items.Length))
            {
                BoneTransforms = new Matrix3_s[Bones.Items.Length];
            }
            for (int i = 0; i < Bones.Items.Length; i++)
            {
                var bone = Bones.Items[i];
                Matrix b = bone.SkinTransform;
                Matrix3_s bt = new Matrix3_s();
                bt.Row1 = b.Column1;
                bt.Row2 = b.Column2;
                bt.Row3 = b.Column3;
                BoneTransforms[i] = bt;
            }
        }






        public Skeleton Clone()
        {
            var skel = new Skeleton();

            skel.BoneTagsCapacity = BoneTagsCapacity;
            skel.BoneTagsCount = BoneTagsCount;
            skel.Unknown_1Ch = Unknown_1Ch;
            skel.Unknown_50h = Unknown_50h;
            skel.Unknown_54h = Unknown_54h;
            skel.Unknown_58h = Unknown_58h;
            skel.BonesCount = BonesCount;
            skel.ChildIndicesCount = ChildIndicesCount;

            if (BoneTags != null)
            {
                skel.BoneTags = new ResourcePointerArray64<SkeletonBoneTag>();
                if (BoneTags.data_items != null)
                {
                    skel.BoneTags.data_items = new SkeletonBoneTag[BoneTags.data_items.Length];
                    for (int i = 0; i < BoneTags.data_items.Length; i++)
                    {
                        var obt = BoneTags.data_items[i];
                        var nbt = new SkeletonBoneTag();
                        skel.BoneTags.data_items[i] = nbt;
                        while (obt != null)
                        {
                            nbt.BoneTag = obt.BoneTag;
                            nbt.BoneIndex = obt.BoneIndex;
                            obt = obt.Next;
                            if (obt != null)
                            {
                                var nxt = new SkeletonBoneTag();
                                nbt.Next = nxt;
                                nbt = nxt;
                            }
                        }
                    }
                }
            }
            if (Bones != null)
            {
                skel.Bones = new SkeletonBonesBlock();
                if (Bones.Items != null)
                {
                    skel.Bones.Items = new Bone[Bones.Items.Length];
                    for (int i = 0; i < Bones.Items.Length; i++)
                    {
                        var ob = Bones.Items[i];
                        var nb = new Bone();
                        nb.Rotation = ob.Rotation;
                        nb.Translation = ob.Translation;
                        nb.Scale = ob.Scale;
                        nb.NextSiblingIndex = ob.NextSiblingIndex;
                        nb.ParentIndex = ob.ParentIndex;
                        nb.Flags = ob.Flags;
                        nb.Index = ob.Index;
                        nb.Tag = ob.Tag;
                        nb.Index2 = ob.Index2;
                        nb.Name = ob.Name;
                        nb.AnimRotation = ob.AnimRotation;
                        nb.AnimTranslation = ob.AnimTranslation;
                        nb.AnimScale = ob.AnimScale;
                        nb.AnimTransform = ob.AnimTransform;
                        nb.BindTransformInv = ob.BindTransformInv;
                        nb.SkinTransform = ob.SkinTransform;
                        skel.Bones.Items[i] = nb;
                    }
                }
            }

            skel.TransformationsInverted = (Matrix[])TransformationsInverted?.Clone();
            skel.Transformations = (Matrix[])Transformations?.Clone();
            skel.ParentIndices = (short[])ParentIndices?.Clone();
            skel.ChildIndices = (short[])ChildIndices?.Clone();

            skel.AssignBoneParents();
            skel.BuildBonesMap();

            return skel;
        }



    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class SkeletonBonesBlock : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get
            {
                long length = 16;
                if (Items != null)
                {
                    foreach (var b in Items)
                    {
                        length += b.BlockLength;
                    }
                }
                return length;
            }
        }

        public uint Count { get; set; }
        public uint Unk0; // 0
        public uint Unk1; // 0
        public uint Unk2; // 0
        public Bone[] Items { get; set; }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            Count = reader.ReadUInt32();
            Unk0 = reader.ReadUInt32();
            Unk1 = reader.ReadUInt32();
            Unk2 = reader.ReadUInt32();

            var count = (uint)parameters[0];
            var items = new Bone[count];
            for (uint i = 0; i < count; i++)
            {
                items[i] = reader.ReadBlock<Bone>();
            }
            Items = items;


            //if (Count != count)
            //{ }//no hit
            //if (Unk0 != 0)
            //{ }//no hit
            //if (Unk1 != 0)
            //{ }//no hit
            //if (Unk2 != 0)
            //{ }//no hit

        }

        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            Count = (uint)(Items?.Length ?? 0);

            writer.Write(Count);
            writer.Write(Unk0);
            writer.Write(Unk1);
            writer.Write(Unk2);

            foreach (var b in Items)
            {
                b.Write(writer);
            }
        }

        public override Tuple<long, IResourceBlock>[] GetParts()
        {
            var list = new List<Tuple<long, IResourceBlock>>();
            long length = 16;
            if (Items != null)
            {
                foreach (var b in Items)
                {
                    list.Add(new Tuple<long, IResourceBlock>(length, b));
                    length += b.BlockLength;
                }
            }
            return list.ToArray();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class SkeletonBoneTag : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 16; }
        }

        // structure data
        public uint BoneTag { get; set; }
        public uint BoneIndex { get; set; }
        public ulong NextPointer { get; set; }

        // reference data
        public SkeletonBoneTag Next { get; set; } //don't know why it's linked here

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.BoneTag = reader.ReadUInt32();
            this.BoneIndex = reader.ReadUInt32();
            this.NextPointer = reader.ReadUInt64();

            // read reference data
            this.Next = reader.ReadBlockAt<SkeletonBoneTag>(
                this.NextPointer // offset
            );
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.NextPointer = (ulong)(this.Next != null ? this.Next.FilePosition : 0);

            // write structure data
            writer.Write(this.BoneTag);
            writer.Write(this.BoneIndex);
            writer.Write(this.NextPointer);
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (Next != null) list.Add(Next);
            return list.ToArray();
        }

        public override string ToString()
        {
            return BoneTag.ToString() + ": " + BoneIndex.ToString();
        }

    }

    [Flags] public enum EBoneFlags : ushort
    {
        None = 0,
        RotX = 0x1,
        RotY = 0x2,
        RotZ = 0x4,
        LimitRotation = 0x8,
        TransX = 0x10,
        TransY = 0x20,
        TransZ = 0x40,
        LimitTranslation = 0x80,
        ScaleX = 0x100,
        ScaleY = 0x200,
        ScaleZ = 0x400,
        LimitScale = 0x800,
        Unk0 = 0x1000,
        Unk1 = 0x2000,
        Unk2 = 0x4000,
        Unk3 = 0x8000,
    }

    // List of BoneTags which are hardcoded/not calculated using ElfHash and CalculateBoneHash
    enum EPedBoneId : ushort
    {
        SKEL_ROOT = 0x0,
        SKEL_Pelvis = 0x2E28,
        SKEL_L_Thigh = 0xE39F,
        SKEL_L_Calf = 0xF9BB,
        SKEL_L_Foot = 0x3779,
        SKEL_L_Toe0 = 0x83C,
        EO_L_Foot = 0x84C5,
        EO_L_Toe = 0x68BD,
        IK_L_Foot = 0xFEDD,
        PH_L_Foot = 0xE175,
        MH_L_Knee = 0xB3FE,
        SKEL_R_Thigh = 0xCA72,
        SKEL_R_Calf = 0x9000,
        SKEL_R_Foot = 0xCC4D,
        SKEL_R_Toe0 = 0x512D,
        EO_R_Foot = 0x1096,
        EO_R_Toe = 0x7163,
        IK_R_Foot = 0x8AAE,
        PH_R_Foot = 0x60E6,
        MH_R_Knee = 0x3FCF,
        RB_L_ThighRoll = 0x5C57,
        RB_R_ThighRoll = 0x192A,
        SKEL_Spine_Root = 0xE0FD,
        SKEL_Spine0 = 0x5C01,
        SKEL_Spine1 = 0x60F0,
        SKEL_Spine2 = 0x60F1,
        SKEL_Spine3 = 0x60F2,
        SKEL_L_Clavicle = 0xFCD9,
        SKEL_L_UpperArm = 0xB1C5,
        SKEL_L_Forearm = 0xEEEB,
        SKEL_L_Hand = 0x49D9,
        SKEL_L_Finger00 = 0x67F2,
        SKEL_L_Finger01 = 0xFF9,
        SKEL_L_Finger02 = 0xFFA,
        SKEL_L_Finger10 = 0x67F3,
        SKEL_L_Finger11 = 0x1049,
        SKEL_L_Finger12 = 0x104A,
        SKEL_L_Finger20 = 0x67F4,
        SKEL_L_Finger21 = 0x1059,
        SKEL_L_Finger22 = 0x105A,
        SKEL_L_Finger30 = 0x67F5,
        SKEL_L_Finger31 = 0x1029,
        SKEL_L_Finger32 = 0x102A,
        SKEL_L_Finger40 = 0x67F6,
        SKEL_L_Finger41 = 0x1039,
        SKEL_L_Finger42 = 0x103A,
        PH_L_Hand = 0xEB95,
        IK_L_Hand = 0x8CBD,
        RB_L_ForeArmRoll = 0xEE4F,
        RB_L_ArmRoll = 0x1470,
        MH_L_Elbow = 0x58B7,
        SKEL_R_Clavicle = 0x29D2,
        SKEL_R_UpperArm = 0x9D4D,
        SKEL_R_Forearm = 0x6E5C,
        SKEL_R_Hand = 0xDEAD,
        SKEL_R_Finger00 = 0xE5F2,
        SKEL_R_Finger01 = 0xFA10,
        SKEL_R_Finger02 = 0xFA11,
        SKEL_R_Finger10 = 0xE5F3,
        SKEL_R_Finger11 = 0xFA60,
        SKEL_R_Finger12 = 0xFA61,
        SKEL_R_Finger20 = 0xE5F4,
        SKEL_R_Finger21 = 0xFA70,
        SKEL_R_Finger22 = 0xFA71,
        SKEL_R_Finger30 = 0xE5F5,
        SKEL_R_Finger31 = 0xFA40,
        SKEL_R_Finger32 = 0xFA41,
        SKEL_R_Finger40 = 0xE5F6,
        SKEL_R_Finger41 = 0xFA50,
        SKEL_R_Finger42 = 0xFA51,
        PH_R_Hand = 0x6F06,
        IK_R_Hand = 0x188E,
        RB_R_ForeArmRoll = 0xAB22,
        RB_R_ArmRoll = 0x90FF,
        MH_R_Elbow = 0xBB0,
        SKEL_Neck_1 = 0x9995,
        SKEL_Head = 0x796E,
        IK_Head = 0x322C,
        FACIAL_facialRoot = 0xFE2C,
        FB_L_Brow_Out_000 = 0xE3DB,
        FB_L_Lid_Upper_000 = 0xB2B6,
        FB_L_Eye_000 = 0x62AC,
        FB_L_CheekBone_000 = 0x542E,
        FB_L_Lip_Corner_000 = 0x74AC,
        FB_R_Lid_Upper_000 = 0xAA10,
        FB_R_Eye_000 = 0x6B52,
        FB_R_CheekBone_000 = 0x4B88,
        FB_R_Brow_Out_000 = 0x54C,
        FB_R_Lip_Corner_000 = 0x2BA6,
        FB_Brow_Centre_000 = 0x9149,
        FB_UpperLipRoot_000 = 0x4ED2,
        FB_UpperLip_000 = 0xF18F,
        FB_L_Lip_Top_000 = 0x4F37,
        FB_R_Lip_Top_000 = 0x4537,
        FB_Jaw_000 = 0xB4A0,
        FB_LowerLipRoot_000 = 0x4324,
        FB_LowerLip_000 = 0x508F,
        FB_L_Lip_Bot_000 = 0xB93B,
        FB_R_Lip_Bot_000 = 0xC33B,
        FB_Tongue_000 = 0xB987,
        RB_Neck_1 = 0x8B93,
        SPR_L_Breast = 0xFC8E,
        SPR_R_Breast = 0x885F,
        IK_Root = 0xDD1C,
        SKEL_Neck_2 = 0x5FD4,
        SKEL_Pelvis1 = 0xD003,
        SKEL_PelvisRoot = 0x45FC,
        SKEL_SADDLE = 0x9524,
        MH_L_CalfBack = 0x1013,
        MH_L_ThighBack = 0x600D,
        SM_L_Skirt = 0xC419,
        MH_R_CalfBack = 0xB013,
        MH_R_ThighBack = 0x51A3,
        SM_R_Skirt = 0x7712,
        SM_M_BackSkirtRoll = 0xDBB,
        SM_L_BackSkirtRoll = 0x40B2,
        SM_R_BackSkirtRoll = 0xC141,
        SM_M_FrontSkirtRoll = 0xCDBB,
        SM_L_FrontSkirtRoll = 0x9B69,
        SM_R_FrontSkirtRoll = 0x86F1,
        SM_CockNBalls_ROOT = 0xC67D,
        SM_CockNBalls = 0x9D34,
        MH_L_Finger00 = 0x8C63,
        MH_L_FingerBulge00 = 0x5FB8,
        MH_L_Finger10 = 0x8C53,
        MH_L_FingerTop00 = 0xA244,
        MH_L_HandSide = 0xC78A,
        MH_Watch = 0x2738,
        MH_L_Sleeve = 0x933C,
        MH_R_Finger00 = 0x2C63,
        MH_R_FingerBulge00 = 0x69B8,
        MH_R_Finger10 = 0x2C53,
        MH_R_FingerTop00 = 0xEF4B,
        MH_R_HandSide = 0x68FB,
        MH_R_Sleeve = 0x92DC,
        FACIAL_jaw = 0xB21,
        FACIAL_underChin = 0x8A95,
        FACIAL_L_underChin = 0x234E,
        FACIAL_chin = 0xB578,
        FACIAL_chinSkinBottom = 0x98BC,
        FACIAL_L_chinSkinBottom = 0x3E8F,
        FACIAL_R_chinSkinBottom = 0x9E8F,
        FACIAL_tongueA = 0x4A7C,
        FACIAL_tongueB = 0x4A7D,
        FACIAL_tongueC = 0x4A7E,
        FACIAL_tongueD = 0x4A7F,
        FACIAL_tongueE = 0x4A80,
        FACIAL_L_tongueE = 0x35F2,
        FACIAL_R_tongueE = 0x2FF2,
        FACIAL_L_tongueD = 0x35F1,
        FACIAL_R_tongueD = 0x2FF1,
        FACIAL_L_tongueC = 0x35F0,
        FACIAL_R_tongueC = 0x2FF0,
        FACIAL_L_tongueB = 0x35EF,
        FACIAL_R_tongueB = 0x2FEF,
        FACIAL_L_tongueA = 0x35EE,
        FACIAL_R_tongueA = 0x2FEE,
        FACIAL_chinSkinTop = 0x7226,
        FACIAL_L_chinSkinTop = 0x3EB3,
        FACIAL_chinSkinMid = 0x899A,
        FACIAL_L_chinSkinMid = 0x4427,
        FACIAL_L_chinSide = 0x4A5E,
        FACIAL_R_chinSkinMid = 0xF5AF,
        FACIAL_R_chinSkinTop = 0xF03B,
        FACIAL_R_chinSide = 0xAA5E,
        FACIAL_R_underChin = 0x2BF4,
        FACIAL_L_lipLowerSDK = 0xB9E1,
        FACIAL_L_lipLowerAnalog = 0x244A,
        FACIAL_L_lipLowerThicknessV = 0xC749,
        FACIAL_L_lipLowerThicknessH = 0xC67B,
        FACIAL_lipLowerSDK = 0x7285,
        FACIAL_lipLowerAnalog = 0xD97B,
        FACIAL_lipLowerThicknessV = 0xC5BB,
        FACIAL_lipLowerThicknessH = 0xC5ED,
        FACIAL_R_lipLowerSDK = 0xA034,
        FACIAL_R_lipLowerAnalog = 0xC2D9,
        FACIAL_R_lipLowerThicknessV = 0xC6E9,
        FACIAL_R_lipLowerThicknessH = 0xC6DB,
        FACIAL_nose = 0x20F1,
        FACIAL_L_nostril = 0x7322,
        FACIAL_L_nostrilThickness = 0xC15F,
        FACIAL_noseLower = 0xE05A,
        FACIAL_L_noseLowerThickness = 0x79D5,
        FACIAL_R_noseLowerThickness = 0x7975,
        FACIAL_noseTip = 0x6A60,
        FACIAL_R_nostril = 0x7922,
        FACIAL_R_nostrilThickness = 0x36FF,
        FACIAL_noseUpper = 0xA04F,
        FACIAL_L_noseUpper = 0x1FB8,
        FACIAL_noseBridge = 0x9BA3,
        FACIAL_L_nasolabialFurrow = 0x5ACA,
        FACIAL_L_nasolabialBulge = 0xCD78,
        FACIAL_L_cheekLower = 0x6907,
        FACIAL_L_cheekLowerBulge1 = 0xE3FB,
        FACIAL_L_cheekLowerBulge2 = 0xE3FC,
        FACIAL_L_cheekInner = 0xE7AB,
        FACIAL_L_cheekOuter = 0x8161,
        FACIAL_L_eyesackLower = 0x771B,
        FACIAL_L_eyeball = 0x1744,
        FACIAL_L_eyelidLower = 0x998C,
        FACIAL_L_eyelidLowerOuterSDK = 0xFE4C,
        FACIAL_L_eyelidLowerOuterAnalog = 0xB9AA,
        FACIAL_L_eyelashLowerOuter = 0xD7F6,
        FACIAL_L_eyelidLowerInnerSDK = 0xF151,
        FACIAL_L_eyelidLowerInnerAnalog = 0x8242,
        FACIAL_L_eyelashLowerInner = 0x4CCF,
        FACIAL_L_eyelidUpper = 0x97C1,
        FACIAL_L_eyelidUpperOuterSDK = 0xAF15,
        FACIAL_L_eyelidUpperOuterAnalog = 0x67FA,
        FACIAL_L_eyelashUpperOuter = 0x27B7,
        FACIAL_L_eyelidUpperInnerSDK = 0xD341,
        FACIAL_L_eyelidUpperInnerAnalog = 0xF092,
        FACIAL_L_eyelashUpperInner = 0x9B1F,
        FACIAL_L_eyesackUpperOuterBulge = 0xA559,
        FACIAL_L_eyesackUpperInnerBulge = 0x2F2A,
        FACIAL_L_eyesackUpperOuterFurrow = 0xC597,
        FACIAL_L_eyesackUpperInnerFurrow = 0x52A7,
        FACIAL_forehead = 0x9218,
        FACIAL_L_foreheadInner = 0x843,
        FACIAL_L_foreheadInnerBulge = 0x767C,
        FACIAL_L_foreheadOuter = 0x8DCB,
        FACIAL_skull = 0x4221,
        FACIAL_foreheadUpper = 0xF7D6,
        FACIAL_L_foreheadUpperInner = 0xCF13,
        FACIAL_L_foreheadUpperOuter = 0x509B,
        FACIAL_R_foreheadUpperInner = 0xCEF3,
        FACIAL_R_foreheadUpperOuter = 0x507B,
        FACIAL_L_temple = 0xAF79,
        FACIAL_L_ear = 0x19DD,
        FACIAL_L_earLower = 0x6031,
        FACIAL_L_masseter = 0x2810,
        FACIAL_L_jawRecess = 0x9C7A,
        FACIAL_L_cheekOuterSkin = 0x14A5,
        FACIAL_R_cheekLower = 0xF367,
        FACIAL_R_cheekLowerBulge1 = 0x599B,
        FACIAL_R_cheekLowerBulge2 = 0x599C,
        FACIAL_R_masseter = 0x810,
        FACIAL_R_jawRecess = 0x93D4,
        FACIAL_R_ear = 0x1137,
        FACIAL_R_earLower = 0x8031,
        FACIAL_R_eyesackLower = 0x777B,
        FACIAL_R_nasolabialBulge = 0xD61E,
        FACIAL_R_cheekOuter = 0xD32,
        FACIAL_R_cheekInner = 0x737C,
        FACIAL_R_noseUpper = 0x1CD6,
        FACIAL_R_foreheadInner = 0xE43,
        FACIAL_R_foreheadInnerBulge = 0x769C,
        FACIAL_R_foreheadOuter = 0x8FCB,
        FACIAL_R_cheekOuterSkin = 0xB334,
        FACIAL_R_eyesackUpperInnerFurrow = 0x9FAE,
        FACIAL_R_eyesackUpperOuterFurrow = 0x140F,
        FACIAL_R_eyesackUpperInnerBulge = 0xA359,
        FACIAL_R_eyesackUpperOuterBulge = 0x1AF9,
        FACIAL_R_nasolabialFurrow = 0x2CAA,
        FACIAL_R_temple = 0xAF19,
        FACIAL_R_eyeball = 0x1944,
        FACIAL_R_eyelidUpper = 0x7E14,
        FACIAL_R_eyelidUpperOuterSDK = 0xB115,
        FACIAL_R_eyelidUpperOuterAnalog = 0xF25A,
        FACIAL_R_eyelashUpperOuter = 0xE0A,
        FACIAL_R_eyelidUpperInnerSDK = 0xD541,
        FACIAL_R_eyelidUpperInnerAnalog = 0x7C63,
        FACIAL_R_eyelashUpperInner = 0x8172,
        FACIAL_R_eyelidLower = 0x7FDF,
        FACIAL_R_eyelidLowerOuterSDK = 0x1BD,
        FACIAL_R_eyelidLowerOuterAnalog = 0x457B,
        FACIAL_R_eyelashLowerOuter = 0xBE49,
        FACIAL_R_eyelidLowerInnerSDK = 0xF351,
        FACIAL_R_eyelidLowerInnerAnalog = 0xE13,
        FACIAL_R_eyelashLowerInner = 0x3322,
        FACIAL_L_lipUpperSDK = 0x8F30,
        FACIAL_L_lipUpperAnalog = 0xB1CF,
        FACIAL_L_lipUpperThicknessH = 0x37CE,
        FACIAL_L_lipUpperThicknessV = 0x38BC,
        FACIAL_lipUpperSDK = 0x1774,
        FACIAL_lipUpperAnalog = 0xE064,
        FACIAL_lipUpperThicknessH = 0x7993,
        FACIAL_lipUpperThicknessV = 0x7981,
        FACIAL_L_lipCornerSDK = 0xB1C,
        FACIAL_L_lipCornerAnalog = 0xE568,
        FACIAL_L_lipCornerThicknessUpper = 0x7BC,
        FACIAL_L_lipCornerThicknessLower = 0xDD42,
        FACIAL_R_lipUpperSDK = 0x7583,
        FACIAL_R_lipUpperAnalog = 0x51CF,
        FACIAL_R_lipUpperThicknessH = 0x382E,
        FACIAL_R_lipUpperThicknessV = 0x385C,
        FACIAL_R_lipCornerSDK = 0xB3C,
        FACIAL_R_lipCornerAnalog = 0xEE0E,
        FACIAL_R_lipCornerThicknessUpper = 0x54C3,
        FACIAL_R_lipCornerThicknessLower = 0x2BBA,
        MH_MulletRoot = 0x3E73,
        MH_MulletScaler = 0xA1C2,
        MH_Hair_Scale = 0xC664,
        MH_Hair_Crown = 0x1675,
        SM_Torch = 0x8D6,
        FX_Light = 0x8959,
        FX_Light_Scale = 0x5038,
        FX_Light_Switch = 0xE18E,
        BagRoot = 0xAD09,
        BagPivotROOT = 0xB836,
        BagPivot = 0x4D11,
        BagBody = 0xAB6D,
        BagBone_R = 0x937,
        BagBone_L = 0x991,
        SM_LifeSaver_Front = 0x9420,
        SM_R_Pouches_ROOT = 0x2962,
        SM_R_Pouches = 0x4141,
        SM_L_Pouches_ROOT = 0x2A02,
        SM_L_Pouches = 0x4B41,
        SM_Suit_Back_Flapper = 0xDA2D,
        SPR_CopRadio = 0x8245,
        SM_LifeSaver_Back = 0x2127,
        MH_BlushSlider = 0xA0CE,
        SKEL_Tail_01 = 0x347,
        SKEL_Tail_02 = 0x348,
        MH_L_Concertina_B = 0xC988,
        MH_L_Concertina_A = 0xC987,
        MH_R_Concertina_B = 0xC8E8,
        MH_R_Concertina_A = 0xC8E7,
        MH_L_ShoulderBladeRoot = 0x8711,
        MH_L_ShoulderBlade = 0x4EAF,
        MH_R_ShoulderBladeRoot = 0x3A0A,
        MH_R_ShoulderBlade = 0x54AF,
        FB_R_Ear_000 = 0x6CDF,
        SPR_R_Ear = 0x63B6,
        FB_L_Ear_000 = 0x6439,
        SPR_L_Ear = 0x5B10,
        FB_TongueA_000 = 0x4206,
        FB_TongueB_000 = 0x4207,
        FB_TongueC_000 = 0x4208,
        SKEL_L_Toe1 = 0x1D6B,
        SKEL_R_Toe1 = 0xB23F,
        SKEL_Tail_03 = 0x349,
        SKEL_Tail_04 = 0x34A,
        SKEL_Tail_05 = 0x34B,
        SPR_Gonads_ROOT = 0xBFDE,
        SPR_Gonads = 0x1C00,
        FB_L_Brow_Out_001 = 0xE3DB,
        FB_L_Lid_Upper_001 = 0xB2B6,
        FB_L_Eye_001 = 0x62AC,
        FB_L_CheekBone_001 = 0x542E,
        FB_L_Lip_Corner_001 = 0x74AC,
        FB_R_Lid_Upper_001 = 0xAA10,
        FB_R_Eye_001 = 0x6B52,
        FB_R_CheekBone_001 = 0x4B88,
        FB_R_Brow_Out_001 = 0x54C,
        FB_R_Lip_Corner_001 = 0x2BA6,
        FB_Brow_Centre_001 = 0x9149,
        FB_UpperLipRoot_001 = 0x4ED2,
        FB_UpperLip_001 = 0xF18F,
        FB_L_Lip_Top_001 = 0x4F37,
        FB_R_Lip_Top_001 = 0x4537,
        FB_Jaw_001 = 0xB4A0,
        FB_LowerLipRoot_001 = 0x4324,
        FB_LowerLip_001 = 0x508F,
        FB_L_Lip_Bot_001 = 0xB93B,
        FB_R_Lip_Bot_001 = 0xC33B,
        FB_Tongue_001 = 0xB987
    };

    [TypeConverter(typeof(ExpandableObjectConverter))] public class Bone : ResourceSystemBlock, IMetaXmlItem
    {
        public override long BlockLength
        {
            get { return 80; }
        }

        // structure data
        public Quaternion Rotation { get; set; }
        public Vector3 Translation { get; set; }
        public uint Unknown_1Ch; // 0x00000000 RHW?
        public Vector3 Scale { get; set; }
        public float Unknown_2Ch { get; set; } = 1.0f; // 1.0  RHW?
        public short NextSiblingIndex { get; set; } //limb end index? IK chain?
        public short ParentIndex { get; set; }
        public uint Unknown_34h; // 0x00000000
        public ulong NamePointer { get; set; }
        public EBoneFlags Flags { get; set; }
        public short Index { get; set; }
        public ushort Tag { get; set; }
        public short Index2 { get; set; }//always same as Index
        public ulong Unknown_48h; // 0x0000000000000000

        // reference data
        public string Name { get; set; }

        public Bone Parent { get; set; }

        private string_r NameBlock = null;


        //used by CW for animating skeletons.
        public Quaternion AnimRotation;//relative to parent
        public Vector3 AnimTranslation;//relative to parent
        public Vector3 AnimScale;
        public Matrix AnimTransform;//absolute world transform, animated
        public Matrix BindTransformInv;//inverse of bind pose transform
        public Matrix SkinTransform;//transform to use for skin meshes
        public Matrix AbsTransform;//original absolute transform from loaded file, calculated from bones hierarchy
        public Vector4 TransformUnk { get; set; } //unknown value (column 4) from skeleton's transform array, used for IO purposes

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.Rotation = new Quaternion(reader.ReadVector4());
            this.Translation = reader.ReadVector3();
            this.Unknown_1Ch = reader.ReadUInt32();
            this.Scale = reader.ReadVector3();
            this.Unknown_2Ch = reader.ReadSingle();
            this.NextSiblingIndex = reader.ReadInt16();
            this.ParentIndex = reader.ReadInt16();
            this.Unknown_34h = reader.ReadUInt32();
            this.NamePointer = reader.ReadUInt64();
            this.Flags = (EBoneFlags)reader.ReadUInt16();
            this.Index = reader.ReadInt16();
            this.Tag = reader.ReadUInt16();
            this.Index2 = reader.ReadInt16();
            this.Unknown_48h = reader.ReadUInt64();

            // read reference data
            this.Name = reader.ReadStringAt(//BlockAt<string_r>(
                this.NamePointer // offset
            );

            //if (Index2 != Index)
            //{ }//no hits

            AnimRotation = Rotation;
            AnimTranslation = Translation;
            AnimScale = Scale;


            //if (Unknown_1Ch != 0)
            //{ }
            //if (Unknown_34h != 0)
            //{ }
            //if (Unknown_48h != 0)
            //{ }

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.NamePointer = (ulong)(this.NameBlock != null ? this.NameBlock.FilePosition : 0);

            // write structure data
            writer.Write(this.Rotation.ToVector4());
            writer.Write(this.Translation);
            writer.Write(this.Unknown_1Ch);
            writer.Write(this.Scale);
            writer.Write(this.Unknown_2Ch);
            writer.Write(this.NextSiblingIndex);
            writer.Write(this.ParentIndex);
            writer.Write(this.Unknown_34h);
            writer.Write(this.NamePointer);
            writer.Write((ushort)this.Flags);
            writer.Write(this.Index);
            writer.Write(this.Tag);
            writer.Write(this.Index2);
            writer.Write(this.Unknown_48h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.StringTag(sb, indent, "Name", Name);
            YdrXml.ValueTag(sb, indent, "Tag", Tag.ToString());
            YdrXml.ValueTag(sb, indent, "Index", Index.ToString());
            YdrXml.ValueTag(sb, indent, "ParentIndex", ParentIndex.ToString());
            YdrXml.ValueTag(sb, indent, "SiblingIndex", NextSiblingIndex.ToString());
            YdrXml.StringTag(sb, indent, "Flags", Flags.ToString());
            YdrXml.SelfClosingTag(sb, indent, "Translation " + FloatUtil.GetVector3XmlString(Translation));
            YdrXml.SelfClosingTag(sb, indent, "Rotation " + FloatUtil.GetVector4XmlString(Rotation.ToVector4()));
            YdrXml.SelfClosingTag(sb, indent, "Scale " + FloatUtil.GetVector3XmlString(Scale));
            YdrXml.SelfClosingTag(sb, indent, "TransformUnk " + FloatUtil.GetVector4XmlString(TransformUnk));
        }
        public void ReadXml(XmlNode node)
        {
            Name = Xml.GetChildInnerText(node, "Name");
            Tag = (ushort)Xml.GetChildUIntAttribute(node, "Tag", "value");
            Index = (short)Xml.GetChildIntAttribute(node, "Index", "value");
            Index2 = Index;
            ParentIndex = (short)Xml.GetChildIntAttribute(node, "ParentIndex", "value");
            NextSiblingIndex = (short)Xml.GetChildIntAttribute(node, "SiblingIndex", "value");
            Flags = Xml.GetChildEnumInnerText<EBoneFlags>(node, "Flags");
            Translation = Xml.GetChildVector3Attributes(node, "Translation");
            Rotation = Xml.GetChildVector4Attributes(node, "Rotation").ToQuaternion();
            Scale = Xml.GetChildVector3Attributes(node, "Scale");
            TransformUnk = Xml.GetChildVector4Attributes(node, "TransformUnk");
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (Name != null)
            {
                NameBlock = (string_r)Name;
                list.Add(NameBlock);
            }
            return list.ToArray();
        }

        public override string ToString()
        {
            return Tag.ToString() + ": " + Name;
        }


        public void UpdateAnimTransform()
        {
            //AnimTransform = Matrix.AffineTransformation(1.0f, AnimRotation, AnimTranslation);//(local transform)
            var pos = AnimTranslation;
            var ori = AnimRotation;
            var sca = AnimScale;
            var pbone = Parent;
            while (pbone != null)
            {
                pos = pbone.AnimRotation.Multiply(pos /** pbone.AnimScale*/) + pbone.AnimTranslation;
                ori = pbone.AnimRotation * ori;
                pbone = pbone.Parent;
            }
            AnimTransform = Matrix.AffineTransformation(1.0f, ori, pos);//(global transform)
            AnimTransform.ScaleVector *= sca;
        }
        public void UpdateSkinTransform()
        {
            SkinTransform = BindTransformInv * AnimTransform;
            //SkinTransform = Matrix.Identity;//(for testing)
        }

        public void ResetAnimTransform()
        {
            AnimRotation = Rotation;
            AnimTranslation = Translation;
            AnimScale = Scale;
            UpdateAnimTransform();
            UpdateSkinTransform();
        }

        public static uint ElfHash_Uppercased(string str)
        {
            uint hash = 0;
            uint x = 0;
            uint i = 0;

            for (i = 0; i < str.Length; i++)
            {
                var c = ((byte)str[(int)i]);
                if ((byte)(c - 'a') <= 25u) // to uppercase
                    c -= 32;

                hash = (hash << 4) + c;

                if ((x = hash & 0xF0000000) != 0)
                {
                    hash ^= (x >> 24);
                }

                hash &= ~x;
            }

            return hash;
        }
        public static ushort CalculateBoneHash(string boneName)
        {
            return (ushort)(ElfHash_Uppercased(boneName) % 0xFE8F + 0x170);
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class Joints : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 64; }
        }

        // structure data
        public uint VFT { get; set; } = 1080130656;
        public uint Unknown_4h = 1; // 0x00000001
        public ulong Unknown_8h; // 0x0000000000000000
        public ulong RotationLimitsPointer { get; set; }
        public ulong TranslationLimitsPointer { get; set; }
        public ulong Unknown_20h; // 0x0000000000000000
        public ulong Unknown_28h; // 0x0000000000000000
        public ushort RotationLimitsCount { get; set; }
        public ushort TranslationLimitsCount { get; set; }
        public ushort Unknown_34h; // 0x0000
        public ushort Unknown_36h = 1; // 0x0001
        public ulong Unknown_38h; // 0x0000000000000000

        // reference data
        public JointRotationLimit_s[] RotationLimits { get; set; }
        public JointTranslationLimit_s[] TranslationLimits { get; set; }

        private ResourceSystemStructBlock<JointRotationLimit_s> RotationLimitsBlock = null; //for saving only
        private ResourceSystemStructBlock<JointTranslationLimit_s> TranslationLimitsBlock = null;


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.Unknown_8h = reader.ReadUInt64();
            this.RotationLimitsPointer = reader.ReadUInt64();
            this.TranslationLimitsPointer = reader.ReadUInt64();
            this.Unknown_20h = reader.ReadUInt64();
            this.Unknown_28h = reader.ReadUInt64();
            this.RotationLimitsCount = reader.ReadUInt16();
            this.TranslationLimitsCount = reader.ReadUInt16();
            this.Unknown_34h = reader.ReadUInt16();
            this.Unknown_36h = reader.ReadUInt16();
            this.Unknown_38h = reader.ReadUInt64();

            // read reference data
            this.RotationLimits = reader.ReadStructsAt<JointRotationLimit_s>(this.RotationLimitsPointer, this.RotationLimitsCount);
            this.TranslationLimits = reader.ReadStructsAt<JointTranslationLimit_s>(this.TranslationLimitsPointer, this.TranslationLimitsCount);

            //if (Unknown_4h != 1)
            //{ }
            //if (Unknown_8h != 0)
            //{ }
            //if (Unknown_20h != 0)
            //{ }
            //if (Unknown_28h != 0)
            //{ }
            //if (Unknown_34h != 0)
            //{ }
            //if (Unknown_36h != 1)
            //{ }
            //if (Unknown_38h != 0)
            //{ }


        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.RotationLimitsPointer = (ulong)(this.RotationLimitsBlock != null ? this.RotationLimitsBlock.FilePosition : 0);
            this.TranslationLimitsPointer = (ulong)(this.TranslationLimitsBlock != null ? this.TranslationLimitsBlock.FilePosition : 0);
            this.RotationLimitsCount = (ushort)(this.RotationLimitsBlock != null ? this.RotationLimitsBlock.ItemCount : 0);
            this.TranslationLimitsCount = (ushort)(this.TranslationLimitsBlock != null ? this.TranslationLimitsBlock.ItemCount : 0);


            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.Unknown_8h);
            writer.Write(this.RotationLimitsPointer);
            writer.Write(this.TranslationLimitsPointer);
            writer.Write(this.Unknown_20h);
            writer.Write(this.Unknown_28h);
            writer.Write(this.RotationLimitsCount);
            writer.Write(this.TranslationLimitsCount);
            writer.Write(this.Unknown_34h);
            writer.Write(this.Unknown_36h);
            writer.Write(this.Unknown_38h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            if (RotationLimits != null)
            {
                YdrXml.WriteItemArray(sb, RotationLimits, indent, "RotationLimits");
            }
            if (TranslationLimits != null)
            {
                YdrXml.WriteItemArray(sb, TranslationLimits, indent, "TranslationLimits");
            }
        }
        public void ReadXml(XmlNode node)
        {
            RotationLimits = XmlMeta.ReadItemArray<JointRotationLimit_s>(node, "RotationLimits");
            TranslationLimits = XmlMeta.ReadItemArray<JointTranslationLimit_s>(node, "TranslationLimits");
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (RotationLimits != null)
            {
                RotationLimitsBlock = new ResourceSystemStructBlock<JointRotationLimit_s>(RotationLimits);
                list.Add(RotationLimitsBlock);
            }
            if (TranslationLimits != null)
            {
                TranslationLimitsBlock = new ResourceSystemStructBlock<JointTranslationLimit_s>(TranslationLimits);
                list.Add(TranslationLimitsBlock);
            }
            return list.ToArray();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public struct JointRotationLimit_s : IMetaXmlItem
    {
        // structure data
        public uint Unknown_0h { get; set; } // 0x00000000
        public uint Unknown_4h { get; set; } // 0x00000000
        public ushort BoneId { get; set; }
        public ushort Unknown_Ah { get; set; }
        public uint Unknown_Ch { get; set; } // 0x00000001
        public uint Unknown_10h { get; set; } // 0x00000003
        public uint Unknown_14h { get; set; } // 0x00000000
        public uint Unknown_18h { get; set; } // 0x00000000
        public uint Unknown_1Ch { get; set; } // 0x00000000
        public uint Unknown_20h { get; set; } // 0x00000000
        public uint Unknown_24h { get; set; } // 0x00000000
        public uint Unknown_28h { get; set; } // 0x00000000
        public float Unknown_2Ch { get; set; } // 1.0
        public uint Unknown_30h { get; set; } // 0x00000000
        public uint Unknown_34h { get; set; } // 0x00000000
        public uint Unknown_38h { get; set; } // 0x00000000
        public uint Unknown_3Ch { get; set; } // 0x00000000
        public float Unknown_40h { get; set; } // 1.0
        public uint Unknown_44h { get; set; } // 0x00000000
        public uint Unknown_48h { get; set; } // 0x00000000
        public uint Unknown_4Ch { get; set; } // 0x00000000
        public float Unknown_50h { get; set; } // -pi
        public float Unknown_54h { get; set; } // pi
        public float Unknown_58h { get; set; } // 1.0
        public Vector3 Min { get; set; }
        public Vector3 Max { get; set; }
        public float Unknown_74h { get; set; } // pi
        public float Unknown_78h { get; set; } // -pi
        public float Unknown_7Ch { get; set; } // pi
        public float Unknown_80h { get; set; } // pi
        public float Unknown_84h { get; set; } // -pi
        public float Unknown_88h { get; set; } // pi
        public float Unknown_8Ch { get; set; } // pi
        public float Unknown_90h { get; set; } // -pi
        public float Unknown_94h { get; set; } // pi
        public float Unknown_98h { get; set; } // pi
        public float Unknown_9Ch { get; set; } // -pi
        public float Unknown_A0h { get; set; } // pi
        public float Unknown_A4h { get; set; } // pi
        public float Unknown_A8h { get; set; } // -pi
        public float Unknown_ACh { get; set; } // pi
        public float Unknown_B0h { get; set; } // pi
        public float Unknown_B4h { get; set; } // -pi
        public float Unknown_B8h { get; set; } // pi
        public uint Unknown_BCh { get; set; } // 0x00000100

        private void Init()
        {
            var pi = (float)Math.PI;
            Unknown_0h = 0;
            Unknown_4h = 0;
            BoneId = 0;
            Unknown_Ah = 0;
            Unknown_Ch = 1;
            Unknown_10h = 3;
            Unknown_14h = 0;
            Unknown_18h = 0;
            Unknown_1Ch = 0;
            Unknown_20h = 0;
            Unknown_24h = 0;
            Unknown_28h = 0;
            Unknown_2Ch = 1.0f;
            Unknown_30h = 0;
            Unknown_34h = 0;
            Unknown_38h = 0;
            Unknown_3Ch = 0;
            Unknown_40h = 1.0f;
            Unknown_44h = 0;
            Unknown_48h = 0;
            Unknown_4Ch = 0;
            Unknown_50h = -pi;
            Unknown_54h = pi;
            Unknown_58h = 1.0f;
            Min = Vector3.Zero;
            Max = Vector3.Zero;
            Unknown_74h = pi;
            Unknown_78h = -pi;
            Unknown_7Ch = pi;
            Unknown_80h = pi;
            Unknown_84h = -pi;
            Unknown_88h = pi;
            Unknown_8Ch = pi;
            Unknown_90h = -pi;
            Unknown_94h = pi;
            Unknown_98h = pi;
            Unknown_9Ch = -pi;
            Unknown_A0h = pi;
            Unknown_A4h = pi;
            Unknown_A8h = -pi;
            Unknown_ACh = pi;
            Unknown_B0h = pi;
            Unknown_B4h = -pi;
            Unknown_B8h = pi;
            Unknown_BCh = 0x100;
        }

        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "BoneId", BoneId.ToString());
            YdrXml.ValueTag(sb, indent, "UnknownA", Unknown_Ah.ToString());
            YdrXml.SelfClosingTag(sb, indent, "Min " + FloatUtil.GetVector3XmlString(Min));
            YdrXml.SelfClosingTag(sb, indent, "Max " + FloatUtil.GetVector3XmlString(Max));
        }
        public void ReadXml(XmlNode node)
        {
            Init();
            BoneId = (ushort)Xml.GetChildUIntAttribute(node, "BoneId", "value");
            Unknown_Ah = (ushort)Xml.GetChildUIntAttribute(node, "UnknownA", "value");
            Min = Xml.GetChildVector3Attributes(node, "Min");
            Max = Xml.GetChildVector3Attributes(node, "Max");
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public struct JointTranslationLimit_s : IMetaXmlItem
    {
        public uint Unknown_0h { get; set; } // 0x00000000
        public uint Unknown_4h { get; set; } // 0x00000000
        public uint BoneId { get; set; }
        public uint Unknown_Ch { get; set; } // 0x00000000
        public uint Unknown_10h { get; set; } // 0x00000000
        public uint Unknown_14h { get; set; } // 0x00000000
        public uint Unknown_18h { get; set; } // 0x00000000
        public uint Unknown_1Ch { get; set; } // 0x00000000
        public Vector3 Min { get; set; }
        public uint Unknown_2Ch { get; set; } // 0x00000000
        public Vector3 Max { get; set; }
        public uint Unknown_3Ch { get; set; } // 0x00000000

        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "BoneId", BoneId.ToString());
            YdrXml.SelfClosingTag(sb, indent, "Min " + FloatUtil.GetVector3XmlString(Min));
            YdrXml.SelfClosingTag(sb, indent, "Max " + FloatUtil.GetVector3XmlString(Max));
        }
        public void ReadXml(XmlNode node)
        {
            BoneId = (ushort)Xml.GetChildUIntAttribute(node, "BoneId", "value");
            Min = Xml.GetChildVector3Attributes(node, "Min");
            Max = Xml.GetChildVector3Attributes(node, "Max");
        }
    }





    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableModelsBlock : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get
            {
                long len = 0;
                len += ListLength(High, len);
                len += ListLength(Med, len);
                len += ListLength(Low, len);
                len += ListLength(VLow, len);
                len += ListLength(Extra, len);
                return len;
            }
        }

        public DrawableBase Owner;

        public DrawableModel[] High { get; set; }
        public DrawableModel[] Med { get; set; }
        public DrawableModel[] Low { get; set; }
        public DrawableModel[] VLow { get; set; }
        public DrawableModel[] Extra { get; set; } //shouldn't be used

        public ResourcePointerListHeader HighHeader { get; set; }
        public ResourcePointerListHeader MedHeader { get; set; }
        public ResourcePointerListHeader LowHeader { get; set; }
        public ResourcePointerListHeader VLowHeader { get; set; }
        public ResourcePointerListHeader ExtraHeader { get; set; }

        public ulong[] HighPointers { get; set; }
        public ulong[] MedPointers { get; set; }
        public ulong[] LowPointers { get; set; }
        public ulong[] VLowPointers { get; set; }
        public ulong[] ExtraPointers { get; set; }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            Owner = parameters[0] as DrawableBase;
            var pos = (ulong)reader.Position;
            var highPointer = (Owner?.DrawableModelsHighPointer ?? 0);
            var medPointer = (Owner?.DrawableModelsMediumPointer ?? 0);
            var lowPointer = (Owner?.DrawableModelsLowPointer ?? 0);
            var vlowPointer = (Owner?.DrawableModelsVeryLowPointer ?? 0);
            var extraPointer = (pos != highPointer) ? pos : 0;

            if (highPointer != 0)
            {
                HighHeader = reader.ReadStructAt<ResourcePointerListHeader>((long)highPointer);
                HighPointers = reader.ReadUlongsAt(HighHeader.Pointer, HighHeader.Capacity, false);
                High = reader.ReadBlocks<DrawableModel>(HighPointers);
            }
            if (medPointer != 0)
            {
                MedHeader = reader.ReadStructAt<ResourcePointerListHeader>((long)medPointer);
                MedPointers = reader.ReadUlongsAt(MedHeader.Pointer, MedHeader.Capacity, false);
                Med = reader.ReadBlocks<DrawableModel>(MedPointers);
            }
            if (lowPointer != 0)
            {
                LowHeader = reader.ReadStructAt<ResourcePointerListHeader>((long)lowPointer);
                LowPointers = reader.ReadUlongsAt(LowHeader.Pointer, LowHeader.Capacity, false);
                Low = reader.ReadBlocks<DrawableModel>(LowPointers);
            }
            if (vlowPointer != 0)
            {
                VLowHeader = reader.ReadStructAt<ResourcePointerListHeader>((long)vlowPointer);
                VLowPointers = reader.ReadUlongsAt(VLowHeader.Pointer, VLowHeader.Capacity, false);
                VLow = reader.ReadBlocks<DrawableModel>(VLowPointers);
            }
            if (extraPointer != 0)
            {
                ExtraHeader = reader.ReadStructAt<ResourcePointerListHeader>((long)extraPointer);
                ExtraPointers = reader.ReadUlongsAt(ExtraHeader.Pointer, ExtraHeader.Capacity, false);
                Extra = reader.ReadBlocks<DrawableModel>(ExtraPointers);
            }
        }

        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {

            ResourcePointerListHeader makeHeader(ref long p, int c)
            {
                p += Pad(p);
                var h = new ResourcePointerListHeader() { Pointer = (ulong)(p + 16), Count = (ushort)c, Capacity = (ushort)c };
                p += HeaderLength(c);
                return h;
            }
            ulong[] makePointers(ref long p, DrawableModel[] a)
            {
                var ptrs = new ulong[a.Length];
                for (int i = 0; i < a.Length; i++)
                {
                    p += Pad(p);
                    ptrs[i] = (ulong)p;
                    p += a[i].BlockLength;
                }
                return ptrs;
            }
            void write(ResourcePointerListHeader h, ulong[] p, DrawableModel[] a)
            {
                writer.WritePadding(16);
                writer.WriteStruct(h);
                writer.WriteUlongs(p);
                for (int i = 0; i < a.Length; i++)
                {
                    writer.WritePadding(16);
                    writer.WriteBlock(a[i]);
                }
            }

            var ptr = writer.Position;
            if (High != null)
            {
                HighHeader = makeHeader(ref ptr, High.Length);
                HighPointers = makePointers(ref ptr, High);
                write(HighHeader, HighPointers, High);
            }
            if (Med != null)
            {
                MedHeader = makeHeader(ref ptr, Med.Length);
                MedPointers = makePointers(ref ptr, Med);
                write(MedHeader, MedPointers, Med);
            }
            if (Low != null)
            {
                LowHeader = makeHeader(ref ptr, Low.Length);
                LowPointers = makePointers(ref ptr, Low);
                write(LowHeader, LowPointers, Low);
            }
            if (VLow != null)
            {
                VLowHeader = makeHeader(ref ptr, VLow.Length);
                VLowPointers = makePointers(ref ptr, VLow);
                write(VLowHeader, VLowPointers, VLow);
            }
            if (Extra != null)
            {
                ExtraHeader = makeHeader(ref ptr, Extra.Length);
                ExtraPointers = makePointers(ref ptr, Extra);
                write(ExtraHeader, ExtraPointers, Extra);
            }

        }


        private long Pad(long o) => ((16 - (o % 16)) % 16);
        private long HeaderLength(int listlength) => 16 + ((listlength) * 8);
        private long ListLength(DrawableModel[] list, long o)
        {
            if (list == null) return 0;
            long l = 0;
            l += HeaderLength(list.Length);
            foreach (var m in list) l += Pad(l) + m.BlockLength;
            return Pad(o) + l;
        }


        public override Tuple<long, IResourceBlock>[] GetParts()
        {
            var parts = new List<Tuple<long, IResourceBlock>>();
            parts.AddRange(base.GetParts());

            void addParts(ref long p, DrawableModel[] a)
            {
                if (a == null) return;
                p += Pad(p);
                p += HeaderLength(a.Length);
                foreach (var m in a)
                {
                    p += Pad(p);
                    parts.Add(new Tuple<long, IResourceBlock>(p, m));
                    p += m.BlockLength;
                }
            }

            var ptr = (long)0;
            addParts(ref ptr, High);
            addParts(ref ptr, Med);
            addParts(ref ptr, Low);
            addParts(ref ptr, VLow);
            addParts(ref ptr, Extra);

            return parts.ToArray();
        }


        public long GetHighPointer()
        {
            if (High == null) return 0;
            return FilePosition;
        }
        public long GetMedPointer()
        {
            if (Med == null) return 0;
            var p = FilePosition;
            p += ListLength(High, p);
            p += Pad(p);
            return p;
        }
        public long GetLowPointer()
        {
            if (Low == null) return 0;
            var p = GetMedPointer();
            p += ListLength(Med, p);
            p += Pad(p);
            return p;
        }
        public long GetVLowPointer()
        {
            if (VLow == null) return 0;
            var p = GetLowPointer();
            p += ListLength(Low, p);
            p += Pad(p);
            return p;
        }
        public long GetExtraPointer()
        {
            if (Extra == null) return 0;
            var p = GetVLowPointer();
            p += ListLength(VLow, p);
            p += Pad(p);
            return p;
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableModel : ResourceSystemBlock, IMetaXmlItem
    {
        public override long BlockLength
        {
            get 
            {
                var off = (long)48;
                off += (GeometriesCount1 * 2); //ShaderMapping
                if (GeometriesCount1 == 1) off += 6;
                else off += ((16 - (off % 16)) % 16);
                off += (GeometriesCount1 * 8); //Geometries pointers
                off += ((16 - (off % 16)) % 16);
                off += (GeometriesCount1 + ((GeometriesCount1 > 1) ? 1 : 0)) * 32; //BoundsData
                for (int i = 0; i < GeometriesCount1; i++)
                {
                    var geom = (Geometries != null) ? Geometries[i] : null;
                    if (geom != null)
                    {
                        off += ((16 - (off % 16)) % 16);
                        off += geom.BlockLength; //Geometries
                    }
                }
                return off;
            }
        }

        // structure data
        public uint VFT { get; set; } = 1080101528;
        public uint Unknown_4h = 1; // 0x00000001
        public ulong GeometriesPointer { get; set; }
        public ushort GeometriesCount1 { get; set; }
        public ushort GeometriesCount2 { get; set; }//always equal to GeometriesCount1
        public uint Unknown_14h; // 0x00000000
        public ulong BoundsPointer { get; set; }
        public ulong ShaderMappingPointer { get; set; }
        public uint SkeletonBinding { get; set; }//4th byte is bone index, 2nd byte for skin meshes
        public ushort RenderMaskFlags { get; set; } //First byte is called "Mask" in GIMS EVO
        public ushort GeometriesCount3 { get; set; } //always equal to GeometriesCount1, is it ShaderMappingCount?
        public ushort[] ShaderMapping { get; set; }
        public ulong[] GeometryPointers { get; set; }
        public AABB_s[] BoundsData { get; set; }
        public DrawableGeometry[] Geometries { get; set; }

        public byte BoneIndex
        {
            get  { return (byte)((SkeletonBinding >> 24) & 0xFF); }
            set { SkeletonBinding = (SkeletonBinding & 0x00FFFFFF) + ((value & 0xFFu) << 24); }
        }
        public byte SkeletonBindUnk2 //always 0
        {
            get { return (byte)((SkeletonBinding >> 16) & 0xFF); }
            set { SkeletonBinding = (SkeletonBinding & 0xFF00FFFF) + ((value & 0xFFu) << 16); }
        }
        public byte HasSkin //only 0 or 1
        {
            get { return (byte)((SkeletonBinding >> 8) & 0xFF); }
            set { SkeletonBinding = (SkeletonBinding & 0xFFFF00FF) + ((value & 0xFFu) << 8); }
        }
        public byte SkeletonBindUnk1 //only 0 or 43 (in rare cases, see below)
        {
            get { return (byte)((SkeletonBinding >> 0) & 0xFF); }
            set { SkeletonBinding = (SkeletonBinding & 0xFFFFFF00) + ((value & 0xFFu) << 0); }
        }

        public byte RenderMask
        {
            get { return (byte)((RenderMaskFlags >> 0) & 0xFF); }
            set { RenderMaskFlags = (ushort)((RenderMaskFlags & 0xFF00u) + ((value & 0xFFu) << 0)); }
        }
        public byte Flags
        {
            get { return (byte)((RenderMaskFlags >> 8) & 0xFF); }
            set { RenderMaskFlags = (ushort)((RenderMaskFlags & 0xFFu) + ((value & 0xFFu) << 8)); }
        }






        public long MemoryUsage
        {
            get
            {
                long val = 0;
                if (Geometries != null)
                {
                    foreach(var geom in Geometries)
                    {
                        if (geom == null) continue;
                        if (geom.VertexData != null)
                        {
                            val += geom.VertexData.MemoryUsage;
                        }
                        if (geom.IndexBuffer != null)
                        {
                            val += geom.IndexBuffer.IndicesCount * 4;
                        }
                        if (geom.VertexBuffer != null)
                        {
                            if ((geom.VertexBuffer.Data1 != null) && (geom.VertexBuffer.Data1 != geom.VertexData))
                            {
                                val += geom.VertexBuffer.Data1.MemoryUsage;
                            }
                            if ((geom.VertexBuffer.Data2 != null) && (geom.VertexBuffer.Data2 != geom.VertexData))
                            {
                                val += geom.VertexBuffer.Data2.MemoryUsage;
                            }
                        }
                    }
                }
                if (BoundsData != null)
                {
                    val += BoundsData.Length * 32;
                }
                return val;
            }
        }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.GeometriesPointer = reader.ReadUInt64();
            this.GeometriesCount1 = reader.ReadUInt16();
            this.GeometriesCount2 = reader.ReadUInt16();
            this.Unknown_14h = reader.ReadUInt32();
            this.BoundsPointer = reader.ReadUInt64();
            this.ShaderMappingPointer = reader.ReadUInt64();
            this.SkeletonBinding = reader.ReadUInt32();
            this.RenderMaskFlags = reader.ReadUInt16();
            this.GeometriesCount3 = reader.ReadUInt16();

            this.ShaderMapping = reader.ReadUshortsAt(this.ShaderMappingPointer, this.GeometriesCount1, false);
            this.GeometryPointers = reader.ReadUlongsAt(this.GeometriesPointer, this.GeometriesCount1, false);
            this.BoundsData = reader.ReadStructsAt<AABB_s>(this.BoundsPointer, (uint)(this.GeometriesCount1 > 1 ? this.GeometriesCount1 + 1 : this.GeometriesCount1), false);
            this.Geometries = reader.ReadBlocks<DrawableGeometry>(this.GeometryPointers);

            if (Geometries != null)
            {
                for (int i = 0; i < Geometries.Length; i++)
                {
                    var geom = Geometries[i];
                    if (geom != null)
                    {
                        geom.ShaderID = ((ShaderMapping != null) && (i < ShaderMapping.Length)) ? ShaderMapping[i] : (ushort)0;
                        geom.AABB = (BoundsData != null) ? ((BoundsData.Length > 1) && ((i + 1) < BoundsData.Length)) ? BoundsData[i + 1] : BoundsData[0] : new AABB_s();
                    }
                }
            }


            ////just testing!

            //var pos = (ulong)reader.Position;
            //var off = (ulong)0;
            //if (ShaderMappingPointer != (pos + off))
            //{ }//no hit
            //off += (ulong)(GeometriesCount1 * 2); //ShaderMapping
            //if (GeometriesCount1 == 1) off += 6;
            //else off += ((16 - (off % 16)) % 16);
            //if (GeometriesPointer != (pos + off))
            //{ }//no hit
            //off += (ulong)(GeometriesCount1 * 8); //Geometries pointers
            //off += ((16 - (off % 16)) % 16);
            //if (BoundsPointer != (pos + off))
            //{ }//no hit
            //off += (ulong)((GeometriesCount1 + ((GeometriesCount1 > 1) ? 1 : 0)) * 32); //BoundsData
            //if ((GeometryPointers != null) && (Geometries != null))
            //{
            //    for (int i = 0; i < GeometriesCount1; i++)
            //    {
            //        var geomptr = GeometryPointers[i];
            //        var geom = Geometries[i];
            //        if (geom != null)
            //        {
            //            off += ((16 - (off % 16)) % 16);
            //            if (geomptr != (pos + off))
            //            { }//no hit
            //            off += (ulong)geom.BlockLength;
            //        }
            //        else
            //        { }//no hit
            //    }
            //}
            //else
            //{ }//no hit

            //if (SkeletonBindUnk2 != 0)
            //{ }//no hit
            //switch (SkeletonBindUnk1)
            //{
            //    case 0:
            //        break;
            //    case 43://des_plog_light_root.ydr, des_heli_scrapyard_skin002.ydr, v_74_it1_ceiling_smoke_02_skin.ydr, buzzard2.yft, vader.yft, zombiea.yft
            //        break;
            //    default:
            //        break;//no hit
            //}
            //switch (HasSkin)
            //{
            //    case 0:
            //    case 1:
            //        break;
            //    default:
            //        break;//no hit
            //}
            //if (Unknown_4h != 1)
            //{ }//no hit
            //if (Unknown_14h != 0)
            //{ }//no hit
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.GeometriesCount1 = (ushort)(this.Geometries != null ? this.Geometries.Length : 0);
            this.GeometriesCount2 = this.GeometriesCount1;//is this correct?
            this.GeometriesCount3 = this.GeometriesCount1;//is this correct?
            
            long pad(long o) => ((16 - (o % 16)) % 16);
            var off = writer.Position + 48;
            this.ShaderMappingPointer = (ulong)off;
            off += (GeometriesCount1 * 2); //ShaderMapping
            if (GeometriesCount1 == 1) off += 6;
            else off += pad(off);
            this.GeometriesPointer = (ulong)off;
            off += (GeometriesCount1 * 8); //Geometries pointers
            off += pad(off);
            this.BoundsPointer = (ulong)off;
            off += (BoundsData.Length) * 32; //BoundsData
            this.GeometryPointers = new ulong[GeometriesCount1];
            for (int i = 0; i < GeometriesCount1; i++)
            {
                var geom = (Geometries != null) ? Geometries[i] : null;
                if (geom != null)
                {
                    off += pad(off);
                    this.GeometryPointers[i] = (ulong)off;
                    off += geom.BlockLength; //Geometries
                }
            }



            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.GeometriesPointer);
            writer.Write(this.GeometriesCount1);
            writer.Write(this.GeometriesCount2);
            writer.Write(this.Unknown_14h);
            writer.Write(this.BoundsPointer);
            writer.Write(this.ShaderMappingPointer);
            writer.Write(this.SkeletonBinding);
            writer.Write(this.RenderMaskFlags);
            writer.Write(this.GeometriesCount3);


            for (int i = 0; i < GeometriesCount1; i++)
            {
                writer.Write(ShaderMapping[i]);
            }
            if (GeometriesCount1 == 1)
            {
                writer.Write(new byte[6]);
            }
            else
            {
                writer.WritePadding(16);
            }
            for (int i = 0; i < GeometriesCount1; i++)
            {
                writer.Write(GeometryPointers[i]);
            }
            writer.WritePadding(16);
            for (int i = 0; i < BoundsData.Length; i++)
            {
                writer.WriteStruct(BoundsData[i]);
            }
            for (int i = 0; i < GeometriesCount1; i++)
            {
                var geom = (Geometries != null) ? Geometries[i] : null;
                if (geom != null)
                {
                    writer.WritePadding(16);
                    writer.WriteBlock(geom);
                }
            }

        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "RenderMask", RenderMask.ToString());
            YdrXml.ValueTag(sb, indent, "Flags", Flags.ToString());
            YdrXml.ValueTag(sb, indent, "HasSkin", HasSkin.ToString());
            YdrXml.ValueTag(sb, indent, "BoneIndex", BoneIndex.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown1", SkeletonBindUnk1.ToString());

            if (Geometries != null)
            {
                YdrXml.WriteItemArray(sb, Geometries, indent, "Geometries");
            }

        }
        public void ReadXml(XmlNode node)
        {
            RenderMask = (byte)Xml.GetChildUIntAttribute(node, "RenderMask", "value");
            Flags = (byte)Xml.GetChildUIntAttribute(node, "Flags", "value");
            HasSkin = (byte)Xml.GetChildUIntAttribute(node, "HasSkin", "value");
            BoneIndex = (byte)Xml.GetChildUIntAttribute(node, "BoneIndex", "value");
            SkeletonBindUnk1 = (byte)Xml.GetChildUIntAttribute(node, "Unknown1", "value");

            var aabbs = new List<AABB_s>();
            var shids = new List<ushort>();
            var min = new Vector4(float.MaxValue);
            var max = new Vector4(float.MinValue);
            var geoms = XmlMeta.ReadItemArray<DrawableGeometry>(node, "Geometries");
            if (geoms != null)
            {
                Geometries = geoms;
                foreach (var geom in geoms)
                {
                    aabbs.Add(geom.AABB);
                    shids.Add(geom.ShaderID);
                    min = Vector4.Min(min, geom.AABB.Min);
                    max = Vector4.Max(max, geom.AABB.Max);
                }
                GeometriesCount1 = GeometriesCount2 = GeometriesCount3 = (ushort)geoms.Length;
            }
            if (aabbs.Count > 1)
            {
                var outeraabb = new AABB_s() { Min = min, Max = max };
                aabbs.Insert(0, outeraabb);
            }

            BoundsData = (aabbs.Count > 0) ? aabbs.ToArray() : null;
            ShaderMapping = (shids.Count > 0) ? shids.ToArray() : null;
        }


        public override Tuple<long, IResourceBlock>[] GetParts()
        {
            var parts = new List<Tuple<long, IResourceBlock>>();
            parts.AddRange(base.GetParts());

            var off = (long)48;
            off += (GeometriesCount1 * 2); //ShaderMapping
            if (GeometriesCount1 == 1) off += 6;
            else off += ((16 - (off % 16)) % 16);
            off += (GeometriesCount1 * 8); //Geometries pointers
            off += ((16 - (off % 16)) % 16);
            off += (GeometriesCount1 + ((GeometriesCount1 > 1) ? 1 : 0)) * 32; //BoundsData
            for (int i = 0; i < GeometriesCount1; i++)
            {
                var geom = (Geometries != null) ? Geometries[i] : null;
                if (geom != null)
                {
                    off += ((16 - (off % 16)) % 16);
                    parts.Add(new Tuple<long, IResourceBlock>(off, geom));
                    off += geom.BlockLength; //Geometries
                }
            }

            return parts.ToArray();
        }

        public override string ToString()
        {
            return "(" + (Geometries?.Length ?? 0).ToString() + " geometr" + ((Geometries?.Length ?? 0) != 1 ? "ies)" : "y)");
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableGeometry : ResourceSystemBlock, IMetaXmlItem
    {
        public override long BlockLength
        {
            get 
            {
                long l = 152;
                if (BoneIds != null)
                {
                    if (BoneIds.Length > 4) l += 8;
                    l += (BoneIds.Length) * 2;
                }
                return l;
            }
        }

        // structure data
        public uint VFT { get; set; } = 1080133528;
        public uint Unknown_4h = 1; // 0x00000001
        public ulong Unknown_8h; // 0x0000000000000000
        public ulong Unknown_10h; // 0x0000000000000000
        public ulong VertexBufferPointer { get; set; }
        public ulong Unknown_20h; // 0x0000000000000000
        public ulong Unknown_28h; // 0x0000000000000000
        public ulong Unknown_30h; // 0x0000000000000000
        public ulong IndexBufferPointer { get; set; }
        public ulong Unknown_40h; // 0x0000000000000000
        public ulong Unknown_48h; // 0x0000000000000000
        public ulong Unknown_50h; // 0x0000000000000000
        public uint IndicesCount { get; set; }
        public uint TrianglesCount { get; set; }
        public ushort VerticesCount { get; set; }
        public ushort Unknown_62h = 3; // 0x0003 // indices per primitive (triangle)
        public uint Unknown_64h; // 0x00000000
        public ulong BoneIdsPointer { get; set; }
        public ushort VertexStride { get; set; }
        public ushort BoneIdsCount { get; set; }
        public uint Unknown_74h; // 0x00000000
        public ulong VertexDataPointer { get; set; }
        public ulong Unknown_80h; // 0x0000000000000000
        public ulong Unknown_88h; // 0x0000000000000000
        public ulong Unknown_90h; // 0x0000000000000000

        // reference data
        public VertexBuffer VertexBuffer { get; set; }
        public IndexBuffer IndexBuffer { get; set; }
        public VertexData VertexData { get; set; }
        public ushort[] BoneIds { get; set; }//embedded at the end of this struct
        public ShaderFX Shader { get; set; }//written by parent DrawableBase, using ShaderID
        public ushort ShaderID { get; set; }//read/written by parent model
        public AABB_s AABB { get; set; }//read/written by parent model


        public bool UpdateRenderableParameters = false; //used by model material editor...


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.Unknown_8h = reader.ReadUInt64();
            this.Unknown_10h = reader.ReadUInt64();
            this.VertexBufferPointer = reader.ReadUInt64();
            this.Unknown_20h = reader.ReadUInt64();
            this.Unknown_28h = reader.ReadUInt64();
            this.Unknown_30h = reader.ReadUInt64();
            this.IndexBufferPointer = reader.ReadUInt64();
            this.Unknown_40h = reader.ReadUInt64();
            this.Unknown_48h = reader.ReadUInt64();
            this.Unknown_50h = reader.ReadUInt64();
            this.IndicesCount = reader.ReadUInt32();
            this.TrianglesCount = reader.ReadUInt32();
            this.VerticesCount = reader.ReadUInt16();
            this.Unknown_62h = reader.ReadUInt16();
            this.Unknown_64h = reader.ReadUInt32();
            this.BoneIdsPointer = reader.ReadUInt64();
            this.VertexStride = reader.ReadUInt16();
            this.BoneIdsCount = reader.ReadUInt16();
            this.Unknown_74h = reader.ReadUInt32();
            this.VertexDataPointer = reader.ReadUInt64();
            this.Unknown_80h = reader.ReadUInt64();
            this.Unknown_88h = reader.ReadUInt64();
            this.Unknown_90h = reader.ReadUInt64();

            // read reference data
            this.VertexBuffer = reader.ReadBlockAt<VertexBuffer>(
                this.VertexBufferPointer // offset
            );
            this.IndexBuffer = reader.ReadBlockAt<IndexBuffer>(
                this.IndexBufferPointer // offset
            );
            this.BoneIds = reader.ReadUshortsAt(this.BoneIdsPointer, this.BoneIdsCount, false);
            if (this.BoneIds != null) //skinned mesh bones to use? peds, also yft props...
            {
            }
            //if (BoneIdsPointer != 0)
            //{
            //    var pos = (ulong)reader.Position;
            //    if (BoneIdsCount > 4) pos += 8;
            //    if (BoneIdsPointer != pos)
            //    { }//no hit - interesting alignment, boneids array always packed after this struct
            //}

            if (this.VertexBuffer != null)
            {
                this.VertexData = this.VertexBuffer.Data1 ?? this.VertexBuffer.Data2;

                if (this.VerticesCount == 0)
                {
                    this.VerticesCount = (ushort)(this.VertexData?.VertexCount ?? 0);
                }

                //if (VertexBuffer.Data1 != VertexBuffer.Data2)
                //{ }//no hit
                //if (VertexDataPointer == 0)
                //{ }//no hit
                //else if (VertexDataPointer != VertexBuffer.DataPointer1)
                //{
                //    ////some mods hit here!
                //    //try
                //    //{
                //    //    this.VertexData = reader.ReadBlockAt<VertexData>(
                //    //        this.VertexDataPointer, // offset
                //    //        this.VertexStride,
                //    //        this.VerticesCount,
                //    //        this.VertexBuffer.Info
                //    //    );
                //    //}
                //    //catch
                //    //{ }
                //}
                //if (VertexStride != VertexBuffer.VertexStride)
                //{ }//no hit
                //if (VertexStride != (VertexBuffer.Info?.Stride ?? 0))
                //{ }//no hit
            }
            //else
            //{ }//no hit


            //if (Unknown_4h != 1)
            //{ }
            //if (Unknown_8h != 0)
            //{ }
            //if (Unknown_10h != 0)
            //{ }
            //if (Unknown_20h != 0)
            //{ }
            //if (Unknown_28h != 0)
            //{ }
            //if (Unknown_30h != 0)
            //{ }
            //if (Unknown_40h != 0)
            //{ }
            //if (Unknown_48h != 0)
            //{ }
            //if (Unknown_50h != 0)
            //{ }
            //if (Unknown_64h != 0)
            //{ }
            //if (Unknown_74h != 0)
            //{ }
            //if (Unknown_80h != 0)
            //{ }
            //if (Unknown_88h != 0)
            //{ }
            //if (Unknown_90h != 0)
            //{ }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.VertexBufferPointer = (ulong)(this.VertexBuffer != null ? this.VertexBuffer.FilePosition : 0);
            this.IndexBufferPointer = (ulong)(this.IndexBuffer != null ? this.IndexBuffer.FilePosition : 0);
            this.VertexDataPointer = (ulong)(this.VertexData != null ? this.VertexData.FilePosition : 0);
            this.VerticesCount = (ushort)(this.VertexData != null ? this.VertexData.VertexCount : 0); //TODO: fix?
            this.VertexStride = (ushort)(this.VertexBuffer != null ? this.VertexBuffer.VertexStride : 0); //TODO: fix?
            this.IndicesCount = (this.IndexBuffer != null ? this.IndexBuffer.IndicesCount : 0); //TODO: fix?
            this.TrianglesCount = this.IndicesCount / 3; //TODO: fix?
            this.BoneIdsPointer = (BoneIds != null) ? (ulong)(writer.Position + 152 + ((BoneIds.Length > 4) ? 8 : 0)) : 0;
            this.BoneIdsCount = (ushort)(BoneIds?.Length ?? 0);
            

            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.Unknown_8h);
            writer.Write(this.Unknown_10h);
            writer.Write(this.VertexBufferPointer);
            writer.Write(this.Unknown_20h);
            writer.Write(this.Unknown_28h);
            writer.Write(this.Unknown_30h);
            writer.Write(this.IndexBufferPointer);
            writer.Write(this.Unknown_40h);
            writer.Write(this.Unknown_48h);
            writer.Write(this.Unknown_50h);
            writer.Write(this.IndicesCount);
            writer.Write(this.TrianglesCount);
            writer.Write(this.VerticesCount);
            writer.Write(this.Unknown_62h);
            writer.Write(this.Unknown_64h);
            writer.Write(this.BoneIdsPointer);
            writer.Write(this.VertexStride);
            writer.Write(this.BoneIdsCount);
            writer.Write(this.Unknown_74h);
            writer.Write(this.VertexDataPointer);
            writer.Write(this.Unknown_80h);
            writer.Write(this.Unknown_88h);
            writer.Write(this.Unknown_90h);

            if (BoneIds != null)
            {
                if (BoneIds.Length > 4)
                {
                    writer.Write((ulong)0);
                }
                for (int i = 0; i < BoneIds.Length; i++)
                {
                    writer.Write(BoneIds[i]);
                }
            }

        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "ShaderIndex", ShaderID.ToString());
            YdrXml.SelfClosingTag(sb, indent, "BoundingBoxMin " + FloatUtil.GetVector4XmlString(AABB.Min));
            YdrXml.SelfClosingTag(sb, indent, "BoundingBoxMax " + FloatUtil.GetVector4XmlString(AABB.Max));
            if (BoneIds != null)
            {
                var ids = new StringBuilder();
                foreach (var id in BoneIds)
                {
                    if (ids.Length > 0) ids.Append(", ");
                    ids.Append(id.ToString());
                }
                YdrXml.StringTag(sb, indent, "BoneIDs", ids.ToString());
            }
            if (VertexBuffer != null)
            {
                YdrXml.OpenTag(sb, indent, "VertexBuffer");
                VertexBuffer.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "VertexBuffer");
            }
            if (IndexBuffer != null)
            {
                YdrXml.OpenTag(sb, indent, "IndexBuffer");
                IndexBuffer.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "IndexBuffer");
            }
        }
        public void ReadXml(XmlNode node)
        {
            ShaderID = (ushort)Xml.GetChildUIntAttribute(node, "ShaderIndex", "value");
            var aabb = new AABB_s();
            aabb.Min = Xml.GetChildVector4Attributes(node, "BoundingBoxMin");
            aabb.Max = Xml.GetChildVector4Attributes(node, "BoundingBoxMax");
            AABB = aabb;
            var bnode = node.SelectSingleNode("BoneIDs");
            if (bnode != null)
            {
                var astr = bnode.InnerText;
                var arr = astr.Split(',');
                var blist = new List<ushort>();
                foreach (var bstr in arr)
                {
                    var tstr = bstr?.Trim();
                    if (string.IsNullOrEmpty(tstr)) continue;
                    if (ushort.TryParse(tstr, out ushort u))
                    {
                        blist.Add(u);
                    }
                }
                BoneIds = (blist.Count > 0) ? blist.ToArray() : null;
            }
            var vnode = node.SelectSingleNode("VertexBuffer");
            if (vnode != null)
            {
                VertexBuffer = new VertexBuffer();
                VertexBuffer.ReadXml(vnode);
                VertexData = VertexBuffer.Data1 ?? VertexBuffer.Data2;
            }
            var inode = node.SelectSingleNode("IndexBuffer");
            if (inode != null)
            {
                IndexBuffer = new IndexBuffer();
                IndexBuffer.ReadXml(inode);
            }
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (VertexBuffer != null) list.Add(VertexBuffer);
            if (IndexBuffer != null) list.Add(IndexBuffer);
            if (VertexData != null) list.Add(VertexData);
            return list.ToArray();
        }

        public override string ToString()
        {
            return VerticesCount.ToString() + " verts, " + Shader.ToString();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class VertexBuffer : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 128; }
        }

        // structure data
        public uint VFT { get; set; } = 1080153080;
        public uint Unknown_4h = 1; // 0x00000001
        public ushort VertexStride { get; set; }
        public ushort Flags { get; set; } //only 0 or 1024
        public uint Unknown_Ch; // 0x00000000
        public ulong DataPointer1 { get; set; }
        public uint VertexCount { get; set; }
        public uint Unknown_1Ch; // 0x00000000
        public ulong DataPointer2 { get; set; }
        public ulong Unknown_28h; // 0x0000000000000000
        public ulong InfoPointer { get; set; }
        public ulong Unknown_38h; // 0x0000000000000000
        public ulong Unknown_40h; // 0x0000000000000000
        public ulong Unknown_48h; // 0x0000000000000000
        public ulong Unknown_50h; // 0x0000000000000000
        public ulong Unknown_58h; // 0x0000000000000000
        public ulong Unknown_60h; // 0x0000000000000000
        public ulong Unknown_68h; // 0x0000000000000000
        public ulong Unknown_70h; // 0x0000000000000000
        public ulong Unknown_78h; // 0x0000000000000000

        // reference data
        public VertexData Data1 { get; set; }
        public VertexData Data2 { get; set; }
        public VertexDeclaration Info { get; set; }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.VertexStride = reader.ReadUInt16();
            this.Flags = reader.ReadUInt16();
            this.Unknown_Ch = reader.ReadUInt32();
            this.DataPointer1 = reader.ReadUInt64();
            this.VertexCount = reader.ReadUInt32();
            this.Unknown_1Ch = reader.ReadUInt32();
            this.DataPointer2 = reader.ReadUInt64();
            this.Unknown_28h = reader.ReadUInt64();
            this.InfoPointer = reader.ReadUInt64();
            this.Unknown_38h = reader.ReadUInt64();
            this.Unknown_40h = reader.ReadUInt64();
            this.Unknown_48h = reader.ReadUInt64();
            this.Unknown_50h = reader.ReadUInt64();
            this.Unknown_58h = reader.ReadUInt64();
            this.Unknown_60h = reader.ReadUInt64();
            this.Unknown_68h = reader.ReadUInt64();
            this.Unknown_70h = reader.ReadUInt64();
            this.Unknown_78h = reader.ReadUInt64();

            // read reference data
            this.Info = reader.ReadBlockAt<VertexDeclaration>(
                this.InfoPointer // offset
            );
            this.Data1 = reader.ReadBlockAt<VertexData>(
                this.DataPointer1, // offset
                this.VertexStride,
                this.VertexCount,
                this.Info
            );
            this.Data2 = reader.ReadBlockAt<VertexData>(
                this.DataPointer2, // offset
                this.VertexStride,
                this.VertexCount,
                this.Info
            );


            //switch (Flags)
            //{
            //    case 0:
            //        break;
            //    case 1024://micro flag? //micro_brow_down.ydr, micro_chin_pointed.ydr
            //        break;
            //    default:
            //        break;
            //}

            //if (Unknown_4h != 1)
            //{ }
            //if (Unknown_Ch != 0)
            //{ }
            //if (Unknown_1Ch != 0)
            //{ }
            //if (Unknown_28h != 0)
            //{ }
            //if (Unknown_38h != 0)
            //{ }
            //if (Unknown_40h != 0)
            //{ }
            //if (Unknown_48h != 0)
            //{ }
            //if (Unknown_50h != 0)
            //{ }
            //if (Unknown_58h != 0)
            //{ }
            //if (Unknown_60h != 0)
            //{ }
            //if (Unknown_68h != 0)
            //{ }
            //if (Unknown_70h != 0)
            //{ }
            //if (Unknown_78h != 0)
            //{ }

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.VertexCount = (uint)(this.Data1 != null ? this.Data1.VertexCount : this.Data2 != null ? this.Data2.VertexCount : 0);
            this.DataPointer1 = (ulong)(this.Data1 != null ? this.Data1.FilePosition : 0);
            this.DataPointer2 = (ulong)(this.Data2 != null ? this.Data2.FilePosition : 0);
            this.InfoPointer = (ulong)(this.Info != null ? this.Info.FilePosition : 0);

            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.VertexStride);
            writer.Write(this.Flags);
            writer.Write(this.Unknown_Ch);
            writer.Write(this.DataPointer1);
            writer.Write(this.VertexCount);
            writer.Write(this.Unknown_1Ch);
            writer.Write(this.DataPointer2);
            writer.Write(this.Unknown_28h);
            writer.Write(this.InfoPointer);
            writer.Write(this.Unknown_38h);
            writer.Write(this.Unknown_40h);
            writer.Write(this.Unknown_48h);
            writer.Write(this.Unknown_50h);
            writer.Write(this.Unknown_58h);
            writer.Write(this.Unknown_60h);
            writer.Write(this.Unknown_68h);
            writer.Write(this.Unknown_70h);
            writer.Write(this.Unknown_78h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.ValueTag(sb, indent, "Flags", Flags.ToString());

            if (Info != null)
            {
                Info.WriteXml(sb, indent, "Layout");
            }
            if (Data1 != null)
            {
                YdrXml.OpenTag(sb, indent, "Data");
                Data1.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "Data");
            }
            if ((Data2 != null) && (Data2 != Data1))
            {
                YdrXml.OpenTag(sb, indent, "Data2");
                Data2.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "Data2");
            }
        }
        public void ReadXml(XmlNode node)
        {
            Flags = (ushort)Xml.GetChildUIntAttribute(node, "Flags", "value");

            var inode = node.SelectSingleNode("Layout");
            if (inode != null)
            {
                Info = new VertexDeclaration();
                Info.ReadXml(inode);
                VertexStride = Info.Stride;
            }
            var dnode = node.SelectSingleNode("Data");
            if (dnode != null)
            {
                Data1 = new VertexData();
                Data1.ReadXml(dnode, Info);
                Data2 = Data1;
                VertexCount = (uint)Data1.VertexCount;
            }
            var dnode2 = node.SelectSingleNode("Data2");
            if (dnode2 != null)
            {
                Data2 = new VertexData();
                Data2.ReadXml(dnode2, Info);
            }
        }


        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (Data1 != null) list.Add(Data1);
            if (Data2 != null) list.Add(Data2);
            if (Info != null) list.Add(Info);
            return list.ToArray();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class VertexData : ResourceSystemBlock
    {


        //private int length = 0;
        public override long BlockLength
        {
            get
            {
                return VertexBytes?.Length ?? 0; //this.length;
            }
        }


        public int VertexStride { get; set; }
        public int VertexCount { get; set; }
        public VertexDeclaration Info { get; set; }
        public VertexType VertexType { get; set; }

        public byte[] VertexBytes { get; set; }


        public long MemoryUsage
        {
            get
            {
                return (long)VertexCount * (long)VertexStride;
            }
        }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            VertexStride = Convert.ToInt32(parameters[0]);
            VertexCount = Convert.ToInt32(parameters[1]);
            Info = (VertexDeclaration)parameters[2];
            VertexType = (VertexType)Info.Flags;

            VertexBytes = reader.ReadBytes(VertexCount * VertexStride);

            switch (Info.Types)
            {
                case VertexDeclarationTypes.GTAV1: //YDR - 0x7755555555996996
                    break;
                case VertexDeclarationTypes.GTAV2:  //YFT - 0x030000000199A006
                    switch (Info.Flags)
                    {
                        case 16473: VertexType = VertexType.PCCH2H4; break;  //  PCCH2H4 
                        default:break;
                    }
                    break;
                case VertexDeclarationTypes.GTAV3:  //YFT - 0x0300000001996006  PNCH2H4
                    switch (Info.Flags)
                    {
                        case 89: VertexType = VertexType.PNCH2; break;     //  PNCH2
                        default: break;
                    }
                    break;
                default:
                    break;
            }

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            if (VertexBytes != null)
            {
                writer.Write(VertexBytes); //not dealing with individual vertex data here any more!
            }
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            var flags = Info?.Flags ?? 0;
            var row = new StringBuilder();
            for (int v = 0; v < VertexCount; v++)
            {
                row.Clear();
                for (int k = 0; k < 16; k++)
                {
                    if (((flags >> k) & 0x1) == 1)
                    {
                        if (row.Length > 0) row.Append("   ");
                        var str = GetString(v, k, " ");
                        row.Append(str);
                    }
                }
                YdrXml.Indent(sb, indent);
                sb.AppendLine(row.ToString());
            }
        }
        public void ReadXml(XmlNode node, VertexDeclaration info)
        {
            Info = info;
            VertexType = (VertexType)(info?.Flags ?? 0);

            if (Info != null)
            {
                var flags = Info.Flags;
                var stride = Info.Stride;
                var vstrs = new List<string[]>();
                var coldelim = new[] { ' ', '\t' };
                var rowdelim = new[] { '\n' };
                var rows = node?.InnerText?.Trim()?.Split(rowdelim, StringSplitOptions.RemoveEmptyEntries);
                if (rows != null)
                {
                    foreach (var row in rows)
                    {
                        var rowt = row.Trim();
                        if (string.IsNullOrEmpty(rowt)) continue;
                        var cols = row.Split(coldelim, StringSplitOptions.RemoveEmptyEntries);
                        vstrs.Add(cols);
                    }
                }
                if (vstrs.Count > 0)
                {
                    AllocateData(vstrs.Count);
                    for (int v = 0; v < vstrs.Count; v++)
                    {
                        var vstr = vstrs[v];
                        var sind = 0;
                        for (int k = 0; k < 16; k++)
                        {
                            if (((flags >> k) & 0x1) == 1)
                            {
                                SetString(v, k, vstr, ref sind);
                            }
                        }
                    }
                }
            }
        }


        public void AllocateData(int vertexCount)
        {
            if (Info != null)
            {
                var stride = Info.Stride;
                var byteCount = vertexCount * stride;
                VertexBytes = new byte[byteCount];
                VertexCount = vertexCount;
            }
        }

        public void SetString(int v, int c, string[] strs, ref int sind)
        {
            if ((Info != null) && (VertexBytes != null) && (strs != null))
            {
                var ind = sind;
                float f(int i) => FloatUtil.Parse(strs[ind + i].Trim());
                byte b(int i) { if (byte.TryParse(strs[ind + i].Trim(), out byte x)) return x; else return 0; }
                var ct = Info.GetComponentType(c);
                var cc = VertexComponentTypes.GetComponentCount(ct);
                if (sind + cc > strs.Length)
                { return; }
                switch (ct)
                {
                    case VertexComponentType.Float: SetFloat(v, c, f(0)); break;
                    case VertexComponentType.Float2: SetVector2(v, c, new Vector2(f(0), f(1))); break;
                    case VertexComponentType.Float3: SetVector3(v, c, new Vector3(f(0), f(1), f(2))); break;
                    case VertexComponentType.Float4: SetVector4(v, c, new Vector4(f(0), f(1), f(2), f(3))); break;
                    case VertexComponentType.Dec3N: SetDec3N(v, c, new Vector3(f(0), f(1), f(2))); break;
                    case VertexComponentType.Half2: SetHalf2(v, c, new Half2(f(0), f(1))); break;
                    case VertexComponentType.Half4: SetHalf4(v, c, new Half4(f(0), f(1), f(2), f(3))); break;
                    case VertexComponentType.Colour: SetColour(v, c, new Color(b(0), b(1), b(2), b(3))); break;
                    case VertexComponentType.UByte4: SetUByte4(v, c, new Color(b(0), b(1), b(2), b(3))); break;
                    default:
                        break;
                }
                sind += cc;
            }
        }
        public void SetFloat(int v, int c, float val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(float)
                if (e <= VertexBytes.Length)
                {
                    var b = BitConverter.GetBytes(val);
                    Buffer.BlockCopy(b, 0, VertexBytes, o, 4);
                }
            }
        }
        public void SetVector2(int v, int c, Vector2 val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 8;//sizeof(Vector2)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.GetBytes(val.X);
                    var y = BitConverter.GetBytes(val.Y);
                    Buffer.BlockCopy(x, 0, VertexBytes, o + 0, 4);
                    Buffer.BlockCopy(y, 0, VertexBytes, o + 4, 4);
                }
            }
        }
        public void SetVector3(int v, int c, Vector3 val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 12;//sizeof(Vector3)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.GetBytes(val.X);
                    var y = BitConverter.GetBytes(val.Y);
                    var z = BitConverter.GetBytes(val.Z);
                    Buffer.BlockCopy(x, 0, VertexBytes, o + 0, 4);
                    Buffer.BlockCopy(y, 0, VertexBytes, o + 4, 4);
                    Buffer.BlockCopy(z, 0, VertexBytes, o + 8, 4);
                }
            }
        }
        public void SetVector4(int v, int c, Vector4 val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 16;//sizeof(Vector4)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.GetBytes(val.X);
                    var y = BitConverter.GetBytes(val.Y);
                    var z = BitConverter.GetBytes(val.Z);
                    var w = BitConverter.GetBytes(val.W);
                    Buffer.BlockCopy(x, 0, VertexBytes, o + 0, 4);
                    Buffer.BlockCopy(y, 0, VertexBytes, o + 4, 4);
                    Buffer.BlockCopy(z, 0, VertexBytes, o + 8, 4);
                    Buffer.BlockCopy(w, 0, VertexBytes, o + 12, 4);
                }
            }
        }
        public void SetDec3N(int v, int c, Vector3 val)
        {
            //see https://docs.microsoft.com/en-us/previous-versions/windows/desktop/bb322868(v%3Dvs.85)
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Dec3N)
                if (e <= VertexBytes.Length)
                {
                    var sx = (val.X < 0.0f);
                    var sy = (val.X < 0.0f);
                    var sz = (val.X < 0.0f);
                    var x = Math.Min((uint)(Math.Abs(val.X) * 511.0f), 511);
                    var y = Math.Min((uint)(Math.Abs(val.Y) * 511.0f), 511);
                    var z = Math.Min((uint)(Math.Abs(val.Z) * 511.0f), 511);
                    var ux = ((sx ? ~x : x) & 0x1FF) + (sx ? 0x200 : 0);
                    var uy = ((sy ? ~y : y) & 0x1FF) + (sy ? 0x200 : 0);
                    var uz = ((sz ? ~z : z) & 0x1FF) + (sz ? 0x200 : 0);
                    var uw = 0u;
                    var u = ux + (uy << 10) + (uz << 20) + (uw << 30);
                    var b = BitConverter.GetBytes(u);
                    Buffer.BlockCopy(b, 0, VertexBytes, o, 4);
                }
            }
        }
        public void SetHalf2(int v, int c, Half2 val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Half2)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.GetBytes(val.X.RawValue);
                    var y = BitConverter.GetBytes(val.Y.RawValue);
                    Buffer.BlockCopy(x, 0, VertexBytes, o + 0, 2);
                    Buffer.BlockCopy(y, 0, VertexBytes, o + 2, 2);
                }
            }
        }
        public void SetHalf4(int v, int c, Half4 val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 8;//sizeof(Half4)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.GetBytes(val.X.RawValue);
                    var y = BitConverter.GetBytes(val.Y.RawValue);
                    var z = BitConverter.GetBytes(val.Z.RawValue);
                    var w = BitConverter.GetBytes(val.W.RawValue);
                    Buffer.BlockCopy(x, 0, VertexBytes, o + 0, 2);
                    Buffer.BlockCopy(y, 0, VertexBytes, o + 2, 2);
                    Buffer.BlockCopy(z, 0, VertexBytes, o + 4, 2);
                    Buffer.BlockCopy(w, 0, VertexBytes, o + 6, 2);
                }
            }
        }
        public void SetColour(int v, int c, Color val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Color)
                if (e <= VertexBytes.Length)
                {
                    var u = val.ToRgba();
                    var b = BitConverter.GetBytes(u);
                    Buffer.BlockCopy(b, 0, VertexBytes, o, 4);
                }
            }
        }
        public void SetUByte4(int v, int c, Color val)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(UByte4)
                if (e <= VertexBytes.Length)
                {
                    var u = val.ToRgba();
                    var b = BitConverter.GetBytes(u);
                    Buffer.BlockCopy(b, 0, VertexBytes, o, 4);
                }
            }
        }

        public string GetString(int v, int c, string d = ", ")
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var ct = Info.GetComponentType(c);
                switch (ct)
                {
                    case VertexComponentType.Float: return FloatUtil.ToString(GetFloat(v, c));
                    case VertexComponentType.Float2: return FloatUtil.GetVector2String(GetVector2(v, c), d);
                    case VertexComponentType.Float3: return FloatUtil.GetVector3String(GetVector3(v, c), d);
                    case VertexComponentType.Float4: return FloatUtil.GetVector4String(GetVector4(v, c), d);
                    case VertexComponentType.Dec3N: return FloatUtil.GetVector3String(GetDec3N(v, c), d);
                    case VertexComponentType.Half2: return FloatUtil.GetHalf2String(GetHalf2(v, c), d);
                    case VertexComponentType.Half4: return FloatUtil.GetHalf4String(GetHalf4(v, c), d);
                    case VertexComponentType.Colour: return FloatUtil.GetColourString(GetColour(v, c), d);
                    case VertexComponentType.UByte4: return FloatUtil.GetColourString(GetUByte4(v, c), d);
                    default:
                        break;
                }
            }
            return string.Empty;
        }
        public float GetFloat(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(float)
                if (e <= VertexBytes.Length)
                {
                    var f = BitConverter.ToSingle(VertexBytes, o);
                    return f;
                }
            }
            return 0;
        }
        public Vector2 GetVector2(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 8;//sizeof(Vector2)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.ToSingle(VertexBytes, o + 0);
                    var y = BitConverter.ToSingle(VertexBytes, o + 4);
                    return new Vector2(x, y);
                }
            }
            return Vector2.Zero;
        }
        public Vector3 GetVector3(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 12;//sizeof(Vector3)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.ToSingle(VertexBytes, o + 0);
                    var y = BitConverter.ToSingle(VertexBytes, o + 4);
                    var z = BitConverter.ToSingle(VertexBytes, o + 8);
                    return new Vector3(x, y, z);
                }
            }
            return Vector3.Zero;
        }
        public Vector4 GetVector4(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 16;//sizeof(Vector4)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.ToSingle(VertexBytes, o + 0);
                    var y = BitConverter.ToSingle(VertexBytes, o + 4);
                    var z = BitConverter.ToSingle(VertexBytes, o + 8);
                    var w = BitConverter.ToSingle(VertexBytes, o + 12);
                    return new Vector4(x, y, z, w);
                }
            }
            return Vector4.Zero;
        }
        public Vector3 GetDec3N(int v, int c)
        {
            //see https://docs.microsoft.com/en-us/previous-versions/windows/desktop/bb322868(v%3Dvs.85)
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Dec3N)
                if (e <= VertexBytes.Length)
                {
                    var u = BitConverter.ToUInt32(VertexBytes, o);
                    var ux = (u >> 0) & 0x3FF;
                    var uy = (u >> 10) & 0x3FF;
                    var uz = (u >> 20) & 0x3FF;
                    var uw = (u >> 30);
                    var sx = (ux & 0x200) > 0;
                    var sy = (uy & 0x200) > 0;
                    var sz = (uz & 0x200) > 0;
                    var x = ((sx ? ~ux : ux) & 0x1FF) / (sx ? -511.0f : 511.0f);
                    var y = ((sy ? ~uy : uy) & 0x1FF) / (sy ? -511.0f : 511.0f);
                    var z = ((sz ? ~uz : uz) & 0x1FF) / (sz ? -511.0f : 511.0f);
                    return new Vector3(x, y, z);
                }
            }
            return Vector3.Zero;
        }
        public Half2 GetHalf2(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Half2)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.ToUInt16(VertexBytes, o + 0);
                    var y = BitConverter.ToUInt16(VertexBytes, o + 2);
                    return new Half2(x, y);
                }
            }
            return new Half2(0, 0);
        }
        public Half4 GetHalf4(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 8;//sizeof(Half4)
                if (e <= VertexBytes.Length)
                {
                    var x = BitConverter.ToUInt16(VertexBytes, o + 0);
                    var y = BitConverter.ToUInt16(VertexBytes, o + 2);
                    var z = BitConverter.ToUInt16(VertexBytes, o + 4);
                    var w = BitConverter.ToUInt16(VertexBytes, o + 6);
                    return new Half4(x, y, z, w);
                }
            }
            return new Half4(0, 0, 0, 0);
        }
        public Color GetColour(int v, int c)
        {
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(Color)
                if (e <= VertexBytes.Length)
                {
                    var rgba = BitConverter.ToUInt32(VertexBytes, o);
                    return new Color(rgba);
                }
            }
            return Color.Black;
        }
        public Color GetUByte4(int v, int c)
        {
            //Color is the same as UByte4 really
            if ((Info != null) && (VertexBytes != null))
            {
                var s = Info.Stride;
                var co = Info.GetComponentOffset(c);
                var o = (v * s) + co;
                var e = o + 4;//sizeof(UByte4)
                if (e <= VertexBytes.Length)
                {
                    var rgba = BitConverter.ToUInt32(VertexBytes, o);
                    return new Color(rgba);
                }
            }
            return new Color(0, 0, 0, 0);
        }


        public override string ToString()
        {
            return "Type: " + VertexType.ToString() + ", Count: " + VertexCount.ToString();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class VertexDeclaration : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 16; }
        }

        // structure data
        public uint Flags { get; set; }
        public ushort Stride { get; set; }
        public byte Unknown_6h { get; set; }//0
        public byte Count { get; set; }
        public VertexDeclarationTypes Types { get; set; }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.Flags = reader.ReadUInt32();
            this.Stride = reader.ReadUInt16();
            this.Unknown_6h = reader.ReadByte();
            this.Count = reader.ReadByte();
            this.Types = (VertexDeclarationTypes)reader.ReadUInt64();

            ////just testing!
            //UpdateCountAndStride();
            //if (Unknown_6h != 0)
            //{ }//no hit

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // write structure data
            writer.Write(this.Flags);
            writer.Write(this.Stride);
            writer.Write(this.Unknown_6h);
            writer.Write(this.Count);
            writer.Write((ulong)this.Types);
        }
        public void WriteXml(StringBuilder sb, int indent, string name)
        {
            YdrXml.OpenTag(sb, indent, name + " type=\"" + Types.ToString() + "\"");

            for (int k = 0; k < 16; k++)
            {
                if (((Flags >> k) & 0x1) == 1)
                {
                    var componentSemantic = (VertexSemantics)k;
                    var tag = componentSemantic.ToString();
                    YdrXml.SelfClosingTag(sb, indent + 1, tag);
                }
            }

            YdrXml.CloseTag(sb, indent, name);
        }
        public void ReadXml(XmlNode node)
        {
            if (node == null) return;

            Types = Xml.GetEnumValue<VertexDeclarationTypes>(Xml.GetStringAttribute(node, "type"));

            uint f = 0;
            foreach (XmlNode cnode in node.ChildNodes)
            {
                if (cnode is XmlElement celem)
                {
                    var componentSematic = Xml.GetEnumValue<VertexSemantics>(celem.Name);
                    var idx = (int)componentSematic;
                    f = f | (1u << idx);
                }
            }
            Flags = f;

            UpdateCountAndStride();
        }

        public ulong GetDeclarationId()
        {
            ulong res = 0;
            for(int i=0; i < 16; i++)
            {
                if (((Flags >> i) & 1) == 1)
                {
                    res += ((ulong)Types & (0xFu << (i * 4)));
                }
            }
            return res;
        }

        public VertexComponentType GetComponentType(int index)
        {
            //index is the flags bit index
            return (VertexComponentType)(((ulong)Types >> (index * 4)) & 0x0000000F);
        }

        public int GetComponentOffset(int index)
        {
            //index is the flags bit index
            var offset = 0;
            for (int k = 0; k < index; k++)
            {
                if (((Flags >> k) & 0x1) == 1)
                {
                    var componentType = GetComponentType(k);
                    offset += VertexComponentTypes.GetSizeInBytes(componentType);
                }
            }
            return offset;
        }

        public void UpdateCountAndStride()
        {
            var cnt = 0;
            var str = 0;
            for (int k = 0; k < 16; k++)
            {
                if (((Flags >> k) & 0x1) == 1)
                {
                    var componentType = GetComponentType(k);
                    str += VertexComponentTypes.GetSizeInBytes(componentType);
                    cnt++;
                }
            }

            ////just testing
            //if (Count != cnt)
            //{ }//no hit
            //if (Stride != str)
            //{ }//no hit

            Count = (byte)cnt;
            Stride = (ushort)str;
        }

        public override string ToString()
        {
            return Stride.ToString() + ": " + Count.ToString() + ": " + Flags.ToString() + ": " + Types.ToString(); 
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class IndexBuffer : ResourceSystemBlock
    {
        public override long BlockLength
        {
            get { return 96; }
        }

        // structure data
        public uint VFT { get; set; } = 1080152408;
        public uint Unknown_4h = 1; // 0x00000001
        public uint IndicesCount { get; set; }
        public uint Unknown_Ch; // 0x00000000
        public ulong IndicesPointer { get; set; }
        public ulong Unknown_18h; // 0x0000000000000000
        public ulong Unknown_20h; // 0x0000000000000000
        public ulong Unknown_28h; // 0x0000000000000000
        public ulong Unknown_30h; // 0x0000000000000000
        public ulong Unknown_38h; // 0x0000000000000000
        public ulong Unknown_40h; // 0x0000000000000000
        public ulong Unknown_48h; // 0x0000000000000000
        public ulong Unknown_50h; // 0x0000000000000000
        public ulong Unknown_58h; // 0x0000000000000000

        // reference data
        //public ResourceSimpleArray<ushort_r> Indices;
        public ushort[] Indices { get; set; }


        private ResourceSystemStructBlock<ushort> IndicesBlock = null; //only used when saving


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            // read structure data
            this.VFT = reader.ReadUInt32();
            this.Unknown_4h = reader.ReadUInt32();
            this.IndicesCount = reader.ReadUInt32();
            this.Unknown_Ch = reader.ReadUInt32();
            this.IndicesPointer = reader.ReadUInt64();
            this.Unknown_18h = reader.ReadUInt64();
            this.Unknown_20h = reader.ReadUInt64();
            this.Unknown_28h = reader.ReadUInt64();
            this.Unknown_30h = reader.ReadUInt64();
            this.Unknown_38h = reader.ReadUInt64();
            this.Unknown_40h = reader.ReadUInt64();
            this.Unknown_48h = reader.ReadUInt64();
            this.Unknown_50h = reader.ReadUInt64();
            this.Unknown_58h = reader.ReadUInt64();

            // read reference data
            //this.Indices = reader.ReadBlockAt<ResourceSimpleArray<ushort_r>>(
            //    this.IndicesPointer, // offset
            //    this.IndicesCount
            //);
            this.Indices = reader.ReadUshortsAt(this.IndicesPointer, this.IndicesCount);


            //if (Unknown_4h != 1)
            //{ }
            //if (Unknown_Ch != 0)
            //{ }
            //if (Unknown_18h != 0)
            //{ }
            //if (Unknown_20h != 0)
            //{ }
            //if (Unknown_28h != 0)
            //{ }
            //if (Unknown_30h != 0)
            //{ }
            //if (Unknown_38h != 0)
            //{ }
            //if (Unknown_40h != 0)
            //{ }
            //if (Unknown_48h != 0)
            //{ }
            //if (Unknown_50h != 0)
            //{ }
            //if (Unknown_58h != 0)
            //{ }

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            // update structure data
            this.IndicesCount = (uint)(this.IndicesBlock != null ? this.IndicesBlock.ItemCount : 0);
            this.IndicesPointer = (ulong)(this.IndicesBlock != null ? this.IndicesBlock.FilePosition : 0);

            // write structure data
            writer.Write(this.VFT);
            writer.Write(this.Unknown_4h);
            writer.Write(this.IndicesCount);
            writer.Write(this.Unknown_Ch);
            writer.Write(this.IndicesPointer);
            writer.Write(this.Unknown_18h);
            writer.Write(this.Unknown_20h);
            writer.Write(this.Unknown_28h);
            writer.Write(this.Unknown_30h);
            writer.Write(this.Unknown_38h);
            writer.Write(this.Unknown_40h);
            writer.Write(this.Unknown_48h);
            writer.Write(this.Unknown_50h);
            writer.Write(this.Unknown_58h);
        }
        public void WriteXml(StringBuilder sb, int indent)
        {
            if (Indices != null)
            {
                YdrXml.WriteRawArray(sb, Indices, indent, "Data", "", null, 24);
            }
        }
        public void ReadXml(XmlNode node)
        {
            var inode = node.SelectSingleNode("Data");
            if (inode != null)
            {
                Indices = Xml.GetRawUshortArray(node);
                IndicesCount = (uint)(Indices?.Length ?? 0);
            }
        }


        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>();
            if (Indices != null)
            {
                IndicesBlock = new ResourceSystemStructBlock<ushort>(Indices);
                list.Add(IndicesBlock);
            }
            return list.ToArray();
        }
    }


    public enum LightType : byte
    {
        Point = 1,
        Spot = 2,
        Capsule = 4,
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class LightAttributes : ResourceSystemBlock, IMetaXmlItem
    {
        public override long BlockLength
        {
            get { return 168; }
        }

        // structure data
        public uint Unknown_0h { get; set; } // 0x00000000
        public uint Unknown_4h { get; set; } // 0x00000000
        public Vector3 Position { get; set; }
        public uint Unknown_14h { get; set; } // 0x00000000
        public byte ColorR { get; set; }
        public byte ColorG { get; set; }
        public byte ColorB { get; set; }
        public byte Flashiness { get; set; }
        public float Intensity { get; set; }
        public uint Flags { get; set; }
        public ushort BoneId { get; set; }
        public LightType Type { get; set; }
        public byte GroupId { get; set; }
        public uint TimeFlags { get; set; }
        public float Falloff { get; set; }
        public float FalloffExponent { get; set; }
        public Vector3 CullingPlaneNormal { get; set; }
        public float CullingPlaneOffset { get; set; }
        public byte ShadowBlur { get; set; }
        public byte Unknown_45h { get; set; }
        public ushort Unknown_46h { get; set; }
        public uint Unknown_48h { get; set; } // 0x00000000
        public float VolumeIntensity { get; set; }
        public float VolumeSizeScale { get; set; }
        public byte VolumeOuterColorR { get; set; }
        public byte VolumeOuterColorG { get; set; }
        public byte VolumeOuterColorB { get; set; }
        public byte LightHash { get; set; }
        public float VolumeOuterIntensity { get; set; }
        public float CoronaSize { get; set; }
        public float VolumeOuterExponent { get; set; }
        public byte LightFadeDistance { get; set; }
        public byte ShadowFadeDistance { get; set; }
        public byte SpecularFadeDistance { get; set; }
        public byte VolumetricFadeDistance { get; set; }
        public float ShadowNearClip { get; set; }
        public float CoronaIntensity { get; set; }
        public float CoronaZBias { get; set; }
        public Vector3 Direction { get; set; }
        public Vector3 Tangent { get; set; }
        public float ConeInnerAngle { get; set; }
        public float ConeOuterAngle { get; set; }
        public Vector3 Extent { get; set; }
        public MetaHash ProjectedTextureHash { get; set; }
        public uint Unknown_A4h { get; set; } // 0x00000000

        public bool UpdateRenderable = false; //used by model light form


        public Quaternion Orientation
        {
            get
            {
                Vector3 tx = new Vector3();
                Vector3 ty = new Vector3();

                switch (Type)
                {
                    case LightType.Point:
                        return Quaternion.Identity;
                    case LightType.Spot:
                        tx = Vector3.Normalize(Tangent);
                        ty = Vector3.Normalize(Vector3.Cross(Direction, Tangent));
                        break;
                    case LightType.Capsule:
                        tx = -Vector3.Normalize(Tangent);
                        ty = Vector3.Normalize(Vector3.Cross(Direction, Tangent));
                        break;
                }

                var m = new Matrix();
                m.Row1 = new Vector4(tx, 0);
                m.Row2 = new Vector4(ty, 0);
                m.Row3 = new Vector4(Direction, 0);
                return Quaternion.RotationMatrix(m);
            }
            set
            {
                var inv = Quaternion.Invert(Orientation);
                var delta = value * inv;
                Direction = Vector3.Normalize(delta.Multiply(Direction));
                Tangent = Vector3.Normalize(delta.Multiply(Tangent));
            }
        }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            //read structure data
            Unknown_0h = reader.ReadUInt32();
            Unknown_4h = reader.ReadUInt32();
            Position = reader.ReadVector3();
            Unknown_14h = reader.ReadUInt32();
            ColorR = reader.ReadByte();
            ColorG = reader.ReadByte();
            ColorB = reader.ReadByte();
            Flashiness = reader.ReadByte();
            Intensity = reader.ReadSingle();
            Flags = reader.ReadUInt32();
            BoneId = reader.ReadUInt16();
            Type = (LightType)reader.ReadByte();
            GroupId = reader.ReadByte();
            TimeFlags = reader.ReadUInt32();
            Falloff = reader.ReadSingle();
            FalloffExponent = reader.ReadSingle();
            CullingPlaneNormal = reader.ReadVector3();
            CullingPlaneOffset = reader.ReadSingle();
            ShadowBlur = reader.ReadByte();
            Unknown_45h = reader.ReadByte();
            Unknown_46h = reader.ReadUInt16();
            Unknown_48h = reader.ReadUInt32();
            VolumeIntensity = reader.ReadSingle();
            VolumeSizeScale = reader.ReadSingle();
            VolumeOuterColorR = reader.ReadByte();
            VolumeOuterColorG = reader.ReadByte();
            VolumeOuterColorB = reader.ReadByte();
            LightHash = reader.ReadByte();
            VolumeOuterIntensity = reader.ReadSingle();
            CoronaSize = reader.ReadSingle();
            VolumeOuterExponent = reader.ReadSingle();
            LightFadeDistance = reader.ReadByte();
            ShadowFadeDistance = reader.ReadByte();
            SpecularFadeDistance = reader.ReadByte();
            VolumetricFadeDistance = reader.ReadByte();
            ShadowNearClip = reader.ReadSingle();
            CoronaIntensity = reader.ReadSingle();
            CoronaZBias = reader.ReadSingle();
            Direction = reader.ReadVector3();
            Tangent = reader.ReadVector3();
            ConeInnerAngle = reader.ReadSingle();
            ConeOuterAngle = reader.ReadSingle();
            Extent = reader.ReadVector3();
            ProjectedTextureHash = new MetaHash(reader.ReadUInt32());
            Unknown_A4h = reader.ReadUInt32();
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            //write structure data
            writer.Write(this.Unknown_0h);
            writer.Write(this.Unknown_4h);
            writer.Write(this.Position);
            writer.Write(this.Unknown_14h);
            writer.Write(this.ColorR);
            writer.Write(this.ColorG);
            writer.Write(this.ColorB);
            writer.Write(this.Flashiness);
            writer.Write(this.Intensity);
            writer.Write(this.Flags);
            writer.Write(this.BoneId);
            writer.Write((byte)this.Type);
            writer.Write(this.GroupId);
            writer.Write(this.TimeFlags);
            writer.Write(this.Falloff);
            writer.Write(this.FalloffExponent);
            writer.Write(this.CullingPlaneNormal);
            writer.Write(this.CullingPlaneOffset);
            writer.Write(this.ShadowBlur);
            writer.Write(this.Unknown_45h);
            writer.Write(this.Unknown_46h);
            writer.Write(this.Unknown_48h);
            writer.Write(this.VolumeIntensity);
            writer.Write(this.VolumeSizeScale);
            writer.Write(this.VolumeOuterColorR);
            writer.Write(this.VolumeOuterColorG);
            writer.Write(this.VolumeOuterColorB);
            writer.Write(this.LightHash);
            writer.Write(this.VolumeOuterIntensity);
            writer.Write(this.CoronaSize);
            writer.Write(this.VolumeOuterExponent);
            writer.Write(this.LightFadeDistance);
            writer.Write(this.ShadowFadeDistance);
            writer.Write(this.SpecularFadeDistance);
            writer.Write(this.VolumetricFadeDistance);
            writer.Write(this.ShadowNearClip);
            writer.Write(this.CoronaIntensity);
            writer.Write(this.CoronaZBias);
            writer.Write(this.Direction);
            writer.Write(this.Tangent);
            writer.Write(this.ConeInnerAngle);
            writer.Write(this.ConeOuterAngle);
            writer.Write(this.Extent);
            writer.Write(this.ProjectedTextureHash.Hash);
            writer.Write(this.Unknown_A4h);
        }

        public void WriteXml(StringBuilder sb, int indent)
        {
            YdrXml.SelfClosingTag(sb, indent, "Position " + FloatUtil.GetVector3XmlString(Position));
            YdrXml.SelfClosingTag(sb, indent, string.Format("Colour r=\"{0}\" g=\"{1}\" b=\"{2}\"", ColorR, ColorG, ColorB));
            YdrXml.ValueTag(sb, indent, "Flashiness", Flashiness.ToString());
            YdrXml.ValueTag(sb, indent, "Intensity", FloatUtil.ToString(Intensity));
            YdrXml.ValueTag(sb, indent, "Flags", Flags.ToString());
            YdrXml.ValueTag(sb, indent, "BoneId", BoneId.ToString());
            YdrXml.StringTag(sb, indent, "Type", Type.ToString());
            YdrXml.ValueTag(sb, indent, "GroupId", GroupId.ToString());
            YdrXml.ValueTag(sb, indent, "TimeFlags", TimeFlags.ToString());
            YdrXml.ValueTag(sb, indent, "Falloff", FloatUtil.ToString(Falloff));
            YdrXml.ValueTag(sb, indent, "FalloffExponent", FloatUtil.ToString(FalloffExponent));
            YdrXml.SelfClosingTag(sb, indent, "CullingPlaneNormal " + FloatUtil.GetVector3XmlString(CullingPlaneNormal));
            YdrXml.ValueTag(sb, indent, "CullingPlaneOffset", FloatUtil.ToString(CullingPlaneOffset));
            YdrXml.ValueTag(sb, indent, "Unknown45", Unknown_45h.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown46", Unknown_46h.ToString());
            YdrXml.ValueTag(sb, indent, "VolumeIntensity", FloatUtil.ToString(VolumeIntensity));
            YdrXml.ValueTag(sb, indent, "VolumeSizeScale", FloatUtil.ToString(VolumeSizeScale));
            YdrXml.SelfClosingTag(sb, indent, string.Format("VolumeOuterColour r=\"{0}\" g=\"{1}\" b=\"{2}\"", VolumeOuterColorR, VolumeOuterColorG, VolumeOuterColorB));
            YdrXml.ValueTag(sb, indent, "LightHash", LightHash.ToString());
            YdrXml.ValueTag(sb, indent, "VolumeOuterIntensity", FloatUtil.ToString(VolumeOuterIntensity));
            YdrXml.ValueTag(sb, indent, "CoronaSize", FloatUtil.ToString(CoronaSize));
            YdrXml.ValueTag(sb, indent, "VolumeOuterExponent", FloatUtil.ToString(VolumeOuterExponent));
            YdrXml.ValueTag(sb, indent, "LightFadeDistance", LightFadeDistance.ToString());
            YdrXml.ValueTag(sb, indent, "ShadowFadeDistance", ShadowFadeDistance.ToString());
            YdrXml.ValueTag(sb, indent, "SpecularFadeDistance", SpecularFadeDistance.ToString());
            YdrXml.ValueTag(sb, indent, "VolumetricFadeDistance", VolumetricFadeDistance.ToString());
            YdrXml.ValueTag(sb, indent, "ShadowNearClip", FloatUtil.ToString(ShadowNearClip));
            YdrXml.ValueTag(sb, indent, "CoronaIntensity", FloatUtil.ToString(CoronaIntensity));
            YdrXml.ValueTag(sb, indent, "CoronaZBias", FloatUtil.ToString(CoronaZBias));
            YdrXml.SelfClosingTag(sb, indent, "Direction " + FloatUtil.GetVector3XmlString(Direction));
            YdrXml.SelfClosingTag(sb, indent, "Tangent " + FloatUtil.GetVector3XmlString(Tangent));
            YdrXml.ValueTag(sb, indent, "ConeInnerAngle", FloatUtil.ToString(ConeInnerAngle));
            YdrXml.ValueTag(sb, indent, "ConeOuterAngle", FloatUtil.ToString(ConeOuterAngle));
            YdrXml.SelfClosingTag(sb, indent, "Extent " + FloatUtil.GetVector3XmlString(Extent));
            YdrXml.StringTag(sb, indent, "ProjectedTextureHash", YdrXml.HashString(ProjectedTextureHash));
        }
        public void ReadXml(XmlNode node)
        {
            Position = Xml.GetChildVector3Attributes(node, "Position");
            ColorR = (byte)Xml.GetChildUIntAttribute(node, "Colour", "r");
            ColorG = (byte)Xml.GetChildUIntAttribute(node, "Colour", "g");
            ColorB = (byte)Xml.GetChildUIntAttribute(node, "Colour", "b");
            Flashiness = (byte)Xml.GetChildUIntAttribute(node, "Flashiness", "value");
            Intensity = Xml.GetChildFloatAttribute(node, "Intensity", "value");
            Flags = Xml.GetChildUIntAttribute(node, "Flags", "value");
            BoneId = (ushort)Xml.GetChildUIntAttribute(node, "BoneId", "value");
            Type = Xml.GetChildEnumInnerText<LightType>(node, "Type");
            GroupId = (byte)Xml.GetChildUIntAttribute(node, "GroupId", "value");
            TimeFlags = Xml.GetChildUIntAttribute(node, "TimeFlags", "value");
            Falloff = Xml.GetChildFloatAttribute(node, "Falloff", "value");
            FalloffExponent = Xml.GetChildFloatAttribute(node, "FalloffExponent", "value");
            CullingPlaneNormal = Xml.GetChildVector3Attributes(node, "CullingPlaneNormal");
            CullingPlaneOffset = Xml.GetChildFloatAttribute(node, "CullingPlaneOffset", "value");
            Unknown_45h = (byte)Xml.GetChildUIntAttribute(node, "Unknown45", "value");
            Unknown_46h = (ushort)Xml.GetChildUIntAttribute(node, "Unknown46", "value");
            VolumeIntensity = Xml.GetChildFloatAttribute(node, "VolumeIntensity", "value");
            VolumeSizeScale = Xml.GetChildFloatAttribute(node, "VolumeSizeScale", "value");
            VolumeOuterColorR = (byte)Xml.GetChildUIntAttribute(node, "VolumeOuterColour", "r");
            VolumeOuterColorG = (byte)Xml.GetChildUIntAttribute(node, "VolumeOuterColour", "g");
            VolumeOuterColorB = (byte)Xml.GetChildUIntAttribute(node, "VolumeOuterColour", "b");
            LightHash = (byte)Xml.GetChildUIntAttribute(node, "LightHash", "value");
            VolumeOuterIntensity = Xml.GetChildFloatAttribute(node, "VolumeOuterIntensity", "value");
            CoronaSize = Xml.GetChildFloatAttribute(node, "CoronaSize", "value");
            VolumeOuterExponent = Xml.GetChildFloatAttribute(node, "VolumeOuterExponent", "value");
            LightFadeDistance = (byte)Xml.GetChildUIntAttribute(node, "LightFadeDistance", "value");
            ShadowFadeDistance = (byte)Xml.GetChildUIntAttribute(node, "ShadowFadeDistance", "value");
            SpecularFadeDistance = (byte)Xml.GetChildUIntAttribute(node, "SpecularFadeDistance", "value");
            VolumetricFadeDistance = (byte)Xml.GetChildUIntAttribute(node, "VolumetricFadeDistance", "value");
            ShadowNearClip = Xml.GetChildFloatAttribute(node, "ShadowNearClip", "value");
            CoronaIntensity = Xml.GetChildFloatAttribute(node, "CoronaIntensity", "value");
            CoronaZBias = Xml.GetChildFloatAttribute(node, "CoronaZBias", "value");
            Direction = Xml.GetChildVector3Attributes(node, "Direction");
            Tangent = Xml.GetChildVector3Attributes(node, "Tangent");
            ConeInnerAngle = Xml.GetChildFloatAttribute(node, "ConeInnerAngle", "value");
            ConeOuterAngle = Xml.GetChildFloatAttribute(node, "ConeOuterAngle", "value");
            Extent = Xml.GetChildVector3Attributes(node, "Extent");
            ProjectedTextureHash = XmlMeta.GetHash(Xml.GetChildInnerText(node, "ProjectedTextureHash"));
        }

    }



    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableBase : ResourceFileBase
    {
        public override long BlockLength
        {
            get { return 168; }
        }

        // structure data
        public ulong ShaderGroupPointer { get; set; }
        public ulong SkeletonPointer { get; set; }
        public Vector3 BoundingCenter { get; set; }
        public float BoundingSphereRadius { get; set; }
        public Vector3 BoundingBoxMin { get; set; }
        public uint Unknown_3Ch { get; set; } = 0x7f800001;
        public Vector3 BoundingBoxMax { get; set; }
        public uint Unknown_4Ch { get; set; } = 0x7f800001;
        public ulong DrawableModelsHighPointer { get; set; }
        public ulong DrawableModelsMediumPointer { get; set; }
        public ulong DrawableModelsLowPointer { get; set; }
        public ulong DrawableModelsVeryLowPointer { get; set; }
        public float LodDistHigh { get; set; }
        public float LodDistMed { get; set; }
        public float LodDistLow { get; set; }
        public float LodDistVlow { get; set; }
        public uint RenderMaskFlagsHigh { get; set; }
        public uint RenderMaskFlagsMed { get; set; }
        public uint RenderMaskFlagsLow { get; set; }
        public uint RenderMaskFlagsVlow { get; set; }
        public ulong JointsPointer { get; set; }
        public ushort Unknown_98h { get; set; } // 0x0000
        public ushort Unknown_9Ah { get; set; }
        public uint Unknown_9Ch { get; set; } // 0x00000000
        public ulong DrawableModelsPointer { get; set; }

        public byte FlagsHigh
        {
            get { return (byte)(RenderMaskFlagsHigh & 0xFF); }
            set { RenderMaskFlagsHigh = (RenderMaskFlagsHigh & 0xFFFFFF00) + (value & 0xFFu); }
        }
        public byte FlagsMed
        {
            get { return (byte)(RenderMaskFlagsMed & 0xFF); }
            set { RenderMaskFlagsMed = (RenderMaskFlagsMed & 0xFFFFFF00) + (value & 0xFFu); }
        }
        public byte FlagsLow
        {
            get { return (byte)(RenderMaskFlagsLow & 0xFF); }
            set { RenderMaskFlagsLow = (RenderMaskFlagsLow & 0xFFFFFF00) + (value & 0xFFu); }
        }
        public byte FlagsVlow
        {
            get { return (byte)(RenderMaskFlagsVlow & 0xFF); }
            set { RenderMaskFlagsVlow = (RenderMaskFlagsVlow & 0xFFFFFF00) + (value & 0xFFu); }
        }
        public byte RenderMaskHigh
        {
            get { return (byte)((RenderMaskFlagsHigh >> 8) & 0xFF); }
            set { RenderMaskFlagsHigh = (RenderMaskFlagsHigh & 0xFFFF00FF) + ((value & 0xFFu) << 8); }
        }
        public byte RenderMaskMed
        {
            get { return (byte)((RenderMaskFlagsMed >> 8) & 0xFF); }
            set { RenderMaskFlagsMed = (RenderMaskFlagsMed & 0xFFFF00FF) + ((value & 0xFFu) << 8); }
        }
        public byte RenderMaskLow
        {
            get { return (byte)((RenderMaskFlagsLow >> 8) & 0xFF); }
            set { RenderMaskFlagsLow = (RenderMaskFlagsLow & 0xFFFF00FF) + ((value & 0xFFu) << 8); }
        }
        public byte RenderMaskVlow
        {
            get { return (byte)((RenderMaskFlagsVlow >> 8) & 0xFF); }
            set { RenderMaskFlagsVlow = (RenderMaskFlagsVlow & 0xFFFF00FF) + ((value & 0xFFu) << 8); }
        }


        // reference data
        public ShaderGroup ShaderGroup { get; set; }
        public Skeleton Skeleton { get; set; }
        public Joints Joints { get; set; }
        public DrawableModelsBlock DrawableModels { get; set; }


        public DrawableModel[] AllModels { get; set; }
        public Dictionary<ulong, VertexDeclaration> VertexDecls { get; set; }

        public object Owner { get; set; }

        public long MemoryUsage
        {
            get
            {
                long val = 0;
                if (AllModels != null)
                {
                    foreach(DrawableModel m in AllModels)
                    {
                        if (m != null)
                        {
                            val += m.MemoryUsage;
                        }
                    }
                }
                if ((ShaderGroup != null) && (ShaderGroup.TextureDictionary != null))
                {
                    val += ShaderGroup.TextureDictionary.MemoryUsage;
                }
                return val;
            }
        }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            base.Read(reader, parameters);

            // read structure data
            this.ShaderGroupPointer = reader.ReadUInt64();
            this.SkeletonPointer = reader.ReadUInt64();
            this.BoundingCenter = reader.ReadVector3();
            this.BoundingSphereRadius = reader.ReadSingle();
            this.BoundingBoxMin = reader.ReadVector3();
            this.Unknown_3Ch = reader.ReadUInt32();
            this.BoundingBoxMax = reader.ReadVector3();
            this.Unknown_4Ch = reader.ReadUInt32();
            this.DrawableModelsHighPointer = reader.ReadUInt64();
            this.DrawableModelsMediumPointer = reader.ReadUInt64();
            this.DrawableModelsLowPointer = reader.ReadUInt64();
            this.DrawableModelsVeryLowPointer = reader.ReadUInt64();
            this.LodDistHigh = reader.ReadSingle();
            this.LodDistMed = reader.ReadSingle();
            this.LodDistLow = reader.ReadSingle();
            this.LodDistVlow = reader.ReadSingle();
            this.RenderMaskFlagsHigh = reader.ReadUInt32();
            this.RenderMaskFlagsMed = reader.ReadUInt32();
            this.RenderMaskFlagsLow = reader.ReadUInt32();
            this.RenderMaskFlagsVlow = reader.ReadUInt32();
            this.JointsPointer = reader.ReadUInt64();
            this.Unknown_98h = reader.ReadUInt16();
            this.Unknown_9Ah = reader.ReadUInt16();
            this.Unknown_9Ch = reader.ReadUInt32();
            this.DrawableModelsPointer = reader.ReadUInt64();

            // read reference data
            this.ShaderGroup = reader.ReadBlockAt<ShaderGroup>(this.ShaderGroupPointer);
            this.Skeleton = reader.ReadBlockAt<Skeleton>(this.SkeletonPointer);
            this.Joints = reader.ReadBlockAt<Joints>(this.JointsPointer);
            this.DrawableModels = reader.ReadBlockAt<DrawableModelsBlock>((DrawableModelsPointer == 0) ? DrawableModelsHighPointer : DrawableModelsPointer, this);


            BuildAllModels();
            BuildVertexDecls();
            AssignGeometryShaders(ShaderGroup);


            ////just testing!!!

            //long pad(long o) => ((16 - (o % 16)) % 16);
            //long listlength(DrawableModel[] list)
            //{
            //    long l = 16;
            //    l += (list.Length) * 8;
            //    foreach (var m in list) l += pad(l) + m.BlockLength;
            //    return l;
            //}
            //var ptr = (long)DrawableModelsPointer;
            //if (DrawableModels?.High != null)
            //{
            //    if (ptr != (long)DrawableModelsHighPointer)
            //    { }//no hit
            //    ptr += listlength(DrawableModels?.High);
            //}
            //if (DrawableModels?.Med != null)
            //{
            //    ptr += pad(ptr);
            //    if (ptr != (long)DrawableModelsMediumPointer)
            //    { }//no hit
            //    ptr += listlength(DrawableModels?.Med);
            //}
            //if (DrawableModels?.Low != null)
            //{
            //    ptr += pad(ptr);
            //    if (ptr != (long)DrawableModelsLowPointer)
            //    { }//no hit
            //    ptr += listlength(DrawableModels?.Low);
            //}
            //if (DrawableModels?.VLow != null)
            //{
            //    ptr += pad(ptr);
            //    if (ptr != (long)DrawableModelsVeryLowPointer)
            //    { }//no hit
            //    ptr += listlength(DrawableModels?.VLow);
            //}


            //switch (Unknown_3Ch)
            //{
            //    case 0x7f800001:
            //    case 0: //only in yft's!
            //        break;
            //    default:
            //        break;
            //}
            //switch (Unknown_4Ch)
            //{
            //    case 0x7f800001:
            //    case 0: //only in yft's!
            //        break;
            //    default:
            //        break;
            //}
            //if ((DrawableModelsHigh?.data_items != null) != (Unknown_80h != 0))
            //{ }//no hit
            //if ((DrawableModelsMedium?.data_items != null) != (Unknown_84h != 0))
            //{ }//no hit
            //if ((DrawableModelsLow?.data_items != null) != (Unknown_88h != 0))
            //{ }//no hit
            //if ((DrawableModelsVeryLow?.data_items != null) != (Unknown_8Ch != 0))
            //{ }//no hit
            //if ((Unknown_80h & 0xFFFF0000) > 0)
            //{ }//no hit
            //if ((Unknown_84h & 0xFFFF0000) > 0)
            //{ }//no hit
            //if ((Unknown_88h & 0xFFFF0000) > 0)
            //{ }//no hit
            //if ((Unknown_8Ch & 0xFFFF0000) > 0)
            //{ }//no hit
            //BuildRenderMasks();

            //switch (FlagsHigh)
            //{
            //    case 2:
            //    case 1:
            //    case 13:
            //    case 4:
            //    case 12:
            //    case 5:
            //    case 3:
            //    case 8:
            //    case 9:
            //    case 15:
            //    case 130:
            //    case 11:
            //    case 10:
            //    case 7:
            //    case 131:
            //    case 129:
            //    case 75:
            //    case 69:
            //    case 6:
            //    case 64:
            //    case 14:
            //    case 77:
            //    case 73:
            //    case 76:
            //    case 71:
            //    case 79:
            //    case 65:
            //    case 0://some yft's have null HD models
            //    case 19:
            //    case 51:
            //        break;
            //    default:
            //        break;
            //}
            //switch (FlagsMed)
            //{
            //    case 0:
            //    case 1:
            //    case 9:
            //    case 8:
            //    case 13:
            //    case 3:
            //    case 2:
            //    case 5:
            //    case 11:
            //    case 15:
            //    case 10:
            //    case 12:
            //    case 4:
            //    case 7:
            //    case 51:
            //        break;
            //    default:
            //        break;
            //}
            //switch (FlagsLow)
            //{
            //    case 0:
            //    case 9:
            //    case 1:
            //    case 8:
            //    case 5:
            //    case 3:
            //    case 13:
            //    case 2:
            //    case 11:
            //    case 15:
            //    case 4:
            //    case 7:
            //    case 51:
            //        break;
            //    default:
            //        break;
            //}
            //switch (FlagsVlow)
            //{
            //    case 0:
            //    case 1:
            //    case 9:
            //    case 3:
            //    case 7:
            //    case 5:
            //    case 49:
            //    case 51:
            //    case 11:
            //        break;
            //    default:
            //        break;
            //}
            //switch (Unknown_98h)
            //{
            //    case 0:
            //        break;
            //    default:
            //        break;//no hit
            //}
            //switch (Unknown_9Ah)//wtf isthis? flags?
            //{
            //    case 18:
            //    case 33:
            //    case 34:
            //    case 46:
            //    case 58:
            //    case 209:
            //    case 71:
            //    case 172:
            //    case 64:
            //    case 122:
            //    case 96:
            //    case 248:
            //    case 147:
            //    case 51:
            //    case 159:
            //    case 134:
            //    case 108:
            //    case 83:
            //    case 336:
            //    case 450:
            //    case 197:
            //    case 374:
            //    case 184:
            //    case 310:
            //    case 36:
            //    case 386:
            //    case 285:
            //    case 260:
            //    case 77:
            //    case 361:
            //    case 235:
            //    case 91:
            //    case 223:
            //    case 1207:
            //    case 2090:
            //    case 45:
            //    case 52:
            //    case 526:
            //    case 3081:
            //    case 294:
            //    case 236:
            //    case 156:
            //        break;
            //    default://still lots more..
            //        break;
            //}

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            base.Write(writer, parameters);

            // update structure data
            this.ShaderGroupPointer = (ulong)(this.ShaderGroup != null ? this.ShaderGroup.FilePosition : 0);
            this.SkeletonPointer = (ulong)(this.Skeleton != null ? this.Skeleton.FilePosition : 0);
            this.DrawableModelsHighPointer = (ulong)(DrawableModels?.GetHighPointer() ?? 0);
            this.DrawableModelsMediumPointer = (ulong)(DrawableModels?.GetMedPointer() ?? 0);
            this.DrawableModelsLowPointer = (ulong)(DrawableModels?.GetLowPointer() ?? 0);
            this.DrawableModelsVeryLowPointer = (ulong)(DrawableModels?.GetVLowPointer() ?? 0);
            this.JointsPointer = (ulong)(this.Joints != null ? this.Joints.FilePosition : 0);
            this.DrawableModelsPointer = (ulong)(DrawableModels?.FilePosition ?? 0);

            // write structure data
            writer.Write(this.ShaderGroupPointer);
            writer.Write(this.SkeletonPointer);
            writer.Write(this.BoundingCenter);
            writer.Write(this.BoundingSphereRadius);
            writer.Write(this.BoundingBoxMin);
            writer.Write(this.Unknown_3Ch);
            writer.Write(this.BoundingBoxMax);
            writer.Write(this.Unknown_4Ch);
            writer.Write(this.DrawableModelsHighPointer);
            writer.Write(this.DrawableModelsMediumPointer);
            writer.Write(this.DrawableModelsLowPointer);
            writer.Write(this.DrawableModelsVeryLowPointer);
            writer.Write(this.LodDistHigh);
            writer.Write(this.LodDistMed);
            writer.Write(this.LodDistLow);
            writer.Write(this.LodDistVlow);
            writer.Write(this.RenderMaskFlagsHigh);
            writer.Write(this.RenderMaskFlagsMed);
            writer.Write(this.RenderMaskFlagsLow);
            writer.Write(this.RenderMaskFlagsVlow);
            writer.Write(this.JointsPointer);
            writer.Write(this.Unknown_98h);
            writer.Write(this.Unknown_9Ah);
            writer.Write(this.Unknown_9Ch);
            writer.Write(this.DrawableModelsPointer);
        }
        public virtual void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            YdrXml.SelfClosingTag(sb, indent, "BoundingSphereCenter " + FloatUtil.GetVector3XmlString(BoundingCenter));
            YdrXml.ValueTag(sb, indent, "BoundingSphereRadius", FloatUtil.ToString(BoundingSphereRadius));
            YdrXml.SelfClosingTag(sb, indent, "BoundingBoxMin " + FloatUtil.GetVector3XmlString(BoundingBoxMin));
            YdrXml.SelfClosingTag(sb, indent, "BoundingBoxMax " + FloatUtil.GetVector3XmlString(BoundingBoxMax));
            YdrXml.ValueTag(sb, indent, "LodDistHigh", FloatUtil.ToString(LodDistHigh));
            YdrXml.ValueTag(sb, indent, "LodDistMed", FloatUtil.ToString(LodDistMed));
            YdrXml.ValueTag(sb, indent, "LodDistLow", FloatUtil.ToString(LodDistLow));
            YdrXml.ValueTag(sb, indent, "LodDistVlow", FloatUtil.ToString(LodDistVlow));
            YdrXml.ValueTag(sb, indent, "FlagsHigh", FlagsHigh.ToString());
            YdrXml.ValueTag(sb, indent, "FlagsMed", FlagsMed.ToString());
            YdrXml.ValueTag(sb, indent, "FlagsLow", FlagsLow.ToString());
            YdrXml.ValueTag(sb, indent, "FlagsVlow", FlagsVlow.ToString());
            YdrXml.ValueTag(sb, indent, "Unknown9A", Unknown_9Ah.ToString());
            if (ShaderGroup != null)
            {
                YdrXml.OpenTag(sb, indent, "ShaderGroup");
                ShaderGroup.WriteXml(sb, indent + 1, ddsfolder);
                YdrXml.CloseTag(sb, indent, "ShaderGroup");
            }
            if (Skeleton != null)
            {
                YdrXml.OpenTag(sb, indent, "Skeleton");
                Skeleton.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "Skeleton");
            }
            if (Joints != null)
            {
                YdrXml.OpenTag(sb, indent, "Joints");
                Joints.WriteXml(sb, indent + 1);
                YdrXml.CloseTag(sb, indent, "Joints");
            }
            if (DrawableModels?.High != null)
            {
                YdrXml.WriteItemArray(sb, DrawableModels.High, indent, "DrawableModelsHigh");
            }
            if (DrawableModels?.Med != null)
            {
                YdrXml.WriteItemArray(sb, DrawableModels.Med, indent, "DrawableModelsMedium");
            }
            if (DrawableModels?.Low != null)
            {
                YdrXml.WriteItemArray(sb, DrawableModels.Low, indent, "DrawableModelsLow");
            }
            if (DrawableModels?.VLow != null)
            {
                YdrXml.WriteItemArray(sb, DrawableModels.VLow, indent, "DrawableModelsVeryLow");
            }
            if (DrawableModels?.Extra != null)//is this right? duplicates..?
            {
                YdrXml.WriteItemArray(sb, DrawableModels.Extra, indent, "DrawableModelsX");
            }
        }
        public virtual void ReadXml(XmlNode node, string ddsfolder)
        {
            BoundingCenter = Xml.GetChildVector3Attributes(node, "BoundingSphereCenter");
            BoundingSphereRadius = Xml.GetChildFloatAttribute(node, "BoundingSphereRadius", "value");
            BoundingBoxMin = Xml.GetChildVector3Attributes(node, "BoundingBoxMin");
            BoundingBoxMax = Xml.GetChildVector3Attributes(node, "BoundingBoxMax");
            LodDistHigh = Xml.GetChildFloatAttribute(node, "LodDistHigh", "value");
            LodDistMed = Xml.GetChildFloatAttribute(node, "LodDistMed", "value");
            LodDistLow = Xml.GetChildFloatAttribute(node, "LodDistLow", "value");
            LodDistVlow = Xml.GetChildFloatAttribute(node, "LodDistVlow", "value");
            FlagsHigh = (byte)Xml.GetChildUIntAttribute(node, "FlagsHigh", "value");
            FlagsMed = (byte)Xml.GetChildUIntAttribute(node, "FlagsMed", "value");
            FlagsLow = (byte)Xml.GetChildUIntAttribute(node, "FlagsLow", "value");
            FlagsVlow = (byte)Xml.GetChildUIntAttribute(node, "FlagsVlow", "value");
            Unknown_9Ah = (ushort)Xml.GetChildUIntAttribute(node, "Unknown9A", "value");
            var sgnode = node.SelectSingleNode("ShaderGroup");
            if (sgnode != null)
            {
                ShaderGroup = new ShaderGroup();
                ShaderGroup.ReadXml(sgnode, ddsfolder);
            }
            var sknode = node.SelectSingleNode("Skeleton");
            if (sknode != null)
            {
                Skeleton = new Skeleton();
                Skeleton.ReadXml(sknode);
            }
            var jnode = node.SelectSingleNode("Joints");
            if (jnode != null)
            {
                Joints = new Joints();
                Joints.ReadXml(jnode);
            }
            this.DrawableModels = new DrawableModelsBlock();
            this.DrawableModels.High = XmlMeta.ReadItemArray<DrawableModel>(node, "DrawableModelsHigh");
            this.DrawableModels.Med = XmlMeta.ReadItemArray<DrawableModel>(node, "DrawableModelsMedium");
            this.DrawableModels.Low = XmlMeta.ReadItemArray<DrawableModel>(node, "DrawableModelsLow");
            this.DrawableModels.VLow = XmlMeta.ReadItemArray<DrawableModel>(node, "DrawableModelsVeryLow");
            this.DrawableModels.Extra = XmlMeta.ReadItemArray<DrawableModel>(node, "DrawableModelsX");
            if (DrawableModels.BlockLength == 0)
            {
                DrawableModels = null;
            }

            BuildRenderMasks();
            BuildAllModels();
            BuildVertexDecls();

            FileVFT = 1079456120;
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>(base.GetReferences());
            if (ShaderGroup != null) list.Add(ShaderGroup);
            if (Skeleton != null) list.Add(Skeleton);
            if (Joints != null) list.Add(Joints);
            if (DrawableModels != null) list.Add(DrawableModels);
            return list.ToArray();
        }


        public void AssignGeometryShaders(ShaderGroup shaderGrp)
        {
            //if (shaderGrp != null)
            //{
            //    ShaderGroup = shaderGrp;
            //}

            //map the shaders to the geometries
            if (shaderGrp != null)
            {
                var shaders = shaderGrp.Shaders.data_items;
                foreach (DrawableModel model in AllModels)
                {
                    if (model?.Geometries == null) continue;

                    int geomcount = model.Geometries.Length;
                    for (int i = 0; i < geomcount; i++)
                    {
                        var geom = model.Geometries[i];
                        var sid = geom.ShaderID;
                        geom.Shader = (sid < shaders.Length) ? shaders[sid] : null;
                    }
                }
            }
            else
            {
            }

        }



        public void BuildAllModels()
        {
            var allModels = new List<DrawableModel>();
            if (DrawableModels?.High != null) allModels.AddRange(DrawableModels.High);
            if (DrawableModels?.Med != null) allModels.AddRange(DrawableModels.Med);
            if (DrawableModels?.Low != null) allModels.AddRange(DrawableModels.Low);
            if (DrawableModels?.VLow != null) allModels.AddRange(DrawableModels.VLow);
            if (DrawableModels?.Extra != null) allModels.AddRange(DrawableModels.Extra);
            AllModels = allModels.ToArray();
        }

        public void BuildVertexDecls()
        {
            var vds = new Dictionary<ulong, VertexDeclaration>();
            foreach (DrawableModel model in AllModels)
            {
                if (model.Geometries == null) continue;
                foreach (var geom in model.Geometries)
                {
                    var info = geom.VertexBuffer.Info;
                    var declid = info.GetDeclarationId();

                    if (!vds.ContainsKey(declid))
                    {
                        vds.Add(declid, info);
                    }
                    //else //debug test
                    //{
                    //    if ((VertexDecls[declid].Stride != info.Stride)||(VertexDecls[declid].Types != info.Types))
                    //    {
                    //    }
                    //}
                }
            }
            VertexDecls = new Dictionary<ulong, VertexDeclaration>(vds);
        }


        public void BuildRenderMasks()
        {
            var hmask = BuildRenderMask(DrawableModels?.High);
            var mmask = BuildRenderMask(DrawableModels?.Med);
            var lmask = BuildRenderMask(DrawableModels?.Low);
            var vmask = BuildRenderMask(DrawableModels?.VLow);

            ////just testing
            //if (hmask != RenderMaskHigh)
            //{ }//no hit
            //if (mmask != RenderMaskMed)
            //{ }//no hit
            //if (lmask != RenderMaskLow)
            //{ }//no hit
            //if (vmask != RenderMaskVlow)
            //{ }//no hit

            RenderMaskHigh = hmask;
            RenderMaskMed = mmask;
            RenderMaskLow = lmask;
            RenderMaskVlow = vmask;

        }
        private byte BuildRenderMask(DrawableModel[] models)
        {
            byte mask = 0;
            if (models != null)
            {
                foreach (var model in models)
                {
                    mask = (byte)(mask | model.RenderMask);
                }
            }
            return mask;
        }


        public DrawableBase ShallowCopy()
        {
            DrawableBase r = null;
            if (this is FragDrawable fd)
            {
                var f = new FragDrawable();
                f.FragMatrix = fd.FragMatrix;
                f.FragMatricesIndsCount = fd.FragMatricesIndsCount;
                f.FragMatricesCapacity = fd.FragMatricesCapacity;
                f.FragMatricesCount = fd.FragMatricesCount;
                f.Bound = fd.Bound;
                f.FragMatricesInds = fd.FragMatricesInds;
                f.FragMatrices = fd.FragMatrices;
                f.Name = fd.Name;
                f.OwnerFragment = fd.OwnerFragment;
                f.OwnerFragmentPhys = fd.OwnerFragmentPhys;
                r = f;
            }
            if (this is Drawable dd)
            {
                var d = new Drawable();
                d.LightAttributes = dd.LightAttributes;
                d.Name = dd.Name;
                d.Bound = dd.Bound;
                r = d;
            }
            if (r != null)
            {
                r.BoundingCenter = BoundingCenter;
                r.BoundingSphereRadius = BoundingSphereRadius;
                r.BoundingBoxMin = BoundingBoxMin;
                r.BoundingBoxMax = BoundingBoxMax;
                r.LodDistHigh = LodDistHigh;
                r.LodDistMed = LodDistMed;
                r.LodDistLow = LodDistLow;
                r.LodDistVlow = LodDistVlow;
                r.RenderMaskFlagsHigh = RenderMaskFlagsHigh;
                r.RenderMaskFlagsMed = RenderMaskFlagsMed;
                r.RenderMaskFlagsLow = RenderMaskFlagsLow;
                r.RenderMaskFlagsVlow = RenderMaskFlagsVlow;
                r.Unknown_98h = Unknown_98h;
                r.Unknown_9Ah = Unknown_9Ah;
                r.ShaderGroup = ShaderGroup;
                r.Skeleton = Skeleton?.Clone();
                r.DrawableModels = new DrawableModelsBlock();
                r.DrawableModels.High = DrawableModels?.High;
                r.DrawableModels.Med = DrawableModels?.Med;
                r.DrawableModels.Low = DrawableModels?.Low;
                r.DrawableModels.VLow = DrawableModels?.VLow;
                r.DrawableModels.Extra = DrawableModels?.Extra;
                r.Joints = Joints;
                r.AllModels = AllModels;
                r.VertexDecls = VertexDecls;
                r.Owner = Owner;
            }
            return r;
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class Drawable : DrawableBase
    {
        public override long BlockLength
        {
            get { return 208; }
        }

        // structure data
        public ulong NamePointer { get; set; }
        public ResourceSimpleList64<LightAttributes> LightAttributes { get; set; }
        public ulong UnkPointer { get; set; } 
        public ulong BoundPointer { get; set; }

        // reference data
        public string Name { get; set; }
        public Bounds Bound { get; set; }

        public string ErrorMessage { get; set; }


        private string_r NameBlock = null;//only used when saving..


#if DEBUG
        public ResourceAnalyzer Analyzer { get; set; }
#endif


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            base.Read(reader, parameters);

            // read structure data
            this.NamePointer = reader.ReadUInt64();
            this.LightAttributes = reader.ReadBlock<ResourceSimpleList64<LightAttributes>>();
            this.UnkPointer = reader.ReadUInt64();
            this.BoundPointer = reader.ReadUInt64();

            try
            {

                // read reference data
                this.Name = reader.ReadStringAt(//BlockAt<string_r>(
                    this.NamePointer // offset
                );

                this.Bound = reader.ReadBlockAt<Bounds>(
                    this.BoundPointer // offset
                );

                if (Bound != null)
                {
                    Bound.Owner = this;
                }

            }
            catch (Exception ex) 
            {
                ErrorMessage = ex.ToString();
            }

            if (UnkPointer != 0)
            { }


#if DEBUG
            Analyzer = new ResourceAnalyzer(reader);
#endif

        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            base.Write(writer, parameters);

            // update structure data
            this.NamePointer = (ulong)(this.NameBlock != null ? this.NameBlock.FilePosition : 0);
            this.BoundPointer = (ulong)(this.Bound != null ? this.Bound.FilePosition : 0);

            // write structure data
            writer.Write(this.NamePointer);
            writer.WriteBlock(this.LightAttributes);
            writer.Write(this.UnkPointer);
            writer.Write(this.BoundPointer);
        }
        public override void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            YdrXml.StringTag(sb, indent, "Name", YdrXml.XmlEscape(Name));
            base.WriteXml(sb, indent, ddsfolder);
            if (Bound != null)
            {
                Bounds.WriteXmlNode(Bound, sb, indent);
            }
            if (LightAttributes?.data_items != null)
            {
                YdrXml.WriteItemArray(sb, LightAttributes.data_items, indent, "Lights");
            }
        }
        public override void ReadXml(XmlNode node, string ddsfolder)
        {
            Name = Xml.GetChildInnerText(node, "Name");
            base.ReadXml(node, ddsfolder);
            var bnode = node.SelectSingleNode("Bounds");
            if (bnode != null)
            {
                Bound = Bounds.ReadXmlNode(bnode, this);
            }

            LightAttributes = new ResourceSimpleList64<LightAttributes>();
            LightAttributes.data_items = XmlMeta.ReadItemArray<LightAttributes>(node, "Lights");

        }
        public static void WriteXmlNode(Drawable d, StringBuilder sb, int indent, string ddsfolder, string name = "Drawable")
        {
            if (d == null) return;
            YdrXml.OpenTag(sb, indent, name);
            d.WriteXml(sb, indent + 1, ddsfolder);
            YdrXml.CloseTag(sb, indent, name);
        }
        public static Drawable ReadXmlNode(XmlNode node, string ddsfolder)
        {
            if (node == null) return null;
            var d = new Drawable();
            d.ReadXml(node, ddsfolder);
            return d;
        }


        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>(base.GetReferences());
            if (Name != null)
            {
                NameBlock = (string_r)Name;
                list.Add(NameBlock);
            }
            if (Bound != null) list.Add(Bound);
            return list.ToArray();
        }
        public override Tuple<long, IResourceBlock>[] GetParts()
        {
            return new Tuple<long, IResourceBlock>[] {
                new Tuple<long, IResourceBlock>(0xB0, LightAttributes),
            };
        }


        public override string ToString()
        {
            return Name;
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawablePtfx : DrawableBase
    {
        public override long BlockLength
        {
            get { return 176; }
        }

        // structure data
        public ulong UnkPointer { get; set; }


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            base.Read(reader, parameters);

            // read structure data
            this.UnkPointer = reader.ReadUInt64();

            if (UnkPointer != 0)
            { }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            base.Write(writer, parameters);

            // write structure data
            writer.Write(this.UnkPointer);
        }
        public override void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            base.WriteXml(sb, indent, ddsfolder);
        }
        public override void ReadXml(XmlNode node, string ddsfolder)
        {
            base.ReadXml(node, ddsfolder);
        }
        public static void WriteXmlNode(DrawablePtfx d, StringBuilder sb, int indent, string ddsfolder, string name = "Drawable")
        {
            if (d == null) return;
            YdrXml.OpenTag(sb, indent, name);
            d.WriteXml(sb, indent + 1, ddsfolder);
            YdrXml.CloseTag(sb, indent, name);
        }
        public static DrawablePtfx ReadXmlNode(XmlNode node, string ddsfolder)
        {
            if (node == null) return null;
            var d = new DrawablePtfx();
            d.ReadXml(node, ddsfolder);
            return d;
        }

    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawablePtfxDictionary : ResourceFileBase
    {
        public override long BlockLength
        {
            get { return 64; }
        }

        // structure data
        public ulong Unknown_10h; // 0x0000000000000000
        public ulong Unknown_18h = 1; // 0x0000000000000001
        public ulong HashesPointer { get; set; }
        public ushort HashesCount1 { get; set; }
        public ushort HashesCount2 { get; set; }
        public uint Unknown_2Ch { get; set; }
        public ulong DrawablesPointer { get; set; }
        public ushort DrawablesCount1 { get; set; }
        public ushort DrawablesCount2 { get; set; }
        public uint Unknown_3Ch { get; set; }

        // reference data
        //public ResourceSimpleArray<uint_r> Hashes { get; set; }
        public uint[] Hashes { get; set; }
        public ResourcePointerArray64<DrawablePtfx> Drawables { get; set; }


        private ResourceSystemStructBlock<uint> HashesBlock = null;//only used for saving


        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            base.Read(reader, parameters);

            // read structure data
            this.Unknown_10h = reader.ReadUInt64();
            this.Unknown_18h = reader.ReadUInt64();
            this.HashesPointer = reader.ReadUInt64();
            this.HashesCount1 = reader.ReadUInt16();
            this.HashesCount2 = reader.ReadUInt16();
            this.Unknown_2Ch = reader.ReadUInt32();
            this.DrawablesPointer = reader.ReadUInt64();
            this.DrawablesCount1 = reader.ReadUInt16();
            this.DrawablesCount2 = reader.ReadUInt16();
            this.Unknown_3Ch = reader.ReadUInt32();

            // read reference data
            this.Hashes = reader.ReadUintsAt(this.HashesPointer, this.HashesCount1);
            this.Drawables = reader.ReadBlockAt<ResourcePointerArray64<DrawablePtfx>>(this.DrawablesPointer, this.DrawablesCount1);

            //if (Unknown_10h != 0)
            //{ }
            //if (Unknown_18h != 1)
            //{ }
            //if (Unknown_2Ch != 0)
            //{ }
            //if (Unknown_3Ch != 0)
            //{ }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            base.Write(writer, parameters);

            // update structure data
            this.HashesPointer = (ulong)(this.HashesBlock != null ? this.HashesBlock.FilePosition : 0);
            this.HashesCount1 = (ushort)(this.HashesBlock != null ? this.HashesBlock.ItemCount : 0);
            this.HashesCount2 = (ushort)(this.HashesBlock != null ? this.HashesBlock.ItemCount : 0);
            this.DrawablesPointer = (ulong)(this.Drawables != null ? this.Drawables.FilePosition : 0);
            this.DrawablesCount1 = (ushort)(this.Drawables != null ? this.Drawables.Count : 0);
            this.DrawablesCount2 = (ushort)(this.Drawables != null ? this.Drawables.Count : 0);

            // write structure data
            writer.Write(this.Unknown_10h);
            writer.Write(this.Unknown_18h);
            writer.Write(this.HashesPointer);
            writer.Write(this.HashesCount1);
            writer.Write(this.HashesCount2);
            writer.Write(this.Unknown_2Ch);
            writer.Write(this.DrawablesPointer);
            writer.Write(this.DrawablesCount1);
            writer.Write(this.DrawablesCount2);
            writer.Write(this.Unknown_3Ch);
        }
        public void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            if (Drawables?.data_items != null)
            {
                for (int i=0; i< Drawables.data_items.Length; i++)
                {
                    var d = Drawables.data_items[i];
                    var h = (MetaHash)((i < (Hashes?.Length ?? 0)) ? Hashes[i] : 0);
                    YddXml.OpenTag(sb, indent, "Item");
                    YddXml.StringTag(sb, indent + 1, "Name", YddXml.XmlEscape(h.ToCleanString()));
                    d.WriteXml(sb, indent + 1, ddsfolder);
                    YddXml.CloseTag(sb, indent, "Item");
                }
            }
        }
        public void ReadXml(XmlNode node, string ddsfolder)
        {
            var drawables = new List<DrawablePtfx>();
            var drawablehashes = new List<uint>();

            var inodes = node.SelectNodes("Item");
            if (inodes != null)
            {
                foreach (XmlNode inode in inodes)
                {
                    var h = XmlMeta.GetHash(Xml.GetChildInnerText(inode, "Name"));
                    var d = new DrawablePtfx();
                    d.ReadXml(inode, ddsfolder);
                    drawables.Add(d);
                    drawablehashes.Add(h);
                }
            }
            if (drawables.Count > 0)
            {
                Hashes = drawablehashes.ToArray();
                Drawables = new ResourcePointerArray64<DrawablePtfx>();
                Drawables.data_items = drawables.ToArray();
            }
        }
        public static void WriteXmlNode(DrawablePtfxDictionary d, StringBuilder sb, int indent, string ddsfolder, string name = "DrawableDictionary")
        {
            if (d == null) return;
            YddXml.OpenTag(sb, indent, name);
            d.WriteXml(sb, indent + 1, ddsfolder);
            YddXml.CloseTag(sb, indent, name);
        }
        public static DrawablePtfxDictionary ReadXmlNode(XmlNode node, string ddsfolder)
        {
            if (node == null) return null;
            var d = new DrawablePtfxDictionary();
            d.ReadXml(node, ddsfolder);
            return d;
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>(base.GetReferences());
            if (Hashes != null)
            {
                HashesBlock = new ResourceSystemStructBlock<uint>(Hashes);
                list.Add(HashesBlock);
            }
            if (Drawables != null) list.Add(Drawables);
            return list.ToArray();
        }
    }

    [TypeConverter(typeof(ExpandableObjectConverter))] public class DrawableDictionary : ResourceFileBase
    {
        public override long BlockLength
        {
            get { return 64; }
        }

        // structure data
        public ulong Unknown_10h; // 0x0000000000000000
        public ulong Unknown_18h = 1; // 0x0000000000000001
        public ulong HashesPointer { get; set; }
        public ushort HashesCount1 { get; set; }
        public ushort HashesCount2 { get; set; }
        public uint Unknown_2Ch; // 0x00000000
        public ulong DrawablesPointer { get; set; }
        public ushort DrawablesCount1 { get; set; }
        public ushort DrawablesCount2 { get; set; }
        public uint Unknown_3Ch; // 0x00000000

        // reference data
        //public ResourceSimpleArray<uint_r> Hashes { get; set; }
        public uint[] Hashes { get; set; }
        public ResourcePointerArray64<Drawable> Drawables { get; set; }


        private ResourceSystemStructBlock<uint> HashesBlock = null;//only used for saving


        public long MemoryUsage
        {
            get
            {
                long val = 0;
                if ((Drawables != null) && (Drawables.data_items != null))
                {
                    foreach(var drawable in Drawables.data_items)
                    {
                        val += drawable.MemoryUsage;
                    }
                }
                return val;
            }
        }

        public override void Read(ResourceDataReader reader, params object[] parameters)
        {
            base.Read(reader, parameters);

            // read structure data
            this.Unknown_10h = reader.ReadUInt64();
            this.Unknown_18h = reader.ReadUInt64();
            this.HashesPointer = reader.ReadUInt64();
            this.HashesCount1 = reader.ReadUInt16();
            this.HashesCount2 = reader.ReadUInt16();
            this.Unknown_2Ch = reader.ReadUInt32();
            this.DrawablesPointer = reader.ReadUInt64();
            this.DrawablesCount1 = reader.ReadUInt16();
            this.DrawablesCount2 = reader.ReadUInt16();
            this.Unknown_3Ch = reader.ReadUInt32();

            // read reference data
            this.Hashes = reader.ReadUintsAt(this.HashesPointer, this.HashesCount1);

            this.Drawables = reader.ReadBlockAt<ResourcePointerArray64<Drawable>>(
                this.DrawablesPointer, // offset
                this.DrawablesCount1
            );

            //if (Unknown_10h != 0)
            //{ }
            //if (Unknown_18h != 1)
            //{ }
            //if (Unknown_2Ch != 0)
            //{ }
            //if (Unknown_3Ch != 0)
            //{ }
        }
        public override void Write(ResourceDataWriter writer, params object[] parameters)
        {
            base.Write(writer, parameters);

            // update structure data
            this.HashesPointer = (ulong)(this.HashesBlock != null ? this.HashesBlock.FilePosition : 0);
            this.HashesCount1 = (ushort)(this.HashesBlock != null ? this.HashesBlock.ItemCount : 0);
            this.HashesCount2 = (ushort)(this.HashesBlock != null ? this.HashesBlock.ItemCount : 0);
            this.DrawablesPointer = (ulong)(this.Drawables != null ? this.Drawables.FilePosition : 0);
            this.DrawablesCount1 = (ushort)(this.Drawables != null ? this.Drawables.Count : 0);
            this.DrawablesCount2 = (ushort)(this.Drawables != null ? this.Drawables.Count : 0);

            // write structure data
            writer.Write(this.Unknown_10h);
            writer.Write(this.Unknown_18h);
            writer.Write(this.HashesPointer);
            writer.Write(this.HashesCount1);
            writer.Write(this.HashesCount2);
            writer.Write(this.Unknown_2Ch);
            writer.Write(this.DrawablesPointer);
            writer.Write(this.DrawablesCount1);
            writer.Write(this.DrawablesCount2);
            writer.Write(this.Unknown_3Ch);
        }
        public void WriteXml(StringBuilder sb, int indent, string ddsfolder)
        {
            if (Drawables?.data_items != null)
            {
                foreach (var d in Drawables.data_items)
                {
                    YddXml.OpenTag(sb, indent, "Item");
                    d.WriteXml(sb, indent + 1, ddsfolder);
                    YddXml.CloseTag(sb, indent, "Item");
                }
            }
        }
        public void ReadXml(XmlNode node, string ddsfolder)
        {
            var drawables = new List<Drawable>();
            var drawablehashes = new List<uint>();

            var inodes = node.SelectNodes("Item");
            if (inodes != null)
            {
                foreach (XmlNode inode in inodes)
                {
                    var d = new Drawable();
                    d.ReadXml(inode, ddsfolder);
                    drawables.Add(d);
                    drawablehashes.Add(JenkHash.GenHash(d.Name));//TODO: check this!
                }
            }

            Hashes = drawablehashes.ToArray();
            Drawables = new ResourcePointerArray64<Drawable>();
            Drawables.data_items = drawables.ToArray();
        }
        public static void WriteXmlNode(DrawableDictionary d, StringBuilder sb, int indent, string ddsfolder, string name = "DrawableDictionary")
        {
            if (d == null) return;
            YddXml.OpenTag(sb, indent, name);
            d.WriteXml(sb, indent + 1, ddsfolder);
            YddXml.CloseTag(sb, indent, name);
        }
        public static DrawableDictionary ReadXmlNode(XmlNode node, string ddsfolder)
        {
            if (node == null) return null;
            var d = new DrawableDictionary();
            d.ReadXml(node, ddsfolder);
            return d;
        }

        public override IResourceBlock[] GetReferences()
        {
            var list = new List<IResourceBlock>(base.GetReferences());
            if (Hashes != null)
            {
                HashesBlock = new ResourceSystemStructBlock<uint>(Hashes);
                list.Add(HashesBlock);
            }
            if (Drawables != null) list.Add(Drawables);
            return list.ToArray();
        }
    }


}
