GGSegmentImplA.mesa
Copyright c 1985, 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on May 1, 1987 3:06:52 pm PDT
Contents: Procedures which implement the Gargoyle segment classes.
Bier, March 1, 1987 10:09:28 pm PST
DIRECTORY
Atom, Cubic2, CubicPaths, CubicSplines, Feedback, GGBasicTypes, GGBoundBox, GGCubic2, GGInterfaceTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, CodeTimer, GGTransform, GGUtility, Vectors2d, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, RealFns, Rope;
GGSegmentImplA:
CEDAR
PROGRAM
IMPORTS Atom, CubicSplines, CubicPaths, GGBoundBox, GGCubic2, Feedback, GGParseIn, GGParseOut, GGSegment, CodeTimer, GGTransform, GGUtility, Vectors2d, IO, Imager, ImagerPath, ImagerTransformation, RealFns, Rope
EXPORTS GGSegment = BEGIN
VEC: TYPE = Imager.VEC;
StrokeEnd: TYPE = Imager.StrokeEnd;
X: NAT = CubicSplines.X;
Y: NAT = CubicSplines.Y;
Bezier: TYPE = Cubic2.Bezier;
BezierRef: TYPE = REF Cubic2.Bezier;
BitVector: TYPE = GGModelTypes.BitVector;
BoundBox: TYPE = GGModelTypes.BoundBox;
BuildPathProc: TYPE = GGSegmentTypes.BuildPathProc;
BuildPathTransformProc: TYPE = GGSegmentTypes.BuildPathTransformProc;
ClassDef: TYPE = REF ClassDefRec;
ClassDefRec: TYPE = GGSegment.ClassDefRec;
DefaultData: TYPE = GGInterfaceTypes.DefaultData;
Circle: TYPE = GGBasicTypes.Circle;
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;
Vector: TYPE = GGBasicTypes.Vector;
SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal;
SequenceOfRealObj: TYPE = GGBasicTypes.SequenceOfRealObj;
defaultStrokeWidth: REAL ← 2.0;
defaultStrokeEnd: StrokeEnd ← round;
segmentClasses: LIST OF ClassDef;
Problem: ERROR [msg: Rope.ROPE] = Feedback.Problem;
Generic Segment Class Procedures
RegisterSegmentClass:
PUBLIC
PROC [classDef: ClassDef] = {
segmentClasses ← CONS[classDef, segmentClasses];
};
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;
ERROR Problem[msg: IO.PutFR["SegmentClass %g is unknown", [rope[Atom.GetPName[type]]] ]];
};
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,
strokeEnd: seg.strokeEnd,
dashed: seg.dashed,
pattern: GGUtility.CopyPattern[seg.pattern], -- this is a REF, so must copy
offset: seg.offset,
length: seg.length,
color: seg.color,
lo: seg.lo, hi: seg.hi,
TselectedInFull: seg.TselectedInFull,
bBox: GGBoundBox.CopyBoundBox[seg.bBox],
data: copyData,
props: seg.props
]];
};
CopyLooks:
PUBLIC PROC [from, to: Segment] = {
to.color ← from.color;
to.strokeWidth ← from.strokeWidth;
to.strokeEnd ← from.strokeEnd;
to.dashed ← from.dashed;
to.pattern ← GGUtility.CopyPattern[from.pattern]; -- this is a REF, so must copy
to.offset ← from.offset;
to.length ← from.length;
};
SetDefaults:
PUBLIC
PROC [seg: Segment, defaults: DefaultData] = {
seg.strokeEnd ← defaults.strokeEnd;
seg.dashed ← defaults.dashed;
seg.pattern ← GGUtility.CopyPattern[defaults.pattern]; -- this is a REF, so must copy
seg.offset ← defaults.offset;
seg.length ← defaults.length;
seg.color ← defaults.strokeColor;
seg.class.setStrokeWidth[seg, defaults.strokeWidth];
};
SameLooks:
PUBLIC PROC [seg1: Segment, seg2: Segment]
RETURNS [
BOOL] = {
IF seg1.dashed # seg2.dashed THEN RETURN[FALSE];
IF seg1.strokeEnd # seg2.strokeEnd THEN RETURN[FALSE];
IF seg1.strokeWidth # seg2.strokeWidth THEN RETURN[FALSE];
IF seg1.dashed
THEN {
IF seg1.offset # seg2.offset THEN RETURN[FALSE];
IF seg1.length # seg2.length THEN RETURN[FALSE];
IF NOT EqualSequence[seg1.pattern, seg2.pattern] THEN RETURN[FALSE];
};
IF NOT GGUtility.EquivalentColors[seg1.color, seg2.color] THEN RETURN[FALSE];
RETURN[TRUE];
};
EqualSequence:
PROC [s1, s2: SequenceOfReal]
RETURNS [
BOOL] = {
IF s1.len # s2.len THEN RETURN[FALSE];
FOR i:
NAT
IN [0..s1.len)
DO
IF s1[i] # s2[i] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
ReverseSegment:
PUBLIC
PROC [seg: Segment] = {
temp: Point;
temp ← seg.lo;
seg.lo ← seg.hi;
seg.hi ← temp;
seg.class.reverse[seg];
};
OpenUpSegment:
PUBLIC
PROC [seg: Segment] = {
Changes all of the cyclic segments in the traj to non-cyclic segs
IF seg.class.type=$CubicSpline
THEN {
data: CubicSplineData ← NARROW[seg.data];
IF data.type=cyclicAL
THEN {
data.type ← naturalAL;
data.path ← CubicPaths.PathFromCubic[CubicSplines.MakeSpline[data.cps, data.type]]; -- remake path data
};
};
};
TransformSegment:
PUBLIC
PROC [seg: Segment, transform: ImagerTransformation.Transformation] = {
seg.lo ← GGTransform.Transform[transform, seg.lo];
seg.hi ← GGTransform.Transform[transform, seg.hi];
seg.class.transform[seg, transform];
};
TranslateSegment:
PUBLIC
PROC [seg: Segment, vector: Vector] = {
A convenience routine which does a TransformSegment, where transform is a simple translation.
transform: ImagerTransformation.Transformation;
transform ← ImagerTransformation.Translate[[vector.x, vector.y]];
TransformSegment[seg, transform];
};
MoveEndPointSegment:
PUBLIC
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
IF lo THEN seg.lo ← newPoint
ELSE seg.hi ← newPoint;
seg.class.endPointMoved[seg, lo, newPoint];
};
ConicData: TYPE = REF ConicDataRec;
ConicDataRec:
TYPE =
RECORD [
p1: VEC,
s: REAL,
p1Selected: SelectedObjectData,
path: CubicPaths.Path
];
BuildConicClass:
PROC []
RETURNS [class: SegmentClass] = {
OPEN GGSegment;
class ←
NEW[SegmentClassObj ← [
type: $Conic,
copyData: ConicCopyData,
reverse: ConicReverse,
boundBox: ConicBoundBox,
tightBox: ConicTightBox,
Transforming
transform: ConicTransform,
endPointMoved: ConicEndPointMoved,
controlPointMoved: ConicControlPointMoved,
Drawing
buildPath: ConicBuildPath,
buildPathTransform: ConicBuildPathTransform,
Control Point Access
controlPointGet: ConicControlPointGet,
controlPointCount: ConicControlPointCount,
controlPointFieldSet: ConicFieldSet,
controlPointFieldGet: ConicFieldGet,
Hit Testing
closestPoint: CurveClosestPoint,
closestControlPoint: ConicClosestControlPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
asSimpleCurve: NoOpAsSimpleCurve,
Editing
addJoint: GGSegment.NoOpAddJoint,
Textual Description
describe: ConicDescribe,
fileOut: ConicFileOut,
fileIn: ConicFileIn,
Editing
setStrokeWidth: ConicSetStrokeWidth
]];
};
ConicSetStrokeWidth:
PROC [seg: Segment, strokeWidth:
REAL] = {
seg.strokeWidth ← strokeWidth;
UpdateConicBoundBox[seg];
};
MakeConic:
PUBLIC
PROC [p0, p1, p2: Point, r:
REAL, props:
LIST
OF
REF
ANY]
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.x,p1.y], s: r, p1Selected: [FALSE, FALSE, FALSE],
path: PathFromConic[p0, p1, p2, r]
]];
seg ←
NEW[SegmentObj ← [
class: GGSegment.FetchSegmentClass[$Conic],
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
]];
UpdateConicBoundBox[seg];
};
ConicBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ← seg.bBox;
};
ConicTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ← GGBoundBox.NullBoundBox[];
UpdateConicTightBox[seg, bBox];
};
UpdateConicTightBox:
PROC [seg: Segment, bBox: BoundBox] = {
Updates bound box which bounds this segment (including stroke width and control points).
data: ConicData ← NARROW[seg.data];
CurveBoundBox[seg, bBox];
};
UpdateConicBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc
data: ConicData ← NARROW[seg.data];
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
UpdateConicTightBox[seg, seg.bBox];
GGBoundBox.EnlargeByPoint[seg.bBox, [data.p1.x, data.p1.y] ]; -- CP is far from curve
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
ConicCopyData: GGSegmentTypes.CopyDataProc = {
GGSegmentTypes.CopyDataProc
Copies the data in the "data" field of seg. This data is class-dependent.
conicData: ConicData ← NARROW[seg.data];
new: ConicData ← NEW[ConicDataRec];
new.p1 ← conicData.p1;
new.s ← conicData.s;
new.p1Selected ← conicData.p1Selected;
new.path ← CubicPaths.CopyPath[conicData.path];
RETURN[new];
};
ConicReverse:
PROC [seg: Segment] = {
GGSegmentTypes.ReverseProc
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: ConicData ← NARROW[seg.data];
CubicPaths.ReversePath[data.path];
};
ConicBuildPath:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathProc
data: ConicData ← NARROW[seg.data];
conicTo[data.p1, seg.hi, data.s];
};
ConicBuildPathTransform:
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: 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 ← IF controlPoints[0] THEN ImagerTransformation.Transform[transform, data.p1]
ELSE data.p1;
};
conicTo[p1,p2,data.s];
};
ConicTransform:
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: ConicData ← NARROW[seg.data];
data.p1 ← ImagerTransformation.Transform[transform, data.p1];
CubicPaths.TransformPath[data.path, transform];
UpdateConicBoundBox[seg];
};
ConicEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
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];
UpdateConicBoundBox[seg];
};
ConicControlPointMoved:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum:
NAT] = {
GGSegmentTypes.ControlPointMovedProc
p1Vec: VEC;
p1: Point;
data: ConicData ← NARROW[seg.data];
IF controlPointNum#0 THEN ERROR;
p1Vec ← ImagerTransformation.Transform[transform, data.p1];
p1 ← [p1Vec.x, p1Vec.y];
data.path ← PathFromConic[seg.lo, p1, seg.hi, data.s];
data.p1 ← p1Vec;
UpdateConicBoundBox[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];
};
Conic Description
ConicDescribe:
PROC [seg: Segment, self, lo, hi:
BOOL, cps: BitVector]
RETURNS [rope: Rope.
ROPE] = {
rope ← "Parabolic Conic";
};
ConicFileOut:
PROC [seg: Segment, f:
IO.
STREAM] = {
GGSegmentTypes.FileOutProc
data: ConicData ← NARROW[seg.data];
p1: Point ← [data.p1.x, data.p1.y];
GGParseOut.WritePoint[f, p1];
f.PutF[" %g", [real[data.s]]];
};
ConicFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
s: REAL;
p1: Point;
p1 ← GGParseIn.ReadPoint[f];
s ← GGParseIn.ReadBlankAndReal[f];
seg ← MakeConic[loPoint, p1, hiPoint, s, NIL];
};
ConicFieldSet: GGSegmentTypes.ControlPointFieldSetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]
data: ConicData ← 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;
};
ConicFieldGet: GGSegmentTypes.ControlPointFieldGetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]
data: ConicData ← 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;
};
ConicControlPointGet:
PROC [seg: Segment, controlPointNum:
NAT]
RETURNS [point: Point] = {
GGSegmentTypes.ControlPointGetProc
data: ConicData ← NARROW[seg.data];
IF controlPointNum#0 THEN ERROR;
RETURN[ [data.p1.x, data.p1.y] ];
};
ConicControlPointCount:
PROC [seg: Segment]
RETURNS [controlPointCount:
NAT] = {
GGSegmentTypes.ControlPointCountProc
data: ConicData ← NARROW[seg.data]; -- just for NarrowRefFault if there is a problem
RETURN[1];
};
ConicClosestControlPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, controlPointNum:
NAT, success:
BOOL] = {
GGSegmentTypes.ClosestControlPointProc
data: ConicData ← NARROW[seg.data];
success ← TRUE;
controlPointNum ← 0;
point ← [data.p1.x, data.p1.y];
};
BezierData: TYPE = REF BezierDataRec;
BezierDataRec:
TYPE =
RECORD [
b1Selected, b2Selected: SelectedObjectData,
path: CubicPaths.Path];
BuildBezierClass:
PROC []
RETURNS [class: SegmentClass] = {
OPEN GGSegment;
class ←
NEW[SegmentClassObj ← [
type: $Bezier,
copyData: BZCopyData,
reverse: BZReverse,
boundBox: BZBoundBox,
tightBox: BZTightBox,
Transformations
transform: BZTransform,
endPointMoved: BZEndPointMoved,
controlPointMoved: BZControlPointMoved,
Drawing
buildPath: CurveBuildPath,
buildPathTransform: BZBuildPathTransform,
Control Point Access
controlPointGet: BZControlPointGet,
controlPointCount: BZControlPointCount,
controlPointFieldSet: BZFieldSet,
controlPointFieldGet: BZFieldGet,
Hit Testing
closestPoint: CurveClosestPoint,
closestControlPoint: BZClosestControlPoint,
closestPointAndTangent: GGSegment.NoOpClosestPointAndTangent,
lineIntersection: GGSegment.NoOpLineIntersection,
circleIntersection: GGSegment.NoOpCircleIntersection,
asSimpleCurve: NoOpAsSimpleCurve,
Editing
addJoint: BZAddJoint,
Textual Description
describe: BZDescribe,
fileOut: BZFileOut,
fileIn: BZFileIn,
Editing
setStrokeWidth: BZSetStrokeWidth
]];
};
BZSetStrokeWidth:
PROC [seg: Segment, strokeWidth:
REAL] = {
seg.strokeWidth ← strokeWidth;
UpdateBZBoundBox[seg];
};
MakeBezier:
PUBLIC
PROC [p0, p1, p2, p3: Point, props:
LIST
OF
REF
ANY]
RETURNS [seg: Segment] = {
Create a Bezier segment which passed thru p0 and p3, guided by p1 and p2.
data: BezierData ←
NEW[BezierDataRec ← [
path: NEW[CubicPaths.PathRec]
]];
data.path.cubics ← NEW[CubicPaths.PathSequence[1]];
data.path.cubics[0] ← [p0, p1, p2, p3];
CubicPaths.UpdateBounds[data.path];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$Bezier],
looks: NIL,
strokeWidth: defaultStrokeWidth,
strokeEnd: defaultStrokeEnd,
color: Imager.black,
lo: p0, hi: p3,
bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data,
props: props
]];
UpdateBZBoundBox[seg];
};
BZBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ← seg.bBox;
};
BZTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ← GGBoundBox.NullBoundBox[];
UpdateBZTightBox[seg, bBox];
};
UpdateBZBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc.
Find the bounding hull of the control points. This will bound the curve as well.
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
data: BezierData ← NARROW[seg.data];
cubics: REF CubicPaths.PathSequence ← data.path.cubics;
seg.bBox^ ← [
loX: MIN[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x],
loY: MIN[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y],
hiX: MAX[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x],
hiY: MAX[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y],
null: FALSE, infinite: FALSE];
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
UpdateBZTightBox:
PROC [seg: Segment, bBox: BoundBox] = {
Creates a new box which bounds this Bezier, and does NOT allow for stroke width and control point size.
CurveBoundBox[seg, bBox];
};
BZCopyData: GGSegmentTypes.CopyDataProc = {
GGSegmentTypes.CopyDataProc
Copies the data in the "data" field of seg. This data is class-dependent.
bzData: BezierData ← NARROW[seg.data];
new: BezierData ← NEW[BezierDataRec];
new.b1Selected ← bzData.b1Selected;
new.b2Selected ← bzData.b2Selected;
new.path ← CubicPaths.CopyPath[bzData.path];
RETURN[new];
};
BZReverse:
PROC [seg: Segment] = {
GGSegmentTypes.ReverseProc
The "lo" end and "hi" end have switched roles. Update data structures as necessary.
data: BezierData ← NARROW[seg.data];
CubicPaths.ReversePath[data.path];
};
BZTransform:
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: BezierData ← NARROW[seg.data];
CubicPaths.TransformPath[data.path, transform];
UpdateBZBoundBox[seg];
};
BZEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
data: BezierData ← NARROW[seg.data];
IF lo THEN data.path.cubics[0].b0 ← [newPoint.x, newPoint.y]
ELSE data.path.cubics[0].b3 ← [newPoint.x, newPoint.y];
UpdateBZBoundBox[seg];
};
BZControlPointMoved:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum:
NAT] = {
GGSegmentTypes.ControlPointMovedProc
data: BezierData ← NARROW[seg.data];
SELECT controlPointNum
FROM
0 => data.path.cubics[0].b1 ← ImagerTransformation.Transform[transform, data.path.cubics[0].b1];
1 => data.path.cubics[0].b2 ← ImagerTransformation.Transform[transform, data.path.cubics[0].b2];
ENDCASE => ERROR;
UpdateBZBoundBox[seg];
};
BZBuildPathTransform:
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.
path: CubicPaths.Path ← GetPath[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, oldC1, oldC2: VEC;
[oldLo, oldHi, oldC1, oldC2] ← BZTransformPath[path, transform, lo, hi, controlPoints];
CubicPaths.EnumeratePath[path, moveTo, curveTo];
BZRestorePath[path, oldLo, oldHi, oldC1, oldC2];
};
};
BZTransformPath:
PROC [path: CubicPaths.Path, transform: ImagerTransformation.Transformation, lo, hi:
BOOL, controlPoints: BitVector]
RETURNS [oldLo, oldHi, oldC1, oldC2:
VEC] ~ {
oldLo ← path.cubics[0].b0;
oldHi ← path.cubics[0].b3;
oldC1 ← path.cubics[0].b1;
oldC2 ← path.cubics[0].b2;
IF lo THEN path.cubics[0].b0 ← ImagerTransformation.Transform[transform, oldLo];
IF hi THEN path.cubics[0].b3 ← ImagerTransformation.Transform[transform, oldHi];
IF controlPoints[0] THEN path.cubics[0].b1 ← ImagerTransformation.Transform[transform, path.cubics[0].b1];
IF controlPoints[1] THEN path.cubics[0].b2 ← ImagerTransformation.Transform[transform, path.cubics[0].b2];
};
BZRestorePath:
PROC [path: CubicPaths.Path, oldLo, oldHi, oldC1, oldC2:
VEC] ~ {
path.cubics[0].b0 ← oldLo;
path.cubics[0].b3 ← oldHi;
path.cubics[0].b1 ← oldC1;
path.cubics[0].b2 ← oldC2;
};
Bezier Control Point Access
BZControlPointGet:
PROC [seg: Segment, controlPointNum:
NAT]
RETURNS [point: Point] = {
GGSegmentTypes.ControlPointGetProc
data: BezierData ← NARROW[seg.data];
SELECT controlPointNum
FROM
0 => RETURN [data.path.cubics[0].b1];
1 => RETURN [data.path.cubics[0].b2];
ENDCASE => ERROR;
};
BZControlPointCount:
PROC [seg: Segment]
RETURNS [controlPointCount:
NAT] = {
GGSegmentTypes.ControlPointCountProc
data: BezierData ← NARROW[seg.data]; -- just for NarrowRefFault if there is a problem
RETURN[2];
};
BZFieldSet: GGSegmentTypes.ControlPointFieldSetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]
data: BezierData ← NARROW[seg.data];
SELECT controlPointNum FROM
0 => {
SELECT selectClass
FROM
normal => data.b1Selected.normal ← selected;
hot => data.b1Selected.hot ← selected;
active => data.b1Selected.active ← selected;
ENDCASE => ERROR;
};
1 => {
SELECT selectClass
FROM
normal => data.b2Selected.normal ← selected;
hot => data.b2Selected.hot ← selected;
active => data.b2Selected.active ← selected;
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
BZFieldGet: GGSegmentTypes.ControlPointFieldGetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]
data: BezierData ← NARROW[seg.data];
SELECT controlPointNum FROM
0 => {
SELECT selectClass
FROM
normal => selected ← data.b1Selected.normal;
hot => selected ← data.b1Selected.hot;
active => selected ← data.b1Selected.active;
ENDCASE => ERROR;
};
1 => {
SELECT selectClass
FROM
normal => selected ← data.b2Selected.normal;
hot => selected ← data.b2Selected.hot;
active => selected ← data.b2Selected.active;
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
BZClosestControlPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, controlPointNum:
NAT, success:
BOOL] = {
GGSegmentTypes.ClosestControlPointProc
data: BezierData ← NARROW[seg.data];
-- Consider only the middle two control points -- other two are treated as joints
d0: REAL ← Vectors2d.DistanceSquared[testPoint, data.path.cubics[0].b1];
d1: REAL ← Vectors2d.DistanceSquared[testPoint, data.path.cubics[0].b2];
IF d0 < d1
THEN {
d0 ← RealFns.SqRt[d0];
point ← data.path.cubics[0].b1;
controlPointNum ← 0;
success ← (d0 <= tolerance);
}
ELSE {
d1 ← RealFns.SqRt[d1];
point ← data.path.cubics[0].b2;
controlPointNum ← 1;
success ← (d1 <= tolerance);
};
};
BZAddJoint:
PUBLIC
PROC [seg: Segment, pos: Point]
RETURNS [seg1, seg2: Segment] = {
GGSegmentTypes.AddJointProc
data: BezierData ← NARROW[seg.data];
bezier, bezier1, bezier2: Cubic2.Bezier;
alpha: REAL;
bezier ← data.path.cubics[0];
alpha ← GGCubic2.GetParam[bezier, pos];
[bezier1, bezier2] ← GGCubic2.AlphaSplit[bezier, alpha];
seg1 ← MakeBezier[bezier1.b0, bezier1.b1, bezier1.b2, bezier1.b3, NIL];
seg2 ← MakeBezier[bezier2.b0, bezier2.b1, bezier2.b2, bezier2.b3, NIL];
};
BZDescribe:
PROC [seg: Segment, self, lo, hi:
BOOL, cps: BitVector]
RETURNS [rope: Rope.
ROPE] = {
rope ← "Bezier";
};
BZFileOut:
PROC [seg: Segment, f:
IO.
STREAM] = {
GGSegmentTypes.FileOutProc
data: BezierData ← NARROW[seg.data];
GGParseOut.WritePoint[f, data.path.cubics[0].b1];
f.PutChar[IO.SP];
GGParseOut.WritePoint[f, data.path.cubics[0].b2];
};
BZFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
p1, p2: Point;
p1 ← GGParseIn.ReadPoint[f];
p2 ← GGParseIn.ReadPoint[f];
seg ← MakeBezier[loPoint, p1, p2, hiPoint, NIL];
};
CubicSplineData: TYPE = REF CubicSplineDataRec;
CubicSplineDataRec:
TYPE =
RECORD [
cps: CubicSplines.KnotSequence,
type: CubicSplines.SplineType,
path: CubicPaths.Path,
cpsSelected: SelectionSequence];
SelectionSequence: TYPE = REF SelectionSequenceObj;
SelectionSequenceObj:
TYPE =
RECORD [
seq: SEQUENCE len: NAT OF SelectedObjectData
];
BuildCubicSplineClass:
PROC []
RETURNS [class: SegmentClass] = {
OPEN GGSegment;
class ←
NEW[SegmentClassObj ← [
type: $CubicSpline,
copyData: CSCopyData,
reverse: CSReverse,
boundBox: CSBoundBox,
tightBox: CSTightBox,
Transformations
transform: CSTransform,
endPointMoved: CSEndPointMoved,
controlPointMoved: CSControlPointMoved,
Drawing
buildPath: CurveBuildPath,
buildPathTransform: CSBuildPathTransform,
Control Point Access
controlPointGet: CSControlPointGet,
controlPointCount: CSControlPointCount,
controlPointFieldSet: CSFieldSet,
controlPointFieldGet: CSFieldGet,
Hit Testing
closestPoint: CurveClosestPoint,
closestControlPoint: CSClosestControlPoint,
closestPointAndTangent: NoOpClosestPointAndTangent,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
asSimpleCurve: NoOpAsSimpleCurve,
Textual Description
describe: CSDescribe,
fileOut: CSFileOut,
fileIn: CSFileIn,
Editing
addJoint: CSAddJoint,
setStrokeWidth: CSSetStrokeWidth
]];
};
CSSetStrokeWidth:
PROC [seg: Segment, strokeWidth:
REAL] = {
seg.strokeWidth ← strokeWidth;
UpdateCSBoundBox[seg];
};
MakeCubicSpline:
PUBLIC
PROC [cps: CubicSplines.KnotSequence, type: CubicSplines.SplineType, props:
LIST
OF
REF
ANY]
RETURNS[seg: Segment] = {
Makes a segment of the types defined in CubicSplines. cps should be a sequence of knots (including endpoints). We'll tweek the data to be in the correct form.
coeffs: CubicSplines.CoeffsSequence;
data: CubicSplineData;
newCPs: CubicSplines.KnotSequence;
last: NAT ← cps.length-1;
SELECT type
FROM
-- determine correct number of control points
naturalAL, cyclicAL => newCPs ← NEW[CubicSplines.KnotSequenceRec[cps.length]];
bspline => newCPs ← NEW[CubicSplines.KnotSequenceRec[cps.length+4]];
ENDCASE => ERROR;
copy in new cp data
data ←
NEW[CubicSplineDataRec ← [
cps: newCPs, type: type,
cpsSelected: NEW[SelectionSequenceObj[cps.length]]
]];
seg ←
NEW[SegmentObj ← [
class: FetchSegmentClass[$CubicSpline],
looks: NIL,
strokeWidth: defaultStrokeWidth,
strokeEnd: defaultStrokeEnd,
color: Imager.black,
lo: [cps[0][X], cps[0][Y]],
hi: [cps[last][X], cps[last][Y]],
bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data,
props: props
]];
Now copy in new cp data. Must do this last so that the set routines work.
CSEndPointSet[seg: seg, lo: TRUE, point: FPToPoint[cps[0]]];
CSEndPointSet[seg: seg, lo: FALSE, point: FPToPoint[cps[last]]];
FOR i:
INT
IN [0..seg.class.controlPointCount[seg])
DO
CSControlPointSet[seg, i, FPToPoint[cps[i+1]]];
ENDLOOP;
coeffs ← CubicSplines.MakeSpline[data.cps, type];
data.path ← CubicPaths.PathFromCubic[coeffs]; -- build a path of beziers and compute bboxes.
Fit bounding box
UpdateCSBoundBox[seg: seg, boundsOK: TRUE];
};
CSBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ← seg.bBox;
};
CSTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
The bound box which bounds this segment, NOT including stroke width and control points.
bBox ← GGBoundBox.NullBoundBox[];
CurveBoundBox[seg, bBox];
};
UpdateCSBoundBox:
PROC [seg: Segment, boundsOK:
BOOL ←
FALSE] = {
GGSegmentTypes.BoundBoxProc
data: CubicSplineData ← NARROW[seg.data];
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REAL ← MAX[cpHalf, strokeWidth];
IF data.type = bspline THEN KnotBoundBox[seg]
ELSE {
CurveBoundBox[seg, seg.bBox, boundsOK];
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
};
CSCopyData: GGSegmentTypes.CopyDataProc = {
GGSegmentTypes.CopyDataProc
Copies the data in the "data" field of seg. This data is class-dependent.
csData: CubicSplineData ← NARROW[seg.data];
new: CubicSplineData ← NEW[CubicSplineDataRec];
cpCount: NAT ← seg.class.controlPointCount[seg];
new.cps ← NEW[CubicSplines.KnotSequenceRec[csData.cps.length]];
new.cpsSelected ← NEW[SelectionSequenceObj[cpCount]];
FOR i:
NAT
IN [0..new.cps.length)
DO
new.cps[i] ← csData.cps[i];
ENDLOOP;
FOR i:
NAT
IN [0..cpCount)
DO
new.cpsSelected[i] ← csData.cpsSelected[i];
ENDLOOP;
new.type ← csData.type;
new.path ← CubicPaths.CopyPath[csData.path];
RETURN[new];
};
CSReverse:
PROC [seg: Segment] = {
GGSegmentTypes.ReverseProc
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;
CubicPaths.ReversePath[data.path];
};
CSBuildPathTransform:
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: CubicSplineData ← NARROW[seg.data];
moveTo: ImagerPath.MoveToProc ← {};
IF entire
THEN {
CubicPaths.TransformPath[data.path, transform];
CubicPaths.EnumeratePath[data.path, moveTo, curveTo];
CubicPaths.TransformPath[data.path, ImagerTransformation.Invert[transform]];
}
ELSE {
coeffs: CubicSplines.CoeffsSequence;
cpsCopy: CubicSplines.KnotSequence ← NEW[CubicSplines.KnotSequenceRec[data.cps.length]]; -- save copy of cps
FOR i:
INT
IN [0..data.cps.length)
DO
cpsCopy[i] ← data.cps[i];
ENDLOOP;
FOR i:
INT
IN [0..seg.class.controlPointCount[seg])
DO
IF controlPoints[i] THEN CSControlPointSet[seg, i, ImagerTransformation.Transform[transform, CSControlPointGet[seg, i]]];
ENDLOOP;
IF lo THEN CSEndPointSet[seg: seg, lo: TRUE, point: ImagerTransformation.Transform[transform, seg.lo]];
IF hi THEN CSEndPointSet[seg: seg, lo: FALSE, point: ImagerTransformation.Transform[transform, seg.hi]];
coeffs ← CubicSplines.MakeSpline[data.cps, data.type];
data.path ← CubicPaths.PathFromCubic[coeffs];
CubicPaths.EnumeratePath[data.path, moveTo, curveTo];
data.cps ← cpsCopy;
coeffs ← CubicSplines.MakeSpline[data.cps, data.type]; -- restore old spline data
data.path ← CubicPaths.PathFromCubic[coeffs];
};
};
CSTransform:
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: 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];
UpdateCSBoundBox[seg];
};
CSEndPointMoved:
PROC [seg: Segment, lo:
BOOL, newPoint: Point] = {
GGSegmentTypes.EndPointMovedProc
data: CubicSplineData ← NARROW[seg.data];
coeffs: CubicSplines.CoeffsSequence;
CSEndPointSet[seg, lo, newPoint];
coeffs ← CubicSplines.MakeSpline[data.cps, data.type]; -- must recompute coeffs & path
data.path ← CubicPaths.PathFromCubic[coeffs];
UpdateCSBoundBox[seg];
};
CSControlPointMoved:
PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum:
NAT] = {
GGSegmentTypes.ControlPointMovedProc
data: CubicSplineData ← NARROW[seg.data];
coeffs: CubicSplines.CoeffsSequence;
CSControlPointSet[seg, controlPointNum, ImagerTransformation.Transform[transform, CSControlPointGet[seg, controlPointNum]]];
coeffs ← CubicSplines.MakeSpline[data.cps, data.type];
data.path ← CubicPaths.PathFromCubic[coeffs];
UpdateCSBoundBox[seg];
};
CSDescribe:
PROC [seg: Segment, self, lo, hi:
BOOL, cps: BitVector]
RETURNS [rope: Rope.
ROPE] = {
data: CubicSplineData ← NARROW[seg.data];
SELECT data.type
FROM
naturalAL => rope ← "Natural";
cyclicAL => rope ← "Cyclic Natural";
bspline => rope ← "BSpline";
ENDCASE => ERROR;
};
CSFileOut:
PROC [seg: Segment, f:
IO.
STREAM] = {
GGSegmentTypes.FileInProc
data: CubicSplineData ← NARROW[seg.data];
cpCount: NAT ← seg.class.controlPointCount[seg];
SELECT data.type
FROM
naturalAL => f.PutF["Type: Natural "];
cyclicAL => f.PutF["Type: CyclicNatural "];
bspline => f.PutF["Type: BSpline "];
ENDCASE => ERROR;
f.PutF["%g ", [integer[cpCount]] ];
FOR i:
INT
IN [0..cpCount)
DO
GGParseOut.WritePoint[f, CSControlPointGet[seg, i]];
f.PutChar[' ];
ENDLOOP;
};
CSFileIn:
PROC [f:
IO.
STREAM, loPoint, hiPoint: Point, version:
REAL]
RETURNS [seg: Segment] = {
GGSegmentTypes.FileInProc
cps: CubicSplines.KnotSequence;
type: CubicSplines.SplineType;
typeWord: Rope.ROPE;
cyclic, ok: BOOL;
IF version >= 8608.12
THEN {
GGParseIn.ReadBlankAndRope[f, "Type:"];
typeWord ← GGParseIn.ReadBlankAndWord[f];
IF Rope.Equal[typeWord, "Natural"] THEN type ← naturalAL
ELSE IF Rope.Equal[typeWord, "CyclicNatural"] THEN type ← cyclicAL
ELSE IF Rope.Equal[typeWord, "BSpline"] THEN type ← bspline
ELSE ERROR;
}
ELSE
IF version >= 8607.22
THEN {
GGParseIn.ReadBlankAndRope[f, "Cyclic:"];
[truth: cyclic, good: ok] ← GGParseIn.ReadBOOL[f, version];
IF NOT ok THEN ERROR; -- Bad format in input file. Expecting BOOL.
IF cyclic THEN type ← cyclicAL ELSE type ← naturalAL;
}
ELSE type ← naturalAL;
cps ← NEW[CubicSplines.KnotSequenceRec[GGParseIn.ReadBlankAndNAT[f]+2]];
FOR i:
INT
IN [1..cps.length-1)
DO
cps[i] ← PointToFP[GGParseIn.ReadPoint[f]];
ENDLOOP;
IF type = cyclicAL AND (loPoint # hiPoint) THEN ERROR; -- both joints of cyclic spline should have the same position
cps[0] ← PointToFP[loPoint];
cps[cps.length-1] ← PointToFP[hiPoint];
seg ← MakeCubicSpline[cps, type, NIL];
};
CSFieldSet: GGSegmentTypes.ControlPointFieldSetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]
data: CubicSplineData ← NARROW[seg.data];
SELECT selectClass
FROM
normal => data.cpsSelected[controlPointNum].normal ← selected;
hot => data.cpsSelected[controlPointNum].hot ← selected;
active => data.cpsSelected[controlPointNum].active ← selected;
ENDCASE => ERROR;
};
CSFieldGet: GGSegmentTypes.ControlPointFieldGetProc = {
[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]
data: CubicSplineData ← NARROW[seg.data];
SELECT selectClass
FROM
normal => selected ← data.cpsSelected[controlPointNum].normal;
hot => selected ← data.cpsSelected[controlPointNum].hot;
active => selected ← data.cpsSelected[controlPointNum].active;
ENDCASE => ERROR;
};
CSControlPointGet:
PROC [seg: Segment, controlPointNum:
NAT]
RETURNS [point: Point] = {
GGSegmentTypes.ControlPointGetProc
data: CubicSplineData ← NARROW[seg.data];
SELECT data.type
FROM
naturalAL, cyclicAL => {
IF controlPointNum >= data.cps.length-2 THEN ERROR;
RETURN [FPToPoint[data.cps[controlPointNum+1]]];
};
bspline => {
IF controlPointNum >= data.cps.length-4 THEN ERROR;
RETURN [FPToPoint[data.cps[controlPointNum+3]]];
};
ENDCASE => ERROR;
};
CSControlPointCount:
PROC [seg: Segment]
RETURNS [controlPointCount:
NAT] = {
GGSegmentTypes.ControlPointCountProc
data: CubicSplineData ← NARROW[seg.data]; -- just for NarrowRefFault if there is a problem
SELECT data.type
FROM
naturalAL, cyclicAL => RETURN[data.cps.length-2]; -- 2 true control points are joints
bspline => RETURN[data.cps.length-6] -- 6 true control points are joints
ENDCASE => ERROR;
};
CSClosestControlPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, controlPointNum:
NAT, success:
BOOL] = {
GGSegmentTypes.ClosestControlPointProc
data: CubicSplineData ← NARROW[seg.data];
-- Consider only the inner control points (edgepoints are treated as joints)
nextDist: REAL;
minDist: REAL;
IF seg.class.controlPointCount[seg] = 0 THEN RETURN[[0,0], 999, FALSE];
minDist ← Vectors2d.DistanceSquared[testPoint, CSControlPointGet[seg, 0]];
controlPointNum ← 0;
FOR i:
INT
IN [1..seg.class.controlPointCount[seg])
DO
IF (nextDist ← Vectors2d.DistanceSquared[testPoint, CSControlPointGet[seg, i]]) < minDist
THEN {
minDist ← nextDist;
controlPointNum ← i;
};
ENDLOOP;
minDist ← RealFns.SqRt[minDist];
success ← (minDist <= tolerance);
point ← CSControlPointGet[seg, controlPointNum];
};
CSAddJoint:
PROC [seg: Segment, pos: Point]
RETURNS [seg1, seg2: Segment] = {
Returns the CubicSpline segments which are formed by placing a joint in the specified position. Note that this position must lie on the spline, since we use this to determine cp ordering
data: CubicSplineData ← NARROW[seg.data];
data1, data2: CubicSplineData;
oldCPCount: INT ← seg.class.controlPointCount[seg];
nearest: INT ← FindNearestPath[data.path, pos];
cpsInSeg1: INT ← PathNumToCPIndex[seg, nearest]; -- The number of cps that will be in seg1
cpsInSeg2: INT ← oldCPCount - cpsInSeg1;
coeffs: CubicSplines.CoeffsSequence;
seg1 ← CopySegment[seg];
seg2 ← CopySegment[seg];
data1 ← NARROW[seg1.data];
data2 ← NARROW[seg2.data];
IF data.type=cyclicAL THEN data1.type ← data2.type ← naturalAL; --cyclic splines are changed
--create and fill in the new CP array of seg1
data1.cps ← NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpsInSeg2]];
data1.cpsSelected ← NEW[SelectionSequenceObj[cpsInSeg1]];
CSEndPointSet[seg1, TRUE, seg.lo];
CSEndPointSet[seg1, FALSE, pos];
seg1.hi ← pos;
FOR i:
INT
IN [0..cpsInSeg1)
DO
CSControlPointSet[seg1, i, CSControlPointGet[seg, i]];
data1.cpsSelected[i] ← data.cpsSelected[i];
ENDLOOP;
coeffs ← CubicSplines.MakeSpline[data1.cps, data1.type];
data1.path ← CubicPaths.PathFromCubic[coeffs];
--create and fill in the new CP array of seg2
data2.cps ← NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpsInSeg1]];
data2.cpsSelected ← NEW[SelectionSequenceObj[cpsInSeg2]];
CSEndPointSet[seg2, TRUE, pos];
CSEndPointSet[seg2, FALSE, seg.hi];
seg2.lo ← pos;
FOR i:
INT
IN [cpsInSeg1..oldCPCount)
DO
CSControlPointSet[seg2, i-cpsInSeg1, CSControlPointGet[seg, i]];
data2.cpsSelected[i-cpsInSeg1] ← data.cpsSelected[i];
ENDLOOP;
coeffs ← CubicSplines.MakeSpline[data2.cps, data2.type];
data2.path ← CubicPaths.PathFromCubic[coeffs];
UpdateCSBoundBox[seg1]; -- added by KAP. December 22, 1986
UpdateCSBoundBox[seg2]; -- added by KAP. December 22, 1986
};
CSControlPointAdd:
PUBLIC
PROC [seg: Segment, pos: Point]
RETURNS [Segment] = {
Returns a CubicSpline segment with a control point added. Note that the specified position must lie on the spline, since control point ordering is determined from this.
data: CubicSplineData ← NARROW[seg.data];
oldCPCount: INT ← seg.class.controlPointCount[seg];
nearest: INT ← FindNearestPath[data.path, pos];
newCPIndex: INT ← PathNumToCPIndex[seg, nearest]; -- newCPIndex is the index of the CP in the old segment that the new CP will precede
newSeg: Segment ← CopySegment[seg];
newSegData: CubicSplineData ← NARROW[newSeg.data];
coeffs: CubicSplines.CoeffsSequence;
create and fill in the new CP array
newSegData.cps ← NEW[CubicSplines.KnotSequenceRec[data.cps.length+1]];
newSegData.cpsSelected ← NEW[SelectionSequenceObj[oldCPCount+1]];
CSEndPointSet[newSeg, TRUE, seg.lo];
CSEndPointSet[newSeg, FALSE, seg.hi];
FOR i:
INT
IN [0..newCPIndex)
DO
CSControlPointSet[newSeg, i, CSControlPointGet[seg, i]];
newSegData.cpsSelected[i] ← data.cpsSelected[i];
ENDLOOP;
CSControlPointSet[newSeg, newCPIndex, pos];
CSFieldSet[newSeg, newCPIndex, TRUE, normal]; --newly created cp will be selected
CSFieldSet[newSeg, newCPIndex, FALSE, hot];
CSFieldSet[newSeg, newCPIndex, FALSE, active];
FOR i:
INT
IN [newCPIndex..oldCPCount)
DO
CSControlPointSet[newSeg, i+1, CSControlPointGet[seg, i]];
newSegData.cpsSelected[i+1] ← data.cpsSelected[i];
ENDLOOP;
coeffs ← CubicSplines.MakeSpline[newSegData.cps, newSegData.type];
newSegData.path ← CubicPaths.PathFromCubic[coeffs];
UpdateCSBoundBox[newSeg]; -- added by KAP. December 22, 1986
RETURN [newSeg];
};
CSControlPointDelete:
PUBLIC
PROC [seg: Segment, cpVec: BitVector]
RETURNS [Segment] = {
Returns a CubicSpline Segment with selected control points deleted.
CountSelected:
PROC [bitVec: BitVector]
RETURNS [selectedCount:
INT ← 0] = {
FOR i:
INT
IN [0..bitVec.len)
DO
IF bitVec[i] THEN selectedCount ← selectedCount + 1;
ENDLOOP;
};
data: CubicSplineData ← NARROW[seg.data];
currentCPIndex: INT ← 0;
oldCPCount: INT ← seg.class.controlPointCount[seg];
cpSelectedCount: INT ← CountSelected[cpVec];
newSeg: Segment ← CopySegment[seg];
coeffs: CubicSplines.CoeffsSequence;
newSegData: CubicSplineData ← NARROW[newSeg.data];
newSegData.cps ← NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpSelectedCount]];
newSegData.cpsSelected ← NEW[SelectionSequenceObj[data.cps.length-cpSelectedCount]];
CSEndPointSet[newSeg, TRUE, seg.lo];
CSEndPointSet[newSeg, FALSE, seg.hi];
FOR i:
INT
IN [0..oldCPCount)
DO
IF
NOT cpVec[i]
THEN {
CSControlPointSet[newSeg, currentCPIndex, CSControlPointGet[seg, i]];
newSegData.cpsSelected[currentCPIndex] ← data.cpsSelected[i];
currentCPIndex ← currentCPIndex + 1;
};
ENDLOOP;
coeffs ← CubicSplines.MakeSpline[newSegData.cps, newSegData.type];
newSegData.path ← CubicPaths.PathFromCubic[coeffs];
UpdateCSBoundBox[newSeg]; -- added by KAP. December 22, 1986
RETURN [newSeg];
};
CSControlPointSet:
PROC [seg: Segment, controlPointNum:
NAT, point: Point] = {
data: CubicSplineData ← NARROW[seg.data];
SELECT data.type
FROM
naturalAL, cyclicAL => data.cps[controlPointNum+1] ← PointToFP[point];
bspline => data.cps[controlPointNum+3] ← PointToFP[point];
ENDCASE => ERROR;
};
CSEndPointSet:
PROC [seg: Segment, lo:
BOOL, point: Point] = {
data: CubicSplineData ← NARROW[seg.data];
SELECT data.type
FROM
naturalAL => {
IF lo THEN data.cps[0] ← PointToFP[point]
ELSE data.cps[data.cps.length-1] ← PointToFP[point];
};
cyclicAL => data.cps[0] ← data.cps[data.cps.length-1] ← PointToFP[point];
bspline => {
IF lo THEN data.cps[0] ← data.cps[1] ← data.cps[2] ← PointToFP[point]
ELSE data.cps[data.cps.length-1] ← data.cps[data.cps.length-2] ← data.cps[data.cps.length-3] ← PointToFP[point];
};
ENDCASE => ERROR;
};
Curve Auxiliary Procedures
CurveBoundBox:
PROC [seg: Segment, bBox: BoundBox, boundsOK:
BOOL ←
FALSE] = {
GGSegmentTypes.BoundBoxProc
Creates a new box which bounds this segment (NOT including stroke width).
path: CubicPaths.Path ← GetPath[seg];
r: Imager.Rectangle;
IF NOT boundsOK THEN CubicPaths.UpdateBounds[path]; -- the bbox of the curve itself
r ← path.bounds;
GGBoundBox.UpdateBoundBox[bBox, r.x, r.y, r.x+r.w, r.y+r.h];
};
CurveMaskStroke: PROC [seg: Segment, dc: Imager.Context] = {
GGSegmentTypes.MaskStrokeProc
Draw yourself into dc as a stroke. Assume that strokeWidth and color are already set.
path: CubicPaths.Path ← GetPath[seg];
pathProc: ImagerPath.PathProc = {CubicPaths.EnumeratePath[path, moveTo, curveTo]};
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.MaskStroke[dc, pathProc];
};
CurveBuildPath:
PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = {
GGSegmentTypes.BuildPathProc
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 ← GetPath[seg];
moveTo: ImagerPath.MoveToProc ← {};
CubicPaths.EnumeratePath[path, moveTo, curveTo];
};
CurveClosestPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, success:
BOOL] = {
GGSegmentTypes.ClosestPointProc
Used for hit testing. Find the nearest point on seg to testPoint.
path: CubicPaths.Path;
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[[0,0], FALSE];
};
CodeTimer.StartInt[$CurveClosestPoint, $Gargoyle];
path ← GetPath[seg];
[point, success] ← GGCubic2.ClosestPointAnalytic[testPoint, path, tolerance];
CodeTimer.StopInt[$CurveClosestPoint, $Gargoyle];
};
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) ];
};
KnotBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc
Creates a boundbox of the segment's knots. Segment must be a cubic spline.
data: CubicSplineData ← NARROW[seg.data];
cps: CubicSplines.KnotSequence ← data.cps;
minX, maxX: REAL ← cps[0][X];
minY, maxY: REAL ← cps[0][Y];
FOR i:
INT
IN [1..cps.length)
DO
IF maxX < cps[i][X] THEN maxX ← cps[i][X];
IF minX > cps[i][X] THEN minX ← cps[i][X];
IF maxY < cps[i][Y] THEN maxY ← cps[i][Y];
IF minY > cps[i][Y] THEN minY ← cps[i][Y];
ENDLOOP;
seg.bBox^ ← [loX: minX, loY: minY, hiX: maxX, hiY: maxY, null: FALSE, infinite: FALSE];
GGBoundBox.AllowForJoints[seg.bBox];
};
FPToPoint:
PROC [
FP: CubicSplines.FPCoords]
RETURNS [point: Point] = {
RETURN [[FP[X], FP[Y]]];
};
PointToFP:
PROC [point: Point]
RETURNS [CubicSplines.FPCoords] = {
RETURN [[point.x, point.y]];
};
FindNearestPath:
PROC [path: CubicPaths.Path, pos: Point]
RETURNS [
INT] = {
Finds which on which subpath a point. Returns -1 if none.
miniPath: CubicPaths.Path ← NEW[CubicPaths.PathRec];
miniPath.cubics ← NEW[CubicPaths.PathSequence[1]];
FOR i:
INT
IN [0..path.cubics.length)
DO
miniPath.cubics[0] ← path.cubics[i];
CubicPaths.UpdateBounds[miniPath];
IF CubicPaths.PointOnPath[pos, miniPath, 0.5] THEN RETURN [i];
ENDLOOP;
RETURN [-1];
};
PathNumToCPIndex:
PROC [seg: Segment, pathNum:
INT]
RETURNS [cpIndex:
INT] = {
--Given a subpath number, figures out which control point "divides" this subpath from the next.
data: CubicSplineData ← NARROW[seg.data];
cpCount: INT ← seg.class.controlPointCount[seg];
SELECT data.type
FROM
cyclicAL, naturalAL => cpIndex ← pathNum;
bspline => cpIndex ← pathNum-1;
ENDCASE => ERROR;
IF cpIndex < 0 THEN cpIndex ← 0
ELSE IF cpIndex > cpCount THEN cpIndex ← cpCount;
RETURN;
};
GetPath:
PROC [seg: Segment]
RETURNS [path: CubicPaths.Path] ~ {
WITH seg.data
SELECT
FROM
type: BezierData => path ← type.path;
type: ConicData => path ← type.path;
type: CubicSplineData => path ← type.path;
ENDCASE => ERROR;
RETURN[path];
};
PathFromConic:
PROC [p0, p1, p2: Point, r:
REAL]
RETURNS [path: CubicPaths.Path] ~ {
bList: LIST OF BezierRef;
joint: VEC ← [p0.x,p0.y];
bproc: ImagerPath.CurveToProc = {
new: BezierRef ← NEW[Cubic2.Bezier ← [joint, p1, p2, p3]];
bList ← CONS[new, bList];
joint ← p3;
};
ImagerPath.ConicToCurves[[p0.x,p0.y], [p1.x,p1.y], [p2.x,p2.y], r, bproc];
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;
CubicPaths.UpdateBounds[path];
RETURN[path];
};
Init:
PROC [] = {
classDef: ClassDef ← NEW[ClassDefRec ← [type: $Bezier, class: BuildBezierClass[]]];
RegisterSegmentClass[classDef];
classDef ← NEW[ClassDefRec ← [type: $CubicSpline, class: BuildCubicSplineClass[]]];
RegisterSegmentClass[classDef];
classDef ← NEW[ClassDefRec ← [type: $Conic, class: BuildConicClass[]]];
RegisterSegmentClass[classDef];
};
InitStats:
PROC [] = {
interval: CodeTimer.Interval;
interval ← CodeTimer.CreateInterval[$CurveClosestPoint];
CodeTimer.AddInt[interval, $Gargoyle];
};
Init[];
InitStats[];
END.