UNIT Three_D;

(* when viewing an object, the x-y axis is horizontal and z is vertical *)
INTERFACE
USES
  Graph;
VAR

  Longitude,                          (* angle <2Pi in x-y plane at which to
                                         view object                         *)
  Latitude,                           (* x-z plane angle at which to view
                                         object || 0
                                                                             *)
  Size,                               (* scalar > 0 to increase/decrease the
                                         object size                         *)
  Zoom                                (* viewing angle in radians 0 < Zoom < Pi
                                                                             *)
                : DOUBLE;

PROCEDURE MapLine(X1, Y1, Z1, X2, Y2, Z2 : DOUBLE);
PROCEDURE SetViewer;

IMPLEMENTATION

VAR
  Ix, Iy, Iz,                         (* X to X' transformation              *)
  Jx, Jy,                             (* Y to Y' transformation              *)
  Kx, Ky, Kz                          (* Z to Z' transformation              *)
                : DOUBLE;

PROCEDURE SetViewer;                  (* this procedure will determine the
                                         coefficients of the rotation and
                                         translation functions               *)
VAR
  Temp1, Temp2  : DOUBLE;

BEGIN

(* start with "longitude" rotation on Z axis and scale by "Size" *)
  Jx := - Size * Sin(Longitude);      (* map x to y'                         *)
  Jy := Size * Cos(Longitude);        (* map y to y'                         *)

(* Jz := 0.0;                      z doesn't map to y', this term not used *)
(* x' y' and z' now rotate on y' axis by "latitude" radians *)
  Temp1 := Sin(Latitude);             (* compute sine once                   *)
  Temp2 := Cos(Latitude);             (* compute cosine once                 *)
  Ix := Jy * Temp2;                   (* map x to x' and incorporate latitude
                                                                             *)
  Iy := - Jx * Temp2;                 (* map y to x' and incorporate latitude
                                                                             *)
  Iz := Size * Temp1;                 (* map z to x' and incorporate latitude
                                                                             *)
  Kx := - Jy * Temp1;                 (* map x to z' and incorporate latitude
                                                                             *)
  Ky := Jx * Temp1;                   (* map y to z' and incorporate latitude
                                                                             *)
  Kz := Size * Temp2;                 (* map z to z' and incorporate latitude
                                                                             *)

(* y & z dot product scalar, Zoom is viewing angle in radians *)
  Temp1 := Zoom / 2.0;
  Temp2 := Cos(Temp1) / Sin(Temp1);
  Jx := Temp2 * Jx;
  Jy := Temp2 * Jy;
  Kx := Temp2 * Kx;
  Ky := Temp2 * Ky;
  Kz := Temp2 * Kz;
END;                                  (* SetViewer                           *)

PROCEDURE MapPoint(X1, Y1, Z1 : DOUBLE;
                   VAR X, Y : DOUBLE);

(* project point onto the focal plane *)
(* point may be out of the viewport... this case handled later *)
VAR
  Temp          : DOUBLE;

BEGIN                                 (* translate rotated object by
                                         "Distance" units                    *)
  Temp := 1.0 / (Distance - (Ix * X1 + Iy * Y1 + Iz * Z1));

(* X' translated and used to determine focal plane intersection.
   Focal plane is always 1 unit away.  Narrow viewing angle is different from
   increased "Size" *)
  X := Temp * (Jx * X1 + Jy * Y1);    (* dot product.. Y'maps to X dimension *)
  Y := Temp * (Kx * X1 + Ky * Y1 + Kz * Z1); (* dot prod. Z' maps to Y
                                         dimension                           *)
END;                                  (* MapPoint                            *)

PROCEDURE MapLine(X1, Y1, Z1, X2, Y2, Z2 : DOUBLE);
VAR
  P1, P2,                             (* have points 1 and 2 been assigned ? *)
  Left, Right, Top, Bot               (* does line segment intersect lines
                                         forming viewport?                   *)
                : BOOLEAN;
  XX1, YY1, XX2, YY2, ILeft, IRight, ITop, IBot : DOUBLE; (* those
                                         intersection points                 *)
  P1_X, P1_Y, P2_X, P2_Y : INTEGER;

BEGIN
  MapPoint(X1, Y1, Z1, XX1, YY1);     (* find point 1 on the focal plane     *)
  MapPoint(X2, Y2, Z2, XX2, YY2);     (* find point 2 on the focal plane     *)

(* these points must lie within the viewport which has limits 1 & -1 in the X
   and Y dimension *)
  P1_X := Round((XX1 + 1.5) * 200);   (* convert to display coordinates      *)
  P2_X := Round((XX2 + 1.5) * 200);
  P1_Y := Round((YY1 - 1.0) * - 200);
  P2_Y := Round((YY2 - 1.0) * - 200);
  Line(P1_X, P1_Y, P2_X, P2_Y);       (* draw the line on the display        *)
END;

VAR
  GrDriver, GrMode, ErrorCode : INTEGER;

BEGIN

 (****************************************************************************)
 (* initialize viewing variables                                             *)
 (****************************************************************************)
  Longitude := 0.0;                   (* x axis pointing at viewer           *)
  Latitude := 0.0;                    (* viewing the equator                 *)
  Distance := 3.5;                    (* 3.5 units away                      *)
  Size := 1.0;                        (* don't scale the object              *)
  Zoom := 1.0;                        (* 1 radian, approx 57 degrees         *)
  SetViewer;                          (* initialize these settings           *)
  GrDriver := Detect;                 (* what kind of display?               *)
  InitGraph(GrDriver, GrMode, 'c:\turbo5'); (* initialize graphics           *)
END.