unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls, ComCtrls,
  OpenGlPanel, OpenGL;

type
  TForm1 = class(TForm)
    OpenGlPanel: TOpenGlPanel;
    Panel1: TPanel;
    TrackBar1: TTrackBar;
    TrackBar2: TTrackBar;
    CheckBox1: TCheckBox;
    Label1: TLabel;
    Label2: TLabel;
    CheckBox2: TCheckBox;
    CheckBox3: TCheckBox;
    CheckBox4: TCheckBox;
    CheckBox5: TCheckBox;
    procedure OpenGlPanelglDraw(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure TrackBar1Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure CheckBox3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


type TVertex3f     = record
 x,y,z : GLFloat;
end;
type TVector3f  = TVertex3f;
type TVertexArray  = array of array of TVertex3f;
type THeightMap    = array of array of byte;

var
  Form1: TForm1;
  heightMap       : THeightMap;
  TerrainVertices : TVertexArray;
  TerrainNormals  : TVertexArray;
  TerrainColors   : TVertexArray;

  r: real;


var
 heightMult : real = 8/5;
 size       : integer = 160;
 quality    : integer = 5;


const
 // a fenyek szinei es pozicioi mind egy egy konstanskent
 lightposition1 : TGLArrayf4 = ( -8, 8, 160, 0);
 ambient        : TGLArrayf4 = ( 1, 1, 1, 1);

 color1                : TGLArrayf4=( 1.0, 0.0, 0.0, 0.0);
 light_position        : TGLArrayf4=( 0, 1, 0, 0);
 light_spot_direction  : TGLArrayf3=( 0, -1, 0);

 color2                : TGLArrayf4=( 0.0, 1.0, 0.0, 0.0);
 light_position2       : TGLArrayf4=( 1, 0, 0, 0);
 light_spot_direction2 : TGLArrayf3=( -1, 0, 0);


implementation

{$R *.dfm}

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
 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) / 5;
    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) / 5;
    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) / 5;
   end; *)


 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;

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







procedure drawTerrain;
 var n,y,x:integer;
     nv:TVector3f;
begin
 n := size div quality;

 if form1.CheckBox5.Checked then
  glEnable (GL_LIGHTING)
 else
  glDisable (GL_LIGHTING);
 for y:=0 to n-2 do begin
  glBegin(GL_TRIANGLE_STRIP);
  for x:=0 to n-2 do begin

   glColor3fv(@TerrainColors[x,y]);
   glNormal3fv(@TerrainNormals[x,y]);
   glVertex3fv(@TerrainVertices[x,y]);

   glColor3fv(@TerrainColors[x+1,y]);
   glNormal3fv(@TerrainNormals[x+1,y]);
   glVertex3fv(@TerrainVertices[x+1,y]);

   glColor3fv(@TerrainColors[x,y+1]);
   glNormal3fv(@TerrainNormals[x,y+1]);
   glVertex3fv(@TerrainVertices[x,y+1]);

   glColor3fv(@TerrainColors[x+1,y+1]);
   glNormal3fv(@TerrainNormals[x+1,y+1]);
   glVertex3fv(@TerrainVertices[x+1,y+1]);
  end;
  glEnd;
 end;



 // DRAW NORMAL VECTORS
 if form1.CheckBox4.Checked then begin
 glDisable (GL_LIGHTING);
 glBegin(GL_LINES);
  glColor4f(1,0,0,1);
  for y:=0 to n-1 do begin
   for x:=0 to n-1 do begin
    glVertex3fv(@TerrainVertices[x,y]);
    nv := VectorMultiply(TerrainNormals[x,y],10);
    nv := VectorAdd(TerrainVertices[x,y],nv);
    glVertex3fv(@nv);
   end;
  end;
 glEnd;
 end;
end;


procedure calcTerrainCoords(heightMap:THeightMap; size,quality:integer; height :real);
var
  x,z,i,n,xi,yi   : integer;
  dx,dy,xx,yy     : double;
  v1,v2:TVertex3f;
Begin
 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);

 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));

   //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 TForm1.OpenGlPanelglDraw(Sender: TObject);
begin
  glClearColor(0.8, 1, 1, 1);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();


  gltranslate(0,-size/1.3*TrackBar1.Position/8,-size*3);
  glrotate(30,1,0,0);
  glrotate(r,0,1,0);
  gltranslate(-size/2,0,-size/2);
  if CheckBox2.Checked then r:=r+0.4;


  DrawAxes(vertex3f(0,0,0),size);
  drawTerrain;

  glFlush();
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
 loadHeightMap;
 heightMult  := TrackBar1.Position/5;
 calcTerrainCoords(heightMap, size,TrackBar2.Position,TrackBar1.Position/8);
 //calcNormalVectors;
 smoothNormalVectors;

 OpenGlPanel.glInit; // az opengl panel komponens inicalizalasa. beallit mindent ami a megjeleniteshez szukseges

 glCullFace(GL_FRONT);
 glEnable(GL_CULL_FACE);
 glShadeModel(GL_SMOOTH);


  // engedelyezzuk a fenyeket
 // itt egy jo leiras a fenyekrol: http://www.inf.u-szeged.hu/oktatas/jegyzetek/KubaAttila/opengl_html/vilagit.html
 glEnable (GL_LIGHTING);
 glEnable(GL_LIGHT0); // openglben max 8 fenyforras lehet, mi most harmat hasznalunk
// glEnable(GL_LIGHT2);
// glEnable(GL_LIGHT3);

 // az elso az ambiens feny ami mindenhova ugyanugy vilagit, olyan mint a napfeny
 glLightfv(GL_LIGHT0,GL_AMBIENT,@ambient);
 glLightfv(GL_LIGHT0,GL_POSITION,@lightposition1); // beallitjuk a poziciojat

 // az egyik spot feny fentrol lefele vilagit piros fennyel. a spot feny olyan mint a reflektor fény
 glLightfv(GL_LIGHT2, GL_POSITION, @light_position ); //pozicio megadasa
 glLightfv(GL_LIGHT2, GL_DIFFUSE, @color1); // beallitjuk a difúz színt
 glLightfv(GL_LIGHT2, GL_SPOT_DIRECTION, @light_spot_direction ); //azt hogy merre világítunk vele

 // a masik spot feny jobbrol balra vilagit zold szinnel
 glLightfv(GL_LIGHT3, GL_POSITION, @light_position2 );
 glLightfv(GL_LIGHT3, GL_DIFFUSE, @color2);
 glLightfv(GL_LIGHT3, GL_SPOT_DIRECTION, @light_spot_direction2 );
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
 quality     := TrackBar2.Position;
 heightMult  := TrackBar1.Position/8;

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

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
 if CheckBox1.Checked then
  glPolygonMode(GL_FRONT_AND_BACK,GL_LINE)
 else
  glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
end;

procedure TForm1.CheckBox3Click(Sender: TObject);
begin
 if CheckBox3.Checked then
  glShadeModel(GL_SMOOTH)
 else
  glShadeModel(GL_FLAT);
end;

end.
