 // ===================================================================================================
 //
 //   Модуль:               ParticleEngine v1.2
 //   Назначение:           Particle Engine для PowerDraw (модифицированной версии 2.4)
 //
 //
 //   Автор:                Darthman
 //   Последние изменения:  31.08.2003
 //
 //   * Добавлен Explosion_Ring
 //   * Разнесен Process на каждый партикл
 //   * Добавлен AddFog. Эффект, применяемый для тумана. 
 //   * Внесен ряд мелких исправлений
 //   * Добавлен партикл дыма и соотвествующий класс. Использовать AddSmoke
 //   * Изменен вывод партиклов. Теперь для всех партиклов используется RotateEffectFix.
 //
 // ===================================================================================================
 //   +
 // ===================================================================================================

 { *************************************************************************************************** }
                                       unit ParticleEngine;
 { *************************************************************************************************** }

 { *************************************************************************************************** }
                                             Interface
 { *************************************************************************************************** }

uses Graphics, windows, classes, PowerD3D, agfunit;

type
  TParticle = class
  protected
    Visual: Integer;
  public
    layer : byte;
    Scale : Double;
    angle : 0..359;
    Color : TColor;
    Cycle: single;
    Xpos, Ypos, Xvel, Yvel : Single;
    LifeTime : Single;
    Next: TParticle;
    constructor Create();
    procedure Draw(); virtual; abstract;
    Function Process() : Boolean; virtual; abstract;
  end;

  Texplosion = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TexplosionRing = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;  

  TLightParticle = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TBrightParticle = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TBloodParticle = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TSmokeParticle = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TFogParticle = class (TParticle)
    procedure Draw(); override;
    Function Process() : Boolean; override;
  end;

  TParticleEngine = class
  private
    ListHead: TParticle;
    function GetCount(): Integer;
  public
    property Count: Integer read GetCount;
    constructor Create(_renderer : TPowerGraph);{ _explosion, _particle, _blood, _Smoke, _Exp_ring : TAGFImage);}
    procedure AddExplosion(Xpos, Ypos, Xvel, Yvel: Single; Scale : Integer);
    procedure AddExplosionRing(Xpos, Ypos, Xvel, Yvel : Single);    
    procedure AddExplosionEx(Xpos, Ypos, Xvel, Yvel: Single; Scale : Integer);
    procedure AddLightParticle(Xpos, Ypos, Xvel, Yvel: Single; color : TColor; LifeTime : Single; _layer : byte);
    procedure AddBrightParticle(Xpos, Ypos, Xvel, Yvel: Single; color : TColor; LifeTime : Single; _layer : byte);
    procedure AddBlood(Xpos, Ypos : Single);
    procedure AddSmoke(Xpos, Ypos : Single; scale : Integer);
    procedure AddFog(Xpos, Ypos : Single; Color : cardinal);
    procedure Process(_Elapsed : Cardinal; cX, cY : Integer);
      { Рендерить слой номер i
        0 - кровь и партиклы, нуждающиеся в рисовании ПОД монстрами и стенами
        1 - свободный слой
        2 - Взрывы и дым}
    procedure Render(i : byte);
  end;

var
 Renderer : TPowerGraph;
 Elapsed : Cardinal;
 explosion : TAGFImage;
 Particle : TAGFImage;
 Blood : TAGFImage;
 Smoke : TAGFImage;
 exp_ring : TAGFImage;
 CamX, CamY : Integer;


 { *************************************************************************************************** }
                                           implementation
 { *************************************************************************************************** }

 { TParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
constructor TParticle.Create();
begin
 inherited Create;

 Cycle:= 0;
 Visual:= 0;

 Next:= nil;
end;


 { TExplosion }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TExplosion.Draw();
begin
  Renderer.RotateEffectFix(Explosion, Round(CamX + Xpos), Round(CamY + Ypos), 90, trunc(Scale), Round(Cycle), effectAdd);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function Texplosion.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/50;
 Result:= Cycle < 16;
end;


 { TExplosionRing }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TExplosionRing.Draw();
begin
  Renderer.RotateEffectFix(Exp_ring, Round(CamX + Xpos), Round(CamY + Ypos), 0, trunc(Scale), Color or (round(lifetime*100-Cycle*100) div 10 shl 24), 0, effectSrcAlpha or effectDiffuseAlpha or effectAdd);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TExplosionRing.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/30;
 Scale:=Scale + Elapsed*0.7;
 Result:= Cycle < Lifetime;
end;


 { TLightParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TLightParticle.Draw();
begin
  Renderer.RotateEffectFix(Particle, Round(CamX + Xpos), Round(CamY + Ypos), 0, 256, Color or (round(lifetime*100-Cycle*100) div 100 shl 24) , 0, effectSrcAlpha or effectDiffuseAlpha or effectAdd);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TLightParticle.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/50;
 Result:=Cycle < LifeTime;
end;

 { TBrightParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TBrightParticle.Draw();
begin
  Renderer.RotateEffectFix(Particle, Round(CamX + Xpos), Round(CamY + Ypos), 0, 256, Color, 0, effectSrcAlpha or effectDiffuseAlpha);
end;


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TBrightParticle.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/50;
 Result:=Cycle < LifeTime;
end;

 { TBloodParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TBloodParticle.Draw();
begin
  Renderer.RotateEffectFix(Blood, Round(CamX + Xpos), Round(CamY + Ypos), Angle, trunc(Scale), Color or (round(Lifetime*100-Cycle*100) div 100 shl 24), 0 , effectSrcAlpha or effectDiffuseAlpha);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TBloodParticle.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/300;
 Result:=Cycle < LifeTime;
end;

 { TSmokeParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TSmokeParticle.Draw();
begin
  Renderer.RotateEffectFix(Smoke, Round(CamX + Xpos), Round(CamY + Ypos), Angle, trunc(Scale), Color or (round(Lifetime*100-Cycle*100) div 10 shl 24), 0 , effectSrcAlpha or effectDiffuseAlpha);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TSmokeParticle.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/50;
 Result:=Cycle < LifeTime;
 angle:=angle + 1;
 Scale:=Scale + 3;
end;

 { TFogParticle }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TFogParticle.Draw();
begin
  if Cycle<50 then
  Renderer.RotateEffect(Smoke, Round(CamX + Xpos), Round(CamY + Ypos), Angle, trunc(Scale), Color or (round(Cycle*50)div 10 shl 24), 0 , effectSrcAlpha or effectDiffuseAlpha)
  else
  Renderer.RotateEffect(Smoke, Round(CamX + Xpos), Round(CamY + Ypos), Angle, trunc(Scale), Color or (round(Lifetime*50-Cycle*50) div 10 shl 24), 0 , effectSrcAlpha or effectDiffuseAlpha)
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TFogParticle.Process(): Boolean;
begin
 Xpos:= Xpos + Xvel*Elapsed/10;
 Ypos:= Ypos + Yvel*Elapsed/10;

 Cycle:=Cycle + Elapsed/300;
 Scale:=Scale + Elapsed/10;
 Result:=Cycle < LifeTime;
end;

 { TParticleEngine }

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
constructor TParticleEngine.Create(_renderer : TPowerGraph);{ _explosion, _particle, _blood, _Smoke, _Exp_ring : TAGFImage);}
begin
 inherited Create;

{ explosion:=_explosion;
 Particle:=_particle;
 Blood:=_blood;
 Smoke:=_Smoke;
 exp_ring:=_Exp_ring;}
 Renderer:=_renderer;
 ListHead:= nil
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddExplosion(Xpos, Ypos, Xvel, Yvel: Single; Scale : Integer);
var
  Particle : TParticle;
begin
 Particle:= Texplosion.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= Xvel;
 Particle.Yvel:= Yvel;
 Particle.layer:= 2;
 Particle.Next:= ListHead;
 Particle.Scale:= Scale;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddExplosionEx(Xpos, Ypos, Xvel, Yvel: Single; Scale : Integer);
var
  Particle : TParticle;
begin
 Particle:= Texplosion.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= Xvel;
 Particle.Yvel:= Yvel;
 Particle.layer:= 2;
 Particle.Next:= ListHead;
 Particle.Scale:= Scale;

 ListHead:= Particle;

 AddExplosionRing(Xpos,Ypos,Xvel,Yvel);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddExplosionRing(Xpos, Ypos, Xvel, Yvel : Single);
var
  Particle : TParticle;
begin
 Particle:= TexplosionRing.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= Xvel;
 Particle.Yvel:= Yvel;
 Particle.layer:= 2;
 Particle.Next:= ListHead;
 Particle.Scale:= 0;
 Particle.LifeTime:=20 + random(100)/20;
 Particle.Color:=$00FFFFFF;

 ListHead:= Particle;

 AddLightParticle(Xpos,Ypos,sin(2/(Pi/2))*512/255,cos(2/(Pi/2))*512/255,$00FFFFFF,20 + random(100)/20,0);
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddLightParticle(Xpos, Ypos, Xvel, Yvel: Single; color : TColor; LifeTime : Single; _layer : byte);
var
  Particle : TParticle;
begin
 Particle:= TLightParticle.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= Xvel;
 Particle.Yvel:= Yvel;
 Particle.Color:=color;
 Particle.LifeTime:=LifeTime;
 Particle.layer:=_layer;
 Particle.Next:= ListHead;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddBrightParticle(Xpos, Ypos, Xvel, Yvel: Single; color : TColor; LifeTime : Single; _layer : byte);
var
  Particle : TParticle;
begin
 Particle:= TBrightParticle.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= Xvel;
 Particle.Yvel:= Yvel;
 Particle.Color:=color;
 Particle.LifeTime:=LifeTime;
 Particle.layer:=_layer;
 Particle.Next:= ListHead;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddBlood(Xpos, Ypos : Single);
var
  Particle : TParticle;
begin
 Particle:= TBloodParticle.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= 0;
 Particle.Yvel:= 0;
 Particle.LifeTime:=100;
 Particle.layer:=0;
 Particle.Color:=$00000077;
 Particle.angle:=Random(360);
 Particle.Scale:=100 + Random(200);
 Particle.Next:= ListHead;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddSmoke(Xpos, Ypos : Single; scale : Integer);
var
  Particle : TParticle;
begin
 Particle:= TSmokeParticle.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= 0;
 Particle.Yvel:= 0;
 Particle.LifeTime:=10;
 Particle.layer:=1;
 Particle.Color:=$00FFFFFF;
 Particle.angle:=Random(360);
 Particle.Scale:=scale;
 Particle.Next:= ListHead;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.AddFog(Xpos, Ypos : Single; Color : cardinal);
var
  Particle : TParticle;
begin
 Particle:= TFogParticle.Create();

 Particle.Xpos:= Xpos;
 Particle.Ypos:= Ypos;
 Particle.Xvel:= 0;
 Particle.Yvel:= 0;
 Particle.LifeTime:=100;
 Particle.layer:=2;
 Particle.Color:=Color;
 Particle.angle:=Random(360);
 TFogParticle(Particle).Scale:=Random(156) + 100;
 Particle.Next:= ListHead;

 ListHead:= Particle;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.Process(_Elapsed : Cardinal; cX, cY : Integer);
var
  Aux, Temp : TParticle;
begin
 // adding one useless particle for reference purposes
 Aux:= TParticle.Create();
 Aux.Next:= ListHead;
 ListHead:= Aux;

 // moving all the particles
 while (Aux <> nil) and (Aux.Next <> nil) do
  begin
   if (not Aux.Next.Process()) then
    begin
     Temp:= Aux.Next;
     Aux.Next:= Aux.Next.Next;
     Temp.Free();
    end;
   Aux:= Aux.Next;
  end;

 // removing first reference object
 Temp:= ListHead;
 ListHead:= ListHead.Next;
 Temp.Free();

 Elapsed:=_Elapsed;
 CamX:=cX;
 CamY:=cY;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
procedure TParticleEngine.Render(i : byte);
var
  Aux : TParticle;
begin
 Aux:= ListHead;

 while (Aux <> nil) do
  begin
   if Aux.layer=i then
   if (Aux is TFogParticle) then
   Aux.Draw() else
   if (Aux.Xpos>abs(CamX)-32) and (Aux.Ypos>abs(CamY)-32) and (Aux.Xpos<abs(CamX - 1024)) and (Aux.Ypos<abs(CamY - 768)) then
   Aux.Draw();

   Aux:= Aux.Next;
  end;
end;

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
function TParticleEngine.GetCount(): Integer;
var
  Aux : TParticle;
begin
 Result:= 0;
 Aux:= ListHead;

 while (Aux <> nil) do
  begin
   Inc(Result);
   Aux:= Aux.Next;
  end;
end;

end.
