//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit GLAnimatedSprite;

(* A sprite that uses a scrolling texture for animation. *)

interface

{$I GLScene.inc}

uses
  Winapi.OpenGL,
  System.Classes,
  System.SysUtils,
  System.Math,
   
  OpenGLTokens,
  GLScene,
  GLVectorTypes,
  GLVectorGeometry,
  GLMaterial,
  GLPersistentClasses,
  XCollection,
  GLRenderContextInfo,
  GLBaseClasses, 
  GLContext, 
  GLState;

type
  TGLSpriteAnimFrame = class;
  TGLSpriteAnimFrameList = class;
  TGLSpriteAnimation = class;
  TGLSpriteAnimationList = class;
  TGLAnimatedSprite = class;

  (* Used by the SpriteAnimation when Dimensions are set manual. The animation
    will use the offsets, width and height to determine the texture coodinates for this frame. *)
  TGLSpriteAnimFrame = class(TXCollectionItem)
  private
    FOffsetX,
      FOffsetY,
      FWidth,
      FHeight: Integer;
    procedure DoChanged;
  protected
    procedure SetOffsetX(const Value: Integer);
    procedure SetOffsetY(const Value: Integer);
    procedure SetWidth(const Value: Integer);
    procedure SetHeight(const Value: Integer);
    procedure WriteToFiler(writer: TWriter); override;
    procedure ReadFromFiler(reader: TReader); override;
  public
    class function FriendlyName: string; override;
    class function FriendlyDescription: string; override;
  published
    property OffsetX: Integer read FOffsetX write SetOffsetX;
    property OffsetY: Integer read FOffsetY write SetOffsetY;
    property Width: Integer read FWidth write SetWidth;
    property Height: Integer read FHeight write SetHeight;
  end;

  TGLSpriteAnimFrameList = class(TXCollection)
  public
    constructor Create(aOwner: TPersistent); override;
    class function ItemsClass: TXCollectionItemClass; override;
  end;

  (* Determines if the texture coordinates are Automatically generated
     from the Animations properties or if they are Manually set through
     the Frames collection. *)
  TGLSpriteFrameDimensions = (sfdAuto, sfdManual);

  (* Used to mask the auto generated frames. The Left, Top, Right and
    Bottom properties determines the number of pixels to be cropped
    from each corresponding side of the frame. Only applicable to auto dimensions. *)
  TGLSpriteAnimMargins = class(TPersistent)
  private
    FOwner: TGLSpriteAnimation;
    FLeft, FTop, FRight, FBottom: Integer;
  protected
    procedure SetLeft(const Value: Integer);
    procedure SetTop(const Value: Integer);
    procedure SetRight(const Value: Integer);
    procedure SetBottom(const Value: Integer);
    procedure DoChanged;
  public
    constructor Create(Animation: TGLSpriteAnimation);
    property Owner: TGLSpriteAnimation read FOwner;
  published
    property Left: Integer read FLeft write SetLeft;
    property Top: Integer read FTop write SetTop;
    property Right: Integer read FRight write SetRight;
    property Bottom: Integer read FBottom write SetBottom;
  end;

  // Animations define how the texture coordinates for each offset are to be determined. 
  TGLSpriteAnimation = class(TXCollectionItem, IGLMaterialLibrarySupported)
  private
    FCurrentFrame,
      FStartFrame,
      FEndFrame,
      FFrameWidth,
      FFrameHeight,
      FInterval: Integer;
    FFrames: TGLSpriteAnimFrameList;
    FLibMaterialName: TGLLibMaterialName;
    FLibMaterialCached: TGLLibMaterial;
    FDimensions: TGLSpriteFrameDimensions;
    FMargins: TGLSpriteAnimMargins;
    procedure DoChanged;
  protected
    procedure SetCurrentFrame(const Value: Integer);
    procedure SetFrameWidth(const Value: Integer);
    procedure SetFrameHeight(const Value: Integer);
    procedure WriteToFiler(writer: TWriter); override;
    procedure ReadFromFiler(reader: TReader); override;
    procedure SetDimensions(const Value: TGLSpriteFrameDimensions);
    procedure SetLibMaterialName(const val: TGLLibMaterialName);
    function GetLibMaterialCached: TGLLibMaterial;
    procedure SetInterval(const Value: Integer);
    procedure SetFrameRate(const Value: Single);
    function GetFrameRate: Single;
    // Implementing IGLMaterialLibrarySupported.
    function GetMaterialLibrary: TGLAbstractMaterialLibrary;
  public
    constructor Create(aOwner: TXCollection); override;
    destructor Destroy; override;
    class function FriendlyName: string; override;
    class function FriendlyDescription: string; override;
    property LibMaterialCached: TGLLibMaterial read GetLibMaterialCached;
  published
    // The current showing frame for this animation.
    property CurrentFrame: Integer read FCurrentFrame write SetCurrentFrame;
    // Defines the starting frame for auto dimension animations.
    property StartFrame: Integer read FStartFrame write FStartFrame;
    // Defines the ending frame for auto dimension animations.
    property EndFrame: Integer read FEndFrame write FEndFrame;
    // Width of each frame in an auto dimension animation.
    property FrameWidth: Integer read FFrameWidth write SetFrameWidth;
    // Height of each frame in an auto dimension animation.
    property FrameHeight: Integer read FFrameHeight write SetFrameHeight;
    (* The name of the lib material the sprites associated material library
       for this animation. *)
    property LibMaterialName: TGLLibMaterialName read FLibMaterialName write
      SetLibMaterialName;
    (* Manual dimension animation frames. Stores the offsets and dimensions
       for each frame in the animation. *)
    property Frames: TGLSpriteAnimFrameList read FFrames;
    // Automatic or manual texture coordinate generation.
    property Dimensions: TGLSpriteFrameDimensions read FDimensions write
      SetDimensions;
    (* The number of milliseconds between each frame in the animation.
       Will automatically calculate the FrameRate value when set.
       Will override the TGLAnimatedSprite Interval is greater than zero. *)
    property Interval: Integer read FInterval write SetInterval;
    (* The number of frames per second for the animation.
       Will automatically calculate the Interval value when set.
       Precision will depend on Interval since Interval has priority. *)
    property FrameRate: Single read GetFrameRate write SetFrameRate;
    // Sets cropping margins for auto dimension animations.
    property Margins: TGLSpriteAnimMargins read FMargins;
  end;

  // A collection for storing SpriteAnimation objects. 
  TGLSpriteAnimationList = class(TXCollection)
  public
    constructor Create(aOwner: TPersistent); override;
    class function ItemsClass: TXCollectionItemClass; override;
  end;

  (* Sets the current animation playback mode:
     samNone - No playback, the animation does not progress.
     samPlayOnce - Plays the animation once then switches to samNone.
     samLoop - Play the animation forward in a continuous loop.
     samLoopBackward - Same as samLoop but reversed direction.
     samBounceForward - Plays forward and switches to samBounceBackward
       when EndFrame is reached.
     samBounceBackward - Plays backward and switches to samBounceForward
       when StartFrame is reached. *)
  TGLSpriteAnimationMode = (samNone, samPlayOnce, samLoop, samBounceForward,
    samBounceBackward, samLoopBackward);

  // An animated version for using offset texture coordinate animation. 
  TGLAnimatedSprite = class(TGLBaseSceneObject)
  private
    FAnimations: TGLSpriteAnimationList;
    FMaterialLibrary: TGLMaterialLibrary;
    FAnimationIndex,
      FInterval,
      FRotation,
      FPixelRatio: Integer;
    FMirrorU,
      FMirrorV: Boolean;
    FAnimationMode: TGLSpriteAnimationMode;
    FCurrentFrameDelta: Double;
    FOnFrameChanged: TNotifyEvent;
    FOnEndFrameReached: TNotifyEvent;
    FOnStartFrameReached: TNotifyEvent;
  protected
    procedure DefineProperties(Filer: TFiler); override;
    procedure WriteAnimations(Stream: TStream);
    procedure ReadAnimations(Stream: TStream);
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure SetInterval(const val: Integer);
    procedure SetAnimationIndex(const val: Integer);
    procedure SetAnimationMode(const val: TGLSpriteAnimationMode);
    procedure SetMaterialLibrary(const val: TGLMaterialLibrary);
    procedure SetPixelRatio(const val: Integer);
    procedure SetRotation(const val: Integer);
    procedure SetMirrorU(const val: Boolean);
    procedure SetMirrorV(const val: Boolean);
    procedure SetFrameRate(const Value: Single);
    function GetFrameRate: Single;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure BuildList(var rci: TGLRenderContextInfo); override;
    procedure DoProgress(const progressTime: TGLProgressTimes); override;
    // Steps the current animation to the next frame
    procedure NextFrame;
  published
    // A collection of animations. Stores the settings for animating then sprite. 
    property Animations: TGLSpriteAnimationList read FAnimations;
    // The material library that stores the lib materials for the animations.
    property MaterialLibrary: TGLMaterialLibrary read FMaterialLibrary write
      SetMaterialLibrary;
    (* Sets the number of milliseconds between each frame. Will recalculate
       the Framerate when set. Will be overridden by the TGLSpriteAnimation
       Interval if it is greater than zero. *)
    property Interval: Integer read FInterval write SetInterval;
    // Index of the sprite animation to be used.
    property AnimationIndex: Integer read FAnimationIndex write
      SetAnimationIndex;
    // Playback mode for the current animation.
    property AnimationMode: TGLSpriteAnimationMode read FAnimationMode write
      SetAnimationMode;
    (* Used to automatically calculate the width and height of a sprite based
       on the size of the frame it is showing. For example, if PixelRatio is
       set to 100 and the current animation frame is 100 pixels wide it will
       set the width of the sprite to 1. If the frame is 50 pixels width the
       sprite will be 0.5 wide. *)
    property PixelRatio: Integer read FPixelRatio write SetPixelRatio;
    // Rotates the sprite (in degrees).
    property Rotation: Integer read FRotation write SetRotation;
    // Mirror the generated texture coords in the U axis.
    property MirrorU: Boolean read FMirrorU write SetMirrorU;
    // Mirror the generated texture coords in the V axis.
    property MirrorV: Boolean read FMirrorV write SetMirrorV;
    (* Sets the frames per second for the current animation. Automatically
       calculates the Interval. Precision will be restricted to the values
       of Interval since Interval takes priority. *)
    property FrameRate: Single read GetFrameRate write SetFrameRate;
    property Position;
    property Scale;
    property Visible;
    // An event fired when the animation changes to it's next frame.
    property OnFrameChanged: TNotifyEvent read FOnFrameChanged write
      FOnFrameChanged;
    // An event fired when the animation reaches the end frame.
    property OnEndFrameReached: TNotifyEvent read FOnEndFrameReached write
      FOnEndFrameReached;
    // An event fired when the animation reaches the start frame.
    property OnStartFrameReached: TNotifyEvent read FOnStartFrameReached write
      FOnStartFrameReached;
  end;

// -----------------------------------------------------------------------------
implementation
// -----------------------------------------------------------------------------

// ----------
// ---------- TGLSpriteAnimFrame ----------
// ----------

procedure TGLSpriteAnimFrame.DoChanged;
begin
  if Assigned(Owner) then
  begin
    if Assigned(Owner.Owner) then
      if Owner.Owner is TGLSpriteAnimation then
        TGLSpriteAnimation(Owner.Owner).DoChanged;
  end;
end;

class function TGLSpriteAnimFrame.FriendlyName: string;
begin
  Result := 'Frame';
end;

class function TGLSpriteAnimFrame.FriendlyDescription: string;
begin
  Result := 'Sprite Animation Frame';
end;

procedure TGLSpriteAnimFrame.WriteToFiler(writer: TWriter);
begin
  inherited;
  writer.WriteInteger(0); // Archive version number
  with writer do
  begin
    WriteInteger(OffsetX);
    WriteInteger(OffsetY);
    WriteInteger(Width);
    WriteInteger(Height);
  end;
end;

procedure TGLSpriteAnimFrame.ReadFromFiler(reader: TReader);
var
  archiveVersion: Integer;
begin
  inherited;
  archiveVersion := reader.ReadInteger;
  Assert(archiveVersion = 0);
  with reader do
  begin
    OffsetX := ReadInteger;
    OffsetY := ReadInteger;
    Width := ReadInteger;
    Height := ReadInteger;
  end;
end;

procedure TGLSpriteAnimFrame.SetOffsetX(const Value: Integer);
begin
  if Value <> FOffsetX then
  begin
    FOffsetX := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimFrame.SetOffsetY(const Value: Integer);
begin
  if Value <> FOffsetY then
  begin
    FOffsetY := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimFrame.SetWidth(const Value: Integer);
begin
  if Value <> FWidth then
  begin
    FWidth := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimFrame.SetHeight(const Value: Integer);
begin
  if Value <> FHeight then
  begin
    FHeight := Value;
    DoChanged;
  end;
end;

// ----------
// ---------- TGLSpriteAnimFrameList ----------
// ----------

constructor TGLSpriteAnimFrameList.Create(aOwner: TPersistent);
begin
  inherited;
end;

class function TGLSpriteAnimFrameList.ItemsClass: TXCollectionItemClass;
begin
  Result := TGLSpriteAnimFrame;
end;

// ----------
// ---------- TGLSpriteAnimMargins ----------
// ----------

constructor TGLSpriteAnimMargins.Create(Animation: TGLSpriteAnimation);
begin
  inherited Create;
  FOwner := Animation;
end;

procedure TGLSpriteAnimMargins.SetLeft(const Value: Integer);
begin
  if Value <> FLeft then
  begin
    FLeft := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimMargins.SetTop(const Value: Integer);
begin
  if Value <> FTop then
  begin
    FTop := Value;
    DoChanged;
  end;
end;


procedure TGLSpriteAnimMargins.SetRight(const Value: Integer);
begin
  if Value <> FRight then
  begin
    FRight := Value;
    DoChanged;
  end;
end;


procedure TGLSpriteAnimMargins.SetBottom(const Value: Integer);
begin
  if Value <> FBottom then
  begin
    FBottom := Value;
    DoChanged;
  end;
end;


procedure TGLSpriteAnimMargins.DoChanged;
begin
  if Assigned(Owner) then
    Owner.DoChanged;
end;

// ----------
// ---------- TGLSpriteAnimation ----------
// ----------

constructor TGLSpriteAnimation.Create(aOwner: TXCollection);
begin
  inherited;
  FFrames := TGLSpriteAnimFrameList.Create(Self);
  FMargins := TGLSpriteAnimMargins.Create(Self);
end;

destructor TGLSpriteAnimation.Destroy;
begin
  FFrames.Free;
  FMargins.Free;
  inherited;
end;

function TGLSpriteAnimation.GetMaterialLibrary: TGLAbstractMaterialLibrary;
begin
  if not (Owner is TGLSpriteAnimationList) then
    Result := nil
  else
  begin
    if not (TGLSpriteAnimationList(Owner).Owner is TGLAnimatedSprite) then
      Result := nil
    else
      Result :=
        TGLAnimatedSprite(TGLSpriteAnimationList(Owner).Owner).FMaterialLibrary;
  end;
end;

class function TGLSpriteAnimation.FriendlyName: string;
begin
  Result := 'Animation';
end;

class function TGLSpriteAnimation.FriendlyDescription: string;
begin
  Result := 'Sprite Animation';
end;

procedure TGLSpriteAnimation.WriteToFiler(writer: TWriter);
begin
  inherited;
  writer.WriteInteger(2); // Archive version number
  Frames.WriteToFiler(writer);
  with writer do
  begin
    // Version 0
    WriteString(LibMaterialName);
    WriteInteger(CurrentFrame);
    WriteInteger(StartFrame);
    WriteInteger(EndFrame);
    WriteInteger(FrameWidth);
    WriteInteger(FrameHeight);
    WriteInteger(Integer(Dimensions));
    // Version 1
    WriteInteger(Interval);
    // Version 2
    WriteInteger(Margins.Left);
    WriteInteger(Margins.Top);
    WriteInteger(Margins.Right);
    WriteInteger(Margins.Bottom);
  end;
end;

procedure TGLSpriteAnimation.ReadFromFiler(reader: TReader);
var
  archiveVersion: Integer;
begin
  inherited;
  archiveVersion := reader.ReadInteger;
  Assert((archiveVersion >= 0) and (archiveVersion <= 2));
  Frames.ReadFromFiler(reader);
  with reader do
  begin
    FLibMaterialName := ReadString;
    CurrentFrame := ReadInteger;
    StartFrame := ReadInteger;
    EndFrame := ReadInteger;
    FrameWidth := ReadInteger;
    FrameHeight := ReadInteger;
    Dimensions := TGLSpriteFrameDimensions(ReadInteger);

    if archiveVersion >= 1 then
    begin
      Interval := ReadInteger;
    end;

    if archiveVersion >= 2 then
    begin
      Margins.Left := ReadInteger;
      Margins.Top := ReadInteger;
      Margins.Right := ReadInteger;
      Margins.Bottom := ReadInteger;
    end;
  end;
end;

procedure TGLSpriteAnimation.DoChanged;
begin
  if Assigned(Owner) then
  begin
    if Assigned(Owner.Owner) then
      if Owner.Owner is TGLBaseSceneObject then
        TGLBaseSceneObject(Owner.Owner).NotifyChange(Self);
  end;
end;

procedure TGLSpriteAnimation.SetCurrentFrame(const Value: Integer);
begin
  if Value <> FCurrentFrame then
  begin
    FCurrentFrame := Value;
    if FCurrentFrame < 0 then
      FCurrentFrame := -1;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimation.SetFrameWidth(const Value: Integer);
begin
  if Value <> FFrameWidth then
  begin
    FFrameWidth := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimation.SetFrameHeight(const Value: Integer);
begin
  if Value <> FFrameHeight then
  begin
    FFrameHeight := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimation.SetDimensions(
  const Value: TGLSpriteFrameDimensions);
begin
  if Value <> FDimensions then
  begin
    FDimensions := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimation.SetLibMaterialName(const val: TGLLibMaterialName);
begin
  if val <> FLibMaterialName then
  begin
    FLibMaterialName := val;
    FLibMaterialCached := nil;
  end;
end;

function TGLSpriteAnimation.GetLibMaterialCached: TGLLibMaterial;
begin
  Result := nil;
  if FLibMaterialName = '' then
    exit;

  if not Assigned(FLibMaterialCached) then
    if Assigned(Owner) then
      if Assigned(Owner.Owner) then
        if Owner.Owner is TGLAnimatedSprite then
          if Assigned(TGLAnimatedSprite(Owner.Owner).MaterialLibrary) then
            FLibMaterialCached :=
              TGLAnimatedSprite(Owner.Owner).MaterialLibrary.Materials.GetLibMaterialByName(FLibMaterialName);

  Result := FLibMaterialCached;
end;

procedure TGLSpriteAnimation.SetInterval(const Value: Integer);
begin
  if Value <> FInterval then
  begin
    FInterval := Value;
    DoChanged;
  end;
end;

procedure TGLSpriteAnimation.SetFrameRate(const Value: Single);
begin
  if Value > 0 then
    Interval := Round(1000 / Value)
  else
    Interval := 0;
end;

function TGLSpriteAnimation.GetFrameRate: Single;
begin
  if Interval > 0 then
    Result := 1000 / Interval
  else
    Result := 0;
end;

// ----------
// ---------- TGLSpriteAnimationList ----------
// ----------

constructor TGLSpriteAnimationList.Create(aOwner: TPersistent);
begin
  inherited;
end;

class function TGLSpriteAnimationList.ItemsClass: TXCollectionItemClass;
begin
  Result := TGLSpriteAnimation;
end;

// ----------
// ---------- TGLAnimatedSprite ----------
// ----------

constructor TGLAnimatedSprite.Create(AOwner: TComponent);
begin
  inherited;

  FAnimations := TGLSpriteAnimationList.Create(Self);
  FAnimationIndex := -1;
  FInterval := 100;
  FPixelRatio := 100;
  FRotation := 0;
  FMirrorU := False;
  FMirrorV := False;

  ObjectStyle := [osDirectDraw];
end;

destructor TGLAnimatedSprite.Destroy;
begin
  FAnimations.Free;
  inherited;
end;

{$WARNINGS Off}

procedure TGLAnimatedSprite.BuildList(var rci: TGLRenderContextInfo);
var
  vx, vy: TAffineVector;
  w, h, temp: Single;
  mat: TMatrix;
  u0, v0, u1, v1: Single;
  x0, y0, x1, y1, TexWidth, TexHeight: Integer;
  Anim: TGLSpriteAnimation;
  Frame: TGLSpriteAnimFrame;
  libMat: TGLLibMaterial;
  IsAuto: Boolean;
begin
  if (FAnimationIndex <> -1) and (FAnimationIndex < Animations.Count) then
  begin
    Anim := TGLSpriteAnimation(Animations[FAnimationIndex]);

    if (Anim.CurrentFrame >= 0) then
    begin
      if (Anim.Dimensions = sfdManual) and (Anim.CurrentFrame <
        Anim.Frames.Count) then
        Frame := TGLSpriteAnimFrame(Anim.Frames[Anim.CurrentFrame])
      else
        Frame := nil;
      IsAuto := (Anim.CurrentFrame <= Anim.EndFrame) and
        (Anim.CurrentFrame >= Anim.StartFrame) and
        (Anim.Dimensions = sfdAuto);
      if Assigned(Frame) or IsAuto then
      begin
        libMat := Anim.LibMaterialCached;

        h := 0.5;
        w := 0.5;
        u0 := 0;
        v0 := 0;
        u1 := 0;
        v1 := 0;

        if Assigned(libMat) then
        begin
          TexWidth := libMat.Material.Texture.Image.Width;
          TexHeight := libMat.Material.Texture.Image.Height;
          if Anim.Dimensions = sfdManual then
          begin
            x0 := Frame.OffsetX;
            y0 := Frame.OffsetY;
            x1 := x0 + Frame.Width - 1;
            y1 := y0 + Frame.Height - 1;
          end
          else
          begin
            if (TexWidth > 0) and (Anim.FrameWidth > 0)
              and (TexHeight > 0) and (Anim.FrameHeight > 0) then
            begin
              x0 := Anim.FrameWidth * (Anim.CurrentFrame mod (TexWidth div
                Anim.FrameWidth));
              y0 := Anim.FrameHeight * (Anim.CurrentFrame div (TexWidth div
                Anim.FrameWidth));
            end
            else
            begin
              x0 := 0;
              y0 := 0;
            end;
            x1 := x0 + Anim.FrameWidth - 1;
            y1 := y0 + Anim.FrameHeight - 1;

            x0 := x0 + Anim.Margins.Left;
            y0 := y0 + Anim.Margins.Top;
            x1 := x1 - Anim.Margins.Right;
            y1 := y1 - Anim.Margins.Bottom;
          end;
          if (TexWidth > 0) and (TexHeight > 0) and (x0 <> x1) and (y0 <> y1)
            then
          begin
            u0 := x0 / TexWidth;
            v0 := 1 - y1 / TexHeight;
            u1 := x1 / TexWidth;
            v1 := 1 - y0 / TexHeight;
            w := 0.5 * (x1 - x0) / FPixelRatio;
            h := 0.5 * (y1 - y0) / FPixelRatio;
          end;
        end;

        gl.GetFloatv(GL_MODELVIEW_MATRIX, @mat);
        vx.X := mat.V[0].X;
        vy.X := mat.V[0].Y;
        vx.Y := mat.V[1].X;
        vy.Y := mat.V[1].Y;
        vx.Z := mat.V[2].X;
        vy.Z := mat.V[2].Y;
        ScaleVector(vx, w * VectorLength(vx));
        ScaleVector(vy, h * VectorLength(vy));

        if FMirrorU then
        begin
          temp := u0;
          u0 := u1;
          u1 := temp;
        end;
        if FMirrorV then
        begin
          temp := v0;
          v0 := v1;
          v1 := temp;
        end;

        if Assigned(libMat) then
          libMat.Apply(rci);
        rci.GLStates.Disable(stLighting);
        if FRotation <> 0 then
        begin
          gl.MatrixMode(GL_MODELVIEW);
          gl.PushMatrix;
          gl.Rotatef(FRotation, mat.V[0].Z, mat.V[1].Z, mat.V[2].Z);
        end;
        gl.Begin_(GL_QUADS);
        gl.TexCoord2f(u1, v1);
        gl.Vertex3f(vx.X + vy.X, vx.Y + vy.Y,
                    vx.Z + vy.Z);
        gl.TexCoord2f(u0, v1);
        gl.Vertex3f(-vx.X + vy.X,
                    -vx.Y + vy.Y,
                    -vx.Z + vy.Z);
        gl.TexCoord2f(u0, v0);
        gl.Vertex3f(-vx.X - vy.X, -vx.Y - vy.Y, -vx.Z - vy.Z);
        gl.TexCoord2f(u1, v0);
        gl.Vertex3f(vx.X - vy.X, vx.Y - vy.Y, vx.Z - vy.Z);
        gl.End_;
        if FRotation <> 0 then
        begin
          gl.PopMatrix;
        end;
        if Assigned(libMat) then
          libMat.UnApply(rci);
      end;
    end;
  end;
end;
{$WARNINGS On}

procedure TGLAnimatedSprite.DoProgress(const progressTime: TGLProgressTimes);
var
  i, intr: Integer;
begin
  inherited;
  if (AnimationIndex = -1) then
    exit;
  intr := TGLSpriteAnimation(Animations[AnimationIndex]).Interval;
  if intr = 0 then
    intr := Interval;
  if (FAnimationMode <> samNone) and (intr > 0) then
  begin
    FCurrentFrameDelta := FCurrentFrameDelta + (progressTime.deltaTime * 1000) /
      intr;
    if FCurrentFrameDelta >= 1 then
    begin
      for i := 0 to Floor(FCurrentFrameDelta) - 1 do
      begin
        NextFrame;
        FCurrentFrameDelta := FCurrentFrameDelta - 1;
      end;
    end;
  end;
end;


procedure TGLAnimatedSprite.Notification(AComponent: TComponent; Operation:
  TOperation);
begin
  if (Operation = opRemove) and (AComponent = FMaterialLibrary) then
    FMaterialLibrary := nil;
  inherited;
end;

procedure TGLAnimatedSprite.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('SpriteAnimations',
    ReadAnimations, WriteAnimations,
    FAnimations.Count > 0);
end;

procedure TGLAnimatedSprite.WriteAnimations(Stream: TStream);
var
  writer: TWriter;
begin
  writer := TWriter.Create(stream, 16384);
  try
    Animations.WriteToFiler(writer);
  finally
    writer.Free;
  end;
end;

procedure TGLAnimatedSprite.ReadAnimations(Stream: TStream);
var
  reader: TReader;
begin
  reader := TReader.Create(stream, 16384);
  try
    Animations.ReadFromFiler(reader);
  finally
    reader.Free;
  end;
end;

procedure TGLAnimatedSprite.NextFrame;
var
  currentFrame,
    startFrame,
    endFrame: Integer;
  Anim: TGLSpriteAnimation;
begin
  if (FAnimationIndex = -1) or (FAnimationIndex >= Animations.Count) then
    exit;

  Anim := TGLSpriteAnimation(Animations[FAnimationIndex]);

  currentFrame := Anim.CurrentFrame;
  if Anim.Dimensions = sfdManual then
  begin
    startFrame := 0;
    endFrame := Anim.Frames.Count - 1
  end
  else
  begin
    startFrame := Anim.StartFrame;
    endFrame := Anim.EndFrame;
  end;

  case AnimationMode of
    samLoop, samBounceForward, samPlayOnce:
      begin
        if (currentFrame = endFrame) and Assigned(FOnEndFrameReached) then
          FOnEndFrameReached(Self);
        Inc(currentFrame);
      end;
    samBounceBackward, samLoopBackward:
      begin
        if (currentFrame = startFrame) and Assigned(FOnStartFrameReached) then
          FOnStartFrameReached(Self);
        Dec(CurrentFrame);
      end;
  end;

  if (AnimationMode <> samNone) and Assigned(FOnFrameChanged) then
    FOnFrameChanged(Self);

  case AnimationMode of

    samPlayOnce:
      begin
        if currentFrame > endFrame then
          AnimationMode := samNone;
      end;

    samLoop:
      begin
        if currentFrame > endFrame then
          currentFrame := startFrame;
      end;

    samBounceForward:
      begin
        if currentFrame = endFrame then
          AnimationMode := samBounceBackward;
      end;

    samLoopBackward:
      begin
        if currentFrame < startFrame then
          CurrentFrame := endFrame;
      end;

    samBounceBackward:
      begin
        if currentFrame = startFrame then
          AnimationMode := samBounceForward;
      end;

  end;

  Anim.CurrentFrame := currentFrame;
end;

procedure TGLAnimatedSprite.SetInterval(const val: Integer);
begin
  if val <> FInterval then
  begin
    FInterval := val;
    NotifyChange(Self);
  end;
end;


procedure TGLAnimatedSprite.SetFrameRate(const Value: Single);
begin
  if Value > 0 then
    Interval := Round(1000 / Value)
  else
    Interval := 0;
end;

function TGLAnimatedSprite.GetFrameRate: Single;
begin
  if Interval > 0 then
    Result := 1000 / Interval
  else
    Result := 0;
end;

procedure TGLAnimatedSprite.SetAnimationIndex(const val: Integer);
begin
  if val <> FAnimationIndex then
  begin
    FAnimationIndex := val;
    if FAnimationIndex < 0 then
      FAnimationIndex := -1;
    if (FAnimationIndex <> -1) and (FAnimationIndex < Animations.Count) then
      with TGLSpriteAnimation(Animations[FAnimationIndex]) do
        case AnimationMode of
          samNone, samPlayOnce, samLoop, samBounceForward:
            CurrentFrame := StartFrame;
          samLoopBackward, samBounceBackward:
            CurrentFrame := EndFrame;
        end;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetAnimationMode(const val: TGLSpriteAnimationMode);
begin
  if val <> FAnimationMode then
  begin
    FAnimationMode := val;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetMaterialLibrary(const val: TGLMaterialLibrary);
var
  i: Integer;
begin
  if val <> FMaterialLibrary then
  begin
    if FMaterialLibrary <> nil then
      FMaterialLibrary.RemoveFreeNotification(Self);
    FMaterialLibrary := val;
    if FMaterialLibrary <> nil then
      FMaterialLibrary.FreeNotification(Self);
    for i := 0 to Animations.Count - 1 do
      TGLSpriteAnimation(Animations[i]).FLibMaterialCached := nil;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetPixelRatio(const val: Integer);
begin
  if (FPixelRatio <> val) and (val > 0) then
  begin
    FPixelRatio := val;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetRotation(const val: Integer);
begin
  if val <> FRotation then
  begin
    FRotation := val;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetMirrorU(const val: Boolean);
begin
  if val <> FMirrorU then
  begin
    FMirrorU := val;
    NotifyChange(Self);
  end;
end;

procedure TGLAnimatedSprite.SetMirrorV(const val: Boolean);
begin
  if val <> FMirrorV then
  begin
    FMirrorV := val;
    NotifyChange(Self);
  end;
end;

// -----------------------------------------------------------------------------
initialization
// -----------------------------------------------------------------------------

  RegisterClasses([TGLAnimatedSprite,
    TGLSpriteAnimFrame, TGLSpriteAnimFrameList,
      TGLSpriteAnimation, TGLSpriteAnimationList]);

  RegisterXCollectionItemClass(TGLSpriteAnimFrame);
  RegisterXCollectionItemClass(TGLSpriteAnimation);

finalization

  UnregisterXCollectionItemClass(TGLSpriteAnimFrame);
  UnregisterXCollectionItemClass(TGLSpriteAnimation);

end.

