unit dflash_player;

interface

uses
  dstrnum,
  dansiview,
  dcolors,
  dvector2f,
  dmatrix4f,
  deng,
  DDraw,
  DDrawVertexBuffer,
  dswf_primitives,
  dflash_coloring,
  dflash_holder,
  dflash_scenebuffer;

type
PFlashPlayer = ^TFlashPlayer;

PDisplayItem = ^TDisplayItem;
TDisplayItem = record
  Depth: LongInt;
  CharacterId: LongInt;
  PlaceObject2: TSwfPlaceObject2;
  Player: PFlashPlayer; // for Sprites
  State: LongInt; // state for buttons
  ButtonUp, ButtonHit: array of record // for button
    Player: PFlashPlayer;
    M, InvM: TMat4f;
  end;
  M: TMat4f;
  InvM: TMat4f;
  procedure ComputeInvM;
end;

PItemData = ^TItemData;
TItemData = record
//
//  Represents precomputed data for DisplayList's item.
//
  Depth: LongInt;
  CharacterId: LongInt;
  Character: PFlashCharacter; // pointer to holder's data
  PlaceObject2: PSwfPlaceObject2; // pointer to holder's data
  M: TMat4f; // precomputed PlaceMatrix for rendering
  InvM: TMat4f; // precomputed inverse of PlaceMatrix
end;

TDisplayListData = object
//
//  List of TItemData sorted by Depth
//
  Count: LongInt;
  Items: PItemData;
end;

TItem = object
  Data: TItemData;
  State: Pointer;
end;

TDisplayList = object
  // Items are sorted by Depth
  Items: array of TItem;
  // function NewDisplayItem(Depth: LongInt): PDisplayItem;
  // procedure DeleteDisplayItem(Depth: LongInt);
end;

TButtonState = object
  State: LongInt; // Up, Over, Down
  DisplayLists: array[0 .. 3] of TDisplayList;
end;

TShapeState = object
  // nothing
end;

TSpriteState = object
  CurrentFrame: LongInt;
  StartFrame: LongInt;
  LastFrame: LongInt;
  Frames: array of TDisplayList;
end;

TFlashPlayer = object
private
  FHolder: PFlashHolder;
  FCharacters: PFlashHolder;
  FTime: Single;
  FCurrentControl: LongInt;
  FCurrentFrame: LongInt;
  FDisplayList: array of TDisplayItem;
  FMaxDepth: LongInt;
  FStart: LongInt;
  FFinish: LongInt;
  FPlaying: Boolean;
  FLoop: Boolean;
  FFlipD, FFlipH, FFlipV: Boolean;
  FConstantColor: UInt32;
  procedure UpdateMaxDepth;
  procedure InitializeObjectAt(Index: LongInt);
  procedure Place(Depth, CharacterId: LongInt; const PlaceObject2: TSwfPlaceObject2);
  procedure Remove(Depth: LongInt);
  procedure DrawCharacter(CharacterId: LongInt);
  function IsInsideCharacter(CharacterId: LongInt; const P: TVec2f): Boolean;
  procedure OnUpdateFrame;
  procedure Init(Holder, Characters: PFlashHolder); overload;
  procedure Assign(Holder, Characters: PFlashHolder); overload;
public
  procedure Init; overload;
  procedure Init(Holder: PFlashHolder); overload;
  procedure Done;
  procedure Assign(Holder: PFlashHolder); overload;
  procedure Draw;
  procedure ResetAnimation;
  procedure UpdateAnimation(DeltaMs: PtrUInt);
  procedure NextFrame;

  procedure Play;
  procedure Stop;
  procedure SetStartFrame(Frame: LongInt); overload;
  procedure SetStartLabel(Frame: TAnsiView); overload;
  procedure SetFinishFrame(Frame: LongInt); overload;
  procedure SetFinishLabel(Frame: TAnsiView); overload;
  function HasFrameLabel(Index: LongInt; Frame: TAnsiView): Boolean;
  function GetFrameByLabel(Name: TAnsiView): LongInt;
  procedure SetLoop(Loop: Boolean);
  function FrameCount: SizeUInt; inline;

  procedure FlipDiagonally;
  procedure FlipHorizontally;
  procedure FlipVertically;

  function FrameLabelsCount: LongInt; inline;
  function FrameLabels: PFlashLabel; inline;

  function IsInside(P: TVec2f): Boolean;
  procedure ComputeBoundingRect(out BMin, BMax: TVec2f);
  function CurrentFrame: LongInt;
  function BackgroundColor: Cardinal;
  function Size: TVec2f; inline;

  // Experimental feature, may be removed in future
  procedure EnableConstantColor(C: UInt32);
  procedure DisableConstantColor;

// See dflash_scenebuffer
  procedure LinkToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
        // A flash player can be linked only to one scene buffer.
  procedure LoadToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
        // Writes data to a chunk. Should be called before DrawFromSceneBuffer.
        // May be called write after LinkToSceneBuffer, but it is optimal to link all
        // flash players first, and then write all of them.
  procedure DrawFromSceneBuffer(SceneBuffer: PFlashSceneBuffer); inline;
  procedure DrawCharacterFromSceneBuffer(CharacterId: LongInt; SceneBuffer: PFlashSceneBuffer);
        // Draws a flash player using scene buffer. The flash player should be
        // linked to the specified SceneBuffer.

  property StartFrame: LongInt read FStart;
  property FinishFrame: LongInt read FFinish;
  property Playing: Boolean read FPlaying;
  property Loop: Boolean read FLoop;
end;

implementation

procedure SwfMatrixToMatrix(const SwfM: TSwfMATRIX; out M: TMat4f);
begin
  M[0] := SwfM[0]; M[4] := SwfM[1]; M[ 8] := 0; M[12] := SwfM[2];
  M[1] := SwfM[3]; M[5] := SwfM[4]; M[ 9] := 0; M[13] := SwfM[5];
  M[2] := 0;       M[6] := 0;       M[10] := 1; M[14] := 0;
  M[3] := 0;       M[7] := 0;       M[11] := 0; M[15] := 1;
end;

procedure SwfMulMatrix(const SwfM: TSwfMATRIX); inline;
var
  M: TMat4f;
begin
  SwfMatrixToMatrix(SwfM, M);
  Eng.Draw.MultMatrix(M);
end;

procedure InvertMatrix(const M: TMat4f; out InvM: TMat4f);
var
  D: Double;
begin
  // M[0] M[4] M[ 8] M[12]
  // M[1] M[5] M[ 9] M[13]
  // M[2] M[6] M[10] M[14]
  // M[3] M[7] M[11] M[15]
  D := M[0] * M[5] - M[4] * M[1];
  if Abs(D) < 0.000001 then begin
    InvM := Identity4f;
    Exit;
  end;
  D := 1 / D;
  InvM[0]  :=   M[5] * D; InvM[4] := - M[4] * D; InvM[ 8] := 0; InvM[12] := (M[4] * M[13] - M[5] * M[12]) * D;
  InvM[1]  := - M[1] * D; InvM[5] :=   M[0] * D; InvM[ 9] := 0; InvM[13] := (M[1] * M[12] - M[0] * M[13]) * D;
  InvM[2]  := 0;          InvM[6] := 0;          InvM[10] := 1; InvM[14] := 0;
  InvM[3]  := 0;          InvM[7] := 0;          InvM[11] := 0; InvM[15] := 1;
end;

function ApplyMatrix(const M: TMat4f; const P: TVec2f): TVec2f;
begin
  Result := Vec2f(P.X * M[0] + P.Y * M[4] + M[12],
                  P.X * M[1] + P.Y * M[5] + M[13]);
end;

function OneSide(const A, B, P, Q: TVec2f): Boolean;
var
  D1, D2: Single;
begin
  D1 := (B.Y - A.Y) * (P.X - A.X) + (A.X - B.X) * (P.Y - A.Y);
  D2 := (B.Y - A.Y) * (Q.X - A.X) + (A.X - B.X) * (Q.Y - A.Y);
  Result := (Abs(D1) > 0) and (D1 * D2 >= 0);
end;

function IsInsideTriangle(T: PVec2f; const P: TVec2f): Boolean;
begin
  Result := OneSide(T[0], T[1], T[2], P) and
            OneSide(T[1], T[2], T[0], P) and
            OneSide(T[2], T[0], T[1], P);
end;

procedure TDisplayItem.ComputeInvM;
begin
  InvertMatrix(M, InvM);
end;

procedure TFlashPlayer.UpdateMaxDepth;
var
  I: LongInt;
begin
  FMaxDepth := 0;
  for I := 0 to High(FDisplayList) do begin
    if FDisplayList[I].Depth > FMaxDepth then begin
      FMaxDepth := FDisplayList[I].Depth;
    end;
  end;
end;

procedure TFlashPlayer.InitializeObjectAt(Index: LongInt);
var
  C, ButtonC: PFlashCharacter;
  I: LongInt;
begin
  FDisplayList[Index].Player := nil;
  FDisplayList[Index].State := SWF_BUTTON_UP;
  C := FCharacters^.GetCharacter(FDisplayList[Index].CharacterId);
  if C = nil then
    Exit;
  if C^.FType = TAG_DefineSprite then begin
    New(FDisplayList[Index].Player);
    FDisplayList[Index].Player^.Init(C^.FHolder, FCharacters);
    // Remove later
    FDisplayList[Index].Player^.NextFrame;
  end;
  if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
    SetLength(FDisplayList[Index].ButtonUp, Length(C^.FButton.FUp));
    for I := 0 to High(C^.FButton.FUp) do begin
      ButtonC := FCharacters.GetCharacter(C^.FButton.FUp[I]^.ButtonRecord.CharacterId);
      SwfMatrixToMatrix(C^.FButton.FUp[I]^.ButtonRecord.PlaceMatrix, FDisplayList[Index].ButtonUp[I].M);
      InvertMatrix(FDisplayList[Index].ButtonUp[I].M, FDisplayList[Index].ButtonUp[I].InvM);
      if (ButtonC <> nil) and (ButtonC^.FType = TAG_DefineSprite) then begin
        New(FDisplayList[Index].ButtonUp[I].Player);
        FDisplayList[Index].ButtonUp[I].Player^.Init(ButtonC^.FHolder, FCharacters);
        // Remove later
        FDisplayList[Index].ButtonUp[I].Player^.NextFrame;
      end;
    end;
    // COPYPASTA :D
    SetLength(FDisplayList[Index].ButtonHit, Length(C^.FButton.FHit));
    for I := 0 to High(C^.FButton.FHit) do begin
      ButtonC := FCharacters.GetCharacter(C^.FButton.FHit[I]^.ButtonRecord.CharacterId);
      SwfMatrixToMatrix(C^.FButton.FHit[I]^.ButtonRecord.PlaceMatrix, FDisplayList[Index].ButtonHit[I].M);
      InvertMatrix(FDisplayList[Index].ButtonHit[I].M, FDisplayList[Index].ButtonHit[I].InvM);
      if (ButtonC <> nil) and (ButtonC^.FType = TAG_DefineSprite) then begin
        New(FDisplayList[Index].ButtonHit[I].Player);
        FDisplayList[Index].ButtonHit[I].Player^.Init(ButtonC^.FHolder, FCharacters);
        // Remove later
        FDisplayList[Index].ButtonHit[I].Player^.NextFrame;
      end;
    end;
  end;
end;

procedure TFlashPlayer.Place(Depth, CharacterId: LongInt; const PlaceObject2: TSwfPlaceObject2);
var
  I: LongInt;
begin
  for I := 0 to High(FDisplayList) do begin
    if FDisplayList[I].Depth = Depth then begin
      FDisplayList[I].CharacterId := CharacterId;
      FDisplayList[I].PlaceObject2 := PlaceObject2;
      if FDisplayList[I].Player <> nil then
        Dispose(FDisplayList[I].Player);
      if PlaceObject2.PlaceFlagHasMatrix then begin
        with FDisplayList[I] do with PlaceObject2 do begin
          SwfMatrixToMatrix(Matrix, M);
          ComputeInvM;
        end;
      end else begin
        FDisplayList[I].M := Identity4f;
      end;
      InitializeObjectAt(I);
      Exit;
    end;
  end;
  SetLength(FDisplayList, Length(FDisplayList) + 1);
  FDisplayList[High(FDisplayList)].Depth := Depth;
  FDisplayList[High(FDisplayList)].CharacterId := CharacterId;
  FDisplayList[High(FDisplayList)].PlaceObject2 := PlaceObject2;
      I := High(FDisplayList);
      if PlaceObject2.PlaceFlagHasMatrix then begin
        with FDisplayList[I] do with PlaceObject2 do begin
          SwfMatrixToMatrix(Matrix, M);
          ComputeInvM;
        end;
      end else begin
        FDisplayList[I].M := Identity4f;
      end;
  InitializeObjectAt(High(FDisplayList));
  UpdateMaxDepth;
end;

procedure TFlashPlayer.Remove(Depth: LongInt);
var
  I, J: LongInt;
begin
  for I := 0 to High(FDisplayList) do begin
    if FDisplayList[I].Depth = Depth then begin
      for J := I to High(FDisplayList) - 1 do begin
        // TODO Free memory
        FDisplayList[J] := FDisplayList[J + 1];
      end;
    end;
  end;
  SetLength(FDisplayList, Length(FDisplayList) - 1);
end;

procedure TFlashPlayer.DrawCharacter(CharacterId: LongInt);
var
  C: PFlashCharacter;
  S: PShape;
  Path: LongInt;
begin
  C := FCharacters^.GetCharacter(CharacterId);
  if C^.FType = TAG_DefineSprite then begin
    // TODO create button's state and render here
    Log('TODO underdeveloped rendering of a sprite inside a button');
    //if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
    //  Eng.Draw.PushMatrix;
    //  Eng.Draw.MultMatrix(FDisplayList[J].M);
    //end;
    //FDisplayList[J].Player^.Draw;
    //if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
    //  Eng.Draw.PopMatrix;
    //end;
    Exit;
  end;
  if (C^.FType <> TAG_DefineShape) and (C^.FType <> TAG_DefineShape2) and (C^.FType <> TAG_DefineShape3) and (C^.FType <> TAG_DefineShape4) then
    Exit;
  S := @C^.FShape;
  for Path := 0 to High(S.FFinal) do begin
    if S.FFinal[Path].TrianglesCount > 0 then begin
      Eng.Draw.Color(S.FFinal[Path].Color);
      Eng.Draw.Triangles(S.FFinal[Path].TrianglesData, S.FFinal[Path].TrianglesCount);
    end;
  end;
end;

function TFlashPlayer.IsInsideCharacter(CharacterId: LongInt; const P: TVec2f): Boolean;
var
  C: PFlashCharacter;
  S: PShape;
  Path, I: LongInt;
begin
  C := FCharacters^.GetCharacter(CharacterId);
  if (C^.FType <> TAG_DefineShape) and (C^.FType <> TAG_DefineShape2) and (C^.FType <> TAG_DefineShape3) and (C^.FType <> TAG_DefineShape4) then
    Exit(False);
  S := @C^.FShape;
  for Path := 0 to High(S.FFinal) do begin
    if S.FFinal[Path].TrianglesCount > 0 then begin
      for I := 0 to S.FFinal[Path].TrianglesCount div 3 - 1 do begin
        if IsInsideTriangle(@S.FFinal[Path].TrianglesData[3 * I], P) then
          Exit(True);
      end;
    end;
  end;
  Exit(False);
end;

procedure TFlashPlayer.OnUpdateFrame;
var
  F: LongInt;
  I: LongInt;
begin
  if not FPlaying then
    Exit;
  if (FCurrentFrame = FFinish) or (FCurrentFrame >= FHolder^.FrameCount - 1) then begin
    if FLoop then begin
      if FStart = FFinish then
        Exit;
      F := FCurrentFrame;
      I := 0;
      //Log('while True started, start=' + IntToStr(FStart) + ', end=' + IntToStr(FFinish) + ', current=' + IntToStr(F));
      while True do begin
        NextFrame;
        if F = FCurrentFrame then // infinite loop
          Exit;
        if FCurrentFrame = FStart then // we just skipped to the first frame
          Exit;
        if I > 1000 then begin
          Log('ERROR TFlashPlayer.OnUpdateFrame infinite loop');
          Exit;
        end;
        Inc(I);
        //Log('while True IT');
      end;
      //Log('while True finished');
    end else begin
      FPlaying := False;
      Exit;
    end;
  end;
  NextFrame;
end;

procedure TFlashPlayer.Init(Holder, Characters: PFlashHolder);
begin
  Init;
  Assign(Holder, Characters);
end;

procedure TFlashPlayer.Assign(Holder, Characters: PFlashHolder);
begin
  // TODO clear previous data
  FHolder := Holder;
  FStart := 0;
  FFinish := Holder^.FrameCount - 1;
  FCharacters := Characters;
  ResetAnimation;
  NextFrame;
  FFlipD := False;
  FFlipH := False;
  FFlipV := False;
end;

procedure TFlashPlayer.Init;
begin
  FConstantColor := 0;
  FStart := 0;
  FFinish := 9999;
  FPlaying := True;
  FLoop := True;
  FFlipD := False;
  FFlipH := False;
  FFlipV := False;
  //ResetAnimation;
end;

procedure TFlashPlayer.Init(Holder: PFlashHolder);
begin
  Init;
  Assign(Holder);
end;

procedure TFlashPlayer.Done;
begin
  // TODO free
end;

procedure TFlashPlayer.Assign(Holder: PFlashHolder);
begin
  // TODO clear previous data
  FHolder := Holder;
  FStart := 0;
  FFinish := Holder^.FrameCount - 1;
  FCharacters := Holder;
  ResetAnimation;
  NextFrame;
  FFlipD := False;
  FFlipH := False;
  FFlipV := False;
end;

function GetPhase(Count: LongInt): LongInt;
begin
  Result := (Eng.Time div 50) mod Count;
end;

procedure TFlashPlayer.Draw;
var
  J, L: LongInt;
  Path, I: LongInt;
  C, C2: PFlashCharacter;
  S: PShape;
  Edge: LongInt;
  Num: LongInt;
  Depth: LongInt;
  Matrix: TMat4f;
  Triangle: LongInt;
  U, V: Single;
  Temp: TVec2f;
  B: PSwfButtonRecord;
begin
  if FFlipD or FFlipH or FFlipV then begin
    Eng.Draw.PushMatrix;
    if FFlipD then begin
      Matrix[0] := 0;                       Matrix[ 4] := 2 * Ord(not FFlipH) - 1; Matrix[ 8] := 0; Matrix[12] := FHolder^.Size.X * Ord(FFlipH);
      Matrix[1] := 2 * Ord(not FFlipV) - 1; Matrix[ 5] := 0;                       Matrix[ 9] := 0; Matrix[13] := FHolder^.Size.Y * Ord(FFlipV);
      Matrix[2] := 0;                       Matrix[ 6] := 0;                       Matrix[10] := 1; Matrix[14] := 0;
      Matrix[3] := 0;                       Matrix[ 7] := 0;                       Matrix[11] := 0; Matrix[15] := 1;
    end else begin
      Matrix[0] := 2 * Ord(not FFlipH) - 1; Matrix[ 4] := 0;                       Matrix[ 8] := 0; Matrix[12] := FHolder^.Size.X * Ord(FFlipH);
      Matrix[1] := 0;                       Matrix[ 5] := 2 * Ord(not FFlipV) - 1; Matrix[ 9] := 0; Matrix[13] := FHolder^.Size.Y * Ord(FFlipV);
      Matrix[2] := 0;                       Matrix[ 6] := 0;                       Matrix[10] := 1; Matrix[14] := 0;
      Matrix[3] := 0;                       Matrix[ 7] := 0;                       Matrix[11] := 0; Matrix[15] := 1;
    end;
    Eng.Draw.MultMatrix(Matrix);
  end;
  Num := 0;
  for Depth := 1 to FMaxDepth do begin
    J := 0;
    while J < Length(FDisplayList) do begin
      if FDisplayList[J].Depth = Depth then
        break;
      Inc(J);
    end;
    if J > High(FDisplayList) then
      continue;
    C := FCharacters^.GetCharacter(FDisplayList[J].CharacterId);
    if C = nil then
      continue;
    if C^.FType = TAG_DefineSprite then begin
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PushMatrix;
        Eng.Draw.MultMatrix(FDisplayList[J].M);
      end;
      FDisplayList[J].Player^.Draw;
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PopMatrix;
      end;
      continue;
    end;
    if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PushMatrix;
        Eng.Draw.MultMatrix(FDisplayList[J].M);
      end;
      case FDisplayList[J].State of
        SWF_BUTTON_UP: begin
            for I := 0 to High(C^.FButton.FUp) do begin
              B := @C^.FButton.FUp[I].ButtonRecord;
              Eng.Draw.PushMatrix;
              Eng.Draw.MultMatrix(FDisplayList[J].ButtonUp[I].M);
              C2 := FCharacters^.GetCharacter(B^.CharacterId);
              if (C2 <> nil) and (C2^.FType = TAG_DefineSprite) then begin
                FDisplayList[J].ButtonUp[I].Player^.Draw;
              end else
                DrawCharacter(B^.CharacterId);
              Eng.Draw.PopMatrix;
            end;
          end;
        SWF_BUTTON_OVER: begin
            for I := 0 to High(C^.FButton.FOver) do begin
              DrawCharacter(C^.FButton.FOver[I].ButtonRecord.CharacterId);
            end;
          end;
        SWF_BUTTON_DOWN: begin
            for I := 0 to High(C^.FButton.FDown) do begin
              DrawCharacter(C^.FButton.FDown[I].ButtonRecord.CharacterId);
            end;
          end;
        SWF_BUTTON_HIT: begin // lol? ok
            for I := 0 to High(C^.FButton.FHit) do begin
              DrawCharacter(C^.FButton.FHit[I].ButtonRecord.CharacterId);
            end;
          end;
      else
        // unknown state???
      end;
            { Shows hitbox for debug
             for I := 0 to High(C^.FButton.FHit) do begin
              B := @C^.FButton.FHit[I].ButtonRecord;
              Eng.Draw.PushMatrix;
              Eng.Draw.MultMatrix(FDisplayList[J].ButtonHit[I].M);
              C2 := FCharacters^.GetCharacter(B^.CharacterId);
              if (C2 <> nil) and (C2^.FType = TAG_DefineSprite) then begin
                FDisplayList[J].ButtonHit[I].Player^.Draw;
              end else
                DrawCharacter(B^.CharacterId);
              Eng.Draw.PopMatrix;
            end;}
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PopMatrix;
      end;
      continue;
    end;
    if (C^.FType <> TAG_DefineShape) and (C^.FType <> TAG_DefineShape2) and (C^.FType <> TAG_DefineShape3) and (C^.FType <> TAG_DefineShape4) then
      continue;
    Inc(Num);
    S := @C^.FShape;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      Eng.Draw.PushMatrix;
      Eng.Draw.MultMatrix(FDisplayList[J].M);
    end;
    for Path := 0 to High(S.FFinal) do begin
      if FConstantColor <> 0 then begin
        Eng.Draw.Color(FConstantColor);
      end else
        Eng.Draw.Color(S.FFinal[Path].Color);
      if S.FFinal[Path].TrianglesCount > 0 then
        Eng.Draw.Triangles(S.FFinal[Path].TrianglesData, S.FFinal[Path].TrianglesCount);
    end;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      Eng.Draw.PopMatrix;
    end;
  end;
  if FFlipD or FFlipH or FFlipV then begin
    Eng.Draw.PopMatrix;
  end;
end;

procedure TFlashPlayer.ResetAnimation;
begin
  FCurrentControl := -1;
  FCurrentFrame := -1;
  FTime := 0;
  SetLength(FDisplayList, 0);
  UpdateMaxDepth;
end;

procedure TFlashPlayer.UpdateAnimation(DeltaMs: PtrUInt);
var
  I, J: LongInt;
  C, ButtonC: PFlashCharacter;
begin
  if DeltaMs <= 0 then
    Exit;
  FTime := FTime + DeltaMs;
  while FTime > FHolder^.FrameDuration do begin
    //Log('Frame duration: ' + IntToStr(Round(FHolder^.FrameDuration)) + ', delta: ' + IntToStr(DeltaMs));
    if FHolder^.FrameDuration < 1 then begin
      //Log('WARNING FrameDuration = ' + IntToStr(Round(FHolder^.FrameDuration)));
      FTime := FTime - 1;
    end else
      FTime := FTime - FHolder^.FrameDuration;
    OnUpdateFrame;
    if (not FLoop) and (FCurrentFrame = FFinish) then begin
      FPlaying := False;
    end;
  end;
  for I := 0 to High(FDisplayList) do begin
    if FDisplayList[I].Player <> nil then begin
      FDisplayList[I].Player^.UpdateAnimation(DeltaMs);
    end;
    C := FCharacters^.GetCharacter(FDisplayList[I].CharacterId);
    if C = nil then
      continue;
    if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
      for J := 0 to High(C^.FButton.FUp) do begin
        ButtonC := FCharacters.GetCharacter(C^.FButton.FUp[J]^.ButtonRecord.CharacterId);
        if (ButtonC <> nil) and (ButtonC^.FType = TAG_DefineSprite) then begin
          FDisplayList[I].ButtonUp[J].Player^.UpdateAnimation(DeltaMs);
        end;
      end;
      for J := 0 to High(C^.FButton.FHit) do begin
        ButtonC := FCharacters.GetCharacter(C^.FButton.FHit[J]^.ButtonRecord.CharacterId);
        if (ButtonC <> nil) and (ButtonC^.FType = TAG_DefineSprite) then begin
          FDisplayList[I].ButtonHit[J].Player^.UpdateAnimation(DeltaMs);
        end;
      end;
    end;
  end;
end;

procedure TFlashPlayer.NextFrame;
var
  Delta, I: LongInt;
  C: PFlashControl;
begin
  if FHolder^.ControlCount < 1 then
    Exit;
  for Delta := 0 to FHolder^.ControlCount - 1 do begin
    I := (FCurrentControl + Delta + 1) mod FHolder^.ControlCount;
    C := FHolder^.GetControl(I);
    case C^.Tag of
      TAG_ShowFrame: begin
        //Log('TAG_ShowFrame');
        if I <= FCurrentControl then begin
          FCurrentFrame := 0;
        end else
          Inc(FCurrentFrame);
        FCurrentControl := I;
        //Log('FRAME: ' + IntToStr(FCurrentFrame));
        Exit;
      end;
      TAG_PlaceObject2: begin
        Place(C^.Depth, C^.CharacterId, C^.PlaceObject2);
      end;
      TAG_RemoveObject: begin
        Remove(C^.Depth);
      end;
    end;
  end;
  FCurrentFrame := 0;
  //Log('FRAME: ' + IntToStr(FCurrentFrame));
end;

procedure TFlashPlayer.Play;
begin
  FPlaying := True;
end;

procedure TFlashPlayer.Stop;
begin
  FPlaying := False;
end;

procedure TFlashPlayer.SetStartFrame(Frame: LongInt);
var
  F: LongInt;
  I: LongInt;
begin
  FStart := Frame;
  F := FCurrentFrame mod FrameCount;
  I := 0;
  while True do begin
    if FCurrentFrame = FStart then
      Exit;
    NextFrame;
    if FCurrentFrame = F then
      Exit;
    Inc(I);
    if I > 1000 then begin
      Log('ERROR TFlashPlayer.SetStartFrame infinite loop');
      Exit;
    end;
  end;
end;

procedure TFlashPlayer.SetStartLabel(Frame: TAnsiView);
begin
  FStart := FHolder^.GetFrameByName(Frame);
  SetStartFrame(FStart);
end;

procedure TFlashPlayer.SetFinishFrame(Frame: LongInt);
begin
  FFinish := Frame;
end;

procedure TFlashPlayer.SetFinishLabel(Frame: TAnsiView);
begin
  FFinish := FHolder^.GetFrameByName(Frame);
end;

function TFlashPlayer.HasFrameLabel(Index: LongInt; Frame: TAnsiView): Boolean;
begin
  Exit(Index = FHolder^.GetFrameByName(Frame));
end;

function TFlashPlayer.GetFrameByLabel(Name: TAnsiView): LongInt;
begin
  Exit(FHolder^.GetFrameByName(Name));
end;

procedure TFlashPlayer.SetLoop(Loop: Boolean);
begin
  FLoop := Loop;
end;

function TFlashPlayer.FrameCount: SizeUInt;
begin
  Exit(FHolder^.FrameCount);
end;

procedure TFlashPlayer.FlipDiagonally;
begin
  FFlipD := not FFlipD;
end;

procedure TFlashPlayer.FlipHorizontally;
begin
  FFlipH := not FFlipH;
end;

procedure TFlashPlayer.FlipVertically;
begin
  FFlipV := not FFlipV;
end;

function TFlashPlayer.FrameLabelsCount: LongInt; inline;
begin
  Result := FHolder^.FrameLabelsCount;
end;

function TFlashPlayer.FrameLabels: PFlashLabel; inline;
begin
  Result := FHolder^.FrameLabels;
end;

function TFlashPlayer.IsInside(P: TVec2f): Boolean;
var
  J: LongInt;
  Path, I: LongInt;
  C: PFlashCharacter;
  S: PShape;
  Edge: LongInt;
  Num: LongInt;
  Depth: LongInt;
  U, V: Single;
  Temp: TVec2f;
  LocalP, ButtonP: TVec2f;
begin
  if FFlipD then begin
    LocalP.X := P.Y;
    LocalP.Y := P.X;
    P := LocalP;
  end;
  if FFlipH then begin
    P.X := FHolder^.Size.X - P.X;
  end;
  if FFlipV then begin
    P.Y := FHolder^.Size.Y - P.Y;
  end;
  if (P.X < 0) or (P.Y < 0) or (P.X > FHolder^.Size.X) or (P.Y > FHolder^.Size.Y) then
    Exit(False);
  //Exit(True);

  for Depth := 1 to FMaxDepth do begin
    J := 0;
    while J < Length(FDisplayList) do begin
      if FDisplayList[J].Depth = Depth then
        break;
      Inc(J);
    end;
    if J > High(FDisplayList) then
      continue;
    C := FCharacters^.GetCharacter(FDisplayList[J].CharacterId);
    if C = nil then
      continue;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      with FDisplayList[J] do begin
        LocalP := Vec2f(P.X * InvM[0] + P.Y * InvM[4] + InvM[12],
                        P.X * InvM[1] + P.Y * InvM[5] + InvM[13]);
      end;
    end else
      LocalP := P;
    if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
      for I := 0 to High(C^.FButton.FHit) do begin
        ButtonP := ApplyMatrix(FDisplayList[J].ButtonHit[I].InvM, LocalP);
        if IsInsideCharacter(C^.FButton.FHit[I].ButtonRecord.CharacterId, ButtonP) then
          Exit(True);
      end;
      Exit(False);
    end;
    if (C^.FType <> TAG_DefineShape) and (C^.FType <> TAG_DefineShape2) and (C^.FType <> TAG_DefineShape3) and (C^.FType <> TAG_DefineShape4) then
      continue;
    S := @C^.FShape;
    for Path := 0 to High(S.FFinal) do begin
      if S.FFinal[Path].TrianglesCount > 0 then begin
        for I := 0 to S.FFinal[Path].TrianglesCount div 3 - 1 do begin
          if IsInsideTriangle(@S.FFinal[Path].TrianglesData[3 * I], LocalP) then
            Exit(True);
        end;
      end;
    end;
  end;
  Exit(False);
end;

procedure TFlashPlayer.ComputeBoundingRect(out BMin, BMax: TVec2f);
var
  J: LongInt;
  Path, I: LongInt;
  C: PFlashCharacter;
  S: PShape;
  Edge: LongInt;
  Num: LongInt;
  Depth: LongInt;
  U, V: Single;
  Temp: TVec2f;
  LocalP, ButtonP: TVec2f;
  MinP, MaxP, LocalMinP, LocalMaxP: TVec2f;
begin
  BMin.X := 0;
  BMin.Y := 0;
  BMax.X := 0;
  BMax.Y := 0;

  for Depth := 1 to FMaxDepth do begin
    J := 0;
    while J < Length(FDisplayList) do begin
      if FDisplayList[J].Depth = Depth then
        break;
      Inc(J);
    end;
    if J > High(FDisplayList) then
      continue;
    C := FCharacters^.GetCharacter(FDisplayList[J].CharacterId);
    if C = nil then
      continue;
    MinP := Vec2f(0, 0);
    MaxP := Vec2f(0, 0);
    {
    // FIXME it's hell 8O
    if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
      for I := 0 to High(C^.FButton.FHit) do begin
        ButtonP := ApplyMatrix(FDisplayList[J].ButtonHit[I].InvM, LocalP);
        if IsInsideCharacter(C^.FButton.FHit[I].ButtonRecord.CharacterId, ButtonP) then
          Exit(True);
      end;
      Exit(False);
    end;
    }
    if (C^.FType <> TAG_DefineShape) and (C^.FType <> TAG_DefineShape2) and (C^.FType <> TAG_DefineShape3) and (C^.FType <> TAG_DefineShape4) then
      continue;
    S := @C^.FShape;
    for Path := 0 to High(S.FFinal) do begin
      if S.FFinal[Path].TrianglesCount > 0 then begin
        for I := 0 to S.FFinal[Path].TrianglesCount - 1 do begin
          LocalP := S.FFinal[Path].TrianglesData[I];
          if LocalP.X < BMin.X then
            MinP.X := LocalP.X;
          if LocalP.Y < BMin.Y then
            MinP.Y := LocalP.Y;
          if LocalP.X > BMax.X then
            MaxP.X := LocalP.X;
          if LocalP.Y > BMax.Y then
            MaxP.Y := LocalP.Y;
        end;
      end;
    end;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      with FDisplayList[J] do begin
        MinP := Vec2f(MinP.X * M[0] + MinP.Y * M[4] + M[12],
                      MinP.X * M[1] + MinP.Y * M[5] + M[13]);
        MaxP := Vec2f(MaxP.X * M[0] + MaxP.Y * M[4] + M[12],
                      MaxP.X * M[1] + MaxP.Y * M[5] + M[13]);
      end;
    end;
    if MinP.X < BMin.X then
      BMin.X := MinP.X;
    if MinP.Y < BMin.Y then
      BMin.Y := MinP.Y;
    if MaxP.X > BMax.X then
      BMax.X := MaxP.X;
    if MaxP.Y > BMax.Y then
      BMax.Y := MaxP.Y;
  end;
end;

function TFlashPlayer.CurrentFrame: LongInt;
begin
  Exit(FCurrentFrame);
end;

function TFlashPlayer.BackgroundColor: Cardinal;
var
  I: LongInt;
  C: PFlashControl;
begin
  for I := 0 to FHolder^.ControlCount - 1 do begin
    C := FHolder.GetControl(I);
    if C^.Tag = TAG_SetBackgroundColor then
      Exit(C^.Color);
  end;
  Exit(RGBA(0, 0, 0, 0));
end;

function TFlashPlayer.Size: TVec2f;
begin
  Result := FHolder^.Size;
end;

procedure TFlashPlayer.EnableConstantColor(C: UInt32);
begin
  FConstantColor := C;
end;

procedure TFlashPlayer.DisableConstantColor;
begin
  FConstantColor := 0;
end;

procedure TFlashPlayer.LinkToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
begin
  FHolder^.LinkToSceneBuffer(SceneBuffer);
end;

procedure TFlashPlayer.LoadToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
begin
  FHolder^.LoadToSceneBuffer(SceneBuffer);
end;

procedure TFlashPlayer.DrawFromSceneBuffer(SceneBuffer: PFlashSceneBuffer);
var
  J, L: LongInt;
  Path, I: LongInt;
  C, C2: PFlashCharacter;
  S: PShape;
  Edge: LongInt;
  Num: LongInt;
  Depth: LongInt;
  Matrix: TMat4f;
  Triangle: LongInt;
  U, V: Single;
  Temp: TVec2f;
  B: PSwfButtonRecord;
begin
  if FFlipD or FFlipH or FFlipV then begin
    Eng.Draw.PushMatrix;
    if FFlipD then begin
      Matrix[0] := 0;                       Matrix[ 4] := 2 * Ord(not FFlipH) - 1; Matrix[ 8] := 0; Matrix[12] := FHolder^.Size.X * Ord(FFlipH);
      Matrix[1] := 2 * Ord(not FFlipV) - 1; Matrix[ 5] := 0;                       Matrix[ 9] := 0; Matrix[13] := FHolder^.Size.Y * Ord(FFlipV);
      Matrix[2] := 0;                       Matrix[ 6] := 0;                       Matrix[10] := 1; Matrix[14] := 0;
      Matrix[3] := 0;                       Matrix[ 7] := 0;                       Matrix[11] := 0; Matrix[15] := 1;
    end else begin
      Matrix[0] := 2 * Ord(not FFlipH) - 1; Matrix[ 4] := 0;                       Matrix[ 8] := 0; Matrix[12] := FHolder^.Size.X * Ord(FFlipH);
      Matrix[1] := 0;                       Matrix[ 5] := 2 * Ord(not FFlipV) - 1; Matrix[ 9] := 0; Matrix[13] := FHolder^.Size.Y * Ord(FFlipV);
      Matrix[2] := 0;                       Matrix[ 6] := 0;                       Matrix[10] := 1; Matrix[14] := 0;
      Matrix[3] := 0;                       Matrix[ 7] := 0;                       Matrix[11] := 0; Matrix[15] := 1;
    end;
    Eng.Draw.MultMatrix(Matrix);
  end;
  Num := 0;
  for Depth := 1 to FMaxDepth do begin
    J := 0;
    while J < Length(FDisplayList) do begin
      if FDisplayList[J].Depth = Depth then
        break;
      Inc(J);
    end;
    if J > High(FDisplayList) then
      continue;
    C := FCharacters^.GetCharacter(FDisplayList[J].CharacterId);
    if C = nil then
      continue;
    if C^.FType = TAG_DefineSprite then begin
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PushMatrix;
        Eng.Draw.MultMatrix(FDisplayList[J].M);
      end;
      FDisplayList[J].Player^.DrawFromSceneBuffer(SceneBuffer);
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.PopMatrix;
      end;
      continue;
    end;
    if (C^.FType = TAG_DefineButton) or (C^.FType = TAG_DefineButton2) then begin
      Eng.Draw.PushMatrix;
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
        Eng.Draw.MultMatrix(FDisplayList[J].M);
      end;
      case FDisplayList[J].State of
        SWF_BUTTON_UP: begin
            for I := 0 to High(C^.FButton.FUp) do begin
              B := @C^.FButton.FUp[I].ButtonRecord;
              Eng.Draw.PushMatrix;
              Eng.Draw.MultMatrix(FDisplayList[J].ButtonUp[I].M);
              C2 := FCharacters^.GetCharacter(B^.CharacterId);
              if (C2 <> nil) and (C2^.FType = TAG_DefineSprite) then begin
                FDisplayList[J].ButtonUp[I].Player^.DrawFromSceneBuffer(SceneBuffer);
              end else
                DrawCharacterFromSceneBuffer(B^.CharacterId, SceneBuffer);
              Eng.Draw.PopMatrix;
            end;
          end;
        SWF_BUTTON_OVER: begin
            for I := 0 to High(C^.FButton.FOver) do begin
              DrawCharacterFromSceneBuffer(C^.FButton.FOver[I].ButtonRecord.CharacterId, SceneBuffer);
            end;
          end;
        SWF_BUTTON_DOWN: begin
            for I := 0 to High(C^.FButton.FDown) do begin
              DrawCharacterFromSceneBuffer(C^.FButton.FDown[I].ButtonRecord.CharacterId, SceneBuffer);
            end;
          end;
        SWF_BUTTON_HIT: begin // lol? ok
            for I := 0 to High(C^.FButton.FHit) do begin
              DrawCharacterFromSceneBuffer(C^.FButton.FHit[I].ButtonRecord.CharacterId, SceneBuffer);
            end;
          end;
      else
        // unknown state???
      end;
      if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      end;
      Eng.Draw.PopMatrix;
      continue;
    end;
    if not C^.IsShape then
      continue;
    S := @C^.FShape;
    Eng.Draw.PushMatrix;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
      Eng.Draw.MultMatrix(FDisplayList[J].M);
    end;
    Eng.Draw.Scale(1 / 20);
// {$IF Declared(DENG_USE_DRAW_OPENGL)}
//     SceneBuffer^.DrawChunk(S^.FSceneBufferChunk);
// {$ELSE}
//     SceneBuffer^.DrawChunk(S^.FSceneBufferChunk);
// {$ENDIF}
    for Path := 0 to High(S.FFinal) do begin
      if S.FFinal[Path].TrianglesCount > 0 then begin
        if FConstantColor <> 0 then begin
          Eng.Draw.Color(FConstantColor);
        end else
          Eng.Draw.Color(S.FFinal[Path].Color);
        SceneBuffer^.DrawChunk(C^.FShape.FFinal[Path].FSceneBufferChunk);
      end;
    end;
    if FDisplayList[J].PlaceObject2.PlaceFlagHasMatrix then begin
    end;
    Eng.Draw.PopMatrix;
  end;
  if FFlipD or FFlipH or FFlipV then begin
    Eng.Draw.PopMatrix;
  end;
end;

procedure TFlashPlayer.DrawCharacterFromSceneBuffer(CharacterId: LongInt; SceneBuffer: PFlashSceneBuffer);
var
  C: PFlashCharacter;
  S: PShape;
  Path: LongInt;
begin
  C := FCharacters^.GetCharacter(CharacterId);
  if C^.FType = TAG_DefineSprite then begin
    Log('TODO underdeveloped rendering of a sprite inside a button');
    Exit;
  end;
  if not C^.IsShape then
    Exit;
  // SceneBuffer^.DrawChunk(C^.FShape.FSceneBufferChunk);
  Eng.Draw.Scale(1 / 20);
  for Path := 0 to High(C^.FShape.FFinal) do begin
    if C^.FShape.FFinal[Path].TrianglesCount > 0 then begin
      if FConstantColor <> 0 then begin
        Eng.Draw.Color(FConstantColor);
      end else
        Eng.Draw.Color(C^.FShape.FFinal[Path].Color);
      SceneBuffer^.DrawChunk(C^.FShape.FFinal[Path].FSceneBufferChunk);
    end;
  end;
end;

end.
