Delphi移植笔记

【Spine】Spine Runtime for Delphi移植笔记(五),spineruntime

////////////////////////////////////////////////////////////////////////////////
//Generic delphi runtime v3.6 for Spine animation tool                        //
//Runtime port by cjk ([email protected])                                       //
////////////////////////////////////////////////////////////////////////////////

unit spine.core.atlas;

interface

uses
  System.Classes, System.SysUtils, System.Generics.Collections, System.TypInfo,
  System.Math, spine.types, spine.classes;

type
  TSpineAltas = class(IAtlas)
  private type
    TupleStringArray = array [0..3] of string;
  private
    FPages: TObjectList<TAtlasPage>;
    FRegions: TObjectDictionary<string, TAtlasRegion>;
    FTextureLoader: ITextureLoader;
    procedure Load(const AStream: TStream);
  public
    constructor Create(const AStream: TStream; const ATextureLoader: ITextureLoader);
    destructor Destroy; override;
  end;

implementation

{ TSpineAltas }

constructor TSpineAltas.Create(const AStream: TStream;
  const ATextureLoader: ITextureLoader);
begin
  inherited Create;
  FPages:= TObjectList<TAtlasPage>.Create;
  FRegions:= TObjectDictionary<string,TAtlasRegion>.Create([doOwnsValues]);
  FTextureLoader:= ATextureLoader;
  Load(AStream);
end;

destructor TSpineAltas.Destroy;
begin
  FPages.Free;
  FRegions.Free;
  inherited;
end;

procedure TSpineAltas.Load(const AStream: TStream);
var
  EndOf: Boolean;

  function ReadLn(): string;
  var
    b, b1: Byte;
  begin
    Result:= '';
    if AStream.Position = AStream.Size then
    begin
      EndOf:= True;
      Exit;
    end;
    //
    while AStream.Position < AStream.Size do
    begin
      {$Hints off}
      AStream.Read(b, 1);
      {$Hints on}
      if (b <> $D) and (b <> $A) then
        Result:= Result + AnsiChar(Chr(b))
      else
        Break;
    end;
    //
    if AStream.Position < AStream.Size then
      {$Hints off}
      AStream.Read(b1, 1)
      {$Hints on}
    else Exit;
    //
    while ((b1 = $D) or (b1 = $A)) and (b1 <> b) and (AStream.Position < AStream.Size) do
      AStream.Read(b1, 1);
    //
    if AStream.Position < AStream.Size then
      AStream.Position:= AStream.Position - 1;
  end;

  function ReadTuple(out ATuple: TupleStringArray): Integer;
  var
    ln: string;
    colon, i, lastMatch, comma: Integer;
  begin
    ln:= Trim(ReadLn);
    colon:= ln.IndexOf(':');
    if colon = -1 then raise Exception.CreateFmt('Invalid line:%s',[ln]);
    lastMatch:= colon + 1;
    for i:= 0 to 2 do
    begin
      comma:= ln.IndexOf(',', lastMatch);
      if comma = -1 then break;
      ATuple[i]:= ln.Substring(lastMatch, comma - lastMatch).Trim;
      lastMatch:= comma + 1;
    end;
    ATuple[i]:= ln.Substring(lastMatch).Trim;
    result:= i + 1;
  end;

  function ReadValue: string;
  var
    ln: string;
    colon, i, lastMatch, comma: Integer;
  begin
    ln:= Trim(ReadLn);
    colon:= ln.IndexOf(':');
    if colon = -1 then raise Exception.CreateFmt('Invalid line:%s',[ln]);
        result:= ln.Substring(colon + 1).Trim;
  end;

var
  Page: TAtlasPage;
  Region: TAtlasRegion;
  Line, Direction: string;
  Tuple: TupleStringArray;
  i: Integer;  
begin
  EndOf:= False;
  Page:= nil;
  Region:= nil;
  while not EndOf do
  begin
    Line:= Trim(ReadLn);
    if Length(Line) > 0 then
    begin
      if Page = nil then
      begin
        Page:= TAtlasPage.Create;
        Page.Name:= Line;
        //
        if ReadTuple(Tuple) = 2 then
        begin
          Page.Width := Tuple[0].ToInteger;
                    Page.Height:= Tuple[1].ToInteger;
                    ReadTuple(Tuple);
                end;
        Page.Format:= TPageFormat(GetEnumValue(TypeInfo(TPageFormat), 'pf'+Tuple[0]));
        //
        ReadTuple(Tuple);
        Page.MinFilter:= TPageTextureFilter(GetEnumValue(TypeInfo(TPageTextureFilter), 'ptf'+Tuple[0])); 
        Page.MagFilter:= TPageTextureFilter(GetEnumValue(TypeInfo(TPageTextureFilter), 'ptf'+Tuple[1]));
        //
        Direction:= ReadValue;
        Page.WrapU:= TPageTextureWrap.ptwClampToEdge;
        Page.WrapV:= TPageTextureWrap.ptwClampToEdge;
        if Direction = 'x' then
          Page.WrapU:= TPageTextureWrap.ptwRepeat
        else if Direction = 'y' then
          Page.WrapV:= TPageTextureWrap.ptwRepeat
        else if Direction = 'xy' then
        begin
          Page.WrapU:= TPageTextureWrap.ptwRepeat;
          Page.WrapV:= TPageTextureWrap.ptwRepeat;
        end;
        //
        FTextureLoader.LoadTexture(Page, Line);
        FPages.Add(Page);
      end else
      begin
        Region:= TAtlasRegion.Create;
        Region.Name:= Line; 
        Region.Rotate:= ReadValue.ToBoolean;
              ReadTuple(Tuple);
        Region.X:= Tuple[0].ToInteger;
        Region.Y:= Tuple[1].ToInteger;
                ReadTuple(Tuple);
        Region.Width := Abs(Tuple[0].ToInteger);
        Region.Height:= Abs(Tuple[1].ToInteger);
        Region.U:= Region.X / Page.Width;
        Region.V:= Region.Y / Page.Height;
        if Region.Rotate then
        begin
          Region.U2:= (Region.X + Region.Height) / Page.Width;
          Region.V2:= (Region.Y + Region.Width) / Page.Height; 
        end else
        begin
          Region.U2:= (Region.X + Region.Width) / Page.Width;
          Region.V2:= (Region.Y + Region.Height) / Page.Height; 
        end;
        //
        if ReadTuple(Tuple) = 4 then
        begin        
          SetLength(Region.Splits, 4);
          Region.Splits[0]:= Tuple[0].ToInteger;
          Region.Splits[1]:= Tuple[1].ToInteger;
          Region.Splits[2]:= Tuple[2].ToInteger;
          Region.Splits[3]:= Tuple[3].ToInteger;
          if ReadTuple(Tuple) = 4 then
          begin
            SetLength(Region.Pads, 4);
            Region.Pads[0]:= Tuple[0].ToInteger;
            Region.Pads[1]:= Tuple[1].ToInteger;
            Region.Pads[2]:= Tuple[2].ToInteger;
            Region.Pads[3]:= Tuple[3].ToInteger;            
            ReadTuple(Tuple);
          end;          
        end;
        Region.OriginalWidth := Tuple[0].ToInteger;
        Region.OriginalHeight:= Tuple[1].ToInteger;
        //
        ReadTuple(Tuple);
        Region.OffsetX:= Tuple[0].ToInteger;
        Region.OffsetY:= Tuple[1].ToInteger;
        Region.Index:= ReadValue.ToInteger;
        FRegions.Add(Line,Region);
      end;
    end else
    begin
      Page:= nil;
      Region:= nil;
    end;
  end;
end;

end.

www.5929.com,图集(.atlas)的分析单元,这一个单元相比独立,参谋了部分别的代码。

www.5929.com 1

Delphi移植笔记。 Runtime for
Delphi移植笔记(五),spineruntime //
//////////////////////////////////////////////////////////////////////////////
// Generic delphi runtime…

【Spine】Spine Runtime for Delphi移植笔记(七),spineruntime

////////////////////////////////////////////////////////////////////////////////
//Generic delphi runtime v3.6 for Spine animation tool                        //
//Runtime port by cjk ([email protected])                                       //
////////////////////////////////////////////////////////////////////////////////

unit spine.core.skeleton;

interface

uses
  System.Classes, System.SysUtils, System.Generics.Collections, System.Math,
  spine.types, spine.classes, spine.data,
  spine.core.bone, spine.core.slot, spine.core.skin, spine.core.attachment, spine.core.constraint;

type
  TSpineSkeleton = class(ISkeleton)
  private
    FPathConstraints: TObjectList<TPathConstraint>;
    FIkConstraints: TObjectList<TIkConstraint>;
    FTransformConstraints: TObjectList<TTransformConstraint>;
    FBones: TObjectList<TSpineBone>;
    FSlots: TObjectList<TSpineSlot>;
    FDrawOrder: TList<TSpineSlot>;
    FUpdateCacheList: TList<IUpdateable>;
    FData: TSkeletonData;
    FUpdateCacheReset: TList<TSpineBone>;
    function GetRootBone: TSpineBone;
  private
    procedure SortIkConstraint(const AConstraint: TIkConstraint);
    procedure SortPathConstraint(const AConstraint: TPathConstraint);
    procedure SortTransformConstraint(const AConstraint: TTransformConstraint);
    procedure SortPathConstraintAttachment(const ASkin: TSpineSkin;
      const ASlotIndex: Integer; const ASlotBone: TSpineBone); overload;
    procedure SortPathConstraintAttachment(const AAttachment: IAttachment;
      const ASlotBone: TSpineBone); overload;
    procedure SortBone(const ABone: TSpineBone);
    procedure SortReset(const ABones: TObjectList<TSpineBone>);
  public
    Skin: TSpineSkin;
    R, G, B, A: Single;
    Time: Single;
    X, Y: Single;
    FlipX, FlipY: Boolean;
    property Data: TSkeletonData read FData;
    property Bones: TObjectList<TSpineBone> read FBones;
    property UpdateCacheList: TList<IUpdateable> read FUpdateCacheList;
    property Slots: TObjectList<TSpineSlot> read FSlots;
    property DrawOrder: TList<TSpineSlot> read FDrawOrder;
    property IkConstraints: TObjectList<TIkConstraint> read FIkConstraints;
    property PathConstraints: TObjectList<TPathConstraint> read FPathConstraints;
    property TransformConstraints: TObjectList<TTransformConstraint> read FTransformConstraints;
    property RootBone: TSpineBone read GetRootBone;
  public
    constructor Create(const AData: TSkeletonData);
    destructor Destroy; override;

    procedure UpdateCache;
    procedure UpdateWorldTransform;
    procedure SetToSetupPose;
    procedure SetBonesToSetupPose;
    procedure SetSlotsToSetupPose;
    procedure SetSkin(const ASkinName: string); overload;
    procedure SetSkin(const ASkin: TSpineSkin); overload;
    procedure SetAttachment(const ASlotName, AAttachmentName: string);
    procedure Update(const ADelta: Single);
    procedure GetBounds(out oX, oY, oWidth, oHeight: Single; var AVertexBuffer: TArray<Single>);
    function GetAttachment(const ASlotName, AAttachmentName: string): IAttachment; overload;
    function GetAttachment(const ASlotIndex: Integer; const AAttachmentName: string): IAttachment; overload;
    function FindBone(const ABoneName: string): TSpineBone;
    function FindBoneIndex(const ABoneName: string): Integer;
    function FindSlot(const ASlotName: string): TSpineSlot;
    function FindSlotIndex(const ASlotName: string): Integer;
    function FindIkConstraint(const AConstraintName: string): TIkConstraint;
    function FindTransformConstraint(const AConstraintName: string): TTransformConstraint;
    function FindPathConstraint(const AConstraintName: string): TPathConstraint;
  end;

implementation

{ TSpineSkeleton }

function TSpineSkeleton.GetRootBone: TSpineBone;
begin
  result:= nil;
  if FBones.Count > 0 then
    result:= FBones.Items[0];
end;

constructor TSpineSkeleton.Create(const AData: TSkeletonData);
var
  lBoneData: TBoneData;
  lParentBone, lBone: TSpineBone;
  lSlotData: TSlotData;
  lSlot: TSpineSlot;
  lIkData  : TIkConstraintData;
  lTfData  : TTransformConstraintData;
  lPathData: TPathConstraintData;
begin
  inherited Create;
  R:= 1; G:= 1; B:= 1; A:= 1;
  FUpdateCacheList:= TList<IUpdateable>.Create;
  FUpdateCacheReset:= TList<TSpineBone>.Create;
  FDrawOrder:= TList<TSpineSlot>.Create;
  FBones:= TObjectList<TSpineBone>.Create;
  FSlots:= TObjectList<TSpineSlot>.Create;
  FIkConstraints:= TObjectList<TIkConstraint>.Create;
  FPathConstraints:= TObjectList<TPathConstraint>.Create;
  FTransformConstraints:= TObjectList<TTransformConstraint>.Create;
  if not Assigned(AData) then raise Exception.Create('data cannot be null.');
  FData:= AData;
  for lBoneData in FData.BoneDatas do
  begin
    if not Assigned(lBoneData.Parent) then
      FBones.Add(TSpineBone.Create(lBoneData, Self, nil))
    else
    begin
      lParentBone:= FBones.Items[lBoneData.Parent.Index];
      lBone:= TSpineBone.Create(lBoneData, Self, lParentBone);
      FBones.Add(lBone);
    end;
  end;
  for lSlotData in FData.SlotDatas do
  begin
    lBone:= FBones.Items[lSlotData.BoneData.Index];
    lSlot:= TSpineSlot.Create(lSlotData, lBone);
    FSlots.Add(lSlot);
    FDrawOrder.Add(lSlot);
  end;
  for lIkData in FData.IkConstraintDatas do
    FIkConstraints.Add(TIkConstraint.Create(lIkData, Self));
  for lTfData in FData.TransformConstraintDatas do
    FTransformConstraints.Add(TTransformConstraint.Create(lTfData, Self));
  for lPathData in FData.PathConstraintDatas do
    FPathConstraints.Add(TPathConstraint.Create(lPathData, Self));
  //
  UpdateCache();
  UpdateWorldTransform();
end;

destructor TSpineSkeleton.Destroy;
begin
  FUpdateCacheReset.Free;
  FBones.Free;
  FUpdateCacheList.Free;
  FSlots.Free;
  FDrawOrder.Free;
  FIkConstraints.Free;
  FPathConstraints.Free;
  FTransformConstraints.Free;
  inherited;
end;

procedure TSpineSkeleton.UpdateCache;
var
  i, lConstraintCount, j: Integer;
  lFound: Boolean;
begin
  FUpdateCacheList.Clear;
  FUpdateCacheReset.Clear;
  for i:= 0 to FBones.Count -1 do
    FBones.Items[i].Sorted:= False;
  lConstraintCount:= FIkConstraints.Count +
                     FTransformConstraints.Count +
                     FPathConstraints.Count;
  lFound:= False;
  for i:= 0 to lConstraintCount -1 do
  begin
    for j:= 0 to FIkConstraints.Count -1 do
    begin
      if FIkConstraints[j].Data.Order = j then
      begin
        SortIkConstraint(FIkConstraints[j]);
        lFound:= True;
        break;
      end;
    end;
    if not lFound then
    begin
      for j:= 0 to FTransformConstraints.Count -1 do
      begin
        if FTransformConstraints[j].Data.Order = j then
        begin
          SortTransformConstraint(FTransformConstraints[j]);
          lFound:= True;
          break;
        end;
      end;
    end;
    if not lFound then
    begin
      for j:= 0 to FPathConstraints.Count -1 do
      begin
        if FPathConstraints[j].Data.Order = j then
        begin
          SortPathConstraint(FPathConstraints[j]);
          lFound:= True;
          break;
        end;
      end;
    end;
  end;
  //
  for i:= 0 to FBones.Count -1 do
    SortBone(FBones.Items[i]);
end;

procedure TSpineSkeleton.SortIkConstraint(const AConstraint: TIkConstraint);
var
  lChild: TSpineBone;
begin
  SortBone(AConstraint.Target);
  SortBone(AConstraint.Bones.Items[0]);
  if AConstraint.Bones.Count > 1 then
  begin
    lChild:= AConstraint.Bones.Last;
    if not FUpdateCacheList.Contains(lChild) then
      FUpdateCacheReset.Add(lChild);
  end;
  FUpdateCacheList.Add(AConstraint);
    SortReset(AConstraint.Bones.Items[0].Children);
  AConstraint.Bones.Last.Sorted:= True;
end;

procedure TSpineSkeleton.SortPathConstraint(const AConstraint: TPathConstraint);
var
  lSlot: TSpineSlot;
  lSlotIndex, i: Integer;
  lSlotBone: TSpineBone;
begin
  lSlot:= AConstraint.Target;
  lSlotIndex:= lSlot.Data.Index;
  lSlotBone := lSlot.Bone;
  if Assigned(Skin) then
    SortPathConstraintAttachment(Skin, lSlotIndex, lSlotBone);
  if Assigned(FData.DefaultSkin) and not FData.DefaultSkin.Equals(Skin) then
    SortPathConstraintAttachment(FData.DefaultSkin, lSlotIndex, lSlotBone);
  for i:= 0 to FData.Skins.Count -1 do
    SortPathConstraintAttachment(FData.Skins.Items[i], lSlotIndex, lSlotBone);
  if (lSlot.Attachment is TPathAttachment) then
    SortPathConstraintAttachment(lSlot.Attachment, lSlotBone);
  for i:= 0 to AConstraint.Bones.Count -1 do
    SortBone(AConstraint.Bones.Items[i]);
  //
  FUpdateCacheList.Add(AConstraint);
  for i:= 0 to AConstraint.Bones.Count -1 do
    SortReset(AConstraint.Bones.Items[i].Children);
  for i:= 0 to AConstraint.Bones.Count -1 do
    AConstraint.Bones.Items[i].Sorted:= True;
end;

procedure TSpineSkeleton.SortTransformConstraint(const AConstraint: TTransformConstraint);
var
  i: Integer;
  lChild: TSpineBone;
begin
  SortBone(AConstraint.Target);
  if AConstraint.Data.Local then
  begin
    for i:= 0 to AConstraint.Bones.Count -1 do
    begin
      lChild:= AConstraint.Bones.Items[i];
      SortBone(lChild.Parent);
      if not FUpdateCacheList.Contains(lChild) then
        FUpdateCacheReset.Add(lChild);
    end;
  end else
  begin
    for i:= 0 to AConstraint.Bones.Count -1 do
      SortBone(AConstraint.Bones.Items[i]);
  end;
  FUpdateCacheList.Add(AConstraint);
  for i:= 0 to AConstraint.Bones.Count -1 do
    SortReset(AConstraint.Bones.Items[i].Children);
  for i:= 0 to AConstraint.Bones.Count -1 do
    AConstraint.Bones.Items[i].Sorted:= True;
end;

procedure TSpineSkeleton.SortPathConstraintAttachment(const ASkin: TSpineSkin;
  const ASlotIndex: Integer; const ASlotBone: TSpineBone);
var
  lPair: TPair<TAttachmentKeyTuple,IAttachment>;
begin
  for lPair in Skin.Attachments do
  begin
    if lPair.Key.SlotIndex = ASlotIndex then
      SortPathConstraintAttachment(lPair.Value, ASlotBone);
  end;
end;

procedure TSpineSkeleton.SortPathConstraintAttachment(
  const AAttachment: IAttachment; const ASlotBone: TSpineBone);
var
  lPathBones: TArray<Integer>;
  i, n, nn, boneCount: Integer;
begin
  if not (AAttachment is TPathAttachment) then exit;
  //
  lPathBones:= TPathAttachment(AAttachment).Bones;
  if Length(lPathBones) = 0 then
    SortBone(ASlotBone)
  else
  begin
    i:= 0;
    n:= Length(lPathBones);
    while i < n do
    begin
            boneCount:= lPathBones[i + 1];
            i:= i + 1;
            nn:= i + boneCount;
            while i < nn do
      begin
                SortBone(FBones.Items[lPathBones[i + 1]]);
                i:= i + 1;
            end
    end;
  end;
end;

procedure TSpineSkeleton.SortBone(const ABone: TSpineBone);
begin
  if ABone.Sorted then exit;
  //
  if Assigned(ABone.Parent) then
    SortBone(ABone.Parent);
  ABone.Sorted:= True;
  FUpdateCacheList.Add(ABone);
end;

procedure TSpineSkeleton.SortReset(const ABones: TObjectList<TSpineBone>);
var
  i: Integer;
  lBone: TSpineBone;
begin
  for i:= 0 to ABones.Count -1 do
  begin
    lBone:= ABones.Items[i];
    if lBone.Sorted then
      SortReset(lBone.Children);
    lBone.Sorted:= False;
  end;
end;

procedure TSpineSkeleton.UpdateWorldTransform;
var
  i: Integer;
  lBone: TSpineBone;
begin
  for i:= 0 to FUpdateCacheReset.Count -1 do
  begin
    lBone:= FUpdateCacheReset.Items[i];
    lBone.AppliedX:= lBone.X;
    lBone.AppliedY:= lBone.Y;
    lBone.AppliedRotation:= lBone.Rotation;
    lBone.AppliedScaleX:= lBone.ScaleX;
    lBone.AppliedScaleY:= lBone.ScaleY;
    lBone.AppliedShearX:= lBone.ShearX;
    lBone.AppliedShearY:= lBone.ShearY;
    lBone.AppliedValid:= True;
  end;
  for i:= 0 to FUpdateCacheList.Count -1 do
    FUpdateCacheList.Items[i].Update;
end;

procedure TSpineSkeleton.SetToSetupPose;
begin
  SetBonesToSetupPose();
  SetSlotsToSetupPose();
end;

procedure TSpineSkeleton.SetBonesToSetupPose;
var
  i: Integer;
begin
  for i:= 0 to FBones.Count -1 do
    FBones.Items[i].SetToSetupPose;
  for i:= 0 to FIkConstraints.Count -1 do
  begin
    with FIkConstraints.Items[i] do
    begin
      BendDirection:= Data.BendDirection;
      Mix:= Data.Mix;
    end;
  end;
  for i:= 0 to FTransformConstraints.Count -1 do
  begin
    with FTransformConstraints.Items[i] do
    begin
      RotateMix:= Data.RotateMix;
      TranslateMix:= Data.TranslateMix;
      ScaleMix:= Data.ScaleMix;
      ShearMix:= Data.ShearMix;
    end;
  end;
  for i:= 0 to FPathConstraints.Count -1 do
  begin
    with FPathConstraints.Items[i] do
    begin
      Position:= Data.Position;
      Spacing:= Data.Spacing;
      RotateMix:= Data.RotateMix;
      TranslateMix:= Data.TranslateMix;
    end;
  end;
end;

procedure TSpineSkeleton.SetSlotsToSetupPose;
var
  i: Integer;
begin
  FDrawOrder.Clear;
  for i:= 0 to FSlots.Count -1 do
    FDrawOrder.Add(FSlots.Items[i]);
  for i:= 0 to FSlots.Count -1 do
    FSlots.Items[i].SetToSetupPose;
end;

function TSpineSkeleton.FindBone(const ABoneName: string): TSpineBone;
var
  i: Integer;
begin
  if ABoneName.Trim.IsEmpty then raise Exception.Create('boneName cannot be null.');
  for i:= 0 to FBones.Count -1 do
  begin
    if FBones.Items[i].Data.Name = ABoneName then
      exit(FBones.Items[i]);
  end;
  result:= nil;
end;

function TSpineSkeleton.FindBoneIndex(const ABoneName: string): Integer;
var
  i: Integer;
begin
  result:= -1;
  if ABoneName.Trim.IsEmpty then raise Exception.Create('boneName cannot be null.');
  for i:= 0 to FBones.Count -1 do
  begin
    if FBones.Items[i].Data.Name = ABoneName then
      exit(i);
  end;
end;

function TSpineSkeleton.FindSlot(const ASlotName: string): TSpineSlot;
var
  i: Integer;
begin
  result:= nil;
  if ASlotName.Trim.IsEmpty then raise Exception.Create('slotName cannot be null.');
  for i:= 0 to FSlots.Count -1 do
  begin
    if FSlots.Items[i].Data.Name = ASlotName then
      exit(FSlots.Items[i]);
  end;
end;

function TSpineSkeleton.FindSlotIndex(const ASlotName: string): Integer;
var
  i: Integer;
begin
  result:= -1;
  if ASlotName.Trim.IsEmpty then raise Exception.Create('slotName cannot be null.');
  for i:= 0 to FSlots.Count -1 do
  begin
    if FSlots.Items[i].Data.Name = ASlotName then
      exit(i);
  end;
end;

procedure TSpineSkeleton.SetSkin(const ASkinName: string);
var
  lSkin: TSpineSkin;
begin
  lSkin:= FData.FindSkin(ASkinName);
  if not Assigned(lSkin) then raise Exception.CreateFmt('Skin not found: %s',[ASkinName]);
    SetSkin(lSkin);
end;

procedure TSpineSkeleton.SetSkin(const ASkin: TSpineSkin);
var
  i: Integer;
  lAttachmentName: string;
  lAttachment: IAttachment;
begin
  if Assigned(Self.Skin) then
    ASkin.AttachAll(Self, Self.Skin)
  else
  begin
    for i:= 0 to FSlots.Count -1 do
    begin
      lAttachmentName:= FSlots.Items[i].Data.AttachmentName;
      if not lAttachmentName.Trim.IsEmpty then
      begin
        lAttachment:= ASkin.GetAttachment(i, lAttachmentName);
        if Assigned(lAttachment) then
          FSlots.Items[i].Attachment:= lAttachment;
      end;
    end;
  end;
  Self.Skin:= ASkin;
end;

function TSpineSkeleton.GetAttachment(const ASlotName,
  AAttachmentName: string): IAttachment;
begin
  result:= GetAttachment(FData.FindSlotIndex(ASlotName), AAttachmentName);
end;

function TSpineSkeleton.GetAttachment(const ASlotIndex: Integer;
  const AAttachmentName: string): IAttachment;
var
  lAttachment: IAttachment;
begin
  result:= nil;
  if AAttachmentName.Trim.IsEmpty then raise Exception.Create('attachmentName cannot be null.');
  if Assigned(Self.Skin) then
  begin
    lAttachment:= Self.Skin.GetAttachment(ASlotIndex, AAttachmentName);
    if Assigned(lAttachment) then exit(lAttachment);
  end;
  if Assigned(FData.DefaultSkin) then
    result:= FData.DefaultSkin.GetAttachment(ASlotIndex, AAttachmentName);
end;

procedure TSpineSkeleton.SetAttachment(const ASlotName,
  AAttachmentName: string);
var
  i: Integer;
  lSlot: TSpineSlot;
  lAttachment: IAttachment;
begin
  if ASlotName.Trim.IsEmpty then raise Exception.Create('slotName cannot be null.');
  for i:= 0 to FSlots.Count -1 do
  begin
    lSlot:= FSlots.Items[i];
    if lSlot.Data.Name.Equals(ASlotName) then
    begin
      lAttachment:= nil;
      if not AAttachmentName.Trim.IsEmpty then
      begin
        lAttachment:= Self.GetAttachment(i, AAttachmentName);
        if not Assigned(lAttachment) then
          raise Exception.CreateFmt('Attachment not found: %s, for slot: %s',[AAttachmentName,ASlotName]);
      end;
      lSlot.Attachment:= lAttachment;
      exit;
    end;
  end;
  raise Exception.CreateFmt('Slot not found: ',[ASlotName]);
end;

function TSpineSkeleton.FindIkConstraint(
  const AConstraintName: string): TIkConstraint;
var
  i: Integer;
  lConstraint: TIkConstraint;
begin
  result:= nil;
  if AConstraintName.Trim.IsEmpty then raise Exception.Create('constraintName cannot be null.');
  for i:= 0 to FIkConstraints.Count -1 do
  begin
    lConstraint:= FIkConstraints.Items[i];
    if lConstraint.Data.Name.Equals(AConstraintName) then exit(lConstraint);
  end;
end;

function TSpineSkeleton.FindTransformConstraint(
  const AConstraintName: string): TTransformConstraint;
var
  i: Integer;
  lConstraint: TTransformConstraint;
begin
  result:= nil;
  if AConstraintName.Trim.IsEmpty then raise Exception.Create('constraintName cannot be null.');
  for i:= 0 to FTransformConstraints.Count -1 do
  begin
    lConstraint:= FTransformConstraints.Items[i];
    if lConstraint.Data.Name.Equals(AConstraintName) then exit(lConstraint);
  end;
end;

function TSpineSkeleton.FindPathConstraint(
  const AConstraintName: string): TPathConstraint;
var
  i: Integer;
  lConstraint: TPathConstraint;
begin
  result:= nil;
  if AConstraintName.Trim.IsEmpty then raise Exception.Create('constraintName cannot be null.');
  for i:= 0 to FPathConstraints.Count -1 do
  begin
    lConstraint:= FPathConstraints.Items[i];
    if lConstraint.Data.Name.Equals(AConstraintName) then exit(lConstraint);
  end;
end;

procedure TSpineSkeleton.Update(const ADelta: Single);
begin
  Self.Time:= Self.Time + ADelta;
end;

procedure TSpineSkeleton.GetBounds(out oX, oY, oWidth, oHeight: Single;
  var AVertexBuffer: TArray<Single>);
var
  lTemp, lVertices: TArray<Single>;
  i, lVerticesLength, j, n: Integer;
  lMinX, lMinY, lMaxX, lMaxY, lVX, lVY: Single;
  lSlot: TSpineSlot;
  lAttachment: IAttachment;
  lRegionAttachment: TRegionAttachment;
  lMeshAttachment: TMeshAttachment;
begin
  lTemp:= AVertexBuffer;
  if Length(lTemp) = 0 then
    SetLength(lTemp, 8);
  lMinX:= Integer.MaxValue;
  lMinY:= Integer.MaxValue;
  lMaxX:= Integer.MinValue;
  lMaxY:= Integer.MinValue;
  for i:= 0 to FDrawOrder.Count -1 do
  begin
    lSlot:= FDrawOrder.Items[i];
    lVerticesLength:= 0;
    SetLength(lVertices, 0);
    lAttachment:= lSlot.Attachment;
    lRegionAttachment:= lAttachment as TRegionAttachment;
    if Assigned(lRegionAttachment) then
    begin
      lVerticesLength:= 8;
      lVertices:= lTemp;
      if Length(lVertices) < 8 then
      begin
        SetLength(lTemp, 8);
        lVertices:= lTemp;
        lRegionAttachment.ComputeWorldVertices(lSlot.Bone, lTemp, 0);
      end;
    end else
    begin
      lMeshAttachment:= lAttachment as TMeshAttachment;
      if Assigned(lMeshAttachment) then
      begin
        lVerticesLength:= lMeshAttachment.WorldVerticesLength;
        lVertices:= lTemp;
        if Length(lVertices) < lVerticesLength then
        begin
          SetLength(lTemp, lVerticesLength);
          lVertices:= lTemp;
          lMeshAttachment.ComputeWorldVertices(lSlot, 0, lVerticesLength, lTemp, 0);
        end;
      end;
    end;
    n:= Length(lVertices);
    if n > 0 then
    begin
      j:= 0;
      while j < n do
      begin
        lVX:= lVertices[j];
        lVY:= lVertices[j+1];
        lMinX:= Min(lMinX, lVX);
        lMinY:= Min(lMinY, lVY);
        lMaxX:= Max(lMaxX, lVX);
        lMaxY:= Max(lMaxY, lVY);
        j:= j + 2;
      end;
    end;
  end;
  oX:= lMinX;
  oY:= lMinY;
  oWidth := lMaxX - lMinX;
  oHeight:= lMaxY - lMinY;
  AVertexBuffer:= lTemp;
end;

end.

Delphi移植笔记。龙骨模块,粗粗检查了一晃,没什么难点。

那边说一下,那些库的移植(其实好多是翻译,因为众多代码笔者一直不领悟,可是不影响笔者翻译正是了)是基于C#的,C#是污源回收机制,移到delphi的话,对象的开创、释放将在很静心,不然很轻松有内部存款和储蓄器败露。

那边防检查查重视正是反省对象的释放,比相当多是TList和TObjectList的挑三拣四主题材料。

Runtime for
Delphi移植笔记(七),spineruntime //
//////////////////////////////////////////////////////////////////////////////
// Generic delphi runtime…

【Spine】Spine Runtime for Delphi移植笔记(四),spineruntime

////////////////////////////////////////////////////////////////////////////////
//Generic delphi runtime v3.6 for Spine animation tool                        //
//Runtime port by cjk ([email protected])                                       //
////////////////////////////////////////////////////////////////////////////////

unit spine.classes;

interface

uses
  System.Classes, System.SysUtils, System.Generics.Collections, spine.types;

type
  THashSet<T> = class
  private
  public
    procedure Clear;
    function Add(const Item: T): Boolean;
  end;

  TExposedList<T> = class(TList<T>)
  private
    FVersion: Integer;
    procedure ClearEx(const AIndex, ALen: Integer); overload;
    procedure Shift(const AStart, ADelta: Integer);
  public
    procedure EnsureCapacity(const AMin: Integer);
    procedure ClearEx(const AClearArray: Boolean = True); overload;
    function  Resize(const ANewSize: Integer): TExposedList<T>;
    procedure RemoveAt(const AIndex: Integer);
    procedure CopyFrom(const ASource: TExposedList<T>); overload;
    procedure CopyFrom(const ASource: TExposedList<T>;
      const ASourceStart, ATargetStart, ACount: Integer); overload;
  end;

  TSpineTexture = class(TObject)

  end;

  TAtlasPage = class(TObject)
  public
    Name: AnsiString;
    Format: TPageFormat;
    MinFilter: TPageTextureFilter;
    MagFilter: TPageTextureFilter;
    WrapU: TPageTextureWrap;
    WrapV: TPageTextureWrap;
    Texture: TSpineTexture;
    Width: Integer;
    Height: Integer;
    end;

  TAtlasRegion = class(TObject)
    Page: TAtlasPage;
    Name: AnsiString;
    X: Integer;
    Y: Integer;
    Width: Integer;
    Height: Integer;
    U: Single;
    V: Single;
    U2: Single;
    V2: Single;
    OffsetX: Single;
    OffsetY: Single;
    OriginalWidth: Integer;
    OriginalHeight: Integer;
    Index: Integer;
    Rotate: Boolean;
    Splits: array of Integer;
    Pads: array of Integer;
    end;

  ITextureLoader = class abstract
    function LoadTexture(const Page: TAtlasPage; const TextureName: string): TSpineTexture; virtual; abstract;
  end;

  IUpdateable = class abstract
    procedure Update; virtual; abstract;
  end;

  IAtlas = class abstract end;
  ISkeleton = class abstract end;
  IBone = class(IUpdateable) end;
  ISlot = class abstract end;
  IEvent = class abstract end;
  IAnimation = class abstract end;
  IAnimationState = class abstract end;
  ISkin = class abstract end;

  IAttachment = class abstract
  protected
    function GetID: Integer; virtual; abstract;
    function GetName: string; virtual; abstract;
  public
    property ID: Integer read GetID;
    property Name: string read GetName;
  end;

  IConstraint = class(IUpdateable)
  protected
    function GetOrder: Integer; virtual; abstract;
  public
    property Order: Integer read GetOrder;
  end;

  ITimeline = class abstract
  protected
    function GetPropertyId: Integer; virtual; abstract;
  public
    procedure Apply(const ASkeleton: ISkeleton;
                    const ALastTime, ATime: Single;
                    const AEvents: TList<IEvent>;
                    const AAlpha: Single;
                    const APose: TMixPose;
                    const ADirection: TMixDirection); virtual; abstract;
    property PropertyId: Integer read GetPropertyId;
  end;

implementation

end.

 

类定义,I
开头的抽象类大大多只是为着定义一下,因为要求单元间交叉援引,不过又不想都以概念为TObject,写接口又没须求,只可以那样写了。

Runtime for
Delphi移植笔记(四),spineruntime //
//////////////////////////////////////////////////////////////////////////////
// Generic delphi runtime…

【Spine】Spine Runtime for Delphi移植笔记(六),spineruntime

////////////////////////////////////////////////////////////////////////////////
//Generic delphi runtime v3.6 for Spine animation tool                        //
//Runtime port by cjk ([email protected])                                       //
////////////////////////////////////////////////////////////////////////////////

unit spine.core.skeleton.binary;

interface

uses
  System.Classes, System.SysUtils, System.Generics.Collections, System.Math,
  spine.types, spine.classes, spine.data,
  spine.core.atlas, spine.core.bone, spine.core.slot, spine.core.skin,
  spine.core.attachment, spine.core.constraint, spine.core.skeleton,
  spine.core.animation.timeline, spine.core.skeleton.json, spine.core.event,
  spine.core.animation;

type
  SByte = ShortInt;
  TSpineSkeletonBinary = class
  public const
        BONE_ROTATE = 0;
        BONE_TRANSLATE = 1;
        BONE_SCALE = 2;
        BONE_SHEAR = 3;

        SLOT_ATTACHMENT = 0;
        SLOT_COLOR = 1;
        SLOT_TWO_COLOR = 2;

        PATH_POSITION = 0;
        PATH_SPACING = 1;
        PATH_MIX = 2;

        CURVE_LINEAR = 0;
        CURVE_STEPPED = 1;
        CURVE_BEZIER = 2;
  private type
        TVertices = record
            Bones: TArray<Integer>;
            Vertices: TArray<Single>;
        end;
  private
    FLinkedMeshes: TObjectList<TSkeletonJson.TLinkedMesh>;
    FAttachmentLoader: TAttachmentLoader;
    FBuffer: array [0..31] of Byte;
    function ReadByte(const AStream: TStream): Integer;
    function ReadString(const AStream: TStream): string;
    function ReadInt(const AStream: TStream): Integer;
    function ReadFloat(const AStream: TStream): Single;
    function ReadBoolean(const AStream: TStream): Boolean;
    function ReadSByte(const AStream: TStream): SByte;
    function ReadVarInt(const AStream: TStream; const AOptimizePositive: Boolean): Integer;
    function ReadShortArray(const AStream: TStream): TArray<Integer>;
    function ReadFloatArray(const AStream: TStream; const ACount: Integer;
      const AScale: Single): TArray<Single>;
    function ReadVertices(const AStream: TStream; const AVertexCount: Integer): TVertices;
    function ReadAttachment(const AStream: TStream; const ASkeletonData: TSkeletonData;
      const ASkin: TSpineSkin; const ASlotIndex: Integer;
      const AAttachmentName: string; const ANonessential: Boolean): IAttachment;
    function ReadSkin (const AStream: TStream; const ASkeletonData: TSkeletonData;
      const ASkinName: string; const ANonessential: Boolean): TSpineSkin;
    procedure ReadCurve(const AStream: TStream; const AFrameIndex: Integer; const ATimeline: TCurveTimeline);
    procedure ReadAnimation(const AName: string; const AStream: TStream; const ASkeletonData: TSkeletonData);
    class procedure ReadFully(const AStream: TStream; var ABuffer: TArray<Byte>;
      const AOffset: Integer; var ALength: Integer); static;
  public
    Scale: Single;
    constructor Create(const AAtlasArray: TArray<TSpineAtlas>); overload;
    constructor Create(const AAttachmentLoader: TAttachmentLoader); overload;
    destructor Destroy; override;

    function ReadSkeletonData(const ASkelFile: string): TSkeletonData; overload;
    function ReadSkeletonData(const ASkelStream: TStream): TSkeletonData; overload;
  end;

implementation

{ TSpineSkeletonBinary }

constructor TSpineSkeletonBinary.Create(const AAtlasArray: TArray<TSpineAtlas>);
begin
  FAttachmentLoader:= TAtlasAttachmentLoader.Create(AAtlasArray);
  Create(FAttachmentLoader);
end;

constructor TSpineSkeletonBinary.Create(
  const AAttachmentLoader: TAttachmentLoader);
begin
  inherited Create;
  if not Assigned(AAttachmentLoader) then raise Exception.Create('attachmentLoader cannot be null.');
  FLinkedMeshes:= TObjectList<TSkeletonJson.TLinkedMesh>.Create;
  FAttachmentLoader:= AAttachmentLoader;
end;

destructor TSpineSkeletonBinary.Destroy;
begin
  FLinkedMeshes.Free;
  if Assigned(FAttachmentLoader) then FreeAndNil(FAttachmentLoader);  
  inherited;
end;

function TSpineSkeletonBinary.ReadSkeletonData(
  const ASkelFile: string): TSkeletonData;
var
  lStream: TFileStream;
begin
  lStream:= TFileStream.Create(ASkelFile, fmOpenRead);
  try
    result:= Self.ReadSkeletonData(lStream);
  finally
    lStream.Free;
  end;
end;

function TSpineSkeletonBinary.ReadSkeletonData(
  const ASkelStream: TStream): TSkeletonData;
var
  lScale: Single;
  lNonessential: Boolean;
  i, j, n, nn: Integer;
  lName: string;
  lBoneDataParent, lBoneData: TBoneData;
  lColor, lDarkColor: Integer;
  lSlotData: TSlotData;
  lIkConstraintData: TIkConstraintData;
  lTransformConstraintData: TTransformConstraintData;
  lPathConstraintData: TPathConstraintData;
  lDefaultSkin, lSkin: TSpineSkin;
  lLinkedMesh: TSkeletonJson.TLinkedMesh;
  lParentAttachment: IAttachment;
  lEventData: TEventData;
begin
  if not Assigned(ASkelStream) then raise Exception.Create('skelstream cannot be null.');
  result:= TSkeletonData.Create;
  result.Name:= ChangeFileExt(ExtractFileName(TFileStream(ASkelStream).FileName),'');
  result.Hash:= Self.ReadString(ASkelStream);
  result.Version:= Self.ReadString(ASkelStream);
  result.Width:= Self.ReadFloat(ASkelStream);
  result.Height:= Self.ReadFloat(ASkelStream);
  lNonessential:= Self.ReadBoolean(ASkelStream);
  if lNonessential then
  begin
    result.FPS:= Self.ReadFloat(ASkelStream);
    result.ImagesPath:= Self.ReadString(ASkelStream);
  end;

  // Bones.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lName:= Self.ReadString(ASkelStream);
    if i = 0 then
      lBoneDataParent:= nil
    else
      lBoneDataParent:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)];
    lBoneData:= TBoneData.Create(i, lName, lBoneDataParent);
    lBoneData.Rotation:= Self.ReadFloat(ASkelStream);
    lBoneData.X:= Self.ReadFloat(ASkelStream) * Self.Scale;
    lBoneData.Y:= Self.ReadFloat(ASkelStream) * Self.Scale;
    lBoneData.ScaleX:= Self.ReadFloat(ASkelStream);
    lBoneData.ScaleY:= Self.ReadFloat(ASkelStream);
    lBoneData.ShearX:= Self.ReadFloat(ASkelStream);
    lBoneData.ShearY:= Self.ReadFloat(ASkelStream);
    lBoneData.Length:= Self.ReadFloat(ASkelStream) * Self.Scale;
    //lBoneData.TransformMode:= TTransformModes[Self.ReadVarInt(ASkelStream, True)];
    if lNonessential then Self.ReadInt(ASkelStream); // Skip bone color.
    result.BoneDatas.Add(lBoneData);
  end;

  // Slots.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lName:= Self.ReadString(ASkelStream);
    lBoneData:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)];
    lSlotData:= TSlotData.Create(i, lName, lBoneData);
    lColor:= Self.ReadInt(ASkelStream);
    lSlotData.R:= ((lColor and $ff) shr 24) / 255;
    lSlotData.G:= ((lColor and $00ff) shr 16) / 255;
    lSlotData.B:= ((lColor and $0000ff) shr 8) / 255;
    lSlotData.A:= (lColor and $000000ff) / 255;
    lDarkColor:= Self.ReadInt(ASkelStream); // 0x00rrggbb
    if lDarkColor <> -1 then
    begin
      lSlotData.HasSecondColor:= True;
      lSlotData.R2:= ((lDarkColor and $00ff) shr 16) / 255;
      lSlotData.G2:= ((lDarkColor and $0000ff) shr 8) / 255;
      lSlotData.B2:= (lDarkColor and $000000ff) / 255;
    end;
    lSlotData.AttachmentName:= Self.ReadString(ASkelStream);
    lSlotData.BlendMode:= TBlendMode(Self.ReadVarInt(ASkelStream, True));
    result.SlotDatas.Add(lSlotData);
  end;

  // IK constraints.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lIkConstraintData:= TIkConstraintData.Create(Self.ReadString(ASkelStream));
    lIkConstraintData.Order:= Self.ReadVarInt(ASkelStream, True);
    nn:= Self.ReadVarInt(ASkelStream, True);
    for j:= 0 to nn -1 do
      lIkConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]);
    lIkConstraintData.Target:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)];
    lIkConstraintData.Mix:= Self.ReadFloat(ASkelStream);
    lIkConstraintData.BendDirection:= Self.ReadSByte(ASkelStream);
    result.IkConstraintDatas.Add(lIkConstraintData);
  end;

  // Transform constraints.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lTransformConstraintData:= TTransformConstraintData.Create(Self.ReadString(ASkelStream));
    lTransformConstraintData.Order:= Self.ReadVarInt(ASkelStream, True);
    nn:= Self.ReadVarInt(ASkelStream, True);
    for j:= 0 to nn -1 do
      lTransformConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]);
    lTransformConstraintData.Target:= result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)];
    lTransformConstraintData.Local:= Self.ReadBoolean(ASkelStream);
    lTransformConstraintData.Relative:= Self.ReadBoolean(ASkelStream);
    lTransformConstraintData.OffsetRotation:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.OffsetX:= Self.ReadFloat(ASkelStream) * Self.Scale;
    lTransformConstraintData.OffsetY:= Self.ReadFloat(ASkelStream) * Self.Scale;
    lTransformConstraintData.OffsetScaleX:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.OffsetScaleY:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.OffsetShearY:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.RotateMix:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.TranslateMix:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.ScaleMix:= Self.ReadFloat(ASkelStream);
    lTransformConstraintData.ShearMix:= Self.ReadFloat(ASkelStream);
    result.TransformConstraintDatas.Add(lTransformConstraintData);
  end;

  // Path constraints
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lPathConstraintData:= TPathConstraintData.Create(Self.ReadString(ASkelStream));
    lPathConstraintData.Order:= Self.ReadVarInt(ASkelStream, True);
    nn:= Self.ReadVarInt(ASkelStream, True);
    for j:= 0 to nn -1 do
      lPathConstraintData.BoneDatas.Add(result.BoneDatas.Items[Self.ReadVarInt(ASkelStream, True)]);
    lPathConstraintData.Target:= result.SlotDatas.Items[Self.ReadVarint(ASkelStream, true)];
    lPathConstraintData.PositionMode:= TPositionMode(Self.ReadVarint(ASkelStream, true));
    lPathConstraintData.SpacingMode:= TSpacingMode(Self.ReadVarint(ASkelStream, true));
    lPathConstraintData.RotateMode:= TRotateMode(Self.ReadVarint(ASkelStream, true));
    lPathConstraintData.OffsetRotation:= Self.ReadFloat(ASkelStream);
    lPathConstraintData.Position:= Self.ReadFloat(ASkelStream);
    if lPathConstraintData.PositionMode = TPositionMode.pmFixed then
      lPathConstraintData.Position:= lPathConstraintData.Position * Self.Scale;
    lPathConstraintData.Spacing:= Self.ReadFloat(ASkelStream);
    if (lPathConstraintData.SpacingMode = TSpacingMode.smLength) or
       (lPathConstraintData.SpacingMode = TSpacingMode.smFixed) then
      lPathConstraintData.Spacing:= lPathConstraintData.Spacing * Self.Scale;
    lPathConstraintData.RotateMix:= Self.ReadFloat(ASkelStream);
    lPathConstraintData.TranslateMix:= Self.ReadFloat(ASkelStream);
    result.PathConstraintDatas.Add(lPathConstraintData);
  end;

  // Default skin.
  lDefaultSkin:= Self.ReadSkin(ASkelStream, result, 'default', lNonessential);
  if Assigned(lDefaultSkin) then
  begin
    result.DefaultSkin:= lDefaultSkin;
    result.Skins.Add(lDefaultSkin);
  end;

  // Skins.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
    result.Skins.Add(Self.ReadSkin(ASkelStream, result, Self.ReadString(ASkelStream), lNonessential));

  // Linked meshes.
  n:= FLinkedMeshes.Count;
  for i:= 0 to n -1 do
  begin
    lLinkedMesh:= FLinkedMeshes[i];
    if not Assigned(lLinkedMesh) then
      lSkin:= lDefaultSkin
    else
      lSkin:= result.FindSkin(lLinkedMesh.Skin);
    if not Assigned(lSkin) then raise Exception.CreateFmt('Skin not found: %s',[lLinkedMesh.Skin]);
    lParentAttachment:= lSkin.GetAttachment(lLinkedMesh.SlotIndex, lLinkedMesh.Parent);
    if not Assigned(lParentAttachment) then raise Exception.CreateFmt('Parent mesh not found: %s',[lLinkedMesh.Parent]);
    lLinkedMesh.Mesh.ParentMesh:= TMeshAttachment(lParentAttachment);
    lLinkedMesh.Mesh.UpdateUVs;
  end;
  FLinkedMeshes.Clear;

  // Events.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
  begin
    lEventData:= TEventData.Create(Self.ReadString(ASkelStream));
    lEventData.IntValue:= Self.ReadVarInt(ASkelStream, False);
    lEventData.FloatValue:= Self.ReadFloat(ASkelStream);
    lEventData.StringValue:= Self.ReadString(ASkelStream);
    result.EventDatas.Add(lEventData);
  end;

  // Animations.
  n:= Self.ReadVarInt(ASkelStream, True);
  for i:= 0 to n -1 do
    Self.ReadAnimation(Self.ReadString(ASkelStream), ASkelStream, result);

  result.BoneDatas.TrimExcess;
  result.SlotDatas.TrimExcess;
  result.Skins.TrimExcess;
  result.EventDatas.TrimExcess;
  result.Animations.TrimExcess;
  result.IkConstraintDatas.TrimExcess;
  result.TransformConstraintDatas.TrimExcess;
  result.PathConstraintDatas.TrimExcess;
end;

function TSpineSkeletonBinary.ReadByte(const AStream: TStream): Integer;
begin
  if AStream.Position + 1 > AStream.Size then exit(-1);
  AStream.Read(result, 1);
end;

function TSpineSkeletonBinary.ReadSkin(const AStream: TStream;
  const ASkeletonData: TSkeletonData; const ASkinName: string;
  const ANonessential: Boolean): TSpineSkin;
var
  lSlotCount, i, lSlotIndex, j, n: Integer;
  lName: string;
  lAttachment: IAttachment;
begin
  lSlotCount:= Self.ReadVarInt(AStream, True);
  if lSlotCount = 0 then exit(nil);
  result:= TSpineSkin.Create;
  for i:= 0 to lSlotCount -1 do
  begin
    lSlotIndex:= Self.ReadVarInt(AStream, True);
    n:= Self.ReadVarInt(AStream, True);
    for j:= 0 to n -1 do
    begin
      lName:= Self.ReadString(AStream);
      lAttachment:= Self.ReadAttachment(AStream, ASkeletonData, result, lSlotIndex, lName, ANonessential);
      if Assigned(lAttachment) then
        result.AddAttachment(lSlotIndex, lName, lAttachment);
    end;
  end;
end;

function TSpineSkeletonBinary.ReadAttachment(const AStream: TStream;
  const ASkeletonData: TSkeletonData; const ASkin: TSpineSkin;
  const ASlotIndex: Integer; const AAttachmentName: string;
  const ANonessential: Boolean): IAttachment;
var
  lName, lPath: string;
  lAttachmentType: TAttachmentType;
  lRotation, lX, lY, lScaleX, lScaleY, lWidth, lHeight: Single;
  lColor: Integer;
  lRegionAttachment: TRegionAttachment;
  lVertexCount: Integer;
  lVertices: TVertices;
  lBoxAttachment: TBoundingBoxAttachment;
  lUVs: TArray<Single>;
  lTriangles, lEdges: TArray<Integer>;
  lHullLength: Integer;
  lMeshAttachment: TMeshAttachment;
  lSkinName, lParentMeshName: string;
  lInheritDeform: Boolean;
  lClosed, lConstantSpeed: Boolean;
  lLengths: TArray<Single>;
  i: Integer;
  lPathAttachment: TPathAttachment;
  lPointAttachment: TPointAttachment;
  lEndSlotIndex: Integer;
  lClippingAttachment: TClippingAttachment;
begin
  lName:= Self.ReadString(AStream);
  if lName.Trim.IsEmpty then lName:= AAttachmentName;
  lAttachmentType:= TAttachmentType(Self.ReadByte(AStream));
  case lAttachmentType of
    TAttachmentType.atRegion:
      begin
        lPath:= Self.ReadString(AStream);
        lRotation:= Self.ReadFloat(AStream);
        lX:= Self.ReadFloat(AStream);
        lY:= Self.ReadFloat(AStream);
        lScaleX:= Self.ReadFloat(AStream);
        lScaleY:= Self.ReadFloat(AStream);
        lWidth:= Self.ReadFloat(AStream);
        lHeight:= Self.ReadFloat(AStream);
        lColor:= Self.ReadInt(AStream);
        if lPath.Trim.IsEmpty then lPath:= lName;
        //
        lRegionAttachment:= FAttachmentLoader.NewRegionAttachment(ASkin, lName, lPath);
        if not Assigned(lRegionAttachment) then exit(nil);
        lRegionAttachment.Path:= lPath;
        lRegionAttachment.X:= lX * Self.Scale;
        lRegionAttachment.Y:= lY * Self.Scale;
        lRegionAttachment.ScaleX:= lScaleX;
        lRegionAttachment.ScaleY:= lScaleY;
        lRegionAttachment.Rotation:= lRotation;
        lRegionAttachment.Width:= lWidth * Self.Scale;
        lRegionAttachment.Height:= lHeight * Self.Scale;
        lRegionAttachment.R:= ((lColor and $ff) shr 24) / 255;
        lRegionAttachment.G:= ((lColor and $00ff) shr 16) / 255;
        lRegionAttachment.B:= ((lColor and $0000ff) shr 8) / 255;
        lRegionAttachment.A:= ((lColor and $000000ff)) / 255;
        lRegionAttachment.UpdateOffset();
        exit(lRegionAttachment);
      end;
    TAttachmentType.atBoundingbox:
      begin
        lVertexCount:= Self.ReadVarint(AStream, True);
        lVertices:= Self.ReadVertices(AStream, lVertexCount);
        if ANonessential then Self.ReadInt(AStream);
        //
        lBoxAttachment:= FAttachmentLoader.NewBoundingBoxAttachment(ASkin, lName);
        if not Assigned(lBoxAttachment) then exit(nil);
        lBoxAttachment.WorldVerticesLength:= lVertexCount shl 1;
        SetLength(lBoxAttachment.Vertices, Length(lVertices.Vertices));
        SetLength(lBoxAttachment.Bones, Length(lVertices.Bones));
        TArray.Copy<Single>(lVertices.Vertices, lBoxAttachment.Vertices, 0, 0, Length(lVertices.Vertices));
        TArray.Copy<Integer>(lVertices.Bones, lBoxAttachment.Bones, 0, 0, Length(lVertices.Bones));
        exit(lBoxAttachment);
      end;
    TAttachmentType.atMesh:
      begin
        lPath:= Self.ReadString(AStream);
        lColor:= Self.ReadInt(AStream);
        lVertexCount:= Self.ReadVarInt(AStream, True);
        lUVs:= Self.ReadFloatArray(AStream, lVertexCount shl 1, 1);
        lTriangles:= Self.ReadShortArray(AStream);
        lVertices:= Self.ReadVertices(AStream, lVertexCount);
        lHullLength:= Self.ReadVarInt(AStream, True);
        lWidth:= 0;
        lHeight:= 0;
        if ANonessential then
        begin
          lEdges:= Self.ReadShortArray(AStream);
          lWidth:= Self.ReadFloat(AStream);
          lHeight:= Self.ReadFloat(AStream);
        end;
        if lPath.Trim.IsEmpty then lPath:= lName;
        lMeshAttachment:= FAttachmentLoader.NewMeshAttachment(ASkin, lName, lPath);
        if not Assigned(lMeshAttachment) then exit(nil);
        lMeshAttachment.Path:= lPath;
        lMeshAttachment.R:= ((lColor and $ff) shr 24) / 255;
        lMeshAttachment.G:= ((lColor and $00ff) shr 16) / 255;
        lMeshAttachment.B:= ((lColor and $0000ff) shr 8) / 255;
        lMeshAttachment.A:= ((lColor and $000000ff)) / 255;
        SetLength(lMeshAttachment.Vertices, Length(lVertices.Vertices));
        SetLength(lMeshAttachment.Bones, Length(lVertices.Bones));
        TArray.Copy<Single>(lVertices.Vertices, lMeshAttachment.Vertices, 0, 0, Length(lVertices.Vertices));
        TArray.Copy<Integer>(lVertices.Bones, lMeshAttachment.Bones, 0, 0, Length(lVertices.Bones));
        lMeshAttachment.WorldVerticesLength:= lVertexCount shl 1;
        SetLength(lMeshAttachment.Triangles, Length(lTriangles));
        SetLength(lMeshAttachment.RegionUVs, Length(lUVs));
        TArray.Copy<Integer>(lTriangles, lMeshAttachment.Triangles, 0, 0, Length(lTriangles));
        TArray.Copy<Single>(lUVs, lMeshAttachment.RegionUVs, 0, 0, Length(lUVs));
        lMeshAttachment.UpdateUVs();
        lMeshAttachment.HullLength:= lHullLength shl 1;
        if ANonessential then
        begin
          TArray.Copy<Integer>(lEdges, lMeshAttachment.Edges, 0, 0, Length(lEdges));
          lMeshAttachment.Width:= lWidth * Self.Scale;
          lMeshAttachment.Height:= lHeight * Self.Scale;
        end;
        exit(lMeshAttachment);
      end;
    TAttachmentType.atLinkedmesh:
      begin
        lPath:= Self.ReadString(AStream);
        lColor:= Self.ReadInt(AStream);
        lSkinName:= Self.ReadString(AStream);
        lParentMeshName:= Self.ReadString(AStream);
        lInheritDeform:= Self.ReadBoolean(AStream);
        lWidth:= 0;
        lHeight:= 0;
        if ANonessential then
        begin
          lWidth:= Self.ReadFloat(AStream);
          lHeight:= Self.ReadFloat(AStream);
        end;
        if lPath.Trim.IsEmpty then lPath:= lName;
        lMeshAttachment:= FAttachmentLoader.NewMeshAttachment(ASkin, lName, lPath);
        if not Assigned(lMeshAttachment) then exit(nil);
        lMeshAttachment.Path:= lPath;
        lMeshAttachment.R:= ((lColor and $ff) shr 24) / 255;
        lMeshAttachment.G:= ((lColor and $00ff) shr 16) / 255;
        lMeshAttachment.B:= ((lColor and $0000ff) shr 8) / 255;
        lMeshAttachment.A:= ((lColor and $000000ff)) / 255;
        lMeshAttachment.InheritDeform:= lInheritDeform;
        if ANonessential then
        begin
          lMeshAttachment.Width:= lWidth * Self.Scale;
          lMeshAttachment.Height:= lHeight * Self.Scale;
        end;
        FLinkedMeshes.Add(TSkeletonJson.TLinkedMesh.Create(lMeshAttachment, lSkinName, ASlotIndex, lParentMeshName));
        exit(lMeshAttachment);
      end;
    TAttachmentType.atPath:
      begin
        lClosed:= Self.ReadBoolean(AStream);
        lConstantSpeed:= Self.ReadBoolean(AStream);
        lVertexCount:= Self.ReadVarint(AStream, True);
        lVertices:= Self.ReadVertices(AStream, lVertexCount);
        SetLength(lLengths, System.Math.Floor(lVertexCount / 3));
        for i:= 0 to Length(lLengths) -1 do
          lLengths[i]:= Self.ReadFloat(AStream) * Self.Scale;
        if ANonessential then Self.ReadInt(AStream);
        //
        lPathAttachment:= FAttachmentLoader.NewPathAttachment(ASkin, lName);
        if not Assigned(lPathAttachment) then exit(nil);
        lPathAttachment.Closed:= lClosed;
        lPathAttachment.ConstantSpeed:= lConstantSpeed;
        lPathAttachment.WorldVerticesLength:= lVertexCount shl 1;
        SetLength(lPathAttachment.Vertices, Length(lVertices.Vertices));
        SetLength(lPathAttachment.Bones, Length(lVertices.Bones));
        SetLength(lPathAttachment.Lengths, Length(lLengths));
        TArray.Copy<Single>(lVertices.Vertices, lPathAttachment.Vertices, 0, 0, Length(lVertices.Vertices));
        TArray.Copy<Integer>(lVertices.Bones, lPathAttachment.Bones, 0, 0, Length(lVertices.Bones));
        TArray.Copy<Single>(lLengths, lPathAttachment.Lengths, 0, 0, Length(lLengths));
        exit(lPathAttachment);
      end;
    TAttachmentType.atPoint:
      begin
        lRotation:= Self.ReadFloat(AStream);
        lX:= Self.ReadFloat(AStream);
        lY:= Self.ReadFloat(AStream);
        if ANonessential then Self.ReadInt(AStream);
        //
        lPointAttachment:= FAttachmentLoader.NewPointAttachment(ASkin, lName);
        if not Assigned(lPointAttachment) then exit(nil);
        lPointAttachment.X:= lX * Self.Scale;
        lPointAttachment.Y:= lY * Self.Scale;
        lPointAttachment.Rotation:= lRotation;
        exit(lPointAttachment);
      end;
    TAttachmentType.atClipping:
      begin
        lEndSlotIndex:= Self.ReadVarint(AStream, True);
        lVertexCount:= Self.ReadVarint(AStream, True);
        lVertices:= Self.ReadVertices(AStream, lVertexCount);
        if ANonessential then Self.ReadInt(AStream);
        //
        lClippingAttachment:= FAttachmentLoader.NewClippingAttachment(ASkin, lName);
        if not Assigned(lClippingAttachment) then exit(nil);
        lClippingAttachment.EndSlot:= ASkeletonData.SlotDatas.Items[lEndSlotIndex];
        lClippingAttachment.worldVerticesLength:= lVertexCount shl 1;
        SetLength(lPathAttachment.Vertices, Length(lVertices.Vertices));
        SetLength(lPathAttachment.Bones, Length(lVertices.Bones));
        TArray.Copy<Single>(lVertices.Vertices, lClippingAttachment.Vertices, 0, 0, Length(lVertices.Vertices));
        TArray.Copy<Integer>(lVertices.Bones, lClippingAttachment.Bones, 0, 0, Length(lVertices.Bones));
        exit(lClippingAttachment);
      end;
  end;
  result:= nil;
end;

function TSpineSkeletonBinary.ReadVertices(const AStream: TStream;
  const AVertexCount: Integer): TVertices;
var
  lVerticesLength, i, lBoneCount, j, idx1, idx2: Integer;
  lWeights: TArray<Single>;
begin
  lVerticesLength:= AVertexCount shl 1;
  if not Self.ReadBoolean(AStream) then
  begin
    result.Vertices:= Self.ReadFloatArray(AStream, lVerticesLength, Self.Scale);
    exit;
  end;
  SetLength(result.Vertices, lVerticesLength * 3 * 3);
  SetLength(result.Bones, lVerticesLength * 3);
  idx1:= 0;
  idx2:= 0;
  for i:= 0 to AVertexCount -1 do
  begin
    lBoneCount:= Self.ReadVarInt(AStream, True);
    result.Bones[idx1]:= Self.ReadVarInt(AStream, True);
    Inc(idx1);
    for j:= 0 to lBoneCount -1 do
    begin
      result.Bones[idx1]:= Self.ReadVarInt(AStream, True);
      Inc(idx1);
      //
      result.Vertices[idx2]:= Self.ReadFloat(AStream) * Self.Scale;
      Inc(idx2);
      result.Vertices[idx2]:= Self.ReadFloat(AStream) * Self.Scale;
      Inc(idx2);
      result.Vertices[idx2]:= Self.ReadFloat(AStream);
      Inc(idx2);
    end;
  end;
end;

function TSpineSkeletonBinary.ReadFloatArray(const AStream: TStream;
  const ACount: Integer; const AScale: Single): TArray<Single>;
var
  i: Integer;
begin
  SetLength(result, ACount);
  if AScale = 1 then
  begin
    for i:= 0 to ACount -1 do
      result[i]:= Self.ReadFloat(AStream);
  end else
  begin
    for i:= 0 to ACount -1 do
      result[i]:= Self.ReadFloat(AStream) * AScale;
  end;
end;

function TSpineSkeletonBinary.ReadShortArray(
  const AStream: TStream): TArray<Integer>;
var
  lCount, i: Integer;
begin
  lCount:= Self.ReadVarInt(AStream, True);
  SetLength(result, lCount);
  for i:= 0 to lCount -1 do
    result[i]:= (Self.ReadByte(AStream) shl 8) or Self.ReadByte(AStream);
end;

procedure TSpineSkeletonBinary.ReadAnimation(const AName: string;
  const AStream: TStream; const ASkeletonData: TSkeletonData);
var
  lAnimation: TSpineAnimation;
  lDuration: Single;
  i, n, lIndex, j, nn, lTimelineType, lFrameCount, k, nnn: Integer;
  lAttachmentTimeline: TAttachmentTimeline;
  lFrameIndex: Integer;
  lColorTimeline: TColorTimeline;
  lTime, lR, lG, lB, lA, lR2, lG2, lB2: Single;
  lColor, lColor2: Integer;
  lTwoColorTimeline: TTwoColorTimeline;
  lRotateTimeline: TRotateTimeline;
  lTranslateTimeline: TTranslateTimeline;
  lTimelineScale: Single;
  lIkConstraintTimeline: TIkConstraintTimeline;
  lTransformConstraintTimeline: TTransformConstraintTimeline;
  lPathConstraintData: TPathConstraintData;
  lPathConstraintPositionTimeline: TPathConstraintPositionTimeline;
  lPathConstraintMixTimeline: TPathConstraintMixTimeline;
  lSkin: TSpineSkin;
  lVertexAttachment: TVertexAttachment;
  lWeighted: Boolean;
  v: Integer;
  lDeformLength: Integer;
  lDeformTiemline: TDeformTimeline;
  lDeform: TArray<Single>;
  lStart, lEnd: Integer;
  lDrawOrderTimeline: TDrawOrderTimeline;
  lSlotCount, lOffsetCount: Integer;
  lDrawOrder, lUnChanged: TArray<Integer>;
  lOriginalIndex, lUnChangedIndex: Integer;
  lEventTimeline: TEventTimeline;
  lEventData: TEventData;
  lEvent: TSpineEvent;
begin
  lAnimation:= TSpineAnimation.Create(AName, 0);
  try
    lDuration:= 0;

    // Slot timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lIndex:= Self.ReadVarInt(AStream, True);  //slotindex
      nn:= Self.ReadVarInt(AStream, True);
      for j:= 0 to nn -1 do
      begin
        lTimelineType:= Self.ReadByte(AStream);
        lFrameCount:= Self.ReadVarInt(AStream, True);
        case lTimelineType of
          SLOT_ATTACHMENT:
            begin
              lAttachmentTimeline:= TAttachmentTimeline.Create(lFrameCount);
              lAttachmentTimeline.SlotIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
                lAttachmentTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadString(AStream));
              lAnimation.Timelines.Add(lAttachmentTimeline);
              lDuration:= System.Math.Max(lDuration, lAttachmentTimeline.Frames[lFrameCount-1]);
            end;
          SLOT_COLOR:
            begin
              lColorTimeline:= TColorTimeline.Create(lFrameCount);
              lColorTimeline.SlotIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lTime:= Self.ReadFloat(AStream);
                lColor:= Self.ReadInt(AStream);
                lR:= ((lColor and $ff) shr 24) / 255;
                lG:= ((lColor and $00ff) shr 16) / 255;
                lB:= ((lColor and $0000ff) shr 8) / 255;
                lA:= ((lColor and $000000ff)) / 255;
                lColorTimeline.SetFrame(lFrameIndex, lTime, lR, lG, lB, lA);
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lColorTimeline);
              end;
              lAnimation.Timelines.Add(lColorTimeline);
              lDuration:= System.Math.Max(lDuration, lColorTimeline.Frames[(lColorTimeline.FrameCount-1)*TColorTimeline.ENTRIES]);
            end;
          SLOT_TWO_COLOR:
            begin
              lTwoColorTimeline:= TTwoColorTimeline.Create(lFrameCount);
              lTwoColorTimeline.SlotIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lTime:= Self.ReadFloat(AStream);
                lColor:= Self.ReadInt(AStream);
                lR:= ((lColor and $ff) shr 24) / 255;
                lG:= ((lColor and $00ff) shr 16) / 255;
                lB:= ((lColor and $0000ff) shr 8) / 255;
                lA:= ((lColor and $000000ff)) / 255;
                lColor2:= Self.ReadInt(AStream); // 0x00rrggbb
                lR2:= ((lColor2 and $00ff) shr 16) / 255;
                lG2:= ((lColor2 and $0000ff) shr 8) / 255;
                lB2:= ((lColor2 and $000000ff)) / 255;
                lTwoColorTimeline.SetFrame(lFrameIndex, lTime, lR, lG, lB, lA, lR2, lG2, lB2);
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTwoColorTimeline);
              end;
              lAnimation.Timelines.Add(lTwoColorTimeline);
              lDuration:= System.Math.Max(lDuration, lTwoColorTimeline.Frames[(lColorTimeline.FrameCount-1)*TTwoColorTimeline.ENTRIES]);
            end;
        end;
      end;
    end;

    // Bone timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lIndex:= Self.ReadVarInt(AStream, True);        //boneindex
      nn:= Self.ReadVarInt(AStream, True);
      for j:= 0 to nn -1 do
      begin
        lTimelineType:= Self.ReadByte(AStream);
        lFrameCount:= Self.ReadVarInt(AStream, True);
        case lTimelineType of
          BONE_ROTATE:
            begin
              lRotateTimeline:= TRotateTimeline.Create(lFrameCount);
              lRotateTimeline.BoneIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lRotateTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream));
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lRotateTimeline);
              end;
              lAnimation.Timelines.Add(lRotateTimeline);
              lDuration:= System.Math.Max(lDuration, lRotateTimeline.Frames[(lFrameCount-1) * TRotateTimeline.ENTRIES]);
            end;
          BONE_TRANSLATE, BONE_SCALE, BONE_SHEAR:
            begin
              lTimelineScale:= 1;
              case lTimelineType of
                BONE_SCALE: lTranslateTimeline:= TScaleTimeline.Create(lFrameCount);
                BONE_SHEAR: lTranslateTimeline:= TShearTimeline.Create(lFrameCount);
                else
                begin
                  lTranslateTimeline:= TTranslateTimeline.Create(lFrameCount);
                  lTimelineScale:= Self.Scale;
                end;
              end;
              lTranslateTimeline.BoneIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lTranslateTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream),
                  Self.ReadFloat(AStream) * lTimelineScale, Self.ReadFloat(AStream) * lTimelineScale);
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTranslateTimeline);
              end;
              lAnimation.Timelines.Add(lTranslateTimeline);
              lDuration:= System.Math.Max(lDuration, lTranslateTimeline.Frames[(lFrameCount-1) * TTranslateTimeline.ENTRIES]);
            end;
        end;
      end;
    end;

    // IK timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lIndex:= Self.ReadVarInt(AStream, True);        //IkConstraintIndex
      lFrameCount:= Self.ReadVarInt(AStream, True);
      lIkConstraintTimeline:= TIkConstraintTimeline.Create(lFrameCount);
      lIkConstraintTimeline.IkConstraintIndex:= lIndex;
      for lFrameIndex:= 0 to lFrameCount -1 do
      begin
        lIkConstraintTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream),
          Self.ReadFloat(AStream), Self.ReadSByte(AStream));
        if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lIkConstraintTimeline);
      end;
      lAnimation.Timelines.Add(lIkConstraintTimeline);
      lDuration:= System.Math.Max(lDuration, lIkConstraintTimeline.Frames[(lFrameCount-1) * TIkConstraintTimeline.ENTRIES]);
    end;

    // Transform constraint timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lIndex:= Self.ReadVarInt(AStream, True);          //TransformConstraintIndex
      lFrameCount:= Self.ReadVarInt(AStream, True);
      lTransformConstraintTimeline:= TTransformConstraintTimeline.Create(lFrameCount);
      lTransformConstraintTimeline.TransformConstraintIndex:= lIndex;
      for lFrameIndex:= 0 to lFrameCount -1 do
      begin
        lTransformConstraintTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream),
          Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream));
        if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lTransformConstraintTimeline);
      end;
      lAnimation.Timelines.Add(lTransformConstraintTimeline);
      lDuration:= System.Math.Max(lDuration, lTransformConstraintTimeline.Frames[(lFrameCount-1) * TTransformConstraintTimeline.ENTRIES]);
    end;

    // Path constraint timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lIndex:= Self.ReadVarInt(AStream, True);         //PathConstraintIndex
      lPathConstraintData:= ASkeletonData.PathConstraintDatas.Items[lIndex];
      nn:= Self.ReadVarInt(AStream, True);
      for j:= 0 to nn -1 do
      begin
        lTimelineType:= Self.ReadSByte(AStream);
        lFrameCount:= Self.ReadVarInt(AStream, True);
        case lTimelineType of
          PATH_POSITION, PATH_SPACING:
            begin
              lTimelineScale:= 1;
              if lTimelineType = PATH_SPACING then
              begin
                lPathConstraintPositionTimeline:= TPathConstraintSpacingTimeline.Create(lFrameCount);
                if (lPathConstraintData.SpacingMode = TSpacingMode.smLength) or
                   (lPathConstraintData.SpacingMode = TSpacingMode.smFixed) then
                  lTimelineScale:= Self.Scale;
              end else
              begin
                lPathConstraintPositionTimeline:= TPathConstraintPositionTimeline.Create(lFrameCount);
                if lPathConstraintData.PositionMode = TPositionMode.pmFixed then
                  lTimelineScale:= Self.Scale;
              end;
              lPathConstraintPositionTimeline.PathConstraintIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lPathConstraintPositionTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream), Self.ReadFloat(AStream) * lTimelineScale);
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lPathConstraintPositionTimeline);
              end;
              lAnimation.Timelines.Add(lPathConstraintPositionTimeline);
              lDuration:= System.Math.Max(lDuration, lPathConstraintPositionTimeline.Frames[(lFrameCount-1) * TPathConstraintPositionTimeline.ENTRIES]);
            end;
          PATH_MIX:
            begin
              lPathConstraintMixTimeline:= TPathConstraintMixTimeline.Create(lFrameCount);
              lPathConstraintMixTimeline.PathConstraintIndex:= lIndex;
              for lFrameIndex:= 0 to lFrameCount -1 do
              begin
                lPathConstraintMixTimeline.SetFrame(lFrameIndex, Self.ReadFloat(AStream),
                  Self.ReadFloat(AStream), Self.ReadFloat(AStream));
                if lFrameIndex < lFrameCount - 1 then Self.ReadCurve(AStream, lFrameIndex, lPathConstraintMixTimeline);
              end;
              lAnimation.Timelines.Add(lPathConstraintMixTimeline);
              lDuration:= System.Math.Max(lDuration, lPathConstraintMixTimeline.Frames[(lFrameCount-1) * TPathConstraintMixTimeline.ENTRIES]);
            end;
        end;
      end;
    end;

    // Deform timelines.
    n:= Self.ReadVarInt(AStream, True);
    for i:= 0 to n -1 do
    begin
      lSkin:= ASkeletonData.Skins.Items[Self.ReadVarInt(AStream, True)];
      nn:= Self.ReadVarInt(AStream, True);
      for j:= 0 to nn -1 do
      begin
        lIndex:= Self.ReadVarInt(AStream, True);         //slotindex
        nnn:= Self.ReadVarInt(AStream, True);
        for k:= 0 to nnn -1 do
        begin
          lVertexAttachment:= TVertexAttachment(lSkin.GetAttachment(lIndex, Self.ReadString(AStream)));
          lWeighted:= Length(lVertexAttachment.Bones) > 0;
          if lWeighted then
            lDeformLength:= System.Math.Floor(Length(lVertexAttachment.Vertices) / 3 *2)
          else
            lDeformLength:= Length(lVertexAttachment.Vertices);
          lFrameCount:= Self.ReadVarInt(AStream, True);
          lDeformTiemline:= TDeformTimeline.Create(lFrameCount);
          lDeformTiemline.SlotIndex:= lIndex;
          lDeformTiemline.Attachment:= lVertexAttachment;
          for lFrameIndex:= 0 to lFrameCount -1 do
          begin
            lTime:= Self.ReadFloat(AStream);
            lEnd:= Self.ReadVarInt(AStream, True);
            if lEnd = 0 then
            begin
              if lWeighted then
                SetLength(lDeform, lDeformLength)
              else
                lDeform:= lVertexAttachment.Vertices;
            end else
            begin
              SetLength(lDeform, lDeformLength);
              lStart:= Self.ReadVarInt(AStream, True);
              lEnd:= lEnd + lStart;
              if Self.Scale = 1 then
              begin
                for v:= lStart to lEnd -1 do
                  lDeform[v]:= Self.ReadFloat(AStream);
              end else
              begin
                for v:= lStart to lEnd -1 do
                  lDeform[v]:= Self.ReadFloat(AStream) * Self.Scale;
              end;
              if not lWeighted then
                for v:= 0 to Length(lDeform) -1 do
                  lDeform[v]:= lDeform[v] + lVertexAttachment.Vertices[v];
            end;
            lDeformTiemline.SetFrame(lFrameIndex, lTime, lDeform);
            if lFrameIndex < lFrameCount -1 then Self.ReadCurve(AStream, lFrameIndex, lDeformTiemline);
          end;
          lAnimation.Timelines.Add(lDeformTiemline);
          lDuration:= System.Math.Max(lDuration, lDeformTiemline.Frames[lFrameCount -1]);
        end;
      end;
    end;

    // Draw order timeline.
    n:= Self.ReadVarInt(AStream, True); //drawOrderCount
    if n > 0 then
    begin
      lDrawOrderTimeline:= TDrawOrderTimeline.Create(n);
      lSlotCount:= ASkeletonData.SlotDatas.Count;
      for i:= 0 to n -1 do
      begin
        lTime:= Self.ReadFloat(AStream);
        lOffsetCount:= Self.ReadVarInt(AStream, True);
        SetLength(lDrawOrder, lSlotCount);
        for j:= lSlotCount -1 downto 0 do
          lDrawOrder[j]:= -1;
        SetLength(lUnChanged, lSlotCount - lOffsetCount);
        lOriginalIndex:= 0;
        lUnChangedIndex:= 0;
        for j:= 0 to lOffsetCount -1 do
        begin
          lIndex:= Self.ReadVarInt(AStream, True); //slotindex
          // Collect unchanged items.
          while lOriginalIndex <> lIndex do
          begin
            lUnChanged[lUnChangedIndex]:= lOriginalIndex;
            Inc(lUnChangedIndex);
            Inc(lOriginalIndex);
          end;
          lDrawOrder[lOriginalIndex+Self.ReadVarInt(AStream, True)]:= lOriginalIndex;
          Inc(lOriginalIndex);
        end;
        // Collect remaining unchanged items.
        while lOriginalIndex < lSlotCount do
        begin
          lUnChanged[lUnChangedIndex]:= lOriginalIndex;
          Inc(lUnChangedIndex);
          Inc(lOriginalIndex);
        end;
        // Fill in unchanged items.
        for j:= lSlotCount downto 0 do
        begin
          if lDrawOrder[j] = -1 then
          begin
            Dec(lUnChangedIndex);
            lDrawOrder[j]:= lUnChanged[lUnChangedIndex];
          end;
        end;
        lDrawOrderTimeline.SetFrame(i, lTime, lDrawOrder);
      end;
      lAnimation.Timelines.Add(lDrawOrderTimeline);
      lDuration:= System.Math.Max(lDuration, lDrawOrderTimeline.Frames[lFrameCount -1]);
    end;

    // Event timeline.
    n:= Self.ReadVarInt(AStream, True); //eventCount
    if n > 0 then
    begin
      lEventTimeline:= TEventTimeline.Create(n);
      for i:= 0 to n -1 do
      begin
        lTime:= Self.ReadFloat(AStream);
        lEventData:= ASkeletonData.EventDatas.Items[Self.ReadVarInt(AStream, True)];
        lEvent:= TSpineEvent.Create(lTime, lEventData);
        lEvent.IntValue:= Self.ReadVarInt(AStream, False);
        lEvent.FloatValue:= Self.ReadFloat(AStream);
        if Self.ReadBoolean(AStream) then
          lEvent.StringValue:= Self.ReadString(AStream)
        else
          lEvent.StringValue:= lEventData.StringValue;
        lEventTimeline.SetFrame(i, lEvent);
      end;
      lAnimation.Timelines.Add(lEventTimeline);
      lDuration:= System.Math.Max(lDuration, lEventTimeline.Frames[lFrameCount -1]);
    end;
    lAnimation.Timelines.TrimExcess;
    lAnimation.Duration:= lDuration;
    ASkeletonData.Animations.Add(lAnimation);
  except
    lAnimation.Free;
    raise;
  end;
end;

procedure TSpineSkeletonBinary.ReadCurve(const AStream: TStream;
  const AFrameIndex: Integer; const ATimeline: TCurveTimeline);
begin
  case Self.ReadByte(AStream) of
    CURVE_STEPPED: ATimeline.SetStepped(AFrameIndex);
    CURVE_BEZIER: ATimeline.SetCurve(AFrameIndex, Self.ReadFloat(AStream),
      Self.ReadFloat(AStream), Self.ReadFloat(AStream), Self.ReadFloat(AStream));
  end;
end;

function TSpineSkeletonBinary.ReadSByte(const AStream: TStream): SByte;
var
  lValue: Integer;
begin
  lValue:= Self.ReadByte(AStream);
  if lValue = -1 then raise Exception.Create('end of stream.');
  result:= lValue;
end;

function TSpineSkeletonBinary.ReadBoolean(const AStream: TStream): Boolean;
begin
  result:= Self.ReadByte(AStream) <> 0;
end;

function TSpineSkeletonBinary.ReadFloat(const AStream: TStream): Single;
begin
  FBuffer[3]:= Self.ReadByte(AStream);
  FBuffer[2]:= Self.ReadByte(AStream);
  FBuffer[1]:= Self.ReadByte(AStream);
  FBuffer[0]:= Self.ReadByte(AStream);
  result:= PSingle(@FBuffer[0])^;
end;

function TSpineSkeletonBinary.ReadInt(const AStream: TStream): Integer;
begin
  result:= Self.ReadByte(AStream) shl 24;
  result:= result + (Self.ReadByte(AStream) shl 16);
  result:= result + (Self.ReadByte(AStream) shl 8);
  result:= result + Self.ReadByte(AStream);
end;

function TSpineSkeletonBinary.ReadVarInt(const AStream: TStream;
  const AOptimizePositive: Boolean): Integer;
var
  lByte: Integer;
begin
  lByte:= Self.ReadByte(AStream);
  result:= lByte and $7F;
  if (lByte and $80) <> 0 then
  begin
    lByte:= Self.ReadByte(AStream);
    result:= result or ((lByte and $7F) shl 7);
    if (lByte and $80) <> 0 then
    begin
      lByte:= Self.ReadByte(AStream);
      result:= result or ((lByte and $7F) shl 14);
      if (lByte and $80) <> 0 then
      begin
        lByte:= Self.ReadByte(AStream);
        result:= result or ((lByte and $7F) shl 21);
        if (lByte and $80) <> 0 then
          result:= result or ((lByte and $7F) shl 28);
      end;
    end;
  end;
  //
  if not AOptimizePositive then
    result:= (((Result shr 1) and $7fffffff) xor -(Result and 1));
end;

function TSpineSkeletonBinary.ReadString(const AStream: TStream): string;
var
  lByteCount: Integer;
  lBuffer: TArray<Byte>;
begin
  lByteCount:= Self.ReadVarInt(AStream, True);
  case lByteCount of
    0, 1: exit('');
    else
      begin
        lByteCount:= lByteCount - 1;
        if Length(lBuffer) < lByteCount then
          SetLength(lBuffer, lByteCount)
        else
          TArray.Copy<Byte>(FBuffer, lBuffer, 0, 0, Length(FBuffer));
        TSpineSkeletonBinary.ReadFully(AStream, lBuffer, 0, lByteCount);
        result:= System.SysUtils.StringOf(lBuffer);
      end;
  end;
end;

class procedure TSpineSkeletonBinary.ReadFully(const AStream: TStream;
  var ABuffer: TArray<Byte>; const AOffset: Integer; var ALength: Integer);
var
  lOffset, lCount: Integer;
begin
  lOffset:= AOffset;
  while ALength > 0 do
  begin
    lCount:= AStream.Read(ABuffer, lOffset, ALength);
    if lCount <= 0 then raise Exception.Create('end of stream.');
    lOffset:= lOffset + lCount;
    ALength:= ALength - lCount;
  end;
end;

end.

上一篇翻译了图集深入分析单元,前日把骨架解析也翻译完了(二进制,.skel文件)。一千多行代码,把作者累的。。。

spine.core.bone, spine.core.slot, spine.core.skin,
spine.core.attachment, spine.core.constraint, spine.core.skeleton,
spine.core.animation.timeline, spine.core.skeleton.json,
spine.core.event,
spine.core.animation

上边那几个依赖的单元部分还在整治,慢慢放上来。

到前日告竣,算是把核心数据的解析专门的学问成功了。

Runtime for
Delphi移植笔记(六),spineruntime //
//////////////////////////////////////////////////////////////////////////////
// Generic delphi runtime…

Leave a Comment.