GGSegmentImplB.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Contents: Procedures which implement the Gargoyle segment classes.
Bier, February 19, 1987 0:32:03 am PST
Pier, February 25, 1987 3:20:16 pm PST
Kurlander, August 25, 1986 5:44:28 pm PDT
Auxiliary module for GGSegmentImplA, because GGSegmentImplA got too big !!
DIRECTORY
Cubic2, CubicSplines, Angles2d, GGBasicTypes, GGBoundBox, GGCircles, Lines2d, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGTransform, Vectors2d, Imager, ImagerTransformation, ImagerPath, ImagerBackdoor, IO, Rope;
GGSegmentImplB:
CEDAR
PROGRAM
IMPORTS Angles2d, GGBoundBox, GGCircles, Lines2d, GGParseIn, GGParseOut, GGSegment, GGTransform, Vectors2d, Imager, ImagerTransformation
EXPORTS GGSegment = BEGIN
Arc: TYPE = GGBasicTypes.Arc;
BitVector: TYPE = GGModelTypes.BitVector;
BoundBox: TYPE = GGModelTypes.BoundBox;
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 = GGBasicTypes.Line;
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;
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 = {
success ← FALSE;
};
NoOpClosestPointAndTangent:
PUBLIC GGSegmentTypes.ClosestPointAndTangentProc = {
success ← FALSE;
};
NoOpLineIntersection:
PUBLIC GGSegmentTypes.LineIntersectionProc = {
points ← NIL;
pointCount ← 0;
};
NoOpCircleIntersection:
PUBLIC GGSegmentTypes.CircleIntersectionProc = {
points ← NIL;
pointCount ← 0;
};
NoOpAsSimpleCurve:
PUBLIC GGSegmentTypes.AsSimpleCurveProc = {
simpleCurve ← NIL;
};
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
CircleData: TYPE = REF CircleDataRec;
CircleDataRec:
TYPE =
RECORD [];
-- doesn't need any data
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
data: CircleData ← NEW[CircleDataRec];
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],
data: data, 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
DiscData: TYPE = REF DiscDataRec;
DiscDataRec:
TYPE =
RECORD [];
-- doesn't need any data
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
data: CircleData ← NEW[CircleDataRec];
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.
data: data,
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,
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,
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.
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
Find the boundBox of the segment, NOT allowing for the size of control points (or stroke width, whichever is larger).
loX, loY, hiX, hiY: 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];
bBox ← GGBoundBox.CreateBoundBox[loX, loY, hiX, hiY];
};
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;
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
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];
seg.bBox^ ← [loX - pad, loY - pad, hiX + pad, hiY + pad, FALSE, FALSE];
};
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: GGBasicTypes.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, success:
BOOL] = {
GGSegmentTypes.ClosestPointProc
lineData: LineData ← NARROW[seg.data];
success ← FALSE;
IF useBBox
THEN {
bigBox: GGBasicTypes.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, tangent: Vector, success:
BOOL] = {
GGSegmentTypes.ClosestPointAndTangentProc
lineData: LineData ← NARROW[seg.data];
success ← FALSE;
IF useBBox
THEN {
bigBox: GGBasicTypes.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;
};
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,
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,
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.
data: data,
props: props
]];
UpdateBoundBoxOfArc[seg];
};
ArcBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ← seg.bBox;
};
ArcTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.TightBoxProc
bBox ← GGBoundBox.NullBoundBox[];
UpdateTightBoxOfArc[seg, bBox];
};
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] = {
boundBox: BoundBox ← seg.bBox;
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
UpdateTightBoxOfArc[seg, boundBox];
GGBoundBox.EnlargeByOffset[boundBox, 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.
data: ArcData ← NARROW[seg.data];
};
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[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;
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;
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, 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;
};
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.