File: PadGraphicsImpl.mesa
Last edited by Bier on May 31, 1984 3:29:44 pm PDT
Author: Eric Bier on February 4, 1987 7:32:14 pm PST
Contents: Simple 2d graphics package for the scratchpad
DIRECTORY
Imager, ImagerPath, PadGraphics, Real, RealFns, SV2d, SVLines2d, SVVector2d;
PadGraphicsImpl: CEDAR PROGRAM
IMPORTS Imager, ImagerPath, Real, RealFns, SVLines2d, SVVector2d
EXPORTS PadGraphics =
BEGIN
Path: TYPE = SV2d.Path;
Point2d: TYPE = SV2d.Point2d;
Polygon: TYPE = SV2d.Polygon;
Vector2d: TYPE = SV2d.Vector2d;
squareSide: REAL ← 6.0;
globalOutline: Imager.Trajectory;
ScreenToPad:
PUBLIC
PROC [screenPoint: Point2d, origin: Point2d, scalar: REAL ← 1]
RETURNS [padPoint: Point2d] = {
origin is the pad origin in screen coordintates
padPoint[1] ← screenPoint[1] - origin[1];
padPoint[2] ← screenPoint[2] - origin[2];
padPoint ← SVVector2d.Scale[padPoint, 1/scalar];
};
PadToScreen:
PUBLIC
PROC [padPoint: Point2d, origin: Point2d, scalar: REAL ← 1]
RETURNS [screenPoint: Point2d] = {
padPoint ← SVVector2d.Scale[padPoint, scalar];
screenPoint[1] ← padPoint[1] + origin[1];
screenPoint[2] ← padPoint[2] + origin[2];
};
MoveTo:
PUBLIC
PROC [dc: Imager.Context, padPoint: Point2d, origin: Point2d] = {
screenPoint: Point2d;
screenPoint ← PadToScreen[padPoint, origin];
globalOutline ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
};
DrawTo:
PUBLIC
PROC [dc: Imager.Context, padPoint: Point2d, origin: Point2d] = {
screenPoint: Point2d;
screenPoint ← PadToScreen[padPoint, origin];
globalOutline ← ImagerPath.LineTo[globalOutline, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, square];
Imager.MaskStrokeTrajectory[dc, globalOutline];
};
MirrorMoveTo:
PUBLIC
PROC [dc: Imager.Context, padPoint: Point2d, origin: Point2d] = {
screenPoint: Point2d;
padPoint[1] ← -padPoint[1];
screenPoint ← PadToScreen[padPoint, origin];
globalOutline ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
};
MirrorDrawTo:
PUBLIC
PROC [dc: Imager.Context, padPoint: Point2d, origin: Point2d] = {
screenPoint: Point2d;
padPoint[1] ← -padPoint[1];
screenPoint ← PadToScreen[padPoint, origin];
globalOutline ← ImagerPath.LineTo[globalOutline, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, square];
Imager.MaskStrokeTrajectory[dc, globalOutline];
};
DrawPathNeighborHood:
PUBLIC
PROC [dc: Imager.Context, path: Path, index:
NAT, origin: Point2d] = {
draw three points and 2 edges corresponding to the neighborhood of path[index];
path is in pad coordinates.
if index is one of the two ends of the path then draw 2 points and 1 edge.
DrawSquare[dc, squareSide, path[index], origin];
IF index>0
THEN {
MoveTo[dc, path[index-1], origin];
DrawTo[dc, path[index], origin];
};
IF index<path.len-1
THEN {
MoveTo[dc, path[index+1], origin];
DrawTo[dc, path[index], origin];
};
};
MirrorDrawPathNeighborHood:
PUBLIC
PROC [dc: Imager.Context, path: Path, index:
NAT, origin: Point2d] = {
draw three points and 2 edges corresponding to the neighborhood of path[index];
path is in pad coordinates.
if index is one of the two ends of the path then draw 2 points and 1 edge.
MirrorDrawSquare[dc, squareSide, path[index], origin];
IF index>0
THEN {
MirrorMoveTo[dc, path[index-1], origin];
MirrorDrawTo[dc, path[index], origin];
};
IF index<path.len-1
THEN {
MirrorMoveTo[dc, path[index+1], origin];
MirrorDrawTo[dc, path[index], origin];
};
};
DrawPolyNeighborHood:
PUBLIC PROC [dc: Imager.Context, poly: Polygon, index:
NAT, origin: Point2d] = {
draw three points and 2 edges corresponding to the neighborhood of poly[index];
poly is in pad coordinates.
poly.len = 2 then draw both points and the edge joining them.
if poly.len = 1 then just draw a point.
indexPlusOne, indexMinusOne: NAT;
indexPlusOne ← IF index = poly.len-1 THEN 0 ELSE index + 1;
indexMinusOne ← IF index = 0 THEN poly.len-1 ELSE index - 1;
DrawSquare[dc, squareSide, poly[index], origin];
IF poly.len = 1 THEN RETURN;
MoveTo[dc, poly[indexPlusOne], origin];
DrawTo[dc, poly[index], origin];
IF poly.len = 2 THEN RETURN;
MoveTo[dc, poly[indexMinusOne], origin];
DrawTo[dc, poly[index], origin];
};
MirrorDrawPolyNeighborHood:
PUBLIC PROC [dc: Imager.Context, poly: Polygon, index:
NAT, origin: Point2d] = {
draw three points and 2 edges corresponding to the neighborhood of poly[index];
poly is in pad coordinates.
poly.len = 2 then draw both points and the edge joining them.
if poly.len = 1 then just draw a point.
indexPlusOne, indexMinusOne: NAT;
indexPlusOne ← IF index = poly.len-1 THEN 0 ELSE index + 1;
indexMinusOne ← IF index = 0 THEN poly.len-1 ELSE index - 1;
MirrorDrawSquare[dc, squareSide, poly[index], origin];
IF poly.len = 1 THEN RETURN;
MirrorMoveTo[dc, poly[indexPlusOne], origin];
MirrorDrawTo[dc, poly[index], origin];
IF poly.len = 2 THEN RETURN;
MirrorMoveTo[dc, poly[indexMinusOne], origin];
MirrorDrawTo[dc, poly[index], origin];
};
DrawPath:
PUBLIC
PROC [dc: Imager.Context, path: Path, origin: Point2d, scalar:
REAL ← 1] = {
poly is in Pad coordinates
transform each point to screen coordinates and draw using Imager.
outline: Imager.Trajectory;
← Graphics.NewPath[path.len];
screenPoint: Point2d;
IF path.len <= 1 THEN RETURN;
screenPoint ← PadToScreen[path[0], origin, scalar];
outline ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
FOR i:
NAT
IN [1..path.len)
DO
screenPoint ← PadToScreen[path[i], origin, scalar];
outline ← ImagerPath.LineTo[outline, [screenPoint[1], screenPoint[2]]];
ENDLOOP;
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, round];
Imager.MaskStrokeTrajectory[dc, outline];
};
DrawPolygon:
PUBLIC
PROC [dc: Imager.Context, poly: Polygon, origin: Point2d, scalar: REAL ← 1] = {
poly is in Pad coordinates
transform each point to screen coordinates and draw using Imager.
screenPoint0, screenPoint1, screenPoint2: Point2d;
IF poly.len <= 1 THEN RETURN;
screenPoint0 ← screenPoint1 ← PadToScreen[poly[0], origin, scalar];
FOR i:
NAT
IN [1..poly.len)
DO
screenPoint2 ← PadToScreen[poly[i], origin, scalar];
LineSandwich[dc, screenPoint1[1], screenPoint1[2], screenPoint2[1], screenPoint2[2]];
screenPoint1 ← screenPoint2;
ENDLOOP;
LineSandwich[dc, screenPoint1[1], screenPoint1[2], screenPoint0[1], screenPoint0[2]];
};
CrossHairs:
PUBLIC
PROC [dc: Imager.Context, origin: Point2d] = {
leftPoint, rightPoint, topPoint, bottomPoint, screenPoint: Point2d;
leftPoint ← [-1000, 0];
rightPoint ← [1000, 0];
topPoint ← [0, 1000];
bottomPoint ← [0, -1000];
screenPoint ← PadToScreen[leftPoint, origin];
globalOutline ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[rightPoint, origin];
globalOutline ← ImagerPath.LineTo[globalOutline, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, square];
Imager.MaskStrokeTrajectory[dc, globalOutline];
screenPoint ← PadToScreen[topPoint, origin];
globalOutline ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[bottomPoint, origin];
globalOutline ← ImagerPath.LineTo[globalOutline, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, square];
Imager.MaskStrokeTrajectory[dc, globalOutline];
};
DrawSquare:
PUBLIC
PROC [dc: Imager.Context, side:
REAL, center: Point2d, origin: Point2d] = {
delta: REAL ← side/2.0;
screenPoint: Point2d;
square: Imager.Trajectory;
square: Graphics.Path ← Graphics.NewPath[4];
screenPoint ← PadToScreen[[center[1]-delta, center[2]-delta], origin];
square ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]-delta, center[2]+delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]+delta, center[2]+delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]+delta, center[2]-delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.MaskStrokeTrajectory[dc, square, TRUE];
};
MirrorDrawSquare:
PUBLIC
PROC [dc: Imager.Context, side:
REAL, center: Point2d, origin: Point2d] = {
delta: REAL ← side/2.0;
screenPoint: Point2d;
square: Imager.Trajectory;
square: Graphics.Path ← Graphics.NewPath[4];
center[1] ← -center[1];
screenPoint ← PadToScreen[[center[1]-delta, center[2]-delta], origin];
square ← ImagerPath.MoveTo[[screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]-delta, center[2]+delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]+delta, center[2]+delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
screenPoint ← PadToScreen[[center[1]+delta, center[2]-delta], origin];
square ← ImagerPath.LineTo[square, [screenPoint[1], screenPoint[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.MaskStrokeTrajectory[dc, square, TRUE];
};
Draw2dVector:
PUBLIC
PROC [dc: Imager.Context, vec: Vector2d, at: Point2d, origin: Point2d] = {
unitVec, vec50: Vector2d;
mag: REAL;
mag ← RealFns.SqRt[vec[1]*vec[1] + vec[2]*vec[2]];
unitVec[1] ← vec[1]/mag;
unitVec[2] ← vec[2]/mag;
vec50[1] ← unitVec[1]*50;
vec50[2] ← unitVec[2]*50;
MoveTo[dc, at, origin];
DrawTo[dc, [at[1]+vec50[1], at[2]+vec50[2]], origin];
}; -- end of Draw2dVector
Circle:
PUBLIC
PROC [dc: Imager.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.Fix[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
centerLine: SV2d.TrigLine ← SVLines2d.CreateEmptyTrigLine[];
LineSandwich:
PUBLIC
PROC [dc: Imager.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.Add[SVVector2d.Sub[[fromX, fromY], leftFirst], [fromX, fromY]];
rightSecond ← SVVector2d.Add[SVVector2d.Sub[[toX, toY], leftSecond], [toX, toY]];
Now draw the left line
Imager.SetColor[dc, Imager.black]; -- black
globalOutline ← ImagerPath.MoveTo[[leftFirst[1], leftFirst[2]]];
globalOutline ← ImagerPath.LineTo[globalOutline, [leftSecond[1], leftSecond[2]]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, butt];
Imager.MaskStrokeTrajectory[dc, globalOutline];
Now draw the center line
Imager.SetColor[dc, Imager.white]; -- white
globalOutline ← ImagerPath.MoveTo[[fromX, fromY]];
globalOutline ← ImagerPath.LineTo[globalOutline, [toX, toY]];
Imager.MaskStrokeTrajectory[dc, globalOutline];
Finally, draw the right line
Imager.SetColor[dc, Imager.black]; -- black
globalOutline ← ImagerPath.MoveTo[[rightFirst[1], rightFirst[2]]];
globalOutline ← ImagerPath.LineTo[globalOutline, [rightSecond[1], rightSecond[2]]];
Imager.MaskStrokeTrajectory[dc, globalOutline];
};
DrawLine:
PRIVATE PROC [dc: Imager.Context, fromX, fromY, toX, toY:
REAL] = {
Just a private utility used by Circle.
globalOutline ← ImagerPath.MoveTo[[fromX, fromY]];
globalOutline ← ImagerPath.LineTo[globalOutline, [toX, toY]];
Imager.SetStrokeWidth[dc, 1];
Imager.SetStrokeEnd[dc, square];
Imager.MaskStrokeTrajectory[dc, globalOutline];
};
END.