MODULE GRAPH ;
  (*------------------------------------------------------------------*)
  (*								      *)
  (*  Written Mar 22,'82   by Jack Gilmer			      *)
  (*								      *)
  (*  Modified Oct 23,'82   by J.A. Koehler from Whitesmiths PASCAL   *)
  (*      as used on LSI-11 to PASCAL MT+                             *)
  (*                                                                  *)
  (*  This file of absolute graphic routines implements the standard  *)
  (* graphics package for the Lear Seigler ADM 3A+ equipped with the  *)
  (* Graphx hard copy.  All positions are in mm. on a 170 by 130 mm.  *)
  (* page.  All angles are in degrees up from the x-axis.	      *)
  (*								      *)
  (*  NOTE - The ADM 3A+ emulates a Tektronix terminal and uses a     *)
  (* virtual screen size of 1024 by 780, and translates this to pixel *)
  (* co-ordinates of 512 by 250 internally.			      *)
  (*------------------------------------------------------------------*)

  CONST
    xfactor = 6.0532;	(* # of steps/mm on the Tektronix 1024 by *)
    yfactor = 6.0387;	(* 780 - ADM 3A+ translates to 512 by 250.*)

    nulcode = 0;	(* ASCII NUL *)
    eotcode = 4;	(* ASCII EOT *)
    enqcode = 5;	(* ASCII ENQ *)
    etbcode = 23;	(* ASCII ETB *)
    cancode = 24;	(* ASCII CAN *)
    emcode = 25;	(* ASCII EM *)
    esccode = 27;	(* ASCII ESC *)
    fscode = 28;	(* ASCII FS *)
    gscode = 29;	(* ASCII GS *)
    uscode = 31;	(* ASCII US *)

    maxsent = 20;	(* # of co-ord pairs sent before handshake *)

    minx = 0;		(* screen co-ords in mm. *)
    maxx = 169;
    miny = 0;
    maxy = 129;

    absmaxx = 10000.0;	(* absolute calculation limits *)
    absmaxy = 10000.0;

    radian = 57.2958;	(* degrees/radian *)


  TYPE
    astringtype = STRING[132];      

    aendtype = (noend, point, square, triangle, cross, ecks, diamond,
                circle);

    alinetype = (noline, solid, dotted, dashed, dotdashed, 
                 dotdotdashed);

    apointtype =  RECORD	(* absolute position in millimeters *)
                    x,
		    y:  REAL;
		  END;

    byte = 0..255;

      				   (* units for 'serio' routines *)
    unittype = (conunit, prunit, admunit, hpunit);


  VAR
    oldend:  aendtype;		(* last end symbol defined *)

    oldline:  alinetype;	(* last set line type *)

    oldpt, 			(* last point plotted *)
    oldleft,			(* existing window corners in mm.*)
    oldright:  apointtype;

    penlifted:  BOOLEAN;	(* last pen position *)

    numsent:  INTEGER;		(* co-ord counter for handshake *)

    oldesize,			(* last set end symbol radius *)
    oldrptlength:  REAL;	(* last set line pattern length *)



  EXTERNAL FUNCTION serin(unit:  unittype):  CHAR;  

  EXTERNAL PROCEDURE serout(unit:  unittype;  ch:  CHAR);  


  FUNCTION getadm:  byte;
  (*------------------------------------------------------------------*)
  (*  Gets a byte from the ADM 3A+ terminal port using 'serin'.	      *)
  (*------------------------------------------------------------------*)

    BEGIN (* getadm *)
      getadm := ORD(serin(admunit)) MOD 128;
    END; (* getadm *)



  PROCEDURE putadm(outbyte:  byte);
  (*------------------------------------------------------------------*)
  (*  Puts out 'outbyte' to the ADM 3A+ terminal port using 'serout'. *)
  (*------------------------------------------------------------------*)

    BEGIN (* putadm *)
      serout(admunit, CHR(outbyte));
    END; (* putadm *)



  PROCEDURE handshake;
  (*------------------------------------------------------------------*)
  (*  Handshakes with the ADM 3A+.				      *)
  (*------------------------------------------------------------------*)

    BEGIN (* handshake *)
      putadm(esccode);		(* ask for response *)
      putadm(enqcode);
      WHILE getadm <> eotcode DO; (* sent when ready *)
    END; (* handshake *)



  PROCEDURE penup;
  (*------------------------------------------------------------------*)
  (*  Simply sets the 'penlifted' indicator as 'drawto' takes care of *)
  (* the ADM 3A+ 'pen'.						      *)
  (*------------------------------------------------------------------*)

    BEGIN (* penup *)
      penlifted := TRUE;
    END; (* penup *)



  PROCEDURE pendown;
  (*------------------------------------------------------------------*)
  (*  Simply sets the 'penlifted' indicator as 'drawto' takes care of *)
  (* the ADM 3A+ 'pen'.						      *)
  (*------------------------------------------------------------------*)

    BEGIN (* pendown *)
      penlifted := FALSE;
    END; (* pendown *)



  FUNCTION isinwindow(pt:  apointtype):  BOOLEAN;
  (*------------------------------------------------------------------*)
  (*  Returns TRUE if the 'pt' is within the window defined by	      *)
  (* 'oldleft' and 'oldright'.					      *)
  (*------------------------------------------------------------------*)

    BEGIN (* isinwindow *)
      isinwindow := (pt.x <= oldright.x) AND (pt.y <= oldright.y) AND
      		    (pt.x >= oldleft.x) AND (pt.y >= oldleft.y);
    END; (* isinwindow *)



  PROCEDURE plot(dest:  apointtype;
		 how:  alinetype;
		 endsym:  aendtype);
  (*------------------------------------------------------------------*)
  (*  Plots a line from the present position to 'dest' using the      *)
  (* pattern in 'how' and the end symbol in 'endsym'.  The line is    *)
  (* constrained to stay within the current window.		      *)
  (*------------------------------------------------------------------*)

      VAR
	previous,		(* pattern endpoints *)
	temp:  apointtype;

	i,			(* pattern loop counter *)
	number:  INTEGER;	(* # of patterns in line *)

	dx,			(* pattern repeat spacing *)
	dy,
	len:  REAL;		(* line length *)



    PROCEDURE drawto(dest:  apointtype);
    (*----------------------------------------------------------------*)
    (*	Moves the pen to 'dest' with the pen specified by 'penlifted'.*)
    (*----------------------------------------------------------------*)

      VAR
	temp:  apointtype;	(* window crossing points *)


      PROCEDURE plotpoint(at:  apointtype);
      (*--------------------------------------------------------------*)
      (*  Moves the pen to 'at'.				      *)
      (*--------------------------------------------------------------*)

	VAR
	  xint,			(* integer plotting values, *)
	  yint:  INTEGER;	(* between 0 and 779 or 1023 *)


	BEGIN (* plotpoint *)
	  xint := round(at.x * xfactor);
	  yint := round(at.y * yfactor);

	  putadm(((yint DIV 32) MOD 32) + 32);	(* ms 1/2 of y *)
	  putadm((yint MOD 32) + 96);		(* ls 1/2 of y *)
	  putadm(((xint DIV 32) MOD 32) + 32);	(* ms 1/2 of x *)
	  putadm((xint MOD 32) + 64);		(* ls 1/2 of x *)

	  numsent := numsent + 1;		(* need to handshake? *)
	  IF numsent > maxsent
	    THEN
	      BEGIN
		numsent := 0;
		handshake;
  		putadm(gscode);		(* re-draw to the point *)
		putadm(((yint DIV 32) MOD 32) + 32);	(* ms 1/2 of y *)
		putadm((yint MOD 32) + 96);		(* ls 1/2 of y *)
		putadm(((xint DIV 32) MOD 32) + 32);	(* ms 1/2 of x *)
		putadm((xint MOD 32) + 64);		(* ls 1/2 of x *)
	      END; (* IF numsent ... *)
	END; (* plotpoint *)


      PROCEDURE interpolate(inside,
			    outside:  apointtype;
			    VAR crossing:  apointtype);
      (*--------------------------------------------------------------*)
      (*  Finds the window crossing point on the line between 'inside'*)
      (* and 'outside'.  The result is returned in 'crossing'.	      *)
      (*--------------------------------------------------------------*)



	FUNCTION vertcross(left,
			   right:  apointtype;
			   midx:  REAL):  REAL;
        (*------------------------------------------------------------*)
	(*  Finds the vertical crossing point at 'midx' on the	      *)
	(* line between 'left' and 'right'.			      *)
        (*------------------------------------------------------------*)

	  BEGIN (* vertcross *)
	    vertcross := left.y + (right.y - left.y) *
	    		 (midx - left.x) / (right.x - left.x);
	  END; (* vertcross *)



	FUNCTION horcross(bottom,
			  top:  apointtype;
			  midy:  REAL):  REAL;
        (*------------------------------------------------------------*)
	(*  Finds the horizontal crossing point at 'midy' on the      *)
	(* line between 'bottom' and 'top'.			      *)
        (*------------------------------------------------------------*)

	  BEGIN (* horcross *)
	    horcross := bottom.x + (top.x - bottom.x) *
	    		 (midy - bottom.y) / (top.y - bottom.y);
	  END; (* horcross *)



	BEGIN (* interpolate *)
	  crossing.x := -1;	(* indicate crossing not found *)
	  crossing.y := -1;

	  IF outside.x < oldleft.x	(* is it to the left? *)
	    THEN
	      BEGIN
		crossing.x := oldleft.x;
		crossing.y := vertcross(outside, inside,
		  		     oldleft.x);
	      END (* IF outside ... *)
	  ELSE IF outside.x > oldright.x  (* or to the right? *)
	    THEN
	      BEGIN
		crossing.x := oldright.x;
		crossing.y := vertcross(inside, outside,
		  		     oldright.x);
	      END; (* ELSE IF outside.x ... *)

	  IF NOT isinwindow(crossing)	(* did we not find it yet? *)
	    THEN
	      IF outside.y < oldleft.y  (* is it below? *)
		THEN
		  BEGIN
		    crossing.y := oldleft.y;
		    crossing.x := horcross(outside, inside,
		  			 oldleft.y);
		  END (* IF outside ... *)
	      ELSE IF outside.y > oldright.y  (* or above? *)
		THEN
		  BEGIN
		    crossing.y := oldright.y;
		    crossing.x := horcross(inside, outside,
		  			 oldright.y);
		  END; (* ELSE IF outside.y ... *)
	END; (* interpolate *)



      BEGIN (* drawto *)

	IF penlifted
	  THEN
	    putadm(gscode);

	IF isinwindow(oldpt)
	  THEN
	    IF isinwindow(dest)
	      THEN
		plotpoint(dest)
	      ELSE
		BEGIN
		  interpolate(oldpt, dest, temp);
		  plotpoint(temp);
		END (* ELSE *)
	ELSE IF isinwindow(dest)
	  THEN
	    BEGIN
	      interpolate(dest, oldpt, temp);
	      putadm(gscode);
	      plotpoint(temp);
	      IF penlifted
		THEN
		  putadm(gscode);
	      plotpoint(dest);
	    END; (* ELSE IF ... *)
	oldpt := dest;
      END; (* drawto *)



    PROCEDURE draw(what:  aendtype);
    (*----------------------------------------------------------------*)
    (*	Draws the endpoint symbol 'what' with size 'oldesize'         *)
    (* centered at the present position.   			      *)
    (*----------------------------------------------------------------*)

      VAR
	origin,			(* holds line endpoint *)
	temp:  apointtype;

	oldpenup:  BOOLEAN;	(* holds old penlifted *)



      PROCEDURE closed(initangle:  REAL;
		       npoints:  INTEGER);
      (*--------------------------------------------------------------*)
      (*  Plots a polygon of size 'oldesize' with 'npoints'           *)
      (* corners starting at 'initangle' degrees clockwise from       *)
      (* vertical centered at 'origin'.				      *)
      (*--------------------------------------------------------------*)

        VAR
	  i:  INTEGER;		(* corner counter *)

	  angle:  REAL;		(* angle counter *)


        BEGIN (* closed *)
	  FOR i := 0 to npoints DO
	    BEGIN
	      IF i = 0
	        THEN
		  penup
	      ELSE IF penlifted
	        THEN
		  pendown;
	      angle := (initangle + i * (360.0 / npoints)) / radian;
	      temp.x := origin.x + oldesize * sin(angle);
	      temp.y := origin.y + oldesize * cos(angle);
	      drawto(temp);
	    END; (* FOR i ... *)
        END; (* closed *)



      PROCEDURE open(initangle:  REAL);
      (*--------------------------------------------------------------*)
      (*  Plots a cross of size 'oldesize' with one axis at           *)
      (* 'initangle' clockwise from vertical centered at 'origin'.    *)
      (*--------------------------------------------------------------*)

        VAR
	  i,			(* loop counters *)
	  j:  INTEGER;

	  angle,		(* angle of a line segment *)
	  stangle:  REAL;	(* start angle of a line segment *)


	BEGIN (* open *)
	  FOR j := 0 TO 1 DO
	    BEGIN
	      stangle := initangle + j * 90.0;
	      FOR i := 0 TO 1 DO
		BEGIN
		  IF i = 0
		    THEN
		      penup
		    ELSE
		      pendown;
		  angle := (stangle + i * 180.0) / radian;
		  temp.x := origin.x + oldesize * sin(angle);
		  temp.y := origin.y + oldesize * cos(angle);
		  drawto(temp);
		END; (* FOR i ... *)
	    END; (* FOR j ... *)
	END; (* open *)



      BEGIN (* draw *)
	IF what <> noend
	  THEN
	    BEGIN
	      origin := oldpt;	(* save endpoint *)
	      oldpenup := penlifted;	(* and pen status *)
	      IF what IN [square, triangle, diamond, circle, cross, ecks]
		THEN
		  CASE what OF
		    square:    closed(45.0, 4);
		    triangle:  closed(0.0, 3);
		    diamond:   closed(0.0, 4);
		    circle:    closed(0.0, 10);
		    cross:	 open(0.0);
		    ecks:	 open(45.0);
		  END (* CASE what *)
	      ELSE IF what = point
		THEN
		  BEGIN
		    pendown;
		    putadm(fscode);	(* go to point mode *)
		    drawto(origin);	(* re-draw point *)
		  END; (* ELSE IF what ... *)

	      IF NOT penlifted	(* return to last endpoint *)
		THEN
		  penup;
	      drawto(origin);		(* also resets vector mode *)
	      IF NOT oldpenup
		THEN
		  pendown;
	    END; (* IF what... *)
      END; (* draw *)



    PROCEDURE pltpat(source,
		     dest:  apointtype);
    (*----------------------------------------------------------------*)
    (*  Plots the a line segment of the pattern 'how' from 'source'   *)
    (* to 'dest'.						        *)
    (*----------------------------------------------------------------*)

      VAR
	p1,			(* pattern fraction endpoints *)
	p2,
	p3:  apointtype;

	dx,			(* pattern fraction increment *)
	dy:  REAL;


      BEGIN (* pltpat *)
	CASE how OF
	  dotted:	    BEGIN
			      IF NOT penlifted
				THEN
				  penup;
			      drawto(dest);
			      draw(point);
			    END; (* dotted: *)
	  dashed:	    BEGIN
			      p1.x := (source.x + dest.x) / 2.0;
			      p1.y := (source.y + dest.y) / 2.0;
			      IF NOT penlifted
				THEN
				  penup;
			      drawto(p1);
			      pendown;
			      drawto(dest);
			    END; (* dashed: *)
	  dotdashed:	    BEGIN
			      dx := (dest.x - source.x) / 3.0;
			      dy := (dest.y - source.y) / 3.0;
			      p1.x := source.x + dx;
			      p2.x := p1.x + dx;
			      p1.y := source.y + dy;
			      p2.y := p1.y + dy;
			      IF NOT penlifted
				THEN
				  penup;
			      drawto(p1);
			      draw(point);
			      drawto(p2);
			      pendown;
			      drawto(dest);
			    END; (* dotdashed: *)
	  dotdotdashed:	    BEGIN
			      dx := (dest.x - source.x) / 4.0;
			      dy := (dest.y - source.y) / 4.0;
			      p1.x := source.x + dx;
			      p2.x := p1.x + dx;
			      p3.x := p2.x + dx;
			      p1.y := source.y + dy;
			      p2.y := p1.y + dy;
			      p3.y := p2.y + dy;
			      IF NOT penlifted
				THEN
				  penup;
			      drawto(p1);
			      draw(point);
			      drawto(p2);
			      draw(point);
			      drawto(p3);
			      pendown;
			      drawto(dest);
			    END; (* dotdotdashed: *)
	  END; (* CASE how *)
	END; (* pltpat *)



    BEGIN (* plot *)
      IF dest.x > absmaxx
	THEN
	  dest.x := absmaxx
      ELSE IF dest.x < (0 - absmaxx)
	THEN
	  dest.x := (0 - absmaxx);
      IF dest.y > absmaxy
	THEN
	  dest.y := absmaxy
      ELSE IF dest.y < (0 - absmaxy)
	THEN
	  dest.y := (0 - absmaxy);

      IF how IN [noline, solid]		(* segmenting not needed *)
	THEN
	  BEGIN
	    IF how = noline
	      THEN
		penup
	      ELSE
		pendown;
	    drawto(dest);
	  END (* IF how ... *)
	ELSE
	  BEGIN
	    len := sqr(oldpt.x - dest.x) + sqr(oldpt.y - dest.y);
	    IF len > 0.25		(* make sure won't bomb on *)
	      THEN			(* underflow *)
		len := sqrt(len)
	      ELSE
		len := 0;
	    number := 1 + trunc(len / oldrptlength);
	    dx := (dest.x - oldpt.x) / number;
	    dy := (dest.y - oldpt.y) / number;
	    previous := oldpt;
	    FOR i := 1 TO number DO
	      BEGIN
		temp.x := previous.x + dx;
		temp.y := previous.y + dy;
		pltpat(previous, temp);
		previous := temp;
	      END; (* FOR i ... *)
	  END; (* ELSE *)

      draw(endsym);
    END; (* plot *)



  FUNCTION abadcall:  BOOLEAN;
  (*------------------------------------------------------------------*)
  (*  Returns TRUE if something is wrong with a procedure call.  Not  *)
  (* implemented.						      *)
  (*------------------------------------------------------------------*)

    BEGIN (* abadcall *)
      abadcall := FALSE;
    END; (* abadcall *)



  PROCEDURE adefault;
  (*------------------------------------------------------------------*)
  (*  Sets all globals to their default values.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* adefault *)
      oldend := noend;		(* simple line *)
      oldesize := 3.0;
      oldrptlength := 5.0;
      oldline := noline;
      oldleft.x := minx;		(* window size to full screen *)
      oldleft.y := miny;
      oldright.x := maxx;
      oldright.y := maxy;
    END; (* adefault *)



  PROCEDURE agraph;
  (*------------------------------------------------------------------*)
  (*  Sets the  ADM 3A+ into the vector graphics mode and clears the  *)
  (* screen. 							      *)
  (*------------------------------------------------------------------*)

    BEGIN (* agraph *)
      putadm(gscode);		(* enter vector mode *)
      putadm(emcode);		(* clear the screen *)
      handshake;
    END; (* agraph *)



  PROCEDURE ainit;
  (*------------------------------------------------------------------*)
  (*  Initializes the I/O to the ADM 3A+ terminal, the character set, *)
  (* sets all paramters to their default values, and places the pen   *)
  (* in the lower left-hand corner.				      *)  
  (*------------------------------------------------------------------*)

    BEGIN (* ainit *)
      adefault;
      agraph;
      oldpt.x := 0;
      oldpt.y := 0;
      numsent := 0;
      plot(oldpt, noline, noend);
    END; (* ainit *)



  PROCEDURE amakecopy;
  (*------------------------------------------------------------------*)
  (*  Initiates a copy cycle from the ADM 3A+ to the Graphx printer   *)
  (* and waits for it to finish.				      *)
  (*------------------------------------------------------------------*)

    BEGIN (* amakecopy *)
      putadm(esccode);		(* send copy command *)
      putadm(etbcode);
      handshake;
    END; (* amakecopy *)



  PROCEDURE aplot(endpoint:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Plots to 'endpoint' using line type 'oldline' and end type      *)
  (* 'oldend'.							      *)
  (*------------------------------------------------------------------*)

    BEGIN (* aplot *)
      plot(endpoint, oldline, oldend);
    END; (* aplot *)



  PROCEDURE asetplot(line:  alinetype;
                     repeatlength,
		     endsize:  REAL;
		     endsymbol:  aendtype);
  (*------------------------------------------------------------------*)
  (*  Sets the characteristics of the line(s) to be plotted next.     *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetplot *)
      oldline := line;
      oldrptlength := repeatlength;
      oldesize := endsize;
      oldend := endsymbol;
    END; (* asetplot *)



  PROCEDURE asetstr(charheight,
                    charwidth,
		    charangle,
		    charspace,
		    strangle:  REAL);
  (*------------------------------------------------------------------*)
  (*  Sets the characteristics of the next string(s) to be plotted.   *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetstr *)		(* do nothing in this implementation *)
    END; (* asetstr *)



  PROCEDURE asetwindow(lowerleft,
                       upperright:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Sets the window size.  Nothing will appear outside this window. *)
  (*------------------------------------------------------------------*)

    BEGIN (* asetwindow *)
      oldleft := lowerleft;
      oldright := upperright;
      IF lowerleft.x < minx
	THEN
	  oldleft.x := minx;
      IF lowerleft.y < miny
	THEN
	  oldleft.y := miny;
      IF upperright.x > maxx
	THEN
	  upperright.x := maxx;
      IF upperright.y > maxy
	THEN
	  upperright.y := maxy;
    END; (* asetwindow *)



  PROCEDURE asize(VAR size:  apointtype);
  (*------------------------------------------------------------------*)
  (*  Returns the size of the display in mm.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* asize *)
      size.x := maxx - minx;
      size.y := maxy - miny;
    END; (* asize *)



  PROCEDURE astr(str:  astringtype);
  (*------------------------------------------------------------------*)
  (*  Writes the null-terminated string 'str' on the screen starting  *)
  (* at the current location (taken as the lower left corner of the   *)
  (* first character) and proceeding at 'oldstrangle'.  The	      *)
  (* character angle, height, width and spacing is 'oldchangle',      *)
  (* 'oldchheight', 'oldchwidth' and 'oldchspace'.		      *)
  (* 								      *)
  (*  In this implementation, the 4010 mode of text writing is        *)
  (* used, so only one size of upper case characters at a horizontal  *)
  (* direction is possible; all the above variables are ignored.      *)
  (*------------------------------------------------------------------*)

    VAR
      index:  INTEGER;


    BEGIN (* astr *)
      handshake;
      putadm(uscode);	(* enter 4010 alpha mode *)

      index := 1;	(* put out characters *)
      WHILE ord(str[index]) <> nulcode DO
	BEGIN
	  putadm(ord(str[index]));
	  index := index + 1;
	END; (* WHILE str... *)

      putadm(gscode);		   (* re-enter vector mode *)
      handshake;
    END; (* astr *)



  PROCEDURE atext;
  (*------------------------------------------------------------------*)
  (*  Sets the display to the text mode.			      *)
  (*------------------------------------------------------------------*)

    BEGIN (* atext *)
      putadm(uscode);		(* enter 4010 alpha mode *)
      putadm(cancode);		(* and then to ADM 3A alpha mode *)
    END; (* atext *)



  PROCEDURE awhere(VAR where:  apointtype;
                   VAR inwindow:  BOOLEAN);
  (*------------------------------------------------------------------*)
  (*  Returns the current locatation and whether it's inside the      *)
  (* window.							      *)
  (*------------------------------------------------------------------*)

    BEGIN (* awhere *)
      where := oldpt;
      inwindow := isinwindow(oldpt);
    END; (* awhere *)
modend.
