File: Lines2dImpl.mesa
Copyright Ó 1992 by Xerox Corporation. All rights reserved.
Author: Eric Bier on June 4, 1985 5:04:38 pm PDT
Last edited by Bier on June 24, 1987 11:34:21 am PDT
Contents: Routines for finding the intersections of various types of lines and line segments in Gargoyle.
Pier, August 8, 1986 12:14:59 pm PDT
Bier, March 21, 1988 12:02:31 pm PST
Michael Plass, March 25, 1992 11:47 am PST
DIRECTORY
Angles2d, Imager, ImagerTransformation, Lines2d, Lines2dTypes, Real, RealFns, Vectors2d;
Lines2dImpl:
CEDAR
PROGRAM
IMPORTS Angles2d, Imager, ImagerTransformation, RealFns, Vectors2d
EXPORTS Lines2d =
BEGIN
Point: TYPE = Lines2dTypes.Point;
Edge: TYPE = REF EdgeObj;
EdgeObj: TYPE = Lines2dTypes.EdgeObj;
Line: TYPE = REF LineObj;
LineObj: TYPE = Lines2dTypes.LineObj;
Ray: TYPE = REF RayObj;
RayObj: TYPE = Lines2dTypes.RayObj;
Vector: TYPE = Lines2dTypes.Vector;
Making Lines
CreateEmptyLine:
PUBLIC
PROC
RETURNS [line: Line] = {
line ¬ NEW[LineObj];
};
CopyLine:
PUBLIC
PROC [from: Line, to: Line] = {
to.c ¬ from.c;
to.s ¬ from.s;
to.theta ¬ from.theta;
to.d ¬ from.d;
to.slope ← from.slope;
to.yInt ← from.yInt;
};
EqualLine:
PUBLIC
PROC [a: Line, b: Line]
RETURNS [
BOOL] = {
Are these lines mathematically identical?
RETURN[a.d = b.d AND a.theta = b.theta];
};
AlmostEqualLine:
PUBLIC
PROC [a: Line, b: Line, errorDegrees:
REAL, errorDistance:
REAL]
RETURNS [
BOOL] = {
Returns TRUE if the lines differ in slope by no more than errorDegrees and differ in distance from the origin by no more than errorDistance.
RETURN[ABS[a.theta - b.theta] < errorDegrees AND ABS[a.d-b.d] < errorDistance];
};
FillLineFromPoints:
PUBLIC
PROC [v1, v2: Point, line: Line] = {
Recall y*c - x*s - d = 0;
Calculates the different parts of a line given an ordered pair of points (the tail and the head). Trig lines are directed in sense since 0 <= line.theta <= 180 implies that v1 is lower than or to the right of) v2.
epsilon:
REAL = 1.0e-3; -- changed from
1.0e-5 in August 1986, by Bier
-- changed from 1.0e-6 in August 1985 by Bier
Deal with very short and vertical segments
x2Minusx1: REAL ¬ v2.x - v1.x;
y2Minusy1: REAL ¬ v2.y - v1.y;
Notice that zero length lines are considered vertical.
IF
ABS[x2Minusx1] < epsilon
THEN {
-- vertical line
IF v2.y > v1.y
THEN {
-- line goes up
line.theta ¬ 90.0;
line.s ¬ 1;
we have -x*s = d. where s = 1. Plug in v1. -v1.x*s = d
line.d ¬ -v1.x}
ELSE {
-- line goes down
line.theta ¬ -90;
line.s ¬ -1;
we have -x*s = d. where s = -1. Plug in v1. -v1.x*s = d
line.d ¬ v1.x};
line.c ¬ 0;
}
Otherwise, use trig functions.
ELSE {
line.theta ¬ RealFns.ArcTanDeg[y2Minusy1, x2Minusx1];
line.c ¬ RealFns.CosDeg[line.theta];
line.s ¬ RealFns.SinDeg[line.theta];
d ← y1c - x1s. Subsitute in a point to find d.
line.d ¬ v1.y*line.c - v1.x*line.s;
};
}; -- end of FillLineFromPoints
VectorTooSmall: PUBLIC SIGNAL = CODE;
FillLineFromPointAndVector:
PUBLIC
PROC [pt: Point, vec: Vector, line: Line] = {
epsilon: REAL = 1.0e-3;
IF
ABS[vec.x] < epsilon
AND
ABS[vec.y] < epsilon
THEN {
SIGNAL VectorTooSmall;
line.theta ¬ 90.0;
}
ELSE line.theta ¬ RealFns.ArcTanDeg[vec.y, vec.x];
line.c ¬ RealFns.CosDeg[line.theta];
line.s ¬ RealFns.SinDeg[line.theta];
line.d ¬ pt.y*line.c - pt.x*line.s;
};
FillLineFromCoefficients:
PUBLIC
PROC [sineOfTheta, cosineOfTheta, distance:
REAL, line: Line] = {
recall y*c - x*s - d = 0;
Calculates the different parts of a line given c, s and d.
line.s ¬ sineOfTheta;
line.c ¬ cosineOfTheta;
line.d ¬ distance;
line.theta ¬ RealFns.ArcTanDeg[sineOfTheta, cosineOfTheta];
IF cosineOfTheta # 0 THEN { -- find its slope and y intercept.
line.slope ← sineOfTheta/cosineOfTheta;
y intercept occurs when x = 0, ie when y*c = d. y = d/c;
line.yInt ← line.d/line.c};
}; -- end of FillLineFromCoefficients
FillLineFromPointAndAngle:
PUBLIC
PROC [pt: Point, degrees:
REAL, line: Line] = {
line.theta ¬ Angles2d.Normalize[degrees];
line.c ¬ RealFns.CosDeg[line.theta];
line.s ¬ RealFns.SinDeg[line.theta];
line.d ¬ pt.y*line.c - pt.x*line.s;
}; -- end of FillLineFromPointAndAngle
FillLineNormalToLineThruPoint:
PUBLIC
PROC [line: Line, pt: Point, normalLine: Line] = {
Find a line which is perpendicular to "line" and passes thru "pt". Useful for dropping perpendiculars.
If line has the form: y*cos(theta) - x*sin(theta) - d = 0, then normalLine will have the form:
y*cos(theta+90) - x*sin(theta+90) - D = 0;
or -y*sin(theta) - (x*cos(theta)) - D = 0;
to find D, we substitute in pt:
D = -pt.y*sin(theta) - pt.x*cos(theta);
normalLine.s ¬ line.c;
normalLine.c ¬ -line.s;
normalLine.d ¬ -pt.y*line.s - pt.x*line.c;
normalLine.theta ¬ Angles2d.Add[line.theta, 90];
IF normalLine.c #0 THEN { -- compute slope and yInt
normalLine.slope ← normalLine.s/normalLine.c;
line.yInt ← normalLine.d/normalLine.c
};
}; -- end of FillLineAsNormal
FillLineLeftOfLine:
PUBLIC
PROC [line: Line, dist:
REAL, parallelLine: Line] = {
parallelLine.s ¬ line.s;
parallelLine.c ¬ line.c;
parallelLine.d ¬ line.d + dist;
parallelLine.theta ¬ line.theta;
parallelLine.slope ← line.slope;
};
FillLineRightOfLine:
PUBLIC
PROC [line: Line, dist:
REAL, parallelLine: Line] = {
parallelLine.s ¬ line.s;
parallelLine.c ¬ line.c;
parallelLine.d ¬ line.d - dist;
parallelLine.theta ¬ line.theta;
parallelLine.slope ← line.slope;
};
FillLineTransform:
PUBLIC
PROC [fixed: Line, transform: ImagerTransformation.Transformation, line: Line] = {
point, newPoint: Point;
direction, newDirection: Vector;
point ¬ PointOnLine[fixed];
direction ¬ DirectionOfLine[fixed];
newPoint ¬ ImagerTransformation.Transform[transform, point];
newDirection ¬ ImagerTransformation.TransformVec[transform, direction];
FillLineFromPointAndVector[newPoint, newDirection, line];
};
LineFromPoints:
PUBLIC
PROC [v1, v2: Point]
RETURNS [line: Line] = {
line ¬ CreateEmptyLine[];
FillLineFromPoints[v1, v2, line];
};
LineFromPointAndVector:
PUBLIC
PROC [pt: Point, vec: Vector]
RETURNS [line: Line] = {
pt2: Point;
pt2 ¬ Vectors2d.Add[pt, vec];
line ¬ LineFromPoints[pt, pt2];
};
LineFromCoefficients:
PUBLIC
PROC [sineOfTheta, cosineOfTheta, distance:
REAL]
RETURNS [line: Line] = {
line ¬ CreateEmptyLine[];
FillLineFromCoefficients[sineOfTheta, cosineOfTheta, distance, line];
};
LineFromPointAndAngle:
PUBLIC
PROC [pt: Point, degrees:
REAL]
RETURNS [line: Line] = {
line ¬ CreateEmptyLine[];
FillLineFromPointAndAngle[pt, degrees, line];
};
LineNormalToLineThruPoint:
PUBLIC
PROC [line: Line, pt: Point]
RETURNS [normalLine: Line] = {
normalLine ¬ CreateEmptyLine[];
FillLineNormalToLineThruPoint[line, pt, normalLine];
};
LineLeftOfLine:
PUBLIC
PROC [line: Line, dist:
REAL]
RETURNS [parallelLine: Line] = {
parallelLine ¬ CreateEmptyLine[];
FillLineLeftOfLine[line, dist, parallelLine];
};
LineRightOfLine:
PUBLIC
PROC [line: Line, dist:
REAL]
RETURNS [parallelLine: Line] = {
parallelLine ¬ CreateEmptyLine[];
FillLineRightOfLine[line, dist, parallelLine];
};
LineTransform:
PUBLIC
PROC [fixed: Line, transform: ImagerTransformation.Transformation]
RETURNS [rotatedLine: Line] = {
Makes a new line that results by transforming line by transform.
rotatedLine ¬ CreateEmptyLine[];
FillLineTransform[fixed, transform, rotatedLine];
};
DrawLine:
PUBLIC
PROC [dc: Imager.Context, line: Line, clippedBy: Imager.Rectangle, strokeWidth:
REAL ¬ 1.0] = {
count: NAT;
ray: Ray;
params: ARRAY[1..2] OF REAL;
p1, p2, basePoint: Point;
direction: Vector;
DoDrawLine:
PROC = {
Imager.SetXY[dc, [p1.x, p1.y]];
Imager.Trans[dc];
Imager.Move[dc];
Imager.SetStrokeEnd[dc, round];
Imager.SetStrokeWidth[dc, strokeWidth];
Imager.MaskVector[dc, [0.0, 0.0], [p2.x - p1.x, p2.y - p1.y]];
Draw2d.Line[dc, [0.0, 0.0], [p2.x - p1.x, p2.y - p1.y], solid];
};
p1 ¬ [clippedBy.x, clippedBy.y];
p2 ¬ [clippedBy.x + clippedBy.w, clippedBy.y + clippedBy.h];
basePoint ¬ PointOnLine[line];
direction ¬ DirectionOfLine[line];
ray ¬ CreateRay[basePoint, direction];
[count, params] ¬ LineRayMeetsBox[ray, p1.x, p1.y, p2.x, p2.y];
IF count = 2
THEN {
p1 ¬ EvalRay[ray, params[1]];
p2 ¬ EvalRay[ray, params[2]];
Imager.DoSave[dc, DoDrawLine];
};
};
Making Edges
CreateEmptyEdge:
PUBLIC
PROC
RETURNS [edge: Edge] = {
edge ¬ NEW[EdgeObj];
edge.line ¬ CreateEmptyLine[];
};
CopyEdge:
PUBLIC
PROC [from: Edge, to: Edge] = {
CopyLine[from.line, to.line];
to.start ¬ from.start;
to.end ¬ from.end;
to.startIsFirst ¬ from.startIsFirst;
}; -- end of CopyEdge
FillEdge:
PUBLIC
PROC [v1, v2: Point, edge: Edge] = {
y2Minusy1: REAL;
FillLineFromPoints[v1, v2, edge.line];
y2Minusy1 ¬ v2.y - v1.y;
IF y2Minusy1 = 0
THEN
IF v2.x > v1.x THEN {edge.end ¬ v2; edge.start ¬ v1; edge.startIsFirst ¬ TRUE}
ELSE {edge.end ¬ v1; edge.start ¬ v2; edge.startIsFirst ¬ FALSE}
ELSE
IF v2.y > v1.y THEN {edge.end ¬ v2; edge.start ¬ v1; edge.startIsFirst ¬ TRUE}
ELSE {edge.end ¬ v1; edge.start ¬ v2; edge.startIsFirst ¬ FALSE};
}; -- end of FillEdge
FillEdgeTransform:
PUBLIC
PROC [fixed: Edge, transform: ImagerTransformation.Transformation, edge: Edge] = {
start, end: Point;
start ¬ ImagerTransformation.Transform[m: transform, v: fixed.start];
end ¬ ImagerTransformation.Transform[m: transform, v: fixed.end];
FillEdge[start, end, edge];
};
CreateEdge:
PUBLIC
PROC [v1, v2: Point]
RETURNS [edge: Edge] = {
edge ¬ CreateEmptyEdge[];
FillEdge[v1, v2, edge];
}; -- end of CreateEdge
EdgeTransform:
PUBLIC
PROC [fixed: Edge, transform: ImagerTransformation.Transformation]
RETURNS [edge: Edge] = {
edge ¬ CreateEmptyEdge[];
FillEdgeTransform[fixed, transform, edge];
};
Making Rays
CreateRay:
PUBLIC
PROC [basePoint: Point, direction: Vector]
RETURNS [ray: Ray] = {
ray ¬ NEW[RayObj ¬ [basePoint, direction]];
};
CreateRayFromPoints:
PUBLIC
PROC [p1, p2: Point]
RETURNS [ray: Ray] = {
ray ¬ NEW[RayObj ¬ [p1, Vectors2d.Sub[p2, p1]]];
};
AlmostEqual:
PROC [r1, r2, almostZero:
REAL]
RETURNS [
BOOL] = {
RETURN[ABS[r1 - r2] < almostZero];
};
LineRayMeetsBox:
PUBLIC
PROC [ray: Ray, xmin, ymin, xmax, ymax:
REAL]
RETURNS [count:
NAT, params:
ARRAY[1..2]
OF
REAL] = {
We can take advantage of the horizontal and vertical lines of the box to do an easy intersection test. Note that we are really testing for line intersections rather than ray intersections.
almostZero: REAL ¬ 1.0e-3;
x, y, t: REAL;
count ¬ 0;
The top line has equation y = ymax. If ray.d.y = 0, we don't hit this line. Otherwise, we use y(t) = ymax = ray.p.y+t*ray.d.y; Solve for t: t = (ymax-ray.p.y)/ray.d.y.
IF ABS[ray.d.y] > almostZero THEN { -- intersection occurs
Top Line
t ¬ (ymax-ray.p.y)/ray.d.y;
x ¬ ray.p.x + t*ray.d.x;
IF x >=xmin-almostZero
AND x<= xmax+almostZero
THEN {
-- hits box
count ¬ count + 1;
params[count] ¬ t;
};
Bottom Line
t ¬ (ymin-ray.p.y)/ray.d.y;
x ¬ ray.p.x + t*ray.d.x;
IF x >=xmin-almostZero
AND x<= xmax+almostZero
THEN {
-- hits box
count ¬ count + 1;
params[count] ¬ t;
};
};
IF ABS[ray.d.x] > almostZero THEN { -- intersection occurs
Right Line
IF count < 2
THEN {
t ¬ (xmax-ray.p.x)/ray.d.x;
IF count = 0
OR (count = 1
AND
NOT AlmostEqual[t, params[1], almostZero])
THEN {
y ¬ ray.p.y + t*ray.d.y;
IF y >=ymin-almostZero
AND y<= ymax+almostZero
THEN {
-- hits box
count ¬ count + 1;
params[count] ¬ t;
};
};
};
Left Line
IF count < 2
THEN {
t ¬ (xmin-ray.p.x)/ray.d.x;
IF count = 0
OR (count = 1
AND
NOT AlmostEqual[t, params[1], almostZero])
THEN {
y ¬ ray.p.y + t*ray.d.y;
IF y >=ymin-almostZero
AND y<= ymax+almostZero
THEN {
-- hits box
count ¬ count + 1;
params[count] ¬ t;
};
};
};
};
IF count = 2
THEN {
IF params[2] < params[1]
THEN {
temp: REAL ¬ params[1];
params[1] ¬ params[2];
params[2] ¬ temp;
};
}; -- make sure hits are sorted
}; -- end of LineRayMeetsBox
EvalRay:
PUBLIC
PROC [ray: Ray, param:
REAL]
RETURNS [point: Point] = {
point.x ¬ ray.p.x + param*ray.d.x;
point.y ¬ ray.p.y + param*ray.d.y;
};
Intersections
AlmostZero:
PROC [r:
REAL]
RETURNS [
BOOL] = {
epsilon: REAL = 1.0e-5;
RETURN[ABS[r] < epsilon];
};
LineMeetsLine:
PUBLIC
PROC [line1, line2: Line]
RETURNS [intersection: Point, parallel:
BOOL] = {
To ensure no errors of more than 0.072 screen dots in a picture of size 14 inches by 14 inches, our angles in radians must be accurate to (theta*1008 < 0.072) theta < 7.142857e-5). In degrees, this is 4.092559e-3. I will use 4e-4 for extra accuracy. e-5 results in determinant = 0.0 for Window.script. (Bier, January 7, 1987)
If line1 is of the form: c1*y - s1*x - d1 = 0;
and line2 of the form: c2*y - s2*x - d2 = 0;
then we solve simultaneously.
x = (c2d1 - c1d2)/(s2c1 - s1c2);
y = (s2d1 - s1d2)/(s2c1 - s1c2);
determinant: REAL;
epsilon: REAL = 4E-4;
parallel ¬ FALSE;
IF Angles2d.AlmostParallel[line1.theta, line2.theta, epsilon]
THEN {
RETURN[intersection: [Real.PlusInfinity, Real.PlusInfinity], parallel: TRUE]
};
determinant ¬ line2.s*line1.c - line1.s*line2.c;
determinant should not be zero since the lines are not parallel.
intersection.x ¬ (line2.c*line1.d - line1.c*line2.d)/determinant;
intersection.y ¬ (line2.s*line1.d - line1.s*line2.d)/determinant;
};
LineMeetsYAxis:
PUBLIC
PROC [line: Line]
RETURNS [yInt:
REAL, parallel:
BOOL] = {
IF line.theta = 90 OR line.theta = -90 THEN parallel ¬ TRUE
ELSE {
-- we just want the y Intercept which is calculated at line creation time for now.
parallel ¬ FALSE;
yInt ¬ line.yInt;}
};
LineMeetsEdge:
PUBLIC
PROC [line: Line, edge: Edge]
RETURNS [intersection: Point, noHit:
BOOL] = {
Find the intersection of line with the line of seg. See if this point is within the bounds of seg.
edgeLine: Line ¬ edge.line;
parallel: BOOL;
[intersection, parallel] ¬ LineMeetsLine[edgeLine, line];
IF parallel THEN {noHit ¬ TRUE; RETURN};
noHit ¬ NOT OnEdge[intersection, edge];
};
EdgeMeetsEdge:
PUBLIC
PROC [e1, e2: Edge]
RETURNS [intersection: Point, noHit:
BOOL] = {
Find the intersection of e1, with e2. See if this point is within the bounds of e1 and e2.
e1Line: Line ¬ e1.line;
[intersection, noHit] ¬ LineMeetsEdge[e1Line, e2];
IF noHit THEN RETURN;
noHit ¬ NOT OnEdge[intersection, e1];
};
Direction and Distance for Lines
SignedLineDistance:
PUBLIC
PROC [pt: Point, line: Line]
RETURNS [d:
REAL] = {
Because of our choice or representation for a Line, plugging the point into the line equation gives us the signed distance.
ie. distance = y*cos(theta) - x*sin(theta) - d;
d ¬ pt.y*line.c - pt.x*line.s - line.d;
}; -- SignedLineDistance
LineDistance:
PUBLIC
PROC [pt: Point, line: Line]
RETURNS [d:
REAL] = {
Because of our choice or representation for a Line, plugging the point into the line equation gives us the signed distance.
ie. distance = y*cos(theta) - x*sin(theta) - d;
d ¬ ABS[pt.y*line.c - pt.x*line.s - line.d];
}; -- LineDistance
DropPerpendicular:
PUBLIC
PROC [pt: Point, line: Line]
RETURNS [projectedPt: Point] = {
We drop a normal from the point onto the line and find where it hits.
The line equation of the normal we drop can be found using FillLineAsNormal above.
We will have line equations:
c*y - s*x - d = 0. The vector v = [c, s] is the unit vector parallel to line. The vector
l = [-s, c] is the unit vector 90 degrees counter-clockwise of v. If pt is distance D from line (along l), then the new point we want is pt-D*l.
This routine takes 4 mults, 4 adds.
D: REAL ¬ pt.y*line.c - pt.x*line.s - line.d;
projectedPt.x ¬ pt.x + D*line.s;
projectedPt.y ¬ pt.y - D*line.c;
};
PointOnLine:
PUBLIC
PROC [line: Line]
RETURNS [pt: Point] = {
Finds any old point on line and returns it.
IF
ABS[line.c] >
ABS[line.s]
THEN {
pt.x ¬ 0.0;
pt.y ¬ line.d/line.c;
}
ELSE {
pt.y ¬ 0.0;
pt.x ¬ -line.d/line.s;
};
};
DirectionOfLine:
PUBLIC
PROC [line: Line]
RETURNS [direction: Vector] = {
Returns the direction vector of line.
direction.x ¬ line.c;
direction.y ¬ line.s;
};
Distance for Edges
NearestEndpoint:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [endpoint: Point] = {
Look for an obvious winner first. If that fails, do math.
IF
ABS[pt.x-edge.start.x] <=
ABS[pt.x-edge.end.x]
THEN
IF ABS[pt.y-edge.start.y] <= ABS[pt.y-edge.end.y] THEN RETURN[edge.start]
ELSE GOTO DoMath
ELSE
IF ABS[pt.y-edge.start.y] > ABS[pt.y-edge.end.y] THEN RETURN[edge.end]
ELSE GOTO DoMath;
EXITS
DoMath =>
IF DistanceSquaredPointToPoint[pt, edge.start] < DistanceSquaredPointToPoint[pt, edge.end]
THEN endpoint ¬ edge.start
ELSE endpoint ¬ edge.end;
};
DistanceSquaredToNearestEndpoint:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [distanceSquared:
REAL] = {
distance2ToPLo, distance2ToPHi: REAL;
distance2ToPLo ¬ DistanceSquaredPointToPoint[pt, edge.start];
distance2ToPHi ¬ DistanceSquaredPointToPoint[pt, edge.end];
RETURN[MIN[distance2ToPLo, distance2ToPHi]];
};
NearestPointOnEdge:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [onEdge: Point] = {
projectedPt: Point ¬ DropPerpendicular[pt, edge.line];
IF LinePointOnEdge[projectedPt, edge] THEN onEdge ¬ projectedPt
ELSE onEdge ¬ NearestEndpoint[pt, edge];
};
DistancePointToEdge:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [distance:
REAL] = {
perpendicular distance if possible, else distance to nearest endpoint.
projectedPt: Point ¬ DropPerpendicular[pt, edge.line];
nearEndpoint: Point;
IF LinePointOnEdge[projectedPt, edge] THEN distance ¬ ABS[LineDistance[pt, edge.line]]
ELSE {
nearEndpoint ¬ NearestEndpoint[pt, edge];
distance ¬ DistancePointToPoint[pt, nearEndpoint];
};
};
DistanceSquaredPointToEdge:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [distanceSquared:
REAL] = {
Perpendicular distance if possible, else distance to nearest endpoint.
projectedPt: Point ¬ DropPerpendicular[pt, edge.line];
IF LinePointOnEdge[projectedPt, edge]
THEN {distanceSquared ¬ LineDistance[pt, edge.line];
distanceSquared ¬ distanceSquared*distanceSquared}
ELSE distanceSquared ¬ DistanceSquaredToNearestEndpoint[pt, edge];
};
OnEdge:
PUBLIC PROC [pt: Point, edge: Edge]
RETURNS [
BOOL] = {
d2: REAL;
d2 ¬ DistanceSquaredPointToEdge[pt, edge];
RETURN[AlmostZero[d2]];
};
LinePointOnEdge:
PUBLIC
PROC [pt: Point, edge: Edge]
RETURNS [
BOOL] = {
Assumes pt is on edge.line. Is it on edge?
IF ABS[edge.end.x - edge.start.x] <= ABS[edge.end.y - edge.start.y] THEN -- line is more vertical or has zero length
RETURN[Between[pt.y, edge.start.y, edge.end.y]]
ELSE -- line is more horizontal
RETURN[Between[pt.x, edge.start.x, edge.end.x]];
}; -- end of LinePointOnEdge
Distance for Points
DistancePointToPoint:
PUBLIC
PROC [p1, p2: Point]
RETURNS [distance:
REAL] = {
distance ¬ RealFns.SqRt[(p2.y-p1.y)*(p2.y-p1.y) + (p2.x-p1.x)*(p2.x-p1.x)];
};
DistanceSquaredPointToPoint:
PUBLIC
PROC [p1, p2: Point]
RETURNS [distance:
REAL] = {
distance ¬ (p2.y-p1.y)*(p2.y-p1.y) + (p2.x-p1.x)*(p2.x-p1.x);
};
PointLeftOfLine:
PUBLIC
PROC [distance:
REAL, pOnLine: Point, line: Line]
RETURNS [point: Point] = {
point is a point to the left of the directed line, on the normal to the line which intersects the line at pOnLine. If distance is negative, the point will be to the right of the directed line.
Method: The point we want will be at the intersection these two lines
1) The line parallel to "line" but distance to its left
2) The line perpendicular to "line" at pOnLine.
We can generate both of these easily as follows:
lineParallel, linePerp: Line;
parallel: BOOL;
lineParallel ¬ CreateEmptyLine[];
linePerp ¬ CreateEmptyLine[];
FillLineFromCoefficients[line.s, line.c, line.d + distance, lineParallel];
FillLineNormalToLineThruPoint[line, pOnLine, linePerp];
[point, parallel] ¬ LineMeetsLine[lineParallel, linePerp];
IF parallel THEN ERROR; -- perpendicular lines are not parallel
};
UTILITY FUNCTIONS
Between:
PRIVATE
PROC [test, a, b:
REAL]
RETURNS [
BOOL] = {
SELECT a
FROM
< b => RETURN [a <= test AND test <= b];
= b => RETURN [test = b];
> b => RETURN [b <= test AND test <= a];
ENDCASE => ERROR;
};
END.