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.
};
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: 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 ← [0.0, 0.0], success: BOOLFALSE] = {
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: BOOLFALSE] = {
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]; };
};
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,
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];
};
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=NIL OR 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;
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;
};
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, 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];
};
};
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.