unit fwSprite;
// -----------------------------------------------------------------------
// Sprite Engine implementation
//
// Supports:
//   - Collision detection
//   - Animations
//   - 2D Rect Tiles
//   - Movement controllers
// -----------------------------------------------------------------------

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}

interface

uses RTLConsts,

     SysUtils,
     Classes,
     Math,
     ContNrs,

     
     fwTypes,
     fwObject,
     fwObjectlist,
     fwResource,
     fwAnimation,

     fwCustomEngine;

const
  spNormal         =  0;  // Sprite is visible
  spLeft           =  1;  // sprite cutted at left
  spTop            =  2;  // sprite cutted at top
  spTopLeft        =  3;
  spRight          =  4;  // sprite cutted at right
  spWidthSpan      =  5;
  spTopRight       =  6;
  spUpperSpan      =  7;
  spBottom         =  8;  // sprite cutted at bottom
  spBottomLeft     =  9;
  spHeightSpan     = 10;
  spLeftSpan       = 11;
  spRightBottom    = 12;
  spLowerSpan      = 13;
  spRightSpan      = 14;
  spOverlay        = 15;  // sprite is bigger than screen

type
  TSpriteState     = (
                       stNone,
                       stEnabled,
                       stDisabled,
                       stDieing,
                       stDied,
                       stHide
                     );

  TModifierState   = (
                       msNone,
                       msStarting,
                       msMoving,
                       msEnding,
                       msDone
                     );

  TfwWorldObject   = class;
  TfwWorldObjects  = class of TfwWorldObject;
  TfwModifyData    = class;
  TfwSpriteModifier= class;
  TfwModifierList  = class;
  TfwSprite        = class;
  TfwSpriteList    = class;
  TfwSpriteEngine  = class;

  TWorldRect       = record
    Left             : Integer;
    Top              : Integer;
    Right            : Integer;
    Bottom           : Integer;
  end;

  TfwWorldObject   = class(TfwObject)
  private
    fWidth            : Integer;
    fHeight           : Integer;
  protected
    fEngine           : TfwSpriteEngine;
    fParent           : TfwWorldObject;
    fItems            : TObjectList;
    fIsCollidable     : Boolean;
    fCheckViewport    : Boolean;
    fLayer            : Integer;
    fPosX             : Integer;   // Bezieht sich imm auf die
    fPosY             : Integer;
    fPosZ             : Integer;

    function  GetCount : Integer;
    function  GetItem(aIndex : Integer) : TfwWorldObject;

    function  GetWidth : Integer; virtual;
    function  GetHeight : Integer; virtual;
    procedure SetPosZ(aZ : Integer); virtual;
    function  GetSortOrder : Integer; virtual;

    // aSrcRect, aDstRect uses screen coordinates, not world coordinates
    function  MapTo(const aDrawRect, aViewport : TWorldRect; var aSrcRect : TCERect) : Cardinal;
    procedure DoDraw(aScreen : TfwCEDrawable; aDrawRect, aViewport : TWorldRect; aSpritePosition : Cardinal); virtual;
  public
    constructor Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil); dynamic;
    destructor Destroy; override;

    class function  IsSpriteEngine : Boolean; virtual;

    function  IsSelectable(aX, aY : Integer) : Boolean; virtual;
    function  IsVisible(aViewport : TWorldRect) : Boolean;
    function  IsCollide(aObject : TfwWorldObject; aDifX : Integer = 0; aDifY : Integer = 0) : Boolean; virtual;

    function  Add : TfwWorldObject; overload; virtual;
    function  Add(aObjectType : TfwWorldObjects) : TfwWorldObject; overload; virtual;
    procedure Add(aObject : TfwWorldObject); overload; virtual;
    function  Remove(aObject : TfwWorldObject) : Integer; overload; virtual;

    procedure SetSize(aWidth, aHeight : Integer); dynamic;
    procedure SetPosition(aPosX, aPosY : Integer; aPosZ : Integer = 0); dynamic;

    function  GetWorldRect : TWorldRect; virtual;
    function  WorldToScreen(aRect : TWorldRect) : TWorldRect; virtual;
    procedure ScreenToWorld(var aX, aY : Integer); overload; virtual;

    procedure Move(aTime : Cardinal); virtual;

    // aViewport use screen coordinates
    procedure Render(aScreen : TfwCEDrawable; aViewport : TWorldRect); overload;
    procedure Render(aDrawList : TfwSpriteList; aViewport : TWorldRect); overload;

    property Count : Integer read GetCount;
    property Items[aIndex : Integer] : TfwWorldObject read GetItem; default;

    property Engine : TfwSpriteEngine read fEngine;
    property Parent : TfwWorldObject read fParent;

    property IsCollidable : Boolean read fIsCollidable write fIsCollidable;
    property CheckViewport : Boolean read fCheckViewport write fCheckViewport;
    property Layer : Integer read fLayer write fLayer;
    property PosX : Integer read fPosX write fPosX;
    property PosY : Integer read fPosY write fPosY;
    property PosZ : Integer read fPosZ write SetPosZ;
    property Width : Integer read GetWidth;
    property Height : Integer read GetHeight;
    property SortOrder : Integer read GetSortOrder;
  end;

  TModStartNotify  = function (aSender : TfwSpriteModifier; aSprite : TfwSprite; aData : TfwModifyData) : Boolean of Object;
  TModMoveNotify   = function (aSender : TfwSpriteModifier; aSprite : TfwSprite; aData : TfwModifyData; aX, aY, aZ : Integer) : Boolean of Object;
  TModEndNotify    = procedure (aSender : TfwSpriteModifier; aSprite : TfwSprite; aData : TfwModifyData) of Object;

  TModOption       = (
                       moAutoDie,
                       moAutoLoop,
                       moRemoveSprite
                     );

  TModOptions      = Set of TModOption;

  TfwModifyData    = class(TfwObject)
  protected
    fState           : TModifierState;
    fOnStart         : TModStartNotify;
    fOnMove          : TModMoveNotify;
    fOnEnd           : TModEndNotify;
    fLocked          : Boolean;
  public
    constructor Create;
    property Locked : Boolean read fLocked;
    property OnStart : TModStartNotify read fOnStart write fOnStart;
    property OnMove : TModMoveNotify read fOnMove write fOnMove;
    property OnEnd : TModEndNotify read fOnEnd write FOnEnd;
    property State : TModifierState read fState;
  end;
  TfwModifyDataClass = class of TfwModifyData;

  TfwSpriteModifier= class(TfwObject)
  private
    fOwner           : TfwModifierList;
  protected
    function  GetHasOwner : Boolean;
    procedure DoStateNone(aSprite : TfwSprite; aData : TfwModifyData; aFireEndEvent : Boolean = False);
    function  DoStateStart(aSprite : TfwSprite; aData : TfwModifyData) : Boolean;
    function  DoStateMoving(aSprite : TfwSprite; aData : TfwModifyData; aX, aY, aZ : Integer) : Boolean;
    procedure DoStateEnd(aSprite : TfwSprite; aData : TfwModifyData);
    function  CreateData : TfwModifyData; virtual;
  public
    constructor Create();
    procedure Move(aTime : Cardinal; aSprite : TfwSprite; aData : TfwModifyData); virtual;
    property HasOwner : Boolean read GetHasOwner;
  end;

  // Simple Sample of Sprite Modifier
  TfwPosModifyData = class(TfwModifyData)
  protected
    fMoveCounter     : Integer;
    fDirX            : Integer;
    fDirY            : Integer;
  public
    constructor Create(aDirX, aDirY, aMoveCounter : Integer);
    property MoveCounter : Integer read fMoveCounter write fMoveCounter;
    property DirX : Integer read fDirX write fDirX;
    property DirY : Integer read fDirY write fDirY;
  end;

  TfwPosModifier   = class(TfwSpriteModifier)
  protected
    fMoveCounter     : Integer;
    fDirX            : Integer;
    fDirY            : Integer;
    function  CreateData : TfwModifyData; override;
  public
    constructor Create(aDirX, aDirY, aMoveCounter : Integer);
    procedure Move(aTime : Cardinal; aSprite : TfwSprite; aData : TfwModifyData); override;
  end;

  TfwModifierClass = class of TfwSpriteModifier;

  TfwModifierList  = class(TfwObject)
  protected
    fItems           : TfwObjectlist;
    function GetCount : Integer;
    function GetItem(aIndex : Integer) : TfwSpriteModifier;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Clear; dynamic;
    function  Add(aModifier : TfwSpriteModifier) : TfwSpriteModifier; dynamic;
    function  IndexOf(aModifier : TfwSpriteModifier) : Integer; dynamic;
    procedure Delete(aIndex : Integer); dynamic;
    procedure Remove(aModifier : TfwSpriteModifier); dynamic;

    property Count : Integer read GetCount;
    property Items[aIndex : Integer] : TfwSpriteModifier read GetItem; default;
  end;

  TfwSprite        = class(TfwWorldObject)
  protected
    fState            : TSpriteState;
    fData             : TfwModifyData;
    fModifier         : TfwSpriteModifier;

    fFrames           : TfwCESurface;
    fAlpha            : Byte;

    fAutoRemove       : Boolean;   // removes sprite marked State = stDead
                                   // from sprite engine list and free sprite
    fAnimController   : TfwAnimController;
    function  GetHasFrames : Boolean;
    function  GetAnimController : TfwAnimController;
    function  GetWidth : Integer; override;
    function  GetHeight : Integer; override;
    procedure SetImage(aImage : TfwCESurface);
    procedure DoDraw(aScreen : TfwCEDrawable; aDrawRect, aViewport : TWorldRect; aSpritePosition : Cardinal); override;
  public
    constructor Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil); override;
    destructor Destroy; override;

    function  IsCollide(aObject : TfwWorldObject; aDifX : Integer = 0; aDifY : Integer = 0) : Boolean; override;

    procedure AnimDone(aSequence : Integer); dynamic;
    procedure AnimNextFrame(); dynamic;

    procedure Hide(); dynamic;
    procedure Show(); dynamic;
    procedure Die(aSequenz : Integer = -1); dynamic;

    procedure SetState(aState : TSpriteState); virtual;
    procedure SetModifier(aModifier : TfwSpriteModifier); virtual;

    procedure Move(aTime : Cardinal); override;

    property AutoRemove : Boolean read fAutoRemove write fAutoRemove;
    property Data : TfwModifyData read fData;
    property State : TSpriteState read fState;

    property Image : TfwCESurface read fFrames write SetImage;

    property Alpha : Byte read fAlpha write fAlpha;
    property HasFrames : Boolean read GetHasFrames;
    property AnimController : TfwAnimController read GetAnimController;
  end;

  // Each tile contains different informations
  PTileItem        = ^TTileItem;
  TTileItem        = record
    ID               : Integer;           // unique identifer, defined by programmer
    Flags            : Cardinal;          // visible, walkable and more
    Surface          : TfwCEDrawable;     // Pointer to used surface
    Position         : TCERect;           // Position on given surface
    Animation        : TfwAnimSequences;  // optional animation for an surface
  end;

  PMapItem         = ^TMapItem;
  TMapItem         = packed record
    Tile             : PTileItem;
    Visible          : WordBool;       // is this tile visible?
    Layer            : Shortint;
    AnimFrame        : Smallint;
    AnimSequence     : Smallint;
    AnimSpeed        : Smallint;
    WorldPos         : TWorldRect;     // Pre caculated position in World
                                       // for faster rendering
  end;

  // Objekt zu Verwaltung einer Karte, bei der die einzelnen Tiles unter-
  // schiedliche Groessen haben koennen und daher nicht direkt ueber
  // eine Index erreichbar sind
  TfwTileMode      = (
                       tmOrthogonal,
                       tmISOMetric,
                       tmHexagonal
                     );

  TfwCustomTileMap = class(TfwWorldObject)
  protected
    fTileImages      : TfwCEDrawables;
    fVisible         : Boolean;
    fReservedTiles   : Integer;
    fMaxTiles        : Integer;
    fTileMode        : TfwTileMode;
    fTileItems       : array of TTileItem;
    fTileWidth       : Integer;
    fTileHeight      : Integer;
    fMaxMaps         : Integer;
    fWidth           : Integer;
    fHeight          : Integer;
    fMap             : array of TMapItem;

    procedure SetTileMode(aMode : TfwTileMode);
    function  GetTile(aTileID : Integer) : PTileItem;
    function  GetTileImage : TfwCEDrawable;
    procedure SetTileImage(aDrawable : TfwCEDrawable);
    function  GetMapItem(aX, aY : Integer) : PMapItem;
    // aSrcRect, aDstRect uses screen coordinates, not world coordinates
    procedure DoDraw(aScreen : TfwCEDrawable; aDestRect, aViewport : TWorldRect; aSpritePosition : Cardinal); override;
  public
    constructor Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil); override;
    destructor Destroy; override;

    function  AddTile(aID : Integer; aPosition : TCERect) : PTileItem;
    procedure AddTiles(aStartID, aCountX, aCountY, aOffsetX, aOffsetY, aWidth, aHeight, aSpaceX, aSpaceY : Integer);
    procedure SetMapSize(aX, aY, aTileWidth, aTileHeight : Integer);
    procedure AddMapItem(aTileID : Integer; aPos : TWorldRect; aVisible : Boolean = True); overload; virtual;
    procedure AddMapItem(aTileID : Integer; aX, aY : Integer; aVisible : Boolean = True); overload; virtual; 

    function  GetMapAtPixelPos(aX, aY : Integer) : PMapItem; virtual;
    
    procedure Show();
    procedure DoAnimation(); virtual;

    property Height : Integer read fHeight;
    property Map[aX, aY : Integer] : PMapItem read GetMapItem; default;
    property TileHeight : Integer read fTileHeight;
    property TileImage : TfwCEDrawable read GetTileImage write SetTileImage;
    property TileMode : TfwTileMode read fTileMode write SetTileMode;
    property TileWidth : Integer read fTileWidth;
    property Visible : Boolean read fVisible;
    property Width : Integer read fWidth;
  end;

  TfwOnCollideFunc = procedure (aItem1, aItem2 : TfwSprite);
  TfwOnCollideMeth = procedure (aItem1, aItem2 : TfwSprite) of Object;

  TfwSpriteList    = class(TObject)
  private
    fSpriteEngine    : TfwSpriteEngine;
    fItems           : TObjectList;
    function GetCount : Integer;
    function GetItem(aIndex : Integer) : TfwSprite;
  public
    constructor Create(aEngine : TfwSpriteEngine; aAutoRegister : Boolean = True);
    destructor Destroy; override;

    procedure Clear;
    procedure Add(aItem : TfwWorldObject);
    procedure Extract(aItem : TfwWorldObject);

    function  CheckCollision(aSprite : TfwSprite; aOnCollide : TfwOnCollideFunc = nil) : Boolean; overload; virtual;
    function  CheckCollision(aList : TfwSpriteList; aOnCollide : TfwOnCollideFunc = nil) : Integer; overload; virtual;
    procedure Move(aTime : Cardinal); virtual;
    procedure Render(aScreen : TfwCEDrawable; aViewport : TWorldRect); virtual;

    property Count : Integer read GetCount;
    property Items[aIndex : Integer] : TfwSprite read GetItem; default;
    property SpriteEngine : TfwSpriteEngine read fSpriteEngine;
  end;

  TfwSpriteEngine  = class(TfwWorldObject)
  protected
    fSprites         : TObjectList;
    fSpriteLists     : TObjectList;
    fModifiers       : TfwModifierList;
    procedure RemoveFromSpriteList(aItem : TfwWorldObject);
  public
    constructor Create(); reintroduce;
    destructor Destroy; override;

    class function IsSpriteEngine : Boolean; override;

    procedure RegisterSpriteList(aList : TfwSpriteList);
    procedure RemoveSprites(aClass : TfwWorldObjects); overload;
    procedure RemoveSprites(aList : TfwSpriteList); overload;
    procedure RemoveSprites(); overload;
    procedure RemoveSprite(aSprite : TfwSprite);

    procedure Move(aTime : Cardinal); override;
    procedure Render(aScreen : TfwCEDrawable; aViewport : TWorldRect); reintroduce;

    property Modifiers : TfwModifierList read fModifiers;
  end;

implementation

type
  TWorldPoint      = record
    x                : Integer;
    y                : Integer;
  end;

function IsRectEmpty(const Rect : TWorldRect) : Boolean;
begin
  Result :=(Rect.Right <= Rect.Left) or (Rect.Bottom <= Rect.Top);
end;

function PtInRect(const aRect : TWorldRect; const aPt : TWorldPoint) : Boolean;
begin
  Result := (aPt.X >= aRect.Left) and (aPt.X <= aRect.Right) and (aPt.Y >= aRect.Top) and (aPt.Y <= aRect.Bottom);
end;

function IntersectRect(var Rect : TWorldRect; const R1, R2 : TWorldRect) : Boolean;
begin
  Rect := R1;
  with R2 do begin
    if (Left > R1.Left) then Rect.Left:=Left;
    if (Top > R1.Top) then Rect.Top:=Top;
    if (Right < R1.Right) then Rect.Right:=Right;
    if (Bottom < R1.Bottom) then Rect.Bottom:=Bottom;
  end;

  if IsRectEmpty(Rect) then begin
    FillChar(Rect, SizeOf(Rect), 0);
    IntersectRect := False;
  end else
    IntersectRect := True;
end;

// TfwWorldObject
function  TfwWorldObject.GetCount : Integer;
begin
  Result := -1;
  if Assigned(fItems) then begin
    Result := fItems.Count;
  end;
end;

function TfwWorldObject.GetItem(aIndex : Integer) : TfwWorldObject;
begin
  if Assigned(fItems) then begin
    if (fItems[aIndex] is TfwWorldObject) then begin
      Result := (fItems[aIndex] as TfwWorldObject);
    end else begin
      raise Exception.Create('Invalid Objecttyp "' + fItems[aIndex].ClassName + '" in list of TfwWorldObjects');
    end;
  end else begin
    raise Exception.Create(SConvIllegalType);
  end;
end;

function TfwWorldObject.GetWidth : Integer;
begin
  Result := fWidth;
end;

function TfwWorldObject.GetHeight : Integer;
begin
  Result := fHeight;
end;

procedure TfwWorldObject.SetPosZ(aZ : Integer);
var
  iLoop            : Integer;
  iPos             : Integer;
begin
  if Assigned(Engine) then begin
    iPos := Engine.fItems.IndexOf(Self);
    for iLoop := 0 to Pred(Engine.Count) do begin
      if (iPos <> iLoop) then begin
        if (Engine[iLoop].PosZ > aZ) then begin
          Engine.fItems.Move(iPos, iLoop);
          Break;
        end;
      end;
    end;
  end;
  fPosZ := aZ;
end;

function TfwWorldObject.GetSortOrder : Integer;
begin
  Result := fPosY - fPosZ;
end;

function TfwWorldObject.MapTo(const aDrawRect, aViewport : TWorldRect; var aSrcRect : TCERect) : Cardinal;
var
  iWidth           : Integer;
  iHeight          : Integer;
begin
  Result := 0;
  if (aDrawRect.Left   < aViewport.Left)   then Result := Result or spLeft;
  if (aDrawRect.Top    < aViewport.Top)    then Result := Result or spTop;
  if (aDrawRect.Right  > aViewport.Right)  then Result := Result or spRight;
  if (aDrawRect.Bottom > aViewport.Bottom) then Result := Result or spBottom;

  iWidth     := Abs(aDrawRect.Right - aDrawRect.Left);
  iHeight    := Abs(aDrawRect.Bottom - aDrawRect.Top);

  aSrcRect.x      := 0;
  aSrcRect.y      := 0;
  aSrcRect.Width  := iWidth;
  aSrcRect.Height := iHeight;

  if (Result = spOverlay) then begin
    aSrcRect.x := fEngine.PosX;
    aSrcRect.y := fEngine.PosY;
  end else begin
    if ((Result and spLeft) > 0) then begin
      aSrcRect.X     := aSrcRect.x + (Width - iWidth);
      aSrcRect.Width := iWidth;
    end;

    if ((Result and spTop) > 0) then begin
      aSrcRect.Y      := aSrcRect.y + (Height - iHeight);
      aSrcRect.Height := iHeight;
    end;

    if ((Result and spRight) > 0) then begin
      aSrcRect.Width := iWidth;
    end;

    if ((Result and spBottom) > 0) then begin
      aSrcRect.Height := iHeight;
    end;
  end;
end;

procedure TfwWorldObject.DoDraw(aScreen : TfwCEDrawable; aDrawRect, aViewport : TWorldRect; aSpritePosition : Cardinal);
// aScreen   - destination Surface
// aDrawRect - X/Y position on screen
// aViewport - Visible area on screen
begin
end;

constructor TfwWorldObject.Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil);
begin
  inherited Create;

  fItems  := TObjectList.Create;
  fEngine := aEngine;
  fParent := aParent;
end;

destructor TfwWorldObject.Destroy;
begin
  FreeAndNil(fItems);

  inherited Destroy;
end;

class function TfwWorldObject.IsSpriteEngine : Boolean;
begin
  Result := False;
end;

function TfwWorldObject.IsSelectable(aX, aY : Integer) : Boolean;
begin
  Result := False;
end;

function TfwWorldObject.IsVisible(aViewport : TWorldRect) : Boolean;
var
  ScreenPos        : TWorldRect;
  DrawRect         : TWorldRect;
begin
  Result    := False;
  ScreenPos := WorldToScreen(GetWorldRect());
  DrawRect  := ScreenPos;
  if fCheckViewport then begin
    if not IntersectRect(DrawRect, aViewport, ScreenPos) then begin
      Exit;
    end;
  end;
  Result := True;
end;

function TfwWorldObject.IsCollide(aObject : TfwWorldObject; aDifX : Integer = 0; aDifY : Integer = 0) : Boolean;
var
  aRect1           : TWorldRect;
  aRect2           : TWorldRect;
  aRect3           : TWorldRect;
begin
  Result := False;
  if fIsCollidable then begin
    aRect1 := GetWorldRect();
    aRect2 := aObject.GetWorldRect();
    if (aDifX <> 0) or (aDifY <> 0) then begin
      aRect2.Left   := aRect2.Left + aDifX;
      aRect2.Top    := aRect2.Top + aDifY;
      aRect2.Right  := aRect2.Right + aDifX;
      aRect2.Bottom := aRect2.Bottom + aDifY;
    end;

    if IntersectRect(aRect3, aRect1, aRect2) then begin
      Result := True;
      Exit;
    end;
  end;
end;

function TfwWorldObject.Add : TfwWorldObject;
begin
  Result := nil;
  if Assigned(fItems) then begin
    Result := TfwWorldObject.Create(Engine, Self);
    if Assigned(Result) then begin
      fItems.Add(Result);
    end;
  end;
end;

function TfwWorldObject.Add(aObjectType : TfwWorldObjects) : TfwWorldObject;
begin
  Result := nil;
  if Assigned(fItems) then begin
    Result := aObjectType.Create(Engine, Self);
    if Assigned(Result) then begin
      fItems.Add(Result);
    end;
  end;
end;

procedure TfwWorldObject.Add(aObject : TfwWorldObject);
begin
  if Assigned(fItems) then begin
    aObject.fEngine := Engine;
    aObject.fParent := Self;
    fItems.Add(aObject);
  end;
end;

function TfwWorldObject.Remove(aObject : TfwWorldObject) : Integer;
begin
  Result := -1;
  if Assigned(fItems) then begin
    Result := fItems.Remove(aObject);
  end;
end;

procedure TfwWorldObject.SetSize(aWidth, aHeight : Integer);
begin
  fWidth  := aWidth;
  fHeight := aHeight;
end;

procedure TfwWorldObject.SetPosition(aPosX, aPosY : Integer; aPosZ : Integer = 0);
begin
  fPosX := aPosX;
  fPosY := aPosY;
  PosZ  := aPosZ;
end;

function TfwWorldObject.GetWorldRect : TWorldRect;
var
  Parent           : TfwWorldObject;
begin
  Result.Left   := PosX;
  Result.Top    := PosY;
  Result.Right  := PosX + Width;
  Result.Bottom := PosY + Height;
  Parent        := fParent;
  if Assigned(Parent) then begin
    repeat
       // Position of sprite egnine is not part of real world
       // position, it is a offset to screen position
       if not Parent.IsSpriteEngine then begin
         Result.Left   := Result.Left   + Parent.PosX;
         Result.Top    := Result.Top    + Parent.PosY;
         Result.Right  := Result.Right  + Parent.PosX;
         Result.Bottom := Result.Bottom + Parent.PosY;
       end;

       Parent := Parent.Parent;
    until (Parent = nil);
  end;
end;

function  TfwWorldObject.WorldToScreen(aRect : TWorldRect) : TWorldRect;
begin
  if Assigned(fEngine) then begin
    Result.Left   := aRect.Left   + fEngine.PosX;
    Result.Top    := aRect.Top    + fEngine.PosY;
    Result.Right  := aRect.Right  + fEngine.PosX;
    Result.Bottom := aRect.Bottom + fEngine.PosY;
  end;
end;

procedure TfwWorldObject.ScreenToWorld(var aX, aY : Integer); 
begin
  if Assigned(fEngine) then begin
    aX := aX + fEngine.PosX;
    aY := aY + fEngine.PosY;
  end;
end;

procedure TfwWorldObject.Move(aTime : Cardinal);
begin
end;

procedure TfwWorldObject.Render(aScreen : TfwCEDrawable; aViewport : TWorldRect);
var
  ScreenPos        : TWorldRect;
  DrawRect         : TWorldRect;
  uBitPos          : Cardinal;
begin
  ScreenPos := WorldToScreen(GetWorldRect());
  DrawRect  := ScreenPos;
  if fCheckViewport then begin
    if not IntersectRect(DrawRect, aViewport, ScreenPos) then begin
      Exit;
    end;
  end;

  uBitPos := 0;
  if (ScreenPos.Left   < aViewport.Left)   then uBitPos := uBitPos or spLeft;
  if (ScreenPos.Top    < aViewport.Top)    then uBitPos := uBitPos or spTop;
  if (ScreenPos.Right  > aViewport.Right)  then uBitPos := uBitPos or spRight;
  if (ScreenPos.Bottom > aViewport.Bottom) then uBitPos := uBitPos or spBottom;

  // Das Sprite ist sichtbar, dann zeichne es auch. Dazu muss der
  // sichtbare Teil des Sprites ermittelt werden, welcher dann in denn
  // Viewport gezeichnet werden soll.
  DoDraw(aScreen, DrawRect, aViewport, uBitPos);
end;

procedure TfwWorldObject.Render(aDrawList : TfwSpriteList; aViewport : TWorldRect);
var
  ScreenPos        : TWorldRect;
  DrawRect         : TWorldRect;
begin
  ScreenPos := WorldToScreen(GetWorldRect());
  DrawRect  := ScreenPos;
  if fCheckViewport then begin
    if not IntersectRect(DrawRect, aViewport, ScreenPos) then begin
      Exit;
    end;
  end;

  aDrawList.Add(Self);
end;

//  ----------------------------------------------------------------------
// TfwModifyData
constructor TfwModifyData.Create;
begin
  inherited Create;

  fState    := msNone;
  fOnStart  := nil;
  fOnMove   := nil;
  fOnEnd    := nil;
  fLocked   := False;
end;

//  ----------------------------------------------------------------------
// TfwPosModifier
function TfwSpriteModifier.GetHasOwner : Boolean;
begin
  Result := Assigned(fOwner);
end;

procedure TfwSpriteModifier.DoStateNone(aSprite : TfwSprite; aData : TfwModifyData; aFireEndEvent : Boolean = False);
begin
  if aFireEndEvent then begin
    if (aData.State > msNone) and (aData.State < msEnding) then begin
      DoStateEnd(aSprite, aData);
    end;
  end;
  aData.fState := msNone;
end;

function TfwSpriteModifier.DoStateStart(aSprite : TfwSprite; aData : TfwModifyData) : Boolean;
begin
  aData.fState := msStarting;
  try
    if Assigned(aData.fOnStart) then begin
      if not aData.fOnStart(Self, aSprite, aData) then begin
        Result := False;
        Exit;
      end;
    end;
    Result := True;
  finally
    aData.fState := msMoving;
  end;
end;

function TfwSpriteModifier.DoStateMoving(aSprite : TfwSprite; aData : TfwModifyData; aX, aY, aZ : Integer) : Boolean;
begin
  if Assigned(aData.fOnMove) then begin
    if not aData.fOnMove(Self, aSprite, aData, aX, aY, aZ) then begin
      Result := False;
      Exit;
    end;
  end;

  aData.fState := msMoving;
  Result       := True; 
end;

procedure TfwSpriteModifier.DoStateEnd(aSprite : TfwSprite; aData : TfwModifyData);
begin
  aData.fState := msEnding;
  try
    if Assigned(aData.fOnEnd) then begin
      aData.fOnEnd(Self, aSprite, aData);
    end;
  finally
    aData.fState := msDone;
  end;
end;

function TfwSpriteModifier.CreateData : TfwModifyData;
begin
  Result := TfwModifyData.Create;
end;

constructor TfwSpriteModifier.Create();
begin
  inherited Create();

  fOwner := nil;
end;

procedure TfwSpriteModifier.Move(aTime : Cardinal; aSprite : TfwSprite; aData : TfwModifyData);
begin
end;

// -----------------------------------------------------------------------
// TfwPosModifyData
constructor TfwPosModifyData.Create(aDirX, aDirY, aMoveCounter : Integer);
begin
  inherited Create;

  fMoveCounter := aMoveCounter;
  fDirX        := aDirX;
  fDirY        := aDirY;
end;

// -----------------------------------------------------------------------
// TfwPosModifier
function TfwPosModifier.CreateData : TfwModifyData;
begin
  Result := TfwPosModifyData.Create(fDirX, fDirY, fMoveCounter);
end;

constructor TfwPosModifier.Create(aDirX, aDirY, aMoveCounter : Integer);
begin
  inherited Create();

  fMoveCounter := aMoveCounter;
  fDirX        := aDirX;
  fDirY        := aDirY;
end;

procedure TfwPosModifier.Move(aTime : Cardinal; aSprite : TfwSprite; aData : TfwModifyData);
begin
  if Assigned(aSprite) then begin
    if (aData is TfwPosModifyData) then begin
      aSprite.PosX := aSprite.PosX + (aData as TfwPosModifyData).DirX;
      aSprite.PosY := aSprite.PosY + (aData as TfwPosModifyData).DirY;
      Dec((aData as TfwPosModifyData).fMoveCounter);
      if ((aData as TfwPosModifyData).MoveCounter <= 0) then begin
        (aData as TfwPosModifyData).fState := msDone;
        aSprite.SetState(stDied);
      end;
    end;
  end;
end;

// -----------------------------------------------------------------------
// TfwModifierList
function TfwModifierList.GetCount : Integer;
begin
  Result := -1;
  if Assigned(fItems) then begin
    Result := fItems.Count;
  end;
end;

function TfwModifierList.GetItem(aIndex : Integer) : TfwSpriteModifier;
begin
  Result := nil;
  if Assigned(fItems) then begin
    Result := (fItems[aIndex] as TfwSpriteModifier);
  end;
end;

constructor TfwModifierList.Create;
begin
  inherited Create;

  fItems := TfwObjectlist.Create(True);
end;

destructor TfwModifierList.Destroy;
begin
  FreeAndNil(fItems);
  
  inherited Destroy;
end;

procedure TfwModifierList.Clear;
begin
  fItems.Clear;
end;

function TfwModifierList.Add(aModifier : TfwSpriteModifier) : TfwSpriteModifier;
begin
  Result := aModifier;
  if Assigned(fItems) then begin
    aModifier.fOwner := Self;
    fItems.Add(aModifier);
  end;
end;

function TfwModifierList.IndexOf(aModifier : TfwSpriteModifier) : Integer;
begin
  Result := -1;
  if Assigned(fItems) then begin
    Result := fItems.IndexOf(aModifier);
  end;
end;

procedure TfwModifierList.Delete(aIndex : Integer);
begin
  if Assigned(fItems) then begin
    fItems.Delete(aIndex);
  end;
end;

procedure TfwModifierList.Remove(aModifier : TfwSpriteModifier);
begin
  if Assigned(fItems) then begin
    fItems.Remove(aModifier);
  end;
end;

// TfwSprite -------------------------------------------------------------
function TfwSprite.GetHasFrames : Boolean;
begin
  Result := False;
  if Assigned(fAnimController) then begin
    Result := fAnimController.HasAnimation;
  end;
end;

function TfwSprite.GetAnimController : TfwAnimController;
begin
  if not Assigned(fAnimController) then begin
    fAnimController := TfwAnimController.Create(Self);
  end;
  Result := fAnimController;
end;

function TfwSprite.GetWidth : Integer;
begin
  Result := inherited GetWidth;
  if HasFrames then begin
    // animation assigned, get source rect from surface
    Result := fAnimController.GetRect().Width;
  end;
end;

function TfwSprite.GetHeight : Integer;
begin
  Result := inherited GetHeight;
  if HasFrames then begin
    if Assigned(fAnimController) then begin
      // animation assigned, get source rect from surface
      Result := fAnimController.GetRect().Height;
    end;
  end;
end;

procedure TfwSprite.SetImage(aImage : TfwCESurface);
begin
  fFrames := aImage;
  if Assigned(aImage) then begin
    fWidth  := aImage.Width;
    fHeight := aImage.Height;
  end;
end;

procedure TfwSprite.DoDraw(aScreen : TfwCEDrawable; aDrawRect, aViewport : TWorldRect; aSpritePosition : Cardinal);
// aScreen   - destination Surface
// aDrawRect - X/Y position on screen
// aViewport - Visible area on screen
var
  src              : TCERect;
  dst              : TCERect;
  iWidth           : Integer;
  iHeight          : Integer;
  iOffsetX         : Integer;
  iOffSetY         : Integer;
  Sequence         : TfwAnimSequence;
begin
  if (fState in [stDied, stHide]) then begin
    Exit;
  end;

  // sprite surface starts always on 0/0
  iOffsetX   := 0;
  iOffSetY   := 0;
  src.X      := 0;
  src.Y      := 0;
  src.Width  := Width;
  src.Height := Height;
  iWidth     := Abs(aDrawRect.Right - aDrawRect.Left);
  iHeight    := Abs(aDrawRect.Bottom - aDrawRect.Top);

  if HasFrames then begin
    // animation enabled
    Sequence := fAnimController.Active;
    if Assigned(Sequence) then begin
      // animation assigned, get source rect from surface
      iOffsetX := Sequence.OffsetX;
      iOffsetY := Sequence.OffsetY;
      src      := fAnimController.GetRect();
    end;
  end;

  if (aSpritePosition = spOverlay) then begin
    src.x := fEngine.PosX;
    src.y := fEngine.PosY;
  end else begin
    if ((aSpritePosition and spLeft) > 0) then begin
      src.x     := src.x + (Width - iWidth);
      src.Width := iWidth;
    end;

    if ((aSpritePosition and spTop) > 0) then begin
      src.Y      := src.y + (Height - iHeight);
      src.Height := iHeight;
    end;

    if ((aSpritePosition and spRight) > 0) then begin
      src.Width := iWidth;
    end;

    if ((aSpritePosition and spBottom) > 0) then begin
      src.Height := iHeight;
    end;
  end;

  // Destination on screen
  dst.x      := aDrawRect.Left + iOffsetX;
  dst.y      := aDrawRect.Top + iOffsetY;
  dst.Width  := aDrawRect.Right - aDrawRect.Left + iOffsetX;
  dst.Height := aDrawRect.Bottom - aDrawRect.Top + iOffsetY;

  if Assigned(fFrames) then begin
    if fFrames.HasSurface then begin
      fFrames.Blit(aScreen, Src, Dst);
    end;
  end;
end;

constructor TfwSprite.Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil);
begin
  inherited Create(aEngine, aParent);

  fAutoRemove     := False;
  fAnimController := nil;
  fData           := nil;
  fModifier       := nil;
  fIsCollidable   := True;
  fCheckViewport  := True;
  fState          := stNone;
end;

destructor TfwSprite.Destroy;
begin
  FreeAndNil(fData);
  FreeAndNil(fAnimController);
  if Assigned(fModifier) then begin
    if not fModifier.HasOwner then begin
      FreeAndNil(fModifier);
    end else begin
      fModifier := nil;
    end;
  end;

  inherited Destroy;
end;

function TfwSprite.IsCollide(aObject : TfwWorldObject; aDifX : Integer = 0; aDifY : Integer = 0) : Boolean;
begin
  Result := inherited IsCollide(aObject, aDifX, aDifY);
  if Result then begin
    if (aObject is TfwSprite) then begin
      // Pixel Kollision
    end;
  end;
end;

procedure TfwSprite.AnimDone(aSequence : Integer);
begin
end;

procedure TfwSprite.AnimNextFrame();
var
  iFrame           : Integer;
begin
  if HasFrames then begin
    if (State in [stNone, stEnabled, stDieing]) then begin
      iFrame := fAnimController.Frame;
      if fAnimController.DoAnimation then begin
        if (iFrame <> 0) and (fAnimController.Frame = 0)
        then begin
          AnimDone(fAnimController.Sequence);
        end;
        
        if fAnimController.Active.AutoDie then begin
          Die();
        end;
      end;
    end;
  end;
end;

procedure TfwSprite.Hide();
begin
  SetState(stNone);
end;

procedure TfwSprite.Show();
begin
  SetState(stEnabled);
end;

procedure TfwSprite.Die(aSequenz : Integer = -1);
begin
  if (State = stDieing) then begin
    SetState(stDied);
  end else begin
    if (aSequenz < 0) then begin
      if Assigned(fAnimController) then begin
        if fAnimController.HasAnimation then begin
          SetState(stDieing);
          fAnimController.Sequence := fAnimController.DieSequence;
        end else begin
          SetState(stDied);
        end;
      end else begin
        SetState(stDied);
      end;
    end else begin
      SetState(stDieing);
      if Assigned(fAnimController) then begin
        fAnimController.Sequence := aSequenz;
      end;
    end;
  end;
end;

procedure TfwSprite.SetState(aState : TSpriteState);
begin
  if (aState <> fState) then begin
    fState := aState;
  end;
end;

procedure TfwSprite.SetModifier(aModifier : TfwSpriteModifier); 
begin
  if Assigned(fData) then begin
    FreeAndNil(fData);
  end;

  fModifier := aModifier;
  fData     := fModifier.CreateData;
end;

procedure TfwSprite.Move(aTime : Cardinal);
begin
  if (State in [stEnabled, stDieing]) then begin
    if Assigned(fModifier) then begin
      fModifier.Move(aTime, Self, fData);
    end;
    AnimNextFrame();
  end;
end;

// TfwTileSprite ---------------------------------------------------------
procedure TfwCustomTileMap.SetTileMode(aMode : TfwTileMode);
begin
  if (aMode <> fTileMode) then begin
    if (fMaxMaps > 0) then begin
      raise Exception.Create('TileMode can''t changed when map is not empty!');
    end;
    fTileMode := aMode;
  end;
end;

function TfwCustomTileMap.GetTile(aTileID : Integer) : PTileItem;
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(Length(fTileItems)) do begin
    if (fTileItems[iLoop].ID = aTileID) then begin
      Result := @fTileItems[iLoop];
      Exit;
    end;
  end;
  Result := nil;
end;

function TfwCustomTileMap.GetTileImage : TfwCEDrawable;
begin
  Result := nil;
  if (fTileImages.Count > 0) then begin
    Result := fTileImages[0];
  end;
end;

procedure TfwCustomTileMap.SetTileImage(aDrawable : TfwCEDrawable);
begin
  if (fTileImages.Count > 0) then begin
    fTileImages[0] := aDrawable;
  end else begin
    fTileImages.Add(aDrawable);
  end;
end;

function TfwCustomTileMap.GetMapItem(aX, aY : Integer) : PMapItem;
var
  iIndex           : Integer;
begin
  Result := nil;
  if (aX >= 0) and (aX < fWidth) and (aY >= 0) and (aY < fHeight)
  then begin
    iIndex := (aY * fWidth) + aY;
    if (iIndex < fMaxMaps) then begin
      Result := @fMap[iIndex];
    end;
  end;
end;

procedure TfwCustomTileMap.DoDraw(aScreen : TfwCEDrawable; aDestRect, aViewport : TWorldRect; aSpritePosition : Cardinal);
var
  iLoop            : Integer;
  MapRect          : TWorldRect;
  DrawRect         : TWorldRect;
  SrcRect          : TCERect;
  DstRect          : TCERect;
begin
  if fVisible then begin
    for iLoop := 0 to Pred(fMaxMaps) do begin
      with fMap[iLoop] do begin
        if Visible then begin
          if not Assigned(Tile) then begin
            // mhm, kein Pointer auf das Tile vorhanden, das
            // duerfte nicht vorkommen
            Exit;
          end;

          if not Assigned(Tile.Surface) then begin
            // mhm, kein Pointer die zugeordnet Grafik des Tile vorhanden
            Exit;
          end;

          // Verschiebe das anzuzeigende Recht an die korrekte Bildschirm-
          // position.
          MapRect.Left   := WorldPos.Left   + fEngine.PosX;
          MapRect.Top    := WorldPos.Top    + fEngine.PosY;
          MapRect.Right  := WorldPos.Right  + fEngine.PosX;
          MapRect.Bottom := WorldPos.Bottom + fEngine.PosY;

          if IntersectRect(DrawRect, MapRect, aViewport) then begin
            SrcRect := Tile^.Position;
            if Assigned(Tile^.Animation) then begin
              if (Tile^.Animation.Count > 0) then begin
                SrcRect := Tile^.Animation.GetRect(AnimSequence, AnimFrame);
              end;
            end;

            if (MapRect.Left < 0) or (MapRect.Top < 0) then begin
              // Das Rechteck ist links oder oben abgeschnitten
              if (MapRect.Left < 0) then begin
                SrcRect.X := SrcRect.X - MapRect.Left;
              end;

              if (MapRect.Top < 0) then begin
                SrcRect.Y := SrcRect.Y - MapRect.Top;
              end;

              SrcRect.Width := DrawRect.Right - DrawRect.Left;
              DstRect.Height := DrawRect.Bottom - DrawRect.Top;
            end;

            // Das Rechteck ist komplett sichtbar
            DstRect.x      := DrawRect.Left;
            DstRect.y      := DrawRect.Top;
            DstRect.Width  := DrawRect.Right - DrawRect.Left;
            DstRect.Height := DrawRect.Bottom - DrawRect.Top;
            Tile^.Surface.Blit(aScreen, SrcRect, DstRect);
          end;
        end;
      end;
    end;
  end;
end;

constructor TfwCustomTileMap.Create(aEngine : TfwSpriteEngine; aParent : TfwWorldObject = nil);
begin
  inherited Create(aEngine, aParent);

  fTileMode      := tmOrthogonal;
  fTileImages    := TfwCEDrawables.Create(False);
  fIsCollidable  := False;
  fReservedTiles := 0;
  fMaxTiles      := 0;
  SetLength(fTileItems, 0);

  fMaxMaps       := 0;
  SetLength(fMap, 0);
end;

destructor TfwCustomTileMap.Destroy;
begin
  SetLength(fTileItems, 0);
  SetLength(fMap, 0);

  inherited Destroy;
end;

function TfwCustomTileMap.AddTile(aID : Integer; aPosition : TCERect) : PTileItem;
var
  iLoop            : Integer;
begin
  if (fMaxTiles >= fReservedTiles) then begin
    SetLength(fTileItems, fReservedTiles + 16);
    for iLoop := fMaxTiles to Pred(fMaxTiles + 16) do begin
      // for easy debugging setup values
      fTileItems[iLoop].ID        := -(iLoop + 1);
      fTileItems[iLoop].Flags     := 0;
      fTileItems[iLoop].Surface   := nil;
      fTileItems[iLoop].Animation := nil;
      fTileItems[iLoop].Position  := CERectCreate(0, 0, 0, 0);
    end;
    Inc(fReservedTiles, 16);
  end;

  Result := @fTileItems[fMaxTiles];
  with Result^ do begin
    ID       := aID;
    Surface  := TileImage;
    Position := aPosition;
  end;
  Inc(fMaxTiles);
end;

procedure TfwCustomTileMap.AddTiles(aStartID, aCountX, aCountY, aOffsetX, aOffsetY, aWidth, aHeight, aSpaceX, aSpaceY : Integer);
var
  iLoopX           : Integer;
  iLoopY           : Integer;
  TilePos          : TCERect;
begin
  for iLoopY := 0 to Pred(aCountY) do begin
    for iLoopX := 0 to Pred(aCountX) do begin
      TilePos.X      := aOffsetX + iLoopX * (aWidth  + aSpaceX);
      TilePos.Y      := aOffsetY + iLoopY * (aHeight + aSpaceY);
      TilePos.Width  := aWidth;
      TilePos.Height := aHeight;
      AddTile(aStartID, TilePos);
      Inc(aStartID);
    end;
  end;
end;

procedure TfwCustomTileMap.SetMapSize(aX, aY, aTileWidth, aTileHeight : Integer);
begin
  SetLength(fMap, aY * aX);
  fTileWidth  := aTileWidth;
  fTileHeight := aTileHeight;
  fWidth      := aX;
  fHeight     := aY;
end;

procedure TfwCustomTileMap.AddMapItem(aTileID : Integer; aPos : TWorldRect; aVisible : Boolean = True);
begin
  if (fMaxMaps < Length(fMap)) then begin
    with fMap[fMaxMaps] do begin
      Tile         := GetTile(aTileID);
      Visible      := aVisible;
      WorldPos     := aPos;
      AnimFrame    := 0;
      AnimSequence := 0;
      AnimSpeed    := 50;
    end;

    Inc(fMaxMaps);
  end;
end;

procedure TfwCustomTileMap.AddMapItem(aTileID : Integer; aX, aY : Integer; aVisible : Boolean = True);
var
  Rect             : TWorldRect;
  Tile             : PTileItem;
begin
  Tile := GetTile(aTileID);
  if Assigned(Tile) then begin
    if (fTileMode = tmOrthogonal) then begin
      Rect.Left   := aX * fTileWidth;
      Rect.Top    := aY * fTileHeight;
    end else begin
      raise Exception.Create('TileMode not defined');
    end;
    Rect.Right  := Rect.Left + Tile^.Position.Width;
    Rect.Bottom := Rect.Top  + Tile^.Position.Height;
    AddMapItem(aTileID, Rect, aVisible);
  end;
end;

function TfwCustomTileMap.GetMapAtPixelPos(aX, aY : Integer) : PMapItem;
var
  iLoop            : Integer;
  Pt               : TWorldPoint;
  Rect             : TWorldRect;
begin
  Result := nil;
  Pt.X := aX;
  Pt.Y := aY;
  for iLoop := 0 to Pred(fMaxTiles) do begin
    Rect := fMap[iLoop].WorldPos;
    if PtInRect(Rect, Pt) then begin
      Result := @fMap[iLoop];
      Exit;
    end;
  end;
end;

procedure TfwCustomTileMap.Show();
begin
  fVisible := True;
end;

procedure TfwCustomTileMap.DoAnimation();
var
  iLoop            : Integer;
  MapRect          : TWorldRect;
  DrawRect         : TWorldRect;
  SrcRect          : TCERect;
  DstRect          : TCERect;
begin
  if fVisible then begin
    for iLoop := 0 to Pred(fMaxMaps) do begin
      with fMap[iLoop] do begin
        if not Assigned(Tile) then begin
          // mhm, kein Pointer auf das Tile vorhanden, das
          // duerfte nicht vorkommen
          Exit;
        end;

        if not Assigned(Tile.Surface) then begin
          // mhm, kein Pointer die zugeordnet Grafik des Tile vorhanden
          Exit;
        end;

        if Assigned(Tile^.Animation) then begin
          if (Tile^.Animation.Count > 0) then begin
          end;
        end;
      end;
    end;
  end;
end;

// TfwDrawList -----------------------------------------------------------
function TfwSpriteList.GetCount : Integer;
begin
  Result := fItems.Count;
end;

function TfwSpriteList.GetItem(aIndex : Integer) : TfwSprite;
begin
  Result := (fItems[aIndex] as TfwSprite);
end;

constructor TfwSpriteList.Create(aEngine : TfwSpriteEngine; aAutoRegister : Boolean = True);
begin
  inherited Create;

  fSpriteEngine := nil;
  fItems        := TObjectList.Create(False);
  if Assigned(aEngine) then begin
    fSpriteEngine := aEngine;
    if aAutoRegister then begin
      fSpriteEngine.RegisterSpriteList(Self);
    end;
  end;
end;

destructor TfwSpriteList.Destroy;
begin
  FreeAndNil(fItems);
  
  inherited Destroy;
end;

procedure TfwSpriteList.Clear;
begin
  fItems.Clear;
end;

procedure TfwSpriteList.Add(aItem : TfwWorldObject);
begin
  fItems.Add(aItem);
end;

procedure TfwSpriteList.Extract(aItem : TfwWorldObject);
begin
  fItems.Extract(aItem);
end;

function TfwSpriteList.CheckCollision(aSprite : TfwSprite; aOnCollide : TfwOnCollideFunc = nil) : Boolean;
var
  iLoop1           : Integer;
  Item             : TfwSprite;
begin
  Result := False;
  if (aSprite.State = stEnabled) then begin
    for iLoop1 := 0 to Pred(Count) do begin
      Item := Items[iLoop1];
      if (Item.State = stEnabled) then begin
        if aSprite.IsCollide(Item) then begin
          if Assigned(aOnCollide) then begin
            Result := True;
            aOnCollide(aSprite, Item);
            Break;
          end;
        end;
      end;
    end;
  end;
end;

function TfwSpriteList.CheckCollision(aList : TfwSpriteList; aOnCollide : TfwOnCollideFunc = nil) : Integer;
var
  iLoop1           : Integer;
  iLoop2           : Integer;
  Item1            : TfwSprite;
  Item2            : TfwSprite;
begin
  Result := 0;
  for iLoop1 := 0 to Pred(Count) do begin
    Item1 := Items[iLoop1];
    if (Item1.State = stEnabled) then begin
      for iLoop2 := 0 to Pred(aList.Count) do begin
        Item2 := aList[iLoop2];
        if (Item2.State = stEnabled) then begin
          if Item1.IsCollide(Item2) then begin
            if Assigned(aOnCollide) then begin
              aOnCollide(Item1, Item2);
            end;
            Inc(Result);
          end;
        end;
      end;
    end;
  end;
end;

procedure TfwSpriteList.Move(aTime : Cardinal);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(Count) do begin
    Items[iLoop].Move(aTime);
  end;
end;

procedure TfwSpriteList.Render(aScreen : TfwCEDrawable; aViewport : TWorldRect);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(fItems.Count) do begin
    (fItems[iLoop] as TfwWorldObject).Render(aScreen, aViewport);
  end;
end;

// TfwSpriteEngine -------------------------------------------------------
procedure TfwSpriteEngine.RemoveFromSpriteList(aItem : TfwWorldObject);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(fSpriteLists.Count) do begin
    (fSpriteLists[iLoop] as TfwSpriteList).Extract(aItem);
  end;
end;

constructor TfwSpriteEngine.Create();
begin
  inherited Create(Self, nil);

  fSprites     := TObjectList.Create(False);
  fSpriteLists := TObjectList.Create(True);
  fModifiers   := TfwModifierList.Create;
end;

destructor TfwSpriteEngine.Destroy;
begin
  FreeAndNil(fModifiers);
  FreeAndNil(fSpriteLists);
  FreeAndNil(fSprites);

  inherited Destroy;
end;

class function TfwSpriteEngine.IsSpriteEngine : Boolean;
begin
  Result := True;
end;

procedure TfwSpriteEngine.RegisterSpriteList(aList : TfwSpriteList);
begin
  if (fSpriteLists.IndexOf(aList) < 0) then begin
    fSpriteLists.Add(aList);
  end;
end;

procedure TfwSpriteEngine.RemoveSprites(aClass : TfwWorldObjects);
var
  iLoop            : Integer;
  Item             : TfwWorldObject;
begin
  for iLoop := Pred(Count) downto 0 do begin
    Item := Items[iLoop];
    if (Item is aClass) then begin
      RemoveFromSpriteList(Item);
      fItems.Delete(iLoop);
    end;
  end;
end;

procedure TfwSpriteEngine.RemoveSprites(aList : TfwSpriteList);
var
  iLoop            : Integer;
  Item             : TfwSprite;
begin
  for iLoop := 0 to Pred(aList.Count) do begin
    Item := aList[iLoop];
    if Assigned(Item) then begin
      fItems.Remove(Item);
    end;
  end;
  aList.Clear;
end;

procedure TfwSpriteEngine.RemoveSprites();
var
  iLoop            : Integer;
  Item             : TfwWorldObject;
begin
  for iLoop := Pred(Count) downto 0 do begin
    Item := Items[iLoop];
    if (Item is TfwSprite) then begin
      if ((Item as TfwSprite).State = stDied) then begin
        if (Item as TfwSprite).AutoRemove then begin
          RemoveFromSpriteList(Item);
          fItems.Delete(iLoop);
        end;
      end;
    end;
  end;
end;

procedure TfwSpriteEngine.RemoveSprite(aSprite : TfwSprite);
begin
  RemoveFromSpriteList(aSprite);
  fItems.Remove(aSprite);
end;

procedure TfwSpriteEngine.Move(aTime : Cardinal);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(Count) do begin
    Items[iLoop].Move(aTime);
  end;
end;

procedure TfwSpriteEngine.Render(aScreen : TfwCEDrawable; aViewport : TWorldRect);
var
  iLoop            : Integer;
begin
  for iLoop := 0 to Pred(Count) do begin
    Items[iLoop].Render(aScreen, aViewport);
  end;
end;


end.
