External GRAPHICS::MAKE(3);
(*$E+ *)

procedure Start;
{ clear screen }
var
  I,J : counter;
begin
  for I := 0 to DotsAcross do
    for J := 0 to DotsDown do
      Screen[I,J] := false
end;	{ start }

(*$L+ *)
procedure Finish;
{ display output for H-19 terminal }
var
  I,J : counter;
begin
  write(chr(escape),'E');	{ clear screen & home cursor }
  write(chr(escape),'F');	{ put terminal into graphics mode }
  write(chr(escape),'w');	{ no wraparound at end of line }
  J := DotsDown;
  while J>0 do
    begin
      for I := 0 to DotsAcross do
	if (Screen[I,J] and Screen[I,J-1])
          then write('q')
          else if Screen[I,J-1]
                 then write('l')
		 else if Screen[I,J]
		        then write('o')
			else write(' ');
         if J>1
           then J:=J-2	{ count down by two }
	   else J := 0;
         if J>0
           then writeln		{ CR/LF unless last line }
    end; { while }
  write(chr(escape),'G');	{ exit graphics mode }
  write(chr(escape),'j');	{ save cursor position }
  write(chr(escape),'x','1');{ enable 25th line }
  write(chr(escape),'Y','8',' ');{ put cursor at start of 25th }
  with EyePt do write('eye:',X:4:1,Y:4:1,Z:4:1);
  with CntrInt do write(' cent:',X:4:1,Y:4:1,Z:4:1);
  readln(CmdChar);		{ get <CR> before continuing }
  write(chr(escape),'l');	{ erase entire line }
  write(chr(escape),'k');	{ restore cursor position }
  write(chr(escape),'v')	{ permit wraparound }
end;	{ Finish }

(*$L+ *)
procedure MoveTo( X,Y : real);
begin
  ScreenX := X;  ScreenY := Y;
end; { MoveTo }

(*$L+ *)
procedure DrawTo( X,Y : real);
var
  I : counter;
  Dx,Dy,Length,StepX,StepY,Xpos,Ypos : real;
begin
  Dx := X - ScreenX;
  Dy := Y - ScreenY;
  if abs(Dx) > abs(Dy)
    then Length := abs(Dx)
    else Length := abs(Dy);
  if Length < 1.0
    then Length := 1.0; { catch zero length lines }
  StepX := Dx/Length;
  StepY := Dy/Length;
  Xpos := ScreenX;
  Ypos := ScreenY;
  for I := 0 to trunc(Length) do
    begin
      Screen[round(Xpos),round(Ypos)] := true;
      Xpos := Xpos + StepX;
      Ypos := Ypos + StepY;
    end; { for }
  ScreenX := X;
  ScreenY := Y;
end;   { DrawTo }

(*$L+ *)
procedure MakePicture;
  { transform and clip, then display polygons }
var
  I,J,NumClp : counter;
  TmpPoly : OnePoly;

  function DotProd( Pt1,Pt2 : Point) : real;
    begin	{ vector dot product }
      DotProd := Pt1.X * Pt2.X + Pt1.Y * Pt2.Y + Pt1.Z * Pt2.Z;
    end; { DotProd }

  procedure Ident(var Mtx : Matrix);
    var
      I,J : counter;
  begin    { initialize matrix to identity matrix }
    for I := 1 to 4 do
      for j := 1 to 4 do
        if I=J
          then Mtx[I,J] := 1.0
          else Mtx[I,J] := 0.0;
  end; { Ident }

  procedure MatrixMult(Mt1,Mt2 : Matrix; var Result : Matrix);
    var
      I,J,K : counter;
  begin	{ multiply two 4 by 4 matrices }
    for I := 1 to 4 do
      for J := 1 to 4 do
        begin
          Result[I,J] := 0.0;
            for K := 1 to 4 do
              Result[I,J] := Result[I,J] + Mt1[K,J]*Mt2[I,K]
        end
  end;

(*$L+ *)
{ This procedure will transform the vertices of a polygon
  using a four-by-four matrix. }
  procedure Transform(Pt : Point; Mtx : Matrix; var NewPt : Point );
  begin
    NewPt.X := Pt.X*Mtx[1,1]+Pt.Y*Mtx[1,2]+Pt.Z*Mtx[1,3]+Mtx[1,4];
    NewPt.Y := Pt.X*Mtx[2,1]+Pt.Y*Mtx[2,2]+Pt.Z*Mtx[2,3]+Mtx[2,4];
    NewPt.Z := Pt.X*Mtx[3,1]+Pt.Y*Mtx[3,2]+Pt.Z*Mtx[3,3]+Mtx[3,4];
  end; { Transform }

(*$L+ *)
{ Distance and veiwing angle transforms are determined by this
  this procedure, which builds a transformation matrix based
  on the relationship between the coordinates of the eyepoint
  and those of the center of interest. }
  procedure GetEyeSpace( EyePt,Cntrint : Point);
    var
      Mtx : Matrix;
      C1,C2 : Point;
      Hypotenuse,CosA,SinA : real;
  begin
    Ident(Eyespace);
    with EyePt do	{ load eyepoint translation }
      begin
        EyeSpace[1,4] := -X;
        EyeSpace[2,4] := -Y;
        EyeSpace[3,4] := -Z
      end;
    Transform(Cntrint,EyeSpace,C1); {translate center of interest }
    Ident(Mtx);	{load rotation about Z-axis }
    with C1 do
      Hypotenuse := sqrt( X*X + Y*Y);
    if Hypotenuse > 0.0 then
      begin
        CosA := C1.Y / Hypotenuse;
        SinA := C1.X / Hypotenuse;
        Mtx[1,1] := CosA;
        Mtx[2,1] := SinA;
        Mtx[1,2] := -SinA;
        Mtx[2,2] := CosA;
        MatrixMult(EyeSpace,Mtx,EyeSpace)
      end;
    Transform(CntrInt,EyeSpace,C2); {rotate center of interest }
    Ident(Mtx);		{load rotation about X-axis }
    with C2 do
      Hypotenuse := sqrt(Y*Y + Z*Z);
    if Hypotenuse > 0.0 then 
      begin
        CosA := C2.Y / Hypotenuse;
        SinA := -C2.Z / Hypotenuse;
        Mtx[2,2] := CosA;
        Mtx[3,2] := SinA;
        Mtx[2,3] := -SinA;
        Mtx[3,3] := CosA;
        MatrixMult(EyeSpace,Mtx,Eyespace)
      end;
    Ident(Mtx);	{ load switch between Y and Z axes }
    Mtx[2,2] := 0.0;
    Mtx[3,3] := 0.0;
    Mtx[2,3] := 1.0;
    Mtx[3,2] := 1.0;
    MatrixMult(EyeSpace,Mtx,EyeSpace)
  end;	{ GetEyeSpace }

(*$L+ *)
  Procedure MakeDisplayable(Var Pt : Point);
{ This procedure achieves a perspective effect by dividing
  the x and y coordinates of each vertex by the z coordinate. }
  begin
    Pt.X := ScreenScale.X * Pt.X / Pt.Z + ScreenCtr.X;
    Pt.Y := ScreenScale.Y * Pt.Y / Pt.Z + ScreenCtr.Y;
  end;	(* MakeDisplayable *)

(*$L+ *)
  Function FacesEye( Poly : OnePoly ) : boolean;
{ This function determines whether or not a polygon will be
  hidden by another part of the same surface in a three-
  dimensional display. }
  var
    TmpPt : Point;
    TmpPoly : OnePoly;
  begin
    with Poly[2] do	{ make copy of second vertex }
      begin
        TmpPt.X:=X;
        TmpPt.Y:=Y;
        TmpPt.Z:=Z
      end;
    TmpPoly[1].X := Poly[1].X - Poly[2].X;	{ directed vector }
    TmpPoly[1].Y := Poly[1].Y - Poly[2].Y;	{ from 2nd to 1st }
    TmpPoly[1].Z := Poly[1].Z - Poly[2].Z;	{ vertex }
    TmpPoly[2].X := Poly[3].X - Poly[2].X;	{ directed vector }
    TmpPoly[2].Y := Poly[3].Y - Poly[2].Y;	{ from 2nd to 3rd }
    TmpPoly[2].Z := Poly[3].Z - Poly[2].Z;	{ vertex }
    GetPlanes( TmpPoly,2 );	{ get plane coefficients }
    if (DotProd( TmpPt,TmpPoly[1] ) <= 0.0 )
      then FacesEye := false
      else FacesEye := true
  end;	(* FacesEye *)

(*$L+ *)
  Procedure ClipIn(Var Poly : OnePoly; Var NumPts : counter);
{ Procedure to determine if any vertices of a polygon lie
  outside previously defined clipping planes; if so the
  polygon is modified accordingly. }
  var
    I,J,LstJ,TmpPts : counter;
    D1,D2,A : Real;
    TmpPoly : OnePoly;
  begin
    for I := 1 to WindowSize do	(* for each window edge *)
      if NumPts > 0 then
        begin
	  D1 := DotProd( Poly[NumPts],Window[I] );
	  LstJ := NumPts;
	  TmpPts := 0;
	  for J:= 1 to NumPts do	(* for each polygon edge *)
	    begin
	      if D1 > 0.0 then	(* is leading vertex inside? *)
		begin
		  TmpPts := TmpPts +1;
		  with TmpPoly[TmpPts] do
		    begin	(* copy leading vertex *)
		      X:=Poly[LstJ].X;
       		      Y:=Poly[LstJ].Y;
		      Z:=Poly[LstJ].Z
		    end
		end;	(* if leading vertex inside *)
	      D2:=DotProd(Poly[J],Window[I] );
	      if D1 * D2 < 0.0 then (* does edge straddle window? *)
		begin
		  A := D1 / (D1 - D2);
		  TmpPts := TmpPts + 1;
		  with TmpPoly[TmpPts] do
		    begin
		      X:=A*Poly[J].X + (1.0-A)*Poly[LstJ].X;
		      Y:=A*Poly[J].Y + (1.0-A)*Poly[LstJ].Y;
		      Z:=A*Poly[J].Z + (1.0-A)*Poly[LstJ].Z
		    end
		end;
	      LstJ := J;
	      D1 := D2
	    end;	(* NumPts loop *)
	  for J:=1 to TmpPts do	(* copy polygon back to input *)
	    with TmpPoly[J] do
	      begin
	        Poly[J].X:=X;
	        Poly[J].Y:=Y;
	        Poly[J].Z:=Z
	      end;
	  NumPts := TmpPts
	end	(* WindowSize Loop *)
  end;	(* ClipIn *)

(*$L+ *)
  Procedure InsertSort(Poly : OnePoly ; NumPts : counter);
{ Based on the average value of their z coordinates,
  polygons are sorted by their distance from the eyepoint
  in this binary insertion sort procedure. }
    var
      I,J,K : counter;
      AvDepth : real;
  begin	(* binary insertion sort on average depth *)
    AvDepth:= 0.0;
    for I := 1 to NumPts do
      with Poly[I] do	(* store vertices and find averge depth *)
	begin
	  OutVtces[NumVtxOut + I + 1].X := X;
	  OutVtces[NumVtxOut + I + 1].Y := Y;
	  OutVtces[NumVtxOut + I + 1].Z := Z;
	  AvDepth := AvDepth + Z	{ sum depths }
	end;
    AvDepth := AvDepth / NumPts;	{ divide for average }
    OutVtces[NumVtxOut + 1].Z := AvDepth; { store for later }
    J:=0;	(* initialize for insertion search *)
    I:=(NumDisplay + 1) div 2;
    K:=NumDisplay;
    while (J<>I) do	(* binary search for insertion point *)
      if (AvDepth < OutVtces[OutPolys[I].Start ].Z) then
	begin
	  K:=I;
	  I:=(I+J) div 2
	end
	else
	  begin
	    J:=I;
	    I:=(I+K+1) div 2
	  end;
    for J:=NumDisplay downto I+1 do { found it, now insert }
      begin
	OutPolys[J+1].Start := OutPolys[J].Start;  { move everything above }
	OutPolys[J+1].NumVtx := OutPolys[J].NumVtx { insertion point up one }
      end;
    OutPolys[I+1].Start := NumVtxOut + 1;	{ store new entry }
    OutPolys[I+1].NumVtx := NumPts;
    NumVtxOut := NumVtxOut + NumPts + 1;	{ vertex count }
    NumDisPlay := NumDisplay + 1		{ polygons stored }
  end;	(* InsertSort *)

(*$L+ *)
  procedure ClipOut(Poly : OnePoly; var NumPts : Vertex; Place : counter);
 { Once sorted polygons are checked to determine if a polygon
   closer to the eyepoint hides all or part of one that is
   farther away. }
  Var
    I,LstI,NumDrawn : Counter;
    Pt1,Pt2 : Point;
    Drawn : boolean;
	
    procedure ClipAfter(Index : counter; Pt1,Pt2 : Point);
      var
	I : counter;
	D1,D2,A : Real;
	Out : boolean;
	Pt3 : Point;
      begin	(* recursively check polygons for oaverlap with input edge *)
	if (Index < Place) then	 (* is polygon closer than edge? *)
	  with OutPolys[Index] do
	    begin
	      I:=Start + NumVtx;
	      Out:=false;
	      repeat (* for each polygon edge *)
		D1:=DotProd( Pt1,OutVtces[I]);
		D2:=DotProd( Pt2,OutVtces[I]);
		if ( (D1 <= 0.0) and (D2 <= 0.0) ) then
		  begin (* both points visible *)
		    Out := true;
		    ClipAfter(Index+1,Pt1,Pt2)
		  end
		  else if (D1 * D2 < 0.0) then
			 begin (* one point visible *)
			   A:=D1/(D1-D2);
			   Pt3.X:=A*Pt2.X+(1.0-A)*Pt1.X;
			   Pt3.Y:=A*Pt2.Y+(1.0-A)*Pt1.Y;
			   Pt3.Z:=A*Pt2.Z+(1.0-A)*Pt1.Z;
			   if (D1 < 0.0) then
			     begin (* Pt1 visible *)
			       ClipAfter(Index+1,Pt1,Pt3);
			       with Pt3 do
				 begin
				   Pt1.X:=X;
				   Pt1.Y:=Y;
				   Pt1.Z:=Z
				 end
			     end
			     else
			       begin	(* Pt2 visible *)
				 ClipAfter(Index+1,Pt3,Pt2);
				 with Pt3 do
				 begin
				   Pt2.X:=X;
				   Pt2.Y:=Y;
				   Pt2.Z:=Z
				 end
			       end
			end;	(* one point visible *)
		      I:=I-1;
   	        until (Out or (I=Start)) { all visible of edges exhausted }
	      end
	    else
	      begin (* reached end of list of closer polygons *)
	        MakeDisplayable(Pt1);
	        MakeDisplayable(Pt2);
	        Moveto(Pt1.X,Pt1.Y);
	        Drawto(Pt2.X,Pt2.Y);
	        Drawn := true	(* as mark is displayed *)
	      end
     end;	(* Clipafter *)

{ Clipout procedure body }
  begin (* clip each poly edge by all closer polys, draw what's left *)
    NumDrawn := 0;
    LstI := NumPts;
    for I:= 1 to NumPts do
      begin
	with Poly[LstI] do
	  begin
	    Pt1.X:=X;
	    Pt1.Y:=Y;
	    Pt1.Z:=Z
	  end;
	with Poly[I] do
	  begin
	    Pt2.X:=X;
	    Pt2.Y:=Y;
	    Pt2.Z:=Z
	  end;
	Drawn := false;
	ClipAfter(1,Pt1,Pt2);	(* check closer polys, then display *)
	if Drawn then
	  NumDrawn := NumDrawn + 1;
	LstI := I
      end;	(* for loop *)
    if NumDrawn = 0 then
      NumPts := 0	(* mark as hidden *)
  end;	(* ClipOut *)

(*$L+ *)
begin  (* MakePicture procedure body *)
  GetEyeSpace(EyePt,CntrInt );	(* get eyespace matrix *)
  NumDisplay :=0;
  NumVtxOut := 0;	(* set output counters *)
  for I:=1 to NumPols do
    with Polygons[I] do
      begin
	for J:=1 to NumVtx do (* get polygon *)
	  begin
	    with Points[Vertices[Start+J]] do
	      begin
	        TmpPoly[J].X:=X;
	        TmpPoly[J].Y:=Y;
	        TmpPoly[J].Z:=Z
	      end;
	    Transform(TmpPoly[J],EyeSpace,TmpPoly[J]); (* transform *)
	  end;
	if FacesEye(TmpPoly) then
	  begin
	    NumClp:=NumVtx;	(* protect original data *)
	    ClipIn(TmpPoly,NumClp); (* clip to veiw window *)
	    if NumClp>0 then
	      InsertSort(TmpPoly,NumClp);
				(* store in sorted order for display *)
	  end
	end;	(* loop for each polygon *)
(* display surviving polygons, clipping each be closer polygons *)
  Start;	(* initialize and clear display *)
  for I:=1 to NumDisplay do
    with OutPolys[I] do
      begin
	for J:=1 to NumVtx do
	  with OutVtces[Start+J] do
	    begin
	      TmpPoly[J].X:=X;
	      TmpPoly[J].Y:=Y;
	      TmpPoly[J].Z:=Z
	    end;
	ClipOut(TmpPoly,NumVtx,I);  (* clip and display *)
	if NumVtx > 0 then
	  begin
	    GetPlanes(TmpPoly,NumVtx);  (* convert to planes *)
	    for J:=1 to NumVtx do (* copy back for later clipping *)
	      with OutVtces[Start+J] do
		begin
		  X:=TmpPoly[J].X;
		  Y:=TmpPoly[J].Y;
		  Z:=TmpPoly[J].Z
		end
	   end
	end;	(* for loop (1 to NumDisplay) *)
  Finish	(* finalize picture *)
end;	(* MakePicture *)
.

