unit Frost.Particles;

interface

uses
  OpenGL,
  _Geometry,
  Frost.GL,
  Frost.GameObjects,
  Frost.Renderers,
  Frost.RenderEngine;

type

TParticleMaterial = class (TTexturedMaterial)
  public
    constructor Create;
end;

TParticle = record
  Active: Boolean;
  Position,
  Velocity: TVector3;
  Size,
  SizeChanging: Single;
  Life: Single;
  end;

TEmitter = class (TComponent)
  private
    FMaterial: TMaterial;
  public
    class function CreateEmitter(Material: TMaterial): TEmitter;
    procedure Render;
    procedure OnStart; override;
    procedure OnDestroy; override;
    property Material: TMaterial read FMaterial write FMaterial;
  end;

TParticleRendererLayer = class sealed (TRenderLayer)
  private
    class var FInstance: TParticleRendererLayer;
    const MAX_PARTICLES = 3000;
  private
    FParticles: Array of TParticle;
    FMaterial: TMaterial;  
    FVertex:   TVertexes;
    FTextures: TTextures;
    FColors:   TColors;
    FFaces:    TQuadFace;
    FCurrentParticleIndex: Cardinal;

    FRendererCount,
    FMaxRenderers: Integer;
    FRenderer: array of TEmitter;     
    procedure AddParticle(Position, Velocity: TVector3; Life: Single; Size, SizeChange: Single);
    procedure AddRenderer(Renderer: TEmitter);
    procedure RemoveRenderer(Renderer: TEmitter);
  protected
    procedure Render; override;
  public                 
    procedure UpdateParticles(Delta: Extended; const Camera: TCamera);
    class procedure Emit(Position, Velocity: TVector3; Life: Single; Size, SizeChange: Single);
    constructor Create;
    destructor Destroy; override;
  end;

function Quad(A,B,C, D: Cardinal): TQuad;

implementation   

{$REGION 'Quad'}
function Quad(A,B,C, D: Cardinal): TQuad;
begin
  Result[0] := A;
  Result[1] := B;
  Result[2] := C;  
  Result[3] := D;
end;
{$ENDREGION}

{$REGION 'material'}     
procedure GenerateParticleTexture(var TextureIndex: Cardinal);
var
  i, j: Word;
  Value: Single;
  ColorBrightness,
  Color: Byte;
  TextureBit: array of Byte;
  PTexture: Pointer;
const
  TEXTURE_SIZE = 32;
  PIXEL_BYTES = 4;
begin
  SetLength(TextureBit, TEXTURE_SIZE * TEXTURE_SIZE * PIXEL_BYTES);
  For i:=0 to TEXTURE_SIZE - 1 do
    For j:=0 to TEXTURE_SIZE - 1 do
      begin
        Value := sqrt((i - TEXTURE_SIZE / 2) * (i - TEXTURE_SIZE / 2) + (j - TEXTURE_SIZE / 2) * (j - TEXTURE_SIZE / 2));
        Value := (150 + Random(250)) * Value / (TEXTURE_SIZE / 2) * Value / (TEXTURE_SIZE / 2);
        if Value > 255 then 
          Value := 255;
        ColorBrightness := 255 - Trunc( Value);//Cells
        Color := 100 + Random(155);
        TextureBit[i * 4 + (j) * TEXTURE_SIZE * 4 + 0]:= Color;
        TextureBit[i * 4 + (j) * TEXTURE_SIZE * 4 + 1]:= Color;
        TextureBit[i * 4 + (j) * TEXTURE_SIZE * 4 + 2]:= Color;
        TextureBit[i * 4 + (j) * TEXTURE_SIZE * 4 + 3]:= ColorBrightness;
      end;
 PTexture := TextureBit;
 TextureIndex := GenerateTexture(PTexture, TEXTURE_SIZE, TEXTURE_SIZE);
end;

constructor TParticleMaterial.Create;
begin
  GenerateParticleTexture(FTextureId);
end;

{$ENDREGION}

{$REGION 'TEmitter'}
class function TEmitter.CreateEmitter(Material: TMaterial): TEmitter;
var
  Emmiter: TEmitter;
begin
  Emmiter := TEmitter.Create(nil);
  Emmiter.FMaterial := Material;
  Result := Emmiter;
end;

procedure TEmitter.Render;
begin           
  {if FMaterial <> nil then
    FMaterial.Use;
  if FMesh <> nil then
    FMesh.Draw;  }
end;

procedure TEmitter.OnStart;
begin
  if (TParticleRendererLayer.FInstance <> nil) then
    TParticleRendererLayer.FInstance.AddRenderer(Self);
end;

procedure TEmitter.OnDestroy;
begin                         
  if (TParticleRendererLayer.FInstance <> nil) then
    TParticleRendererLayer.FInstance.RemoveRenderer(Self);
end;
{$ENDREGION}
          
{$REGION 'TParticleRendererLayer'}
constructor TParticleRendererLayer.Create;
var
  i, j: Word;
begin          
  FInstance := Self;
  FRendererCount := 0;
  FMaxRenderers := 0;

  //TODO              
  FMaterial := TParticleMaterial.Create;
  SetLength(FParticles, MAX_PARTICLES);
  SetLength(FVertex  , MAX_PARTICLES * 4);
  SetLength(FTextures, MAX_PARTICLES * 4);
  SetLength(FColors  , MAX_PARTICLES * 4);
  SetLength(FFaces   , MAX_PARTICLES);

  for i := 0  to MAX_PARTICLES - 1 do
  begin
    FFaces[i] := Quad(i * 4 + 0, i * 4 + 1, i * 4 + 2, i * 4 + 3);
    for j := 0 to 3 do
      FColors[i * 4 + j] := Vector4(0.2, 0.2, 0.2,1);
    FTextures[i * 4 + 0] := Vector2(0, 0);
    FTextures[i * 4 + 1] := Vector2(1, 0);
    FTextures[i * 4 + 2] := Vector2(1, 1);
    FTextures[i * 4 + 3] := Vector2(0, 1);
  end;
end;

class procedure TParticleRendererLayer.Emit(Position, Velocity: TVector3; Life: Single; Size, SizeChange: Single);
begin
  if (FInstance <> nil) then
  with FInstance do
  begin
    AddParticle(Position, Velocity, Life, Size, SizeChange);
  end;
end;

procedure TParticleRendererLayer.AddParticle(Position, Velocity: TVector3; Life: Single; Size, SizeChange: Single);
var
  Marker: Boolean;
  i: Word;
begin
  Marker := False;
  for i := FCurrentParticleIndex to MAX_PARTICLES - 1 do
    if not FParticles[i].Active then
    begin
      FParticles[i].Active := True;   
      FParticles[i].Position := Position;
      FParticles[i].Velocity := Velocity;
      FParticles[i].Life := Life;
      FParticles[i].Size := Size;      
      FParticles[i].SizeChanging := SizeChange;
      Marker := True;
      FCurrentParticleIndex := i;
      Break;
    end;
  if not Marker then
    for i := 0 to FCurrentParticleIndex - 1 do
      if not FParticles[i].Active then
      begin
        FParticles[i].Active := True;
        FParticles[i].Position := Position;
        FParticles[i].Velocity := Velocity;
        FParticles[i].Life := Life;      
        FParticles[i].Size := Size;
        FParticles[i].SizeChanging := SizeChange;
        FCurrentParticleIndex := i;
        Marker := True;
        Break;
      end;
  
  Inc(FCurrentParticleIndex);
  if (FCurrentParticleIndex >= MAX_PARTICLES - 1) then
    FCurrentParticleIndex := 0;
end;

destructor TParticleRendererLayer.Destroy;
begin
  inherited;
end;

procedure TParticleRendererLayer.AddRenderer(Renderer: TEmitter);
begin                 
  if Renderer = nil then
    Exit;
  if (FRendererCount >= FMaxRenderers) then
    begin
      FMaxRenderers := FMaxRenderers + 5;
      SetLength(FRenderer, FMaxRenderers + 1);
    end;               
  FRenderer[FRendererCount] := Renderer;
  Inc(FRendererCount);
end;

procedure TParticleRendererLayer.RemoveRenderer(Renderer: TEmitter);
var
  i: Word;
begin
  for i := 0 to FRendererCount - 1 do
    if (FRenderer[i] = Renderer) then
      begin
        FRenderer[i] := nil;
        break;
      end;
end;

procedure TParticleRendererLayer.UpdateParticles(Delta: Extended; const Camera: TCamera);
var
  i, j: Word;
  Color: Single;
  Sides: Array [0..3] of TVector3;
const
  SIDE_POINT: Array [0..3] of TVector3 =  (
        (A : ( 1, 0, 0)),
        (A : ( 0, 1, 0)),
        (A : (-1, 0, 0)),
        (A : ( 0,-1, 0)));
begin
  for j := 0  to 3 do
    Sides[j] := TMatrix3(Camera.View) * SIDE_POINT[j];
  for i := 0 to MAX_PARTICLES - 1 do
    if FParticles[i].Active then
    begin
      FParticles[i].Life := FParticles[i].Life - Delta;
      FParticles[i].Position := FParticles[i].Position + FParticles[i].Velocity * Delta;
      FParticles[i].Size := FParticles[i].Size + FParticles[i].SizeChanging * Delta;
      Color := FParticles[i].Life / 10;
      if (Color > 1) then
        Color := 1;
      for j := 0 to 3 do
      begin
        FVertex[i * 4 + j] := FParticles[i].Position + Sides[j] * FParticles[i].Size;
        FColors[i * 4 + j].W := Color;
      end;  
      if FParticles[i].Life <= 0 then
      begin
        FParticles[i].Active := False;   
        for j := 0 to 3 do
        begin
          FVertex[i * 4 + j] := VECTOR3_NULL;
        end;
      end;
    end;

end;
 
procedure TParticleRendererLayer.Render;
begin
  FMaterial.Use;
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  glColor4f(5,5,5,5);
  glDepthMask(False);
  //glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  //glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY); 
  glColorPointer(4, GL_FLOAT, 0, FColors);
  glTexCoordPointer(2, GL_FLOAT, 0, FTextures);
  glVertexPointer(3, GL_FLOAT, 0, FVertex);

  glDrawElements(GL_QUADS, (MAX_PARTICLES) * 4, GL_UNSIGNED_INT, FFaces);
  {for i := 0 to FRendererCount - 1 do
    if (FRenderer[i] <> nil)then// and (FRenderer[i].FMesh <> nil) then
    begin
     // SetFlags(FRenderer[i].FMesh.FRenderFlags, CurrentFlags);
      glPushMatrix;
      glMultMatrixf(@FRenderer[i].GameObject.View);
      FRenderer[i].Render;  
      glPopMatrix;
    end;   }
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  glDisableClientState(GL_VERTEX_ARRAY);
  //glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_COLOR_ARRAY);   
  //glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glDepthMask(True);
  glDisable(GL_BLEND);
end;
{$ENDREGION}

end.
