unit sgOpenGL;

interface

uses
  Windows, Classes, sgConsts, Math;

const
  pi_180: extended = 3.1415926535897932384626433832795/180;
  _2pi: extended = 2 * 3.1415926535897932384626433832795;
  ZERO_EPS = 0.00000000000001;

type
  TArray4s = array[0 .. 3] of single;
  PArray4s = ^TArray4s;
  PglMatrix = ^TglMatrix;
  TglMatrix = array [0 .. 3, 0 .. 3] of single;
  PglMatrixd = ^TglMatrixd;
  TglMatrixd = array [0 .. 3, 0 .. 3] of Double;
  TglSwapMatrixes = array[boolean] of TglMatrix;
  PglSwapMatrixes = ^TglSwapMatrixes;

var
  glSwapMatrixes: TglSwapMatrixes;
  bCurentMatrix: boolean;

procedure IdentMatGL(A: PglMatrix);
procedure IdentMatGLd(A: PglMatrixd);
procedure IdentSwapMatrix;
procedure MultMatrixGL(A, B, C: PglMatrix);
procedure RotateGL(Axis: TsgAxes; Angle: Extended);
procedure FixPoint(ptIn, ptOut: PArray4s; pmat: PglMatrix);
procedure Normalize(n: PFPoint);
//procedure CalcNormal(Extent: single; Vector1, Vector2, Vector3, Normal: PFPoint);
procedure CalcNormal(P1, P2, P3, N: PFPoint);
procedure ScalePoint(p, s: PArray4s);
function ResetGLScene(const ARect: TRect; const ABox: TFRect;
  const AOrto, AUseModelViewMatrix: Boolean): TFRect;
procedure glInit;
function GLMatrixToFMatrix(const AMode: Cardinal): TFMatrix;
function GetModelViewMatrix: TglMatrixd;
function GetProjectionMatrix: TglMatrixd;

function GetCoord(const X, Y: Double; const Z: Double = 0.0): TFPoint; overload;
function GetCoord(const APoint: TFPoint): TFPoint; overload;
function GetCoord(const APoint: TPoint): TFPoint; overload;

implementation

uses
  OpenGL, sgFunction;

function sr(Angle: extended): extended;
begin
  Result := sin(pi_180*Angle);
end;

function cr(Angle: extended): extended;
begin
  Result := cos(pi_180*Angle);
end;

procedure FixPoint(ptIn, ptOut: PArray4s; pmat: PglMatrix);
begin
  ptOut^[0] := ptIn^[0] * pmat^[0, 0] + ptIn^[1] * pmat^[0, 1] + ptIn^[2] * pmat^[0, 2] + pmat^[0, 3];
  ptOut^[1] := ptIn^[0] * pmat^[1, 0] + ptIn^[1] * pmat^[1, 1] + ptIn^[2] * pmat^[1, 2] + pmat^[1, 3];
  ptOut^[2] := ptIn^[0] * pmat^[2, 0] + ptIn^[1] * pmat^[2, 1] + ptIn^[2] * pmat^[2, 2] + pmat^[2, 3];
end;

procedure ScalePoint(p, s: PArray4s);
begin
  p^[0] := p^[0] * s^[0];
  p^[1] := p^[1] * s^[1];
  p^[2] := p^[2] * s^[2];
end;

procedure IdentMatGL(A: PglMatrix);
begin
  FillChar(A^, SizeOf(A^), 0);
  A^[0,0] := 1;
  A^[1,1] := 1;
  A^[2,2] := 1;
  A^[3,3] := 1;
end;

procedure IdentMatGLd(A: PglMatrixd);
begin
  FillChar(A^, SizeOf(A^), 0);
  A^[0,0] := 1;
  A^[1,1] := 1;
  A^[2,2] := 1;
  A^[3,3] := 1;
end;

procedure IdentSwapMatrix;
begin
  IdentMatGL(@glSwapMatrixes[bCurentMatrix][0, 0]);
  IdentMatGL(@glSwapMatrixes[not bCurentMatrix][0, 0]);
end;

procedure MultMatrixGL(A, B, C: PglMatrix);
var
  I,J,K: Integer;
begin
  for I := 0 to 3 do
  begin
    for J := 0 to 3 do
    begin
      C^[I, J] := 0.0;
      for K := 0 to 2 do
        C^[I, J] := C^[I, J] + A^[I, K] * B^[K, J];
    end;
  end;
  for J := 0 to 3 do
    C^[3, J] := C^[3, J] + B^[3, J];
  C^[3, 3] := 1.0;
end;

procedure RotateGL(Axis: TsgAxes; Angle: Extended);
var
  vM: TglMatrix;

  procedure DoMat(A, B: Integer);
  var
    S, C: Extended;
  begin
    IdentMatGL(@vM[0, 0]);
    SinCos(Radian(Angle), S, C);
    if Axis = axisY then
      S := -S;
    vM[A, A] := C;
    vM[B, B] := C;
    vM[A, B] := S;
    vM[B, A] := -S;
    MultMatrixGL(@glSwapMatrixes[bCurentMatrix][0, 0], @vM[0, 0], @glSwapMatrixes[not bCurentMatrix][0, 0]);
    bCurentMatrix := not bCurentMatrix;
  end;

begin
  case Axis of
    axisX: DoMat(1, 2);
    axisY: DoMat(0, 2);
    axisZ: DoMat(0, 1);
  end;
end;

procedure Normalize(n: PFPoint);
var
  len: Double;
begin
  len := Sqrt((n^.x * n^.x) + (n^.y * n^.y) + (n^.z * n^.z));
  if len <> 0 then
  begin
    n^.x := n^.x / len;
    n^.y := n^.y / len;
    n^.z := n^.z / len;
  end;
end;

function Vector(const P1, P2, N: PFPoint): TFPoint;
begin
  N^.X := P1^.Y * P2^.Z - P1^.Z * P2^.Y;
  N^.Y := P1^.Z * P2^.X - P1^.X * P2^.Z;
  N^.Z := P1^.X * P2^.Y - P1^.Y * P2^.X;
end;

procedure CalcNormal(P1, P2, P3, N: PFPoint);
var
  V1, V2: TFpoint;
begin
  V1.X := P2^.X - P1^.X;
  V1.Y := P2^.Y - P1^.Y;
  V1.Z := P2^.Z - P1^.Z;

  V2.X := P3^.X - P1^.X;
  V2.Y := P3^.Y - P1^.Y;
  V2.Z := P3^.Z - P1^.Z;

  Vector(@V1, @V2, N);
  Normalize(N);
end;

function GetModelViewMatrix: TglMatrixd;
var
  vMode: Cardinal;
begin
  glGetIntegerv(GL_MATRIX_MODE, @vMode);
  glMatrixMode(GL_MODELVIEW);
  glGetDoublev(GL_MODELVIEW_MATRIX, @Result);
  glMatrixMode(vMode);
end;

function GetProjectionMatrix: TglMatrixd;
var
  vMode: Cardinal;
begin
  glGetIntegerv(GL_MATRIX_MODE, @vMode);
  glMatrixMode(GL_PROJECTION);
  glGetDoublev(GL_PROJECTION_MATRIX, @Result);
  glMatrixMode(vMode);
end;

function GLMatrixToFMatrix(const AMode: Cardinal): TFMatrix;
var
  I, J: Integer;
  vglMd: TglMatrixd;
begin
  case AMode of
    GL_MODELVIEW: vglMd := GetModelViewMatrix;
    GL_PROJECTION: vglMd := GetProjectionMatrix;
  end;
  for I := 0 to 3 do for J := 0 to 2 do Result.M[I, J] := vglMd[I, J];
end;

{------------------------------------------------------------------}
{ GetCoord - convert client point to GL scene 3d coordinates       }
{------------------------------------------------------------------}
function GetCoord(const APoint: TFPoint): TFPoint; overload;
begin
  Result := GetCoord(APoint.X, APoint.Y, APoint.Z);
end;

function GetCoord(const X, Y, Z: Double): TFPoint; overload;
var
  R: TRect;
  vModelViewMat, vProjectionMat: TglMatrixd;
begin
  vModelViewMat := GetModelViewMatrix;
  vProjectionMat := GetProjectionMatrix;
  glGetIntegerv(GL_VIEWPORT, @R);
  if gluUnProject(X, Y, 0, @vModelViewMat[0, 0], @vProjectionMat[0, 0],
    @R.Left, Result.X, Result.Y, Result.Z) = 0 then
    Result := cnstFPointZero;
end;

function GetCoord(const APoint: TPoint): TFPoint; overload;
begin
  Result := GetCoord(MakeFPoint(APoint.X, APoint.Y));
end;

{------------------------------------------------------------------}
{  Reset the GL scene                                              }
{------------------------------------------------------------------}
function ResetGLScene(const ARect: TRect; const ABox: TFRect;
  const AOrto, AUseModelViewMatrix: Boolean): TFRect;
var
  I, J: Integer;
  vClAsp, vDrAsp, W, H, vz_min, vz_max: Double;
  vDist, vDistTransformed{, vDepth, vDepthScale}: Double;
  vCenter: TFPoint;
  vglMd: TglMatrixd;
  vM: TFMatrix;
  vBox, R: TFRect;
  vMode: Integer;
begin
  glPushMatrix;
  glGetIntegerv(GL_MATRIX_MODE, @vMode);
  try
    I := ARect.Right - ARect.Left;
    J := ARect.Bottom - ARect.Top;
    glViewport(ARect.Left, ARect.Top, I, J);    // Set the viewport for the OpenGL window
    vClAsp := I / J;

    R := ABox;
    vBox := ABox;
    vDistTransformed := 1;
    if AUseModelViewMatrix then
    begin
      glMatrixMode(GL_MODELVIEW);
      glGetDoublev(GL_MODELVIEW_MATRIX, @vglMd);
      I := glGetError;
      if I = 0 then
      begin
        for I := 0 to 3 do for J := 0 to 2 do vM.M[I, J] := vglMd[I, J];
        // calc transformred
        vBox.TopLeft := FPointXMat(ABox.TopLeft, vM);
        vBox.BottomRight := FPointXMat(ABox.BottomRight, vM);
        vDistTransformed := DistanceFPoint(vBox.TopLeft, vBox.BottomRight) / 2;
      end;
    end;

    glMatrixMode(GL_PROJECTION);        // Change Matrix Mode to Projection
    glLoadIdentity;               // Reset View


    vCenter.X := (ABox.Left + ABox.Right) / 2;
    vCenter.Y := (ABox.Bottom + ABox.Top) / 2;
    vCenter.Z := (ABox.Z1 + ABox.Z2) / 2;
    vDist := DistanceFPoint(vCenter, ABox.TopLeft);

    vz_min := Min(vBox.Z1, vBox.Z2);
    vz_max := Max(vBox.Z1, vBox.Z2);

    //vDepth := vz_max - vz_min;
    //vDepthScale := vDistTransformed / vDist;

    if AUseModelViewMatrix then
      vDist := Max(vDist, vDistTransformed);

    R.Z1 := vz_min - vDist - 1;
    R.Z2 := vz_max + vDist + 1;

    W := R.Right - R.Left;
    H := R.Top - R.Bottom;
    if H  = 0 then
      Exit;

    vDrAsp := W / H;
    if vDrAsp > 1 then
    begin
      R.Bottom := vCenter.Y - W / vClAsp / 2;
      R.Top := R.Bottom + W / vClAsp;
    end
    else
    begin
      R.Left := vCenter.X - H * vClAsp / 2;
      R.Right := R.Left + H * vClAsp;
    end;
    if AOrto then
    begin
      //glOrtho(R.Left, R.Right, R.Bottom, R.Top, -Max(Abs(R.Z1), Abs(R.Z2)), Max(Abs(R.Z1), Abs(R.Z2)));
      glOrtho(R.Left, R.Right, R.Bottom, R.Top, -Max(Abs(R.Z1), Abs(R.Z2)), Max(Abs(R.Z1), Abs(R.Z2)));
    end
    else
      gluPerspective(90, vClAsp, 0.1, Max(Abs(R.Z1), Abs(R.Z2)));  // Do the perspective calculations. Last value = max clipping depth
    Result := R;
  finally
    glMatrixMode(vMode);
    glPopMatrix;
  end;
end;

{------------------------------------------------------------------}
{  Initialise OpenGL                                               }
{------------------------------------------------------------------}
procedure glInit;
begin
  glClearColor(0.5, 0.5, 0.5, 0.0); 	   // Black Background
  glShadeModel(GL_SMOOTH);                 // Enables Smooth Color Shading
  glClearDepth(2.0);                       // Depth Buffer Setup
  glEnable(GL_DEPTH_TEST);                 // Enable Depth Buffer
  glDepthFunc(GL_LESS);		           // The Type Of Depth Test To Do
  glEnable(GL_COLOR_MATERIAL);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);   //Realy Nice perspective calculations
  glEnable(GL_LIGHTING);
  glEnable(GL_NORMALIZE);
end;

end.
