GGSegmentImplB.mesa
Copyright Ó 1986, 1987, 1989 by Xerox Corporation. All rights reserved.
Contents: Procedures which implement the Gargoyle segment classes.
Bier, May 19, 1989 12:24:32 pm PDT
Pier, February 18, 1992 4:10 pm PST
Kurlander, August 25, 1986 5:44:28 pm PDT
Eisenman, August 5, 1987 4:20:04 pm PDT
Last edited by: David Kurlander - August 20, 1987 11:53:33 pm PDT
Doug Wyatt, December 19, 1989 11:19:19 am PST
Auxiliary module for GGSegmentImplA, because GGSegmentImplA got too big !!
Arcs, Lines, Old Circles, and NoOps.
DIRECTORY
Angles2d, Cubic2, CubicSplines, GGBasicTypes, GGBoundBox, GGCircles, GGCoreTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGTransform, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, Lines2d, Real, RealFns, Rope, Vectors2d;
GGSegmentImplB:
CEDAR
PROGRAM
IMPORTS Angles2d, GGBoundBox, GGCircles, GGParseIn, GGParseOut, GGSegment, GGTransform, Imager, ImagerTransformation, Lines2d, Real, RealFns, Vectors2d
EXPORTS GGSegment = BEGIN
Arc: TYPE = GGBasicTypes.Arc;
BitVector: TYPE = GGModelTypes.BitVector;
BoundBox: TYPE = REF BoundBoxObj;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
BuildPathProc: TYPE = GGSegmentTypes.BuildPathProc;
BuildPathTransformProc: TYPE = GGSegmentTypes.BuildPathTransformProc;
Circle: TYPE = GGBasicTypes.Circle;
ClassDef: TYPE = GGSegment.ClassDef;
ClassDefRec: TYPE = GGSegment.ClassDefRec;
Edge: TYPE = GGBasicTypes.Edge;
Line: TYPE = GGCoreTypes.Line;
PairSequence: TYPE = GGSegmentTypes.PairSequence;
PairSequenceRep: TYPE = GGSegmentTypes.PairSequenceRep;
Point: TYPE = GGBasicTypes.Point;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentClass: TYPE = REF SegmentClassObj;
SegmentClassObj: TYPE = GGSegmentTypes.SegmentClassObj;
SegmentObj: TYPE = GGSegmentTypes.SegmentObj;
SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData;
SelectionClass: TYPE = GGSegmentTypes.SelectionClass;
VEC: TYPE = Imager.VEC;
StrokeEnd: TYPE = Imager.StrokeEnd;
Vector: TYPE = GGBasicTypes.Vector;
X: NAT = CubicSplines.X;
Y: NAT = CubicSplines.Y;
defaultStrokeWidth: REAL ← 2.0;
defaultStrokeEnd: StrokeEnd ← round;
NoOpSameShapeProc:
PUBLIC GGSegmentTypes.SameShapeProc = {
RETURN[TRUE]; -- most of the work of the comparison is current done in GGSliceImplD.FindImagerObject.
};
NoOpControlPointMoved: PUBLIC GGSegmentTypes.ControlPointMovedProc = {};
NoOpControlPointGet: PUBLIC GGSegmentTypes.ControlPointGetProc = {RETURN[ [0.0, 0.0] ]};
NoOpControlPointCount: PUBLIC GGSegmentTypes.ControlPointCountProc = {controlPointCount ← 0};
NoOpControlPointFieldSet: PUBLIC GGSegmentTypes.ControlPointFieldSetProc = {};
NoOpControlPointFieldGet:
PUBLIC GGSegmentTypes.ControlPointFieldGetProc = {
ERROR;
};
NoOpClosestControlPoint:
PUBLIC GGSegmentTypes.ClosestControlPointProc = {
normal ← [0,-1];
point ← [0.0, 0.0];
controlPointNum ← 0;
success ← FALSE;
};
NoOpClosestPointAndTangent:
PUBLIC GGSegmentTypes.ClosestPointAndTangentProc = {
point ← [0.0, 0.0];
tangent ← [0,-1];
success ← FALSE;
};
NoOpLineIntersection:
PUBLIC GGSegmentTypes.LineIntersectionProc = {
points ← NIL;
pointCount ← 0;
};
NoOpCircleIntersection:
PUBLIC GGSegmentTypes.CircleIntersectionProc = {
points ← NIL;
pointCount ← 0;
};
NoOpAsSimpleCurve:
PUBLIC GGSegmentTypes.AsSimpleCurveProc = {
simpleCurve ← NIL;
};
NoOpAsPolyline:
PUBLIC GGSegmentTypes.AsPolylineProc = {
polyline ← NEW[PairSequenceRep[2]];
polyline.length ← 2;
polyline[0] ← seg.lo;
polyline[1] ← seg.hi;
};
NoOpJointNormal:
PUBLIC GGSegmentTypes.JointNormalProc = {
normal ← [0,-1];
tangent ← [0,-1];
};
NoOpCPNormal:
PUBLIC GGSegmentTypes.CPNormalProc = {
normal ← [0,-1];
};
NoOpAddJoint:
PUBLIC GGSegmentTypes.AddJointProc = {
ERROR;
};
NoOpFileOut: PUBLIC GGSegmentTypes.FileOutProc = {};
NoOpFileIn is not allowed.
The Circle Class. THIS IS A VESTIGIAL CLASS KEPT FOR BACKWARD COMPATIBILITY
BuildCircleClass:
PROC []
RETURNS [circleClass: SegmentClass] = {
circleClass ←
NEW[SegmentClassObj ← [
type: $Circle,
boundBox: CircleBoundBox,
endPointMoved: CircleEndPointMoved,
fileIn: CircleFileIn
]];
};
CircleBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
Find the boundBox of the segment, allowing for the size of control points.
radius: REAL;
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
radius ← Vectors2d.Distance[seg.lo, seg.hi];
seg.bBox^ ← [seg.lo.x - radius - cpHalf, seg.lo.y - radius - cpHalf, seg.lo.x + radius + cpHalf, seg.lo.y + radius + cpHalf, FALSE, FALSE];
bBox ← seg.bBox;
};
CircleFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
seg ←
NEW[SegmentObj ← [
class: GGSegment.FetchSegmentClass[$Circle],
looks: NIL,
strokeWidth: 1.0,
strokeEnd: round,
color: Imager.black,
lo: loPoint, hi: hiPoint,
bBox: GGBoundBox.CreateBoundBox[0,0,0,0],
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0],
data: NIL, props: NIL]];
[] ← seg.class.boundBox[seg];
};
CircleEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
Circles are simple. Nothing to do.
};
The Disc Class. THIS IS A VESTIGIAL CLASS KEPT FOR BACKWARD COMPATIBILITY
Discs are identical to circles and are filled with the parent outline fill color
BuildDiscClass:
PROC []
RETURNS [discClass: SegmentClass] = {
discClass ←
NEW[SegmentClassObj ← [
type: $Disc,
boundBox: CircleBoundBox,
endPointMoved: CircleEndPointMoved,
fileIn: DiscFileIn
]];
};
DiscFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
seg ←
NEW[SegmentObj ← [
class: GGSegment.FetchSegmentClass[$Disc],
looks: NIL,
strokeWidth: 1.0,
strokeEnd: round,
color: Imager.black,
lo: loPoint, hi: hiPoint,
bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down.
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down.
data: NIL,
props: NIL]];
[] ← seg.class.boundBox[seg];
};
The Line (straight line) Class
LineData: TYPE = REF LineDataRec;
LineDataRec:
TYPE =
RECORD [
edge: Edge -- so we don't have to allocate it all of the time
];
BuildLineClass:
PROC []
RETURNS [lineClass: SegmentClass] = {
OPEN GGSegment;
lineClass ←
NEW[SegmentClassObj ← [
type: $Line,
copyData: LineCopyData,
reverse: LineReverse,
boundBox: LineBoundBox,
tightBox: LineTightBox,
sameShape: NoOpSameShapeProc,
Transforming
transform: LineTransform,
endPointMoved: LineEndPointMoved,
controlPointMoved: NoOpControlPointMoved,
Textual Description
describe: LineDescribe,
fileOut: GGSegment.NoOpFileOut,
fileIn: LineFileIn,
Drawing
buildPath: LineBuildPath,
buildPathTransform: LineBuildPathTransform,
Control Point Access
controlPointGet: NoOpControlPointGet,
controlPointCount: NoOpControlPointCount,
controlPointFieldSet: NoOpControlPointFieldSet,
controlPointFieldGet: NoOpControlPointFieldGet,
Hit Testing
closestPoint: LineClosestPoint,
closestControlPoint: NoOpClosestControlPoint,
closestPointAndTangent: LineClosestPointAndTangent,
lineIntersection: LineLineIntersection,
circleIntersection: LineCircleIntersection,
asSimpleCurve: LineAsSimpleCurve,
asPolyline: NoOpAsPolyline,
cPNormal: NoOpCPNormal,
jointNormal: LineJointNormal,
Editing
addJoint: LineAddJoint,
setStrokeWidth: LineSetStrokeWidth
]];
};
LineSetStrokeWidth:
PROC [seg: Segment, strokeWidth:
REAL] = {
seg.strokeWidth ← strokeWidth;
UpdateLineBoundBox[seg];
};
MakeLine:
PUBLIC
PROC [p0, p1: Point, props:
LIST
OF
REF
ANY]
RETURNS [seg: Segment] = {
lineSegment: LineData ←
NEW[LineDataRec ← [
edge: Lines2d.CreateEdge[p0, p1]
]];
seg ←
NEW[SegmentObj ← [
class: GGSegment.FetchSegmentClass[$Line],
looks: NIL,
strokeWidth: defaultStrokeWidth,
strokeEnd: defaultStrokeEnd,
color: Imager.black,
lo: p0, hi: p1,
bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down.
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down.
data: lineSegment,
props: props
]];
UpdateLineBoundBox[seg];
};
LineBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ← seg.bBox;
};
LineTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.TightBoxProc
bBox ← seg.tightBox;
};
root2Over2: REAL = 0.707106816;
UpdateLineBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc
Find the boundBox of the segment, allowing for the size of control points (or stroke width, whichever is larger).
loX, loY, hiX, hiY: REAL;
pad: REAL;
loX ← MIN[seg.lo.x, seg.hi.x];
hiX ← MAX[seg.lo.x, seg.hi.x];
loY ← MIN[seg.lo.y, seg.hi.y];
hiY ← MAX[seg.lo.y, seg.hi.y];
GGBoundBox.UpdateBoundBox[seg.tightBox, loX, loY, hiX, hiY];
seg.bBox^ ← seg.tightBox^;
IF seg.strokeEnd = square
THEN pad ← seg.strokeWidth*root2Over2 + 1.0
ELSE pad ← seg.strokeWidth/2.0 + 1.0;
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
LineCopyData:
PROC [seg: Segment]
RETURNS [data:
REF
ANY] = {
GGSegmentTypes.CopyDataProc
lineSegment: LineData ← NARROW[seg.data];
data ←
NEW[LineDataRec ← [edge: Lines2d.CreateEdge[seg.lo, seg.hi]
]];
};
LineReverse:
PROC [seg: Segment] = {
GGSegmentTypes.ReverseProc
Lines are simple. Nothing to do.
};
LineBuildPath:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathProc
lineTo[seg.hi];
};
LineBuildPathTransform:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathTransformProc
hiPoint: Point;
hiPoint ←
IF hi
OR entire
THEN GGTransform.Transform[transform, seg.hi]
ELSE [seg.hi.x, seg.hi.y];
lineTo[ [hiPoint.x, hiPoint.y] ];
};
LineTransform:
PROC [seg: Segment, transform: ImagerTransformation.Transformation] = {
GGSegmentTypes.TransformProc
lineSegment: LineData ← NARROW[seg.data];
Lines2d.FillEdge[seg.lo, seg.hi, lineSegment.edge];
UpdateLineBoundBox[seg];
};
LineEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
lineSegment: LineData ← NARROW[seg.data];
Lines2d.FillEdge[seg.lo, seg.hi, lineSegment.edge];
UpdateLineBoundBox[seg];
};
LineDescribe:
PROC [seg: Segment, self, lo, hi:
BOOL, cps: BitVector]
RETURNS [rope: Rope.
ROPE] = {
rope ← "Line";
};
LineFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
seg ← MakeLine[loPoint, hiPoint, NIL];
};
PointIsInBox:
PROC [test: Point, box: BoundBoxObj]
RETURNS [
BOOL] = {
RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ];
};
LineClosestPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point ← [0.0, 0.0], success:
BOOL ←
FALSE] = {
GGSegmentTypes.ClosestPointProc
lineData: LineData ← NARROW[seg.data];
IF useBBox
THEN {
bigBox: BoundBoxObj ← [seg.bBox.loX-tolerance, seg.bBox.loY-tolerance, seg.bBox.hiX+tolerance, seg.bBox.hiY+tolerance, FALSE, FALSE];
IF NOT PointIsInBox[testPoint, bigBox] THEN RETURN;
};
point ← Lines2d.NearestPointOnEdge[testPoint, lineData.edge];
success ← TRUE;
};
LineClosestPointAndTangent:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point ← [0.0, 0.0], tangent: Vector ← [0,-1], success:
BOOL ←
FALSE] = {
GGSegmentTypes.ClosestPointAndTangentProc
lineData: LineData ← NARROW[seg.data];
IF useBBox
THEN {
bigBox: BoundBoxObj ← [seg.bBox.loX-tolerance, seg.bBox.loY-tolerance, seg.bBox.hiX+tolerance, seg.bBox.hiY+tolerance, FALSE, FALSE];
IF NOT PointIsInBox[testPoint, bigBox] THEN RETURN;
};
point ← Lines2d.NearestPointOnEdge[testPoint, lineData.edge];
tangent ← Lines2d.DirectionOfLine[lineData.edge.line];
success ← TRUE;
};
LineLineIntersection:
PROC [seg: Segment, line: Line]
RETURNS [points:
LIST
OF Point, pointCount:
NAT] = {
GGSegmentTypes.LineIntersectionProc
lineData: LineData ← NARROW[seg.data];
failure: BOOL;
ipoint: Point;
[ipoint, failure] ← Lines2d.LineMeetsEdge[line, lineData.edge];
IF failure
THEN {
pointCount ← 0;
RETURN;
};
points ← LIST[ipoint];
pointCount ← 1;
};
LineCircleIntersection:
PROC [seg: Segment, circle: Circle]
RETURNS [points:
LIST
OF Point, pointCount:
NAT] = {
GGSegmentTypes.CircleIntersectionProc
lineData: LineData ← NARROW[seg.data];
hitPoints: ARRAY[1..2] OF Point;
[hitPoints, pointCount] ← GGCircles.CircleMeetsEdge[circle, lineData.edge];
points ← NIL;
FOR i:
NAT
IN [1..pointCount]
DO
points ← CONS[hitPoints[i], points];
ENDLOOP;
};
LineAsSimpleCurve:
PROC [seg: Segment, point: Point]
RETURNS [simpleCurve:
REF
ANY] = {
lineData: LineData ← NARROW[seg.data];
edge: Edge ← lineData.edge;
simpleCurve ← edge;
};
LineJointNormal:
PROC [seg: Segment, joint, point: Point, hi:
BOOL]
RETURNS [normal, tangent: Vector] = {
GGSegmentTypes.JointNormalProc
normal1, normal2, direction: Vector;
p0,p1 : Point;
lineData: LineData ← NARROW[seg.data];
normal1 ← Vectors2d.RightNormalOfEdge[lineData.edge];
normal2 ← Vectors2d.LeftNormalOfEdge[lineData.edge];
direction ← Vectors2d.VectorFromPoints[joint, point];
IF
ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] <
ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]]
THEN{
normal ← normal1; }
ELSE {normal ← normal2;};
IF lineData.edge.startIsFirst
THEN {
p0 ← lineData.edge.start;
p1 ← lineData.edge.end;
}
ELSE {
p0 ← lineData.edge.end;
p1 ← lineData.edge.start;
};
IF hi THEN {tangent ← Vectors2d.VectorFromPoints[p1,p0]; }
ELSE {tangent ← Vectors2d.VectorFromPoints[p0,p1]; };
};
LineAddJoint:
PROC [seg: Segment, pos: Point]
RETURNS [seg1, seg2: Segment] = {
GGSegmentTypes.AddJointProc
seg1 ← GGSegment.CopySegment[seg];
seg1.hi ← pos;
UpdateLineBoundBox[seg1];
seg2 ← GGSegment.CopySegment[seg];
seg2.lo ← pos;
UpdateLineBoundBox[seg2];
};
ArcData: TYPE = REF ArcDataRec;
ArcDataRec:
TYPE =
RECORD [
p1: VEC,
p1Selected: SelectedObjectData,
BuildArcClass:
PROC []
RETURNS [class: SegmentClass] = {
OPEN GGSegment;
class ←
NEW[SegmentClassObj ← [
type: $Arc,
copyData: ArcCopyData,
reverse: ArcReverse,
boundBox: ArcBoundBox,
tightBox: ArcTightBox,
sameShape: NoOpSameShapeProc,
Transforming
transform: ArcTransform,
endPointMoved: ArcEndPointMoved,
controlPointMoved: ArcControlPointMoved,
Textual Description
describe: ArcDescribe,
fileOut: ArcFileOut,
fileIn: ArcFileIn,
Drawing
buildPath: ArcBuildPath,
buildPathTransform: ArcBuildPathTransform,
Control Point Access
controlPointGet: ArcControlPointGet,
controlPointCount: ArcControlPointCount,
controlPointFieldSet: ArcFieldSet,
controlPointFieldGet: ArcFieldGet,
Hit Testing
closestPoint: ArcClosestPoint,
closestControlPoint: ArcClosestControlPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
asSimpleCurve: ArcAsSimpleCurve,
asPolyline: ArcAsPolyline,
cPNormal: ArcCPNormal,
jointNormal: ArcJointNormal,
Editing
addJoint: ArcAddJoint,
setStrokeWidth: ArcSetStrokeWidth
]];
};
ArcSetStrokeWidth:
PROC [seg: Segment, strokeWidth:
REAL] = {
seg.strokeWidth ← strokeWidth;
UpdateBoundBoxOfArc[seg];
};
MakeArc:
PUBLIC
PROC [p0, p1, p2: Point, props:
LIST
OF
REF
ANY]
RETURNS [seg: Segment] = {
Circular Arc through the three points
data: ArcData ←
NEW[ArcDataRec ← [
p1: [p1.x,p1.y],
p1Selected: [FALSE, FALSE, FALSE],
arc: GGCircles.CreateArc[p0, p1, p2]
]];
seg ←
NEW[SegmentObj ← [
class: GGSegment.FetchSegmentClass[$Arc],
looks: NIL,
strokeWidth: defaultStrokeWidth,
strokeEnd: defaultStrokeEnd,
color: Imager.black,
lo: p0, hi: p2,
bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data,
props: props
]];
UpdateBoundBoxOfArc[seg];
};
ArcGetParams:
PUBLIC
PROC [seg: Segment]
RETURNS [p0, p1, p2: Point] = {
Used by the matchtool (allows it not to know about ArcDatas)
data: ArcData ← NARROW[seg.data];
p0 ← seg.lo;
p2 ← seg.hi;
p1 ← data.p1;
};
ArcBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ← seg.bBox;
};
ArcTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.TightBoxProc
bBox ← seg.tightBox;
};
UpdateTightBoxOfArc:
PROC [seg: Segment, boundBox: BoundBox] = {
data: ArcData ← NARROW[seg.data];
arc: Arc ← data.arc;
IF arc.edge # NIL THEN UpdateBoundBoxOfEdge[arc.edge, boundBox]
ELSE {
boundBox^ ← [loX: arc.p0.x, loY: arc.p0.y, hiX: arc.p0.x, hiY: arc.p0.y, null: FALSE, infinite: FALSE];
GGBoundBox.EnlargeByPoint[boundBox, arc.p2];
IF Angles2d.IsInCCWInterval2[0.0, arc.theta0, arc.deltaTheta]
THEN
GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin,
[arc.circle.radius, 0.0]]];
IF Angles2d.IsInCCWInterval2[90.0, arc.theta0, arc.deltaTheta]
THEN
GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin,
[0.0, arc.circle.radius]]];
IF Angles2d.IsInCCWInterval2[180.0, arc.theta0, arc.deltaTheta]
THEN
GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin,
[-arc.circle.radius, 0.0]]];
IF Angles2d.IsInCCWInterval2[-90.0, arc.theta0, arc.deltaTheta]
THEN
GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin,
[0.0, -arc.circle.radius]]];
};
};
UpdateBoundBoxOfEdge:
PROC [edge: Edge, boundBox: BoundBox] = {
boundBox^ ← [
For when the arc is straight.
loX: MIN[edge.start.x, edge.end.x],
loY: MIN[edge.start.y, edge.end.y],
hiX: MAX[edge.start.x, edge.end.x],
hiY: MAX[edge.start.y, edge.end.y],
null: FALSE, infinite: FALSE];
};
UpdateBoundBoxOfArc:
PROC [seg: Segment] = {
pad: REAL;
UpdateTightBoxOfArc[seg, seg.tightBox];
seg.bBox^ ← seg.tightBox^;
IF seg.strokeEnd = square
THEN pad ← seg.strokeWidth*root2Over2 + 1.0
ELSE pad ← seg.strokeWidth/2.0 + 1.0;
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
ArcCopyData: GGSegmentTypes.CopyDataProc = {
GGSegmentTypes.CopyDataProc
Copies the data in the "data" field of seg. This data is class-dependent.
arcData: ArcData ← NARROW[seg.data];
new: ArcData ← NEW[ArcDataRec];
new.p1 ← arcData.p1;
new.p1Selected ← arcData.p1Selected;
new.arc ← GGCircles.CreateEmptyArc[];
GGCircles.CopyArc[from: arcData.arc, to: new.arc];
RETURN[new];
};
ArcReverse:
PROC [seg: Segment] = {
GGSegmentTypes.ReverseProc
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
arcData: ArcData ← NARROW[seg.data];
GGCircles.ReverseArc[arcData.arc];
};
ArcTransform:
PROC [seg: Segment, transform: ImagerTransformation.Transformation] = {
GGSegmentTypes.TransformProc
Apply the given transformation to all internal data of the segment. It is now in a new position, orientation, scaling, or skewing.
data: ArcData ← NARROW[seg.data];
data.p1 ← ImagerTransformation.Transform[transform, data.p1];
GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc];
UpdateBoundBoxOfArc[seg];
};
ArcEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
data: ArcData ← NARROW[seg.data];
p0: Point ← IF lo THEN newPoint ELSE seg.lo;
p1: Point ← [data.p1.x, data.p1.y];
p2: Point ← IF lo THEN seg.hi ELSE newPoint;
GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc];
UpdateBoundBoxOfArc[seg];
};
ArcControlPointMoved:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum:
NAT] = {
GGSegmentTypes.ControlPointMovedProc
p1Vec: VEC;
p1: Point;
data: ArcData ← NARROW[seg.data];
IF controlPointNum#0 THEN ERROR;
p1Vec ← ImagerTransformation.Transform[transform, data.p1];
p1 ← [p1Vec.x, p1Vec.y];
data.p1 ← p1Vec;
GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc];
UpdateBoundBoxOfArc[seg];
};
TransformEndPoints:
PROC [loPt, hiPt: Point, lo, hi:
BOOL, transform: ImagerTransformation.Transformation]
RETURNS [newLo, newHi:
VEC] = {
IF lo THEN newLo ← ImagerTransformation.Transform[transform, [loPt.x,loPt.y]]
ELSE newLo ← [loPt.x,loPt.y];
IF hi THEN newHi ← ImagerTransformation.Transform[transform, [hiPt.x,hiPt.y]]
ELSE newHi ← [hiPt.x,hiPt.y];
};
Arc Drawing
ArcBuildPath:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathProc
data: ArcData ← NARROW[seg.data];
arcTo[data.p1, [seg.hi.x, seg.hi.y]];
};
ArcBuildPathTransform:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL, controlPoints: BitVector, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathTransformProc
Like BuildPathProc but you should assume that transform has been applied to your lower joint iff lo = TRUE and to your higher joint iff hi = TRUE. This is for rubberbanding filled areas.
data: ArcData ← NARROW[seg.data];
p0, p1, p2: VEC;
IF entire
THEN {
[p0, p2] ← TransformEndPoints[seg.lo, seg.hi, TRUE, TRUE, transform];
p1 ← ImagerTransformation.Transform[transform, data.p1];
}
ELSE {
[p0, p2] ← TransformEndPoints[seg.lo, seg.hi, lo, hi, transform];
p1 ← IF controlPoints=NIL OR controlPoints[0] THEN ImagerTransformation.Transform[transform, data.p1]
ELSE data.p1;
};
arcTo[p1,p2];
};
ArcDescribe:
PROC [seg: Segment, self, lo, hi:
BOOL, cps: BitVector]
RETURNS [rope: Rope.
ROPE] = {
data: ArcData ← NARROW[seg.data];
arc: Arc ← data.arc;
IF arc.edge # NIL THEN rope ← "Straight Arc"
ELSE rope ← IO.PutFR ["Radius %g Arc", [real[arc.circle.radius]]];
ELSE rope ← "Arc";
};
ArcFileOut:
PROC [seg: Segment, f:
IO.
STREAM] = {
GGSegmentTypes.FileOutProc
data: ArcData ← NARROW[seg.data];
p1: Point ← [data.p1.x, data.p1.y];
GGParseOut.WritePoint[f, p1];
};
ArcFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
p1: Point;
p1 ← GGParseIn.ReadPoint[f];
seg ← MakeArc[loPoint, p1, hiPoint, NIL];
};
ArcFieldSet: GGSegmentTypes.ControlPointFieldSetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]
data: ArcData ← NARROW[seg.data];
IF controlPointNum # 0 THEN ERROR;
SELECT selectClass
FROM
normal => data.p1Selected.normal ← selected;
hot => data.p1Selected.hot ← selected;
active => data.p1Selected.active ← selected;
match => data.p1Selected.match ← selected;
ENDCASE => ERROR;
};
ArcFieldGet: GGSegmentTypes.ControlPointFieldGetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]
data: ArcData ← NARROW[seg.data];
IF controlPointNum # 0 THEN ERROR;
SELECT selectClass
FROM
normal => selected ← data.p1Selected.normal;
hot => selected ← data.p1Selected.hot;
active => selected ← data.p1Selected.active;
match => selected ← data.p1Selected.match;
ENDCASE => ERROR;
};
ArcControlPointGet:
PROC [seg: Segment, controlPointNum:
NAT]
RETURNS [point: Point] = {
GGSegmentTypes.ControlPointGetProc
data: ArcData ← NARROW[seg.data];
IF controlPointNum#0 THEN ERROR;
RETURN[ [data.p1.x, data.p1.y] ];
};
ArcControlPointCount:
PROC [seg: Segment]
RETURNS [controlPointCount:
NAT] = {
GGSegmentTypes.ControlPointCountProc
data: ArcData ← NARROW[seg.data];
RETURN[1];
};
ArcClosestPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, success:
BOOL] = {
data: ArcData ← NARROW[seg.data];
point ← GGCircles.NearestPointOnArc[testPoint, data.arc];
success ← TRUE;
};
ArcClosestControlPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, normal: Vector ← [0,-1], controlPointNum:
NAT, success:
BOOL] = {
GGSegmentTypes.ClosestControlPointProc
data: ArcData ← NARROW[seg.data];
success ← TRUE;
controlPointNum ← 0;
point ← [data.p1.x, data.p1.y];
};
ArcAsSimpleCurve:
PROC [seg: Segment, point: Point]
RETURNS [simpleCurve:
REF
ANY] = {
arcData: ArcData ← NARROW[seg.data];
arc: Arc ← arcData.arc;
simpleCurve ← arc;
};
ArcAsPolyline:
PROC [seg: Segment, tolerance:
REAL]
RETURNS [polyline: GGSegmentTypes.PairSequence] = {
arcData: ArcData ← NARROW[seg.data];
arc: Arc ← arcData.arc;
IF arc.edge#
NIL
OR 2.0*arc.circle.radius<=tolerance
THEN {
-- also does "degenerate" case in which the radius of the circle is less than half the tolerance
polyline ← NEW[GGSegmentTypes.PairSequenceRep[2]];
polyline.length ← 2;
polyline[0] ← seg.lo;
polyline[1] ← seg.hi;
}
ELSE {
r: REAL = arc.circle.radius;
minSides: REAL;
n: NAT;
phi, sin, cos, deltaTheta: REAL;
startAngle: REAL;
deltaTheta ← 0.01745328 * arc.deltaTheta; -- convert to radians
argument to the following SqRt better be positive. See comment above.
minSides ← deltaTheta/
(2.0*RealFns.ArcTan[y: RealFns.SqRt[(2.0*r - tolerance)*tolerance], x: r-tolerance]);
n ← Real.Ceiling[minSides];
phi ← deltaTheta/REAL[n];
IF arc.ccw
THEN {
startAngle ← 0.01745328*arc.theta0;
}
ELSE {
phi ← -phi;
startAngle ← 0.01745328*Vectors2d.AngleFromVector[Vectors2d.Sub[seg.lo, arc.circle.origin]];
};
polyline ← NEW[GGSegmentTypes.PairSequenceRep[n+1]];
polyline.length ← n+1;
polyline[0] ← seg.lo;
FOR i:
NAT
IN [1..n)
DO
sin ← RealFns.Sin[startAngle+phi*i];
cos ← RealFns.Cos[startAngle+phi*i];
polyline[i] ← Vectors2d.Add[arc.circle.origin, [cos*r, sin*r]];
ENDLOOP;
polyline[n] ← seg.hi;
};
};
ArcCPNormal:
PROC [seg: Segment, controlPointNum:
NAT, cPoint, testPoint: Point]
RETURNS [normal: Vector] = {
GGSegmentTypes.JointNormalProc
arcData: ArcData ← NARROW[seg.data];
origin: Point ← arcData.arc.circle.origin;
normal ← Vectors2d.Normalize[Vectors2d.Sub[cPoint, origin]];
};
ArcJointNormal:
PROC [seg: Segment, joint, point: Point, hi:
BOOL]
RETURNS [normal, tangent: Vector] = {
GGSegmentTypes.JointNormalProc
arcData: ArcData ← NARROW[seg.data];
origin: Point ← arcData.arc.circle.origin;
normal ← Vectors2d.Normalize[Vectors2d.Sub[joint, origin]];
tangent ← Vectors2d.Normalize[Vectors2d.VectorPlusAngle[normal, 90]];
};
Arc Editing
ArcAddJoint:
PROC [seg: Segment, pos: Point]
RETURNS [seg1, seg2: Segment] = {
GGSegmentTypes.AddJointProc
data: ArcData ← NARROW[seg.data];
data1, data2: ArcData;
p0Rel: Point ← Vectors2d.Sub[data.arc.p0, data.arc.circle.origin];
p1Rel: Point ← Vectors2d.Sub[data.p1, data.arc.circle.origin];
p2Rel: Point ← Vectors2d.Sub[data.arc.p2, data.arc.circle.origin];
caretRel: Point ← Vectors2d.Sub[pos, data.arc.circle.origin];
cpAngle: REAL ← Vectors2d.AngleCCWBetweenVectors[p0Rel, p1Rel];
caretAngle: REAL ← Vectors2d.AngleCCWBetweenVectors[p0Rel, caretRel];
rotVec: Point;
seg1 ← GGSegment.CopySegment[seg];
seg2 ← GGSegment.CopySegment[seg];
data1 ← NARROW[seg1.data];
data2 ← NARROW[seg2.data];
seg2.lo ← seg1.hi ← pos;
seg1.lo ← data.arc.p0; seg2.hi ← data.arc.p2;
IF caretAngle < cpAngle
THEN {
-- following arc, caret is closer to p0 than is cp
rotVec ← ImagerTransformation.Transform[ImagerTransformation.Rotate[-caretAngle/2.0], caretRel];
data1.p1 ← Vectors2d.Add[rotVec, data.arc.circle.origin];
}
ELSE {
-- following arc, cp is closer to p0 than is caret
caretAngle ← Vectors2d.AngleCCWBetweenVectors[caretRel, p2Rel]; -- now we want angle from caret to p2
rotVec ← ImagerTransformation.Transform[ImagerTransformation.Rotate[caretAngle/2.0], caretRel];
data2.p1 ← Vectors2d.Add[rotVec, data.arc.circle.origin];
};
data1.arc ← GGCircles.CreateArc[seg1.lo, data1.p1, seg1.hi];
data2.arc ← GGCircles.CreateArc[seg2.lo, data2.p1, seg2.hi];
UpdateBoundBoxOfArc[seg1];
UpdateBoundBoxOfArc[seg2];
IF
NOT data.arc.ccw
THEN {
-- p0 to p2 always counter-clockwise. Need to take into account.
GGSegment.ReverseSegment[seg1];
GGSegment.ReverseSegment[seg2];
RETURN [seg2, seg1];
};
};
Init:
PROC [] = {
classDef: ClassDef ← NEW[ClassDefRec ← [type: $Line, class: BuildLineClass[]]];
GGSegment.RegisterSegmentClass[classDef];
classDef ← NEW[GGSegment.ClassDefRec ← [type: $Arc, class: BuildArcClass[]]];
GGSegment.RegisterSegmentClass[classDef];
classDef ← NEW[ClassDefRec ← [type: $Circle, class: BuildCircleClass[]]];
GGSegment.RegisterSegmentClass[classDef];
classDef ← NEW[ClassDefRec ← [type: $Disc, class: BuildDiscClass[]]];
GGSegment.RegisterSegmentClass[classDef];
};
Init[];
END.