unit AnimationrigMisc;

interface

uses SysUtils,Math,Classes;

// {$SCOPEDENUMS ON}

type
  TAnimrigMisc = class
    protected
    public
      // type
      type ObjectType = (
        anoAny,
        anoSpine,
        anoNull,
        anoCamera,
        anoAnimationBMP
      );
      type LayerType = (
        anlNone,
        anlAnimBlend4,anlAnimBlend2,anlAnimBlend1,
        anlSeekBlend4,anlSeekBlend2,anlSeekBlend1,
        anlTransform,anlCollision,anlEvent,anlPriority,anlState,
        anlPhysicsForce,
        anlAnimationBMP
      );
      type FloatColor = record
        a,r,g,b : single;

        procedure SetMul(const c1,c2 : FloatColor); inline;
        procedure SetAdd(const c1,c2 : FloatColor); inline;
      end;
      type MixBlend = (anmReplace,anmAdd);
      // const
      const FloatZero:single = 0.000001;
      const InputCount = 32;
      const InputFloatIndex = $10000;
      const _noname:string = 'unnamed';
      const RadianToAngle:double = 1 / (Pi*2);
      // binary read write
      class var LittleEndian : boolean;  // def : true
      class function IsInputValue(f:single):boolean; inline;
      class procedure BitSwap(buffer:pointer; typesize:integer; count:integer);
      class procedure WriteString(ms : TMemoryStream; const s:string);
      class function ReadString(ms : TMemoryStream; max_:integer=4096) : string;
      class procedure WriteFloat(ms : TMemoryStream; value:single);
      class function ReadFloat(ms : TMemoryStream; min_:single=-$ffffff; max_:single=$ffffff; round_:boolean=false; checkspecial:boolean=false) : single;
      class procedure WriteInteger(ms : TMemoryStream; value:integer);
      class function ReadInteger(ms : TMemoryStream; min_:integer=-$ffffff; max_:integer=$ffffff; round_:boolean=false; checkspecial:boolean=false) : integer;
      class procedure WriteBool(ms:TMemoryStream; b:boolean);
      class function ReadBool(ms : TMemoryStream) : boolean;
      class procedure WriteWord(ms : TMemoryStream; value:word);
      class function ReadWord(ms : TMemoryStream; max_:integer=65535) : word;
      class procedure WriteHead(ms:TMemoryStream; const s:string);
      class procedure ReadHead(ms:TMemoryStream; const s:string);  // error throw exception
      class procedure WriteStringList(ms:TMemoryStream; sl:TStringList);
      class procedure ReadStringList(ms :TMemoryStream; sl:TStringList);  // error throw exception
      class function GetStringHash(const s:string):integer;
      // data skip function
      class function WriteSkipBegin(ms:TmemoryStream):Int64;
      class procedure WriteSkipEnd(ms:TmemoryStream; pos_:Int64);
      class function ReadSkipBegin(ms:TmemoryStream):Int64;
      class procedure JumpSkipEnd(ms:TmemoryStream; pos_:Int64);
      // convert
      class function ObjectTypeToString(t:ObjectType):string;
      class function StringToObjectType(const s:string):ObjectType;
      class function LayerTypeToString(t:LayerType):string;
      class function StringToLayerType(const s:string):LayerType;
      // easing
      type Easing = (
        aneNone,
        aneLinear,
        aneOutQuad,aneInQuad,aneOutInQuad,aneInOutQuad,
        aneOutExpo,aneInExpo,aneOutInExpo,aneInOutExpo,
        aneOutElastic,aneInElastic,aneOutInElastic,aneInOutElastic,
        aneOutBounce,aneInBounce,aneOutInBounce,aneInOutBounce,
        aneOutBack,aneInBack,aneOutInBack,aneInOutBack,
        aneWaveSquare,aneWaveSaw,aneWaveTriangle,aneWaveSin,
        aneTerminate,
        aneCustom0=$80,
        aneCustom1=$81,
        aneCustom2=$82,
        aneCustom3=$83,
        aneCustom4=$84,
        aneCustom5=$85,
        aneCustom6=$86,
        aneCustom7=$87,
        aneCustomTerminate=$88
      );
      type EasingModeFunction = function (x1,x2:single; a,b:single) : single of object;
      class function ReadEasing(ms:TMemoryStream) : Easing;
      class function EasingSplit(x1,x2:single; a,b:single; f1,f2:EasingModeFunction) : single; inline;
      class function EasingFloat(x1,x2:single; a,b:single; mode:Easing) : single;
      class function EasingLinear(x1,x2:single; a:single) : single; inline;
      class function EasingOutQuad(x1,x2:single; a,b:single) : single; inline;
      class function EasingInQuad(x1,x2:single; a,b:single) : single; inline;
      class function EasingOutExpo(x1,x2:single; a,b:single) : single; inline;
      class function EasingInExpo(x1,x2:single; a,b:single) : single; inline;
      class function EasingOutElastic(x1,x2:single; a,b:single) : single; inline;
      class function EasingInElastic(x1,x2:single; a,b:single) : single; inline;
      class function EasingOutBounce(x1,x2:single; a,b:single) : single; inline;
      class function EasingInBounce(x1,x2:single; a,b:single) : single; inline;
      class function EasingOutBounceDrop(x1,x2:single; a,b:single) : single; inline;
      class function EasingInBounceDrop(x1,x2:single; a,b:single) : single; inline;
      class function EasingOutBack(x1,x2:single; a,b:single) : single; inline;
      class function EasingInBack(x1,x2:single; a,b:single) : single; inline;
      class function EasingWaveSquare(x1,x2:single; a,b:single) : single; inline;
      class function EasingWaveSaw(x1,x2:single; a,b:single) : single; inline;
      class function EasingWaveTriangle(x1,x2:single; a,b:single) : single; inline;
      class function EasingWaveSin(x1,x2:single; a,b:single) : single; inline;

      // easing value
      const EasingSaveVersion = 1;
      type EasingData = record
        Value : single;
        PreValue : single;
        SetValue : single;
        // state
        Done : boolean;
        Duration : single;
        Beta : single;
        Playtime : double;
        Mode : Easing;
      end;
      class procedure EasingValueClear(var d:EasingData);
      class procedure EasingValueUpdate(var d:EasingData; deltatime:double);
      class procedure EasingValueEase(var d:EasingData; v:single; duration:single=0.0; mode:Easing=aneLinear; beta:single=1.0);
      class procedure EasingValueSaveToBinary(const d:EasingData; ms:TmemoryStream);
      class function  EasingValueLoadFromBinary(var d:EasingData; ms:TmemoryStream):boolean;
      // spline
      type Spline = (
        ansLinear,
        ansBSpline,
        ansHermite,
        ansBezierAuto,
        ansTerminate,
        ansCustom0=$80,
        ansCustom1=$81,
        ansCustom2=$82,
        ansCustom3=$83,
        ansCustom4=$84,
        ansCustom5=$85,
        ansCustom6=$86,
        ansCustom7=$87,
        ansCustomTerminate=$88
      );
      class function ReadSpline(ms: TMemoryStream): Spline;
      class function SplineFloat(x0,x1,x2,x3 : single; a,b:single; mode:Spline) : single;
      // color
      class function LinearColor(c1,c2:integer; a:single):integer;
      class function MultiplyColor(c1,c2:integer; a:single):integer;
      // matrix
      type RotateOrder = (anrPass , anrXYZ,anrXZY,anrYXZ,anrYZX,anrZXY,anrZYX);
      class function ReadRotateOrder(ms:TMemoryStream) : RotateOrder;
      type
        Matrix4 = record
          _11:Single; _12:Single; _13:Single; _14:Single;
          _21:Single; _22:Single; _23:Single; _24:Single;
          _31:Single; _32:Single; _33:Single; _34:Single;
          _41:Single; _42:Single; _43:Single; _44:Single;
        end;
        PMatrix4 = ^Matrix4;
      type
        Vector3 = record
          x,y,z,_dummy : Single;
        end;
        PVector3 = ^Vector3;
      const PI = 3.1415926535;
      const IdentityMatrix:Matrix4 = (
        _11:1; _12:0; _13:0; _14:0;
        _21:0; _22:1; _23:0; _24:0;
        _31:0; _32:0; _33:1; _34:0;
        _41:0; _42:0; _43:0; _44:1
      );
      const WhiteColor : FloatColor = (
        a : 1.0;
        r : 1.0;
        g : 1.0;
        b : 1.0;
      );
      const BlackColor : FloatColor = (
        a : 1.0;
        r : 0.0;
        g : 0.0;
        b : 0.0;
      );
      class procedure v3SetVector(var v:Vector3; x,y,z:single);
      class function v3Compare(const v1,v2 : Vector3):boolean;
      class function m4Transform(const v:Vector3; const m:Matrix4):Vector3;
      class procedure m4PushMove(var m:Matrix4; x,y,z:single);
      class procedure m4PushRotateX(var m:Matrix4; r:single);
      class procedure m4PushRotateY(var m:Matrix4; r:single);
      class procedure m4PushRotateZ(var m:Matrix4; r:single);
      class procedure m4PushRotate(var m:Matrix4; x,y,z:single; mode:RotateOrder);
      class procedure m4PushScale(var m:Matrix4; x,y,z:single);
      class procedure m4PushMatrix(var m1:Matrix4; const m2:Matrix4);
      class function m4InvertMatrix(const m:Matrix4; var vaild:boolean) : Matrix4;
      // string misc
      class function DivideString(const delimiter,src:string):TStringList;
      // sort
      class procedure GetSortedIndexes(var tbl:array of integer; const data:array of double; count:integer; ascending:boolean=true);
  end;

implementation

{ TAnimrigMisc }

class procedure TAnimrigMisc.BitSwap(buffer: pointer; typesize,count: integer);
var
  i : integer;
  ptr2 : pword;     // 2 byte
  ptr4 : pcardinal; // 4 byte
  t : cardinal;
begin
  // byte
  if typesize <= 1 then exit;
  // 2 byte
  if typesize = 2 then
  begin
    ptr2 := buffer;
    for i:=0 to count-1 do
    begin
      t := ptr2^;
      ptr2^ := ((t and $ff00) shr 8) or ((t and $00ff ) shl 8);
      inc(ptr2);
    end;
    exit;
  end;
  // 4 byte
  if typesize = 4 then
  begin
    ptr4 := buffer;
    for i:=0 to count-1 do
    begin
      t := ptr4^;
      ptr4^ := ((t and $ff000000) shr 24) or ((t and $ff0000 ) shr 8) or ((t and $ff00) shl 8) or ((t and $ff) shl 24);
      inc(ptr4);
    end;
    exit;
  end;

  raise Exception.Create('BitSwap : unsupport bit swap type length : '+IntToStr(typesize));
end;

class function TAnimrigMisc.DivideString(const delimiter, src: string): TStringList;
var
  i : integer;
  len : integer;
  d : char;
  s : string;
begin
  result := TStringList.Create;
  // result.Delimiter := delimiter[1];   // do not use. divide line by space & tab word.
  // result.DelimitedText := src;

  d := delimiter[1];
  len := length(src);
  s := '';
  for i:=0 to len-1 do
  begin
    if src[i+1] = d then
    begin
      result.Add(s);
      s := '';
      continue;
    end;
    s := s + src[i+1];
  end;
  if s <> '' then result.Add(s);
end;

class function TAnimrigMisc.EasingFloat(x1, x2, a, b: single; mode: Easing): single;
begin
  if x1 = x2 then
  begin
    result := x1;
    exit;
  end;

  case mode of
    aneNone : if a < 1.0 then result := x1
                         else result := x2;
    aneLinear : result := EasingLinear(x1,x2 , a);
    // quad
    aneOutQuad : result := EasingOutQuad(x1,x2 ,a,b);
    aneInQuad : result := EasingInQuad(x1,x2 ,a,b);
    aneOutInQuad : result := EasingSplit(x1,x2 ,a,b, EasingOutQuad,EasingInQuad);
    aneInOutQuad : result := EasingSplit(x1,x2 ,a,b, EasingInQuad,EasingOutQuad);
    // expo
    aneOutExpo : result := EasingOutExpo(x1,x2 ,a,b);
    aneInExpo : result := EasingInExpo(x1,x2, a,b);
    aneOutInExpo : result := EasingSplit(x1,x2, a,b, EasingOutExpo,EasingInExpo);
    aneInOutExpo : result := EasingSplit(x1,x2, a,b, EasingInExpo,EasingOutExpo);
    // elastic
    aneOutElastic : result := EasingOutElastic(x1,x2 ,a,b);
    aneInElastic : result := EasingInElastic(x1,x2, a,b);
    aneOutInElastic : result := EasingSplit(x1,x2, a,b, EasingOutElastic,EasingInElastic);
    aneInOutElastic : result := EasingSplit(x1,x2, a,b, EasingInElastic,EasingOutElastic);
    // bounce
    aneOutBounce : result := EasingOutBounce(x1,x2 ,a,b);
    aneInBounce : result := EasingInBounce(x1,x2, a,b);
    aneOutInBounce : result := EasingSplit(x1,x2, a,b, EasingOutBounce,EasingInBounce);
    aneInOutBounce : result := EasingSplit(x1,x2, a,b, EasingInBounce,EasingOutBounce);
    // back
    aneOutBack : result := EasingOutBack(x1,x2 ,a,b);
    aneInBack : result := EasingInBack(x1,x2, a,b);
    aneOutInBack : result := EasingSplit(x1,x2, a,b, EasingOutBack,EasingInBack);
    aneInOutBack : result := EasingSplit(x1,x2, a,b, EasingInBack,EasingOutBack);
    // wave
    aneWaveSquare : result := EasingWaveSquare(x1,x2 ,a,b);
    aneWaveSaw : result := EasingWaveSaw(x1,x2 ,a,b);
    aneWaveTriangle : result := EasingWaveTriangle(x1,x2 ,a,b);
    aneWaveSin : result := EasingWaveSin(x1,x2 ,a,b);
  else
    result := x1;
  end;
end;

class function TAnimrigMisc.EasingInBack(x1, x2, a, b: single): single;
begin
  result := x1 + EasingOutBack(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingInBounce(x1, x2, a, b: single): single;
begin
  result := x1 + EasingOutBounce(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingInBounceDrop(x1, x2, a,b: single): single;
begin
  result := x1 + EasingOutBounceDrop(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingInElastic(x1, x2, a, b: single): single;
begin
  result := x1 + EasingOutElastic(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingInExpo(x1, x2, a, b: single): single;
begin
  result := x1 + EasingOutExpo(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingInQuad(x1, x2, a, b: single): single;
begin
  result := x1 + EasingOutQuad(x2-x1,0 , 1.0-a,b);
end;

class function TAnimrigMisc.EasingLinear(x1, x2, a: single): single;
begin
  result := x1 + (x2-x1) * a;
end;

class function TAnimrigMisc.EasingOutBack(x1, x2, a, b: single): single;
var
  d : single;
begin
  d := EasingOutQuad(0.0,1.0,a,b);
  d := (x2-x1) * (1.0-d);
  RESULT := x2 - cos(a*PI*1.5)*d;
end;

class function TAnimrigMisc.EasingOutBounce(x1, x2, a, b: single): single;
var
  d : Single;
begin
  d := EasingOutQuad(0.0,1.0,a,2.0);
  d := (x2-x1) * (1.0 - d);
  RESULT := x2 - abs(cos(a*PI*(b*4+1.0)))*d;
end;

class function TAnimrigMisc.EasingOutBounceDrop(x1, x2, a, b: single): single;
var
  d : single;
begin
  a := EasingInQuad(0.0,1.0,a,1.0);   // time stretch
  d := EasingOutQuad(0.0,1.0,a,2.0);
  d := (x2-x1) * (1.0 - d);
  RESULT := x2 - abs(cos(a*PI*(b*4+1.0)))*d;
end;

class function TAnimrigMisc.EasingOutElastic(x1, x2, a, b: single): single;
var
  d : Single;
begin
  d := EasingOutQuad(0.0,1.0,a,2.0);
  d := (x2-x1) * (1.0 - d);
  RESULT := x2 - cos(a*PI*(b*4+1.0))*d;
end;

class function TAnimrigMisc.EasingOutExpo(x1, x2, a, b: single): single;
var
  d,y : single;
begin
  d := x2 - x1;
  if b < 1.0 then
    y := 5 + b * 5
  else
    y := 10 + (b - 1.0) * 10;

  result := d * (1.0 - Math.power(2, -y * a)) + x1;
end;

class function TAnimrigMisc.EasingOutQuad(x1, x2, a, b: single): single;
var
  v1,v2 : single;
  ia : single;
begin
  ia := 1.0-a;
  if b < 1.0 then
  begin
    v1 := 1.0-ia;
    v2 := 1.0-(ia*ia);
    a := v1 + (v2-v1) * b;
  end
  else
  begin
    ia := ia*ia;
    v1 := 1.0-(ia);
    v2 := 1.0-(ia*ia);
    a := v1 + (v2-v1) * (b-1.0);
  end;
  result := x1 + (x2-x1) * a;
end;

class function TAnimrigMisc.EasingWaveSaw(x1, x2, a, b: single): single;
var
  l : integer;
begin
  l := floor(b*100 + 0.5);
  l := max(l,2);

  if l and 1 = 0 then
  begin
    // saw
    l := l div 2;
    a := a * l * 1.0; // 1set = 1cycle
    a := a - floor(a);
    result := x1 + (x2-x1) * a;
  end
  else
  begin
    // inv saw
    l := l div 2;
    a := a * l * 1.0; // 1set = 1cycle
    a := a - floor(a);
    result := x2 + (x1-x2) * a;
  end;
end;

class function TAnimrigMisc.EasingSplit(x1, x2, a, b: single; f1, f2: EasingModeFunction): single;
var
  x3 : single;
begin
  x3 := (x1+x2) * 0.5;
  if a < 0.5 then
    result := f1(x1,x3 , a*2,b)
  else
    result := f2(x3,x2 , (a-0.5)*2,b);
end;

class function TAnimrigMisc.EasingWaveSquare(x1, x2, a, b: single): single;
var
  l : integer;
begin
  l := floor(b*100 + 0.5);
  l := max(l,1);

  a := a * l * 0.5;    // 1step = half cycle
  a := a - floor(a);
  if a < 0.5 then
    result := x2
  else
    result := x1;
end;

class function TAnimrigMisc.EasingWaveTriangle(x1, x2, a, b: single): single;
var
  l : integer;
begin
  l := floor(b*100 + 0.5);
  l := max(l,1);

  a := a * l * 0.5;    // 1step = half cycle
  a := a - floor(a);
  if (a < 0.5) then a := a * 2
               else a := 1.0 - (a-0.5) * 2;
  result := x1 + (x2-x1) * a;
end;

class procedure TAnimrigMisc.EasingValueClear(var d: EasingData);
begin
  d.Done := true;
  d.Mode := aneNone;
  d.Duration := 0.0;
  d.Beta := 1.0;
  d.Playtime := 0.0;

  d.Value := 0.0;
  d.SetValue := 0.0;
  d.PreValue := 0.0;
end;

class procedure TAnimrigMisc.EasingValueEase(var d: EasingData; v, duration:single; mode: Easing; beta: single);
begin
  // no time
  if duration < 0.0001 then
  begin
    d.Value := v;
    d.SetValue := v;
    d.PreValue := v;

    d.Done := true;
    exit;
  end;

  // optimize same value
  if d.SetValue = v then
    if d.Duration = duration then
      if d.Beta = beta then
        if d.Mode = mode then
        begin
          exit;
        end;

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

  d.Done := false;
end;

class function TAnimrigMisc.EasingValueLoadFromBinary(var d: EasingData; ms: TmemoryStream): boolean;
var
  done : boolean;
  f : single;
  ver : integer;
begin
  result := false;
  try
    ReadHead(ms,'easing');
    ver := ReadInteger(ms,0,EasingSaveVersion);
    // done
    done := ReadBool(ms);
    if done then
    begin
      // value only
      d.Done := true;
      f := ReadFloat(ms , -$ffffff,$ffffff);
      EasingValueEase(d,f);
    end
    else
    begin
      // value + ease param
      d.Done := true;
      d.Value := ReadFloat(ms , -$ffffff,$ffffff);
      //
      d.PreValue := ReadFloat(ms , -$ffffff,$ffffff);
      d.SetValue := ReadFloat(ms , -$ffffff,$ffffff);
      d.Duration := ReadFloat(ms);
      d.Beta := ReadFloat(ms);
      d.Playtime := ReadFloat(ms);
      d.Mode := ReadEasing(ms);
    end;
  except
    exit;
  end;
  result := true;
end;

class procedure TAnimrigMisc.EasingValueSaveToBinary(const d: EasingData; ms: TmemoryStream);
begin
  WriteHead(ms,'easing');
  WriteInteger(ms,EasingSaveVersion);
  if d.Done then
  begin
    // value only
    // done
    WriteBool(ms,true);
    // value
    WriteFloat(ms,d.Value);
  end
  else
  begin
    // value + ease param
    // done
    WriteBool(ms,false);
    // value
    WriteFloat(ms,d.Value);
    // ease state
    WriteFloat(ms,d.PreValue);
    WriteFloat(ms,d.SetValue);
    WriteFloat(ms,d.Duration);
    WriteFloat(ms,d.Beta);
    WriteFloat(ms,d.Playtime);
    WriteInteger(ms,integer(d.Mode));
  end;
end;

class procedure TAnimrigMisc.EasingValueUpdate(var d: EasingData; deltatime: double);
var
  a : single;
begin
  if d.Done then exit;

  if d.Duration < 0.0001 then
  begin
    d.Value := d.SetValue;
    d.Playtime := d.Duration;
    d.Done := true;
    exit;
  end;

  // update time
  d.Playtime := d.Playtime + deltatime;
  if d.Playtime >= d.Duration then
  begin
    d.Value := d.SetValue;
    d.Playtime := d.Duration;
    d.Done := true;
    exit;
  end;

  a := d.Playtime / d.Duration;
  d.Value := EasingFloat(d.PreValue,d.SetValue , a,d.Beta , d.Mode);
end;

class function TAnimrigMisc.EasingWaveSin(x1, x2, a, b: single): single;
var
  l : integer;
begin
  l := floor(b*100 + 0.5);
  l := max(l,1);

  a := a * l * 0.5;    // 1step = half cycle
  a := a - floor(a);
  a := (1.0 - cos(a*PI*2)) * 0.5;
  result := x1 + (x2-x1) * a;
end;

class procedure TAnimrigMisc.GetSortedIndexes(var tbl : array of integer; const data : array of double; count: integer; ascending:boolean);
var
  i,j : integer;
  t : integer;
begin
  if count = 0 then exit;

  for i:=0 to count-1 do
    tbl[i] := i;

  if count = 1 then exit;

  if ascending then
  begin
    for i:=count-1-1 downto 0 do
      for j:=i to count-1-1 do
        if data[tbl[j]] > data[tbl[j+1]] then
        begin
          t := tbl[j];
          tbl[j] := tbl[j+1];
          tbl[j+1] := t;
        end;
  end
  else
  begin
    for i:=count-1-1 downto 0 do
      for j:=i to count-1-1 do
        if data[tbl[j]] < data[tbl[j+1]] then
        begin
          t := tbl[j];
          tbl[j] := tbl[j+1];
          tbl[j+1] := t;
        end;
  end;
end;

class function TAnimrigMisc.GetStringHash(const s: string): integer;
var
  i : integer;
  len : integer;
begin
  result := 0;
  len := length(s);
  if len > 256 then len := 256;
  for i:=0 to len-1 do
    result := result + (integer(s[i+1]) shl (i and 7));
end;

class function TAnimrigMisc.IsInputValue(f: single): boolean;
begin
  result := false;
  if f < InputFloatIndex then exit;
  result := true;

  // range check
  if not(floor(f - InputFloatIndex + 0.5) < InputCount) then
    raise Exception.Create('IsInputValue : out of range.');
end;

class procedure TAnimrigMisc.JumpSkipEnd(ms: TmemoryStream; pos_:Int64);
begin
  if ms.Position = pos_ then exit;

  ms.Seek(pos_,soFromBeginning);   // jump end point
end;
class function TAnimrigMisc.LayerTypeToString(t: LayerType): string;
begin
  case t of
    anlNone : result := 'none';
    anlAnimBlend4 : result := 'anim4';
    anlAnimBlend2 : result := 'anim2';
    anlAnimBlend1 : result := 'anim1';
    anlSeekBlend4 : result := 'seek4';
    anlSeekBlend1 : result := 'seek1';
    anlTransform : result := 'transform';
    anlCollision : result := 'collision';
    anlEvent : result := 'event';
    anlPriority : result := 'priority';
    anlState : result := 'state';
    anlPhysicsForce : result := 'physicsforce';
    anlAnimationBMP : result := 'abmp';
  else
    raise Exception.Create('LayerTypeToString : detect undefined type.');
  end;
end;

class function TAnimrigMisc.LinearColor(c1, c2: integer; a: single): integer;
var
  aa : integer;
  rr1,gg1,bb1,rr2,gg2,bb2 : integer;
begin
  aa := floor(a*256 + 0.5);

  rr1 := (c1 shr 16) and $ff;
  gg1 := (c1 shr 8) and $ff;
  bb1 := c1 and $ff;

  rr2 := (c2 shr 16) and $ff;
  gg2 := (c2 shr 8) and $ff;
  bb2 := c2 and $ff;

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

  result := (rr1 shl 16) or (gg1 shl 8) or bb1;
end;

class function TAnimrigMisc.m4InvertMatrix(const m: Matrix4; var vaild: boolean): Matrix4;
var
  d,rd : single;
  t : Matrix4;
  v1,v2,v3 : single;
begin
  vaild := false;

  d := m._11 * (m._22*m._33 - m._32*m._23) - m._12 * (m._21*m._33 - m._31*m._23) + m._13 * (m._21*m._32 - m._31*m._22);
  if d = 0.0 then
  begin
    result := IdentityMatrix;
    exit;
  end;

  //result := IdentityMatrix;
  //m4PushMove(result,-m._41,-m._42,-m._43);

  t := IdentityMatrix;
  rd := 1.0 / d;
  with m do begin
    t._11 := (_22*_33-_23*_32) * rd;
    t._12 := (-(_12*_33-_32*_13)) * rd;
    t._13 := (_12*_23-_22*_13) * rd;

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

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

  //m4PushMatrix(result,t);
  v1 := -m._41;
  v2 := -m._42;
  v3 := -m._43;
  t._41 := t._11*v1 + t._21*v2 + t._31*v3 + t._41;
  t._42 := t._12*v1 + t._22*v2 + t._32*v3 + t._42;
  t._43 := t._13*v1 + t._23*v2 + t._33*v3 + t._43;
  t._44 := t._14*v1 + t._24*v2 + t._34*v3 + t._44;
  result := t;

  vaild := true;
end;

class procedure TAnimrigMisc.m4PushMatrix(var m1: Matrix4; const m2: Matrix4);
var
  v1,v2,v3,v4 : single;
begin
  v1 := m1._11;
  v2 := m1._12;
  v3 := m1._13;
  v4 := m1._14;
  m1._11 := m2._11*v1 + m2._21*v2 + m2._31*v3 + m2._41*v4;
  m1._12 := m2._12*v1 + m2._22*v2 + m2._32*v3 + m2._42*v4;
  m1._13 := m2._13*v1 + m2._23*v2 + m2._33*v3 + m2._43*v4;
  m1._14 := m2._14*v1 + m2._24*v2 + m2._34*v3 + m2._44*v4;

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

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

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

class procedure TAnimrigMisc.m4PushMove(var m: Matrix4; x, y, z: single);
var
  v4 : single;
begin
  if (x = 0.0)and(y = 0.0)and(z = 0.0) then exit;
  //      1         2         3        4
  // 1
  // 2
  // 3
  // 4    x         y         z        1.0
  v4 := m._14;
  m._11 := m._11 + v4*x;
  m._12 := m._12 + v4*y;
  m._13 := m._13 + v4*z;

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

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

  v4 := m._44;
  m._41 := m._41 + v4*x;
  m._42 := m._42 + v4*y;
  m._43 := m._43 + v4*z;
end;

class procedure TAnimrigMisc.m4PushRotate(var m: Matrix4; x, y, z: single; mode: RotateOrder);
begin
  case mode of
    anrXYZ :begin
              m4PushRotateX(m , x);
              m4PushRotateY(m , y);
              m4PushRotateZ(m , z);
            end;
    anrXZY :begin
              m4PushRotateX(m , x);
              m4PushRotateZ(m , z);
              m4PushRotateY(m , y);
            end;
    anrYXZ :begin
              m4PushRotateY(m , y);
              m4PushRotateX(m , x);
              m4PushRotateZ(m , z);
            end;
    anrYZX :begin
              m4PushRotateY(m , y);
              m4PushRotateZ(m , z);
              m4PushRotateX(m , x);
            end;
    anrZXY :begin
              m4PushRotateZ(m , z);
              m4PushRotateX(m , x);
              m4PushRotateY(m , y);
            end;
    anrZYX,anrPass :begin
              m4PushRotateZ(m , z);
              m4PushRotateY(m , y);
              m4PushRotateX(m , x);
            end;
  end;
end;

class procedure TAnimrigMisc.m4PushRotateX(var m: Matrix4; r: single);
var
  s,c : single;
  v2,v3 : single;
begin
  if (r = 0.0)then exit;

  //      1         2         3        4
  // 1
  // 2              c         s
  // 3              -s        c
  // 4
  s := sin(r * 2*PI);
  c := cos(r * 2*PI);

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

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

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

  v2 := m._42;
  v3 := m._43;
  m._42 := v2*c + v3*-s;
  m._43 := v2*s + v3*c;
end;

class procedure TAnimrigMisc.m4PushRotateY(var m: Matrix4; r: single);
var
  s,c : single;
  v1,v3 : single;
begin
  if (r = 0.0)then exit;

  //      1         2         3        4
  // 1    c                   -s
  // 2
  // 3    s                   c
  // 4
  s := sin(r * 2*PI);
  c := cos(r * 2*PI);

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

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

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

  v1 := m._41;
  v3 := m._43;
  m._41 := v1*c  + v3*s;
  m._43 := v1*-s + v3*c;
end;

class procedure TAnimrigMisc.m4PushRotateZ(var m: Matrix4; r: single);
var
  s,c : single;
  v1,v2 : single;
begin
  if (r = 0.0)then exit;

  //      1         2         3        4
  // 1    c         s
  // 2    -s        c
  // 3
  // 4
  s := sin(r * 2*PI);
  c := cos(r * 2*PI);

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

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

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

  v1 := m._41;
  v2 := m._42;
  m._41 := v1*c + v2*-s;
  m._42 := v1*s + v2*c;
end;

class procedure TAnimrigMisc.m4PushScale(var m: Matrix4; x, y, z: single);
begin
  if (x = 1.0)and(y = 1.0)and(z = 1.0) then exit;

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

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

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

  m._41 := m._41 * x;
  m._42 := m._42 * y;
  m._43 := m._43 * z;
end;

class function TAnimrigMisc.m4Transform(const v: Vector3; const m: Matrix4): Vector3;
begin
  result.x := v.x*m._11 + v.y*m._21 + v.z*m._31 + m._41;
  result.y := v.x*m._12 + v.y*m._22 + v.z*m._32 + m._42;
  result.z := v.x*m._13 + v.y*m._23 + v.z*m._33 + m._43;
  result._dummy := 1.0;
end;

class function TAnimrigMisc.MultiplyColor(c1, c2: integer; a: single): integer;
var
  aa : integer;
  rr1,gg1,bb1,rr2,gg2,bb2 : integer;
begin
  aa := floor(a*256 + 0.5);

  rr1 := (c1 shr 16) and $ff;
  gg1 := (c1 shr 8) and $ff;
  bb1 := c1 and $ff;

  rr2 := (c2 shr 16) and $ff;
  gg2 := (c2 shr 8) and $ff;
  bb2 := c2 and $ff;

  rr2 := rr2 + ($ff-rr2)*aa div 256;
  gg2 := gg2 + ($ff-gg2)*aa div 256;
  bb2 := bb2 + ($ff-bb2)*aa div 256;

  rr1 := rr1 * rr2 div 256;
  gg1 := gg1 * gg2 div 256;
  bb1 := bb1 * bb2 div 256;

  result := (rr1 shl 16) or (gg1 shl 8) or bb1;
end;

class function TAnimrigMisc.ObjectTypeToString(t: ObjectType): string;
begin
  case t of
    anoAny          : result := 'any';
    anoSpine        : result := 'spine';
    anoNull         : result := 'null';
    anoCamera       : result := 'camera';
    anoAnimationBMP : result := 'abmp';
  else
    raise Exception.Create('ObjectTypeToString : detect undefined type.');
  end;
end;

class function TAnimrigMisc.ReadBool(ms: TMemoryStream): boolean;
begin
  result := ReadInteger(ms,0,1) = 1;
end;

class function TAnimrigMisc.ReadEasing(ms: TMemoryStream): Easing;
var
  v : integer;
begin
  v := ReadInteger(ms,0,$ff);

  if v < integer(TAnimrigMisc.Easing.aneCustom0) then
  begin
    // preset
    v := min(v,integer(Easing.aneTerminate)-1);
    result := Easing(v);
  end
  else
  begin
    // custom
    v := min(v,integer(Easing.aneCustomTerminate)-1);
    result := Easing(v);
  end;
end;

class function TAnimrigMisc.ReadFloat(ms: TMemoryStream; min_,max_: single; round_,checkspecial:boolean): single;
var
  len : integer;
begin
  len := ms.Read(result,sizeof(single));
  if not(LittleEndian) then BitSwap(@result,sizeof(single),1);

  if len <> sizeof(single) then
    raise Exception.Create('ReadFloatFromBinary : can not read stream.');

  if round_ then
  begin
    if (result < InputFloatIndex)or(checkspecial = false) then
      result := min(max(result,min_),max_)
    else
      result := min(max(result,min_),InputFloatIndex+InputCount-1.0);
  end
  else
  begin
    if checkspecial then
      if IsInputValue(result) then exit;

    if (result < min_)or(result > max_)then
      raise Exception.Create('ReadFloatFromBinary : out of range : ' + FloatToStr(result));
  end;
end;

class procedure TAnimrigMisc.ReadHead(ms: TMemoryStream; const s: string);
begin
  if ReadInteger(ms) <> GetStringHash(s) then
    raise Exception.Create('ReadHead : head miss match : ' + s);
end;

class function TAnimrigMisc.ReadInteger(ms: TMemoryStream; min_,max_: integer; round_,checkspecial:boolean): integer;
var
  len : integer;
begin
  len := ms.Read(result,sizeof(integer));
  if not(LittleEndian) then BitSwap(@result,sizeof(integer),1);

  if len <> sizeof(integer) then
    raise Exception.Create('ReadIntegerFromBinary : can not read stream.');

  if round_ then
  begin
    result := min(max(result,min_),max_);
  end
  else
  begin
    if (result < min_)or(result > max_)then
      raise Exception.Create('ReadIntegerFromBinary : out of range : ' + IntToStr(result));
  end;
end;

class function TAnimrigMisc.ReadRotateOrder(ms: TMemoryStream): RotateOrder;
begin
  result := RotateOrder( ReadInteger(ms , 0,6+1,true) );
end;

class function TAnimrigMisc.ReadSkipBegin(ms: TmemoryStream): Int64;
var
  v : integer;
  p : int64;
begin
  p := ms.Position;            // now position
  v := ReadInteger(ms,-$fffffff,$fffffff);
  result := p + v;             // return end point
end;

class function TAnimrigMisc.ReadSpline(ms: TMemoryStream): Spline;
var
  v : integer;
begin
  v := ReadInteger(ms,0,$ff);

  if v < integer(Spline.ansCustom0) then
  begin
    // preset
    v := min(v,integer(Spline.ansTerminate)-1);
    result := Spline(v);
  end
  else
  begin
    // custom
    v := min(v,integer(Spline.ansCustomTerminate)-1);
    result := Spline(v);
  end;
end;

class function TAnimrigMisc.ReadString(ms: TMemoryStream; max_: integer): string;
var
  len : word;
begin
  len := ReadWord(ms,max_);

  if len = 0 then
  begin
    result := '';
    exit;
  end;

  SetLength(result,len);
  ms.Read(result[1],len*sizeof(widechar));

  if not(LittleEndian) then BitSwap(@result[1],sizeof(widechar),len);
end;

class procedure TAnimrigMisc.ReadStringList(ms: TMemoryStream; sl: TStringList);
var
  n,i : integer;
begin
  sl.Clear;

  n := ReadInteger(ms , 0,$ffff);
  for i:=0 to n-1 do
    sl.Add(ReadString(ms));
end;

class function TAnimrigMisc.ReadWord(ms: TMemoryStream; max_: integer): word;
begin
  if ms.Read(result,sizeof(word)) <> sizeof(word) then
    raise Exception.Create('ReadWordFromBinary : can not read stream.');

  if result > max_ then
    raise Exception.Create('ReadWordFromBinary : out of length : ' + IntToStr(result));

  if not(LittleEndian) then BitSwap(@result,2,1);
end;

class function TAnimrigMisc.SplineFloat(x0, x1, x2, x3, a, b: single; mode: Spline): single;
var
  ia,id :Single;
  t1,t2 : Single;
begin
  if (x1 = x2)and(x0 = x3)and(x1 = x2) then
  begin
    result := x1;
    exit;
  end;

  case mode of
    ansLinear : result := x1 + (x2-x1) * a;
    ansBSpline :
      begin
        ia := 1 - a;
        t1 := a * a;
        t2 := t1 * a;
        result := ( (x0*ia*ia*ia) + x1*(3*t2-6*t1+4) + x2*(-3*(t2-t1-a)+1) + x3*t2) / 6.0;
      end;
    ansHermite :
      begin
        ia := 1.0 - a;
        id := a - 1.0;
        // fake class line
        t1 := ( x2 - x0 ) * b;
        t2 := ( x3 - x1 ) * b;
        // slow down
        //if x1 = x2 then
        //begin
        //  t1 := t1 * 0.25;
        //  t2 := t2 * 0.25;
        //end;
        result :=
          x1 * ( id * id ) * ( a * 2.0 + 1.0 ) +
          x2 * ( a * a   ) * ( 3.0 - a * 2.0 ) +
          t1 * ( ia * ia ) * ( a             ) +
          t2 * ( id      ) * ( a * a         );
      end;
    ansBezierAuto :
      begin
        // auto anchor point
        ia := 1.0-a;
        t1 := ((x1-x0) + (x2-x1)) * 0.25 * b;
        t2 := ((x2-x3) + (x1-x2)) * 0.25 * b;
        t1 := x1 + t1;
        t2 := x2 + t2;
        // bezier
        result := ia*ia*ia*x1 + 3*ia*ia*a*t1 + 3*ia*a*a*t2 + a*a*a*x2;
      end;
  else
    result := x1 + (x2-x1) * a;
  end;
end;

class function TAnimrigMisc.StringToLayerType(const s: string): LayerType;
begin
  if s = 'none' then result := anlNone
    else if s = 'spine4' then result := anlAnimBlend4   // compati
    else if s = 'spine1' then result := anlAnimBlend1
    else if s = 'anim4' then result := anlAnimBlend4
    else if s = 'anim2' then result := anlAnimBlend2
    else if s = 'anim1' then result := anlAnimBlend1
    else if s = 'seek4' then result := anlSeekBlend4
    else if s = 'seek1' then result := anlSeekBlend1
    else if s = 'transform' then result := anlTransform
    else if s = 'collision' then result := anlCollision
    else if s = 'event' then result := anlEvent
    else if s = 'priority' then result := anlPriority
    else if s = 'state' then result := anlState
    else if s = 'physicsforce' then result := anlPhysicsForce
    else if s = 'abmp' then result := anlAnimationBMP
    else
      raise Exception.Create('StringToLayerType : detect undefined type : ' + s);
end;

class function TAnimrigMisc.StringToObjectType(const s: string): ObjectType;
begin
  if s = 'any' then result := anoAny
    else if s = 'spine' then result := anoSpine
    else if s = 'null' then result := anoNull
    else if s = 'camera' then result := anoCamera
    else if s = 'abmp' then result := anoAnimationBMP
    else
      raise Exception.Create('StringToObjectType : detect undefined type : ' + s);
end;

class function TAnimrigMisc.v3Compare(const v1, v2: Vector3):boolean;
begin
  result := false;
  if v1.x <> v2.x then exit;
  if v1.y <> v2.y then exit;
  if v1.z <> v2.z then exit;
  result := true;
end;

class procedure TAnimrigMisc.v3SetVector(var v:Vector3; x, y, z: single);
begin
  v.x := x;
  v.y := y;
  v.z := z;
  v._dummy := 0;
end;

class procedure TAnimrigMisc.WriteBool(ms: TMemoryStream; b: boolean);
begin
  if b then
    WriteInteger(ms,1)
  else
    WriteInteger(ms,0);
end;

class procedure TAnimrigMisc.WriteFloat(ms: TMemoryStream; value: single);
begin
  if not(LittleEndian) then BitSwap(@value,sizeof(single),1);
  ms.Write(value,sizeof(single));
end;

class procedure TAnimrigMisc.WriteHead(ms: TMemoryStream; const s: string);
begin
  WriteInteger(ms,GetStringHash(s));
end;

class procedure TAnimrigMisc.WriteInteger(ms: TMemoryStream; value: integer);
begin
  if not(LittleEndian) then BitSwap(@value,sizeof(integer),1);
  ms.Write(value,sizeof(integer));
end;

class function TAnimrigMisc.WriteSkipBegin(ms: TmemoryStream): Int64;
var
  t : integer;
begin
  result := ms.Position;        // return now position
  t := 0;                       // 0 clear
  WriteInteger(ms,t);
end;

class procedure TAnimrigMisc.WriteSkipEnd(ms: TmemoryStream; pos_: Int64);
var
  v : integer;
  p : Int64;
begin
  p := ms.Position;                // now position
  v := p - pos_;                   // diff position
  ms.Seek(pos_,soFromBeginning);   // seek skip size chank
  WriteInteger(ms,v);
  ms.Seek(p,soFromBeginning);      // restore position
end;

class procedure TAnimrigMisc.WriteString(ms: TMemoryStream; const s: string);
var
  len : word;
  t : string;
begin
  len := length(s);
  WriteWord(ms,len);

  if len = 0 then exit;

  if LittleEndian then
  begin
    ms.write(s[1],len*sizeof(widechar));
  end
  else
  begin
    t := s;
    BitSwap(@t[1],sizeof(widechar),len);
    ms.Write(t[1],len*sizeof(widechar));
  end;
end;

class procedure TAnimrigMisc.WriteStringList(ms: TMemoryStream; sl: TStringList);
var
  s : string;
begin
  WriteInteger(ms , sl.Count);
  for s in sl do
    WriteString(ms , s);
end;

class procedure TAnimrigMisc.WriteWord(ms: TMemoryStream; value: word);
begin
  if LittleEndian then
  begin
    ms.Write(value,sizeof(word));
  end
  else
  begin
    BitSwap(@value,sizeof(word),1);
    ms.Write(value,sizeof(word));
  end;
end;

{ TAnimrigMisc.FloatColor }

procedure TAnimrigMisc.FloatColor.SetAdd(const c1, c2: FloatColor);
begin
  // a := c1.a * c2.a;
  r := Min(c1.r + c2.r , 1.0);
  g := Min(c1.g + c2.g , 1.0);
  b := Min(c1.b + c2.b , 1.0);
end;

procedure TAnimrigMisc.FloatColor.SetMul(const c1,c2: FloatColor);
begin
  a := c1.a * c2.a;
  r := c1.r * c2.r;
  g := c1.g * c2.g;
  b := c1.b * c2.b;
end;

{ initialization }

initialization
begin
  TAnimrigMisc.LittleEndian := true;
end;

end.
