unit AnimationrigState;

interface

uses SysUtils,Classes,Contnrs,StrUtils,math,
     AnimationrigMisc,Animationrig;

type
  // Animrig state renderer base object
  TAnimrigStateRenderer = class
    protected
      fObjectType : TAnimrigMisc.ObjectType;
    public
      // return state
      RendererEnable : boolean;
      RendererVisible : boolean;
      RendererInheritedEnable : boolean;
      RendererInheritedVisible : boolean;
      // draw priority info
      // * design priority > 3D z position
      DrawPriorityZ : single;
      DrawPriorityDesign : single;
      // transform
      TransPosition : TAnimrigMisc.Vector3;
      TransMatrix : TAnimrigMisc.Matrix4;
      TransColor : array[0..4] of single;    // [0]..alpha [1]..red [2]..green [3]..blue

      constructor Create;
      property ObjectType : TAnimrigMisc.ObjectType read fObjectType;
      // common
      procedure Update(deltatime:double); virtual; abstract;
      function GetPrioritySortValue : double;
      // spine
      procedure SetAnimation(trackindex,usetrack: integer; const basename: string; loop:boolean; addanim:boolean; trackmix:TAnimrigMisc.MixBlend; duration:single; seekmode:boolean); virtual;
      procedure SetTrackBlendAlpha(trackindex,usetrack:integer; alpha1, alpha2, alpha3, alpha4: single; exists:boolean); virtual;
      procedure SetTrackSeekTime(trackindex,usetrack:integer; time1, time2, time3, time4: single; exists:boolean); virtual;
      procedure SetMixDuration(duration:single); virtual;
  end;
  // Animrig state(instance)
  TAnimrigStateTrackLayer = class
    protected
      fLayerType : TAnimrigMisc.LayerType;
    public
      const ValueCount = 16;   // state keep value count (*not input values)
      const SaveVersion = 1;
    var
      // pass? (empty,disable)
      IsPass : boolean;
      // now state
      Animationname : string;
      EventID : string;
      PrevTrackAlpha,CurrentTrackAlpha : single;
      Values : array[0..ValueCount-1] of single;
      RotateOrder : TAnimrigMisc.RotateOrder;
      // previous track state
      pPrevTrackAlpha,pCurrentTrackAlpha : single;
      pValues : array[0..ValueCount-1] of single;
      pRotateOrder : TAnimrigMisc.RotateOrder;

      class procedure ClearValues(var v:array of single; layertype_:TAnimrigMisc.LayerType);
      constructor Create(layertype_:TAnimrigMisc.LayerType);
      procedure Clear(clearprevious:boolean);

      procedure SaveToBinary(ms:TmemoryStream);
      function LoadFromBinary(ms:TMemoryStream):boolean;

      property LayerType : TAnimrigMisc.LayerType read fLayerType;
    end;
  TAnimrigStateTrackObject = class
    private
      function GetLayerCount: integer; inline;
      function GetLayers(n: integer): TAnimrigStateTrackLayer; inline;
    protected
      fLayerList : TObjectList;
      fObjectType : TAnimrigMisc.ObjectType;
    public
      constructor Create(data:TAnimrigObject);
      destructor Destroy; override;
      procedure Clear(clearprevious:boolean);

      procedure SaveToBinary(ms:TmemoryStream);
      function LoadFromBinary(ms:TMemoryStream):boolean;

      property LayerCount : integer read GetLayerCount;
      property Layers[n:integer] : TAnimrigStateTrackLayer read GetLayers;
      property ObjectType : TAnimrigMisc.ObjectType read fObjectType;
  end;
  TAnimrigStateTrack = class
    private
      function GetObjectCount: integer; inline;
      function GetObjects(n: integer): TAnimrigStateTrackObject; inline;
      function GetAnimationsetData: TAnimrigAnimation; inline;
      function GetSelfClear: boolean;
    protected
      fData:TAnimrig;
      fObjectList : TObjectList;
    public
      // track state
      AnimationsetIndex : integer;
      PlayFrame : double;
      PrevPlayFrame : integer;
      SelfClearLimit : double;
      // control
      PlaySpeed : double;
      Pause : boolean;
      Enable : boolean;
      DisableLoop : boolean;
      TrackAlphaEase : TAnimrigMisc.EasingData;   // track alpha. fade in out.
      MixAlphaEase : TAnimrigMisc.EasingData;     // previous animation value mix. fade mix.

      const SaveVersion = 2;

      constructor Create(data:TAnimrig; animindex:integer);
      destructor Destroy; override;
      procedure Clear;
      procedure Reset(clearprevious:boolean=false);

      procedure SaveToBinary(ms:TmemoryStream);
      function LoadFromBinary(ms:TmemoryStream; ver:integer):boolean;

      procedure Rebuild;
      procedure Update(deltaframe:double);

      procedure Fademix(duration:single; mode:TAnimrigMisc.Easing=aneLinear; beta:single=1.0);
      procedure Fadein(duration:single; trackalpha:single=1.0; mode:TAnimrigMisc.Easing=aneLinear; beta:single=1.0);
      procedure Fadeout(duration:single; mode:TAnimrigMisc.Easing=aneLinear; beta:single=1.0);

      property ObjectCount : integer read GetObjectCount;
      property Objects[n:integer] : TAnimrigStateTrackObject read GetObjects;
      property Animation : TAnimrigAnimation read GetAnimationsetData;
      property IsSelfClear : boolean read GetSelfClear;
  end;
  TAnimrigStateInput = class
    private
      fRenderer: TAnimrigStateRenderer;
      procedure SetRenderer(const Value: TAnimrigStateRenderer);
      procedure SetValueCount(count:integer);
    protected
      fObjectType : TAnimrigMisc.ObjectType;
      fValueCount : integer;
    public
      // user object
      UserObject : pointer;

      Values : array of TAnimrigMisc.EasingData;

      DefaultValues : array of single;
      DefaultScales : array of single;
      ValueNames : array of string;

      constructor Create(t:TAnimrigMisc.ObjectType; valuelength:integer=TAnimrigMisc.InputCount);
      procedure Clear;
      procedure SetupDefault(reserveobj:TAnimrigObject);
      procedure Update(deltatime:double);

      procedure SetValue(index:integer; v:single; duration:single=0.0; mode:TAnimrigMisc.Easing=aneLinear; beta:single=1.0);
      function  GetValue(index:integer):single;
      procedure SetDefaults(duration:single; mode:TAnimrigMisc.Easing=aneLinear; beta:single=1.0);

      function ConvertSpecialFloat(v:single):single; inline;

      property ObjectType : TAnimrigMisc.ObjectType read fObjectType;
      property ValueCount : integer read fValueCount write SetValueCount;
      property Renderer : TAnimrigStateRenderer read fRenderer write SetRenderer;
  end;
  // Event layer callback
  TAnimrigState = class;
  TAnimrigStateEventData = record
    // allow always
    Ignition : boolean;
    // state
    Input : TAnimrigStateInput;
    Track : TAnimrigStateTrack;
    Key : TAnimrigEventKey;
    // data
    Id : string;
    Strings : array[0..1] of string;
    Floats : array[0..1] of single;
  end;
  TAnimrigStateTempObjectState = record
    Matrix : TAnimrigMisc.Matrix4;
    InheritedEnbale : boolean;
    InheritedVisible : boolean;
    DrawPriority : single;
    TransColor : array [0..3] of single;
  end;
  TAnimrigStateEventCallback = procedure(state:TAnimrigState; const data:TAnimrigStateEventData) of object;
  // state
  TAnimrigState = class
    private
      function GetTracks(n: integer): TAnimrigStateTrack; inline;
      function GetTrackCount: integer; inline;
      function GetObjectCount: integer; inline;
      function GetInputObject(n: integer): TAnimrigStateInput; inline;
      function GetInputCount:integer; inline;
    protected
      fData : TAnimrig;
      fFramePool : double;
      fTrackList : TObjectList;
      fInputList : TObjectList;
      fObjectUpdateOrder : array of integer;
      fEventCallback : TAnimrigStateEventCallback;
      fTempObjectMatrix : array of TAnimrigStateTempObjectState;

      procedure CompactionTracks;
      procedure ChangeTracks(count:integer);

      function GetSpineUseTrack(t : TAnimrigMisc.LayerType):integer;
      procedure Update_Event(track : TAnimrigStateTrack; prevkeyevent:boolean; seekmode:boolean);
      procedure Update_Interpolation(first:boolean; seekmode:boolean);
      procedure Update_Composite;
    public
      const InputValueSaveVersion = 1;
      const SaveVersion =
        TAnimrigStateTrack.SaveVersion +
        TAnimrigStateTrackLayer.SaveVersion +
        InputValueSaveVersion +
        1;

      var RootMatrix : TAnimrigMisc.Matrix4;

      constructor Create(data:TAnimrig);
      destructor Destroy; override;

      procedure SaveToBinary(ms:TMemoryStream);
      function LoadFromBinary(ms:TMemorySTream):boolean;

      procedure ClearObjects;
      function Seek(targettrackindex:integer; time : double; framegrid:boolean):boolean;
      procedure Update(deltatime : double; updateonce:boolean=false; seekmode:boolean=false);

      procedure Rebuild;

      function EntryTrack(trackindex:integer; animindex:integer; reset:boolean=false) : TAnimrigStateTrack; overload;
      function EntryTrack(trackindex:integer; const animname:string; reset:boolean=false) : TAnimrigStateTrack; overload;
      procedure ClearTrack(trackindex:integer);
      procedure ClearTracks;

      function EntryTrackFade(trackindex:integer; const animname:string; fadeduration:single; mode:TAnimrigMisc.Easing=aneInOutQuad; beta:single=1.0) : TAnimrigStateTrack;
      function ClearTrackFade(trackindex:integer; fadeduration:single; mode:TAnimrigMisc.Easing=aneInOutQuad; beta:single=1.0) : TAnimrigStateTrack;

      // tracks : Allow out of range access
      property Tracks[n:integer] : TAnimrigStateTrack read GetTracks;
      property TrackCount : integer read GetTrackCount;
      function UsageTrackCount : integer;
      // Inputs : Allow out of range access
      property ObjectCount : integer read GetObjectCount;
      property InputCount : integer read GetInputCount;
      property Inputs[n:integer]:TAnimrigStateInput read GetInputObject;
      // event
      property OnEvent : TAnimrigStateEventCallback read fEventCallback write fEventCallback;
  end;

implementation

{ TAnimrigStateRenderer }

constructor TAnimrigStateRenderer.Create;
begin
  fObjectType := anoNone;

  RendererEnable := true;
  RendererVisible := true;
  RendererInheritedEnable := true;
  RendererInheritedVisible := true;
  DrawPriorityZ := 0.0;
  DrawPriorityDesign := 0.0;

  TAnimrigMisc.m4SetIdentity(TransMatrix);
  TransColor[0] := 1.0;
  TransColor[1] := 1.0;
  TransColor[2] := 1.0;
  TransColor[3] := 1.0;
end;

function TAnimrigStateRenderer.GetPrioritySortValue: double;
begin
  result := (-DrawPriorityZ) +
            (+DrawPriorityDesign*100)*$10000;
end;

procedure TAnimrigStateRenderer.SetAnimation(trackindex, usetrack: integer; const basename: string; loop, addanim: boolean; trackmix: TAnimrigMisc.MixBlend; duration:single; seekmode:boolean);
begin
  //
end;

procedure TAnimrigStateRenderer.SetTrackBlendAlpha(trackindex,usetrack: integer; alpha1, alpha2, alpha3, alpha4: single; exists: boolean);
begin
  //
end;

procedure TAnimrigStateRenderer.SetTrackSeekTime(trackindex, usetrack: integer; time1, time2, time3, time4: single; exists: boolean);
begin

end;

procedure TAnimrigStateRenderer.SetMixDuration(duration: single);
begin
  //
end;

{ TAnimrigStateTrackLayer }

procedure TAnimrigStateTrackLayer.Clear(clearprevious: boolean);
var
  i : integer;
begin
  if clearprevious then
  begin
    ClearValues(pValues,LayerType);
    Animationname := '';
    EventID := '';
    pPrevTrackAlpha := 1.0;
    pCurrentTrackAlpha := 1.0;
    pRotateOrder := anrPass;
  end
  else
  begin
    // copy previous
    for i:=0 to ValueCount-1 do
      pValues[i] := Values[i];
    // Animationname := '';       keep animation name
    // EventName := '';
    pPrevTrackAlpha := PrevTrackAlpha;
    pCurrentTrackAlpha := CurrentTrackAlpha;
    pRotateOrder := RotateOrder;
  end;

  ClearValues(Values,LayerType);
  PrevTrackAlpha := 1.0;
  CurrentTrackAlpha := 1.0;
  RotateOrder := anrPass;
end;

class procedure TAnimrigStateTrackLayer.ClearValues(var v: array of single; layertype_: TAnimrigMisc.LayerType);
var
  i : integer;
begin
  for i:=0 to ValueCount-1 do
    v[i] := 0.0;

  case layertype_ of
    anlAnimBlend4,anlAnimBlend2,anlAnimBlend1 :
      begin
        // track blend alpha
        // [0..1]
      end;
    anlSeekBlend4,anlSeekBlend2,anlSeekBlend1 :
      begin
        // seek time
        // [0..1]
      end;
    anlPriority :
      begin
        // draw priority
        // [0]
      end;
    anlTransform :
      begin
        // tansform
        // [0..2]
        // scale
        v[3] := 1.0;
        v[4] := 1.0;
        v[5] := 1.0;
        // rotate
        // [6..8]
        // alpha
        v[9] := 1.0;
        // color
        v[10] := 1.0;
        v[11] := 1.0;
        v[12] := 1.0;
      end;
  end;
end;

constructor TAnimrigStateTrackLayer.Create(layertype_: TAnimrigMisc.LayerType);
begin
  fLayerType := layertype_;
end;

function TAnimrigStateTrackLayer.LoadFromBinary(ms: TMemoryStream): boolean;
var
  i,n : integer;
  ver,t : integer;
  f : single;
begin
  result := false;
  Clear(false);

  try
    // version
    ver := TAnimrigMisc.ReadInteger(ms , 0,SaveVersion);

    // type
    t := TAnimrigMisc.ReadInteger(ms);

    // pass? (empty,disable)
    IsPass := TAnimrigMisc.ReadBool(ms);
    // now state
    Animationname := TAnimrigMisc.ReadString(ms);
    EventID := TAnimrigMisc.ReadString(ms);
    PrevTrackAlpha := TAnimrigMisc.ReadFloat(ms);
    CurrentTrackAlpha := TAnimrigMisc.ReadFloat(ms);
    n := TAnimrigMisc.ReadInteger(ms , 0,$ffff);  // value count
    for i:=0 to n-1 do
    begin
      f := TAnimrigMisc.ReadFloat(ms , -$ffffff,+$ffffff);
      if i < ValueCount then Values[i] := f;
    end;
    RotateOrder := TAnimrigMisc.ReadRotateOrder(ms);
    // prev state
    pPrevTrackAlpha := TAnimrigMisc.ReadFloat(ms);
    pCurrentTrackAlpha := TAnimrigMisc.ReadFloat(ms);
    n := TAnimrigMisc.ReadInteger(ms , 0,$ffff);  // value count
    for i:=0 to n-1 do
    begin
      f := TAnimrigMisc.ReadFloat(ms , -$ffffff,+$ffffff);
      if i < ValueCount then pValues[i] := f;
    end;
    pRotateOrder := TAnimrigMisc.ReadRotateOrder(ms);
  except
    exit;
  end;

  // type check
  if t <> integer(LayerType) then Clear(false);

  result := true;
end;

procedure TAnimrigStateTrackLayer.SaveToBinary(ms: TmemoryStream);
var
  i : integer;
begin
  // version
  TAnimrigMisc.WriteInteger(ms , SaveVersion);

  // type
  TAnimrigMisc.WriteInteger(ms , integer(fLayerType));

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

  // now state
  TAnimrigMisc.WriteString(ms , Animationname);
  TAnimrigMisc.WriteString(ms , EventID);
  TAnimrigMisc.WriteFloat(ms , PrevTrackAlpha);
  TAnimrigMisc.WriteFloat(ms , CurrentTrackAlpha);
  TAnimrigMisc.WriteInteger(ms , ValueCount);  // value count
  for i:=0 to ValueCount-1 do
    TAnimrigMisc.WriteFloat(ms , Values[i]);
  TAnimrigMisc.WriteInteger(ms , integer(RotateOrder));

  // prev state
  TAnimrigMisc.WriteFloat(ms , pPrevTrackAlpha);
  TAnimrigMisc.WriteFloat(ms , pCurrentTrackAlpha);
  TAnimrigMisc.WriteInteger(ms , ValueCount);  // value count
  for i:=0 to ValueCount-1 do
    TAnimrigMisc.WriteFloat(ms , pValues[i]);
  TAnimrigMisc.WriteInteger(ms , integer(pRotateOrder));
end;

{ TAnimrigStateTrackObject }

procedure TAnimrigStateTrackObject.Clear(clearprevious: boolean);
var
  i : integer;
begin
  for i:=0 to LayerCount-1 do
    Layers[i].Clear(clearprevious);
end;

constructor TAnimrigStateTrackObject.Create(data: TAnimrigObject);
var
  i,n : integer;
begin
  fLayerList := TObjectList.Create;
  fObjectType := data.ObjectType;

  // create layers
  n := data.LayerCount;
  for i:=0 to n-1 do
  begin
    fLayerList.Add( TAnimrigStateTrackLayer.Create(data.Layers[i].LayerType) );
  end;

  Clear(true);
end;

destructor TAnimrigStateTrackObject.Destroy;
begin
  fLayerList.Free;
  inherited;
end;

function TAnimrigStateTrackObject.GetLayerCount: integer;
begin
  result := fLayerList.Count;
end;

function TAnimrigStateTrackObject.GetLayers(n: integer): TAnimrigStateTrackLayer;
begin
  result := TAnimrigStateTrackLayer(fLayerList.Items[n]);
end;

function TAnimrigStateTrackObject.LoadFromBinary(ms: TMemoryStream): boolean;
var
  i,n : integer;
  endpoint : int64;
begin
  result := false;
  try
    // layers
    TAnimrigMisc.ReadHead(ms,'tracklayers');
    n := TAnimrigMisc.ReadInteger(ms , 0,$ffff);
    for i:=0 to n-1 do
    begin
      // header
      TAnimrigMisc.ReadHead(ms,'tracklayer');
      // data length
      endpoint := TAnimrigMisc.ReadSkipBegin(ms);
      // data
      if i < LayerCount then
        if not Layers[i].LoadFromBinary(ms) then exit;
      // jump data endpoint
      TAnimrigMisc.JumpSkipEnd(ms,endpoint);
    end;
  except
    exit;
  end;
  result := true;
end;

procedure TAnimrigStateTrackObject.SaveToBinary(ms: TmemoryStream);
var
  i,n : integer;
  endpoint : int64;
begin
  // layers
  TAnimrigMisc.WriteHead(ms,'tracklayers');
  n := LayerCount;
  TAnimrigMisc.WriteInteger(ms,n);
  for i:=0 to n-1 do
  begin
    // header
    TAnimrigMisc.WriteHead(ms,'tracklayer');
    // data length
    endpoint := TAnimrigMisc.WriteSkipBegin(ms);
    // layer data
    Layers[i].SaveToBinary(ms);
    // write data length
    TAnimrigMisc.WriteSkipEnd(ms,endpoint);
  end;
end;

{ TAnimrigStateTrack }

procedure TAnimrigStateTrack.Clear;
begin
  // play control
  Enable := true;
  Pause := false;
  PlaySpeed := 1.0;
  DisableLoop := false;

  TAnimrigMisc.EasingValueEase(TrackAlphaEase,1.0);
  TAnimrigMisc.EasingValueEase(MixAlphaEase,1.0);
  SelfClearLimit := $100000;

  Reset(true);
end;

constructor TAnimrigStateTrack.Create(data: TAnimrig; animindex: integer);
begin
  fData := data;
  AnimationsetIndex := animindex;
  TAnimrigMisc.EasingValueClear(MixAlphaEase);
  TAnimrigMisc.EasingValueClear(TrackAlphaEase);

  fObjectList := TObjectList.Create;
  Rebuild;

  Clear;
end;

destructor TAnimrigStateTrack.Destroy;
begin
  fObjectList.Free;
  inherited;
end;

procedure TAnimrigStateTrack.Fadein(duration,trackalpha : single; mode:TAnimrigMisc.Easing; beta:single);
begin
  TAnimrigMisc.EasingValueEase(TrackAlphaEase , 0.0);
  TAnimrigMisc.EasingValueEase(TrackAlphaEase , trackalpha,duration , mode,beta);
  SelfClearLimit := $100000;
end;

procedure TAnimrigStateTrack.Fademix(duration: single; mode: TAnimrigMisc.Easing; beta: single);
begin
  TAnimrigMisc.EasingValueEase(MixAlphaEase , 0.0);
  TAnimrigMisc.EasingValueEase(MixAlphaEase , 1.0,duration , mode,beta);
end;

procedure TAnimrigStateTrack.Fadeout(duration : single; mode:TAnimrigMisc.Easing; beta: single);
begin
  TAnimrigMisc.EasingValueEase(TrackAlphaEase , 0.0,duration , mode,beta);
  SelfClearLimit := duration;
end;

function TAnimrigStateTrack.GetAnimationsetData: TAnimrigAnimation;
begin
  result := nil;
  if AnimationsetIndex < 0 then exit;
  if AnimationsetIndex > fData.AnimationCount-1 then exit;

  result := fData.Animations[AnimationsetIndex];
end;

function TAnimrigStateTrack.GetObjectCount: integer;
begin
  result := fObjectList.Count;
end;

function TAnimrigStateTrack.GetObjects(n: integer): TAnimrigStateTrackObject;
begin
  result := TAnimrigStateTrackObject(fObjectList.Items[n]);
end;

function TAnimrigStateTrack.GetSelfClear: boolean;
begin
  result := SelfClearLimit < TAnimrigMisc.FloatZero;
end;

function TAnimrigStateTrack.LoadFromBinary(ms: TmemoryStream; ver:integer): boolean;
var
  i,n : integer;
  endpoint : int64;
begin
  result := false;
  try
    // animation name
    // move upper class
    //s := TAnimrigMisc.ReadString(ms);
    //track := EntryTrack(i, s,true);

    // play frame
    PlayFrame := TAnimrigMisc.ReadFloat(ms , -$ffffff,+$ffffff , true);
    PrevPlayFrame := TAnimrigMisc.ReadInteger(ms , -$ffffff,$ffffff , true);
    // control state
    PlaySpeed := TAnimrigMisc.ReadFloat(ms , -$ffff,+$ffff , true);
    Pause := TAnimrigMisc.ReadBool(ms);
    Enable := TAnimrigMisc.ReadBool(ms);
    DisableLoop := TAnimrigMisc.ReadBool(ms);
    if ver >= 2 then SelfClearLimit := TAnimrigMisc.ReadFloat(ms);
    // track alpha easing
    if ver >= 2 then TAnimrigMisc.EasingValueLoadFromBinary(TrackAlphaEase,ms);
    if ver >= 2 then TAnimrigMisc.EasingValueLoadFromBinary(MixAlphaEase,ms);

    // object
    // header
    TAnimrigMisc.ReadHead(ms , 'trackobjects');
    n := TAnimrigMisc.ReadInteger(ms , 0,$ffff);
    for i:=0 to n-1 do
    begin
      // header
      TAnimrigMisc.ReadHead(ms , 'trackobject');
      // data length
      endpoint := TAnimrigMisc.ReadSkipBegin(ms);
      // data
      if i < ObjectCount then
        if not Objects[i].LoadFromBinary(ms) then exit;
      // jump data endpoint
      TAnimrigMisc.JumpSkipEnd(ms,endpoint);
    end;
  except
    exit;
  end;
  result := true;
end;

procedure TAnimrigStateTrack.Rebuild;
var
  i : integer;
begin
  fObjectList.Clear;
  for i:=0 to fData.ObjectCount-1 do
    fObjectList.Add(TAnimrigStateTrackObject.Create( fData.Objects[i] ));

  // update index
  AnimationsetIndex := min( max(AnimationsetIndex,0),fData.AnimationCount-1 );
end;

procedure TAnimrigStateTrack.Reset(clearprevious: boolean);
var
  i : integer;
begin
  // clear & backup
  for i :=0 to ObjectCount-1 do
    Objects[i].Clear(clearprevious);

  // state reset
  PlayFrame := -1;
  PrevPlayFrame := -1;
  // reset fade
  SelfClearLimit := $100000;
  TAnimrigMisc.EasingValueEase(TrackAlphaEase,1.0);
  TAnimrigMisc.EasingValueEase(MixAlphaEase,1.0);
end;

procedure TAnimrigStateTrack.SaveToBinary(ms: TmemoryStream);
var
  i,n : integer;
  endpoint : int64;
begin
  // animation name
  // move upper class
  //s := '';
  //if track.Animation <> nil then s := track.Animation.Name;
  //TAnimrigMisc.WriteString(ms , s);

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

  // object
  // header
  TAnimrigMisc.WriteHead(ms , 'trackobjects');
  n := ObjectCount;
  TAnimrigMisc.WriteInteger(ms,n);
  for i:=0 to n-1 do
  begin
    // head
    TAnimrigMisc.WriteHead(ms , 'trackobject');
    // data length
    endpoint := TAnimrigMisc.WriteSkipBegin(ms);
    // object data
    Objects[i].SaveToBinary(ms);
    // write data length
    TAnimrigMisc.WriteSkipEnd(ms,endpoint);
  end;
end;

procedure TAnimrigStateTrack.Update(deltaframe: double);
begin
  // easing
  if not TrackAlphaEase.Done then
    TAnimrigMisc.EasingValueUpdate(TrackAlphaEase,deltaframe / fData.Framerate);
  if not MixAlphaEase.Done then
    TAnimrigMisc.EasingValueUpdate(MixAlphaEase,deltaframe / fData.Framerate);
  // self clear
  if SelfClearLimit < $ffff then
    if SelfClearLimit > 0.0 then
      SelfClearLimit := SelfClearLimit - (deltaframe / fData.Framerate);

  PlayFrame := PlayFrame + deltaframe;
end;

{ TAnimrigStateInput }

procedure TAnimrigStateInput.Clear;
var
  i : integer;
begin
  Renderer := nil;

  for i:=0 to fValueCount-1 do
  begin
    TAnimrigMisc.EasingValueClear(Values[i]);
  end;
end;

function TAnimrigStateInput.ConvertSpecialFloat(v: single): single;
var
  index : integer;
begin
  if v < TAnimrigMisc.InputFloatIndex then
  begin
    result := v;
    exit;
  end;

  index := floor(v - TAnimrigMisc.InputFloatIndex + 0.5);
  if index > fValueCount-1 then
    result := 0.0
  else
    result := Values[index].Value;
end;

constructor TAnimrigStateInput.Create(t: TAnimrigMisc.ObjectType; valuelength: integer);
begin
  fObjectType := t;
  UserObject := nil;
  SetValueCount(valuelength);

  Clear;
end;

function TAnimrigStateInput.GetValue(index: integer): single;
begin
  result := 0.0;
  if index < 0 then exit;
  if index >= fValueCount then exit;

  result := Values[index].Value;
end;

procedure TAnimrigStateInput.SetDefaults(duration:single; mode:TAnimrigMisc.Easing; beta:single);
var
  i : integer;
begin
  for i:=0 to ValueCount-1 do
    SetValue(i, DefaultValues[i] ,duration,mode,beta);
end;

procedure TAnimrigStateInput.SetRenderer(const Value: TAnimrigStateRenderer);
begin
  if Value <> nil then
    if Value.ObjectType <> ObjectType then
    begin
      raise Exception.Create('AnimrigStateInput.SetRenderer : miss match objecttype class.');
    end;

  fRenderer := Value;
end;

procedure TAnimrigStateInput.SetupDefault(reserveobj: TAnimrigObject);
var
  sl : TStringList;
  p : integer;
  s1,s2 : string;
  v1,v2 : string;
  index : integer;
  alphax,alphay,scale : single;
  name : string;
begin
  for s1 in reserveobj.InputPanelField do
  begin
    sl := TAnimrigMisc.DivideString(',',s1);
    index := -1;
    alphax := 0.0;
    alphay := 0.0;
    scale := 1.0;
    name := '';
    for s2 in sl do
    begin
      p := pos(':',s2);
      if p = 0 then continue;
      v1 := copy(s2,1,p-1);
      v2 := copy(s2,p+1,4096);
      if (v1 = 'no')or(v1 = 'index')then index := StrToIntDef(v2,-1)
      else if (v1 = 'name') then name := v2
      else if (v1 = 'defx')then alphax := StrToFloatDef(v2,0.0)
      else if (v1 = 'defy')then alphay := StrToFloatDef(v2,0.0)
      else if (v1 = 'scale')or(v1 = 'range') then scale := StrToFloatDef(v2,1.0)
    end;

    if (index >= 0)and(index*2+1 < ValueCount) then
    begin
      DefaultValues[index*2+0] := alphax;
      DefaultValues[index*2+1] := alphay;
      DefaultScales[index*2+0] := scale;
      DefaultScales[index*2+1] := scale;
      ValueNames[index*2+0] := name;
      ValueNames[index*2+1] := name;
    end;

    sl.Free;
  end;
end;

procedure TAnimrigStateInput.SetValue(index: integer; v, duration : single; mode: TAnimrigMisc.Easing; beta: single);
begin
  if index < 0 then exit;
  if index >= fValueCount then exit;

  TAnimrigMisc.EasingValueEase(Values[index] , v,duration , mode,beta);
end;

procedure TAnimrigStateInput.SetValueCount(count: integer);
var
  i : integer;
begin
  if ValueCount = count then exit;

  Setlength(Values,count);
  SetLength(DefaultValues,count);
  SetLength(DefaultScales,count);
  SetLength(ValueNames,count);
  for i:=0 to count-1 do
  begin
    TAnimrigMisc.EasingValueClear(Values[i]);
    DefaultValues[i] := 0.0;
    DefaultScales[i] := 1.0;
    ValueNames[i] := '';
  end;

  fValueCount := count;
end;

procedure TAnimrigStateInput.Update(deltatime: double);
var
  i : integer;
begin
  for i:=0 to fValueCount-1 do
    TAnimrigMisc.EasingValueUpdate(Values[i] , deltatime);

  if fRenderer <> nil then fRenderer.Update(deltatime);
end;

{ TAnimrigState }

procedure TAnimrigState.ChangeTracks(count: integer);
begin
  if count <= fTrackList.Count then exit;

  count := (count div 4)*4 + 4;
  fTrackList.Count := count;
end;

procedure TAnimrigState.ClearObjects;
var
  i : integer;
begin
  for i:=0 to fInputList.count-1 do
    Inputs[i].Clear;
end;

procedure TAnimrigState.ClearTrack(trackindex: integer);
begin
  if trackindex < 0 then exit;
  if trackindex > fTrackList.Count-1 then exit;

  // dispose & null
  fTrackList.Items[trackindex] := nil;

  CompactionTracks;
end;

function TAnimrigState.ClearTrackFade(trackindex: integer; fadeduration: single; mode:TAnimrigMisc.Easing; beta:single): TAnimrigStateTrack;
var
  track : TAnimrigStateTrack;
begin
  if fadeduration < 0.001 then
  begin
    ClearTrack(trackindex);
    result := nil;
    exit;
  end;

  track := Tracks[trackindex];
  if track <> nil then track.Fadeout(fadeduration,mode,beta);
  result := track;
end;

procedure TAnimrigState.ClearTracks;
var
  i : integer;
begin
  for i:=0 to fTrackList.Count-1 do
    fTrackList.Items[i] := nil;
  CompactionTracks;
end;

procedure TAnimrigState.CompactionTracks;
begin
  ChangeTracks(UsageTrackCount);
end;

constructor TAnimrigState.Create(data: TAnimrig);
begin
  fData := data;
  fTrackList := TObjectList.Create;
  fInputList := TObjectList.Create;
  TAnimrigMisc.m4SetIdentity(RootMatrix);

  Rebuild;

  if fData = nil then
    raise Exception.Create('TAnimrigState : can not set null class. need TAnimrig class.');
end;

destructor TAnimrigState.Destroy;
begin
  fTrackList.Free;
  fInputList.Free;

  inherited;
end;

function TAnimrigState.EntryTrack(trackindex, animindex: integer; reset: boolean): TAnimrigStateTrack;
begin
  result := nil;
  if fData = nil then exit;

  if animindex < -1 then exit;
  if animindex > fData.AnimationCount-1 then exit;

  // change track length
  if trackindex < 0 then exit;
  ChangeTracks(trackindex+1);

  // exists?
  if fTrackList.Items[trackindex] = nil then
  begin
    fTrackList.Items[trackindex] := TAnimrigStateTrack.Create(fData , animindex);
    result := Tracks[trackindex];
  end
  else
  begin
    Tracks[trackindex].Reset(reset);
    Tracks[trackindex].AnimationsetIndex := animindex;
    result := Tracks[trackindex];
  end;
end;

function TAnimrigState.EntryTrack(trackindex: integer; const animname: string; reset: boolean): TAnimrigStateTrack;
var
  index : integer;
begin
  result := nil;
  if fData = nil then exit;

  index := fData.Search(animname);
  if index = -1 then exit;

  result := EntryTrack(trackindex , index,reset);
end;

function TAnimrigState.EntryTrackFade(trackindex: integer; const animname: string; fadeduration: single; mode:TAnimrigMisc.Easing; beta:single): TAnimrigStateTrack;
var
  track : TAnimrigStateTrack;
begin
  if fadeduration < 0.001 then
  begin
    result := EntryTrack(trackindex,animname);
    exit;
  end;

  track := Tracks[trackindex];
  if track = nil then
  begin
    // empty previous animation
    result := EntryTrack(trackindex,animname,false);
    if result <> nil then result.Fadein(fadeduration,1.0,mode,beta);
  end
  else
  begin
    // exists previous animation
    result := EntryTrack(trackindex,animname,false);
    if result <> nil then result.Fademix(fadeduration,mode,beta);
  end;
end;

function TAnimrigState.GetInputCount: integer;
begin
  if (fInputList.count <> ObjectCOunt)then Rebuild;
  result := fInputList.Count;
end;

function TAnimrigState.GetInputObject(n: integer): TAnimrigStateInput;
begin
  result := nil;
  if n < 0 then exit;
  if n >= fInputList.Count then exit;

  result := TAnimrigStateInput(fInputList.Items[n]);
end;

function TAnimrigState.GetObjectCount: integer;
begin
  if fData = nil then
    result := 0
  else
    result := fData.ObjectCount;
end;

function TAnimrigState.GetTrackCount: integer;
begin
  result := fTrackList.Count;
end;

function TAnimrigState.GetTracks(n: integer): TAnimrigStateTrack;
begin
  result := nil;
  if n < 0 then exit;
  if n > fTrackList.Count-1 then exit;

  result := TAnimrigStateTrack(fTrackList.Items[n]);
end;

function TAnimrigState.LoadFromBinary(ms: TMemorySTream): boolean;
  function inInputValue(ms:TMemoryStream):boolean;
  var
    i,j,n1,n2 : integer;
    ver : integer;
    type_ : integer;
    endpoint : int64;
  begin
    result := false;

    // clear
    for i:=0 to fInputList.count-1 do
      Inputs[i].Clear;

    try
      // header
      TAnimrigMisc.ReadHead(ms,'inputobjects');
      n1 := TAnimrigMisc.ReadInteger(ms , 0,$ffff,false,false);
      for i:=0 to n1-1 do
      begin
        // header
        TAnimrigMisc.ReadHead(ms,'inputobject');
        // data length
        endpoint := TAnimrigMisc.ReadSkipBegin(ms);

        if i < ObjectCount then
        begin
          // version
          ver := TAnimrigMisc.ReadInteger(ms , 0,InputValueSaveVersion);
          // type
          type_ := TAnimrigMisc.ReadInteger(ms);
          // head
          TAnimrigMisc.ReadHead(ms,'values');
          // value count
          n2 := TAnimrigMisc.ReadInteger(ms , 0,$ff);
          // set length
          if n2 > Inputs[i].ValueCount then Inputs[i].ValueCount := n2;
          for j:=0 to n2-1 do
          begin
            if not TAnimrigMisc.EasingValueLoadFromBinary(Inputs[i].Values[j],ms) then exit;
          end;

          // type check
          if integer(Inputs[i].ObjectType) <> type_ then Inputs[i].Clear;
        end;
        // jump end point
        TAnimrigMisc.JumpSkipEnd(ms,endpoint);
      end;
    except
      exit;
    end;
    result := true;
  end;
var
  i,n : integer;
  ver : integer;
  b : boolean;
  s : string;
  track : TAnimrigStateTrack;
  endpoint : int64;
begin
  result := false;
  try
    // header
    TAnimrigMisc.ReadHead(ms , 'animrigstate');
    // version
    ver := TAnimrigMisc.ReadInteger(ms);

    // tracks
    TAnimrigMisc.ReadHead(ms , 'tracks');
    n := TAnimrigMisc.ReadInteger(ms , 0,$ffff,false,false);

    ClearTracks;

    for i:=0 to n-1 do
    begin
      // header
      TAnimrigMisc.ReadHead(ms,'track');
      // data length
      endpoint := TAnimrigMisc.ReadSkipBegin(ms);
      // version
      ver := TAnimrigMisc.ReadInteger(ms , 0,TAnimrigStateTrack.SaveVersion);
      // exists = true
      b := TAnimrigMisc.ReadBool(ms);
      // track data
      if b then
      begin
        // animation name
        s := TAnimrigMisc.ReadString(ms);
        track := EntryTrack(i, s,true);   // try set animation name. (*can not move Track.LoadFromBinary() because of need EntryTrack())
        if track <> nil then
          if not track.LoadFromBinary(ms,ver) then exit;
      end;
      // jump data endpoint
      TAnimrigMisc.JumpSkipEnd(ms,endpoint);
    end;

    // input values
    if not inInputValue(ms) then exit;
  except
    exit;
  end;
  result := true;
end;

procedure TAnimrigState.Rebuild;
  procedure inBuildUpdateOrder;
  var
    i,level,idx,count : integer;
    pri : array of double;
  begin
    count := ObjectCount;

    SetLength(pri,count);
    for i:=0 to count-1 do
      pri[i] := 0;

    for i:=0 to count-1 do
    begin
      idx := fData.Objects[i].ParentObjectIndex;
      level := 0;
      while true do
      begin
        if idx < 0 then break;   // none
        if idx = i then break;   // self index
        if idx >= count then break;   // limit over
        inc(level);
        idx := fData.Objects[idx].ParentObjectIndex;;
      end;
      pri[i] := level;
    end;

    TAnimrigMisc.GetSortedIndexes(fObjectUpdateOrder , pri , count , true);    // get sorted indexes array. ascending order.
  end;
var
  i : integer;
  p : pointer;
  o : TAnimrigObject;
  inp : TAnimrigStateInput;
begin
  // object update priority
  SetLength(fObjectUpdateOrder,ObjectCount);
  inBuildUpdateOrder;

  // tempolary matrix
  SetLength(fTempObjectMatrix,ObjectCount);

  // input objects
  fInputList.Clear;
  for p in fData.ObjectList do
  begin
    o := TAnimrigObject(p);
    inp := TAnimrigStateInput.Create(o.ObjectType);
    inp.SetupDefault(o);
    fInputList.Add(inp);
  end;

  // tracks
  for i:=0 to TrackCount-1 do
    if Tracks[i] <> nil then Tracks[i].Rebuild;
end;

procedure TAnimrigState.SaveToBinary(ms: TMemoryStream);
  procedure inInputValue(ms : TMemoryStream);
  var
    i,j,n1,n2 : integer;
    endpoint : int64;
  begin
    // header
    TAnimrigMisc.WriteHead(ms,'inputobjects');
    n1 := ObjectCount;
    TAnimrigMisc.WriteInteger(ms,n1);
    for i:=0 to n1-1 do
    begin
      // header
      TAnimrigMisc.WriteHead(ms,'inputobject');
      // data length
      endpoint := TAnimrigMisc.WriteSkipBegin(ms);
      // version
      TAnimrigMisc.WriteInteger(ms,InputValueSaveVersion);
      // type
      TAnimrigMisc.WriteInteger(ms,integer(Inputs[i].ObjectType));
      // head
      TAnimrigMisc.WriteHead(ms,'values');
      // value count
      n2 := Inputs[i].ValueCount;
      TAnimrigMisc.WriteInteger(ms,n2);
      for j:=0 to n2-1 do
      begin
        TAnimrigMisc.EasingValueSaveToBinary(Inputs[i].Values[j],ms);
      end;
      // write data length
      TAnimrigMisc.WriteSkipEnd(ms,endpoint);
    end;
  end;
var
  i,n : integer;
  endpoint : int64;
  track : TAnimrigStateTrack;
  s : string;
begin
  // header
  TAnimrigMisc.WriteHead(ms , 'animrigstate');
  // version
  TAnimrigMisc.WriteInteger(ms , SaveVersion);

  // tracks
  TAnimrigMisc.WriteHead(ms , 'tracks');
  n := UsageTrackCount;
  TAnimrigMisc.WriteInteger(ms,n);
  for i:=0 to n-1 do
  begin
    // head
    TAnimrigMisc.WriteHead(ms,'track');
    // data length
    endpoint := TAnimrigMisc.WriteSkipBegin(ms);
    // version
    TAnimrigMisc.WriteInteger(ms,TAnimrigStateTrack.SaveVersion);

    track := Tracks[i];
    if track = nil then
    begin
      // exists = false
      TAnimrigMisc.WriteBool(ms,false);
    end
    else
    begin
      // exists = true
      TAnimrigMisc.WriteBool(ms,true);
      // track data
      s := '';
      if track.Animation <> nil then s := track.Animation.Name;
      TAnimrigMisc.WriteString(ms , s);
      track.SaveToBinary(ms);
    end;

    // write data length
    TAnimrigMisc.WriteSkipEnd(ms,endpoint);
  end;

  // input value
  inInputValue(ms);
end;

function TAnimrigState.Seek(targettrackindex: integer; time: double; framegrid: boolean): boolean;
var
  frame,d,fps : double;
  track : TAnimrigStateTrack;
begin
  result := false;   // rewind flag

  if targettrackindex < 0 then exit;
  if targettrackindex > TrackCount-1 then exit;
  track := Tracks[targettrackindex];
  if track = nil then exit;

  time := max(time,0.0);

  frame := time * fData.Framerate;
  if framegrid then frame := floor(frame + 0.5);
  fps := 1.0 / fData.Framerate;
  d := frame - floor(track.PlayFrame+0.5);

  // frame grid
  if framegrid then
    if abs(d) < fps then exit;

  // seek
  if d >= 0.0 then
  begin
    Update(d * fps , false , true);
  end
  else
  begin
    result := true;   // rewind flag
    track.Reset(true);
    Update(time , true , true);  // skip mode
  end;
end;

function TAnimrigState.GetSpineUseTrack(t: TAnimrigMisc.LayerType): integer;
begin
  result := 0;
  case t of
    anlAnimBlend1,anlSeekBlend1 : result := 1;
    anlAnimBlend2,anlSeekBlend2 : result := 2;
    anlAnimBlend4,anlSeekBlend4 : result := 4;
  end;
end;

procedure TAnimrigState.Update(deltatime: double; updateonce: boolean; seekmode: boolean);
  // object setup
  procedure inObjectSetup;
  var
    i : integer;
  begin
    for i:=0 to ObjectCount-1 do
    begin
      if Inputs[i].Renderer = nil then continue;

      case Inputs[i].ObjectType of
        anoSpine :
          begin
            Inputs[i].Renderer.SetMixDuration( TAnimrigSpineObject(fData.Objects[i]).MixDuration );
          end;
      end;
    end;
  end;
  // object update and setting
  procedure inObjectUpdate(deltatime:double);
  var
    i : integer;
    enable : boolean;
  begin
    for i:=0 to ObjectCount-1 do
    begin
      enable := fData.Objects[i].Enable;
      if not(enable) then continue;

      // value easing & renderer update
      Inputs[i].Update(deltatime);
    end;
  end;
  // event
  procedure inFirstFrameEvent;
  var
    i : integer;
    track : TAnimrigStateTrack;
  begin
    for i:=0 to TrackCount-1 do
    begin
      track := Tracks[i];
      if track = nil then continue;

      if track.Enable then
        if track.PlayFrame < 0.0 then
        begin
          track.PlayFrame := 0.0;
          Update_Event(track,true,seekmode);
        end;
    end;
  end;
  procedure inOneFrameEvent(deltaframe:double);
  var
    i : integer;
    track : TAnimrigStateTrack;
  begin
    for i:=0 to TrackCount-1 do
    begin
      track := Tracks[i];
      if track = nil then continue;

      // self clear
      if track.IsSelfClear then
      begin
        ClearTrack(i);
        continue;
      end;

      if track.Enable then
      begin
        if seekmode then
        begin
          track.Update(deltaframe);
        end
        else
        begin
          if not(track.Pause) then
            track.Update(deltaframe * track.PlaySpeed);
        end;
        Update_Event(track,false,seekmode);
      end;
    end;
  end;
var
  deltaframe,f : double;
  first : boolean;
  loopcount : integer;
begin
  // seekmode : pass enable & playspeed
  // skip : 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
  inObjectSetup;

  // first frame (prevkey & frame0) event
  inFirstFrameEvent;

  // execute per frame
  deltaframe := deltatime * fData.Framerate;
  first := true;
  loopcount := 0;
  while deltaframe >= 0.0 do
  begin
    // 0.0 once loop break
    if (loopcount > 0)and(deltaframe <= TAnimrigMisc.FloatZero)then break;

    if deltaframe >= 1.0 then f := 1.0
                         else f := deltaframe;
    inOneFrameEvent(f);
    if not updateonce then
    begin
      // interpolation
      Update_Interpolation(first , seekmode);
      // composite
      Update_Composite;
      // object update
      inObjectUpdate(f / fData.Framerate);

      first := false;
    end;

    deltaframe := deltaframe - 1.0;
    inc(loopcount);
  end;

  // skip
  if updateonce then
  begin
    // interpolation
    Update_Interpolation(first , seekmode);
    // composite
    Update_Composite;
    // object update
    inObjectUpdate(deltatime);
  end;
end;

procedure TAnimrigState.Update_Composite;
  function inCalcCommonLayer(track:TAnimrigStateTrack; var mix : array of single; mixcount:integer; objectindex,layerindex : integer):boolean;
  var
    layer : TAnimrigStateTrackLayer;
    aa1 : single;
    k : integer;
  begin
    result := false;
    if track = nil then exit;
    if track.Animation = nil then exit;

    layer := track.Objects[objectindex].layers[layerindex];
    if layer.IsPass then
      if track.MixAlphaEase.Done then exit;      // fade blend

    aa1 := 1.0-track.MixAlphaEase.Value;
    for k:=0 to mixcount-1 do
      mix[k] := layer.Values[k] + (layer.pValues[k]-layer.Values[k])*aa1;
    result := true;
  end;
var
  i,j,k,o : integer;
  layercount : integer;
  obj : TAnimrigObject;
  track : TAnimrigStateTrack;
  layer : TAnimrigStateTrackLayer;
  sum : array [0..TAnimrigStateTrackLayer.ValueCount-1] of single;   // track sum
  mix : array[0..TAnimrigStateTrackLayer.ValueCount-1] of single;    // animation mix
  rotateorder : TAnimrigMisc.RotateOrder;
  aa1,aa2,aa3,aa4 : single;
  trackindex,usetrack : integer;
  spinelayerexists : boolean;
  enable,visible : boolean;
  transmatrix : TAnimrigMisc.Matrix4;
  transcolor : array[0..3] of single;
  objectindex,parentindex : integer;
  renderer : TAnimrigStateRenderer;
  zeroposition : TAnimrigMisc.Vector3;
  drawpriority : single;
begin
  // composite

  zeroposition.x := 0.0;
  zeroposition.y := 0.0;
  zeroposition.z := 0.0;

  for o:=0 to ObjectCount-1 do
  begin
    // object update order (parent object link)
    objectindex := fObjectUpdateOrder[o];
    obj := fData.Objects[objectindex];     // reserve object.
    renderer := Inputs[objectindex].Renderer; // renderer object
    layercount := obj.LayerCount;

    // enable / visible
    enable := obj.Enable;
    visible := obj.Visible;
    // offset , scale
    TAnimrigMisc.m4SetIdentity(transmatrix);
    TAnimrigMisc.m4PushMove(transmatrix , -obj.Offset.x,-obj.Offset.y,-obj.Offset.z);
    TAnimrigMisc.m4PushScale(transmatrix , obj.Scale.x,obj.Scale.y,obj.Scale.z);

    transcolor[0] := 1.0;  // argb
    transcolor[1] := 1.0;
    transcolor[2] := 1.0;
    transcolor[3] := 1.0;
    trackindex := 0;
    drawpriority := obj.DrawPriority;

    // composite layer direction : blendalpha
    for i:=0 to layercount-1 do
    begin
      // spine use track count
      // * It is not affected by layer.enable. keep layer.index and spine.trackindex.
      usetrack := GetSpineUseTrack(obj.Layers[i].LayerType);

      case obj.Layers[i].LayerType of
        anlAnimBlend4,anlAnimBlend2,anlAnimBlend1 :
          begin
            // clear
            TAnimrigStateTrackLayer.ClearValues(sum,anlAnimBlend4);
            spinelayerexists := false;

            for j:=0 to TrackCount-1 do
            begin
              track := Tracks[j];
              if track = nil then continue;
              if not(inCalcCommonLayer(track,mix,2, objectindex,i)) then continue;
              layer := track.Objects[objectindex].Layers[i];
              // track blend
              aa3 := track.TrackAlphaEase.Value;
              aa1 := 1.0 + (layer.PrevTrackAlpha-1.0) * aa3;
              aa2 := layer.CurrentTrackAlpha * aa3;
              sum[0] := sum[0]*aa1 + mix[0]*aa2;
              sum[1] := sum[1]*aa1 + mix[1]*aa2;
              spinelayerexists := true;
            end;

            aa1 := min( max(sum[1],0),+2.0 );
            aa2 := min( max(sum[0],0),+2.0 );
            aa3 := - min( max(sum[1],-2.0),0.0 );
            aa4 := - min( max(sum[0],-2.0),0.0 );
            if renderer <> nil then
              if enable then
                renderer.SetTrackBlendAlpha(
                  trackindex,usetrack,
                  aa1,aa2,aa3,aa4,
                  spinelayerexists    // exists enable layer layer
                );
          end;
        anlSeekBlend4,anlSeekBlend2,anlSeekBlend1 :
          begin
            // clear
            TAnimrigStateTrackLayer.ClearValues(sum,anlSeekBlend4);
            spinelayerexists := false;

            for j:=0 to TrackCount-1 do
            begin
              track := Tracks[j];
              if track = nil then continue;
              if not(inCalcCommonLayer(track,mix,2, objectindex,i)) then continue;
              layer := track.Objects[objectindex].Layers[i];
              // track blend
              aa3 := track.TrackAlphaEase.Value;
              aa1 := 1.0 + (layer.PrevTrackAlpha-1.0) * aa3;
              aa2 := layer.CurrentTrackAlpha * aa3;
              sum[0] := sum[0]*aa1 + mix[0]*aa2;
              sum[1] := sum[1]*aa1 + mix[1]*aa2;
              spinelayerexists := true;
            end;

            aa1 := min( max(sum[1],0),+3600);
            aa2 := min( max(sum[0],0),+3600);
            aa3 := - min( max(sum[1],-3600),0.0 );
            aa4 := - min( max(sum[0],-3600),0.0 );
            if renderer <> nil then
              if enable then
              begin
                renderer.SetTrackBlendAlpha(
                  trackindex,usetrack,
                  1.0,1.0,1.0,1.0,
                  spinelayerexists    // exists enable layer layer
                );
                renderer.SetTrackSeekTime(
                  trackindex,usetrack,
                  aa1,aa2,aa3,aa4,
                  spinelayerexists    // exists enable layer layer
                );
              end;
          end;
        anlTransform :
          begin
            // clear
            TAnimrigStateTrackLayer.ClearValues(sum,anlTransform);
            rotateorder := anrPass;

            for j:=0 to TrackCount-1 do
            begin
              track := Tracks[j];
              if track = nil then continue;
              if not(inCalcCommonLayer(track,mix,3+3+3+4, objectindex,i)) then continue;
              layer := track.Objects[objectindex].Layers[i];
              // track blend
              aa3 := track.TrackAlphaEase.Value;
              aa1 := 1.0 + (layer.PrevTrackAlpha-1.0) * aa3;
              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.0 + (sum[3]-1.0)*aa1) * (1.0 + (mix[3]-1.0)*aa2);
              sum[4] := (1.0 + (sum[4]-1.0)*aa1) * (1.0 + (mix[4]-1.0)*aa2);
              sum[5] := (1.0 + (sum[5]-1.0)*aa1) * (1.0 + (mix[5]-1.0)*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 <> anrPass then rotateorder := layer.RotateOrder;
              // alpha,color
              sum[9] := (1.0 + (sum[9]-1.0)*aa1) * (1.0 + (mix[9]-1.0)*aa2);
              sum[10] := (1.0 + (sum[10]-1.0)*aa1) * (1.0 + (mix[10]-1.0)*aa2);
              sum[11] := (1.0 + (sum[11]-1.0)*aa1) * (1.0 + (mix[11]-1.0)*aa2);
              sum[12] := (1.0 + (sum[12]-1.0)*aa1) * (1.0 + (mix[12]-1.0)*aa2);
            end;

            if rotateorder = anrPass then rotateorder := anrZYX;
            // composite
            TAnimrigMisc.m4PushScale(transmatrix , sum[3],sum[4],sum[5] );
            TAnimrigMisc.m4PushRotate(transmatrix , sum[6],sum[7],sum[8] , rotateorder);   // rotate mode
            TAnimrigMisc.m4PushMove(transmatrix , sum[0],sum[1],sum[2]);

            transcolor[0] := transcolor[0] * sum[9];
            transcolor[1] := transcolor[1] * sum[10];
            transcolor[2] := transcolor[2] * sum[11];
            transcolor[3] := transcolor[3] * sum[12];
          end;
        anlPriority :
          begin
            // clear
            TAnimrigStateTrackLayer.ClearValues(sum,anlPriority);

            for j:=0 to TrackCount-1 do
            begin
              track := Tracks[j];
              if track = nil then continue;
              if not(inCalcCommonLayer(track,mix,2, objectindex,i)) then continue;
              layer := track.Objects[objectindex].Layers[i];
              // track blend
              aa3 := track.TrackAlphaEase.Value;
              aa1 := 1.0 + (layer.PrevTrackAlpha-1.0) * aa3;
              aa2 := layer.CurrentTrackAlpha * aa3;
              sum[0] := sum[0]*aa1 + mix[0]*aa2;
            end;
            drawpriority := drawpriority + sum[0];
          end;
      end;

      // spine track index
      trackindex := trackindex + usetrack;
    end;

    // group
    parentindex := fData.Objects[objectindex].ParentObjectIndex;
    if (parentindex >= 0)and(parentindex < ObjectCount)and(parentindex <> objectindex) then
    begin
      // composite parent matrix
      TAnimrigMisc.m4PushMatrix(transmatrix,fTempObjectMatrix[parentindex].Matrix);
      fTempObjectMatrix[objectindex].Matrix := transmatrix; // hard copy
      fTempObjectMatrix[objectindex].InheritedEnbale := enable and fTempObjectMatrix[parentindex].InheritedEnbale;
      fTempObjectMatrix[objectindex].InheritedVisible := visible and fTempObjectMatrix[parentindex].InheritedVisible;
      fTempObjectMatrix[objectindex].DrawPriority := drawpriority + fTempObjectMatrix[parentindex].DrawPriority;
      fTempObjectMatrix[objectindex].TransColor[0] := transcolor[0] * fTempObjectMatrix[parentindex].TransColor[0];
      fTempObjectMatrix[objectindex].TransColor[1] := transcolor[1] * fTempObjectMatrix[parentindex].TransColor[1];
      fTempObjectMatrix[objectindex].TransColor[2] := transcolor[2] * fTempObjectMatrix[parentindex].TransColor[2];
      fTempObjectMatrix[objectindex].TransColor[3] := transcolor[3] * fTempObjectMatrix[parentindex].TransColor[3];
    end
    else
    begin
      // none
      TAnimrigMisc.m4PushMatrix(transmatrix,RootMatrix);
      fTempObjectMatrix[objectindex].Matrix := transmatrix; // hard copy
      fTempObjectMatrix[objectindex].InheritedEnbale := enable;
      fTempObjectMatrix[objectindex].InheritedVisible := visible;
      fTempObjectMatrix[objectindex].DrawPriority := drawpriority;
      fTempObjectMatrix[objectindex].TransColor[0] := transcolor[0];
      fTempObjectMatrix[objectindex].TransColor[1] := transcolor[1];
      fTempObjectMatrix[objectindex].TransColor[2] := transcolor[2];
      fTempObjectMatrix[objectindex].TransColor[3] := transcolor[3];
    end;

    // state
    if renderer <> nil then
    begin
      renderer.RendererEnable := enable;
      renderer.RendererVisible := visible;
      renderer.RendererInheritedEnable := fTempObjectMatrix[objectindex].InheritedEnbale;
      renderer.RendererInheritedVisible := fTempObjectMatrix[objectindex].InheritedVisible;
      renderer.TransColor[0] := fTempObjectMatrix[objectindex].TransColor[0];
      renderer.TransColor[1] := fTempObjectMatrix[objectindex].TransColor[1];
      renderer.TransColor[2] := fTempObjectMatrix[objectindex].TransColor[2];
      renderer.TransColor[3] := fTempObjectMatrix[objectindex].TransColor[3];
      renderer.TransMatrix := transmatrix;   // hard copy
      // priority
      renderer.TransPosition := TAnimrigMisc.m4Transform(zeroposition , transmatrix);
      renderer.DrawPriorityZ := renderer.TransPosition.z;
      renderer.DrawPriorityDesign := fTempObjectMatrix[objectindex].DrawPriority;
    end;
  end;
end;

procedure TAnimrigState.Update_Event(track : TAnimrigStateTrack; prevkeyevent:boolean; seekmode:boolean);
  // event. execute per 1 frame
  procedure inEventAnimationBlend(layer:TAnimrigStateTrackLayer; input:TAnimrigStateInput; key:TAnimrigAnimBlend4Key; trackindex,usetrack:integer);
  begin
    if input = nil then exit;
    if input.Renderer = nil then exit;
    // name
    if key.Animation = 'none' then exit;
    // allow
    if not(key.AnimationAllowAlways)then
      if layer.Animationname = key.Animation then exit;

    input.Renderer.SetAnimation(
      trackindex,usetrack,
      key.Animation,
      key.AnimationLoop,
      key.AnimationAdd,
      key.AnimationMixBlend,
      key.AnimationMixDuration,
      false
    );
    layer.Animationname := key.Animation;
  end;
  procedure inEventSeekTimeBlend(layer:TAnimrigStateTrackLayer; input:TAnimrigStateInput; key:TAnimrigSeekBlend4Key; trackindex,usetrack:integer);
  begin
    if input = nil then exit;
    if input.Renderer = nil then exit;
    // name
    if key.Animation = 'none' then exit;
    // allow
    if not(key.AnimationAllowAlways)then
      if layer.Animationname = key.Animation then exit;

    input.Renderer.SetAnimation(
      trackindex,usetrack,
      key.Animation,
      key.AnimationLoop,
      key.AnimationAdd,
      key.AnimationMixBlend,
      key.AnimationMixDuration,
      true
    );
    layer.Animationname := key.Animation;
  end;
  procedure inEventEvent(layer:TAnimrigStateTrackLayer; input:TAnimrigStateInput; key:TAnimrigEventKey; track:TAnimrigStateTrack);
  var
    rec : TAnimrigStateEventData;
    f1,f2 : single;
  begin
    // sender : TAnimrigState
    // owner : Object
    // renderer : TRenderer
    // string[0,1]
    // float[0,1]
    if not(Assigned(fEventCallback)) then exit;
    // seek flag
    if seekmode then exit;
    // ignition
    if key.IgnitionAlways then exit;
    // name
    if key.ID = 'none' then exit;
    if key.ID = '' then exit;
    if not key.AllowAlways then
      if key.ID = layer.EventID then exit;

    rec.ignition := false;
    rec.input := input;
    rec.track := track;
    rec.key := key;
    rec.ID := key.ID;
    rec.Strings[0] := key.EventString[0];
    rec.Strings[1] := key.EventString[1];
    f1 := input.ConvertSpecialFloat(key.EventFloat[0]);
    f2 := input.ConvertSpecialFloat(key.EventFloat[1]);
    rec.Floats[0] := f1;
    rec.Floats[1] := f2;
    fEventCallback(self,rec);

    layer.EventID := key.ID;
  end;
var
  f : integer;
  i,j,k : integer;
  obj : TAnimrigObject;
  layer : TAnimrigLayer;
  key : TAnimrigKey;
  trackindex,usetrack : integer;
  enable : boolean;
  layercount,keycount : integer;
begin
  if track.Animation = nil then exit;
  // loop
  f := floor(track.Animation.GetLoopedFrame(track.PlayFrame));
  if track.PrevPlayFrame = f then exit;
  // object
  for i:=0 to ObjectCount-1 do
  begin
    obj := track.Animation.Objects[i];
    enable := fData.Objects[i].Enable;  // reserve object
    trackindex := 0;
    layercount := obj.LayerCount;
    for j:=0 to layercount-1 do
    begin
      layer := obj.Layers[j];
      // spine use track count
      // * It is not affected by layer.enable. keep layer.index and spine.trackindex.
      usetrack := GetSpineUseTrack(layer.LayerType);

      if layer.Enable then
      begin
        // prevkey(first only)
        if (f = 0)and(prevkeyevent) then
        begin
          key := layer.PrevKey;
          if enable then
            case key.LayerType of
              anlAnimBlend4,anlAnimBlend2,anlAnimBlend1 :
                inEventAnimationBlend(track.Objects[i].layers[j] , Inputs[i] , TAnimrigAnimBlend4Key(key) , trackindex,usetrack);
              anlSeekBlend4,anlSeekBlend2,anlSeekBlend1 :
                inEventSeekTimeBlend(track.Objects[i].layers[j] , Inputs[i] , TAnimrigSeekBlend4Key(key) , trackindex,usetrack);
              anlEvent :
                inEventEvent(track.Objects[i].layers[j] , Inputs[i] , TAnimrigEventKey(key) , track);
            end;
        end;
        // keys
        // * Optimize some day :Q
        keycount := layer.KeyCount;
        for k := 0 to keycount-1 do
        begin
          key := layer.Keys[k];
          if key.Frame > f then break;
          if key.Frame <> f then continue;
          if enable then
            case key.LayerType of
              anlAnimBlend4,anlAnimBlend2,anlAnimBlend1 :
                inEventAnimationBlend(track.Objects[i].layers[j] , Inputs[i] , TAnimrigAnimBlend4Key(key) , trackindex,usetrack);
              anlSeekBlend4,anlSeekBlend2,anlSeekBlend1 :
                inEventSeekTimeBlend(track.Objects[i].layers[j] , Inputs[i] , TAnimrigSeekBlend4Key(key) , trackindex,usetrack);
              anlEvent :
                inEventEvent(track.Objects[i].layers[j] , Inputs[i] , TAnimrigEventKey(key) , track);
            end;
        end;
      end;
      // spine trackset index
      trackindex := trackindex + usetrack;
    end;
  end;
  // prev play frame
  track.PrevPlayFrame := f;
end;

procedure TAnimrigState.Update_Interpolation(first: boolean; seekmode:boolean);
  // interpolation.
  function inInterpolationValue(input:TAnimrigStateInput; x0,x1,x2,x3 : single; a,b:single; splinemode : TAnimrigMisc.Spline):single;
  begin
    if splinemode = ansLinear then
    begin
      // 2 key : linear
      if x1 >= TAnimrigMisc.InputFloatIndex then x1 := input.ConvertSpecialFloat(x1);
      if x2 >= TAnimrigMisc.InputFloatIndex then x2 := input.ConvertSpecialFloat(x2);
      result := x1 + (x2-x1) * a;
    end
    else
    begin
      // 4key : spline
      if x0 >= TAnimrigMisc.InputFloatIndex then x0 := input.ConvertSpecialFloat(x0);
      if x1 >= TAnimrigMisc.InputFloatIndex then x1 := input.ConvertSpecialFloat(x1);
      if x2 >= TAnimrigMisc.InputFloatIndex then x2 := input.ConvertSpecialFloat(x2);
      if x3 >= TAnimrigMisc.InputFloatIndex then x3 := input.ConvertSpecialFloat(x3);
      result := TAnimrigMisc.SplineFloat(x0,x1,x2,x3 , a,b , splinemode);
    end;
  end;
  procedure inInterpolationCommon(input:TAnimrigStateInput; layer:TAnimrigStateTrackLayer; key0,key1,key2,key3:TAnimrigKey; a:single);
  var
    aa : single;
  begin
    // track blend alpha
    aa := TAnimrigMisc.EasingFloat(0.0,1.0 , a,key1.TrackAlphaPower , key1.TrackAlphaEasing);

    if key1.IsPrevKey then
    begin
      layer.PrevTrackAlpha := TAnimrigMisc.EasingLinear(layer.pPrevTrackAlpha,key2.PrevTrackAlpha , aa);
      layer.CurrentTrackAlpha := TAnimrigMisc.EasingLinear(layer.pCurrentTrackAlpha,key2.CurrentTrackAlpha , aa);
    end
    else
    begin
      layer.PrevTrackAlpha := TAnimrigMisc.EasingLinear(key1.PrevTrackAlpha,key2.PrevTrackAlpha , aa);
      layer.CurrentTrackAlpha := TAnimrigMisc.EasingLinear(key1.CurrentTrackAlpha,key2.CurrentTrackAlpha , aa);
    end;
  end;
  procedure inInterpolationAnimationBlend(input:TAnimrigStateInput; layer:TAnimrigStateTrackLayer; key0,key1,key2,key3:TAnimrigAnimBlend4Key; a:single);
  var
    aa,bb : single;
    ss : TAnimrigMisc.Spline;
  begin
    aa := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.BlendAlphaPower , key1.BlendAlphaEasing);
    bb := 1.0;
    ss := key1.BlendAlphaSpline;

    if key1.IsPrevKey then
    begin
      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);
    end
    else
    begin
      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);
    end;
  end;
  procedure inInterpolationSeekTimeBlend(input:TAnimrigStateInput; layer:TAnimrigStateTrackLayer; key0,key1,key2,key3:TAnimrigSeekBlend4Key; a:single);
  var
    aa,bb : single;
    ss : TAnimrigMisc.Spline;
  begin
    aa := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.SeekTimePower , key1.SeekTimeEasing);
    bb := 1.0;
    ss := key1.SeekTimeSpline;

    if key1.IsPrevKey then
    begin
      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);
    end
    else
    begin
      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);
    end;
  end;
  procedure inInterpolationTransform(input:TAnimrigStateInput; layer:TAnimrigStateTrackLayer; key0,key1,key2,key3:TAnimrigTransformKey; a:single);
  var
    aa,bb : single;
    ss : TAnimrigMisc.Spline;
  begin
    aa := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.TransformPower , key1.TransformEasing);
    bb := 1.0;
    ss := key1.TransSpline;

    if key1.IsPrevKey then
    begin
      // 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 := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.ColorPower , key1.ColorEasing);
      layer.Values[9] := TAnimrigMisc.EasingLinear(layer.pValues[9] , key2.mulalpha , aa);
      layer.Values[10] := TAnimrigMisc.EasingLinear(layer.pValues[10]*$ff , (key2.mulcolor shr 16 and $ff) , aa) * (1/$ff);
      layer.Values[11] := TAnimrigMisc.EasingLinear(layer.pValues[11]*$ff , (key2.mulcolor shr  8 and $ff) , aa) * (1/$ff);
      layer.Values[12] := TAnimrigMisc.EasingLinear(layer.pValues[12]*$ff , (key2.mulcolor        and $ff) , aa) * (1/$ff);
    end
    else
    begin
      // 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 := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.ColorPower , key1.ColorEasing);
      layer.Values[9] := TAnimrigMisc.EasingLinear(key1.mulalpha , key2.mulalpha , aa);
      layer.Values[10] := TAnimrigMisc.EasingLinear((key1.mulcolor shr 16 and $ff) , (key2.mulcolor shr 16 and $ff) , aa) * (1/$ff);
      layer.Values[11] := TAnimrigMisc.EasingLinear((key1.mulcolor shr  8 and $ff) , (key2.mulcolor shr  8 and $ff) , aa) * (1/$ff);
      layer.Values[12] := TAnimrigMisc.EasingLinear((key1.mulcolor        and $ff) , (key2.mulcolor        and $ff) , aa) * (1/$ff);
    end;
  end;
  procedure inInterpolationPriority(input:TAnimrigStateInput; layer:TAnimrigStateTrackLayer; key0,key1,key2,key3:TAnimrigPriorityKey; a:single);
  var
    aa : single;
  begin
    aa := TAnimrigMisc.EasingFloat(0.0,1.0, a,key1.PriorityPower , key1.PriorityEasing);

    if key1.IsPrevKey then
    begin
      layer.Values[0] := TAnimrigMisc.EasingLinear(layer.pValues[0],key2.Priority , aa);
    end
    else
    begin
      layer.Values[0] := TAnimrigMisc.EasingLinear(key1.Priority,key2.Priority , aa);
    end;
  end;
  procedure inInterpolationEvent(input:TAnimrigStateInput; track:TAnimrigStateTrack; layer:TAnimrigStateTrackLayer; key:TAnimrigEventKey);
  var
    rec : TAnimrigStateEventData;
    f1,f2 : single;
  begin
    if not(Assigned(fEventCallback)) then exit;
    // seek flag
    if seekmode then exit;
    // ignition
    if not key.IgnitionAlways then exit;
    // name
    if key.ID = 'none' then exit;
    if key.ID = '' then exit;
    // * unsupport allowdiffrent
    // if not key1.AllowAlways then
    //   if key1.ID = layer.EventID then exit;

    rec.ignition := true;
    rec.input := input;
    rec.track := track;
    rec.key := key;
    rec.ID := key.ID;
    rec.Strings[0] := key.EventString[0];
    rec.Strings[1] := key.EventString[1];
    f1 := input.ConvertSpecialFloat(key.EventFloat[0]);
    f2 := input.ConvertSpecialFloat(key.EventFloat[1]);
    rec.Floats[0] := f1;
    rec.Floats[1] := f2;
    fEventCallback(self,rec);

    layer.EventID := key.ID;
  end;
  procedure inTrack(track : TAnimrigStateTrack);
  var
    f : double;
    i,j : integer;
    obj : TAnimrigObject;
    layer : TAnimrigLayer;
    tracklayer : TAnimrigStateTrackLayer;
    anim : TAnimrigAnimation;
    key0,key1,key2,key3 : TAnimrigKey;    // key1 .. current
    index,keycount : integer;
    d : double;
  begin
    anim := track.Animation;
    if anim = nil then exit;
    // loop (float)
    f := anim.GetLoopedFrame(track.PlayFrame);
    // object
    for i:=0 to ObjectCount-1 do
    begin
      obj := anim.Objects[i];
      for j:=0 to obj.LayerCount-1 do
      begin
        layer := obj.Layers[j];
        tracklayer := track.Objects[i].layers[j];
        keycount := layer.KeyCount;
        tracklayer.IsPass := false;
        // key empty
        if keycount = 0 then tracklayer.IsPass := true;
        // enable
        if not(layer.Enable) then tracklayer.IsPass := true;
        // pass
        if tracklayer.IsPass then
        begin
          TAnimrigStateTrackLayer.ClearValues(tracklayer.Values,tracklayer.LayerType);
          continue;
        end;

        // get current key
        index := layer.GetKeyIndex(floor(f));
        if index = -1 then
        begin
          // between prevkey(0frame) to key[0]
          key1 := layer.PrevKey;
          if keycount > 0 then key2 := layer.keys[0]
                          else key2 := key1;
          d := (f - 0.0) / max((key2.Frame - 0.0) , 1.0);
          key0 := key1;
          if 1 < keycount then key3 := layer.Keys[1]
                          else key3 := key2;
        end
        else
        begin
          // keys[]
          // current key
          key1 := layer.Keys[index];
          // next key
          if index+1 < keycount then
          begin
            key2 := layer.Keys[index+1];
            d := (f - key1.Frame) / max((key2.Frame - key1.Frame) , 1);
          end
          else
          begin
            // last key
            key2 := key1;
            d := 0.0;
          end;
          // previous key
          if index-1 < 0 then key0 := layer.PrevKey
                         else key0 := layer.Keys[index-1];
          // next next key
          if index+2 < keycount then key3 := layer.Keys[index+2]
                                else key3 := key2;
        end;
        // common
        inInterpolationCommon(
          Inputs[i],
          tracklayer,                    // track.layers (output)
          key0,key1,key2,key3,
          d
        );
        // layer type
        case layer.LayerType of
          anlAnimBlend4,anlANimBlend2,anlAnimBlend1 :
            inInterpolationAnimationBlend(
              Inputs[i],
              tracklayer,
              TAnimrigAnimBlend4Key(key0),TAnimrigAnimBlend4Key(key1),TAnimrigAnimBlend4Key(key2),TAnimrigAnimBlend4Key(key3),
              d
            );
          anlSeekBlend4,anlSeekBlend2,anlSeekBlend1 :
            inInterpolationSeekTimeBlend(
              Inputs[i],
              tracklayer,
              TAnimrigSeekBlend4Key(key0),TAnimrigSeekBlend4Key(key1),TAnimrigSeekBlend4Key(key2),TAnimrigSeekBlend4Key(key3),
              d
            );
          anlTransform :
            inInterpolationTransform(
              Inputs[i],
              tracklayer,
              TAnimrigTransformKey(key0),TAnimrigTransformKey(key1),TAnimrigTransformKey(key2),TAnimrigTransformKey(key3),
              d
            );
          anlPriority :
            inInterPolationPriority(
              Inputs[i],
              tracklayer,
              TAnimrigPriorityKey(key0),TAnimrigPriorityKey(key1),TAnimrigPriorityKey(key2),TAnimrigPriorityKey(key3),
              d
            );
          anlEvent :
            if first then        // * call once. first only.
              inInterPolationEvent(
                Inputs[i],
                track,
                tracklayer,
                TAnimrigEventKey(key1)
              );
        end;
      end;
    end;
  end;
var
  i : integer;
  track : TAnimrigStateTrack;
begin
  for i:=0 to TrackCount-1 do
  begin
    track := Tracks[i];
    if track = nil then continue;
    if not(track.Enable) then continue;
    inTrack(track);
  end;
end;

function TAnimrigState.UsageTrackCount: integer;
var
  i : integer;
begin
  result := 0;
  for i:=TrackCount-1 downto 0 do
    if fTrackList.Items[i] <> nil then
    begin
      result := i + 1;
      exit;
    end;
end;

end.
