【Spine】Spine Runtime for Delphi移植笔记(六) - spine.core.skeleton.binary

////////////////////////////////////////////////////////////////////////////////
//Generic delphi runtime v3.6 for Spine animation tool                        //
//Runtime port by cjk (hzi1980@163.com)                                       //
////////////////////////////////////////////////////////////////////////////////

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文件)。1000多行代码,把我累的。。。

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

上面这些依赖的单元部分还在整理,慢慢放上来。

到现在为止,算是把基本数据的解析工作完成了。

posted @ 2017-08-02 10:48 水中盗影 阅读(...) 评论(...) 编辑 收藏