
using System;
using System.IO;
using System.Collections.Generic;

namespace Animrig
{
    public abstract class StateRenderer
    {
        //protected
        // property
        public ObjectType ObjectType { get; protected set; }  = ObjectType.None;
        public List<PhysicsGroup> PhysicsGroups = new List<PhysicsGroup>();   // physics
        // return state
        public bool RendererEnable;
        public bool RendererVisible;
		public bool RendererInheritedEnable;
		public bool RendererInheritedVisible;
		// draw priority info
		// * design priority > 3D z position
		public float DrawPriorityZ;
        public float DrawPriorityDesign;
		// transform
		public Vector3 TransPosition;
        public Matrix4 TransMatrix;
        public float[] TransColor = new float[4];    // [0]..alpha [1]..red [2]..green [3]..blue
        // public
        public StateRenderer()
        {
            ObjectType = ObjectType.None;
        }
        // public common
        public virtual void Clear()
        {
            RendererEnable = true;
            RendererVisible = true;
            RendererInheritedEnable = true;
            RendererInheritedVisible = true;
            DrawPriorityZ = 0.0f;
            DrawPriorityDesign = 0.0f;

            TransMatrix = Misc.IdentityMatrix4;
            TransColor[0] = 1.0f;
            TransColor[1] = 1.0f;
            TransColor[2] = 1.0f;
            TransColor[3] = 1.0f;

            PhysicsGroups.Clear();
        }
        public virtual void Update(double deltatime)
        {
            // object update code
            // Object.Update(deltatime);

            // physics
            //AddForcePhysics();
            //UpdatePhysics(deltatime);
            //ApplyPhysics();

            // object transform update code
            // Object.SetupTransform();
        }
        public double GetPrioritySortValue()
        {
            return (-DrawPriorityZ) + (+DrawPriorityDesign * 100) * 0x10000;
        }
        // physics
        public virtual void SetupPhysics(List<string> setting)
        {
            // need reset code
            PhysicsGroups.Clear();
        }
        public virtual void UpdatePhysics(double deltatime)
        {
            foreach(var g in PhysicsGroups)
            {
                g.Update(deltatime);
            }
        }
        public virtual void AddPhysicsTransform(ref Matrix4 externalmatrix)
        {
            // ExternalMatrix
            // default : Identity matrix.
            // If you want to apply power to physics from outside Animrig, set transfrom to extrnalmatrix.
        }
        public virtual void ApplyPhysics()
        {

        }
        public virtual void AppeasePhysics()
        {

        }
        public virtual void AddPhysicsForce(string target, double x, double y,double z)
        {
            
        }
        // public spine control
        public virtual void SetAnimation(int trackindex, int usetrack, string basename, bool loop, bool addanim, MixBlend trackmix, float duration, bool seekmode)
        {
            //
        }
        public virtual void SetTrackBlendAlpha(int trackindex, int usetrack, float alpha1, float alpha2, float alpha3, float alpha4, bool exists)
        {
            //
        }
        public virtual void SetTrackSeekTime(int trackindex, int usetrack, float time1, float time2, float time3, float time4, bool exists)
        {
            //
        } 
        public virtual void SetMixDuration(float duration)
        {
            //
        }
        public virtual void SetSkin(string skinname)
        {
            //
        }
        public virtual Matrix4 GetBoneMatrix(string bonename)
        {
            return Misc.IdentityMatrix4;
        }
    }
    public class StateTrackLayer
    {
        // protected
        // property
        public const int ValueCount = 16;   // state keep value count (*not input values)
        public static readonly int SaveVersion = 1;
        public LayerType LayerType { get; protected set; }
        public bool IsPass;     // pass? (empty,disable)
        //   now state
        public string String1, String2;
        public float PrevTrackAlpha;
        public float CurrentTrackAlpha;
        public float[] Values = new float[ValueCount];
        public RotateOrder RotateOrder;
        //   previous track state
        public float pPrevTrackAlpha;
        public float pCurrentTrackAlpha;
        public float[] pValues = new float[ValueCount];
        public RotateOrder pRotateOrder;
        // public
        public static void ClearValues(float[] v, LayerType layertype)
        {
            for (var i = 0; i < ValueCount; i++)
                v[i] = 0.0f;

            switch (layertype)
            {
                case LayerType.State:
                    {
                        // [0] enable
                        v[0] = 1.0f;
                        // [1] disable
                        v[1] = 1.0f;
                        break;
                    }
                case LayerType.Transform:
                    {
                        // tansform
                        // [0..2]
                        // scale
                        v[3] = 1.0f;
                        v[4] = 1.0f;
                        v[5] = 1.0f;
                        // rotate
                        // [6..8]
                        // alpha
                        v[9] = 1.0f;
                        // color
                        v[10] = 1.0f;
                        v[11] = 1.0f;
                        v[12] = 1.0f;
                        break;
                    }
            }
        }
        public StateTrackLayer(LayerType layertype)
        {
            LayerType = layertype;
        }
        public void Clear(bool clearprevious)
        {
            if (clearprevious)
            {
                ClearValues(pValues, LayerType);
                String1 = "";
                String2 = "";
                pPrevTrackAlpha = 1.0f;
                pCurrentTrackAlpha = 1.0f;
                pRotateOrder = RotateOrder.Pass;

            }
            else
            {
                for (var i = 0; i < ValueCount; i++)
                {
                    pValues[i] = Values[i];
                }
                pPrevTrackAlpha = PrevTrackAlpha;
                pCurrentTrackAlpha = CurrentTrackAlpha;
                pRotateOrder = RotateOrder;
            }

            ClearValues(Values, LayerType);
            PrevTrackAlpha = 1.0f;
            CurrentTrackAlpha = 1.0f;
            RotateOrder = RotateOrder.Pass;
        }
        public void SaveToBinary(MemoryStream ms)
        {
            // version
            Misc.WriteInteger(ms, SaveVersion);

            // type
            Misc.WriteInteger(ms, (int)LayerType);

            // pass? (empty,disable)
            Misc.WriteBool(ms, IsPass);

            // now state
            Misc.WriteString(ms, String1);
            Misc.WriteString(ms, String2);
            Misc.WriteFloat(ms, PrevTrackAlpha);
            Misc.WriteFloat(ms, CurrentTrackAlpha);
            Misc.WriteInteger(ms, ValueCount);  // value count
            for (var i = 0; i < ValueCount; i++) {
                Misc.WriteFloat(ms, Values[i]);
            }
            Misc.WriteInteger(ms, (int)RotateOrder);

            // prev state
            Misc.WriteFloat(ms, pPrevTrackAlpha);
            Misc.WriteFloat(ms, pCurrentTrackAlpha);
            Misc.WriteInteger(ms, ValueCount);  // value count
            for (var i = 0; i < ValueCount; i++)
            {
                Misc.WriteFloat(ms, pValues[i]);
            }
            Misc.WriteInteger(ms, (int)pRotateOrder);
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            Clear(false);

            int t = 0;
            try
            {
                // version
                var ver = Misc.ReadInteger(ms, 0, SaveVersion);

                // type
                t = Misc.ReadInteger(ms);

                // pass? (empty,disable)
                IsPass = Misc.ReadBool(ms);
                // now state
                String1 = Misc.ReadString(ms);
                String2 = Misc.ReadString(ms);
                PrevTrackAlpha = Misc.ReadFloat(ms);
                CurrentTrackAlpha = Misc.ReadFloat(ms);
                {
                    var count = Misc.ReadInteger(ms, 0, 0xffff);  // value count
                    for (var i = 0; i < count; i++)
                    {
                        var f = Misc.ReadFloat(ms, -0xffffff, +0xffffff);
                        if (i < ValueCount) Values[i] = f;
                    }
                }
                RotateOrder = Misc.ReadRotateOrder(ms);
                // prev state
                pPrevTrackAlpha = Misc.ReadFloat(ms);
                pCurrentTrackAlpha = Misc.ReadFloat(ms);
                {
                    var count = Misc.ReadInteger(ms, 0, 0xffff);  // value count
                    for (var i = 0; i < count; i++)
                    {
                        var f = Misc.ReadFloat(ms, -0xffffff, +0xffffff);
                        if (i < ValueCount) pValues[i] = f;
                    }
                }
                pRotateOrder = Misc.ReadRotateOrder(ms);
            }
            catch
            {
                return false;
            }

            // type check
            if (t != (int)LayerType) Clear(false);

            return true;
        }
    }
    public class StateTrackObject
    {
        // protected
        // property
        public ObjectType ObjectType { get; protected set; }
        public List<StateTrackLayer> Layers { get; protected set; }  = new List<StateTrackLayer>();
        public int LayerCount { get { return Layers.Count; } }
        // public
        public StateTrackObject(AnimObject data)
        {
            //Layers = new
            ObjectType = data.ObjectType;

            // create layers
            foreach(var l in data.Layers)
            {
                Layers.Add(new StateTrackLayer(l.LayerType));
            }
            
            Clear(true);
        }
        public void Clear(bool clearprevious)
        {
            foreach (var l in Layers)
            {
                l.Clear(clearprevious);
            }
        }
        public void SaveToBinary(MemoryStream ms)
        {
            // layers
            Misc.WriteHead(ms, "tracklayers");
            Misc.WriteInteger(ms, LayerCount);
            foreach(var l in Layers)
            {
                Misc.WriteHead(ms, "tracklayer");
                // data length
                var endpoint = Misc.WriteSkipBegin(ms);
                // layer data
                l.SaveToBinary(ms);
                // write data length
                Misc.WriteSkipEnd(ms, endpoint);
            }
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            try
            {
                // layers
                Misc.ReadHead(ms, "tracklayers");
                var count = Misc.ReadInteger(ms, 0,0xffff);
                for (var i = 0; i < count; i++)
                {
                    // header
                    Misc.ReadHead(ms, "tracklayer");
                    // data length
                    var endpoint = Misc.ReadSkipBegin(ms);
                    // data
                    if (i < LayerCount)
                    {
                        if (!Layers[i].LoadFromBinary(ms)) return false;
                    }
                    // jump data endpoint
                    Misc.JumpSkipEnd(ms, endpoint);
                }

            }
            catch
            {
                return false;
            }
            return true;
        }
    }
    public class StateTrack
    {
        // protected
        // property
        public static readonly int SaveVersion = 2;
        public Animationrig Data { get; protected set; }
        //   track state
        public int AnimationIndex;
        public double PlayFrame;
        public int PrevPlayFrame;
        public double SelfClearLimit;
        //   control
        public double PlaySpeed;
        public bool Pause;
        public bool Enable;
        public bool DisableLoop;
        public EasingData TrackAlphaEase;   // track alpha. fade in out.
        public EasingData MixAlphaEase;     // previous animation value mix. fade mix.
        //   objects
        public List<StateTrackObject> Objects { get; protected set; } = new List<StateTrackObject>();
        public int ObjectCount { get { return Objects.Count; } }
        //   play animation
        public Animation Animation
        {
            get
            {
                if (AnimationIndex < 0) return null;
                if (AnimationIndex > Data.AnimationCount - 1) return null;

                return Data.Animations[AnimationIndex];
            }
        }
        public bool IsSelfClear
        {
            get
            {
                return SelfClearLimit < Misc.FloatZero;
            }
        }

        // public
        public StateTrack(Animationrig data, int animationindex)
        {
            Data = data;
            AnimationIndex = animationindex;
            Misc.EasingValueClear(ref MixAlphaEase);
            Misc.EasingValueClear(ref TrackAlphaEase);

            // Objects = new
            Rebuild();

            Clear();
        }
        public void Clear()
        {
            // play control
            Enable = true;
            Pause = false;
            PlaySpeed = 1.0f;
            DisableLoop = false;

            Misc.EasingValueEase(ref TrackAlphaEase, 1.0f);
            Misc.EasingValueEase(ref MixAlphaEase, 1.0f);
            SelfClearLimit = 0x100000;

            Reset(true);
        }
        public void Reset(bool clearprevious = false)
        {
            // clear & backup
            foreach (var o in Objects)
            {
                o.Clear(clearprevious);
            }
            // state reset
            PlayFrame = -1;
            PrevPlayFrame = -1;
            // reset fade
            SelfClearLimit = 0x100000;
            Misc.EasingValueEase(ref TrackAlphaEase, 1.0f);
            Misc.EasingValueEase(ref MixAlphaEase, 1.0f);
        }
        public void Rebuild()
        {
            Objects.Clear();
            foreach (var o in Data.Objects)
            {
                Objects.Add(new StateTrackObject(o));
            }
            // update index
            AnimationIndex = Math.Min(Math.Max(AnimationIndex, 0), Data.AnimationCount - 1);
        }
        public void Update(double deltaframe)
        {
            // easing
            if (!TrackAlphaEase.Done) Misc.EasingValueUpdate(ref TrackAlphaEase, deltaframe / Data.Framerate);
            if (!MixAlphaEase.Done) Misc.EasingValueUpdate(ref MixAlphaEase, deltaframe / Data.Framerate);
            // self clear
            if ((SelfClearLimit < 0xffff)&&(SelfClearLimit > 0.0))
            {
                SelfClearLimit = SelfClearLimit - (deltaframe / Data.Framerate);
            }

            PlayFrame = PlayFrame + deltaframe;
        }
        public void SaveToBinary(MemoryStream ms)
        {
            // animation name
            // move upper class
            //s := '';
            //if track.Animation <> nil then s := track.Animation.Name;
            //TAnimrigMisc.WriteString(ms , s);

            // play frame
            Misc.WriteFloat(ms, (float)PlayFrame);
            Misc.WriteInteger(ms, PrevPlayFrame);
            // control state
            Misc.WriteFloat(ms, (float)PlaySpeed);
            Misc.WriteBool(ms, Pause);
            Misc.WriteBool(ms, Enable);
            Misc.WriteBool(ms, DisableLoop);
            Misc.WriteFloat(ms, (float)SelfClearLimit);
            // track alpha easing
            Misc.EasingValueSaveToBinary(ref TrackAlphaEase, ms);
            Misc.EasingValueSaveToBinary(ref MixAlphaEase, ms);

            // object
            // header
            Misc.WriteHead(ms, "trackobjects");
            Misc.WriteInteger(ms, ObjectCount);
            foreach (var o in Objects)
            {
                // head
                Misc.WriteHead(ms, "trackobject");
                // data length
                var endpoint = Misc.WriteSkipBegin(ms);
                // object data
                o.SaveToBinary(ms);
                // write data length
                Misc.WriteSkipEnd(ms, endpoint);
            }
        }
        public bool LoadFromBinary(MemoryStream ms, int ver)
        {
            try
            {
                // animation name
                // move upper class
                //s := TAnimrigMisc.ReadString(ms);
                //track := EntryTrack(i, s,true);

                // play frame
                PlayFrame = Misc.ReadFloat(ms, -0xffffff, +0xffffff, true);
                PrevPlayFrame = Misc.ReadInteger(ms, -0xffffff,0xffffff, true);
                // control state
                PlaySpeed = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                Pause = Misc.ReadBool(ms);
                Enable = Misc.ReadBool(ms);
                DisableLoop = Misc.ReadBool(ms);
                if (ver >= 2) SelfClearLimit = Misc.ReadFloat(ms);
                // track alpha easing
                if (ver >= 2)
                {
                    Misc.EasingValueLoadFromBinary(ref TrackAlphaEase, ms);
                    Misc.EasingValueLoadFromBinary(ref MixAlphaEase, ms);
                }

                // object
                // header
                Misc.ReadHead(ms, "trackobjects");
                var count = Misc.ReadInteger(ms, 0,0xffff);
                for (var i = 0; i < count; i++)
                {
                    // header
                    Misc.ReadHead(ms, "trackobject");
                    // data length
                    var endpoint = Misc.ReadSkipBegin(ms);
                    // data 
                    if (i < ObjectCount)
                    {
                        if (!Objects[i].LoadFromBinary(ms)) return false;
                    }
                    // jump data endpoint
                    Misc.JumpSkipEnd(ms, endpoint);

                }
            }
            catch
            {
                return false;
            }
            return true;
        }
        public void Fademix(float duration, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            Misc.EasingValueEase(ref MixAlphaEase, 0.0f);
            Misc.EasingValueEase(ref MixAlphaEase, 1.0f, duration, mode, beta);
        }
        public void Fadein(float duration, float trackalpha = 1.0f, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            Misc.EasingValueEase(ref TrackAlphaEase, 0.0f);
            Misc.EasingValueEase(ref TrackAlphaEase, trackalpha, duration, mode, beta);
            SelfClearLimit = 0x100000;
        }
        public void Fadeout(float duration, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            Misc.EasingValueEase(ref TrackAlphaEase, 0.0f, duration, mode, beta);
            SelfClearLimit = duration;
        }
    }
    public class StateInput
    {
        // protected
        protected void SetValueCount(int count)
        {
            if (ValueCount == count) return;

            Array.Resize(ref Values, count);
            Array.Resize(ref DefaultValues, count);
            Array.Resize(ref DefaultRange, count);
            Array.Resize(ref ValueNames, count);
            
            for (var i = 0; i < count; i++)
            {
                Misc.EasingValueClear(ref Values[i]);
                DefaultValues[i] = 0.0f;
                DefaultRange[i] = 1.0f;
                ValueNames[i] = "";
            }
        }
        protected StateRenderer fRenderer = null;
        protected void SetRenderer(StateRenderer value)
        {
            if (value != null)
            {
                if (value.ObjectType != ObjectType)
                {
                    throw new Exception("SetRenderer : miss match objecttype class.");
                }
            }
    
            fRenderer = value;
        }
        // property
        public ObjectType ObjectType {get; protected set;}
        public StateRenderer Renderer
        {
            get { return fRenderer; }
            set { SetRenderer(value); }
        }
        public int ValueCount {
            get { return Values.Length; }
            set { SetValueCount(value); }
        }
        public Object UserObject;
        public EasingData[] Values;
        public float[] DefaultValues;
        public float[] DefaultRange;
        public string[] ValueNames;
        // public
        public StateInput(ObjectType objecttype, int valuecount = Misc.InputCount)
        {
            ObjectType = objecttype;
            UserObject = null;
            Values = new EasingData[0];
            DefaultValues = new float[0];
            DefaultRange = new float[0];
            ValueNames = new string[0];

            SetValueCount(valuecount);

            Clear();
        }
        public void Clear()
        {
            Renderer = null;

            for (var i = 0; i < ValueCount; i++)
            {
                Misc.EasingValueClear(ref Values[i]);
            }
        }
        public void SetupDefault(AnimObject reserveobj)
        {
            foreach (var s1 in reserveobj.InputPanelField)
            {
                var sl = Misc.DivideString(',', s1);
                int index = -1;
                float alphax = 0.0f;
                float alphay = 0.0f;
                float range = 1.0f;
                string name = "";
                foreach (var s2 in sl)
                {
                    var p = s2.IndexOf(':');
                    if (p < 0) continue;
                    var v1 = Misc.Slice(s2, 0, p);
                    var v2 = Misc.Slice(s2, p + 1, 4096);

                    if ((v1 == "no") || (v1 == "index")) index = Misc.ToIntDef(v2, -1);
                    else if (v1 == "name") name = v2;
                    else if (v1 == "defx") alphax = Math.Min(Math.Max(Misc.ToFloatDef(v2, 0.0f), -0xffff), +0xffff);
                    else if (v1 == "defy") alphay = Math.Min(Math.Max(Misc.ToFloatDef(v2, 0.0f), -0xffff), +0xffff);
                    else if ((v1 == "scale") || (v1 == "range")) range = Math.Min(Math.Max(Misc.ToFloatDef(v2, 1.0f), -0xffff), +0xffff);
                }

                if ((index >= 0) && (index * 2 + 1 < ValueCount))
                {
                    DefaultValues[index * 2 + 0] = alphax;
                    DefaultValues[index * 2 + 1] = alphay;
                    DefaultRange[index * 2 + 0] = range;
                    DefaultRange[index * 2 + 1] = range;
                    ValueNames[index * 2 + 0] = name;
                    ValueNames[index * 2 + 1] = name;
                }
            }
        }
        public void Update(double deltatime)
        {
            for (var i = 0; i < ValueCount; i++)
            {
                Misc.EasingValueUpdate(ref Values[i], deltatime);
            }

            if (Renderer != null)
                if (Renderer.RendererInheritedEnable) Renderer.Update(deltatime);
        }
        public void SetValue(int index, float v, float duration = 0.0f, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            if (index < 0) return;
            if (index > ValueCount - 1) return;

            Misc.EasingValueEase(ref Values[index], v, duration, mode, beta);
        }
        public float GetValue(int index)
        {
            if (index < 0) return 0.0f;
            if (index > ValueCount - 1) return 0.0f;

            return Values[index].Value;
        }
        public void SetDefaults(float duration = 0.0f, Easing mode = Easing.Linear, float beta = 1.0f)
        {
            for (var i = 0; i < ValueCount; i++)
            {
                SetValue(i, DefaultValues[i], duration, mode, beta);
            }
        }
        public float ConvertSpecialFloat(float v)
        {
            if (v < Misc.InputFloatIndex) return v;

            var index = (int)Math.Floor(v - Misc.InputFloatIndex + 0.5);
            if (index > ValueCount - 1) return 0.0f;

            return Values[index].Value;
        }
    }
    // event layer callback
    public struct StateEventData
    {
        // allow always
        public bool Ignition;
        // state
        public StateInput Input;
        public StateTrack Track;
        public EventKey Key;
        // data
        public string ID;
        public string Strings0; public string Strings1;   //public fixed string[2] Strings;   // avoidance unsafe
        public float Floats0; public float Floats1;      //public fixed float[2] Floats      // avoidance unsafe
    }
	public struct ObjectCache
	{
		public Matrix4 Matrix;
		public bool InheritedEnable;
		public bool InheritedVisible;
		public float DrawPriority;
		public float TransColor0, TransColor1, TransColor2, TransColor3;
	}
    public delegate void StateEventCallback(AnimationrigState state, ref StateEventData data);  // of object
    // state
    public class AnimationrigState
    {
        // protected
        protected int[] ObjectUpdateOrder = new int[0];    // need speed
        protected double FramePool = 0.0;
        protected ObjectCache[] ObjectCache = new ObjectCache[0];
        protected void CompactionTracks()
        {
            var count = TrackCount;
            var usage = 0;
            for (var i = count-1; i >= 0; i--)
            {
                if (Tracks[i] != null)
                {
                    usage = i + 1;
                    break;
                }
            }

            ChangeTracks(usage);
        }
        protected void ChangeTracks(int count)
        {
            var usage = (count / 4) * 4 + 4;
            if (usage == Tracks.Count) return;

            // resize
            if (Tracks.Count < usage)
            {
                var d = usage - Tracks.Count;
                for (var i = 0; i < d; i++)
                {
                    Tracks.Add(null);
                }
            }
            else
            {
                var d = Tracks.Count - usage;
                for (var i = 0; i < d; i++)
                {
                    Tracks.RemoveAt(Tracks.Count - 1);
                }
            }
        }
        // protected update()
        protected int GetSpineUseTrack(LayerType layertype)
        {
            switch (layertype)
            {
                case LayerType.AnimBlend1:
                case LayerType.SeekBlend1:
                    return 1;
                case LayerType.AnimBlend2:
                case LayerType.SeekBlend2:
                    return 2;
                case LayerType.AnimBlend4:
                case LayerType.SeekBlend4:
                    return 4;
            }
            return 0;
        }
        protected void Update_TrackEvent(StateTrack track, bool prevkeyevent, bool seekmode)
        {
            // event
            void inEventAnimationBlend(StateTrackLayer layer, StateInput input, AnimBlend4Key key, int trackindex, int usetrack)
            {
                if (input == null) return;
                if (input.Renderer == null) return;
                // name
                if (key.Animation == "none") return;
                // allow
                if (!key.AnimationAllowAlways)
                    if (layer.String1 == key.Animation) return;

                input.Renderer.SetAnimation(
                    trackindex, usetrack,
                    key.Animation,
                    key.AnimationLoop,
                    key.AnimationAdd,
                    key.AnimationMixBlend,
                    key.AnimationMixDuration,
                    false
                );
                layer.String1 = key.Animation;
            }
            void inEventSeekTimeBlend(StateTrackLayer layer, StateInput input, SeekBlend4Key key, int trackindex, int usetrack)
            {
                if (input == null) return;
                if (input.Renderer == null) return;
                // name
                if (key.Animation == "none") return;
                // allow
                if (!key.AnimationAllowAlways)
                    if (layer.String1 == key.Animation) return;

                input.Renderer.SetAnimation(
                  trackindex, usetrack,
                  key.Animation,
                  key.AnimationLoop,
                  key.AnimationAdd,
                  key.AnimationMixBlend,
                  key.AnimationMixDuration,
                  true
                );
                layer.String1 = key.Animation;
            }
            void inEventEvent(StateTrackLayer layer, StateInput input, EventKey key, StateTrack track_)
            {
                // sender : TAnimrigState
                // owner : Object
                // renderer : TRenderer
                // string[0,1]
                // float[0,1]
                if (OnEvent == null) return;
                // seek flag
                if (seekmode) return;
                // ignition
                if (key.IgnitionAlways) return;
                // name
                if (key.ID == "none") return;
                if (key.ID == "") return;
                if (!key.AllowAlways)
                    if (key.ID == layer.String1) return;

                StateEventData rec; // struct
                rec.Ignition = false;
                rec.Input = input;
                rec.Track = track_;
                rec.Key = key;
                rec.ID = key.ID;
                rec.Strings0 = key.EventString[0];
                rec.Strings1 = key.EventString[1];
                rec.Floats0 = input.ConvertSpecialFloat(key.EventFloat[0]);
                rec.Floats1 = input.ConvertSpecialFloat(key.EventFloat[1]);
                OnEvent(this, ref rec);

                layer.String1 = key.ID;
            }

            {
                if (track.Animation == null) return;
                // loop
                var f = (int)Math.Floor(track.Animation.GetLoopedFrame(track.PlayFrame));
                if (track.PrevPlayFrame == f) return;
                // object
                for (var i = 0; i < ObjectCount; i++)
                {
                    var obj = track.Animation.Objects[i];
                    var objectenable = Data.Objects[i].Enable;  // reserve object
                    var trackindex = 0;
                    var layercount = obj.LayerCount;
                    for (var j = 0; j < layercount; j++)
                    {
                        // layer
                        var layer = obj.Layers[j];
                        // spine use track count
                        // * It is not affected by layer.enable. keep layer.index and spine.trackindex.
                        var usetrack = GetSpineUseTrack(layer.LayerType);

                        if (layer.Enable)
                        {
                            // prevkey(first only)
                            if ((f == 0) && (prevkeyevent))
                            {
                                var key = layer.PrevKey;
                                if (objectenable)
                                {
                                    switch (key.LayerType)
                                    {
                                        case LayerType.AnimBlend1:
                                        case LayerType.AnimBlend2:
                                        case LayerType.AnimBlend4:
                                            inEventAnimationBlend(track.Objects[i].Layers[j], Inputs[i], (AnimBlend4Key)key, trackindex, usetrack);
                                            break;
                                        case LayerType.SeekBlend1:
                                        case LayerType.SeekBlend2:
                                        case LayerType.SeekBlend4:
                                            inEventSeekTimeBlend(track.Objects[i].Layers[j], Inputs[i], (SeekBlend4Key)key, trackindex, usetrack);
                                            break;
                                        case LayerType.Event:
                                            inEventEvent(track.Objects[i].Layers[j], Inputs[i], (EventKey)key, track);
                                            break;
                                    }
                                }
                            }
                            // keys
                            // * Optimize some day :Q
                            var count = layer.KeyCount;
                            for (var k = 0; k < count; k++)
                            {
                                var key = layer.Keys[k];
                                if (key.Frame > f) break;
                                if (key.Frame != f) continue;
                                if (objectenable)
                                {
                                    switch (key.LayerType)
                                    {
                                        case LayerType.AnimBlend1:
                                        case LayerType.AnimBlend2:
                                        case LayerType.AnimBlend4:
                                            inEventAnimationBlend(track.Objects[i].Layers[j], Inputs[i], (AnimBlend4Key)key, trackindex, usetrack);
                                            break;
                                        case LayerType.SeekBlend1:
                                        case LayerType.SeekBlend2:
                                        case LayerType.SeekBlend4:
                                            inEventSeekTimeBlend(track.Objects[i].Layers[j], Inputs[i], (SeekBlend4Key)key, trackindex, usetrack);
                                            break;
                                        case LayerType.Event:
                                            inEventEvent(track.Objects[i].Layers[j], Inputs[i], (EventKey)key, track);
                                            break;
                                    }
                                }
                            }
                        }
                        // spine trackset index
                        trackindex = trackindex + usetrack;
                    }
                }
                // prev play frame
                track.PrevPlayFrame = f;
            }
        }
        protected void Update_Interpolation(bool first, bool seekmode)
        {
            float inInterpolationValue(StateInput input, float x0, float x1, float x2, float x3, float a, float b, Spline splinemode)
            {
                if (splinemode == Spline.Linear)
                {
                    // 2 key : linear
                    if (x1 >= Misc.InputFloatIndex) x1 = input.ConvertSpecialFloat(x1);
                    if (x2 >= Misc.InputFloatIndex) x2 = input.ConvertSpecialFloat(x2);
                    return x1 + (x2 - x1) * a;
                }
                else
                {
                    // 4key : spline
                    if (x0 >= Misc.InputFloatIndex) x0 = input.ConvertSpecialFloat(x0);
                    if (x1 >= Misc.InputFloatIndex) x1 = input.ConvertSpecialFloat(x1);
                    if (x2 >= Misc.InputFloatIndex) x2 = input.ConvertSpecialFloat(x2);
                    if (x3 >= Misc.InputFloatIndex) x3 = input.ConvertSpecialFloat(x3);
                    return Misc.SplineFloat(x0, x1, x2, x3, a, b, splinemode);
                }
            }
            void inInterpolationCommon(StateInput input, StateTrackLayer layer, AnimKey key0, AnimKey key1, AnimKey key2, AnimKey key3, float a)
            {
                // track blend alpha
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.TrackAlphaPower, key1.TrackAlphaEasing);
                if (key1.IsPrevKey)
                {
                    layer.PrevTrackAlpha = Misc.EasingLinear(layer.pPrevTrackAlpha, key2.PrevTrackAlpha, aa);
                    layer.CurrentTrackAlpha = Misc.EasingLinear(layer.pCurrentTrackAlpha, key2.CurrentTrackAlpha, aa);
                }
                else
                {
                    layer.PrevTrackAlpha = Misc.EasingLinear(key1.PrevTrackAlpha, key2.PrevTrackAlpha, aa);
                    layer.CurrentTrackAlpha = Misc.EasingLinear(key1.CurrentTrackAlpha, key2.CurrentTrackAlpha, aa);
                }
            }
            void inInterpolationAnimationBlend(StateInput input, StateTrackLayer layer, AnimBlend4Key key0, AnimBlend4Key key1, AnimBlend4Key key2, AnimBlend4Key key3, float a)
            {
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.BlendAlphaPower, key1.BlendAlphaEasing);
                var bb = 1.0f;
                var ss = key1.BlendAlphaSpline;

                if (key1.IsPrevKey)
                {
                    layer.Values[0] = inInterpolationValue(input, layer.pValues[0], layer.pValues[0], key2.BlendAlphaX, key3.BlendAlphaX, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, layer.pValues[1], layer.pValues[1], key2.BlendAlphaY, key3.BlendAlphaY, aa, bb, ss);
                }
                else
                {
                    layer.Values[0] = inInterpolationValue(input, key0.BlendAlphaX, key1.BlendAlphaX, key2.BlendAlphaX, key3.BlendAlphaX, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, key0.BlendAlphaY, key1.BlendAlphaY, key2.BlendAlphaY, key3.BlendAlphaY, aa, bb, ss);
                }
            }
            void inInterpolationSeekTimeBlend(StateInput input, StateTrackLayer layer, SeekBlend4Key key0, SeekBlend4Key key1, SeekBlend4Key key2, SeekBlend4Key key3, float a)
            {
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.SeekTimePower, key1.SeekTimeEasing);
                var bb = 1.0f;
                var ss = key1.SeekTimeSpline;

                if (key1.IsPrevKey)
                {
                    layer.Values[0] = inInterpolationValue(input, layer.pValues[0], layer.pValues[0], key2.SeekTimeX, key3.SeekTimeX, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, layer.pValues[1], layer.pValues[1], key2.SeekTimeY, key3.SeekTimeY, aa, bb, ss);
                }
                else
                {
                    layer.Values[0] = inInterpolationValue(input, key0.SeekTimeX, key1.SeekTimeX, key2.SeekTimeX, key3.SeekTimeX, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, key0.SeekTimeY, key1.SeekTimeY, key2.SeekTimeY, key3.SeekTimeY, aa, bb, ss);
                }
            }
            void inInterpolationTransform(StateInput input, StateTrackLayer layer, TransformKey key0, TransformKey key1, TransformKey key2, TransformKey key3, float a)
            {
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.TransformPower, key1.TransformEasing);
                var bb = 1.0f;
                var ss = key1.TransSpline;

                if (key1.IsPrevKey)
                {
                    // position
                    layer.Values[0] = inInterpolationValue(input, layer.pValues[0], layer.pValues[0], key2.Position.x, key3.Position.x, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, layer.pValues[1], layer.pValues[1], key2.Position.y, key3.Position.y, aa, bb, ss);
                    layer.Values[2] = inInterpolationValue(input, layer.pValues[2], layer.pValues[2], key2.Position.z, key3.Position.z, aa, bb, ss);
                    // scale
                    layer.Values[3] = inInterpolationValue(input, layer.pValues[3], layer.pValues[3], key2.Scale.x, key3.Scale.x, aa, bb, ss);
                    layer.Values[4] = inInterpolationValue(input, layer.pValues[4], layer.pValues[4], key2.Scale.y, key3.Scale.y, aa, bb, ss);
                    layer.Values[5] = inInterpolationValue(input, layer.pValues[5], layer.pValues[5], key2.Scale.z, key3.Scale.z, aa, bb, ss);
                    // rotate
                    layer.Values[6] = inInterpolationValue(input, layer.pValues[6], layer.pValues[6], key2.Rotate.x, key3.Rotate.x, aa, bb, ss);
                    layer.Values[7] = inInterpolationValue(input, layer.pValues[7], layer.pValues[7], key2.Rotate.y, key3.Rotate.y, aa, bb, ss);
                    layer.Values[8] = inInterpolationValue(input, layer.pValues[8], layer.pValues[8], key2.Rotate.z, key3.Rotate.z, aa, bb, ss);
                    layer.RotateOrder = layer.pRotateOrder;
                    // color (not support spline)
                    aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.ColorPower, key1.ColorEasing);
                    layer.Values[9] = Misc.EasingLinear(layer.pValues[9], key2.mulAlpha, aa);
                    layer.Values[10] = Misc.EasingLinear(layer.pValues[10] * 0xff, key2.mulColor >> 16 & 0xff, aa) * (1.0f / 0xff);
                    layer.Values[11] = Misc.EasingLinear(layer.pValues[11] * 0xff, key2.mulColor >> 8  & 0xff, aa) * (1.0f / 0xff);
                    layer.Values[12] = Misc.EasingLinear(layer.pValues[12] * 0xff, key2.mulColor       & 0xff, aa) * (1.0f / 0xff);
                }
                else
                {
                    // position
                    layer.Values[0] = inInterpolationValue(input, key0.Position.x, key1.Position.x, key2.Position.x, key3.Position.x, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, key0.Position.y, key1.Position.y, key2.Position.y, key3.Position.y, aa, bb, ss);
                    layer.Values[2] = inInterpolationValue(input, key0.Position.z, key1.Position.z, key2.Position.z, key3.Position.z, aa, bb, ss);
                    // scale
                    layer.Values[3] = inInterpolationValue(input, key0.Scale.x, key1.Scale.x, key2.Scale.x, key3.Scale.x, aa, bb, ss);
                    layer.Values[4] = inInterpolationValue(input, key0.Scale.y, key1.Scale.y, key2.Scale.y, key3.Scale.y, aa, bb, ss);
                    layer.Values[5] = inInterpolationValue(input, key0.Scale.z, key1.Scale.z, key2.Scale.z, key3.Scale.z, aa, bb, ss);
                    // rotate
                    layer.Values[6] = inInterpolationValue(input, key0.Rotate.x, key1.Rotate.x, key2.Rotate.x, key3.Rotate.x, aa, bb, ss);
                    layer.Values[7] = inInterpolationValue(input, key0.Rotate.y, key1.Rotate.y, key2.Rotate.y, key3.Rotate.y, aa, bb, ss);
                    layer.Values[8] = inInterpolationValue(input, key0.Rotate.z, key1.Rotate.z, key2.Rotate.z, key3.Rotate.z, aa, bb, ss);
                    layer.RotateOrder = key1.RotateOrder;
                    // color (not support spline)
                    aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.ColorPower, key1.ColorEasing);
                    layer.Values[9] = Misc.EasingLinear(key1.mulAlpha, key2.mulAlpha, aa);
                    layer.Values[10] = Misc.EasingLinear(key1.mulColor >> 16 & 0xff , key2.mulColor >> 16 & 0xff , aa) * (1.0f / 0xff);
                    layer.Values[11] = Misc.EasingLinear(key1.mulColor >>  8 & 0xff , key2.mulColor >>  8 & 0xff , aa) * (1.0f / 0xff);
                    layer.Values[12] = Misc.EasingLinear(key1.mulColor       & 0xff , key2.mulColor       & 0xff , aa) * (1.0f / 0xff);
                }
            }
            void inInterpolationPriority(StateInput input, StateTrackLayer layer, PriorityKey key0, PriorityKey key1, PriorityKey key2, PriorityKey key3, float a)
            {
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.PriorityPower, key1.PriorityEasing);

                if (key1.IsPrevKey)
                {
                    layer.Values[0] = Misc.EasingLinear(layer.pValues[0], key2.Priority, aa);
                }
                else
                {
                    layer.Values[0] = Misc.EasingLinear(key1.Priority, key2.Priority, aa);
                }
            }
            void inInterpolationState(StateInput input, StateTrackLayer layer, StateKey key0, StateKey key1, StateKey key2, StateKey key3, float a)
            {
                layer.Values[0] = 0.0f;
                layer.Values[1] = 0.0f;
                if (key1.Enable) layer.Values[0] = 1.0f;
                if (key1.Visible) layer.Values[1] = 1.0f;
            }
            void inInterpolationPhysicsForce(StateInput input, StateTrackLayer layer, PhysicsForceKey key0, PhysicsForceKey key1, PhysicsForceKey key2, PhysicsForceKey key3, float a, double pt)
            {
                float ww = 0.0f;
                var aa = Misc.EasingFloat(0.0f, 1.0f, a, key1.ForcePower, key1.ForceEasing);
                var bb = 1.0f;
                var ss = key1.ForceSpline;
                layer.String1 = key1.PhysicsTarget;

                if (key1.IsPrevKey)
                {
                    layer.Values[0] = inInterpolationValue(input, layer.pValues[0], layer.pValues[0], key2.ForceVector.x, key3.ForceVector.x, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, layer.pValues[1], layer.pValues[1], key2.ForceVector.y, key3.ForceVector.y, aa, bb, ss);
                    layer.Values[2] = inInterpolationValue(input, layer.pValues[2], layer.pValues[2], key2.ForceVector.z, key3.ForceVector.z, aa, bb, ss);
                    ww = 0.0f;
                }
                else
                {
                    layer.Values[0] = inInterpolationValue(input, key0.ForceVector.x, key1.ForceVector.x, key2.ForceVector.x, key3.ForceVector.x, aa, bb, ss);
                    layer.Values[1] = inInterpolationValue(input, key0.ForceVector.y, key1.ForceVector.y, key2.ForceVector.y, key3.ForceVector.y, aa, bb, ss);
                    layer.Values[2] = inInterpolationValue(input, key0.ForceVector.z, key1.ForceVector.z, key2.ForceVector.z, key3.ForceVector.z, aa, bb, ss);
                    ww = key1.WiggleRatio + (key2.WiggleRatio - key1.WiggleRatio) * aa;
                }

                if (ww > 0.0)
                {
                    aa = ww * (Wiggle.Random(pt * (double)key1.WiggleCycle, WiggleType.BSpline, 275) - 0.5f);
                    layer.Values[0] = layer.Values[0] + layer.Values[0] * aa;
                    layer.Values[1] = layer.Values[1] + layer.Values[1] * aa;
                    layer.Values[2] = layer.Values[2] + layer.Values[2] * aa;
                }
            }
            void inInterpolationEvent(StateInput input, StateTrack track_, StateTrackLayer layer, EventKey key)
            {
                if (OnEvent == null) return;
                // seek flag
                if (seekmode) return;
                // ignition
                if (!key.IgnitionAlways) return;
                // name
                if (key.ID == "none") return;
                if (key.ID == "") return;
                // * unsupport allowdiffrent
                // if not key1.AllowAlways then
                //   if key1.ID = layer.EventID then exit;
                StateEventData rec;
                rec.Ignition = true;
                rec.Input = input;
                rec.Track = track_;
                rec.Key = key;
                rec.ID = key.ID;
                rec.Strings0 = key.EventString[0];
                rec.Strings1 = key.EventString[1];
                rec.Floats0 = input.ConvertSpecialFloat(key.EventFloat[0]);
                rec.Floats1 = input.ConvertSpecialFloat(key.EventFloat[1]);
                OnEvent(this, ref rec);

                layer.String1 = key.ID;
            }

            // main
            foreach(var track in Tracks)
            {
                if (track == null) continue;
                if (!track.Enable) continue;

                var anim = track.Animation;
                if (anim == null) return;
                // loop
                double f = anim.GetLoopedFrame(track.PlayFrame);
                // object
                for (var i = 0; i < ObjectCount; i++)
                {
                    var obj = anim.Objects[i];
                    var layercount = obj.LayerCount;
                    for (var j = 0; j < layercount; j++)
                    {
                        var layer = obj.Layers[j];
                        var tracklayer = track.Objects[i].Layers[j];
                        var keycount = layer.KeyCount;
                        tracklayer.IsPass = false;
                        // key empty
                        if (keycount == 0) tracklayer.IsPass = true;
                        // enable
                        if (!layer.Enable) tracklayer.IsPass = true;
                        // pass
                        if (tracklayer.IsPass)
                        {
                            StateTrackLayer.ClearValues(tracklayer.Values, tracklayer.LayerType);   // static
                            continue;
                        }

                        // get current key
                        AnimKey key0, key1, key2, key3;
                        double d = 0.0;
                        var index = layer.GetKeyIndex((int)Math.Floor(f));
                        if (index == -1)
                        {
                            // between prevkey(0frame) to key[0]
                            key1 = layer.PrevKey;
                            if (keycount > 0)
                                key2 = layer.Keys[0];
                            else
                                key2 = key1;

                            d = (f - 0.0) / Math.Max(key2.Frame - 0.0, 1.0);
                            key0 = key1;
                            if (1 < keycount)
                                key3 = layer.Keys[1];
                            else
                                key3 = key2;
                        }
                        else
                        {
                            // keys[]
                            // current key
                            key1 = layer.Keys[index];
                            // next key
                            if (index + 1 < keycount)
                            {
                                key2 = layer.Keys[index + 1];
                                d = (f - key1.Frame) / Math.Max(key2.Frame - key1.Frame, 1);
                            }
                            else
                            {
                                // last key
                                key2 = key1;
                                d = 0.0;
                            }
                            // previous key
                            if (index - 1 < 0)
                                key0 = layer.PrevKey;
                            else
                                key0 = layer.Keys[index - 1];
                            // next next key
                            if (index + 2 < keycount)
                                key3 = layer.Keys[index + 2];
                            else
                                key3 = key2;
                        }
                        // common
                        inInterpolationCommon(
                          Inputs[i],
                          tracklayer,                    // track.layers (output)
                          key0, key1, key2, key3,
                          (float)d
                        );
                        // layer type
                        switch (layer.LayerType)
                        {
                            case LayerType.AnimBlend1:
                            case LayerType.AnimBlend2:
                            case LayerType.AnimBlend4:
                                inInterpolationAnimationBlend(
                                    Inputs[i],
                                    tracklayer,
                                    (AnimBlend4Key)key0, (AnimBlend4Key)key1, (AnimBlend4Key)key2, (AnimBlend4Key)key3,
                                    (float)d
                                );
                                break;
                            case LayerType.SeekBlend1:
                            case LayerType.SeekBlend2:
                            case LayerType.SeekBlend4:
                                inInterpolationSeekTimeBlend(
                                    Inputs[i],
                                    tracklayer,
                                    (SeekBlend4Key)key0, (SeekBlend4Key)key1, (SeekBlend4Key)key2, (SeekBlend4Key)key3,
                                    (float)d
                                );
                                break;
                            case LayerType.Transform:
                                inInterpolationTransform(
                                    Inputs[i],
                                    tracklayer,
                                    (TransformKey)key0, (TransformKey)key1, (TransformKey)key2, (TransformKey)key3,
                                    (float)d
                                );
                                break;
                            case LayerType.Priority:
                                inInterpolationPriority(
                                    Inputs[i],
                                    tracklayer,
                                    (PriorityKey)key0, (PriorityKey)key1, (PriorityKey)key2, (PriorityKey)key3,
                                    (float)d
                                );
                                break;
                            case LayerType.State:
                                inInterpolationState(
                                    Inputs[i],
                                    tracklayer,
                                    (StateKey)key0, (StateKey)key1, (StateKey)key2, (StateKey)key3,
                                    (float)d
                                );
                                break;
                            case LayerType.PhysicsForce:
                                inInterpolationPhysicsForce(
                                    Inputs[i],
                                    tracklayer,
                                    (PhysicsForceKey)key0, (PhysicsForceKey)key1, (PhysicsForceKey)key2, (PhysicsForceKey)key3,
                                    (float)d, track.PlayFrame / Data.Framerate
                                );
                                break;
                            case LayerType.Event:
                                inInterpolationEvent(
                                    Inputs[i],
                                    track,
                                    tracklayer,
                                    (EventKey)key1
                                );
                                break;
                        }
                    }
                }
            }
        }
        protected void Update_Composite(double deltatime)
        {
            bool inCalcCommonLayer(StateTrack track, float[] mix, int mixcount, int objectindex, int layerindex)
            {
                if (track == null) return false;
                if (track.Animation == null) return false;

                var layer = track.Objects[objectindex].Layers[layerindex];
                if (layer.IsPass)
                    if (track.MixAlphaEase.Done) return false;      // fade blend

                var aa1 = (float)(1.0 - track.MixAlphaEase.Value);
                for (var i = 0; i < mixcount; i++)
                {
                    mix[i] = layer.Values[i] + (layer.pValues[i] - layer.Values[i]) * aa1;
                }
                return true;
            }
            // main
            {
                Vector3 zeroposition;
                Misc.v3SetVector(out zeroposition, 0.0f, 0.0f, 0.0f);
                // align
                Matrix4 alignmatrix;
                if (AlignEnabled)
                {
                    alignmatrix = GetAlignMatrix();
                    Misc.m4PushMatrix(ref alignmatrix, ref RootMatrix);
                }
                else{
                    alignmatrix = RootMatrix;
                }
               
                Matrix4 transmatrix;
                float[] transcolor = new float[4];
                float[] sum = new float[StateTrackLayer.ValueCount];
                float[] mix = new float[StateTrackLayer.ValueCount];

                for (var o = 0; o < ObjectCount; o++)
                {
                    // object update order (parent object link)
                    var objectindex = ObjectUpdateOrder[o];
                    var obj = Data.Objects[objectindex];     // reserve object.
                    var renderer = Inputs[objectindex].Renderer; // renderer object
                    var layercount = obj.LayerCount;

                    // enable / visible
                    var enable = obj.Enable;
                    var visible = obj.Visible;
                    // offset , scale
                    transmatrix = Misc.IdentityMatrix4;
                    Misc.m4PushMove(ref transmatrix, -obj.Offset.x, -obj.Offset.y, -obj.Offset.z);
                    Misc.m4PushScale(ref transmatrix, obj.Scale.x, obj.Scale.y, obj.Scale.z);
                    // color
                    transcolor[0] = 1.0f;  // argb
                    transcolor[1] = 1.0f;
                    transcolor[2] = 1.0f;
                    transcolor[3] = 1.0f;
                    // etc
                    var trackindex = 0;     // spine track index
                    var drawpriority = obj.DrawPriority;
                    var rotateorder = RotateOrder.Pass;
                    var spinelayerexists = false;

                    // composite layer direction : blendalpha
                    for (var i = 0; i < layercount; i++)
                    {
                        // spine use track count
                        // * It is not affected by layer.enable. keep layer.index and spine.trackindex.
                        var layertype = obj.Layers[i].LayerType;
                        var usetrack = GetSpineUseTrack(layertype);

                        // Split code someday ...
                        switch (layertype)
                        {
                            case LayerType.AnimBlend1:
                            case LayerType.AnimBlend2:
                            case LayerType.AnimBlend4:
                                StateTrackLayer.ClearValues(sum, LayerType.AnimBlend4);    // static
                                spinelayerexists = false;
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    if (!inCalcCommonLayer(track, mix, 2, objectindex, i)) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    // track blend
                                    var aa3 = track.TrackAlphaEase.Value;
                                    var aa1 = 1.0f + (layer.PrevTrackAlpha - 1.0f) * aa3;
                                    var aa2 = layer.CurrentTrackAlpha * aa3;
                                    sum[0] = sum[0] * aa1 + mix[0] * aa2;
                                    sum[1] = sum[1] * aa1 + mix[1] * aa2;
                                    spinelayerexists = true;
                                }
                                {
                                    var bb1 = Math.Min(Math.Max(sum[1], 0.0f), +2.0f);
                                    var bb2 = Math.Min(Math.Max(sum[0], 0.0f), +2.0f);
                                    var bb3 = -Math.Min(Math.Max(sum[1], -2.0f), 0.0f);
                                    var bb4 = -Math.Min(Math.Max(sum[0], -2.0f), 0.0f);
                                    if ((renderer != null) && (enable))
                                        renderer.SetTrackBlendAlpha(
                                            trackindex, usetrack,
                                            bb1, bb2, bb3, bb4,
                                            spinelayerexists    // exists enable layer layer
                                        );
                                }
                                break;
                            case LayerType.SeekBlend1:
                            case LayerType.SeekBlend2:
                            case LayerType.SeekBlend4:
                                StateTrackLayer.ClearValues(sum, LayerType.SeekBlend4);  // static
                                spinelayerexists = false;
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    if (!inCalcCommonLayer(track, mix, 2, objectindex, i)) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    // track blend
                                    var aa3 = track.TrackAlphaEase.Value;
                                    var aa1 = 1.0f + (layer.PrevTrackAlpha - 1.0f) * aa3;
                                    var aa2 = layer.CurrentTrackAlpha * aa3;
                                    sum[0] = sum[0] * aa1 + mix[0] * aa2;
                                    sum[1] = sum[1] * aa1 + mix[1] * aa2;
                                    spinelayerexists = true;
                                }
                                {
                                    var tt1 = Math.Min(Math.Max(sum[1], 0.0f), +3600f);
                                    var tt2 = Math.Min(Math.Max(sum[0], 0.0f), +3600f);
                                    var tt3 = -Math.Min(Math.Max(sum[1], -3600f), 0.0f);
                                    var tt4 = -Math.Min(Math.Max(sum[0], -3600f), 0.0f);
                                    if ((renderer != null) && (enable))
                                    {
                                        renderer.SetTrackBlendAlpha(
                                            trackindex, usetrack,
                                            1.0f, 1.0f, 1.0f, 1.0f,
                                            spinelayerexists    // exists enable layer layer
                                        );
                                        renderer.SetTrackSeekTime(
                                            trackindex, usetrack,
                                            tt1, tt2, tt3, tt4,
                                            spinelayerexists    // exists enable layer layer
                                        );
                                    }
                                }
                                break;
                            case LayerType.Transform:
                                StateTrackLayer.ClearValues(sum, LayerType.Transform);  // static
                                rotateorder = RotateOrder.Pass;
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    if (!inCalcCommonLayer(track, mix, 3 + 3 + 3 + 4, objectindex, i)) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    // track blend
                                    var aa3 = track.TrackAlphaEase.Value;
                                    var aa1 = 1.0f + (layer.PrevTrackAlpha - 1.0f) * aa3;
                                    var aa2 = layer.CurrentTrackAlpha * aa3;
                                    // move
                                    sum[0] = sum[0] * aa1 + mix[0] * aa2;
                                    sum[1] = sum[1] * aa1 + mix[1] * aa2;
                                    sum[2] = sum[2] * aa1 + mix[2] * aa2;
                                    // scale
                                    sum[3] = (1.0f + (sum[3] - 1.0f) * aa1) * (1.0f + (mix[3] - 1.0f) * aa2);
                                    sum[4] = (1.0f + (sum[4] - 1.0f) * aa1) * (1.0f + (mix[4] - 1.0f) * aa2);
                                    sum[5] = (1.0f + (sum[5] - 1.0f) * aa1) * (1.0f + (mix[5] - 1.0f) * aa2);
                                    // rotate
                                    sum[6] = sum[6] * aa1 + mix[6] * aa2;
                                    sum[7] = sum[7] * aa1 + mix[7] * aa2;
                                    sum[8] = sum[8] * aa1 + mix[8] * aa2;
                                    // rotatemode
                                    if (layer.RotateOrder != RotateOrder.Pass) rotateorder = layer.RotateOrder;
                                    // alpha,color
                                    sum[9] = (1.0f + (sum[9] - 1.0f) * aa1) * (1.0f + (mix[9] - 1.0f) * aa2);
                                    sum[10] = (1.0f + (sum[10] - 1.0f) * aa1) * (1.0f + (mix[10] - 1.0f) * aa2);
                                    sum[11] = (1.0f + (sum[11] - 1.0f) * aa1) * (1.0f + (mix[11] - 1.0f) * aa2);
                                    sum[12] = (1.0f + (sum[12] - 1.0f) * aa1) * (1.0f + (mix[12] - 1.0f) * aa2);
                                }
                                {
                                    if (rotateorder == RotateOrder.Pass) rotateorder = RotateOrder.ZYX;
                                    // composite transform
                                    Misc.m4PushScale(ref transmatrix, sum[3], sum[4], sum[5]);
                                    Misc.m4PushRotate(ref transmatrix, sum[6], sum[7], sum[8], rotateorder);   // rotate mode
                                    Misc.m4PushMove(ref transmatrix, sum[0], sum[1], sum[2]);
                                    // color
                                    transcolor[0] = transcolor[0] * sum[9];
                                    transcolor[1] = transcolor[1] * sum[10];
                                    transcolor[2] = transcolor[2] * sum[11];
                                    transcolor[3] = transcolor[3] * sum[12];
                                }
                                break;
                            case LayerType.Priority:
                                StateTrackLayer.ClearValues(sum, LayerType.Priority);  // static
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    if (!inCalcCommonLayer(track, mix, 1, objectindex, i)) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    // track blend
                                    var aa3 = track.TrackAlphaEase.Value;
                                    var aa1 = 1.0f + (layer.PrevTrackAlpha - 1.0f) * aa3;
                                    var aa2 = layer.CurrentTrackAlpha * aa3;
                                    sum[0] = sum[0] * aa1 + mix[0] * aa2;
                                }
                                {
                                    drawpriority = drawpriority + sum[0];
                                }
                                break;
                            case LayerType.State:
                                // clear
                                StateTrackLayer.ClearValues(sum, LayerType.State);
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    enable = enable & (layer.Values[0] > 0.0f);
                                    visible = visible & (layer.Values[1] > 0.0f);
                                }
                                break;
                            case LayerType.PhysicsForce:
                                StateTrackLayer.ClearValues(sum, LayerType.PhysicsForce);
                                for (var j = 0; j < TrackCount; j++)
                                {
                                    var track = Tracks[j];
                                    if (track == null) continue;
                                    if (!inCalcCommonLayer(track, mix,3, objectindex,i)) continue;
                                    var layer = track.Objects[objectindex].Layers[i];
                                    // current track alpha only
                                    var aa3 = layer.CurrentTrackAlpha * track.TrackAlphaEase.Value;
                                    aa3 = (float)(aa3 * deltatime);    // apply dt
                                    if (renderer != null) renderer.AddPhysicsForce(layer.String1, mix[0] * aa3, mix[1] * aa3, mix[2] * aa3);
                                }
                                break;
                        }

                        // spine track index
                        trackindex = trackindex + usetrack;
                    }

                    // group
					// ref cache =  ref ObjectCache[objectindex];   // need C# ver 7.0
                    var parentindex = Data.Objects[objectindex].ParentObjectIndex;
                    if ((parentindex >= 0) && (parentindex < ObjectCount) && (parentindex != objectindex))
                    {
                        // composite parent matrix
                        var s = Data.Objects[objectindex].ParentBoneName;
                        if ((s == "") || (s == "none") || (Inputs[parentindex].Renderer == null))
                        {
                            Misc.m4PushMatrix(ref transmatrix, ref ObjectCache[parentindex].Matrix);
                            ObjectCache[objectindex].Matrix = transmatrix; // hard copy
                        }
                        else
                        {
                            var tempmatrix = Inputs[parentindex].Renderer.GetBoneMatrix(s);
                            Misc.m4PushMatrix(ref transmatrix, ref tempmatrix);
                            Misc.m4PushMatrix(ref transmatrix,ref ObjectCache[parentindex].Matrix);
                            ObjectCache[objectindex].Matrix = transmatrix; // hard copy
                        }

						ObjectCache[objectindex].InheritedEnable = enable & ObjectCache[parentindex].InheritedEnable;
						ObjectCache[objectindex].InheritedVisible = visible & ObjectCache[parentindex].InheritedVisible;
						ObjectCache[objectindex].DrawPriority  = drawpriority + ObjectCache[parentindex].DrawPriority;
						ObjectCache[objectindex].TransColor0 = transcolor[0] * ObjectCache[parentindex].TransColor0;
						ObjectCache[objectindex].TransColor1 = transcolor[1] * ObjectCache[parentindex].TransColor1;
						ObjectCache[objectindex].TransColor2 = transcolor[2] * ObjectCache[parentindex].TransColor2;
						ObjectCache[objectindex].TransColor3 = transcolor[3] * ObjectCache[parentindex].TransColor3;
					}
                    else
                    {
                        Misc.m4PushMatrix(ref transmatrix, ref alignmatrix);
						ObjectCache[objectindex].Matrix = transmatrix; // hard copy
						ObjectCache[objectindex].InheritedEnable = enable;
						ObjectCache[objectindex].InheritedVisible = visible;
						ObjectCache[objectindex].DrawPriority = drawpriority;
						ObjectCache[objectindex].TransColor0 = transcolor[0];
						ObjectCache[objectindex].TransColor1 = transcolor[1];
						ObjectCache[objectindex].TransColor2 = transcolor[2];
						ObjectCache[objectindex].TransColor3 = transcolor[3];
					}

                    // state
                    if (renderer != null)
                    {
                        renderer.RendererEnable = enable;
                        renderer.RendererVisible = visible;
						renderer.RendererInheritedEnable = ObjectCache[objectindex].InheritedEnable;
						renderer.RendererInheritedVisible = ObjectCache[objectindex].InheritedVisible;
						renderer.TransColor[0] = ObjectCache[objectindex].TransColor0;
                        renderer.TransColor[1] = ObjectCache[objectindex].TransColor1;
						renderer.TransColor[2] = ObjectCache[objectindex].TransColor2;
						renderer.TransColor[3] = ObjectCache[objectindex].TransColor3;
						renderer.TransMatrix = transmatrix;   // struct hard copy
                        // priority
						renderer.TransPosition = Misc.m4Transform(ref zeroposition, ref transmatrix);
						renderer.DrawPriorityZ = renderer.TransPosition.z;
                        renderer.DrawPriorityDesign = ObjectCache[objectindex].DrawPriority;
                    }
                }
            }
        }
        // property
        public static readonly int InputValueSaveVersion = 1;
        public static readonly int SaveVersion =
          StateTrack.SaveVersion +
          StateTrackLayer.SaveVersion +
          InputValueSaveVersion +
          1;    // self version

        public Matrix4 RootMatrix;
        public StateEventCallback OnEvent = null;
        public Animationrig Data { get; protected set; } = null;
        //   align
        public bool AlignEnabled = false;
        public float AlignX = 0.0f;
        public float AlignY = 0.0f;
        public float AlignZ = 0.0f;
        public Matrix4 GetAlignMatrix()
        {
            Matrix4 m = Misc.IdentityMatrix4;
            Misc.m4PushMove(
                ref m,
                -Data.Offset.x - Data.Size.x * AlignX,
                -Data.Offset.y - Data.Size.y * AlignY,
                -Data.Offset.z - Data.Size.z * AlignZ
            );
            return m;
        }
        //   tracks
        public List<StateTrack> Tracks { get; protected set; } = new List<StateTrack>();
        public int TrackCount { get { return Tracks.Count; } }
        public int UsageTrackCount {
            get
            {
                for (var i = TrackCount - 1; i >= 0; i--)
                    if (Tracks[i] != null) return i + 1;
                return 0;
            }
        }
        public StateTrack TracksSafe(int index)     // Allow out of range access. safty.
        {
            if (index < 0) return null;
            if (index > TrackCount - 1) return null;
            return Tracks[index];
        }
        //   inputs
        public List<StateInput> Inputs { get; protected set; } = new List<StateInput>();
        public int InputCount { get { if (Inputs.Count != ObjectCount) Rebuild(); return Inputs.Count; } }
        public int ObjectCount { get { return Data.ObjectCount; } }
        public StateInput InputsSafe(int index)     // Allow out of range access. safty.
        {
            if (index < 0) return null;
            if (index > InputCount - 1) return null;
            return Inputs[index];
        }
        // public
        public AnimationrigState(Animationrig data)
        {
            Data = data;

            // Tracks = new
            // Inputs:= new 
            RootMatrix = Misc.IdentityMatrix4;

            Rebuild();

            if (Data == null)
            {
                throw new Exception("AnimationRigState : need AnimationRig class.");
            }
        }
        public void SaveToBinary(MemoryStream ms)
        {
            // header
            Misc.WriteHead(ms, "animrigstate");
            // version
            Misc.WriteInteger(ms, SaveVersion);

            // tracks
            {
                Misc.WriteHead(ms, "tracks");
                var count = UsageTrackCount;
                Misc.WriteInteger(ms, count);
                for (var i = 0; i < count; i++)
                {
                    // head
                    Misc.WriteHead(ms, "track");
                    // data length
                    var endpoint = Misc.WriteSkipBegin(ms);
                    // version
                    Misc.WriteInteger(ms, StateTrack.SaveVersion);

                    var track = Tracks[i];
                    if (track == null)
                    {
                        // exists = false
                        Misc.WriteBool(ms, false);
                    }
                    else
                    {
                        // exists = true
                        Misc.WriteBool(ms, true);
                        // track data
                        var s = "";
                        if (track.Animation != null) s = track.Animation.Name;
                        Misc.WriteString(ms, s);
                        track.SaveToBinary(ms);
                    }

                    // write data length
                    Misc.WriteSkipEnd(ms, endpoint);
                }
            }
            // input value
            {
                // header
                Misc.WriteHead(ms, "inputobjects");
                var count = ObjectCount;
                Misc.WriteInteger(ms, count);
                for (var i = 0; i < count; i++)
                {
                    // header
                    Misc.WriteHead(ms, "inputobject");
                    // data length
                    var endpoint = Misc.WriteSkipBegin(ms);
                    // version
                    Misc.WriteInteger(ms, InputValueSaveVersion);
                    // type
                    Misc.WriteInteger(ms, (int)(Inputs[i].ObjectType));
                    // head
                    Misc.WriteHead(ms, "values");
                    var valuecount = Inputs[i].ValueCount;
                    Misc.WriteInteger(ms, valuecount);
                    for (var j = 0; j < valuecount; j++)
                    {
                        Misc.EasingValueSaveToBinary(ref Inputs[i].Values[j], ms);
                    }
                    // write data length
                    Misc.WriteSkipEnd(ms, endpoint);
                }
            }
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            try
            {
                // header
                Misc.ReadHead(ms, "animrigstate");
                // version
                var ver = Misc.ReadInteger(ms);

                // tracks
                {
                    Misc.ReadHead(ms, "tracks");
                    var count = Misc.ReadInteger(ms, 0, 0xffff, false, false);

                    ClearTracks();

                    for (var i = 0; i < count; i++)
                    {
                        // header
                        Misc.ReadHead(ms, "track");
                        // data length
                        var endpoint = Misc.ReadSkipBegin(ms);
                        // version
                        var trackver = Misc.ReadInteger(ms, 0, StateTrack.SaveVersion);
                        // exists = true
                        var b = Misc.ReadBool(ms);
                        if (b)
                        {
                            // animation name
                            var s = Misc.ReadString(ms);
                            var track = EntryTrack(i, s, true);   // try set animation name. (*can not move Track.LoadFromBinary() because of need EntryTrack())
                            if (track != null)
                                if (!track.LoadFromBinary(ms, trackver)) return false;
                        }
                        // jump data endpoint
                        Misc.JumpSkipEnd(ms, endpoint);
                    }
                }
                // inputs
                {
                    // clear
                    {
                        var count = InputCount;
                        for (var i = 0; i < InputCount; i++)
                        {
                            Inputs[i].Clear();
                        }
                    }
                    // header
                    {
                        Misc.ReadHead(ms, "inputobjects");
                        var count = Misc.ReadInteger(ms, 0, 0xffff);
                        for (var i = 0; i < count; i++)
                        {
                            // header
                            Misc.ReadHead(ms, "inputobject");
                            // data length
                            var endpoint = Misc.ReadSkipBegin(ms);

                            if (i < ObjectCount)
                            {
                                // version
                                var inputver = Misc.ReadInteger(ms, 0, InputValueSaveVersion);
                                // type
                                var type_ = Misc.ReadInteger(ms);
                                // head
                                Misc.ReadHead(ms, "values");
                                // value count
                                var valuecount = Misc.ReadInteger(ms, 0, 0xff);
                                // set length
                                if (valuecount > Inputs[i].ValueCount) Inputs[i].ValueCount = valuecount;
                                // valuse
                                for (var j = 0; j < valuecount; j++)
                                {
                                    if (!Misc.EasingValueLoadFromBinary(ref Inputs[i].Values[j], ms)) return false;
                                }

                                // type check
                                if ((int)Inputs[i].ObjectType != type_) Inputs[i].Clear();
                            }
                            // jump end point
                            Misc.JumpSkipEnd(ms, endpoint);
                        }
                    }
                }
            }
            catch
            {
                return false;
            }

            return true;
        }
        public void ClearObjects()
        {
            foreach(var o in Inputs)
            {
                o.Clear();
            }
        }
        /// <summary>
        ///   for editor!
        ///   seek time function.
        /// </summary>
        /// <param name="targettrackindex">track index no.</param>
        /// <param name="time">seek time(sec).</param>
        /// <param name="framegrid">move time by frame rate.</param>
        /// <returns></returns>
        public bool Seek(int targettrackindex, double time, bool framegrid)
        {
            if (targettrackindex < 0) return false;
            if (targettrackindex > TrackCount - 1) return false;
            var track = Tracks[targettrackindex];
            if (track == null) return false;

            time = Math.Max(time, 0.0);

            var frame = time * Data.Framerate;
            if (framegrid) {
                frame = Math.Floor(frame + 0.5);
            }
            var fps = 1.0 / Data.Framerate;
            var d = frame - Math.Floor(track.PlayFrame + 0.5);

            // frame grid
            if (framegrid) {
                if (Math.Abs(d) < fps) return false;
            }

            // seek
            if (d >= 0.0)
            {
                Update(d * fps, false, true);
                return false;
            }
            else
            {
                track.Reset(true);
                Update(time, true, true);   // skip mode
                return true;                // rewind flag
            }
        }
        public void Rebuild()
        {
            var count = ObjectCount;
            // tempolary matrix
            ObjectCache = new ObjectCache[count];
            // object update priority
            ObjectUpdateOrder = new int[count];
            {
                var pri = new double[count];
                for (var i = 0; i < count; i++)
                {
                    pri[i] = 0;
                }

                for (var i = 0; i < count; i++)
                {
                    var idx = Data.Objects[i].ParentObjectIndex;
                    var level = 0;
                    while (true)
                    {
                        if (idx < 0) break;   // none
                        if (idx == i) break;   // self index
                        if (idx >= count) break;   // limit over
                        level++;
                        idx = Data.Objects[idx].ParentObjectIndex;
                    }
                    pri[i] = level;
                }

                Misc.GetSortedIndexes(ObjectUpdateOrder, pri, count, true); //get sorted indexes array. ascending order.
            }

            // input objects
            Inputs.Clear();
            foreach (var o in Data.Objects)
            {
                var input = new StateInput(o.ObjectType);
                input.SetupDefault(o);
                Inputs.Add(input);
            }

            // tracks
            foreach(var t in Tracks)
            {
                if (t == null) continue;
                t.Rebuild();
            }
        }
        public StateTrack EntryTrack(int trackindex, int animindex, bool reset = false)
        {
            if (Data == null) return null;

            // animation
            if (animindex < -1) return null;
            if (animindex > Data.AnimationCount - 1) return null;

            // change track length
            if (trackindex < 0) return null;
            ChangeTracks(trackindex + 1);

            // exists?
            if (Tracks[trackindex] == null)
            {
                Tracks[trackindex] = new StateTrack(Data, animindex);
                return Tracks[trackindex];
            }
            else
            {
                Tracks[trackindex].Reset(reset);
                Tracks[trackindex].AnimationIndex = animindex;
                return Tracks[trackindex];
            }
        }
        public StateTrack EntryTrack(int trackindex, string animname, bool reset = false)
        {
            if (Data == null) return null;

            var index = Data.Search(animname);
            if (index == -1) return null;

            return EntryTrack(trackindex, index, reset);
        }
        public void ClearTrack(int trackindex)
        {
            if (trackindex < 0) return;
            if (trackindex > Tracks.Count - 1) return;

            // dispose & null
            Tracks[trackindex] = null;
            CompactionTracks();
        }
        public void ClearTracks()
        {
            for (var i= 0; i < TrackCount; i++)
            {
                Tracks[i] = null;
            }
            CompactionTracks();
        }
        public StateTrack EntryTrackFade(int trackindex, string animname, float fadeduration, Easing mode = Easing.InOutQuad, float beta = 1.0f)
        {
            if (fadeduration < 0.001f) {
                return EntryTrack(trackindex, animname);
            }
            
            if (Tracks[trackindex] == null)
            {
                // empty previous animation
                var track = EntryTrack(trackindex, animname, false);
                if (track != null) track.Fadein(fadeduration, 1.0f, mode, beta);
                return track;
            }
            else
            {
                // exists previous animation
                var track = EntryTrack(trackindex, animname, false);
                if (track != null) track.Fademix(fadeduration, mode, beta);
                return track;
            }
        }
        public StateTrack ClearTrackFade(int trackindex, float fadeduration, Easing mode = Easing.InOutQuad, float beta = 1.0f)
        {
            if (fadeduration < 0.001)
            {
                ClearTrack(trackindex);
                return null;
            }

            var track = Tracks[trackindex];
            if (track != null) track.Fadeout(fadeduration, mode, beta);
            return track;
        }


        /// <summary>
        ///   update time.
        /// </summary>
        /// <param name="deltatime">time(sec).</param>
        /// <param name="updateonce">(def:false) execute objectupdate() only once. but object animation time is shifted.</param>
        /// <param name="seekmode">(def false) for editor. skip(pass) event , playspeed.</param>
        public void Update(double deltatime, bool updateonce=false, bool seekmode = false)
        {
            // object update and setting
            void inObjectUpdate(double deltatime_)
            {
                for (var i = 0; i < ObjectCount; i++)
                {
                    if (!Data.Objects[i].Enable) continue;
                    Inputs[i].Update(deltatime_);
                }
            }
            void inOneFrameEvent(double deltaframe)
            {
                for (var i = 0; i < TrackCount; i++)
                {
                    var track = Tracks[i];
                    if (track == null) continue;

                    if (track.IsSelfClear)
                    {
                        ClearTrack(i);
                        continue;
                    }

                    if (track.Enable)
                    {
                        if (seekmode)
                        {
                            track.Update(deltaframe);
                        }
                        else
                        {
                            if (!track.Pause)
                                track.Update(deltaframe * track.PlaySpeed);
                        }
                        Update_TrackEvent(track, false, seekmode);
                    }
                }
            }
            // seekmode : pass enable & playspeed
            // updateonce : execute objectupdate() only once. but object animation time is shifted.
            //
            // *caution
            // keep spine-function execute order below
            // 1.Set/AddAnimation
            // 2.control Track.alpha Track.Duration
            // 3.ObjectUpdate(update -> apply -> updateWorldTransfor)

            // object setup
            for (var i = 0; i < ObjectCount; i++)
            {
                var input = Inputs[i];
                if (input.Renderer == null) continue;

                switch (input.ObjectType)
                {
                    case ObjectType.Spine:
                        var spine = Data.Objects[i] as SpineObject;
                        input.Renderer.SetMixDuration(spine.MixDuration);
                        break;
                }
            }
            // first frame (prevkey & frame0) event
            foreach(var track in Tracks)
            {
                if (track == null) continue;
                if (!track.Enable) continue;

                if (track.PlayFrame < 0.0)
                {
                    track.PlayFrame = 0.0;
                    Update_TrackEvent(track, true, seekmode);
                }
            }
            {
                // execute per frame
                double deltaframe = deltatime * Data.Framerate;
                var first = true;
                var loopcount = 0;
                while (deltaframe >= 0.0)
                {
                    // 0.0 once loop break
                    if ((loopcount > 0) && (deltaframe <= Misc.FloatZero)) break;

                    double f;
                    if (deltaframe >= 1.0)
                        f = 1.0;
                    else
                        f = deltaframe;
                    inOneFrameEvent(f);

                    if (!updateonce)
                    {
                        Update_Interpolation(first, seekmode);  
                        Update_Composite(f / Data.Framerate);
                        inObjectUpdate(f / Data.Framerate);
                        first = false;
                    }

                    deltaframe -= 1.0;
                    loopcount++;
                }

                // skip
                if (updateonce)
                {
                    Update_Interpolation(first, seekmode);
                    Update_Composite(deltatime);
                    inObjectUpdate(deltatime);
                }
            }
        }
        public void UpdatePhysics(double deltatime)
        {
            for (var i = 0; i < ObjectCount; i++)
            {
                var enable = Data.Objects[i].Enable;
                if (!enable) continue;

                if (Inputs[i].Renderer != null) Inputs[i].Renderer.UpdatePhysics(deltatime);
            }
        }
        public void AppeasePhysics()
        {
            for (var i = 0; i < ObjectCount; i++)
            {
                if (Inputs[i].Renderer != null) Inputs[i].Renderer.AppeasePhysics();
            }
        }
        public void SetupPhysics()
        {
            for (var i = 0; i < ObjectCount; i++)
            {
                if (Inputs[i].Renderer != null) Inputs[i].Renderer.SetupPhysics(Data.Objects[i].SettingField);
            }
        }




    }
}