
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.CompilerServices;

using CommonMemoryStream;

namespace Animrig
{
    // enum
    public enum ObjectType
    {
        Any,
        Spine,
        Null,
        Camera,
        AnimationBMP
    }
    public enum LayerType
    {
        None,
        AnimBlend4, AnimBlend2, AnimBlend1,
        SeekBlend4, SeekBlend2, SeekBlend1,
        Transform, Collision, Event, Priority, State, PhysicsForce,
        AnimationBMP
    }
    public enum MixBlend
    {
        Replace, Add
    }
    public struct FloatColor
    {
        public float a, r, g, b;

        public void SetMul(ref FloatColor c1, ref FloatColor c2)
        {
            a = c1.a * c2.a;
            r = c1.r * c2.r;
            g = c1.g * c2.g;
            b = c1.b * c2.b;
        }
        public void SetAdd(ref FloatColor c1, ref FloatColor c2)
        {
            //a = c1.a * c2.a;
            r = Math.Min(c1.r + c2.r , 1.0f);
            g = Math.Min(c1.g + c2.g , 1.0f);
            b = Math.Min(c1.b + c2.b , 1.0f);
        }
    }
    public enum Easing
    {
        None,
        Linear,
        OutQuad, InQuad, OutInQuad, InOutQuad,
        OutExpo, InExpo, OutInExpo, InOutExpo,
        OutElastic, InElastic, OutInElastic, InOutElastic,
        OutBounce, InBounce, OutInBounce, InOutBounce,
        OutBack, InBack, OutInBack, InOutBack,
        WaveSquare, WaveSaw, WaveTriangle, WaveSin,
        Terminate,
        Custom0 = 0x80,
        Custom1 = 0x81,
        Custom2 = 0x82,
        Custom3 = 0x83,
        Custom4 = 0x84,
        Custom5 = 0x85,
        Custom6 = 0x86,
        Custom7 = 0x87,
        CustomTerminate = 0x88
    }
    public struct EasingData
    {
        public float Value;
        public float PreValue;
        public float SetValue;
        // state
        public bool Done;
        public float Duration;
        public float Beta;
        public double Playtime;
        public Easing Mode;
    }
    public enum Spline
    {
        Linear,
        BSpline,
        Hermite,
        BezierAuto,
        Terminate,
        Custom0 = 0x80,
        Custom1 = 0x81,
        Custom2 = 0x82,
        Custom3 = 0x83,
        Custom4 = 0x84,
        Custom5 = 0x85,
        Custom6 = 0x86,
        Custom7 = 0x87,
        CustomTerminate = 0x88
    }
    public enum RotateOrder
    {
        Pass,
        XYZ, XZY, YXZ, YZX, ZXY, ZYX
    }
    public struct Matrix4
    {
        public float _11; public float _12; public float _13; public float _14;
        public float _21; public float _22; public float _23; public float _24;
        public float _31; public float _32; public float _33; public float _34;
        public float _41; public float _42; public float _43; public float _44;
    }
    public struct Vector3
    {
        public float x;
        public float y;
        public float z;
        public float _dummy;
    }
	public struct Quaternion
	{
		public float x, y, z, w;
	}

    public class Misc
    {
        // const
        public static readonly float FloatZero = 0.000001f;
        public const int InputCount = 32;
        public static readonly float InputFloatIndex = 0x10000;
        public static readonly string _noname = "unnamed";
        public const double RadianToAngle = 1.0 / (Math.PI * 2);
        public static readonly Matrix4 IdentityMatrix4 = new Matrix4
        {
            _11 = 1.0f,
            _12 = 0.0f,
            _13 = 0.0f,
            _14 = 0.0f,
            _21 = 0.0f,
            _22 = 1.0f,
            _23 = 0.0f,
            _24 = 0.0f,
            _31 = 0.0f,
            _32 = 0.0f,
            _33 = 1.0f,
            _34 = 0.0f,
            _41 = 0.0f,
            _42 = 0.0f,
            _43 = 0.0f,
            _44 = 1.0f
        };
        public static readonly Vector3 ZeroVector3 = new Vector3
        {
            x = 0.0f,
            y = 0.0f,
            z = 0.0f,
            _dummy = 0.0f
        };
        public static readonly FloatColor WhiteColor = new FloatColor
        {
            a = 1.0f,
            r = 1.0f,
            g = 1.0f,
            b = 1.0f
        };
        public static readonly FloatColor BlackColor = new FloatColor
        {
            a = 1.0f,
            r = 0.0f,
            g = 0.0f,
            b = 0.0f
        };
        public static bool IsInputValue(float f)  // inline;
        {
            if (f < InputFloatIndex) return false;
            // range check
            if (!(Math.Floor(f - InputFloatIndex + 0.5) < InputCount))
            {
                throw new Exception("IsInputValue : out of range.");
            }
            return true;
        }
        // read write
        public static void WriteString(MemoryStream ms, string s)
        {
            CommonMS.WriteString(ms, s);
        }
        public static string ReadString(MemoryStream ms, int max_ = 4096)
        {
            return CommonMS.ReadString(ms, max_);
        }
        public static void WriteFloat(MemoryStream ms, float f)
        {
            CommonMS.WriteFloat(ms, f);
        }
        public static float ReadFloat(MemoryStream ms, float min_ = -0xffffff, float max_ = 0xffffff, bool round_ = false, bool checkspecial = false)
        {
            var f = CommonMS.ReadFloat(ms);

            if (round_)
            {
                if (!checkspecial)
                {
                    f = Math.Min(Math.Max(f, min_), max_);
                }
                else
                {
                    f = Math.Min(Math.Max(f, min_), InputFloatIndex + InputCount - 1.0f);
                }
            }
            else
            {
                if ((f < min_) || (f > max_))
                {
                    throw new FileLoadException("ReadFloat : out of range : " + f.ToString());
                }
            }

            return f;
        }
        public static void WriteInteger(MemoryStream ms, int v)
        {
            CommonMS.WriteInt32(ms, v);
        }
        public static int ReadInteger(MemoryStream ms, int min_ = -0xffffff, int max_ = 0xffffff, bool round_ = false, bool checkspecial = false)
        {
            var v = CommonMS.ReadInt32(ms);

            if (round_)
            {
                if (!checkspecial)
                {
                    v = Math.Min(Math.Max(v, min_), max_);
                }
                else
                {
                    // unsupport
                    // v = Math.Min(Math.Max(v, min_), InputIntIndex + InputCount - 1.0f);
                    v = Math.Min(Math.Max(v, min_), max_);
                }
            }
            else
            {
                if ((v < min_) || (v > max_))
                {
                    throw new FileLoadException($"ReadInteger : out of range : {v}");
                }
            }

            return v;
        }
        public static void WriteBool(MemoryStream ms, bool b)
        {
            CommonMS.WriteBool(ms, b);
        }
        public static bool ReadBool(MemoryStream ms)
        {
            return CommonMS.ReadBool(ms);
        }
        public static void WriteHead(MemoryStream ms, string s)
        {
            CommonMS.WriteHead(ms, s);
        }
        public static void ReadHead(MemoryStream ms, string s)
        {
            CommonMS.ReadHead(ms, s);
        }
        public static void WriteStringList(MemoryStream ms, List<string> sl)
        {
            WriteInteger(ms, sl.Count);
            foreach (var s in sl)
            {
                WriteString(ms, s);
            }
        }
        public static void ReadStringList(MemoryStream ms, List<string> sl)
        {
            sl.Clear();

            var count = ReadInteger(ms, 0, 0xffffff);
            for (var i = 0; i < count; i++)
            {
                var s = ReadString(ms);
                sl.Add(s);
            }
        }
        // data skip function
        public static Int64 WriteSkipBegin(MemoryStream ms)
        {
            var p = ms.Position;
            int diff = 0;
            WriteInteger(ms, diff);
            return p;
        }
        public static void WriteSkipEnd(MemoryStream ms, Int64 pos)
        {
            var p = ms.Position;
            var diff = p - pos;
            ms.Seek(pos, SeekOrigin.Begin);
            WriteInteger(ms, (int)diff);
            ms.Seek(p, SeekOrigin.Begin);
        }
        public static Int64 ReadSkipBegin(MemoryStream ms)
        {
            var p = ms.Position;
            var diff = ReadInteger(ms, 0, 0xffffff);
            return p + diff;
        }
        public static void JumpSkipEnd(MemoryStream ms, Int64 pos)
        {
            // optimize
            if (ms.Position == pos) return;

            ms.Seek(pos, SeekOrigin.Begin);
        }
        // type <--> string
        public static string ObjectTypeToString(ObjectType t)
        {
            switch (t)
            {
                case ObjectType.Any: return "any";
                case ObjectType.Null: return "null";
                case ObjectType.Spine: return "spine";
                case ObjectType.Camera: return "camera";
            }

            throw new Exception("ObjectTypeToString : detect undefined type.");
        }
        public static ObjectType StringToObjectType(string s)
        {
            if (s == "any") return ObjectType.Any;
            else if (s == "null") return ObjectType.Null;
            else if (s == "spine") return ObjectType.Spine;
            else if (s == "camera") return ObjectType.Camera;

            throw new Exception("StringToObjectType : detect undefined type : " + s);
        }
        public static string LayerTypeToString(LayerType t)
        {
            switch (t)
            {
                case LayerType.None: return "none";
                case LayerType.AnimBlend4: return "anim4";
                case LayerType.AnimBlend2: return "anim2";
                case LayerType.AnimBlend1: return "anim1";
                case LayerType.SeekBlend4: return "seek4";
                case LayerType.SeekBlend1: return "seek1";
                case LayerType.Transform: return "transform";
                case LayerType.Collision: return "collision";
                case LayerType.Event: return "event";
                case LayerType.Priority: return "priority";
                case LayerType.State: return "state";
                case LayerType.PhysicsForce: return "physicsforce";
            }
            throw new Exception("LayerTypeToString : detect undefined type.");
        }
        public static LayerType StringToLayerType(string s)
        {
            if (s == "none") return LayerType.None;
            else if (s == "spine4") return LayerType.AnimBlend4;    // compati
            else if (s == "spine1") return LayerType.AnimBlend1;
            else if (s == "anim4") return LayerType.AnimBlend4;
            else if (s == "anim2") return LayerType.AnimBlend2;
            else if (s == "anim1") return LayerType.AnimBlend1;
            else if (s == "seek4") return LayerType.SeekBlend4;
            else if (s == "seek1") return LayerType.SeekBlend1;
            else if (s == "transform") return LayerType.Transform;
            else if (s == "collision") return LayerType.Collision;
            else if (s == "event") return LayerType.Event;
            else if (s == "priority") return LayerType.Priority;
            else if (s == "state") return LayerType.State;
            else if (s == "physicsforce") return LayerType.PhysicsForce;

            throw new Exception("StringToLayerType : detect undefined type : " + s);
        }
        // easing
        public delegate float EasingModeFunction(float x1, float x2, float a, float b);
        public static Easing ReadEasing(MemoryStream ms)
        {
            var v = Misc.ReadInteger(ms, 0, 0xff,true);
            if (v < (int)Easing.Custom0)
            {
                v = Math.Min(v, (int)Easing.Terminate - 1);
                return (Easing)v;
            }
            else
            {
                v = Math.Min(v, (int)Easing.CustomTerminate - 1);
                return (Easing)v;
            }
        }
        public static float EasingFloat(float x1, float x2, float a, float b, Easing mode)
        {
            if (x1 == x2) return x1;

            switch (mode)
            {
                case Easing.None: return a < 1.0f ? x1 : x2;
                case Easing.Linear: return EasingLinear(x1, x2, a);
                case Easing.OutQuad: return EasingOutQuad(x1, x2, a, b);
                case Easing.InQuad: return EasingOutQuad(x1, x2, a, b);
                case Easing.OutInQuad: return EasingSplit(x1, x2, a, b, EasingOutQuad, EasingInQuad);
                case Easing.InOutQuad: return EasingSplit(x1, x2, a, b, EasingInQuad, EasingOutQuad);
                case Easing.OutExpo: return EasingOutExpo(x1, x2, a, b);
                case Easing.InExpo: return EasingInExpo(x1, x2, a, b);
                case Easing.OutInExpo: return EasingSplit(x1, x2, a, b, EasingOutExpo, EasingInExpo);
                case Easing.InOutExpo: return EasingSplit(x1, x2, a, b, EasingInExpo, EasingOutExpo);
                case Easing.OutElastic: return EasingOutElastic(x1, x2, a, b);
                case Easing.InElastic: return EasingInElastic(x1, x2, a, b);
                case Easing.OutInElastic: return EasingSplit(x1, x2, a, b, EasingOutElastic, EasingInElastic);
                case Easing.InOutElastic: return EasingSplit(x1, x2, a, b, EasingInElastic, EasingOutElastic);
                case Easing.OutBounce: return EasingOutBounce(x1, x2, a, b);
                case Easing.InBounce: return EasingInBounce(x1, x2, a, b);
                case Easing.OutInBounce: return EasingSplit(x1, x2, a, b, EasingOutBounce, EasingInBounce);
                case Easing.InOutBounce: return EasingSplit(x1, x2, a, b, EasingInBounce, EasingOutBounce);
                case Easing.OutBack: return EasingOutBack(x1, x2, a, b);
                case Easing.InBack: return EasingInBack(x1, x2, a, b);
                case Easing.OutInBack: return EasingSplit(x1, x2, a, b, EasingOutBack, EasingInBack);
                case Easing.InOutBack: return EasingSplit(x1, x2, a, b, EasingInBack, EasingOutBack);
                case Easing.WaveSquare: return EasingWaveSquare(x1, x2, a, b);
                case Easing.WaveSaw: return EasingWaveSaw(x1, x2, a, b);
                case Easing.WaveTriangle: return EasingWaveTriangle(x1, x2, a, b);
                case Easing.WaveSin: return EasingWaveSin(x1, x2, a, b);
            }

            return x1;
        }
        public static float EasingSplit(float x1, float x2, float a, float b, EasingModeFunction f1, EasingModeFunction f2)
        {
            float x3 = (x1 + x2) * 0.5f;
            if (a < 0.5)
                return f1(x1, x3, a * 2.0f, b);
            else
                return f2(x3, x2, (a - 0.5f) * 2.0f, b);
        }
        public static float EasingLinear(float x1, float x2, float a)
        {
            return x1 + (x2 - x1) * a;
        }
        public static float EasingOutQuad(float x1, float x2, float a, float b)
        {
            float ia = 1.0f - a;
            if (b < 1.0)
            {
                float v1 = 1.0f - ia;
                float v2 = 1.0f - (ia * ia);
                a = v1 + (v2 - v1) * b;
            }
            else
            {
                ia = ia * ia;
                float v1 = 1.0f - (ia);
                float v2 = 1.0f - (ia * ia);
                a = v1 + (v2 - v1) * (b - 1.0f);
            }
            return x1 + (x2 - x1) * a;
        }
        public static float EasingInQuad(float x1, float x2, float a, float b)
        {
            return x1 + EasingOutQuad(x2 - x1, 0.0f, 1.0f - a, b);
        }
        public static float EasingOutExpo(float x1, float x2, float a, float b)
        {
            float y;
            if (b < 1.0f)
                y = 5 + b * 5;
            else
                y = 10 + (b - 1.0f) * 10;

            return (x2 - x1) * (1.0f - (float)Math.Pow(2.0f, -y * a)) + x1;
        }
        public static float EasingInExpo(float x1, float x2, float a, float b)
        {
            return x1 + EasingOutExpo(x2 - x1, 0.0f, 1.0f - a, b);
        }
        public static float EasingOutElastic(float x1, float x2, float a, float b)
        {
            float d = EasingOutQuad(0.0f, 1.0f, a, 2.0f);
            d = (x2 - x1) * (1.0f - d);
            return x2 - (float)Math.Cos(a * Math.PI * (b * 4.0f + 1.0f)) * d;
        }
        public static float EasingInElastic(float x1, float x2, float a, float b)
        {
            return x1 + EasingOutElastic(x2 - x1, 0.0f, 1.0f - a, b);
        }
        public static float EasingOutBounce(float x1, float x2, float a, float b)
        {
            float d = EasingOutQuad(0.0f, 1.0f, a, 2.0f);
            d = (x2 - x1) * (1.0f - d);
            return x2 - Math.Abs((float)Math.Cos(a * Math.PI * (b * 4.0f + 1.0f))) * d;
        }
        public static float EasingInBounce(float x1, float x2, float a, float b)
        {
            return x1 + EasingOutBounce(x2 - x1, 0.0f, 1.0f - a, b);
        }
        public static float EasingOutBack(float x1, float x2, float a, float b)
        {
            float d = EasingOutQuad(0.0f, 1.0f, a, b);
            d = (x2 - x1) * (1.0f - d);
            return x2 - (float)Math.Cos(a * Math.PI * 1.5) * d;
        }
        public static float EasingInBack(float x1, float x2, float a, float b)
        {
            return x1 + EasingOutBack(x2 - x1, 0.0f, 1.0f - a, b);
        }
        public static float EasingWaveSquare(float x1, float x2, float a, float b)
        {
            int l = (int)(b * 100.0f + 0.5f);
            l = Math.Max(l, 1);

            a = a * l * 0.5f;    // 1step = half cycle
            a = a - (float)Math.Floor(a);
            if (a < 0.5f)
                return x2;
            else
                return x1;
        }
        public static float EasingWaveSaw(float x1,float x2,float a,float b)
        {
            int l = (int)(b * 100.0f + 0.5f);
            l = Math.Max(l, 2);

            if ((l & 1) == 0)
            {
                // saw
                l = l / 2;
                a = a * l * 1.0f; // 1set = 1cycle
                a = a - (float)Math.Floor(a);
                return x1 + (x2 - x1) * a;
            }
            else
            {
                // inv saw
                l = l / 2;
                a = a * l * 1.0f; // 1set = 1cycle
                a = a - (float)Math.Floor(a);
                return x2 + (x1 - x2) * a;
            }
        }
        public static float EasingWaveTriangle(float x1, float x2, float a, float b)
        {
            int l = (int)(b * 100.0f + 0.5f);
            l = Math.Max(l, 1);

            a = a * l * 0.5f;    // 1step = half cycle
            a = a - (float)Math.Floor(a);
            if (a < 0.5f)
                a = a * 2.0f;
            else
                a = 1.0f - (a - 0.5f) * 2.0f;
            return x1 + (x2 - x1) * a;
        }
        public static float EasingWaveSin(float x1, float x2, float a, float b)
        {
            int l = (int)(b * 100.0f + 0.5f);
            l = Math.Max(l, 1);

            a = a * l * 0.5f;    // 1step = half cycle
            a = a - (float)Math.Floor(a);
            a = (1.0f - (float)Math.Cos(a * Math.PI * 2.0)) * 0.5f;
            return x1 + (x2 - x1) * a;
        }

        // easing data
        public static readonly int EasingSaveVersion = 1;
        public static void EasingValueClear(ref EasingData d)
        {
            d.Done = true;
            d.Mode = Easing.None;
            d.Duration = 0.0f;
            d.Beta = 1.0f;
            d.Playtime = 0.0f;

            d.Value = 0.0f;
            d.SetValue = 0.0f;
            d.PreValue = 0.0f;
        }
        public static void EasingValueUpdate(ref EasingData d, double deltatime)
        {
            if (d.Done) return;

            if (d.Duration < 0.0001f)
            {
                d.Value = d.SetValue;
                d.Playtime = d.Duration;
                d.Done = true;
                return;
            }

            // update time
            d.Playtime = d.Playtime + deltatime;
            if (d.Playtime >= d.Duration)
            {
                d.Value = d.SetValue;
                d.Playtime = d.Duration;
                d.Done = true;
                return;
            }

            float a = (float)d.Playtime / d.Duration;
            d.Value = EasingFloat(d.PreValue, d.SetValue, a, d.Beta, d.Mode);
        }
        public static void EasingValueEase(ref EasingData d, float v, float duration = 0.0f, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            // no time
            if (duration < 0.0001f)
            {
                d.Value = v;
                d.SetValue = v;
                d.PreValue = v;
                d.Done = true;
                return;
            }

            // optimize same value
            if (d.SetValue == v)
                if (d.Duration == duration)
                    if (d.Beta == beta)
                        if (d.Mode == mode)
                            return;

            d.PreValue = d.Value;
            d.SetValue = v;
            d.Playtime = 0.0f;
            d.Duration = duration;
            d.Beta = beta;
            d.Mode = mode;

            d.Done = false;
        }
        public static void EasingValueSaveToBinary(ref EasingData d, MemoryStream ms)
        {
            WriteHead(ms, "easing");
            WriteInteger(ms, EasingSaveVersion);
            if (d.Done)
            {
                // value only
                // done
                WriteBool(ms, true);
                // value
                WriteFloat(ms, d.Value);
                return;
            }
            else
            {
                // value + ease param
                // done
                WriteBool(ms, false);
                // value
                WriteFloat(ms, d.Value);
                // ease state
                WriteFloat(ms, d.PreValue);
                WriteFloat(ms, d.SetValue);
                WriteFloat(ms, d.Duration);
                WriteFloat(ms, d.Beta);
                WriteFloat(ms, (float)d.Playtime);
                WriteInteger(ms, (int)d.Mode);
            }
        }
        public static bool EasingValueLoadFromBinary(ref EasingData d, MemoryStream ms)
        {
            try
            {
                ReadHead(ms, "easing");
                var ver = ReadInteger(ms, 0, EasingSaveVersion);

                var done = ReadBool(ms);
                if (done)
                {
                    // value only
                    d.Done = true;
                    var f = ReadFloat(ms, -0xffffff, 0xffffff);
                    EasingValueEase(ref d, f);
                }
                else
                {
                    // value + ease param
                    d.Done = true;
                    d.Value = ReadFloat(ms, -0xffffff, 0xffffff);
                    //
                    d.PreValue = ReadFloat(ms, -0xffffff, 0xffffff);
                    d.SetValue = ReadFloat(ms, -0xffffff, 0xffffff);
                    d.Duration = ReadFloat(ms);
                    d.Beta = ReadFloat(ms);
                    d.Playtime = ReadFloat(ms);
                    d.Mode = ReadEasing(ms);
                }
            }
            catch
            {
                return false;
            }
            return true;

        }
        // spline
        public static Spline ReadSpline(MemoryStream ms)
        {
            var v = ReadInteger(ms, 0, 0xff,true);

            if (v < (int)Spline.Custom0)
            {
                v = Math.Min(v, (int)Spline.Terminate - 1);
                return (Spline)v;
            }
            else
            {
                v = Math.Min(v, (int)Spline.CustomTerminate - 1);
                return (Spline)v;
            }
        }
        public static float SplineFloat(float x0, float x1, float x2, float x3, float a, float b, Spline mode)
        {
            if ((x1 == x2) && (x0 == x3) && (x1 == x2))
            {
                return x1;
            }

            switch (mode)
            {
                case Spline.Linear: return x1 + (x2 - x1) * a;
                case Spline.BSpline:
                    {
                        float ia = 1.0f - a;
                        float t1 = a * a;
                        float t2 = t1 * a;
                        return ((x0 * ia * ia * ia) + x1 * (3.0f * t2 - 6.0f * t1 + 4.0f) + x2 * (-3.0f * (t2 - t1 - a) + 1.0f) + x3 * t2) / 6.0f;
                    }
                case Spline.Hermite:
                    {
                        float ia = 1.0f - a;
                        float id = a - 1.0f;
                        // fake class line
                        float t1 = (x2 - x0) * b;
                        float t2 = (x3 - x1) * b;
                        // slow down
                        //if x1 = x2 then
                        //begin
                        //  t1 := t1 * 0.25;
                        //  t2 := t2 * 0.25;
                        //end;
                        return
                            x1 * (id * id) * (a * 2.0f + 1.0f) +
                            x2 * (a * a) * (3.0f - a * 2.0f) +
                            t1 * (ia * ia) * (a) +
                            t2 * (id) * (a * a);
                    }
                case Spline.BezierAuto:
                    {
                        // auto anchor point
                        float ia = 1.0f - a;
                        float t1 = ((x1 - x0) + (x2 - x1)) * 0.25f * b;
                        float t2 = ((x2 - x3) + (x1 - x2)) * 0.25f * b;
                        t1 = x1 + t1;
                        t2 = x2 + t2;
                        // bezier
                        return ia * ia * ia * x1 + 3.0f * ia * ia * a * t1 + 3.0f * ia * a * a * t2 + a * a * a * x2;
                    }
            }

            return x1 + (x2 - x1) * a;
        }
        // color
        public static int LinearColor(int c1, int c2, float a)
        {
            int aa = (int)Math.Floor(a * 256 + 0.5);

            int rr1 = (c1 >> 16) & 0xff;
            int gg1 = (c1 >> 8) & 0xff;
            int bb1 = c1 & 0xff;

            int rr2 = (c2 >> 16) & 0xff;
            int gg2 = (c2 >> 8) & 0xff;
            int bb2 = c2 & 0xff;

            rr1 = rr1 + (rr2 - rr1) * aa / 256;
            gg1 = gg1 + (gg2 - gg1) * aa / 256;
            bb1 = bb1 + (bb2 - bb1) * aa / 256;

            return (rr1 << 16) | (gg1 << 8) | bb1;
        }
        public static int MultiplyColor(int c1, int c2, float a)
        {
            int aa = (int)Math.Floor(a * 256 + 0.5);

            int rr1 = (c1 >> 16) & 0xff;
            int gg1 = (c1 >> 8) & 0xff;
            int bb1 = c1 & 0xff;

            int rr2 = (c2 >> 16) & 0xff;
            int gg2 = (c2 >> 8) & 0xff;
            int bb2 = c2 & 0xff;

            rr2 = rr2 + (0xff - rr2) * aa / 256;
            gg2 = gg2 + (0xff - gg2) * aa / 256;
            bb2 = bb2 + (0xff - bb2) * aa / 256;

            rr1 = rr1 * rr2 / 256;
            gg1 = gg1 * gg2 / 256;
            bb1 = bb1 * bb2 / 256;

            return (rr1 << 16) | (gg1 << 8) | bb1;
        }
        // matrix
        public static RotateOrder ReadRotateOrder(MemoryStream ms)
        {
            return (RotateOrder)ReadInteger(ms, 0, 6 + 1, true);
        }
        public static void v3SetVector(out Vector3 v, float x, float y, float z)
        {
            v.x = x;
            v.y = y;
            v.z = z;
            v._dummy = 0;
        }
        public static bool v3Compare(ref Vector3 v1, ref Vector3 v2)
        {
            if (v1.x != v2.x) return false;
            if (v1.y != v2.y) return false;
            if (v1.z != v2.z) return false;
            return true;
        }
		public static float v3DotProduct(ref Vector3 v1, ref Vector3 v2)
		{
			return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
		}
		public static Vector3 v3CrossProduct(ref Vector3 v1, ref Vector3 v2)
		{
			Vector3 r;
			r.x = v1.y * v2.z - v1.z * v2.y;
			r.y = v1.z * v2.x - v1.x * v2.z;
			r.z = v1.x * v2.y - v1.y * v2.x;
			r._dummy = 0.0f;

			return r;
		}
		public static void v3Normalize(ref Vector3 v)
		{
			double len = Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
			
			if (Math.Abs(len) < 0.000001f)
			{
				return;
			}
			else
			{
				len = 1 / len;
				v.x = (float)(v.x * len);
				v.y = (float)(v.y * len);
				v.z = (float)(v.z * len);
				//v.x = (float)(v.x / len);
				//v.y = (float)(v.y / len);
				//v.z = (float)(v.z / len);
			}
		}
		public static Vector3 m4Transform(ref Vector3 v, ref Matrix4 m)
        {
            Vector3 o;
            o.x = v.x * m._11 + v.y * m._21 + v.z * m._31 + m._41;
            o.y = v.x * m._12 + v.y * m._22 + v.z * m._32 + m._42;
            o.z = v.x * m._13 + v.y * m._23 + v.z * m._33 + m._43;
            o._dummy = 1.0f;
            return o;
        }
        public static void m4PushMove(ref Matrix4 m, float x, float y, float z)
        {
            if ((x == 0.0f) && (y == 0.0f) && (z == 0.0f)) return;
            //      1         2         3        4
            // 1
            // 2
            // 3
            // 4    x         y         z        1.0
            float v4 = m._14;
            m._11 += v4 * x;
            m._12 += v4 * y;
            m._13 += v4 * z;

            v4 = m._24;
            m._21 += v4 * x;
            m._22 += v4 * y;
            m._23 += v4 * z;

            v4 = m._34;
            m._31 += v4 * x;
            m._32 += v4 * y;
            m._33 += v4 * z;

            v4 = m._44;
            m._41 += v4 * x;
            m._42 += v4 * y;
            m._43 += v4 * z;
        }
        public static void m4PushRotateX(ref Matrix4 m, float r)
        {
            if (r == 0.0f) return;

            //      1         2         3        4
            // 1
            // 2              c         s
            // 3              -s        c
            // 4
            float s = (float)Math.Sin(r * 2.0f * Math.PI);
            float c = (float)Math.Cos(r * 2.0f * Math.PI);

            float v2 = m._12;
            float v3 = m._13;
            m._12 = v2 * c + v3 * -s;
            m._13 = v2 * s + v3 * c;

            v2 = m._22;
            v3 = m._23;
            m._22 = v2 * c + v3 * -s;
            m._23 = v2 * s + v3 * c;

            v2 = m._32;
            v3 = m._33;
            m._32 = v2 * c + v3 * -s;
            m._33 = v2 * s + v3 * c;

            v2 = m._42;
            v3 = m._43;
            m._42 = v2 * c + v3 * -s;
            m._43 = v2 * s + v3 * c;
        }
        public static void m4PushRotateY(ref Matrix4 m, float r)
        {
            if (r == 0.0f) return;

            //      1         2         3        4
            // 1    c                   -s
            // 2
            // 3    s                   c
            // 4
            float s = (float)Math.Sin(r * 2.0f * Math.PI);
            float c = (float)Math.Cos(r * 2.0f * Math.PI);

            float v1 = m._11;
            float v3 = m._13;
            m._11 = v1 * c + v3 * s;
            m._13 = v1 * -s + v3 * c;

            v1 = m._21;
            v3 = m._23;
            m._21 = v1 * c + v3 * s;
            m._23 = v1 * -s + v3 * c;

            v1 = m._31;
            v3 = m._33;
            m._31 = v1 * c + v3 * s;
            m._33 = v1 * -s + v3 * c;

            v1 = m._41;
            v3 = m._43;
            m._41 = v1 * c + v3 * s;
            m._43 = v1 * -s + v3 * c;
        }
        public static void m4PushRotateZ(ref Matrix4 m, float r)
        {
            if (r == 0.0f) return;

            //      1         2         3        4
            // 1    c         s
            // 2    -s        c
            // 3
            // 4
            float s = (float)Math.Sin(r * 2.0f * Math.PI);
            float c = (float)Math.Cos(r * 2.0f * Math.PI);

            float v1 = m._11;
            float v2 = m._12;
            m._11 = v1 * c + v2 * -s;
            m._12 = v1 * s + v2 * c;

            v1 = m._21;
            v2 = m._22;
            m._21 = v1 * c + v2 * -s;
            m._22 = v1 * s + v2 * c;

            v1 = m._31;
            v2 = m._32;
            m._31 = v1 * c + v2 * -s;
            m._32 = v1 * s + v2 * c;

            v1 = m._41;
            v2 = m._42;
            m._41 = v1 * c + v2 * -s;
            m._42 = v1 * s + v2 * c;
        }
        public static void m4PushRotate(ref Matrix4 m, float x, float y, float z, RotateOrder mode)
        {
            switch (mode)
            {
                case RotateOrder.XYZ:
                    {
                        m4PushRotateX(ref m, x);
                        m4PushRotateY(ref m, y);
                        m4PushRotateZ(ref m, z);
                        break;
                    }
                case RotateOrder.XZY:
                    {
                        m4PushRotateX(ref m, x);
                        m4PushRotateZ(ref m, z);
                        m4PushRotateY(ref m, y);
                        break;
                    }
                case RotateOrder.YXZ:
                    {
                        m4PushRotateY(ref m, y);
                        m4PushRotateX(ref m, x);
                        m4PushRotateZ(ref m, z);
                        break;
                    }
                case RotateOrder.YZX:
                    {
                        m4PushRotateY(ref m, y);
                        m4PushRotateZ(ref m, z);
                        m4PushRotateX(ref m, x);
                        break;
                    }
                case RotateOrder.ZXY:
                    {
                        m4PushRotateZ(ref m, z);
                        m4PushRotateX(ref m, x);
                        m4PushRotateY(ref m, y);
                        break;
                    }
                case RotateOrder.ZYX:
                case RotateOrder.Pass:
                    {
                        m4PushRotateZ(ref m, z);
                        m4PushRotateY(ref m, y);
                        m4PushRotateX(ref m, x);
                        break;
                    }


            }
        }
        public static void m4PushScale(ref Matrix4 m, float x, float y, float z)
        {
            if ((x == 1.0f)&&(y == 1.0f)&&(z == 1.0f)) return;

            //      1         2         3        4
            // 1    x
            // 2              y
            // 3                        z
            // 4                                 1.0
            m._11 *= x;
            m._12 *= y;
            m._13 *= z;

            m._21 *= x;
            m._22 *= y;
            m._23 *= z;

            m._31 *= x;
            m._32 *= y;
            m._33 *= z;

            m._41 *= x;
            m._42 *= y;
            m._43 *= z;
        }
        public static void m43PushMatrix(ref Matrix4 m1, ref Matrix4 m2)
        {
            float v1 = m1._11;
            float v2 = m1._12;
            float v3 = m1._13;
            //float v4 = m1._14; // 0
            m1._11 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._12 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._13 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._14 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._21;
            v2 = m1._22;
            v3 = m1._23;
            //v4 = m1._24; // 0
            m1._21 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._22 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._23 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._24 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._31;
            v2 = m1._32;
            v3 = m1._33;
            //v4 = m1._34; // 0
            m1._31 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._32 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._33 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._34 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._41;
            v2 = m1._42;
            v3 = m1._43;
            //float v4 = m1._44; //1
            m1._41 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41;// * v4;
            m1._42 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42;// * v4;
            m1._43 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43;// * v4;
            //m1._44 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;
        }
        public static void m43PushMatrixBack(ref Matrix4 m2, ref Matrix4 m1)
        {
            float v1 = m1._11;
            float v2 = m1._12;
            float v3 = m1._13;
            //float v4 = m1._14; // 0
            m1._11 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._12 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._13 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._14 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._21;
            v2 = m1._22;
            v3 = m1._23;
            //v4 = m1._24; // 0
            m1._21 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._22 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._23 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._24 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._31;
            v2 = m1._32;
            v3 = m1._33;
            //v4 = m1._34; // 0
            m1._31 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3;// + m2._41 * v4;
            m1._32 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3;// + m2._42 * v4;
            m1._33 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3;// + m2._43 * v4;
            //m1._34 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._41;
            v2 = m1._42;
            v3 = m1._43;
            //float v4 = m1._44; //1
            m1._41 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41;// * v4;
            m1._42 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42;// * v4;
            m1._43 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43;// * v4;
            //m1._44 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;
        }
        public static void m44PushMatrix(ref Matrix4 m1, ref Matrix4 m2)
        {
            float v1 = m1._11;
            float v2 = m1._12;
            float v3 = m1._13;
            float v4 = m1._14;
            m1._11 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
            m1._12 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
            m1._13 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
            m1._14 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._21;
            v2 = m1._22;
            v3 = m1._23;
            v4 = m1._24;
            m1._21 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
            m1._22 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
            m1._23 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
            m1._24 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._31;
            v2 = m1._32;
            v3 = m1._33;
            v4 = m1._34;
            m1._31 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
            m1._32 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
            m1._33 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
            m1._34 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

            v1 = m1._41;
            v2 = m1._42;
            v3 = m1._43;
            v4 = m1._44;
            m1._41 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
            m1._42 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
            m1._43 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
            m1._44 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;
        }
        public static Matrix4 m4InvertMatrix(ref Matrix4 m, out bool vaild)
        {
            vaild = false;

            var d = m._11 * (m._22 * m._33 - m._32 * m._23) - m._12 * (m._21 * m._33 - m._31 * m._23) + m._13 * (m._21 * m._32 - m._31 * m._22);
            if (d == 0.0f) {
                return IdentityMatrix4;
            }
            
            var t = IdentityMatrix4;
            float rd = (float)(1.0 / d);

            t._11 = (m._22 * m._33 - m._23 * m._32) * rd;
            t._12 = (-(m._12 * m._33 - m._32 * m._13)) * rd;
            t._13 = (m._12 * m._23 - m._22 * m._13) * rd;

            t._21 = (-(m._21 * m._33 - m._23 * m._31)) * rd;
            t._22 = (m._11 * m._33 - m._31 * m._13) * rd;
            t._23 = (-(m._11 * m._23 - m._21 * m._13)) * rd;

            t._31 = (m._21 * m._32 - m._31 * m._22) * rd;
            t._32 = (-(m._11 * m._32 - m._31 * m._12)) * rd;
            t._33 = (m._11 * m._22 - m._21 * m._12) * rd;

            var v1 = -m._41;
            var v2 = -m._42;
            var v3 = -m._43;
            t._41 = t._11 * v1 + t._21 * v2 + t._31 * v3 + t._41;
            t._42 = t._12 * v1 + t._22 * v2 + t._32 * v3 + t._42;
            t._43 = t._13 * v1 + t._23 * v2 + t._33 * v3 + t._43;
            t._44 = t._14 * v1 + t._24 * v2 + t._34 * v3 + t._44;

            vaild = true;
            return t;
        }
		// matrix to TRS
		public static Vector3 m4ExtractMove(ref Matrix4 m)
		{
			Vector3 v;
			v.x = m._41;
			v.y = m._42;
			v.z = m._43;
			v._dummy = 0.0f;

			return v;
		}
		public static Vector3 m4ExtractScale(ref Matrix4 m)
		{
			Vector3 v;
			v.x = (float)Math.Sqrt(m._11 * m._11 + m._12 * m._12 + m._13 * m._13 + m._14 * m._14);
			v.y = (float)Math.Sqrt(m._21 * m._21 + m._22 * m._22 + m._23 * m._23 + m._24 * m._24);
			v.z = (float)Math.Sqrt(m._31 * m._31 + m._32 * m._32 + m._33 * m._33 + m._34 * m._34);
			v._dummy = 0.0f;
			return v;
		}
		public static Quaternion q4CreateFromAxisAngle(ref Vector3 axis, float angle)
		{
			float halfangle = angle * 0.5f;
			float s = (float)Math.Sin(halfangle);
			float c = (float)Math.Cos(halfangle);

			Quaternion q;
			q.x = axis.x * s;
			q.y = axis.y * s;
			q.z = axis.z * s;
			q.w = c;
			return q;
		}
		public static Quaternion q4LookAt(ref Vector3 facedir, ref Vector3 forward, ref Vector3 up)
		{
			var nfacedir = facedir;
			v3Normalize(ref nfacedir);

			var nforward = forward;
			v3Normalize(ref nforward);

			var nup = up;
			v3Normalize(ref nup);

			return q4LookAtNormalized(ref nfacedir, ref nforward, ref nup);
		}
		public static Quaternion q4LookAtNormalized(ref Vector3 facedir, ref Vector3 forward, ref Vector3 up)
		{
			float dot = v3DotProduct(ref facedir, ref forward);
			if (Math.Abs(dot - (-1.0f)) < 0.000001f)
			{
				Quaternion q;
				q.x = up.x;
				q.y = up.y;
				q.z = up.z;
				q.w = (float)Math.PI;
				return q;
			}
			if (Math.Abs(dot - (1.0f)) < 0.000001f)
			{
				Quaternion q;
				q.x = 0.0f;
				q.y = 0.0f;
				q.z = 0.0f;
				q.w = 1.0f;
				return q;
			}
			float angle = (float)Math.Acos(dot);
			Vector3 axis = v3CrossProduct(ref facedir, ref forward);
			v3Normalize(ref axis);
			return q4CreateFromAxisAngle(ref axis, angle);
		}
		public static Quaternion m4ExtractRotateQuaternion(ref Matrix4 m)
		{
			Vector3 face;
			face.x = 0.0f;
			face.y = 0.0f;
			face.z = 1.0f;
			face._dummy = 0.0f;

			Vector3 forward;
			forward.x = m._31;
			forward.y = m._32;
			forward.z = m._33;
			forward._dummy = 0.0f;
			v3Normalize(ref forward);

			Vector3 up;
			up.x = m._21;
			up.y = m._22;
			up.z = m._23;
			up._dummy = 0.0f;
			v3Normalize(ref up);

			return q4LookAtNormalized(ref face, ref forward, ref up);
		}
		public static Vector3 q4ToRotate(ref Quaternion q)
		{
			Vector3 v;

			double q0q0 = q.x * q.x;
			double q0q1 = q.x * q.y;
			double q0q2 = q.x * q.z;
			double q0q3 = q.x * q.w;
			double q1q1 = q.y * q.y;
			double q1q2 = q.y * q.z;
			double q1q3 = q.y * q.w;
			double q2q2 = q.z * q.z;
			double q2q3 = q.z * q.z;
			double q3q3 = q.w * q.w;
			v.x = (float)Math.Atan2(2.0 * (q2q3 + q0q1), q0q0 - q1q1 - q2q2 + q3q3);
			v.y = (float)Math.Asin(2.0 * (q0q2 - q1q3));
			v.z = (float)Math.Atan2(2.0 * (q1q2 + q0q3), q0q0 + q1q1 - q2q2 - q3q3);
			v._dummy = 0.0f;

			return v;
		}

        // misc
        public static int ToIntDef(string s, int b)
        {
            if (s == "") return b;
            try
            {
                return Convert.ToInt32(s);
            }
            catch
            {
                return b;
            }
        }
        public static float ToFloatDef(string s, float b)
        {
            if (s == "") return b;
            try
            {
                return Convert.ToSingle(s);
            }
            catch
            {
                return b;
            }
        }
        public static string Slice(string s, int offset, int count)
        {
            var length = s.Length;
            if (length < 1) return "";
            if (count < 1) return "";
            // backward
            if (offset < 0) offset = length - offset;
            // check
            if (offset < 0) return "";
            if (offset >= length) return "";
            if (offset + count > length) count = length - offset;

            return s.Substring(offset, count);
        }
        public static List<string> DivideString(char delimiter, string src)
        {
            var sl = new List<string>();

            var len = src.Length;

            var s = "";
            for (var i = 0; i < len; i++)
            {
                if (src[i] == delimiter)
                {
                    sl.Add(s);
                    s = "";
                    continue;
                }
                s = s + src[i];
            }
            if (s != "") sl.Add(s);

            return sl;
        }
        public static Dictionary<string,string> ConvertKeyValue(List<string> sl)
        {
            var dl = new Dictionary<string, string>();
            foreach (var s in sl)
            {
                var p = s.IndexOf('=');
                if (p < 0) continue;
                var s1 = Slice(s, 0, p);
                var s2 = Slice(s, p + 1, 4096);
                dl.Add(s1, s2);
            }
            return dl;
        }
        public static void GetSortedIndexes(int[] tbl, double[] data, int count, bool ascending=true)
        {
            if (count == 0) return;

            for (var i = 0; i < count; i++)
            {
                tbl[i] = i;
            }
            if (count == 1) return;

            if (ascending)
            {
                for (var i = count - 1 - 1; i >= 0; i--)
                {
                    for (var j = i; j < count - 1; j++)
                    {
                        if (data[tbl[j]] > data[tbl[j + 1]])
                        {
                            int t = tbl[j];
                            tbl[j] = tbl[j + 1];
                            tbl[j + 1] = t;
                        }
                    }
                }
            }
            else
            {
                for (var i = count - 1 - 1; i >= 0; i--)
                {
                    for (var j = i; j < count - 1; j++)
                    {
                        if (data[tbl[j]] < data[tbl[j + 1]])
                        {
                            int t = tbl[j];
                            tbl[j] = tbl[j + 1];
                            tbl[j + 1] = t;
                        }
                    }
                }
            }
        }
    }
}
