SVLines3dImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Bier on February 18, 1987
Contents: Procedures for performing geometric operations on lines and points in 3-space.
DIRECTORY
SVMatrix3d, RealFns, SV3d, SVLines3d, SVVector3d;
SVLines3dImpl:
CEDAR
PROGRAM
IMPORTS SVMatrix3d, RealFns, SVVector3d
EXPORTS SVLines3d
=
BEGIN
Edge3d: TYPE = SV3d.Edge3d;
Edge3dObj: TYPE = SV3d.Edge3dObj;
Line3d: TYPE = SV3d.Line3d;
Line3dObj: TYPE = SV3d.Line3dObj;
Matrix4by4: TYPE = SV3d.Matrix4by4;
Point3d: TYPE = SV3d.Point3d;
Vector3d: TYPE = SV3d.Vector3d;
Making Lines
CreateEmptyLine:
PUBLIC
PROC []
RETURNS [line: Line3d] = {
line ← NEW[Line3dObj];
};
CopyLine:
PUBLIC
PROC [from: Line3d, to: Line3d] = {
to^ ← from^;
};
EqualLine:
PUBLIC
PROC [a: Line3d, b: Line3d]
RETURNS [
BOOL] = {
RETURN[a.base = b.base AND a.direction = b.direction];
};
AlmostEqualLine:
PUBLIC
PROC [a: Line3d, b: Line3d, errorDegrees:
REAL, errorDistance:
REAL]
RETURNS [
BOOL] = {
angle: REAL ← SVVector3d.AngleCCWBetweenVectors[a.direction, b.direction];
angleOK: BOOL ← ABS[angle] < errorDegrees OR ABS[angle-180.0] < errorDegrees;
IF NOT angleOK THEN RETURN[FALSE];
RETURN[
ABS[
DistancePointToLine[[0.0, 0.0, 0.0], a] -
DistancePointToLine[[0.0, 0.0, 0.0], b]] < errorDistance
];
};
LineFromPoints:
PUBLIC
PROC [v1, v2: Point3d]
RETURNS [line: Line3d] = {
line ← CreateEmptyLine[];
FillLineFromPoints[v1, v2, line];
};
LineFromPointAndVector:
PUBLIC
PROC [pt: Point3d, vec: Vector3d]
RETURNS [line: Line3d] = {
line ← CreateEmptyLine[];
FillLineFromPointAndVector[pt, vec, line];
};
LineNormalToLineThruPoint:
PUBLIC
PROC [line: Line3d, pt: Point3d]
RETURNS [normalLine: Line3d] = {
line and pt determine a plane. Within that plane, compute the line that is perpendicular to line and passes throught pt.
};
FillLineFromPoints:
PUBLIC
PROC [v1, v2: Point3d, line: Line3d] = {
Our first job is to find the nearest point on the line to the origin.
base = v1 + [(O-v1)N] N.
OPEN SVVector3d;
line.direction ← Normalize[Sub[v2, v1]];
line.base ← Add[v1, Scale[line.direction, DotProduct[Sub[[0,0,0], v1], line.direction]]];
};
FillLineFromPoints: PUBLIC PROC [v1, v2: Point3d, line: Line3d] = {
Our first job is to find the nearest point on the line to the origin.
originInWorld: Point3d = [0,0,0];
originInLine: Point3d;
lineInWorld, worldInLine: Matrix4by4;
zInLine: REAL;
line.direction ← SVVector3d.Normalize[SVVector3d.Sub[v2, v1]];
lineInWorld ← SVMatrix3d.MakeHorizontalMatFromZAxis[line.direction, v1];
worldInLine ← SVMatrix3d.Inverse[lineInWorld];
originInLine ← SVMatrix3d.Update[originInWorld, worldInLine];
zInLine ← originInLine[3];
line.base ← SVVector3d.Add[v1, SVVector3d.Scale[line.direction, zInLine]];
line.mat ← SVMatrix3d.SetOrigin[lineInWorld, line.base];
};
FillLineFromPointAndVector:
PUBLIC
PROC [pt: Point3d, vec: Vector3d, line: Line3d] = {
p2: Point3d ← SVVector3d.Add[pt, vec];
FillLineFromPoints[pt, p2, line];
};
FillLineNormalToLineThruPoint:
PUBLIC
PROC [line: Line3d, pt: Point3d, normalLine: Line3d] = {
};
line and pt determine a plane. Within that plane, compute the line that is perpendicular to line and passes throught pt.
Making Edges
CreateEmptyEdge:
PUBLIC
PROC
RETURNS [edge: Edge3d] = {
edge ← NEW[Edge3dObj];
edge.line ← CreateEmptyLine[];
};
CopyEdge:
PUBLIC
PROC [from: Edge3d, to: Edge3d] = {
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: Point3d, edge: Edge3d] = {
FillLineFromPoints[v1, v2, edge.line];
edge.start ← v1;
edge.end ← v2;
}; -- end of FillEdge
FillEdgeTransform:
PUBLIC
PROC [fixed: Edge3d, transform: Matrix4by4, edge: Edge3d] = {
start, end: Point3d;
start ← SVMatrix3d.Update[fixed.start, transform];
end ← SVMatrix3d.Update[fixed.end, transform];
FillEdge[start, end, edge];
};
CreateEdge:
PUBLIC
PROC [v1, v2: Point3d]
RETURNS [edge: Edge3d] = {
edge ← CreateEmptyEdge[];
FillEdge[v1, v2, edge];
}; -- end of CreateEdge
Direction and Distance for Lines
DirectionOfLine:
PUBLIC
PROC [line: Line3d]
RETURNS [direction: Vector3d] = {
Returns the unit direction vector of line.
direction ← line.direction;
};
DistancePointToLine:
PUBLIC
PROC [pt: Point3d, line: Line3d]
RETURNS [d:
REAL] = {
U: Point3d;
U ← DropPerpendicular[pt, line];
d ← SVVector3d.Distance[pt, U];
};
Distance2PointToLine:
PUBLIC
PROC [pt: Point3d, line: Line3d]
RETURNS [d
2:
REAL] = {
U: Point3d;
U ← DropPerpendicular[pt, line];
d2 ← SVVector3d.DistanceSquared[pt, U];
};
DropPerpendicular:
PUBLIC
PROC [pt: Point3d, line: Line3d]
RETURNS [projectedPt: Point3d] = {
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 FillLineNormalToLineThruPoint above.
The projectedPt U is:
U = A + [(V-A)N] N.
OPEN SVVector3d;
projectedPt ← Add[line.base, Scale[line.direction, DotProduct[Sub[pt,line.base], line.direction]]];
};
[Artwork node; type 'Artwork on' to command tool]
Find the points of closest approach of lines [A, N] and [B, M].
NearLinePoints:
PUBLIC
PROC [l1, l2: Line3d]
RETURNS [p1, p2: Point3d, parallel:
BOOL ← FALSE] = {
Method: Let l1 be (A, N) and l2 be (B, M). Let p1 be U and p2 be V.
Find U on (A, N) and V on (B, M) such that
(U-V)M = 0
(U-V)N = 0.
Let k = MN. If k H 1 then d = DistancePointToLine[A, (B, M)].
Otherwise,
U = A + [((B-A)P)/(1-k2)] N, where P = N- kM.
V = B + [(U-B)M] M.
d = Distance[U, V].
OPEN SVVector3d;
A, B: Point3d;
N, M, P: Vector3d;
k: REAL;
A ← l1.base; N ← l1.direction; B ← l2.base; M ← l2.direction;
k ← DotProduct[M,N];
IF ABS[1.0-ABS[k]] < 3.808737e-5 -- within 0.5 degrees of parallel. Numerically stable this ain't.
THEN {parallel ← TRUE; RETURN};
P ← Sub[N, Scale[M, k]];
p1 ← Add[A, Scale[N, DotProduct[Sub[B,A], P]/(1.0-k*k)]];
p2 ← Add[B, Scale[M, DotProduct[Sub[p1,B], M]]];
};
DistanceLineToLine:
PUBLIC
PROC [l1, l2: Line3d]
RETURNS [d:
REAL] = {
OPEN SVVector3d;
U, V: Point3d;
parallel: BOOL;
[U, V, parallel] ← NearLinePoints[l1, l2];
IF parallel THEN RETURN[DistancePointToLine[l1.base, l2]];
d ← Distance[U,V];
};
Distance for Edges
Comparing Edges to Points
NearEndpointToPoint:
PUBLIC
PROC [pt: Point3d, edge: Edge3d]
RETURNS [endpoint: Point3d] = {
Look for an obvious winner first. If that fails, do math.
IF
ABS[pt[1]-edge.start[1]] <=
ABS[pt[1]-edge.end[1]]
THEN {
IF ABS[pt[2]-edge.start[2]] <= ABS[pt[2]-edge.end[2]] AND
ABS[pt[3]-edge.start[3]] <= ABS[pt[3]-edge.end[3]] THEN RETURN[edge.start]
ELSE GOTO DoMath
}
ELSE {
IF ABS[pt[2]-edge.start[2]] > ABS[pt[2]-edge.end[2]] AND
ABS[pt[3]-edge.start[3]] > ABS[pt[3]-edge.end[3]] THEN RETURN[edge.end]
ELSE GOTO DoMath;
};
EXITS
DoMath =>
IF SVVector3d.DistanceSquared[pt, edge.start] <= SVVector3d.DistanceSquared[pt, edge.end]
THEN endpoint ← edge.start
ELSE endpoint ← edge.end;
};
Distance2PointToEndpoint:
PUBLIC PROC [pt: Point3d, edge: Edge3d]
RETURNS [distanceSquared:
REAL] = {
distance2ToPLo, distance2ToPHi: REAL;
distance2ToPLo ← SVVector3d.DistanceSquared[pt, edge.start];
distance2ToPHi ← SVVector3d.DistanceSquared[pt, edge.end];
RETURN[MIN[distance2ToPLo, distance2ToPHi]];
};
NearEdgePointToPoint:
PUBLIC PROC [pt: Point3d, edge: Edge3d]
RETURNS [onEdge: Point3d] = {
projectedPt: Point3d ← DropPerpendicular[pt, edge.line];
IF LinePointOnEdge[projectedPt, edge] THEN onEdge ← projectedPt
ELSE onEdge ← NearEndpointToPoint[pt, edge];
};
Distance2PointToEdge:
PUBLIC
PROC [pt: Point3d, edge: Edge3d]
RETURNS [distanceSquared:
REAL] = {
Perpendicular distance if possible, else distance to nearest endpoint.
projectedPt: Point3d ← DropPerpendicular[pt, edge.line];
IF LinePointOnEdge[projectedPt, edge]
THEN distanceSquared ← SVVector3d.DistanceSquared[pt, projectedPt]
ELSE distanceSquared ← Distance2PointToEndpoint[pt, edge];
};
DistancePointToEdge:
PUBLIC
PROC [pt: Point3d, edge: Edge3d]
RETURNS [distance:
REAL] = {
perpendicular distance if possible, else distance to nearest endpoint.
projectedPt: Point3d ← DropPerpendicular[pt, edge.line];
nearEndpoint: Point3d;
IF LinePointOnEdge[projectedPt, edge]
THEN distance ← SVVector3d.Distance[pt, projectedPt]
ELSE {
nearEndpoint ← NearEndpointToPoint[pt, edge];
distance ← SVVector3d.Distance[pt, nearEndpoint];
};
};
LinePointOnEdge:
PUBLIC
PROC [pt: Point3d, edge: Edge3d]
RETURNS [
BOOL] = {
Assumes pt is on edge.line. Is it on edge?
dx, dy, dz, max: REAL;
dx ← ABS[edge.end[1] - edge.start[1]];
dy ← ABS[edge.end[2] - edge.start[2]];
dz ← ABS[edge.end[3] - edge.start[3]];
max ← MAX[dx, dy, dz];
IF dx = max THEN RETURN[Between[pt[1], edge.start[1], edge.end[1]]]
ELSE IF dy = max THEN RETURN[Between[pt[2], edge.start[2], edge.end[2]]]
ELSE RETURN[Between[pt[3], edge.start[3], edge.end[3]]];
}; -- end of LinePointOnEdge
Comparing Edges to Lines
NearEndpointToLine:
PUBLIC
PROC [line: Line3d, edge: Edge3d]
RETURNS [endpoint: Point3d] = {
distance2ToPLo, distance2ToPHi: REAL;
distance2ToPLo ← Distance2PointToLine[edge.start, line];
distance2ToPHi ← Distance2PointToLine[edge.end, line];
IF distance2ToPLo <= distance2ToPHi THEN endpoint ← edge.start
ELSE endpoint ← edge.end;
};
DistanceLineToEndpoint:
PROC [line: Line3d, edge: Edge3d]
RETURNS [distance:
REAL] = {
distance2ToPLo, distance2ToPHi: REAL;
distance2ToPLo ← Distance2PointToLine[edge.start, line];
distance2ToPHi ← Distance2PointToLine[edge.end, line];
IF distance2ToPLo <= distance2ToPHi THEN distance ← RealFns.SqRt[distance2ToPLo]
ELSE distance ← RealFns.SqRt[distance2ToPHi];
};
DistanceAndEndpointLineToEdge:
PROC [line: Line3d, edge: Edge3d]
RETURNS [distance:
REAL, endpoint: Point3d] = {
distance2ToPLo, distance2ToPHi: REAL;
distance2ToPLo ← Distance2PointToLine[edge.start, line];
distance2ToPHi ← Distance2PointToLine[edge.end, line];
IF distance2ToPLo <= distance2ToPHi
THEN {
endpoint ← edge.start;
distance ← RealFns.SqRt[distance2ToPLo];
}
ELSE {
endpoint ← edge.end;
distance ← RealFns.SqRt[distance2ToPHi];
};
};
DistanceLineToEdge:
PUBLIC
PROC [line: Line3d, edge: Edge3d]
RETURNS [distance:
REAL] = {
U, V: Point3d;
parallel: BOOL;
[U, V, parallel] ← NearLinePoints[line, edge.line];
IF parallel THEN RETURN[DistancePointToLine[line.base, edge.line]];
IF LinePointOnEdge[V, edge] THEN distance ← SVVector3d.Distance[U,V]
ELSE distance ← DistanceLineToEndpoint[line, edge];
};
NearEdgePointToLine:
PUBLIC
PROC [line: Line3d, edge: Edge3d]
RETURNS [edgePoint: Point3d] = {
U, V: Point3d;
parallel: BOOL;
[U, V, parallel] ← NearLinePoints[line, edge.line];
IF parallel THEN RETURN[edge.start]; -- any point on edge will do
IF LinePointOnEdge[V, edge] THEN edgePoint ← V
ELSE edgePoint ← NearEndpointToLine[line, edge];
};
DistanceAndPointLineToEdge:
PUBLIC
PROC [line: Line3d, edge: Edge3d]
RETURNS [distance:
REAL, edgePoint: Point3d] = {
U, V: Point3d;
parallel: BOOL;
[U, V, parallel] ← NearLinePoints[line, edge.line];
IF parallel
THEN
{
distance ← DistancePointToLine[line.base, edge.line];
edgePoint ← edge.start; -- any point on edge will do
}
ELSE {
IF LinePointOnEdge[V, edge]
THEN {
distance ← SVVector3d.Distance[U,V];
edgePoint ← V;
}
ELSE {
[distance, edgePoint] ← DistanceAndEndpointLineToEdge[line, edge];
distance ← DistanceLineToEndpoint[line, edge];
edgePoint ← NearEndpointToLine[line, edge];
};
};
};
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.