GGSegmentImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last edited by Bier on September 19, 1985 10:35:53 pm PDT
Contents: Procedures which implement the Gargoyle segment classes.
Stone, August 9, 1985 2:42:18 pm PDT
DIRECTORY
Cubic2,
CubicPaths,
CubicPathsExtras,
CubicSplines,
GGBoundBox,
GGLines,
GGModelTypes,
GGSegment,
GGTransform,
Imager,
ImagerPath,
ImagerTransformation;
GGSegmentImpl:
CEDAR
PROGRAM
IMPORTS CubicSplines, CubicPaths, CubicPathsExtras, GGBoundBox, GGLines, GGTransform, Imager, ImagerPath, ImagerTransformation
EXPORTS GGSegment =
BEGIN
VEC: TYPE = Imager.VEC;
X: NAT = CubicSplines.X;
Y: NAT = CubicSplines.Y;
BoundBox: TYPE = GGModelTypes.BoundBox;
Edge: TYPE = GGModelTypes.Edge;
Point: TYPE = GGModelTypes.Point;
Segment: TYPE = GGModelTypes.Segment;
SegmentObj: TYPE = GGModelTypes.SegmentObj;
SegmentClass: TYPE = REF SegmentClassObj;
SegmentClassObj: TYPE = GGModelTypes.SegmentClassObj;
Vector: TYPE = GGModelTypes.Vector;
CopyDataProc: TYPE = GGModelTypes.CopyDataProc;
TransformProc: TYPE = GGModelTypes.TransformProc;
MaskStrokeProc: TYPE = GGModelTypes.MaskStrokeProc;
MaskStrokeTransformProc: TYPE = GGModelTypes.MaskStrokeTransformProc;
BuildPathProc: TYPE = GGModelTypes.BuildPathProc;
BuildPathTransformProc: TYPE = GGModelTypes.BuildPathTransformProc;
ClosestPointProc: TYPE = GGModelTypes.ClosestPointProc;
The segment data assumes that the endpoints are in the segment record (lo and hi).
segmentClasses: LIST OF ClassDef;
ClassDef: TYPE = REF ClassDefRec;
ClassDefRec: TYPE = RECORD[type: ATOM, class: SegmentClass];
globalEdgePool: ARRAY [0..MaxEdges-1] OF Edge;
globalEdgePoolIndex: NAT;
MaxEdges: NAT = 3;
NoOpClosestPointAndTangent:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, tangent: Vector, success:
BOOL] = {
success ← FALSE;
};
Init:
PROC [] = {
classDef: ClassDef ← NEW[ClassDefRec ← [type: $Line, class: BuildLineClass[]]];
segmentClasses ← CONS[classDef, segmentClasses];
classDef ← NEW[ClassDefRec ← [type: $Arc, class: BuildArcClass[]]];
segmentClasses ← CONS[classDef, segmentClasses];
classDef ← NEW[ClassDefRec ← [type: $Conic, class: BuildConicClass[]]];
segmentClasses ← CONS[classDef, segmentClasses];
classDef ← NEW[ClassDefRec ← [type: $Bezier, class: BuildBezierClass[]]];
segmentClasses ← CONS[classDef, segmentClasses];
classDef ← NEW[ClassDefRec ← [type: $CubicSpline, class: BuildCubicSplineClass[]]];
segmentClasses ← CONS[classDef, segmentClasses];
FOR i:
NAT
IN [0..MaxEdges)
DO
globalEdgePool[i] ← GGLines.CreateEmptyEdge[];
ENDLOOP;
globalEdgePoolIndex ← 0;
};
NotFound: PUBLIC SIGNAL = CODE;
FetchSegmentClass:
PUBLIC
PROC [type:
ATOM]
RETURNS [class: SegmentClass] = {
FOR l:
LIST
OF ClassDef ← segmentClasses, l.rest
UNTIL l=
NIL
DO
IF l.first.type=type THEN RETURN[l.first.class];
ENDLOOP;
SIGNAL NotFound;
RETURN[NIL];
};
Making Segments (much like the Imager interface)
MakeLine:
PUBLIC
PROC [p0, p1: Point]
RETURNS [seg: Segment] = {
lineSegment: LineData ← NEW[LineDataRec];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$Line],
looks: NIL,
strokeWidth: 1.0,
color: Imager.black,
lo: p0, hi: p1,
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets et one line down.
data: lineSegment]];
seg.class.boundBox[seg];
};
MakeBezier:
PUBLIC PROC [p0, p1, p2, p3: Point]
RETURNS [seg: Segment] = {
Create a Bezier segment which passed thru p0 and p3, guided by p1 and p2.
data: BezierData ←
NEW[BezierDataRec ← [
b1: [p1[1], p1[2]], b2: [p2[1], p2[2]],
path: NEW[CubicPaths.PathRec]
]];
data.path.cubics ← NEW[CubicPaths.PathSequence[1]];
data.path.cubics[0] ← [b0: [p0[1], p0[2]], b1: [p1[1], p1[2]], b2: [p2[1], p2[2]], b3: [p3[1], p3[2]]];
CubicPathsExtras.UpdateBounds[data.path];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$Bezier],
looks: NIL,
strokeWidth: 1.0,
color: Imager.black,
lo: p0, hi: p3,
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data]];
seg.class.boundBox[seg];
};
MakeCubicSpline:
PUBLIC PROC [cps: CubicSplines.KnotSequence, type: CubicSplines.SplineType]
RETURNS[seg: Segment] = {
Makes a segment of any of the types defined in CubicSplines. Expected to be used principally for interpolating splines and sequences of Bezier cubics
coeffs: CubicSplines.CoeffsSequence ← CubicSplines.MakeSpline[cps, type];
data: CubicSplineData ←
NEW[CubicSplineDataRec ← [
cps: cps, type: type,
path: CubicPaths.PathFromCubic[coeffs]
]];
last: NAT ← data.path.cubics.length-1;
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$CubicSpline],
looks: NIL,
strokeWidth: 1.0,
color: Imager.black,
lo: [data.path.cubics[0].b0.x, data.path.cubics[0].b0.y],
hi: [data.path.cubics[last].b3.x,data.path.cubics[last].b3.y],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data]];
seg.class.boundBox[seg];
};
MakeConic:
PUBLIC PROC [p0, p1, p2: Point, r:
REAL]
RETURNS [seg: Segment] = {
Let m be the midpoint of [p0, p2].
Let p be the point on [m, p1] such that Length[m, p]/Length[m, p1] = r.
The curve starts at p0, passes through p, and ends at p2.
It is a line if r=0; an ellipse if 0<r<1/2; a parabola if r=1/2; a hyperbola if 1/2<r<1.
The curve is bounded by the triangle [p0, p1, p2].
data: ConicData ←
NEW[ConicDataRec ← [
p1: [p1[1],p1[2]], s: r,
path: PathFromConic[p0, p1, p2, r]
]];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$Conic],
looks: NIL,
strokeWidth: 1.0,
color: Imager.black,
lo: p0, hi: p2,
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data]];
seg.class.boundBox[seg];
};
MakeArc:
PUBLIC PROC [p0, p1, p2: Point]
RETURNS [seg: Segment] = {
Circular Arc through the three points
data: ArcData ←
NEW[ArcDataRec ← [
p1: [p1[1],p1[2]],
path: PathFromArc[p0, p1, p2]
]];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$Arc],
looks: NIL,
strokeWidth: 1.0,
color: Imager.black,
lo: p0, hi: p2,
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data]];
seg.class.boundBox[seg];
};
CopySegment:
PUBLIC PROC [seg: Segment]
RETURNS [copy: Segment] = {
copyData: REF ANY;
copyData ← seg.class.copyData[seg];
copy ←
NEW[SegmentObj ← [
class: seg.class,
looks: seg.looks,
strokeWidth: seg.strokeWidth,
color: seg.color,
lo: seg.lo, hi: seg.hi,
boundBox: GGBoundBox.CopyBoundBox[seg.boundBox],
data: copyData]];
The copy will be unselected.
};
ReverseSegment:
PUBLIC
PROC [seg: Segment] = {
temp: Point;
temp ← seg.lo;
seg.lo ← seg.hi;
seg.hi ← temp;
seg.class.reverse[seg];
};
The Curve (line) Segment Classes
BuildArcClass:
PROC []
RETURNS [class: SegmentClass] = {
class ←
NEW[SegmentClassObj ← [
type: $Arc,
copyData: ArcCopyData,
reverse: ArcReverse,
transform: ArcTransform,
endpointMoved: ArcEndPointMoved,
maskStroke: CurveMaskStroke,
maskStrokeTransform: ArcMaskStrokeTransform,
buildPath: CurveBuildPath,
buildPathTransform: ArcBuildPathTransform,
closestPoint: CurveClosestPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
boundBox: CurveBoundBox
]];
ArcData: TYPE = REF ArcDataRec;
ArcDataRec:
TYPE =
RECORD [
p1: VEC,
path: CubicPaths.Path];
ArcCopyData:
PROC [seg: Segment]
RETURNS [
REF
ANY] = {
Copies the data in the "data" field of seg. This data is class-dependent.
data: ArcData ← NARROW[seg.data];
new: ArcData ← NEW[ArcDataRec];
new.p1 ← data.p1;
new.path ← CubicPathsExtras.CopyPath[data.path];
RETURN[new];
};
ArcReverse:
PROC [seg: Segment] = {
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: ArcData ← NARROW[seg.data];
CubicPathsExtras.ReversePath[data.path];
};
ArcTransform:
PROC [transform: ImagerTransformation.Transformation, seg: Segment] = {
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];
CubicPaths.TransformPath[data.path, transform];
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
ArcEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
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;
data.path ← PathFromArc[p0, p1, p2];
};
ArcMaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
Draw yourself into dc as a stroke. However, apply transform to your lower joint iff lo = TRUE, and apply transform to your higher joint iff hi = TRUE. This is for rubberbanding.
data: ArcData ← NARROW[seg.data];
p0,p1,p2: VEC;
pathProc: ImagerPath.PathProc = {moveTo[p0]; arcTo[p1,p2]};
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 ← data.p1;
};
Imager.MaskStroke[dc, pathProc];
};
ArcBuildPathTransform:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
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 ← data.p1;
};
arcTo[p1,p2];
};
BuildConicClass:
PROC []
RETURNS [class: SegmentClass] = {
class ←
NEW[SegmentClassObj ← [
type: $Conic,
copyData: ConicCopyData,
reverse: ConicReverse,
transform: ConicTransform,
endpointMoved: ConicEndPointMoved,
maskStroke: CurveMaskStroke,
maskStrokeTransform: ConicMaskStrokeTransform,
buildPath: CurveBuildPath,
buildPathTransform: ConicBuildPathTransform,
closestPoint: CurveClosestPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
boundBox: CurveBoundBox
]];
ConicData: TYPE = REF ConicDataRec;
ConicDataRec:
TYPE =
RECORD [
p1: VEC,
s: REAL,
path: CubicPaths.Path];
ConicCopyData:
PROC [seg: Segment]
RETURNS [
REF
ANY] = {
Copies the data in the "data" field of seg. This data is class-dependent.
data: ConicData ← NARROW[seg.data];
new: ConicData ← NEW[ConicDataRec];
new.p1 ← data.p1;
new.path ← CubicPathsExtras.CopyPath[data.path];
RETURN[new];
};
ConicReverse:
PROC [seg: Segment] = {
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: ConicData ← NARROW[seg.data];
CubicPathsExtras.ReversePath[data.path];
};
ConicTransform:
PROC [transform: ImagerTransformation.Transformation, seg: Segment] = {
Apply the given transformation to all internal data of the segment. It is now in a new position, orientation, scaling, or skewing.
data: ConicData ← NARROW[seg.data];
data.p1 ← ImagerTransformation.Transform[transform, data.p1];
CubicPaths.TransformPath[data.path, transform];
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
ConicEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
data: ConicData ← 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;
data.path ← PathFromConic[p0, p1, p2, data.s];
};
ConicMaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
Draw yourself into dc as a stroke. However, apply transform to your lower joint iff lo = TRUE, and apply transform to your higher joint iff hi = TRUE. This is for rubberbanding.
data: ConicData ← NARROW[seg.data];
p0,p1,p2: VEC;
pathProc: ImagerPath.PathProc = {moveTo[p0]; conicTo[p1,p2,data.s]};
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 ← data.p1;
};
Imager.MaskStroke[dc, pathProc];
};
ConicBuildPathTransform:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
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: ConicData ← 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 ← data.p1;
};
conicTo[p1,p2,data.s];
};
TransformEndPoints:
PROC [loPt, hiPt: Point, lo, hi:
BOOL, transform: ImagerTransformation.Transformation]
RETURNS [newLo, newHi:
VEC] ~ {
IF lo THEN newLo ← ImagerTransformation.Transform[transform, [loPt[1],loPt[2]]]
ELSE newLo ← [loPt[1],loPt[2]];
IF hi THEN newHi ← ImagerTransformation.Transform[transform, [hiPt[1],hiPt[2]]]
ELSE newHi ← [hiPt[1],hiPt[2]];
};
BuildBezierClass:
PROC []
RETURNS [class: SegmentClass] = {
class ←
NEW[SegmentClassObj ← [
type: $Bezier,
copyData: BZCopyData,
reverse: BZReverse,
transform: BZTransform,
endpointMoved: BZEndPointMoved,
maskStroke: CurveMaskStroke,
maskStrokeTransform: CubicMaskStrokeTransform,
buildPath: CurveBuildPath,
buildPathTransform: CubicBuildPathTransform,
closestPoint: CurveClosestPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
boundBox: CurveBoundBox
]];
BezierData: TYPE = REF BezierDataRec;
BezierDataRec:
TYPE =
RECORD [
b1, b2: VEC,
path: CubicPaths.Path];
BZCopyData:
PROC [seg: Segment]
RETURNS [
REF
ANY] = {
Copies the data in the "data" field of seg. This data is class-dependent.
data: BezierData ← NARROW[seg.data];
new: BezierData ← NEW[BezierDataRec];
new.b1 ← data.b1; new.b2 ← data.b2;
new.path ← CubicPathsExtras.CopyPath[data.path];
RETURN[new];
};
BZReverse:
PROC [seg: Segment] = {
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: BezierData ← NARROW[seg.data];
temp: VEC ← data.b1;
data.b1 ← data.b2;
data.b2 ← temp;
CubicPathsExtras.ReversePath[data.path];
};
BZTransform:
PROC [transform: ImagerTransformation.Transformation, seg: Segment] = {
Apply the given transformation to all internal data of the segment. It is now in a new position, orientation, scaling, or skewing.
data: BezierData ← NARROW[seg.data];
data.b1 ← ImagerTransformation.Transform[transform, data.b1];
data.b2 ← ImagerTransformation.Transform[transform, data.b2];
CubicPaths.TransformPath[data.path, transform];
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
BZEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
data: BezierData ← NARROW[seg.data];
IF lo THEN data.path.cubics[0].b0 ← [newPoint[1], newPoint[2]]
ELSE data.path.cubics[0].b3 ← [newPoint[1], newPoint[2]];
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
BuildCubicSplineClass:
PROC []
RETURNS [class: SegmentClass] = {
class ←
NEW[SegmentClassObj ← [
type: $CubicSpline,
copyData: CSCopyData,
reverse: CSReverse,
transform: CSTransform,
endpointMoved: CSEndPointMoved,
maskStroke: CurveMaskStroke,
maskStrokeTransform: CubicMaskStrokeTransform,
buildPath: CurveBuildPath,
buildPathTransform: CubicBuildPathTransform,
closestPoint: CurveClosestPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
boundBox: CurveBoundBox
]];
CubicSplineData: TYPE = REF CubicSplineDataRec;
CubicSplineDataRec:
TYPE =
RECORD [
cps: CubicSplines.KnotSequence,
type: CubicSplines.SplineType,
path: CubicPaths.Path];
CSCopyData:
PROC [seg: Segment]
RETURNS [
REF
ANY] = {
Copies the data in the "data" field of seg. This data is class-dependent.
data: CubicSplineData ← NARROW[seg.data];
new: CubicSplineData ← NEW[CubicSplineDataRec];
new.cps ← NEW[CubicSplines.KnotSequenceRec[data.cps.length]];
FOR i:
NAT
IN [0..new.cps.length)
DO
new.cps[i] ← data.cps[i];
ENDLOOP;
new.type ← data.type;
new.path ← CubicPathsExtras.CopyPath[data.path];
RETURN[new];
};
CSReverse:
PROC [seg: Segment] = {
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: CubicSplineData ← NARROW[seg.data];
temp: CubicSplines.FPCoords;
last: NAT ← data.cps.length-1;
FOR i:
NAT
IN [0..data.cps.length/2)
DO
temp ← data.cps[i];
data.cps[i] ← data.cps[last-i];
data.cps[last-i] ← temp;
ENDLOOP;
CubicPathsExtras.ReversePath[data.path];
};
CSTransform:
PROC [transform: ImagerTransformation.Transformation, seg: Segment] = {
Apply the given transformation to all internal data of the segment. It is now in a new position, orientation, scaling, or skewing.
data: CubicSplineData ← NARROW[seg.data];
FOR i:
NAT
IN [0..data.cps.length)
DO
vec: VEC ← [data.cps[i][X], data.cps[i][Y]];
vec ← ImagerTransformation.Transform[transform, vec];
data.cps[i][X] ← vec.x;
data.cps[i][Y] ← vec.y;
ENDLOOP;
CubicPaths.TransformPath[data.path, transform];
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
CSEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
data: CubicSplineData ← NARROW[seg.data];
IF lo
THEN {
data.cps[0] ← newPoint;
data.path.cubics[0].b0.x ← newPoint[1];
data.path.cubics[0].b0.y ← newPoint[2];
}
ELSE {
data.cps[data.cps.length-1] ← newPoint;
data.path.cubics[data.path.cubics.length-1].b3 ← [newPoint[1], newPoint[2]];
};
CubicPathsExtras.UpdateBounds[data.path];
UpdateBoundBox[data.path.bounds, seg.boundBox];
};
CurveMaskStroke:
PROC [dc: Imager.Context, seg: Segment] = {
Draw yourself into dc as a stroke. Assume that strokeWidth, and color are already set.
path: CubicPaths.Path ← PathFromSeg[seg];
pathProc: ImagerPath.PathProc = {CubicPaths.EnumeratePath[path, moveTo, curveTo]};
Imager.MaskStroke[dc, pathProc];
};
CurveBuildPath:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
Assume that the Imager's current point is at your lower joint. Call the procedures given to draw yourself (moveTo is left out -- you shouldn't need it).
path: CubicPaths.Path ← PathFromSeg[seg];
moveTo: ImagerPath.MoveToProc ← {};
CubicPaths.EnumeratePath[path, moveTo, curveTo];
};
CubicMaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
Draw yourself into dc as a stroke. However, apply transform to your lower joint iff lo = TRUE, and apply transform to your higher joint iff hi = TRUE. This is for rubberbanding.
path: CubicPaths.Path ← PathFromSeg[seg];
pathProc: ImagerPath.PathProc = {CubicPaths.EnumeratePath[path, moveTo, curveTo]};
IF entire
THEN {
CubicPaths.TransformPath[path, transform];
Imager.MaskStroke[dc, pathProc];
CubicPaths.TransformPath[path, ImagerTransformation.Invert[transform]];
}
ELSE {
oldLo, oldHi: VEC;
[oldLo, oldHi] ← TransformPathEnds[path, transform, lo, hi];
Imager.MaskStroke[dc, pathProc];
RestorePathEnds[path, oldLo, oldHi];
};
};
CubicBuildPathTransform:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
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.
path: CubicPaths.Path ← PathFromSeg[seg];
moveTo: ImagerPath.MoveToProc ← {};
IF entire
THEN {
CubicPaths.TransformPath[path, transform];
CubicPaths.EnumeratePath[path, moveTo, curveTo];
CubicPaths.TransformPath[path, ImagerTransformation.Invert[transform]];
}
ELSE {
oldLo, oldHi: VEC;
[oldLo, oldHi] ← TransformPathEnds[path, transform, lo, hi];
CubicPaths.EnumeratePath[path, moveTo, curveTo];
RestorePathEnds[path, oldLo, oldHi];
};
};
TransformPathEnds:
PROC [path: CubicPaths.Path, transform: ImagerTransformation.Transformation, lo, hi:
BOOL]
RETURNS [oldLo, oldHi:
VEC] ~ {
last: NAT ← path.cubics.length-1;
oldLo ← path.cubics[0].b0;
oldHi ← path.cubics[last].b3;
IF lo THEN path.cubics[0].b0 ← ImagerTransformation.Transform[transform, oldLo];
IF hi THEN path.cubics[last].b3 ← ImagerTransformation.Transform[transform, oldHi];
};
RestorePathEnds:
PROC [path: CubicPaths.Path, oldLo, oldHi:
VEC] ~ {
path.cubics[0].b0 ← oldLo;
path.cubics[path.cubics.length-1].b3 ← oldHi;
};
CurveClosestPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, success:
BOOL] = {
Used for hit testing. Find the nearest point on seg to testPoint.
path: CubicPaths.Path ← PathFromSeg[seg];
pt: VEC ← CubicPathsExtras.ClosestPoint[[testPoint[1],testPoint[2]], path];
RETURN[[pt.x, pt.y], TRUE];
};
CurveBoundBox:
PROC [seg: Segment] = {
Creates a new box which bounds this segment (including stroke width).
path: CubicPaths.Path ← PathFromSeg[seg];
UpdateBoundBox[path.bounds, seg.boundBox];
};
UpdateBoundBox:
PROC[r: Imager.Rectangle, box: BoundBox] = {
box^ ← [loX: r.x, loY: r.y, hiX: r.x+r.w, hiY: r.y+r.h];
};
PathFromSeg:
PROC [seg: Segment]
RETURNS [path: CubicPaths.Path] ~ {
WITH seg.data
SELECT
FROM
type: BezierData => path ← type.path;
type: ConicData => path ← type.path;
type: ArcData => path ← type.path;
type: CubicSplineData => path ← type.path;
ENDCASE => ERROR;
RETURN[path];
};
BezierRef: TYPE = REF Cubic2.Bezier;
PathFromConic:
PROC [p0, p1, p2: Point, r:
REAL]
RETURNS [path: CubicPaths.Path] ~ {
bList: LIST OF BezierRef;
joint: VEC ← [p0[1],p0[2]];
bproc: ImagerPath.CurveToProc = {
new: BezierRef ← NEW[Cubic2.Bezier ← [joint, p1, p2, p3]];
bList ← CONS[new, bList];
joint ← p3;
};
ImagerPath.ConicToCurves[[p0[1],p0[2]], [p1[1],p1[2]], [p2[1],p2[2]], r, bproc];
path ← PathFromList[bList];
RETURN[path];
};
PathFromArc:
PROC [p0, p1, p2: Point]
RETURNS [path: CubicPaths.Path] ~ {
Conic: TYPE = REF ConicRec;
ConicRec: TYPE = RECORD[p1, p2: VEC, r: REAL];
joint: VEC ← [p0[1],p0[2]];
rList, list: LIST OF Conic;
bList: LIST OF BezierRef;
proc: ImagerPath.ConicToProc = {
new: Conic ← NEW[ConicRec ← [p1, p2, r]];
rList ← CONS[new,rList];
};
bproc: ImagerPath.CurveToProc = {
new: BezierRef ← NEW[Cubic2.Bezier ← [joint, p1, p2, p3]];
bList ← CONS[new, bList];
joint ← p3;
};
ImagerPath.ArcToConics[[p0[1],p0[2]], [p1[1],p1[2]], [p2[1],p2[2]], proc];
now have a list of conics in reverse order. Reverse it.
FOR l:
LIST
OF Conic ← rList, l.rest
UNTIL l=
NIL
DO
list ← CONS[l.first, list];
ENDLOOP;
Need a list of Bezier
FOR l:
LIST
OF Conic ← list, l.rest
UNTIL l=
NIL
DO
ImagerPath.ConicToCurves[joint, l.first.p1, l.first.p2, l.first.r, bproc];
joint ← l.first.p2;
ENDLOOP;
now we have a list of Bezier.
path ← PathFromList[bList];
RETURN[path];
};
PathFromList:
PROC [list:
LIST
OF BezierRef]
RETURNS [CubicPaths.Path] ~ {
count: NAT ← 0;
path: CubicPaths.Path ← NEW[CubicPaths.PathRec];
FOR l:
LIST
OF BezierRef ← list, l.rest
UNTIL l=
NIL
DO
count ← count+1;
ENDLOOP;
path.cubics ← NEW[CubicPaths.PathSequence[count]];
Put the cubics in the sequence. CONS put them on the list "backwards"
FOR l:
LIST
OF BezierRef ← list, l.rest
UNTIL l=
NIL
DO
count ← count-1; --count starts at number of entries, not last entry
path.cubics[count] ← l.first^;
ENDLOOP;
CubicPathsExtras.UpdateBounds[path];
RETURN[path];
};
The Straight-Line (line) Segment Class
LineData: TYPE = REF LineDataRec;
LineDataRec: TYPE = RECORD []; --doesn't need any data
BuildLineClass:
PROC []
RETURNS [lineClass: SegmentClass] = {
lineClass ←
NEW[SegmentClassObj ← [
type: $Line,
copyData: LineCopyData,
reverse: LineReverse,
transform: LineTransform,
endpointMoved: LineEndPointMoved,
maskStroke: LineMaskStroke,
maskStrokeTransform: LineMaskStrokeTransform,
buildPath: LineBuildPath,
buildPathTransform: LineBuildPathTransform,
closestPoint: LineClosestPoint,
closestPointAndTangent: LineClosestPointAndTangent,
boundBox: LineBoundBox
]];
LineCopyData:
PROC [seg: Segment]
RETURNS [data:
REF
ANY] = {
lineSegment: LineData ← NARROW[seg.data];
data ← NEW[LineDataRec];
};
LineReverse:
PROC [seg: Segment] = {
Lines are simple. Nothing to do.
};
LineTransform:
PROC [transform: ImagerTransformation.Transformation, seg: Segment] = {
Lines are simple. Nothing to do.
};
LineEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
Lines are simple. Nothing to do.
};
LineMaskStroke:
PROC [dc: Imager.Context, seg: Segment] = {
Imager.MaskVector[dc, [seg.lo[1], seg.lo[2]], [seg.hi[1], seg.hi[2]]];
};
LineMaskStrokeTransform:
PROC [dc: Imager.Context, seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi:
BOOL] = {
loPoint, hiPoint: Point;
loPoint ←
IF lo
OR entire
THEN GGTransform.Transform[transform, seg.lo]
ELSE [seg.lo[1], seg.lo[2]];
hiPoint ←
IF hi
OR entire
THEN GGTransform.Transform[transform, seg.hi]
ELSE [seg.hi[1], seg.hi[2]];
Imager.MaskVector[dc, [loPoint[1], loPoint[2]], [hiPoint[1], hiPoint[2]]];
};
LineBuildPath: BuildPathProc = {
lineTo[ [seg.hi[1], seg.hi[2]] ];
};
LineBuildPathTransform: BuildPathTransformProc = {
hiPoint: Point;
hiPoint ←
IF hi
OR entire
THEN GGTransform.Transform[transform, seg.hi]
ELSE [seg.hi[1], seg.hi[2]];
lineTo[ [hiPoint[1], hiPoint[2]] ];
};
LineClosestPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, success:
BOOL] = {
edge: Edge;
edge ← GetEdgeFromPool[];
GGLines.FillEdge[seg.lo, seg.hi, edge];
point ← GGLines.NearestPointOnEdge[testPoint, edge];
ReturnEdgeToPool[edge];
success ← TRUE;
};
LineClosestPointAndTangent:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, tangent: Vector, success:
BOOL] = {
edge: Edge;
edge ← GetEdgeFromPool[];
GGLines.FillEdge[seg.lo, seg.hi, edge];
point ← GGLines.NearestPointOnEdge[testPoint, edge];
ReturnEdgeToPool[edge];
tangent ← GGLines.DirectionOfLine[edge.line];
success ← TRUE;
};
LineBoundBox:
PROC [seg: Segment] = {
Find the boundBox of the segment, allowing for the size of control points (or the segment width, whichever is larger).
loX, loY, hiX, hiY: REAL;
loX ← MIN[seg.lo[1], seg.hi[1]];
hiX ← MAX[seg.lo[1], seg.hi[1]];
loY ← MIN[seg.lo[2], seg.hi[2]];
hiY ← MAX[seg.lo[2], seg.hi[2]];
seg.boundBox^ ← [loX, loY, hiX, hiY];
};
GetEdgeFromPool:
PROC []
RETURNS [edge: Edge] = {
To be used to reduce CreateEdge allocations.
IF globalEdgePoolIndex = MaxEdges THEN ERROR;
edge ← globalEdgePool[globalEdgePoolIndex];
globalEdgePoolIndex ← globalEdgePoolIndex + 1;
};
ReturnEdgeToPool:
PROC [edge: Edge] = {
IF globalEdgePoolIndex = 0 THEN ERROR;
globalEdgePoolIndex ← globalEdgePoolIndex - 1;
globalEdgePool[globalEdgePoolIndex] ← edge;
};
END.