(******************************************************************************
 *          This unit is part of the Rantanplan Graphics Library              *
 *                            http://rgl.uw.hu                                *
 *                                                                            *
 * author:      Girhiny Róbert                                                *
 * web:         http://robcaa.uw.hu                                           *
 * mail:        robcaa@gmail.com                                              *
 *                                                                            *
 * decription:  four texture on the terrain                                   *
 *                                                                            *
 ******************************************************************************)
unit main;

interface

uses Forms,Windows,Dialogs,SysUtils,Graphics,
     opengl,dglOpengl,
     Shader,Camera,Textures;


procedure BeforeStart;
procedure Render;
procedure BeforeExit;
procedure UpdateInput;


type TVertex2f  = record
 s,t : GLFloat;
end;
type TVertex3f  = record
 x,y,z : GLFloat;
end;
type TVertex3i  = record
 x,y,z : GLint;
end;
type TVector3f  = TVertex3f;
type TVertexArray  = array of array of TVertex3f;
type TTexCoordArray  = array of array of TVertex2f;
type THeightMap    = array of array of byte;

type
  PKeys = ^TKeys;
  TKeys = record                       // Structure For Keyboard Stuff
   keyDown: array[0..255] of Boolean;  // Holds TRUE / FALSE For Each Key
   keyUp: array[0..255] of Boolean;
  end;
type
  PApplication = ^TApplication;
  TApplication = record                // Contains Information Vital To Applications
    hInstance: HINST;                  // Application Instance
    icon:      TIcon;                  // Application Icon
    title:     WideString;
    className: PAnsiChar;             // Application ClassName
  end;

  PGL_WindowInit = ^TGL_WindowInit;
  TGL_WindowInit = record              // Window Creation Info
    application:  PApplication;        // Application Structure
    title:        PAnsiChar;           // Window Title
    width:        Integer;             // Width
    height:       Integer;             // Height
    bitsPerPixel: Integer;             // Bits Per Pixel
    isFullScreen: Boolean;             // FullScreen?
  end;

  PGL_Window = ^TGL_Window;
  TGL_Window = record                  // Contains Information Vital To A Window
    keys:          PKeys;              // Key Structure
    h_Wnd:         HWND;               // Window Handle
    h_DC:          HDC;                // Device Context
    h_RC:          HGLRC;              // Rendering Context
    init:          TGL_WindowInit;     // Window Init
    isVisible:     Boolean;            // Window Visible?
    lastTickCount: DWORD;              // Tick Counter
  end;
var g_keys: PKeys;
    g_window: PGL_Window;
    g_isProgramLooping: Boolean;
    APPLICATION_WIDTH,APPLICATION_Height:integer;
    TimeCorrection    : DWORD;



//##############################################################################
implementation


const
  speed = 0.2;


var
    shader : GLhandleARB;
    heightMap       : THeightMap;
    TerrainVertices : TVertexArray;
    TerrainNormals  : TVertexArray;
    TerrainColors   : TVertexArray;
    TerrainTexCoord0 : TTexCoordArray;

    heightMult    : real = 12;
    size          : integer = 16000;
    quality       : integer = 200;

    texture   : GLUint; // TEXTURES
    stexture  : GLUint; // SHADER TEXTURE

    mask    : GLUint; // TEXTURES
    smask   : GLUint; // SHADER TEXTURE

    tex1,tex2,tex3,tex4   : GLUint; // TEXTURES
    stex1,stex2,stex3,stex4  : GLUint; // SHADER TEXTURE













function Vertex3f(x,y,z:glFloat) : TVertex3f;
begin
 result.x := x;
 result.y := y;
 result.z := z;
end;





function makeVector(Vertex1,Vertex2 : TVertex3f) : TVector3f; // ket vertexbol vektort keszit
begin
 Result.X := Vertex2.x - Vertex1.x;
 Result.Y := Vertex2.y - Vertex1.y;
 Result.Z := Vertex2.z - Vertex1.z;
end;

function VectorAdd(const Vector1, Vector2:TVector3f):TVector3f;
begin
  Result.X := Vector1.X + Vector2.X;
  Result.Y := Vector1.Y + Vector2.Y;
  Result.Z := Vector1.Z + Vector2.Z;
end;

function VectorMultiply(const Vector : TVector3f; Value:Single):TVector3f;
begin
 Result.x := Vector.x * Value;
 Result.y := Vector.y * Value;
 Result.z := Vector.z * Value;
end;

function vectorLength(const Vector:TVector3f):Single; // egy vektor meretet adja vissza
begin
 Result := Sqrt((Vector.X * Vector.X) + (Vector.Y * Vector.Y) + (Vector.Z * Vector.Z));
end;

function VectorCrossProduct(const Vector1, Vector2:TVector3f):TVector3f; // a vektorok normalvektorat adja
begin
 Result.X := (Vector1.Y * Vector2.Z) - (Vector1.Z * Vector2.Y);
 Result.Y := (Vector1.Z * Vector2.X) - (Vector1.X * Vector2.Z);
 Result.Z := (Vector1.X * Vector2.Y) - (Vector1.Y * Vector2.X);
end;

procedure normalizeVector(var Vector:TVector3f); // normalizalja a vektort
 var length : GLFloat;
begin
 length := vectorLength(vector);
 Vector.X := Vector.x / length;
 Vector.Y := Vector.y / length;
 Vector.Z := Vector.z / length;
end;



procedure smoothNormalVectors;
 var y,x,n:integer;
begin
 if high(TerrainNormals)<0 then exit;
 n := size div quality;

 for y:=1 to n-2 do
   for x:=1 to n-2 do begin
    TerrainNormals[x][y].x := (TerrainNormals[x][y].x+ TerrainNormals[x+1][y].x+ TerrainNormals[x-1][y].x+ TerrainNormals[x][y+1].x+ TerrainNormals[x][y-1].x +
                               TerrainNormals[x-1][y-1].x+ TerrainNormals[x-1][y+1].x+ TerrainNormals[x+1][y+1].x+ TerrainNormals[x+1][y-1].x) / 9;
    TerrainNormals[x][y].y := (TerrainNormals[x][y].y+ TerrainNormals[x+1][y].y+ TerrainNormals[x-1][y].y+ TerrainNormals[x][y+1].y+ TerrainNormals[x][y-1].y +
                               TerrainNormals[x-1][y-1].y+ TerrainNormals[x-1][y+1].y+ TerrainNormals[x+1][y+1].y+ TerrainNormals[x+1][y-1].y) / 9;
    TerrainNormals[x][y].z := (TerrainNormals[x][y].z+ TerrainNormals[x+1][y].z+ TerrainNormals[x-1][y].z+ TerrainNormals[x][y+1].z+ TerrainNormals[x][y-1].z +
                               TerrainNormals[x-1][y-1].z+ TerrainNormals[x-1][y+1].z+ TerrainNormals[x+1][y+1].z+ TerrainNormals[x+1][y-1].z) / 9;
   end;
end;



//==============================================================================
function validHeightmapCoords(x,z: real) : boolean;
begin
 if (x>0)and(z>0)and(x<high(heightMap))and(z<high(heightMap[0])) then
  result := true
 else
  result := false;
end;

function  getHeight(x,z: real; height:real) : real;
 var ix,iz : integer;
     fx,fz:double;
     topLeft,topRight,bottomLeft,bottomRight,percentX,percentZ,omdx,omdz : real;
begin
 if high(heightmap)<0 then exit;
 result:=0;

  //BILINEAR
  fx := x*high(heightmap)/size;
  fz := z*high(heightmap[0])/size;
  ix := round(fx);
  iz := round(fz);
  if not (validHeightmapCoords(ix,iz)and
          validHeightmapCoords(ix+1,iz)and
          validHeightmapCoords(ix,iz+1)and
          validHeightmapCoords(ix+1,iz+1)) then exit;
  topLeft     := heightMap[ix,  iz]  *height;
  topRight    := heightMap[ix+1,iz]  *height;
  bottomLeft  := heightMap[ix,  iz+1]*height;
  bottomRight := heightMap[ix+1,iz+1]*height;
  percentX    := fx - (ix);
  percentZ    := fz - (iz);
  omdx        := 1 - percentX;
  omdz        := 1 - percentZ;
  result := omdx*omdz         *topLeft +
            omdx*percentZ     *bottomLeft +
            percentX*omdz     *topRight +
            percentX*percentZ *bottomRight;
end;

procedure CheckCameraTerrainCollision(m_radius:real);
 var h,v:real;
begin
  h:=getHeight(cam.Position.x,cam.Position.z,heightMult);
  if cam.Position.y-m_radius<h then begin
   v:= cam.Position.y-cam.View.y;
   cam.Position.y := h+m_radius;
   cam.View.y     := h+m_radius-v;
  end;
end;



//==============================================================================
procedure drawTerrain;
 var n,y,x:integer;
begin
 if high(TerrainVertices)<0 then exit;
 n := size div quality;


 //glEnable (GL_LIGHTING);
 glDisable (GL_LIGHTING);


 for x:=0 to n-2 do begin
  glBegin(GL_TRIANGLE_STRIP);
  for y:=0 to n-2 do begin


   glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, @TerrainTexCoord0[x,y]);
   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
   glColor3fv(@TerrainColors[x,y]);
   glNormal3fv(@TerrainNormals[x,y]);
   glVertex3fv(@TerrainVertices[x,y]);

   glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, @TerrainTexCoord0[x+1,y]);
   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
   glColor3fv(@TerrainColors[x+1,y]);
   glNormal3fv(@TerrainNormals[x+1,y]);
   glVertex3fv(@TerrainVertices[x+1,y]);

   glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, @TerrainTexCoord0[x,y+1]);
   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
   glColor3fv(@TerrainColors[x,y+1]);
   glNormal3fv(@TerrainNormals[x,y+1]);
   glVertex3fv(@TerrainVertices[x,y+1]);

   glMultiTexCoord2fvARB(GL_TEXTURE0_ARB, @TerrainTexCoord0[x+1,y+1]);
   glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
   glColor3fv(@TerrainColors[x+1,y+1]);
   glNormal3fv(@TerrainNormals[x+1,y+1]);
   glVertex3fv(@TerrainVertices[x+1,y+1]);
  end;
  glEnd;
 end;
end;


procedure calcTerrainCoords(heightMap:THeightMap; size,quality:integer; height :real);
var
  x,z,i,n,xi,yi,ti   : integer;
  dx,dy,xx,yy     : double;
  v1,v2:TVertex3f;
Begin
 if high(heightMap)<0 then exit;
 n := size div quality; 				  // meghatározzuk mennyi vertex lesz

 setLength(TerrainVertices,  n);	// helyfoglalás a vertexek számára
 for i:=0 to n-1 do
  setLength(TerrainVertices[i],  n);
 setLength(TerrainNormals,  n);
 for i:=0 to n-1 do
  setLength(TerrainNormals[i],  n);
 setLength(TerrainColors,  n);
 for i:=0 to n-1 do
  setLength(TerrainColors[i],  n);
 setLength(TerrainTexCoord0,  n);
 for i:=0 to n-1 do
  setLength(TerrainTexCoord0[i],  n);


 dx:= high(heightMap) / n; 		  // egy lépés a heightmapon x irányba
 dy:= high(heightMap[0]) / n; 	// egy lépés a heightmapon y irányba
 x:=0;  				// x,z az aktuális vertex 						   világkoordinátái
 xx:=0;				// xx,yy a magasságtérkép koordinátái
 xi:=0;       // xi,yi a  matrixbeli pozicio
 while xi < n do begin
  z:=0;
  yy:=0;
  yi:=0;
  while yi < n do begin
   x := round((size)/n *(xi+1));
   z := round((size)/n *(yi+1));

   //TEXTURE COORD'S
   TerrainTexCoord0[xi][yi].s := (size)/n *(xi) / size;
   TerrainTexCoord0[xi][yi].t := -(size)/n *(yi) / size;

   //COLOR
   TerrainColors[xi][yi].x:=heightMap[round(xx),round(yy)]/255;
   TerrainColors[xi][yi].y:=heightMap[round(xx),round(yy)]/255;
   TerrainColors[xi][yi].z:=heightMap[round(xx),round(yy)]/255;

   //NORMAL
   v1 := makeVector( vertex3f(x,heightMap[round(xx),round(yy)]*height,z), vertex3f(x+(size)/n,heightMap[round(xx+dx),round(yy)]*height,z));
   v2 := makeVector( vertex3f(x,heightMap[round(xx),round(yy)]*height,z), vertex3f(x,heightMap[round(xx),round(yy+dy)]*height,z+(size)/n));
   TerrainNormals[xi][yi] := VectorCrossProduct(v1,v2);
   TerrainNormals[xi][yi] := VectorMultiply(TerrainNormals[xi,yi],-1);
   normalizeVector(TerrainNormals[xi][yi]);

   //VERTEX
   TerrainVertices[xi][yi].x:=x;
   TerrainVertices[xi][yi].y:=heightMap[round(xx),round(yy)]*height;
   TerrainVertices[xi][yi].z:=z;

   yy:=yy+dy;
   inc(yi);
  end;
  xx:=xx+dx;
  inc(xi);
 end;
End;



procedure loadHeightMap;
var bt:TBitmap;
    y,x:integer;
begin
 bt := Tbitmap.Create;
 bt.LoadFromFile('heightmap.bmp');

 setLength(heightMap,bt.Width);
 for x:=0 to bt.Width-1 do
  setLength(heightMap[x],bt.Height);

 for y:=0 to bt.Height-1 do
  for x:=0 to bt.Width-1 do
   heightMap[x,y] := getRValue(bt.Canvas.Pixels[x,y]);
end;


procedure DrawAxes(origo:TVertex3f; length:GLFloat);
begin
glPushMatrix();
glDisable(GL_LIGHTING);
glLineWidth(2);

glBegin(GL_LINES);
 glColor3f(1,0,0); // X axis is red.
  glVertex3fv (@origo);
  glVertex3f  (origo.x+length,origo.y,origo.z);
 glColor3f(0,1,0); // Y axis is green.
  glVertex3fv (@origo);
  glVertex3f  (origo.x,origo.y+length,origo.z);
 glColor3f(0,0,1); // z axis is blue.
  glVertex3fv (@origo);
  glVertex3f  (origo.x,origo.y,origo.z+length);
glEnd();
glLineWidth(1);

glPopMatrix();
end;














//##############################################################################
procedure BeforeStart;
begin
 glClearColor(0, 0, 0, 0);
 glEnable(GL_TEXTURE_2D);
 glCullFace(GL_FRONT);

 glGetIntegerv(GL_VIEWPORT, @vPort);
 SetCursorPos((vPort[2] SHR 1)+app_left, (vPort[3] SHR 1)+app_top);
 cam.GoToCamera(0.81,2690.39,0.93, 1.49,2690.23,1.54);

 Shader := LinkPrograms( [
      LoadShaderFromFile('shader.vert', GL_VERTEX_SHADER_ARB),
      LoadShaderFromFile('shader.frag', GL_FRAGMENT_SHADER_ARB)
    ] );

 loadHeightMap;
 calcTerrainCoords(heightMap, size,quality,heightMult);
 smoothNormalVectors;


 LoadTexture('texture.bmp',texture,false);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 stexture := glGetUniformLocationARB( Shader, 'texture' );

 LoadTexture('mask.tga',mask,false);
 smask := glGetUniformLocationARB( Shader, 'mask' );

 LoadTexture('tex1.bmp',tex1,false);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 stex1 := glGetUniformLocationARB( Shader, 'tex1' );

 LoadTexture('tex2.bmp',tex2,false);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 stex2 := glGetUniformLocationARB( Shader, 'tex2' );

 LoadTexture('tex3.bmp',tex3,false);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 stex3 := glGetUniformLocationARB( Shader, 'tex3' );

 LoadTexture('tex4.bmp',tex4,false);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);
 stex4 := glGetUniformLocationARB( Shader, 'tex4' );
end;





//##############################################################################
procedure BeforeExit;
begin
end;






//##############################################################################
procedure UpdateInput;
begin
  if (g_keys.keyDown[VK_UP])or(g_keys.keyDown[87]) then
   Cam.MoveCamera(TimeCorrection/speed);
  if (g_keys.keyDown[VK_DOWN])or(g_keys.keyDown[83])  then
   Cam.MoveCamera(-abs(TimeCorrection/speed));
  if (g_keys.keyDown[VK_RIGHT])or(g_keys.keyDown[68]) then
   Cam.StrafeCamera(-abs(TimeCorrection/speed));
  if (g_keys.keyDown[VK_LEFT])or(g_keys.keyDown[65])  then
   Cam.StrafeCamera(TimeCorrection/speed);
  if (g_keys.keyDown[32])  then begin
    Cam.View.Y := Cam.View.Y + TimeCorrection/speed;
    Cam.Position.Y := Cam.Position.Y + TimeCorrection/speed;
    end;
  cam.MoveCameraByMouse;


  if g_keys.keyDown[VK_ESCAPE] then  begin
   PostMessage(g_window^.h_Wnd, $0012, 0, 0);
   g_isProgramLooping := FALSE;
  end;
end;







//##############################################################################
procedure Render;
begin
  glClearColor(0.8, 1, 1, 1);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;


  cam.SetViewport;
  CheckCameraTerrainCollision(600);
  DrawAxes(vertex3f(0,0,0),size);


  glUseProgramObjectARB( Shader );
  glUniform1iARB( stexture, 0);
  glUniform1iARB( smask, 1);
  glUniform1iARB( stex1, 2);
  glUniform1iARB( stex2, 3);
  glUniform1iARB( stex3, 4);
  glUniform1iARB( stex4, 5);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, texture);

  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, mask);

  glActiveTexture(GL_TEXTURE2);
  glBindTexture(GL_TEXTURE_2D, tex1);
  glActiveTexture(GL_TEXTURE3);
  glBindTexture(GL_TEXTURE_2D, tex2);
  glActiveTexture(GL_TEXTURE4);
  glBindTexture(GL_TEXTURE_2D, tex3);
  glActiveTexture(GL_TEXTURE5);
  glBindTexture(GL_TEXTURE_2D, tex4);

  drawTerrain;
  glUseProgramObjectARB( 0 );

  glFlush();
end;
end.



