unit GLScene;

interface

uses Windows, Classes, OpenGL, SysUtils, Controller,
  MiscRoutines, U3d, StdFont, Setup;

const M_PI        = 3.14159265358979324;

// =======================================================================
// Режим сцены
// =======================================================================
type
  TGameMode =
(
  modeNotInitialized,   // Контекст не создан
  modeGame,             // Идет непрерывное обновление
  modePaused,           // Обновление - только в ответ на WM_PAINT
  modeFocusLost,        // То же, но с возможностью восстановления сост. 
  modeMenu              // То же, что и 'modePaused'
);

// =======================================================================
// Платформанно - независимый код
// =======================================================================
type
  TOpenGLScene = class
  private
    ClientRect    : TRect;
    procedure     StoreClientRect(const Rc:TRect);
  public
    FPS           : integer;
    Mode          : TGameMode;
    Surface       : TGLSurface;
    SurfaceCreated: Boolean;
    KeyController : TController;
    constructor   Create;
    destructor    Destroy;override;
    procedure     Initialize;
    procedure     Finalize;
    procedure     ChangeControlMode;
    procedure     AdjustViewportOrtho(const Rc:TRect);
    procedure     AdjustViewportFrustum(const Rc:TRect);
    procedure     AdjustViewportDirect(const Rc:TRect);
    procedure     Control(PreviousTime,Time:integer);
    procedure     Draw(PreviousTime,Time:integer);
    procedure     HandleKey(var Key: Word;Shift: TShiftState);
  end;

implementation

// =======================================================================
// Create / Destroy;
// =======================================================================

constructor TOpenGLScene.Create;
begin
  KeyController:=TController.Create;
  ChangeControlMode;
  memZeroMemory(@ClientRect,sizeof(TRect));
  SurfaceCreated:=False;
end;

destructor TOpenGLScene.Destroy;
begin
  KeyController.Destroy;
  inherited;
end;

procedure TOpenGLScene.ChangeControlMode;
var
  NewMode : TControlMode;
begin
  if(AppSEtup.GLFlags.UseVR) then
    NewMode:=cmVirtualReality
  else
    NewMode:=cmSceneControl;
  if(KeyController.ControlMode<>NewMode) then
    KeyController.ControlMode := NewMode;
  KeyController.CalcAccel;
end;

// =======================================================================
// AdjustViewportXXX
// =======================================================================

procedure TOpenGLScene.StoreClientRect;
begin
  ClientRect.Left:=Rc.Left;
  ClientRect.Top:=Rc.Top;
  ClientRect.Right:=Rc.Right;
  ClientRect.Bottom:=Rc.Bottom;
end;

procedure TOpenGLScene.AdjustViewportOrtho;
var
  asp_r : single;
begin
  if((Rc.Bottom>Rc.Top)and(Rc.Right>Rc.Left)) then
  begin
    StoreClientRect(Rc);
    Opengl.glViewport(Rc.Left,Rc.Top,Rc.Right,Rc.Bottom);
    Opengl.glMatrixMode(GL_PROJECTION);
    Opengl.glLoadIdentity();
    asp_r:=(Rc.Bottom-Rc.Top)/(Rc.Right-Rc.Left);
    Opengl.glOrtho(-1,1,asp_r,-asp_r,1,32);
    Opengl.glMatrixMode(GL_MODELVIEW);
    Opengl.glLoadIdentity();
  end;
end;

procedure TOpenGLScene.AdjustViewportDirect;
begin
  if((Rc.Bottom>Rc.Top)and(Rc.Right>Rc.Left)) then
  begin
    with Rc do
    begin
      StoreClientRect(Rc);
      OpenGL.glViewport(Left,Top,Right,Bottom);
      OpenGL.glMatrixMode(GL_PROJECTION);
      OpenGL.glLoadIdentity();
      OpenGL.glOrtho(0,Right-Left-1,Bottom-Top-1,0,-10,10);
    end;
  end;
end;

procedure TOpenGLScene.AdjustViewportFrustum;
var
  asp_r : single;
begin
  if((Rc.Bottom>Rc.Top)and(Rc.Right>Rc.Left)) then
  begin
    StoreClientRect(Rc);
    Opengl.glViewport(Rc.Left,Rc.Top,Rc.Right,Rc.Bottom);
    Opengl.glMatrixMode(GL_PROJECTION);
    Opengl.glLoadIdentity();
    asp_r:=(Rc.Bottom-Rc.Top)/(Rc.Right-Rc.Left);
    Opengl.glFrustum(-1,1,-asp_r,asp_r,1,32);
    Opengl.glMatrixMode(GL_MODELVIEW);
    Opengl.glLoadIdentity();
  end;
end;

procedure TOpenGLScene.Control;
var
  dt : u3dval;
begin
  dt:=Time-PreviousTime;
  KeyController.KeyboardControl(dt);
end;

// =======================================================================
// Вспомогательные процедуры рисования
// =======================================================================

procedure DrawQuadPatch;  // y = 0; z,x = -0.5 - 0.5
var
  i : u3dval;
begin
  i:=-0.5;
  glBegin(GL_LINES);
  while(i<0.55) do
  begin
    glVertex3f(i,0,-0.5);
    glVertex3f(i,0,0.5);
    i:=i+0.05;
  end;
  i:=-0.5;
  while(i<0.55) do
  begin
    glVertex3f(-0.5,0,i);
    glVertex3f(0.5,0,i);
    i:=i+0.05;
  end;
  glEnd;
end;

const
  ParaFaces : array[0..5,0..3] of integer =
(
  (0, 1, 2, 3),
  (2, 1, 6, 5),
  (3, 2, 5, 4),
  (0, 3, 4, 7),
  (1, 0, 7, 6),
  (4, 5, 6, 7)
);

procedure DrawWireParrallelepiped(var Pd : array of vector_t);
var
  f,v : integer;
begin
  for f:=0 to 5 do
  begin
    glBegin(GL_LINE_STRIP);
    for v:=0 to 3 do
    begin
      glVertex3fv(@(Pd[ParaFaces[f,v]]));
    end;
    glEnd;
  end;
end;

procedure BuildParalelepiped(var Pd : array of vector_t;var min,max:vector_t);
begin
  vtSet(Pd[0], min.x, max.y, min.z );
  vtSet(Pd[1], min.x, max.y, max.z );
  vtSet(Pd[2], max.x, max.y, max.z );
  vtSet(Pd[3], max.x, max.y, min.z );
  vtSet(Pd[4], max.x, min.y, min.z );
  vtSet(Pd[5], max.x, min.y, max.z );
  vtSet(Pd[6], min.x, min.y, max.z );
  vtSet(Pd[7], min.x, min.y, min.z );
end;

procedure DrawBoundingBox(var Min,Max:vector_t);
var
  Parralelepiped : array[0..7] of vector_t;
  exdelta, m, x : vector_t;
begin
  vtSub(exdelta,Max,Min);
  vtMul(exdelta,exdelta,0.01);
  vtAdd(x,Max,exdelta);
  vtSub(m,Min,exdelta);
  glLineWidth(1.7);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  BuildParalelepiped(Parralelepiped,m,x);
  DrawWireParrallelepiped(Parralelepiped);
  glDisable(GL_BLEND);
  glDisable(GL_LINE_SMOOTH);
end;

procedure DrawBasis; // Фиолетовый, Белый, Голубой
var
  rgb: rgb_t;
begin
  glLineWidth(2.0);
  glBegin(GL_LINES);
    Color2RGB(AppSetup.GLFlags.XWallColor,rgb);
    glColor3fv(PGLfloat(@rgb));
    glVertex3f(0,0,0);
    glVertex3f(1,0,0);
    Color2RGB(AppSetup.GLFlags.YWallColor,rgb);
    glColor3fv(PGLfloat(@rgb));
    glVertex3f(0,0,0);
    glVertex3f(0,1,0);
    Color2RGB(AppSetup.GLFlags.ZWallColor,rgb);
    glColor3fv(PGLfloat(@rgb));
    glVertex3f(0,0,0);
    glVertex3f(0,0,1);
  glEnd;
  glLineWidth(1.0);
end;

procedure TOpenGLScene.Draw;
const
  MsgPaused = 'Обновление остановлено';
var
  Bmin,Bmax : vector_t;
  rgb : rgb_t;
begin
  // =====================================================================
  // Здесь размещаются команды отрисовки сцены.
  // =====================================================================
  Color2RGB(AppSetup.GLFlags.BackColor,rgb);
  glClearColor(rgb.r,rgb.g,rgb.b,0.5);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  // =====================================================================
  // Управление
  glLoadMatrixf(PGLFloat(KeyController.GetTransform));
  // ZXY -> XYZ
  glScalef(4,4,4);
  // =====================================================================
  glEnable(GL_DEPTH_TEST);
  // =====================================================================
  // Пол и две стены
  glEnable(GL_LINE_SMOOTH);
  glLineWidth(1.4);
  glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_BLEND);
  glPushMatrix;
    Color2RGB(AppSetup.GLFlags.YWallColor,rgb);
    glColor4f(rgb.r,rgb.g,rgb.b,0.5);
    DrawQuadPatch;
    glPushMatrix;
      glTranslatef(0,0.5,-0.5);
      glRotatef(90,1,0,0);
      Color2RGB(AppSetup.GLFlags.ZWallColor,rgb);
      glColor4f(rgb.r,rgb.g,rgb.b,0.5);
      DrawQuadPatch;
    glPopMatrix;
    glTranslatef(-0.5,0.5,0);
    glRotatef(90,0,0,1);
    Color2RGB(AppSetup.GLFlags.XWallColor,rgb);
    glColor4f(rgb.r,rgb.g,rgb.b,0.5);
    DrawQuadPatch;
    // yOz
  glPopMatrix;
  glDisable(GL_BLEND);
  glDisable(GL_LINE_SMOOTH);
  // =====================================================================
  if(SurfaceCreated) then
  begin
    surfGetBBox(@Surface.Surf,Bmin,Bmax);
    Surface.Draw(AppSetup.GLFlags.BlendSurface,
      AppSetup.GLFlags.DrawLines,AppSetup.GLFlags.LineColor);
    glEnable(GL_DEPTH_TEST);
    if(AppSetup.GLFlags.DrawBasis) then
    begin
      glScalef(0.5,1,0.5);
      DrawBasis;
      glScalef(2,1,2);
    end;
    glDisable(GL_BLEND);
    glColor3f(0,1,0);
    if(AppSetup.GLFlags.DrawBBox) then
    begin
      Color2RGB(AppSetup.GLFlags.BBoxColor,rgb);
      glColor4f(rgb.r,rgb.g,rgb.b,0.6);
      DrawBoundingBox(Bmin,Bmax);
    end;
  end;
  // =====================================================================
  if(AppSetup.GLFlags.ShowFPS) then
  begin
    glLoadIdentity;
    glMatrixMode(GL_PROJECTION);
    glPushMatrix;
      AdjustViewportDirect(ClientRect);
      Color2RGB(AppSetup.GLFlags.BackColor,rgb);
      rgb.r:=1-rgb.r;
      rgb.g:=1-rgb.g;
      rgb.b:=1-rgb.b;
      glColor3fv(PGLfloat(@rgb));
      glRasterPos2d(ClientRect.Left,ClientRect.Top);
      glDisable(GL_DEPTH_TEST);
      GLPuts('FPS = '+FloatToStr(FPS));
      if(Mode=modePaused) then
      begin
        glRasterPos2d(
          (ClientRect.Right-ClientRect.Left-8*Length(MsgPaused))/2,
           ClientRect.Top+5);
        GLPuts(MsgPaused);
      end;
    glPopMatrix;
    glMatrixMode(GL_MODELVIEW);
  end;
  // =====================================================================
end;

procedure TOpenGLScene.HandleKey(var Key: Word;Shift: TShiftState);
begin
end;

{$OPTIMIZATION OFF}

procedure TOpenGLScene.Initialize;
begin
  GenerateFontLists;
end;

{$OPTIMIZATION ON}

procedure TOpenGLScene.Finalize;
begin
  DestroyFontLists;
  if(SurfaceCreated) then
  begin
    Surface.Destroy;
    SurfaceCreated:=False;
  end;
end;

end.

