unit l_camera;

interface

uses
 Windows, OpenGL,
 l_gl, l_math, l_player, l_input, l_sound;

type
 TCamera = object
  protected
   Angle   : TVector;
   FOV     : single;  // угол обзора
   tFOV    : single;
  public
   Pos    : TVector;
   m      : TMatrix4;
   proj   : TMatrix4;
   Target : PPLayer;
   Plane  : array [0..3, 0..3] of single;
   procedure Init;
   procedure Update;
   procedure SetRender;
   procedure SetProj;
   procedure BuildFrustum;
   function inFrustum(var p: TVector; Radius: single): boolean;
 end;

var
 culling : boolean = true; // отсечение

implementation

procedure TCamera.Init;
begin
FOV  := 90;
tFOV := 90;
end;

procedure TCamera.Update;
var
 A, B, C, D, E, F : single;
 cx, cy, cz       : TVector;
begin
if KeyDown(ord('Q')) then
 tFOV := 20
else
 tFOV := 90;
FOV := FOV + (tFOV - FOV)/10;
if Target <> nil then
 begin
 Angle := Target^.Angle;
 Pos := V_Add(Target^.Pos, Vector(0, 2, 0));
 with Target^.Angle do
  begin
  A := cos(X);
  B := sin(X);
  C := sin(Y + pi);
  D := cos(Y + pi);
  E := cos(Z);
  F := sin(Z);
  end;
{
// ZXY
[  E  F  0 ]   [ 1  0  0 ]   [ C  0 -D ]   [ CE+BDF AF BCF-ED ]
[ -F  E  0 ] X [ 0  A  B ] X [ 0  1  0 ] = [ BDE-CF AE DF+BCE ]
[  0  0  1 ]   [ 0 -B  A ]   [ D  0  C ]   [ AD     -B AC     ]
}

 cx := Vector(C*E+B*D*F, A*F, B*C*F-E*D);
 cy := Vector(B*D*E-C*F, A*E, D*F+B*C*E);
 cz := Vector(A*D,        -B,       A*C);

 m[0][0] := cx.X; m[0][1] := cy.X; m[0][2] := cz.X; m[0][3] := 0;
 m[1][0] := cx.Y; m[1][1] := cy.Y; m[1][2] := cz.Y; m[1][3] := 0;
 m[2][0] := cx.Z; m[2][1] := cy.Z; m[2][2] := cz.Z; m[2][3] := 0;
 m[3][0] := -V_Dot(cx, Pos);
 m[3][1] := -V_Dot(cy, Pos);
 m[3][2] := -V_Dot(cz, Pos);
 m[3][3] := 1;
 BuildFrustum;
 // звук (ориентация и позиция)
 snd_SetOrientation(V_Mult(cz, -1), V_Mult(cy, -1));
 snd_SetGlobalPos(Pos);
 end;
end;

procedure UpdateSound;
begin

end;

procedure TCamera.SetProj;
const
 zNear = 0.1;
 zFar  = 512;
begin
glSetProj(FOV, zNear, zFar);
glGetFloatv(GL_PROJECTION_MATRIX, @proj);
glViewPort(0, 0, glWidth, glHeight);
end;

procedure TCamera.SetRender;
begin
glLoadMatrixf(@m);
end;

procedure TCamera.BuildFrustum;

 procedure Normalize;
 var
  len : single;
  i   : integer;
 begin
 for i := 0 to 3 do
  begin
  len := 1/sqrt(sqr(Plane[i][0]) + sqr(Plane[i][1]) + sqr(Plane[i][2]));
  Plane[i][0] := Plane[i][0]*len;
  Plane[i][1] := Plane[i][1]*len;
  Plane[i][2] := Plane[i][2]*len;
  Plane[i][3] := Plane[i][3]*len; 
  end;
 end;

var
 f : TMatrix4;
begin
f := M_Mult(m, proj);
// Расчёт плоскостей фрустума //
// Правая
Plane[0][0] := f[0][3] - f[0][0];
Plane[0][1] := f[1][3] - f[1][0];
Plane[0][2] := f[2][3] - f[2][0];
Plane[0][3] := f[3][3] - f[3][0];
// Левая
Plane[1][0] := f[0][3] + f[0][0];
Plane[1][1] := f[1][3] + f[1][0];
Plane[1][2] := f[2][3] + f[2][0];
Plane[1][3] := f[3][3] + f[3][0];
// Верхняя
Plane[2][0] := f[0][3] - f[0][1];
Plane[2][1] := f[1][3] - f[1][1];
Plane[2][2] := f[2][3] - f[2][1];
Plane[2][3] := f[3][3] - f[3][1];
// Нижняя
Plane[3][0] := f[0][3] + f[0][1];
Plane[3][1] := f[1][3] + f[1][1];
Plane[3][2] := f[2][3] + f[2][1];
Plane[3][3] := f[3][3] + f[3][1];
// Нормализация нормалей к плоскостям
Normalize;
end;

function TCamera.inFrustum(var p: TVector; Radius: single): boolean;
var
 i : integer;
begin
Result := false;
for i := 0 to 3 do
 if Plane[i][0] * p.X +
    Plane[i][1] * p.Y +
    Plane[i][2] * p.Z +
    Plane[i][3] < -Radius then
  Exit;
Result := true;
end;

end.

