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: REALMAX[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.
};
Line Drawing
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] ];
};
Line Transforming
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];
};
Line Description
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];
};
useBBox: BOOLTRUE;
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) ];
};
Line Hit Testing
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;
};
Line Editing
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];
};
The Arc Class
ArcData: TYPE = REF ArcDataRec;
ArcDataRec: TYPE = RECORD [
p1: VEC,
p1Selected: SelectedObjectData,
arc: Arc];
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: REALMAX[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];
};
Arc Transforming
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];
};
Arc Description
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];
};
Arc Parts
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;
};
Arc Part Generators
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];
};
Arc Hit Testing
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];
};
};
Initialization
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.