(*
   DEMO05 - A Gouraud-shaded rotating torus
   (c) 1994 Alessandro Scotti
*)
uses Crt, Modex, Threed;

(* Define ALTPAL for alternate palette *)
{$define ALTPAL}

const
  MAXVTX1 = 15; RADIUS1 = 70;   (* MAXVTX1+1 must be multiple of 4 *)
  MAXVTX2 = 15; RADIUS2 = 30;
  MAXVTX  = (MAXVTX1+1)*(MAXVTX2+1)-1;
  MAXFACE = MAXVTX;
  Trans : TPoint = ( X:0; Y:0; Z:0 );           (* Object translation *)
  Light : TPoint = ( X:0; Y:0; Z:-63*$10000 );  (* Light direction *)
type
  TQuad   = record
    QVtx  : array[ 0..3 ] of integer;
  end;
var
  Vtx, XVtx : array[ 0..MAXVTX ] of TPoint;     (* Points *)
  VVtx      : array[ 0..MAXVTX ] of record X, Y: integer end;
  Face      : array[ 0..MAXFACE ] of TQuad;     (* Polys *)
  Culled    : array[ 0..MAXFACE ] of integer;
  GNrm,XGNrm: array[ 0..MAXVTX ] of TVector;    (* Gouraud normals *)
  VtxLight  : array[ 0..MAXVTX ] of integer;    (* Points brightness *)
  Page      : word;

function GetVtx( I1, I2: integer ): integer;
begin
  GetVtx := (I1 mod (MAXVTX1+1))*(MAXVTX2+1) + I2 mod (MAXVTX2+1);
end;

procedure Init;
var
  R, N, X, Y, Z: real;
  I, J, K, V: integer;
begin
  (* Build vertexes *)
  for I:=0 to MAXVTX1 do begin
    K := (I + (MAXVTX1+1) shr 2) mod (MAXVTX1+1);
    R := RADIUS1 + RADIUS2*Cos( 2*K*Pi / (MAXVTX1+1) );
    for J:=0 to MAXVTX2 do begin
      V := I*(MAXVTX2+1)+J;                     (* Index of current vertex *)
      (* Compute coordinates of current vertex *)
      X := R*Cos(2*J*Pi / (MAXVTX2+1));         (* Get coordinates *)
      Y := R*Sin(2*J*Pi / (MAXVTX2+1));
      Z := RADIUS2*Sin(2*K*Pi / (MAXVTX1+1));
      Vtx[V].X := Round( X )*$10000;            (* Save coordinates *)
      Vtx[V].Y := Round( Y )*$10000;
      Vtx[V].Z := Round( Z )*$10000;
      (* Compute direction of Gouraud normal thru current vertex *)
      X := X - RADIUS1*Cos(2*J*Pi / (MAXVTX2+1));
      Y := Y - RADIUS1*Sin(2*J*Pi / (MAXVTX2+1));
      N := Sqrt( X*X + Y*Y + Z*Z );             (* Get vector length *)
      GNrm[V].X := Trunc( X*$10000/N );         (* Save normal vector *)
      GNrm[V].Y := Trunc( Y*$10000/N );
      GNrm[V].Z := Trunc( Z*$10000/N );
    end;
  end;
  (* Generate faces so that depth-sorting is not needed: there are still *)
  (* some *very* little errors, but this is the best I could devise *)
  J := 0;
  K := 0;
  for I:=0 to MAXFACE do with Face[I] do begin
    QVtx[0] := GetVtx( J, K );
    QVtx[1] := GetVtx( J, K+1 );
    QVtx[2] := GetVtx( J+1, K+1 );
    QVtx[3] := GetVtx( J+1, K );
    Inc( K );
    if( K > MAXVTX2 ) then begin
      K := 0;
      Inc( J );
    end;
  end;
{$ifndef ALTPAL}
  for I:=0 to 63 do mxSetColor( I+64, 0, 0, I );     (* Blue palette *)
{$else}
  for I:=0 to 31 do mxSetColor(I+64, 0, I shl 1, 0); (* Green neon palette *)
  for I:=32 to 63 do mxSetColor ( I+64, (I-32) shl 1, 63, (I-32) shl 1 );
{$endif}
end;

var
  AX, AY, AZ: byte;
  I: word;
begin
  mxInit;
  mxSetMode( MX_320x240 );
  Init;
  Page := 240;          (* Start with hidden page *)

  AX := 0;
  AY := 0;
  AZ := 0;
  (* Init 3D transforms, perspective is intentionally exaggerated *)
  tdSetTranslation( Trans );
  tdSetLight( Light );
  tdSetPerspective( 400*$10000, $10000, $10000 );
  (* Main loop, all magic here! *)
  while( not KeyPressed ) do begin
    tdSetRotation( AX, AY, AZ );                (* Set new angles *)
    tdTransform( Vtx, XVtx, MAXVTX+1 );         (* 3D transform points *)
    tdTransformToImage( XVtx, VVtx, MAXVTX+1, 160, 120+Page );
    tdRotate( GNrm, XGNrm, MAXVTX+1 );          (* Rotate Gouraud normals *)
    tdTransformLight( XGNrm, VtxLight, MAXVTX+1 );
    (* Backplane culling is not really needed here! *)
    FillChar( Culled, SizeOf(Culled), 0 );
    tdBackPlaneCull( Face, XVtx, Culled, MAXFACE+1, SizeOf(TQuad) );
    Inc( AX, 1 );                               (* Bump angles *)
    Inc( AY, 2 );
    Inc( AZ, 3 );
    mxSetClipRegion( 0, Page, 320, 240 );       (* Set clip to new page *)
    mxSetClip( TRUE );
    mxFillBox( 0, Page, 320, 240, 0, OP_MOVE ); (* Clear screen *)
    (* Draw polygons *)
    for I:=0 to MAXFACE do with Face[I] do
      if( Culled[I] >= 0 ) then mxGouraudPoly( 4, QVtx, VVtx, VtxLight, 64 );
    (* Flip page: at 320x240 the Start Address Register Low is always zero *)
    case Page of
      0  : begin PortW[$3D4] := $000C; Page := 240; end;
      240: begin PortW[$3D4] := $4B0C; Page := 480; end;
      480: begin PortW[$3D4] := $960C; Page := 0; end;
    end;
    mxWaitRetrace; (* Uncomment this instruction if screen flickers *)
  end;

  mxSetMode( MX_TEXT );
  mxTerm;
end.
