(***********************************************)
(*                                             *)
(* The Hexagon 3D Graphic Demo                 *)
(* Copyright (c) 1998 by TMT Development Corp. *)
(* Copyright by Archmage (sigjol@ice.is)       *)
(*                                             *)
(* Targets:                                    *)
(*   MS-DOS 32-bit protected mode              *)
(*   Win32 application                         *)
(***********************************************)

{ DOS version of this program has been published in                     }
{ SWAG. Here is slightly changed original version with author comments. }

{ Very primitive 3d engine                                              }
{ features: convex vectors and lambert shading..                        }
{ Under construction                                                    }
{ Coded by Archmage (sigjol@ice.is)                                     }

uses CRT, Graph;

{$r-,q-}
const
  pntsnr     = 12;
  facenr     = 20;
  polyvertnr = 3;        // nr of points in each polygon
  distance   = 4000;
  enddist    = 600;
  xcenter    = 0;
  ycenter    = 0;        // the pos of object on the screen
  zcenter    = 0;
  xrotadd    = 3;        // rotations about x,y,z axis
  yrotadd    = 4;
  zrotadd    = -2;
  ytopclip   = 0;
  ybotclip   = 200;
  norm       = 90;       // set this to -1000 to make the object transparent...

  // Conventional x,y,z format...
  Verts: array [1..(pntsnr*3)] of Longint=
    (200, 0, -120, 62, -189, -120, -160, -119, -120, -163, 115, -120,
     58, 191, -120, 0, 0, -243, -200, -3, 79, -62, -189, 80, -65, 188,
     80, 160, -115, 79, 160, 118, 78, -1, 0, 202);

  // Format: nr of faces; face1,face2,face3...; color;texmap info
  // This format is compatible with 3ded
  connect: array [1..facenr*6] of Longint=
    (3,  4, 3, 8,  6, 0, 3,  6, 7, 11,  5, 0, 3,  6, 2, 7,  7, 0 ,
     3,  2, 1, 7,  7, 0 ,3,  7, 1, 9,   5, 0, 3,  7, 9, 11, 6, 0,
     3,  11, 9, 10,  24, 0,  3,  9, 0,  10, 26 ,0,  3, 9, 1,  0, 7, 0,
     3,  5, 0, 1,  7, 0, 3,  5, 1, 2,  8, 0, 3,  6, 3, 2,  6, 0,
     3,  5, 2, 3,  6, 0, 3,  4, 0, 5,  7, 0, 3,  5, 3, 4,  6, 0,
     3,  10, 0, 4,  26, 0, 3,  8, 6, 11,  6, 0, 3,  10, 8, 11,  23, 0,
     3,  10, 4, 8,  25, 0, 3,  8, 3, 6,  6, 0);

type
  VecVerts = record
    x: Real;
    y: Real;
    z: Real;
end;

screen_coords = record
  x: DWORD;
  y: DWORD;
end;

var
  polyxyz: array[1..pntsnr] of vecverts;
  polyxyzb: array[1..pntsnr] of vecverts;
  normxyz: array[1..facenr] of vecverts;
  normxyzb: array[1..facenr] of vecverts;
  scrcoords: array[1..pntsnr] of screen_coords;
  rotxyz: array[1..pntsnr] of vecverts;
  rotnxyz: array[1..facenr] of vecverts;
  xr, yr, zr: Longint := 0;
  v, i, a, offs: Longint := 0;
  loop2: Longint := 1;
  Dist: Longint := 2000;
  VecCol: DWORD := 0;
  xadd, yadd, zadd: Real := 0;
  precsin: array[0..360] of Real;
  preccos: array[0..360] of Real;
  poly: array[0..199, 1..2] of Longint;
  Page: Boolean := FALSE;
  Mode3D: Boolean := TRUE;
  Ch: Char;

procedure FillMenu;
begin
  Rectangle(1, 1, 639, 479, 235);
  SetColor(120);
  OutTextXY(190, 400, 'Use "M" key to define view mode');
  SetColor(215);
  OutTextXY(180, 420, 'Use "+" and "-" to adjust distance');
  SetColor(220);
  OutTextXY(260, 440, 'Use "ESC" to exit');
end;

procedure ChangePage;
begin
  if Page then
  begin
    SetActivePage(0);
    SetVisualPage(1, TRUE);
    Page := FALSE;
  end else
  begin
   SetActivePage(1);
   SetVisualPage(0, TRUE);
   Page := TRUE;
  end;
  SetFillColor(clBlack);
  Bar(160, 56, 480, 352);
end;

// This routine defines the vertices of the cube
// They must be in this order for calculating normals
procedure SetuPoints;
begin
  i := 1;
  v :=1 ;
  repeat
    polyxyzb[v].x := Verts[i];
    i := i + 1;
    polyxyzb[v].y := Verts[i];
    i := i + 1;
    polyxyzb[v].z := Verts[i];
    i := i + 1;
    v := v + 1;
  until v = pntsnr + 1;
  for i := 1 to facenr * 6 do
    connect[i] := connect[i] + 1
end;

// This creates the lookup table
procedure SetUpsc;
var
  loop1: Longint;
  ax, ay, az, bx, by, bz: Real;
begin
  offs := 0;
  for loop1 := 0 to 360 do
  begin
    precsin[loop1] := sin(loop1 * pi / 180);
    preccos[loop1] := cos(loop1 * pi / 180);
  end;
  offs := 0;
  for i := 1 to facenr do
  begin
    // Here I precalculate the normals and then all
    // I have to do is rotate them for each frame
    ax := (polyxyzb[connect[3 + offs]].x) - (polyxyzb[connect[2 + offs]].x);
    ay := (polyxyzb[connect[3 + offs]].y) - (polyxyzb[connect[2 + offs]].y);
    az := (polyxyzb[connect[3 + offs]].z) - (polyxyzb[connect[2 + offs]].z);
    bx := (polyxyzb[connect[4 + offs]].x) - (polyxyzb[connect[2 + offs]].x);
    by := (polyxyzb[connect[4 + offs]].y) - (polyxyzb[connect[2 + offs]].y);
    bz := (polyxyzb[connect[4 + offs]].z) - (polyxyzb[connect[2 + offs]].z);
    normxyz[i].x := 0;
    normxyz[i].y := 0;
    normxyz[i].z := 0;
    normxyzb[i].x := 0;
    normxyzb[i].y := 0;
    normxyzb[i].z := 0;
    normxyzb[i].x := (ay * bz) - (by * az);
    normxyzb[i].y := (az * bx) - (bz * ax);
    normxyzb[i].z := (ax * by) - (bx * ay);
    offs := offs + 6;
  end;
end;

procedure RotatePoints(xrot, yrot, zrot: Longint);
var
  sinxr, cosxr, sinyr, cosyr, sinzr, coszr: Real;
begin
  // here are all rotations values updated and precalculated
  xr := xr + xrot;
  if xr > 360 then xr := 1;
  if xr < 0 then xr := 360;
  yr := yr + yrot;
  if yr > 360 then yr := 1;
  if yr < 0 then zr := 360;
  zr := zr + zrot;
  if zr > 360 then zr := 1;
  if zr < 0 then zr := 360;
  sinxr := precsin[xr];
  cosxr := preccos[xr];
  sinyr := precsin[yr];
  cosyr := preccos[yr];
  sinzr := precsin[zr];
  coszr := preccos[zr];
  for i := 1 to pntsnr do
  begin
   // this is the formula for vector rotations around all axis in 3d space }
    rotxyz[i].x := 0;
    rotxyz[i].y := 0;
    rotxyz[i].z := 0;
    polyxyz[i].x := 0;
    polyxyz[i].y := 0;
    polyxyz[i].z := 0;
    polyxyz[i].x := polyxyzb[i].x;
    polyxyz[i].y := polyxyzb[i].y;
    polyxyz[i].z := polyxyzb[i].z;
    rotxyz[i].y := polyxyz[i].y * cosxr - polyxyz[i].z * sinxr;
    rotxyz[i].z := polyxyz[i].z * cosxr + polyxyz[i].y * sinxr;
    polyxyz[i].y := rotxyz[i].y;
    polyxyz[i].z := rotxyz[i].z;
    rotxyz[i].z := polyxyz[i].z * cosyr - polyxyz[i].x * sinyr;
    rotxyz[i].x := polyxyz[i].x * cosyr + polyxyz[i].z * sinyr;
    polyxyz[i].x := rotxyz[i].x;
    rotxyz[i].x := polyxyz[i].x * coszr - polyxyz[i].y * sinzr;
    rotxyz[i].y := polyxyz[i].y * coszr + polyxyz[i].x * sinzr;
  end;
  for i := 1 to facenr do
  begin
    rotnxyz[i].x := 0;
    rotnxyz[i].y := 0;
    rotnxyz[i].z := 0;
    normxyz[i].x := normxyzb[i].x;
    normxyz[i].y := normxyzb[i].y;
    normxyz[i].z := normxyzb[i].z;
    rotnxyz[i].y := normxyz[i].y * cosxr - normxyz[i].z * sinxr;
    rotnxyz[i].z := normxyz[i].z * cosxr + normxyz[i].y * sinxr;
    normxyz[i].y := rotnxyz[i].y;
    normxyz[i].z := rotnxyz[i].z;
    rotnxyz[i].z := normxyz[i].z * cosyr - normxyz[i].x * sinyr;
    rotnxyz[i].x := normxyz[i].x * cosyr + normxyz[i].z * sinyr;
    normxyz[i].x := rotnxyz[i].x;
    rotnxyz[i].x := normxyz[i].x * coszr - normxyz[i].y * sinzr;
    rotnxyz[i].y := normxyz[i].y * coszr + normxyz[i].x * sinzr;
  end;
end;

procedure ScrProject(xc, yc, zc: Longint);
begin
  for i := 1 to pntsnr do
  begin
    scrcoords[i].x := trunc((320 * (rotxyz[i].x + xc) / (dist - (rotxyz[i].z + zc))) + 320);
    scrcoords[i].y := trunc(200 - (320 * (rotxyz[i].y + yc) / (dist - (rotxyz[i].z + zc))));
  end;
end;

procedure Draw;
begin
  offs := -6;
  a := 1 ;
  for i := 1 to facenr do
  begin
    offs := offs + 6;
    VecCol := trunc(rotnxyz[i].z / 400);
    if rotnxyz[i].z > 10000 then
    begin
      SetColor(VecCol); SetFillColor(VecCol);
      if Mode3D then
        FillTriangle(scrcoords[connect[2 + offs]].x, scrcoords[connect[2 + offs]].y,
          scrcoords[connect[3 + offs]].x, scrcoords[connect[3 + offs]].y,
          scrcoords[connect[4 + offs]].x, scrcoords[connect[4 + offs]].y)
      else
        Triangle(scrcoords[connect[2 + offs]].x, scrcoords[connect[ 2+ offs]].y,
          scrcoords[connect[3 + offs]].x, scrcoords[connect[3 + offs]].y,
          scrcoords[connect[4 + offs]].x, scrcoords[connect[4 + offs]].y);
    end;
  end;
end;

// Here is the main program
begin
  // global variables initialized
  loop2 := 1;
  SetuPoints;                                  // define vector vertices
  SetupSc;
  Dist := 2000;
  SetSVGAMode(640, 480, 8, LFBorBanked);
  if GraphResult <> 0 then
  begin
    ClrScr;
    Writeln(GraphErrorMsg(GraphResult));
    exit;
  end;
  DrawBorder := FALSE;
  ClearDevice;
  SetColor(125);
  OutTextXY(270, 230, 'Please wait...');
  SetActivePage(0);
  FillMenu;
  SetActivePage(1);
  FillMenu;
  for i := 1 to 126 do
    SetRGBPalette (i, i div 5, i div 3, (20 + i) div 3);
  repeat
    if dist < enddist then dist := enddist;
    rotatepoints(xrotadd, yrotadd, zrotadd);   // rotate around x,y,z axis
    scrproject(xcenter, ycenter, zcenter);
    Draw;
    ChangePage;
    if KeyPressed then
    begin
      ch := ReadKey;
      if UpCase(ch) = 'M' then Mode3D := not Mode3D;
      if (ch = '+') then Dist -:= 50;
      if (ch = '-') then Dist +:= 50;
       if Dist > Distance then Dist := Distance;
      if Dist < EndDist then Dist := EndDist;
    end;
  until ch = #27;
  CloseGraph;
end.