unit AnimationrigSpine;

interface

uses SysUtils,Classes,Contnrs,StrUtils,math,
     uMisc_Math,
     Animationrig,AnimationrigMisc,AnimationrigState,
     uSpineAnimation,uSpineSkeleton,uSpineAttachment;

type
  TAnimrigStateSpineRenderer = class(TAnimrigStateRenderer)
    protected
      fstate : pspAnimationState;
      fskeleton : pspSkeleton;
    public
      const SaveVersion = 1;

      constructor Create;

      procedure Update(deltatime:double); override;

      procedure SetAnimation(trackindex,usetrack: integer; const basename: string; loop:boolean; addanim:boolean; trackmix:TAnimrigMisc.MixBlend; duration:single; seekmode:boolean); override;
      procedure SetTrackBlendAlpha(trackindex,usetrack: integer; alpha1, alpha2, alpha3, alpha4: single; exists:boolean); override;
      procedure SetTrackSeekTime(trackindex,usetrack:integer; time1, time2, time3, time4: single; exists:boolean); override;
      procedure SetMixDuration(duration:single); override;

      property AnimationState : pspAnimationState read fstate write fstate;
      property Skeleton : pspSkeleton read fskeleton write fskeleton;

      procedure SaveToBinary(ms:TMemoryStream);

      class procedure SpineAnimationStateSaveToBinary(state:pspAnimationState; ms:TMemoryStream);
      class function  SpineAnimationStateLoadFormBinary(state:pspAnimationState; ms:TMemoryStream):boolean;
  end;

implementation

{ TAnimrigStateRendererSpine }

constructor TAnimrigStateSpineRenderer.Create; //(state: pspAnimationState; skeleton:pspSkeleton);
begin
  fObjectType := anoSpine;
  fstate := nil;
  fskeleton := nil;
end;

procedure TAnimrigStateSpineRenderer.SaveToBinary(ms: TMemoryStream);
begin
  SpineAnimationStateSaveToBinary(fstate,ms);
end;

procedure TAnimrigStateSpineRenderer.SetAnimation(trackindex,usetrack: integer; const basename: string; loop, addanim:boolean; trackmix:TAnimrigMisc.MixBlend; duration:single; seekmode:boolean);
  procedure inSetEmpty(trackindex:integer);
  begin
    if addanim then
      spAnimationState_addEmptyAnimation(fstate,trackindex , fstate.data.defaultMix , 0.0)
    else
      spAnimationState_setEmptyAnimation(fstate,trackindex , fstate.data.defaultMix);
  end;
  procedure inSetAnimation(trackindex:integer; const name1,name2:string);
  var
    anim : pspAnimation;
    track : pspTrackEntry;
    l : integer;
  begin
    // search
    anim := spSkeletonData_findAnimation(fstate.data.skeletonData,name1);
    if anim = nil then anim := spSkeletonData_findAnimation(fstate.data.skeletonData,name2);

    if anim = nil then
    begin
      // set/add empty
      inSetEmpty(trackindex);
    end
    else
    begin
      // set/add animation
      l := 0;
      if loop then l := 1;
      if addanim then
        track := spAnimationState_addAnimation(fstate,trackindex,anim , l,0.0)
      else
        track := spAnimationState_setAnimation(fstate,trackindex,anim , l);
      if track <> nil then
      begin
        // mixblend
        if track.mixBlend >= SP_MIX_BLEND_REPLACE then   // ignore SETUP FIRST
        begin
          case trackmix of
            anmReplace : track.mixBlend := SP_MIX_BLEND_REPLACE;
            anmAdd     : track.mixBlend := SP_MIX_BLEND_ADD;
          end;
        end;
        // duration
        if duration < 0.0 then
        begin
          // use spine default mix
        end
        else
        begin
          track.mixDuration := max(duration,0.0);
        end;
        // seekmode
        if seekmode then track.timeScale := 0.0
                    else track.timeScale := 1.0;
      end;
    end;
  end;
begin
  if fstate = nil then exit;

  // layertype = anlSpine1
  if usetrack = 1 then
  begin
    if basename = 'empty' then
    begin
      inSetEmpty(trackindex+0);
    end
    else
    begin
      inSetAnimation(trackindex+0 , basename,basename+'@up');
    end;
  end;

  // layertype = anlSpine4
  if usetrack = 4 then
  begin
    if basename = 'empty' then
    begin
      inSetEmpty(trackindex+0);
      inSetEmpty(trackindex+1);
      inSetEmpty(trackindex+2);
      inSetEmpty(trackindex+3);
    end
    else
    begin
      inSetAnimation(trackindex+0 , basename,basename+'@up');
      inSetAnimation(trackindex+1 , basename+'@right','');
      inSetAnimation(trackindex+2 , basename+'@down','');
      inSetAnimation(trackindex+3 , basename+'@left','');
    end;
  end;
end;

procedure TAnimrigStateSpineRenderer.Update(deltatime: double);
var
  fpumask : cardinal;
  changed : boolean;
begin
  if fstate = nil then exit;
  if fskeleton = nil then exit;

  changed := false;
  fpumask := Get8087CW;
  if (fpumask and $3f) <> $3f then
  begin
    math.SetExceptionMask([exInvalidOp,exDenormalized,exZeroDivide,exOverflow,exUnderflow,exPrecision]);   // = $3F
    changed := true;
  end;

  spAnimationState_update(fstate , deltatime);
  spAnimationState_apply(fstate , fskeleton);     // *zero div
  spSkeleton_updateWorldTransform(fskeleton);    //x,y transform after , next call spSkeleton_updateWorldTransform

  if changed then Set8087CW(fpumask);
end;

procedure TAnimrigStateSpineRenderer.SetTrackBlendAlpha(trackindex,usetrack: integer; alpha1, alpha2, alpha3, alpha4: single; exists:boolean);
  procedure inSet(trackindex:integer; alpha:single);
  var
    track : pspTrackEntry;
  begin
    track := spAnimationState_getCurrent(fstate,trackindex);
    if track = nil then exit;

    track.alpha := alpha;
  end;
begin
  if fstate = nil then exit;

  // exists enable layer flag
  // if pass track alpha , check flag and return function.
  //if not(exists) then exit;

  // 1 animation blend layer
  if usetrack = 1 then
  begin
    inSet(trackindex+0 , alpha1);  // up
  end;

  // 4 animations blend layer
  if usetrack = 4 then
  begin
    inSet(trackindex+0 , alpha1);  // up
    inSet(trackindex+1 , alpha2);  // right
    inSet(trackindex+2 , alpha3);  // down
    inSet(trackindex+3 , alpha4);  // left
  end;
end;

procedure TAnimrigStateSpineRenderer.SetTrackSeekTime(trackindex, usetrack: integer; time1, time2, time3, time4: single; exists: boolean);
  procedure inSet(trackindex:integer; time_:single);
  var
    track : pspTrackEntry;
  begin
    track := spAnimationState_getCurrent(fstate,trackindex);
    if track = nil then exit;

    track.trackTime := time_;
  end;
begin
  if fstate = nil then exit;

  // exists enable layer flag
  // if pass track alpha , check flag and return function.
  //if not(exists) then exit;

  // 1 animation seek time layer
  if usetrack = 1 then
  begin
    inSet(trackindex+0 , time1);  // up
  end;

  // 4 animations seek time layer
  if usetrack = 4 then
  begin
    inSet(trackindex+0 , time1);  // up
    inSet(trackindex+1 , time2);  // right
    inSet(trackindex+2 , time3);  // down
    inSet(trackindex+3 , time4);  // left
  end;
end;

class function TAnimrigStateSpineRenderer.SpineAnimationStateLoadFormBinary(state: pspAnimationState; ms: TMemoryStream): boolean;
var
  endpoint : int64;
  b : boolean;
begin
  result := false;

  endpoint := -1;
  try
    // head
    TAnimrigMisc.ReadHead(ms,'spinestate');
    // data size
    endpoint := TAnimrigMisc.ReadSkipBegin(ms);
    // state exists
    if state = nil then
    begin
      // skip
    end
    else
    begin
      // data exists
      b := TAnimrigMisc.ReadBool(ms);
      if b then
      begin
        // exists
      end
      else
      begin
        // null
      end;
    end;
    result := true;
  except
    //
  end;

  // end point
  TAnimrigMisc.JumpSkipEnd(ms,endpoint);
end;

class procedure TAnimrigStateSpineRenderer.SpineAnimationStateSaveToBinary(state: pspAnimationState; ms:TMemoryStream);
  procedure inTrack(track : pspTrackEntry);
  var
    endpoint : int64;
    s : string;
  begin
    // head
    TAnimrigMisc.WriteHead(ms , 'track');
    // data size
    endpoint := TAnimrigMisc.WriteSkipBegin(ms);

    // exists
    if track = nil then
    begin
      TAnimrigMisc.WriteBool(ms , false);
    end
    else
    begin
      TAnimrigMisc.WriteBool(ms , true);
      // animation (include <empty>)
      s := UTF8ToString(track.animation.name);
      TAnimrigMisc.WriteString(ms , s);
      // state
      TAnimrigMisc.WriteInteger(ms , track.loop);
      TAnimrigMisc.WriteFloat(ms , track.delay);
      TAnimrigMisc.WriteFloat(ms , track.trackTime);
      TAnimrigMisc.WriteFloat(ms , track.timeScale);
      TAnimrigMisc.WriteFloat(ms , track.alpha);
      TAnimrigMisc.WriteInteger(ms , integer(track.mixBlend));
      // next
      inTrack(pspTrackEntry(track.next));
    end;

    // write data size
    TAnimrigMisc.WriteSkipEnd(ms,endpoint);
  end;
var
  i : integer;
  endpoint : int64;
begin
  // head
  TAnimrigMisc.WriteHead(ms , 'spinestate');
  // data size
  endpoint := TAnimrigMisc.WriteSkipBegin(ms);

  // exists
  if state = nil then
  begin
    TAnimrigMisc.WriteBool(ms , false);
  end
  else
  begin
    TAnimrigMisc.WriteBool(ms , true);
    // version
    TAnimrigMisc.WriteInteger(ms , SaveVersion);
    // tracks
    TAnimrigMisc.WriteHead(ms , 'tracks');
    TAnimrigMisc.WriteInteger(ms , state.tracksCount);
    // track
    for i:=0 to state.tracksCount-1 do
    begin
      inTrack(state.tracks[i]);
    end;
  end;

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

procedure TAnimrigStateSpineRenderer.SetMixDuration(duration: single);
begin
  if fstate = nil then exit;

  fstate.data.defaultMix := duration;
end;

end.
