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];
};
The Conic Class
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: REALMAX[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];
};
Conic Drawing
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];
};
Conic Transforming
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];
};
Conic Parts
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];
};
Conic Hit Testing
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];
};
The Bezier Class
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: REALMAX[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];
};
Bezier Transforming
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];
};
Bezier Drawing
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;
};
Beier Hit Testing
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);
};
};
Beier Editing
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];
};
Beier Description
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];
};
The Cubic Spline Class
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: BOOLFALSE] = {
GGSegmentTypes.BoundBoxProc
data: CubicSplineData ← NARROW[seg.data];
cpHalf: REAL ← GGModelTypes.halfJointSize + 1;
strokeWidth: REAL ← seg.strokeWidth;
pad: REALMAX[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];
};
CS Drawing
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];
};
CS Description
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];
};
CS Parts
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;
};
CS Part Generators
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;
};
CS Hit Testing
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];
};
CS Editing
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: BOOLFALSE] = {
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];
};
useBBox: BOOLTRUE;
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];
};
Auxiliary Path Procs
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 Procs
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.