unit l_map;

interface

uses
 Windows, OpenGL, l_utils, JpegObj, KOL, err,
 l_math, l_gl, l_model, l_spider, l_zenit, l_sound, l_particle;

type
 TMap = object
   snd__ric  : integer;
   W, H      : integer;  // мнимая ширина и высота карты
   Faces     : integer;  // Кол-во полигонов
   Tex       : TTexture;
   TexData   : PRGB;
   DetailTex : TTexture;
   Sky       : array [0..4] of TTexture;
   Vertex    : array of TVector;
   Face      : array of TFace;
   TexCoord  : array of TVector2D;
   DetCoord  : array of TVector2D;
   Plane     : array of TPlane;
  protected
   V_Buff  : DWORD;
   F_Buff  : DWORD;
  public
   EnvTex  : TTexture;
   Spiders : integer; // Кол-во пауков на карте
   Zenits  : integer; // Кол-во стационарок
   Spider  : PSpider; // Список пауков
   Zenit   : PZenit;  // Список стационарок
  // работа со списком пауков
   function Spider_add: PSpider;
   procedure Spider_del(s: PSpider);
  // работа со списком стационарок
   function Zenit_add: PZenit;
   function Zenit_del(s: PZenit): PZenit;

   procedure Init;
   procedure Clear(all: boolean);
   function LoadFromFile(const Name: string): boolean;
   function Collision(var Pos, Normal: TVector): integer; overload;
   function Collision(var Pos: TVector): integer; overload;
   function Fire(Pos, Angle: TVector): boolean;
   procedure Update;
   procedure Render;
   procedure RenderSkyBox;
 end;

const
 MAP_SCALE   = 2;
 MAP_DETAIL  = 0.1;

var
 detail     : boolean = true;  // использовать текстуру детализации
 pcollision : boolean = false; // проверка коллизий для игрока

implementation

uses
 l_game;

// пушки
function TMap.Spider_add: PSpider;
var
 s : PSpider;
begin
New(s);
s^.Init;
s^.Next := Spider;
if Spider <> nil then
 Spider^.Prev := s;
Spider := s;
Result := Spider;
inc(Spiders);
end;

procedure TMap.Spider_del(s: PSpider);
begin
if s^.Prev <> nil then
 s^.Prev^.Next := s^.Next
else
 Spider := s^.Next;
if s^.Next <> nil then
 s^.Next^.Prev := s^.Prev;
Dispose(s);
dec(Spiders);
end;

// стационарки
function TMap.Zenit_add: PZenit;
var
 s : PZenit;
begin
New(s);
s^.Init;
s^.Next := Zenit;
if Zenit <> nil then
 Zenit^.Prev := s;
Zenit  := s;
Result := s;
inc(Zenits);
end;

function TMap.Zenit_del(s: PZenit): PZenit;
begin
Result := s^.Next;
if s^.Prev <> nil then
 s^.Prev^.Next := s^.Next
else
 Zenit := s^.Next;
if s^.Next <> nil then
 s^.Next^.Prev := s^.Prev;
Dispose(s);
dec(Zenits);
end;

procedure TMap.Init;
var
 i : integer;
begin
F_Buff  := 0;
V_Buff  := 0;
Spiders := 0;
Zenits  := 0;
Spider := nil;
Zenit  := nil;
EnvTex    := glTex_Load('data\textures\env.jpg');
DetailTex := glTex_Load('data\textures\detail.jpg');
snd__ric  := snd_Load('data\sounds\ric.wav');
TexData   := nil;
// Грузим скайбокс
for i := 0 to 4 do
 begin
 Sky[i] := glTex_Load('data\textures\sky_' + IntToStr(i + 1) + '.jpg');
 glTex_Enable(Sky[i]);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 end;
end;

procedure TMap.Clear(all: boolean);
begin
if TexData <> nil then
 begin
 FreeMem(TexData);
 TexData := nil;
 end;  
// геометрия
Vertex   := nil;
Face     := nil;
TexCoord := nil;
DetCoord := nil;
Plane    := nil;

Dispose(Spider); // паучки
Dispose(Zenit);  // пушки
Zenit   := nil;
Spider  := nil;
Zenits  := 0;
Spiders := 0;
glTex_Free(Tex);
if all then
 begin
 // текстуры
 glTex_Free(EnvTex);
 glTex_Free(DetailTex);
 // звуки
 snd_Free(snd__ric);
 end;
end;

function TMap.LoadFromFile(const Name: string): boolean;
var
 jpg  : PJPEG;
 x, y : integer;
 i, j : integer;
 p    : PRGB;
begin
Clear(false);
Result := false;
 try
 JPG := NewJPEG;

 if jpg.LoadFromFile('data\maps\' + Name + '_height.jpg') then
  begin
  W := jpg.Width;
  H := jpg.Height;
  Faces := (W - 1) * (H - 1) * 2;
  SetLength(Vertex, W * H);
  SetLength(TexCoord, W * H);
  SetLength(DetCoord, W * H);
  SetLength(Face, Faces);
  i := 0; // индекс вершины
  j := 0; // индекс полигона
  for y := 0 to H - 1 do
   begin
   p := jpg.Bitmap.ScanLine[y];
   for x := 0 to W - 1 do
    begin
    // вершины
    Vertex[i].X := x * MAP_SCALE;
    Vertex[i].Y := p[x].R/16 * MAP_SCALE;
    Vertex[i].Z := y * MAP_SCALE;
    TexCoord[i].X := x/W;
    TexCoord[i].Y := y/H;
    DetCoord[i].X := x * MAP_DETAIL;
    DetCoord[i].Y := y * MAP_DETAIL;
    // полигоны
    if (x < W - 2) and
       (y < H - 2) then
     begin
     Face[j][0] := i + W;
     Face[j][1] := i + 1;
     Face[j][2] := i;
     inc(j);
     Face[j][0] := i + W + 1;
     Face[j][1] := i + 1;
     Face[j][2] := i + W;
     inc(j);
     end;
    inc(i);
    end;
   end;
  end;
 jpg.Free;

 Tex := glTex_Load('data\maps\' + Name + '.jpg', true);
 TexData := glTexBuf;
 // Расчёт плоскостей полигонов
 SetLength(Plane, Faces);
 for j := 0 to Faces - 1 do
  Plane[j] := P_Plane(Vertex[Face[j][0]],
                      Vertex[Face[j][1]],
                      Vertex[Face[j][2]]);
 Result := true;
 except
 // Ипаца! Обмануют цуки!
 end;
end;

function TMap.Collision(var Pos, Normal: TVector): integer;
var
 j : integer;
begin
// Ограничение на свободу перемещения :)
if Pos.X < 10 * MAP_SCALE then Pos.X := 10 * MAP_SCALE;
if Pos.Z < 10 * MAP_SCALE then Pos.Z := 10 * MAP_SCALE;
if Pos.X > (W - 10) * MAP_SCALE then Pos.X := (W - 10) * MAP_SCALE;
if Pos.Z > (H - 10) * MAP_SCALE then Pos.Z := (H - 10) * MAP_SCALE;
// Ограничение зоны проходимости для игрока
if pcollision then
 begin
 if Pos.X > 75 * MAP_SCALE then Pos.X := 75 * MAP_SCALE;
 if Pos.Z > 75 * MAP_SCALE then Pos.Z := 75 * MAP_SCALE;
 end;
// Достаточно хитрый алгоритм вычисления полигона под ногами
j := (trunc(Pos.Z) div MAP_SCALE * (W - 2) + trunc(Pos.X) div MAP_SCALE) * 2;
with Vertex[Face[j][0]] do
 if (Vertex[Face[j][1]].X - X) * (Pos.Z - Z) - (Vertex[Face[j][1]].Z - Z) * (Pos.X - X) >= 0 then
  j := j + 1;
// Вектор нормали
Normal := Plane[j].Normal;
// Ищем высоту
// Ax + By + Cz + D = 0
// => y = (-Ax - Cz - D)/B
with Normal do
 Pos.Y := (- X * Pos.X - Z * Pos.Z - Plane[j].D)/Y;
Result := j;
end;

// Оптимизированная прокерка высоты (для пули)
function TMap.Collision(var Pos: TVector): integer;
var
 j : integer;
begin
j := (trunc(Pos.Z) div MAP_SCALE * (W - 2) + trunc(Pos.X) div MAP_SCALE) * 2;
with Vertex[Face[j][0]] do
 if (Vertex[Face[j][1]].X - X) * (Pos.Z - Z) - (Vertex[Face[j][1]].Z - Z) * (Pos.X - X) >= 0 then
  j := j + 1;
with Plane[j].Normal do
 Pos.Y := (- X * Pos.X - Z * Pos.Z - Plane[j].D)/Y;
Result := j;
end;

function TMap.Fire(Pos, Angle: TVector): boolean;
const
 Step   = 2;
 OFFSET = pi/36; // разброс 10 градусов
var
 i      : integer;
 EPos   : TVector;
 Speed  : TVector;
 p, n   : TVector;
 D, r   : single;
 dist   : single;
 s, sc  : PSpider;
 spider_rad : single;
begin
Result := false;
Angle.X := Angle.X + random*OFFSET - OFFSET/2;
Angle.Y := Angle.Y + random*OFFSET - OFFSET/2;

n.X := cos(Angle.Y) * cos(Angle.X);
n.Y := sin(Angle.X);
n.Z := sin(Angle.Y) * cos(Angle.X);

Particle_Add(TP_Smoke.Create(Pos));
Particle_Add(TP_Fire.Create(Pos));

// Попадания во врагов
// n = (A, B, C)
D    := -V_Dot(Pos, n);
dist := 1000000;
sc := nil;
s  := Spider;
spider_rad := sqr(spider_xmd.Radius/2); // радиус попадания в 2 раза меньше видового
while s <> nil do
 begin
 r := V_Distq(Pos, s^.Pos);
 if (r < dist) and                  // расстояние не превышает минимальное
    (V_Dot(s^.Pos, n) + D > 0) and  // положение положительно траектории полёта
    // расстояние от траектории полёта до радиуса попадания паука
    (System.abs(V_Distq(Pos, s^.Pos)) - sqr(V_Dot(V_Sub(Pos, s^.Pos), n)) < spider_rad) then
  begin
  sc   := s;
  dist := r;
  end;
 s := s^.Next;
 end;

// Столкновение с ландшафтом
Speed := V_Mult(n, Step);
EPos := Pos;
while true do  // смертельный номер!!! 8)
 begin
 EPos := V_Add(EPos, Speed);
 with EPos do // страховка ;)
  if (X < 0) or (X >= (W - 1) * MAP_SCALE) or
     (Z < 0) or (Z >= (H - 1) * MAP_SCALE) or
     (Y < 0) or (Y >= 16 * MAP_SCALE) then
   break;
 p := EPos;
 Map.Collision(p);
 if (p.Y >= EPos.Y) and (V_Distq(EPos, Pos) < dist) then
  begin
  sc := nil;
  // Корректируем EPos
  with Plane[Collision(p)] do
   begin
   // т.к. для проверки попадания в ландшафт использую трейсинг пули,
   // то из-за больших значений Step может быть погрешнойсть в определении попадания
   // Здесь я нахожу точку пересечения прямой с плоскостью
   p    := V_Sub(EPos, Pos);
   r    := V_Dot(Normal, p);
   if r <> 0 then
    EPos := V_Add(Pos, V_Mult(p, V_Dot(Normal, V_Sub(V, Pos))/r));
   end;
  Particle_Add(TP_Trace.Create(Pos, EPos));
  snd_Play(snd__ric, false, EPos);
  break;
  end;
 end;

if sc <> nil then
 begin
 dec(sc^.Health);
 if not noblood then
  for i := 1 to BLOOD_COUNT do
   Particle_Add(TP_Blood.Create(sc^.Pos));
 if sc^.Health <= 0 then
  begin
  Result := true;
  if not nogibs then
   for i := 0 to spider_xmd.Meshes - 1 do
    Particle_Add(TP_Gibs.Create(sc^.Pos, sc^.Angle, spider_xmd.Mesh[i]));
  snd_Play(snd__spider_die, false, sc^.Pos);
  Spider_del(sc);
  end;
 end;
end;

procedure TMap.Update;
var
 s : PSpider;
 z : PZenit;
begin
// обновление пауков
s := Spider;
while s <> nil do
 begin
 s^.Update;
 s := s^.Next;
 end;
// обновление стационарок
if Player.Health > 0 then
 begin
 z := Zenit;
 while z <> nil do
  begin
  z^.Update;
  if z^.DieTimer <= 0 then
   z := Zenit_del(z)
  else
   z := z^.Next;
  end;
 end;
end;

procedure TMap.Render;
var
 s : PSpider;
 z : PZenit;
 i : integer;
begin
vis_spiders := 0;
glColor3f(1, 1, 1);

// Рисуем небеси
RenderSkyBox;
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);

///// Отрисовка ландшафта /////
glActiveTextureARB(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, Tex);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
if detail then // DetailTexture
 begin
 glActiveTextureARB(GL_TEXTURE1);
 glEnable(GL_TEXTURE_2D);
 glBindTexture(GL_TEXTURE_2D, DetailTex);
 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 end;

// у нас крутая видяха (98 фпс)
if @glClientActiveTextureARB <> nil then
 begin
 // Detail Texture
 if detail then
  begin
  glClientActiveTextureARB(GL_TEXTURE1);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glTexCoordPointer(2, GL_FLOAT, 0, @DetCoord[0]);
  end;

 // Main Texture
 glEnableClientState(GL_VERTEX_ARRAY);
 glClientActiveTextureARB(GL_TEXTURE0);
 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 glTexCoordPointer(2, GL_FLOAT, 0, @TexCoord[0]);

 glVertexPointer(3, GL_FLOAT, 0, @Vertex[0]);
 // Собсна, отрисовка ландшафта
 glDrawElements(GL_TRIANGLES, Faces*3, GL_UNSIGNED_SHORT, @Face[0]);
 if detail then
  begin
  glClientActiveTextureARB(GL_TEXTURE1);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  end;
 glClientActiveTextureARB(GL_TEXTURE0);
 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 end
else // опана! дерьмо подсунули! тогда через жопу... (74 фпс)
 begin
 glBegin(GL_TRIANGLES);
 for i := 0 to Faces - 1 do
  begin
  glMultiTexCoord2fvARB(GL_TEXTURE0, @TexCoord[Face[i][0]]);
  glMultiTexCoord2fvARB(GL_TEXTURE1, @DetCoord[Face[i][0]]);
  glVertex3fv(@Vertex[Face[i][0]]);
  glMultiTexCoord2fvARB(GL_TEXTURE0, @TexCoord[Face[i][1]]);
  glMultiTexCoord2fvARB(GL_TEXTURE1, @DetCoord[Face[i][1]]);
  glVertex3fv(@Vertex[Face[i][1]]);
  glMultiTexCoord2fvARB(GL_TEXTURE0, @TexCoord[Face[i][2]]);
  glMultiTexCoord2fvARB(GL_TEXTURE1, @DetCoord[Face[i][2]]);
  glVertex3fv(@Vertex[Face[i][2]]);
  end;
 glEnd;
 end;
  
if detail then
 begin
 glActiveTextureARB(GL_TEXTURE1);
 glBindTexture(GL_TEXTURE_2D, 0);
 glDisable(GL_TEXTURE_2D);
 end;
glActiveTextureARB(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);


// Т.к. паучков мягко говоря - ДОХРЕНА!
glTex_Disable;
glCullFace(GL_FRONT);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);

glLight_Pos(GL_LIGHT0, Vector(100, 100, 100));

glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glPushMatrix;
// Рисуем всех паучков
s := spider;
while s <> nil do
 begin
 s^.Render;
 s := s^.Next;
 end;
glPopMatrix; 
// Отрубаем буферы
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);

glDisable(GL_CULL_FACE);

glTex_Enable(EnvTex);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glPushMatrix;
z := Zenit;
while z <> nil do
 begin
 z^.Render;
 z := z^.Next;
 end;
glPopMatrix;
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);

glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);

glDisable(GL_LIGHTING);
glBlend(1);
end;

procedure TMap.RenderSkyBox;
begin
glDisable(GL_DEPTH_TEST);
glColor4f(1, 1, 1, 1);
glPushMatrix;
with Camera.Pos do
 glTranslatef(X, Y, Z);
glTex_Enable(Sky[0]); // right
 glBegin(GL_QUADS);
  glTexCoord2f(0, 1); glVertex3f( 1, -1, -1);
  glTexCoord2f(0, 0); glVertex3f( 1,  1, -1);
  glTexCoord2f(1, 0); glVertex3f( 1,  1,  1);
  glTexCoord2f(1, 1); glVertex3f( 1, -1,  1);
 glEnd;
glTex_Enable(Sky[1]); // front
 glBegin(GL_QUADS);
  glTexCoord2f(0, 1); glVertex3f(-1, -1, -1);
  glTexCoord2f(0, 0); glVertex3f(-1,  1, -1);
  glTexCoord2f(1, 0); glVertex3f( 1,  1, -1);
  glTexCoord2f(1, 1); glVertex3f( 1, -1, -1);
 glEnd;
glTex_Enable(Sky[2]); // back
 glBegin(GL_QUADS);
  glTexCoord2f(0, 1); glVertex3f( 1, -1,  1);
  glTexCoord2f(0, 0); glVertex3f( 1,  1,  1);
  glTexCoord2f(1, 0); glVertex3f(-1,  1,  1);
  glTexCoord2f(1, 1); glVertex3f(-1, -1,  1);
 glEnd;
glTex_Enable(Sky[3]); // left
 glBegin(GL_QUADS);
  glTexCoord2f(0, 1); glVertex3f(-1, -1,  1);
  glTexCoord2f(0, 0); glVertex3f(-1,  1,  1);
  glTexCoord2f(1, 0); glVertex3f(-1,  1, -1);
  glTexCoord2f(1, 1); glVertex3f(-1, -1, -1);
 glEnd;
glTex_Enable(Sky[4]); // top
 glBegin(GL_QUADS);
  glTexCoord2f(1, 1); glVertex3f(-1,  1,  1);
  glTexCoord2f(0, 1); glVertex3f( 1,  1,  1);
  glTexCoord2f(0, 0); glVertex3f( 1,  1, -1);
  glTexCoord2f(1, 0); glVertex3f(-1,  1, -1);
 glEnd;
glPopMatrix;

glEnable(GL_DEPTH_TEST);
end;

end.
