-- File: SVDrawImpl.mesa
-- Last edited by Bier on December 18, 1982 1:32 am
-- Author: Eric Bier on October 19, 1982 3:10 pm
-- Contents: Some convenience functions to draw shapes not directly available in Cedar Graphics

DIRECTORY
 Graphics,
 GraphicsColor,
 Real,
 RealFns,
 SV2d,
 SVDraw,
 SVLines2d,
 SVVector2d;

SVDrawImpl: PROGRAM
IMPORTS Graphics, Real, RealFns, SVLines2d, SVVector2d
EXPORTS SVDraw =

BEGIN

Point2d: TYPE = SV2d.Point2d;

globalSandwich: Graphics.Path ← Graphics.NewPath[2];

Circle: PUBLIC PROC [dc: Graphics.Context, originX, originY, radius: REAL] = {
-- draw 1/8 of a circle carefully and get the rest by mirroring around vertical, horizontal, and 45 degree axes.
-- as an exercise in flatness tests, I declared that a straight line adequately approximates an arc of the circle if the maximum deviation of the line from the circle is less than z pixel heights. Given a circle of radius r (r in pixel heights), an arc of angle theta, starts and ends on a line of distance r*cos(theta/2) from the origin, reaches a maximum distance r-r*cos(theta/2) from this line and returns. Hence, we must chose theta so that r*(1-cos(theta/2) < z. Theta is clearly less than 90 degrees so 0 < cos(theta/2) < 1, hence 0 < (1 - cos(theta/2)) < 1. Also, r >0, so
-- (1-cos(theta/2) < z/r. -cos(theta/2) < z/r - 1. cos(theta/2) > (1 - z/r). Theta/2 < ArcCos[1 - z/r]. Theta < 2*ArcCos[1 - z/r]. Let z = 0.5.
 cosine, sine, rcosine, rsine, lastRsine, lastRcosine: REAL;
 z: REAL = 0.5;
 deltaTheta, theta, lastTheta: REAL;
 numberOfTimesGoesIn: NAT;
 cosine ← 1-z/radius;
 sine ← RealFns.SqRt[1-cosine*cosine];
 deltaTheta ← RealFns.ArcTanDeg[sine, cosine];
-- Now find a deltaTheta smaller than this which goes evenly into 45 degrees.
 numberOfTimesGoesIn ← Real.FixC[45.0/deltaTheta];
 deltaTheta ← 45.0/(numberOfTimesGoesIn + 1);
 lastTheta ← 0;
 lastRsine ← 0;
 lastRcosine ← radius;

FOR i: NAT IN [1..numberOfTimesGoesIn+1] DO
  theta ← lastTheta + deltaTheta;
  rsine ← radius*RealFns.SinDeg[theta];
  rcosine ← radius*RealFns.CosDeg[theta];
  DrawLine[dc, originX + lastRcosine,  originY + lastRsine,
       originX + rcosine,    originY + rsine];
  DrawLine[dc, originX + lastRsine,  originY + lastRcosine,
       originX + rsine,   originY + rcosine];
  DrawLine[dc, originX + lastRcosine,  originY - lastRsine,
       originX + rcosine,   originY - rsine];
  DrawLine[dc, originX + lastRsine,  originY - lastRcosine,
       originX + rsine,   originY - rcosine];
  DrawLine[dc, originX - lastRcosine,  originY + lastRsine,
       originX - rcosine,   originY + rsine];
  DrawLine[dc, originX - lastRsine,  originY + lastRcosine,
       originX - rsine,   originY + rcosine];
  DrawLine[dc, originX - lastRcosine,  originY - lastRsine,
       originX - rcosine,   originY - rsine];
  DrawLine[dc, originX - lastRsine,  originY - lastRcosine,
       originX - rsine,   originY - rcosine];
  lastTheta ← theta;
  lastRsine ← rsine;
  lastRcosine ← rcosine;
ENDLOOP;
 }; -- end of Circle

DrawLine: PROC [dc: Graphics.Context, fromX, fromY, toX, toY: REAL] = {
 Graphics.SetCP[dc, fromX, fromY];
 Graphics.DrawTo[dc, toX, toY];
 };

Cross: PUBLIC PROC [dc: Graphics.Context, originX, originY, length: REAL] = {
 halfLength: REAL ← length/2.0;
 DrawLine[dc, originX-halfLength, originY, originX+halfLength, originY];
 DrawLine[dc, originX, originY-halfLength, originX, originY+halfLength];
 };

centerLine: SV2d.TrigLine ← SVLines2d.CreateEmptyTrigLine[];

LineSandwich: PUBLIC PROC [dc: Graphics.Context, fromX, fromY, toX, toY: REAL] = {
 leftFirst, leftSecond, rightFirst, rightSecond: Point2d;
-- Draw three parallel lines, 2 black with 1 white in between so that the total width is three screen dots. This requires that we calculate the points with are 1 unit from the given central line segment and normal to that line segment.
-- find the left and right segments given the center segment
 SVLines2d.FillTrigLineFromPoints[[fromX, fromY], [toX, toY], centerLine];
 leftFirst ← SVLines2d.PointLeftOfTrigLine[1, [fromX, fromY], centerLine];
 leftSecond ← SVLines2d.PointLeftOfTrigLine[1, [toX, toY], centerLine];
 rightFirst ← SVVector2d.Sum[SVVector2d.Difference[[fromX, fromY], leftFirst], [fromX, fromY]];
 rightSecond ← SVVector2d.Sum[SVVector2d.Difference[[toX, toY], leftSecond], [toX, toY]];
-- now draw the left line
 Graphics.SetColor[dc, GraphicsColor.black]; -- black
 Graphics.MoveTo[globalSandwich, leftFirst[1], leftFirst[2]];
 Graphics.LineTo[globalSandwich, leftSecond[1], leftSecond[2]];
 Graphics.DrawStroke[dc, globalSandwich, 1, FALSE, butt];
-- now draw the center line
 Graphics.SetColor[dc, GraphicsColor.white]; -- white
 Graphics.MoveTo[globalSandwich, fromX, fromY];
 Graphics.LineTo[globalSandwich, toX, toY];
 Graphics.DrawStroke[dc, globalSandwich, 1, FALSE, butt];
-- finally, draw the right line
 Graphics.SetColor[dc, GraphicsColor.black]; -- dark gray
 Graphics.MoveTo[globalSandwich, rightFirst[1], rightFirst[2]];
 Graphics.LineTo[globalSandwich, rightSecond[1], rightSecond[2]];
 Graphics.DrawStroke[dc, globalSandwich, 1, FALSE, butt];
 };

END.