unit l_particle;
(*************************************)
(* XParticle libary                  *)
(*************************************)
(* site : http://XProger.mirgames.ru *)
(* mail : XProger@list.ru            *)
(*************************************)
interface

uses
 OpenGL,
 l_math, l_gl, l_model;

type
 PP_Particle = ^TP_Particle;

 TP_Particle = class
   constructor Create(Pos: TVector);
  protected
   Next : TP_Particle;
   Die  : integer;
   ID   : Byte;    // Используется, как идентификатор порядка при отрисовке
  public
   Pos : TVector;
   procedure Update; virtual;
   procedure Render; virtual; abstract;
 end;

 TP_Trace = class(TP_Particle)
   constructor Create(Pos, EPos: TVector);
  public
   EPos  : TVector;
   procedure Render; override;
 end;

 TP_Smoke = class(TP_Particle)
   constructor Create(Pos: TVector);
  public
   procedure Update; override;
   procedure Render; override;
 end;

 TP_Ricochet = class(TP_Smoke)
   procedure Render; override;
 end;
 
 TP_Fire = class(TP_Particle)
   constructor Create(Pos: TVector);
  public
   Angle : single;
   procedure Render; override;
 end;

 TP_Gibs = class(TP_Particle)
   constructor Create(Pos: TVector; Angle: single; Mesh: TMesh);
  public
   Angle    : single;
   Speed    : TVector;
   onground : boolean;
   Mesh     : TMesh;
   procedure Update; override;
   procedure Render; override;
 end;

 TP_Blood = class(TP_Particle)
   constructor Create(Pos: TVector);
  public
   Speed  : TVector;
   Angle  : single;
   dAngle : single;
   procedure Update; override;
   procedure Render; override;
 end;

procedure Particle_Init;
procedure Particle_Free;
procedure Particle_Clear;
function  Particle_Add(P: TP_Particle): TP_Particle;
procedure Particle_Update;
procedure Particle_Render;
function  Particle_Count: integer;
procedure Particle_Gibs(Pos: TVector);

const
 SMOKE_SIZE    = 1;
 RICOCHET_SIZE = 1;
 FIRE_SIZE     = 1;
 BLOOD_SIZE    = 1;
 BLOOD_COUNT   = 3;

var
 smoke    : TTexture;
 ricochet : TTexture;
 fire     : TTexture;
 blood    : TTexture;
 gibs     : PRGB;

 nogibs   : boolean;
 noblood  : boolean;
 
implementation

uses
 l_game, l_map;

var
 Particle        : TP_Particle;
 FParticle_Count : integer;

//// Биллборд ////
procedure glBillBoard(Pos: TVector; Size: single; Angle: single = 0);
var
 v      : array [0..3] of TVector;
 v1, v2 : TVector;
 m      : TMatrix4;
begin
with Camera do
 begin
 v1 := V_Mult(Vector(m[0][0], m[1][0], m[2][0]), Size);
 v2 := V_Mult(Vector(m[0][1], m[1][1], m[2][1]), Size);
 end;

if Angle <> 0 then
 begin
 m := M_Rotation(V_Cross(v1, v2), Angle);
 v1 := M_Mult(m, v1);
 v2 := M_Mult(m, v2);
 end;

v[0] := V_Add(Pos, Vector(-v1.X - v2.X, -v1.Y - v2.Y, -v1.Z - v2.Z));
v[1] := V_Add(Pos, Vector( v1.X - v2.X,  v1.Y - v2.Y,  v1.Z - v2.Z));
v[2] := V_Add(Pos, Vector( v1.X + v2.X,  v1.Y + v2.Y,  v1.Z + v2.Z));
v[3] := V_Add(Pos, Vector(-v1.X + v2.X, -v1.Y + v2.Y, -v1.Z + v2.Z));

glBegin(GL_QUADS);
 glTexCoord2f(0, 0); glVertex3fv(@v[0]);
 glTexCoord2f(1, 0); glVertex3fv(@v[1]);
 glTexCoord2f(1, 1); glVertex3fv(@v[2]);
 glTexCoord2f(0, 1); glVertex3fv(@v[3]);
glEnd;
end;

procedure RenderToMap(Pos: TVector; Width, Height: integer; Data: PRGB);
const
 MIPMAPS = 4; // можно поставить и выше, но у некоторых личностей игра тормозила на 6600, хотя 5200 и Ti4200 тянули без проблем ;)
var
 x, y : integer;
 i, j : integer;
 t    : integer;
 c    : TRGB;
 k    : single;
 tmp  : Byte;
begin
// рендер в текстуру
x := trunc(Pos.X/MAP_SCALE * 4) - Width div 2;
y := trunc(Pos.Z/MAP_SCALE * 4) - Height div 2;
glTex_Enable(Map.Tex);
for j := y to y + Height - 1 do
 begin
 if j < 0 then continue;
 if j >= 512 then break;
 for i := x to x + Width - 1 do
  begin
  if i < 0 then continue;
  if i >= 512 then break;
  c := Data[(j - y) * Width + (i - x)];
  if c.R + c.G + c.B > 0 then
   begin
   // В затенённых областях следы темнее
   with Map.TexData[j * 512 + i] do
    k := (R + G + B)/(3 * 255);
   with c do
    begin
    tmp := trunc(R * k);
    R := trunc(B * k);
    G := trunc(G * k);
    B := tmp;
    end;
   // рендер в mipmap уровни текстуры
   for t := 0 to MIPMAPS do
    if (i mod (1 shl t) = 0) and (j mod (1 shl t) = 0) then
     glTexSubImage2D(GL_TEXTURE_2D, t, i shr t, j shr t, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, @c);
   end;
  end;
 end;
end;
/////////////////////////

procedure Particle_Init;
begin
FParticle_Count := 0;
glClamp := true;
smoke    := glTex_Load('data\textures\smoke.tga');
ricochet := glTex_Load('data\textures\ricochet.tga');
fire     := glTex_Load('data\textures\fire.jpg');
blood    := glTex_Load('data\textures\blood.tga');
glTex_Free(glTex_Load('data\textures\gibs.tga', true));
gibs     := glTexBuf;
glClamp := false;
end;

procedure Particle_Free;
begin
glTex_Free(smoke);
glTex_Free(ricochet);
glTex_Free(fire);
glTex_Free(blood);
FreeMem(gibs);
end;

procedure Particle_Clear;
var
 p, n : TP_Particle;
begin
FParticle_Count := 0;
p := Particle;
while p <> nil do
 begin
 n := p;
 p := p.Next;
 n.Free;
 end;
Particle := nil; 
end;

function Particle_Add(P: TP_Particle): TP_Particle;
begin
p.Next := Particle;
Particle := p;
p.Update;
Result := p;
inc(FParticle_Count);
end;

procedure Particle_Update;
var
 n : PP_Particle;
 p : TP_Particle;
begin
n := @Particle;
while n^ <> nil do
 begin
 n^.Update;
 if n^.Die <= 0 then
  begin
  p := n^;
  n^ := n^.Next;
  p.Free;
  dec(FParticle_Count);
  end
 else
  n := @n^.Next;
 end;
end;

procedure Particle_Render;
var
 i : integer;
 n : PP_Particle;
begin
for i := 0 to 1 do
 begin
 n := @Particle;
 while n^ <> nil do
  begin
  if n^.ID = i then
   n^.Render;
  n := @n^.Next;
  end;
 end;
end;

function Particle_Count: integer;
begin
Result := FParticle_Count;
end;

procedure Particle_Gibs(Pos: TVector);
begin
RenderToMap(Pos, 8, 8, gibs);
end;

// базовый класс \\
constructor TP_Particle.Create(Pos: TVector);
begin
self.Pos := Pos;
Die      := 1;
ID       := 0;
end;

procedure TP_Particle.Update;
begin
dec(Die);
end;

// cлед от пули \\
constructor TP_Trace.Create(Pos, EPos: TVector);
var
 Color  : TRGB;
 len    : single;
 Normal : TVector;
begin
inherited Create(Pos);
self.EPos := EPos;
Die   := 3;

with EPos do
 Particle_Add(TP_Ricochet.Create(Vector(X, Y + RICOCHET_SIZE/2, Z)));

Color.R := 32;
Color.G := 32;
Color.B := 32;
RenderToMap(EPos, 1, 1, @Color);

Normal := V_Sub(EPos, Pos);
len    := V_Length(Normal);
Normal := V_Mult(Normal, 1/len);

self.EPos := V_Add(Pos, V_Mult(Normal, random*(len/2)));  // хвост
self.Pos  := V_Add(self.EPos, V_Mult(Normal, random*(len/2)));
end;

procedure TP_Trace.Render;
begin
glEnable(GL_LINe_SMOOTH);
glTex_Disable;
glBegin(GL_LINES);
 glColor4f(1, 1, 0, 1);   glVertex3fv(@Pos);
 glColor4f(1, 0, 0, 0.3); glVertex3fv(@EPos);
glEnd;
glDisable(GL_LINe_SMOOTH);
end;

// дымок \\
constructor TP_Smoke.Create(Pos: TVector);
begin
inherited;
Die := 50;
ID  := 1;
end;

procedure TP_Smoke.Update;
begin
inherited;
Pos.Y := Pos.Y + 0.02;
end;

procedure TP_Smoke.Render;
begin
if not Camera.inFrustum(Pos, SMOKE_SIZE) then Exit;
glTex_Enable(smoke);
glDepthMask(false);
glColor4f(0.8, 0.8, 0.8, Die/250);
glBillBoard(Pos, (50 - Die)/50 * SMOKE_SIZE + 0.1);
glDepthMask(true);
end;

// рикошет \\
procedure TP_Ricochet.Render;
begin
if not Camera.inFrustum(Pos, RICOCHET_SIZE) then Exit;
glTex_Enable(ricochet);
glDepthMask(false);
glColor4f(1, 1, 1, Die/50);
glBillBoard(Pos, (50 - Die)/50 * RICOCHET_SIZE + 0.1);
glDepthMask(true);
end;

// огонь при выстреле \\
constructor TP_Fire.Create(Pos: TVector);
begin
inherited;
Die := 2;
ID  := 1;
Angle := random*pi*2;
end;

procedure TP_Fire.Render;
begin
glTex_Enable(fire);
glBlend(2);
glDepthMask(false);
glColor4f(1, 1, 1, 1);

glBillBoard(Pos, FIRE_SIZE, Angle);

glDepthMask(true);
glBlend(1);
end;

// какашки \\
constructor TP_Gibs.Create(Pos: TVector; Angle: single; Mesh: TMesh);
begin
inherited Create(Pos);
onground   := false;
Speed      := Vector(random*0.8 - 0.4, 0.4, random*0.8 - 0.4);
die        := 128;
self.Mesh  := Mesh;
self.Angle := Angle * rad2deg;
end;

procedure TP_Gibs.Update;
var
 p : TVector;
begin
if not onground then
 begin
 Speed.Y := Speed.Y - 0.04;
 Pos := V_Add(Pos, Speed);
 p   := Pos;
 Map.Collision(p);
 if p.Y > Pos.Y then
  begin
  Pos.Y    := p.Y;
  onground := true;
  end;
 end
else
 dec(Die);
end;

procedure TP_Gibs.Render; 
begin
if not Camera.inFrustum(Pos, 2) then Exit;
glTex_Disable;
glEnable(GL_LIGHTING);
glPushMatrix;
glTranslatef(Pos.X, Pos.Y - (1 - die/128) * 2, Pos.Z);
glRotatef(Angle, 0, 1, 0);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glColor3ubv(@Mesh.Color);
with Mesh do
 begin
 glNormalPointer(GL_FLOAT, 0, @Normal[0]);
 glVertexPointer(3, GL_FLOAT, 0, @Vertex[0]);
 glDrawElements(GL_TRIANGLES, F_Count*3, GL_UNSIGNED_SHORT, @Face[0]);
 end;
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glPopMatrix;
glDisable(GL_LIGHTING);
end;

// кровяка //
constructor TP_Blood.Create(Pos: TVector);
begin
inherited;
Speed  := Vector(random*0.8 - 0.4, 0.4, random*0.8 - 0.4);
die    := 128;
Angle  := random * 2 * pi;
dAngle := random * pi/12 - pi/24;
end;

procedure TP_Blood.Update;
var
 p : TVector;
begin
inherited;
Angle   := Angle + dAngle;
Speed.Y := Speed.Y - 0.04;
Pos     := V_Add(Pos, Speed);
p := Pos;
Map.Collision(p);
if p.Y > Pos.Y then
 begin
 die := 0;
 Particle_Gibs(Pos);
 end;
end;

procedure TP_Blood.Render;
begin
if not Camera.inFrustum(Pos, BLOOD_SIZE) then Exit;
glTex_Enable(blood);
glBlend(1);
glColor4f(1, 1, 1, die/128);
glBillBoard(Pos, BLOOD_SIZE, Angle);
end;

end.
