GGSegmentImplA.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1990, 1992 by Xerox Corporation. All rights reserved.
Eisenman, August 11, 1987 4:37:34 pm PDT
Kurlander, August 21, 1987 0:30:49 am PDT
Last edited by Pier on June 30, 1992 7:03 pm PDT
Bier, November 30, 1992 5:23 pm PST
Doug Wyatt, April 14, 1992 2:31 pm PDT
Contents: Procedures which implement the Gargoyle segment classes.
Conics, Beziers, and Cubics
DIRECTORY
Atom, CodeTimer, Cubic2, CubicPaths, CubicSplines, Feedback, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGCubic2, GGInterfaceTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGTransform, GGUtility, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, List, Real, RealFns, Rope, Vectors2d;
GGSegmentImplA:
CEDAR
PROGRAM
IMPORTS Atom, CodeTimer, Cubic2, CubicPaths, CubicSplines, Feedback, GGBoundBox, GGCoreOps, GGCubic2, GGParseIn, GGParseOut, GGSegment, GGTransform, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, List, Real, RealFns, Rope, Vectors2d
EXPORTS GGSegment = BEGIN
Bezier: TYPE = Cubic2.Bezier;
BezierRef: TYPE = REF Bezier;
BitVector: TYPE = GGModelTypes.BitVector;
BoundBox: TYPE = REF BoundBoxObj;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
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 = GGCoreTypes.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;
StrokeEnd: TYPE = Imager.StrokeEnd;
Vector: TYPE = GGBasicTypes.Vector;
SequenceOfReal: TYPE = REF SequenceOfRealObj;
SequenceOfRealObj: TYPE = GGCoreTypes.SequenceOfRealObj;
VEC: TYPE = Imager.VEC;
defaultStrokeWidth: REAL ¬ 2.0;
defaultStrokeEnd: StrokeEnd ¬ round;
segmentClasses: LIST OF ClassDef;
X: NAT = CubicSplines.X;
Y: NAT = CubicSplines.Y;
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.PutFR1["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],
tightBox: GGBoundBox.CopyBoundBox[seg.tightBox],
data: copyData,
props: List.Append[l1: seg.props, l2: NIL] -- Appends l2 to a copy of l1.
]];
};
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 GGCoreOps.EquivalentColors[seg1.color, seg2.color] THEN RETURN[FALSE];
RETURN[TRUE];
};
SameShape:
PUBLIC
PROC [seg1: Segment, seg2: Segment]
RETURNS [same:
BOOL] = {
RETURN [seg1.class.sameShape[seg1, seg2]];
};
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 Line Class
DrawLine:
PUBLIC
PROC [dc: Imager.Context, p1, p2: Point, seg: Segment] = {
IF seg.color=NIL OR seg.strokeWidth=0.0 THEN RETURN
ELSE {
Imager.SetStrokeWidth[dc, seg.strokeWidth];
Imager.SetStrokeEnd[dc, seg.strokeEnd];
Imager.SetColor[dc, seg.color];
IF seg.dashed
THEN {
EdgePathProc: Imager.PathProc = {moveTo[p1]; lineTo[p2]; };
PatternProc: PROC [i: NAT] RETURNS [REAL] = {RETURN[pattern[i]]};
pattern: SequenceOfReal ¬ seg.pattern;
Imager.MaskDashedStroke[dc, EdgePathProc, pattern.len, PatternProc, seg.offset, seg.length];
}
ELSE Imager.MaskVector[dc, p1, p2];
};
};
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,
sameShape: ConicSameShape,
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,
asPolyline: ConicAsPolyline,
cPNormal: NoOpCPNormal,
jointNormal: NoOpJointNormal,
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.
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data,
props: props
]];
UpdateConicBoundBox[seg];
};
ConicGetParams:
PUBLIC
PROC [seg: Segment]
RETURNS [p0, p1, p2: Point, r:
REAL] = {
data: ConicData ¬ NARROW[seg.data];
p0 ¬ seg.lo;
p1 ¬ data.p1;
p2 ¬ seg.hi;
r ¬ data.s;
};
ConicSetRatio:
PUBLIC
PROC [seg: Segment, r:
REAL] = {
data: ConicData ¬ NARROW[seg.data];
data.s ¬ r;
data.path ¬ PathFromConic[seg.lo, data.p1, seg.hi, data.s];
UpdateConicBoundBox[seg];
};
ConicSetControlPoint:
PUBLIC
PROC [seg: Segment, p1: Point] = {
data: ConicData ¬ NARROW[seg.data];
data.p1 ¬ p1;
data.path ¬ PathFromConic[seg.lo, data.p1, seg.hi, data.s];
UpdateConicBoundBox[seg];
};
ConicBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ¬ seg.bBox;
};
ConicTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
bBox ¬ seg.tightBox;
};
ConicSameShape:
PROC [seg1, seg2: Segment]
RETURNS [same:
BOOL ¬
TRUE] = {
seg1Data: ConicData ¬ NARROW[seg1.data];
seg2Data: ConicData ¬ NARROW[seg2.data];
same ¬ seg1Data.s = seg2Data.s;
};
root2Over2: REAL = 0.707106816;
UpdateConicBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc
data: ConicData ¬ NARROW[seg.data];
pad: REAL;
minX, minY, maxX, maxY: REAL;
minX ¬ MIN[seg.lo.x, seg.hi.x, data.p1.x];
minY ¬ MIN[seg.lo.y, seg.hi.y, data.p1.y];
maxX ¬ MAX[seg.lo.x, seg.hi.x, data.p1.x];
maxY ¬ MAX[seg.lo.y, seg.hi.y, data.p1.y];
GGBoundBox.UpdateBoundBox[seg.tightBox, minX, minY, maxX, maxY];
CurveBoundBox[seg, seg.bBox, FALSE];
IF seg.strokeEnd = square
THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0
ELSE pad ¬ seg.strokeWidth/2.0 + 1.0;
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=NIL OR 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, FALSE];
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.PutF1[" %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.ReadReal[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;
match => data.p1Selected.match ¬ 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;
match => selected ¬ data.p1Selected.match;
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, normal: Vector ¬ [0,-1], controlPointNum:
NAT, success:
BOOL] = {
GGSegmentTypes.ClosestControlPointProc
data: ConicData ¬ NARROW[seg.data];
success ¬ TRUE;
controlPointNum ¬ 0;
point ¬ [data.p1.x, data.p1.y];
};
ConicAsPolyline:
PROC [seg: Segment, tolerance:
REAL]
RETURNS [polyline: GGSegmentTypes.PairSequence] = {
Use a flatness test and recursive subdivision to represent a conic as a polyline.
data: ConicData ¬ NARROW[seg.data];
p0, p1, p2: VEC;
r: REAL;
p0 ¬ seg.lo;
p1 ¬ data.p1;
p2 ¬ seg.hi;
r ¬ data.s;
polyline ¬ ConicPolyline[p0, p1, p2, r, tolerance];
};
Mid:
PROC [p, q:
VEC]
RETURNS [
VEC] ~
INLINE {
RETURN [[Half[p.x+q.x], Half[p.y+q.y]]]
};
Half:
PROC [r:
REAL]
RETURNS [
REAL] ~
INLINE {
RETURN [Real.FScale[r, -1]]
};
ConicPolyline:
PROC [p0, p1, p2:
VEC, r:
REAL, tolerance:
REAL]
RETURNS [polyline: GGSegmentTypes.PairSequence] = {
SELECT r
FROM
> 0.9999 => {
polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[3]];
polyline.length ¬ 3; polyline[0] ¬ p0; polyline[1] ¬ p1; polyline[2] ¬ p2;
};
<= 0.0 => {
polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[2]];
polyline.length ¬ 2; polyline[0] ¬ p0; polyline[1] ¬ p2;
};
ENDCASE => {
p02: VEC ~ Mid[p0, p2];
m: VEC ~ [p1.x-p02.x, p1.y-p02.y];
IF (
ABS[m.x]+
ABS[m.y])*r <= tolerance
THEN {
-- conservative estimate of flatness
polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[2]];
polyline.length ¬ 2; polyline[0] ¬ p0; polyline[1] ¬ p2;
}
ELSE {
q: VEC ~ [m.x*r+p02.x, m.y*r+p02.y];
rNew: REAL ~ 1.0/(1.0+RealFns.SqRt[2.0*(1-r)]);
poly1, poly2: GGSegmentTypes.PairSequence;
totalPoints: NAT;
poly1 ¬ ConicPolyline[p0, [(p1.x-p0.x)*r+p0.x, (p1.y-p0.y)*r+p0.y], q, rNew, tolerance];
poly2 ¬ ConicPolyline[q, [(p1.x-p2.x)*r+p2.x, (p1.y-p2.y)*r+p2.y], p2, rNew, tolerance];
totalPoints ¬ poly1.length+poly2.length-1;
polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[totalPoints]];
polyline.length ¬ totalPoints;
FOR i:
NAT
IN [0..poly1.length)
DO
polyline[i] ¬ poly1[i];
ENDLOOP;
FOR i:
NAT
IN [1..poly2.length)
DO
polyline[i+poly1.length-1] ¬ poly2[i];
ENDLOOP;
};
};
};
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,
sameShape: GGSegment.NoOpSameShapeProc,
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: BZAsSimpleCurve,
asPolyline: BZAsPolyline,
cPNormal: NoOpCPNormal,
jointNormal: BZJointNormal,
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.
tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down.
data: data,
props: props
]];
UpdateBZBoundBox[seg];
};
BezierGetParams:
PUBLIC
PROC [seg: Segment]
RETURNS [p0, p1, p2, p3: Point] = {
data: BezierData ¬ NARROW[seg.data];
p0 ¬ data.path.cubics[0].b0;
p1 ¬ data.path.cubics[0].b1;
p2 ¬ data.path.cubics[0].b2;
p3 ¬ data.path.cubics[0].b3;
};
BZBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ¬ seg.bBox;
};
BZTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ¬ seg.tightBox;
};
UpdateBZBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc.
Find the bounding hull of the control points. This will bound the curve as well.
pad: REAL;
data: BezierData ¬ NARROW[seg.data];
cubics: REF CubicPaths.PathSequence ¬ data.path.cubics;
We use the bounding box of control points, because hit testing requres that the box contain all of the control points. Note: refresh can actually use a tighter box.
the following nonsense with extra temporary variables avoids a bug in the c compiler. KAP June 18, 1992.
floX, floY, fhiX, fhiY: REAL;
ft1, ft2, ft3, ft4: REAL;
floX ¬ MIN[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x];
ft1 ¬ cubics[0].b0.x; ft2 ¬ cubics[0].b1.x; ft3 ¬ cubics[0].b2.x; ft4 ¬ cubics[0].b3.x;
floX ¬ MIN[ft1, ft2, ft3, ft4];
floY ¬ MIN[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y];
ft1 ¬ cubics[0].b0.y; ft2 ¬ cubics[0].b1.y; ft3 ¬ cubics[0].b2.y; ft4 ¬ cubics[0].b3.y;
floY ¬ MIN[ft1, ft2, ft3, ft4];
fhiX ¬ MAX[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x];
ft1 ¬ cubics[0].b0.x; ft2 ¬ cubics[0].b1.x; ft3 ¬ cubics[0].b2.x; ft4 ¬ cubics[0].b3.x;
fhiX ¬ MAX[ft1, ft2, ft3, ft4];
fhiY ¬ MAX[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y];
ft1 ¬ cubics[0].b0.y; ft2 ¬ cubics[0].b1.y; ft3 ¬ cubics[0].b2.y; ft4 ¬ cubics[0].b3.y;
fhiY ¬ MAX[ft1, ft2, ft3, ft4];
GGBoundBox.UpdateBoundBox[bBox: seg.tightBox,
loX: floX,
loY: floY,
hiX: fhiX,
hiY: fhiY];
CubicPaths.UpdateBounds[data.path];
GGBoundBox.UpdateBoundBox[seg.bBox, data.path.bounds.x, data.path.bounds.y, data.path.bounds.x + data.path.bounds.w, data.path.bounds.y + data.path.bounds.h];
IF seg.strokeEnd = square
THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0
ELSE pad ¬ seg.strokeWidth/2.0 + 1.0;
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
};
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, FALSE];
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];
};
BZControlPointMovedTo:
PUBLIC PROC [seg: Segment, newPos: Point, controlPointNum:
NAT] = {
data: BezierData ¬ NARROW[seg.data];
SELECT controlPointNum
FROM
0 => data.path.cubics[0].b1 ¬ newPos;
1 => data.path.cubics[0].b2 ¬ newPos;
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[data.path, transform];
CubicPaths.EnumeratePath[data.path, moveTo, curveTo];
CubicPaths.TransformPath[data.path, ImagerTransformation.Invert[transform]];
scratchPath: CubicPaths.Path ¬ CubicPaths.CopyPath[path];
CubicPaths.TransformPath[scratchPath, transform, FALSE];
CubicPaths.EnumeratePath[scratchPath, moveTo, curveTo];
}
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];
bound box is still OK
};
};
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=NIL OR controlPoints[0] THEN path.cubics[0].b1 ¬ ImagerTransformation.Transform[transform, path.cubics[0].b1];
IF controlPoints=NIL OR controlPoints[1] THEN path.cubics[0].b2 ¬ ImagerTransformation.Transform[transform, path.cubics[0].b2];
};
BZSpecialTransformPath:
PUBLIC
PROC [seg: Segment, transform: ImagerTransformation.Transformation, lo, hi:
BOOL, controlPoints: BitVector, loTransform, hiTransform: ImagerTransformation.Transformation, curveTo: ImagerPath.CurveToProc] = {
oldLo, oldHi, oldC1, oldC2: VEC;
path: CubicPaths.Path ¬ GetPath[seg];
moveTo: ImagerPath.MoveToProc ¬ {};
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=NIL OR controlPoints[0] THEN path.cubics[0].b1 ¬ ImagerTransformation.Transform[loTransform, path.cubics[0].b1];
IF controlPoints=NIL OR controlPoints[1] THEN path.cubics[0].b2 ¬ ImagerTransformation.Transform[hiTransform, path.cubics[0].b2];
CubicPaths.EnumeratePath[path, moveTo, curveTo];
BZRestorePath[path, oldLo, oldHi, oldC1, oldC2];
};
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;
match => data.b1Selected.match ¬ selected;
ENDCASE => ERROR;
};
1 => {
SELECT selectClass
FROM
normal => data.b2Selected.normal ¬ selected;
hot => data.b2Selected.hot ¬ selected;
active => data.b2Selected.active ¬ selected;
match => data.b2Selected.match ¬ 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;
match => selected ¬ data.b1Selected.match;
ENDCASE => ERROR;
};
1 => {
SELECT selectClass
FROM
normal => selected ¬ data.b2Selected.normal;
hot => selected ¬ data.b2Selected.hot;
active => selected ¬ data.b2Selected.active;
match => selected ¬ data.b2Selected.match;
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
BZClosestControlPoint:
PROC [seg: Segment, testPoint: Point, tolerance:
REAL]
RETURNS [point: Point, normal: Vector ¬ [0,-1], 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);
};
};
BZAsSimpleCurve:
PROC [seg: Segment, point: Point]
RETURNS [simpleCurve:
REF
ANY] = {
data: BezierData ¬ NARROW[seg.data];
bezier: Bezier ¬ data.path.cubics[0];
simpleCurve ¬ NEW[Bezier ¬ bezier];
};
BZAsPolyline:
PROC [seg: Segment, tolerance:
REAL]
RETURNS [polyline: GGSegmentTypes.PairSequence] = {
data: BezierData ¬ NARROW[seg.data];
polyline ¬ GGCubic2.AsPolyline[data.path.cubics[0], tolerance];
};
BZJointNormal:
PROC [seg: Segment, joint, point: Point, hi:
BOOL]
RETURNS [normal, tangent: Vector] = {
GGSegmentTypes.JointNormalProc
data: BezierData ¬ NARROW[seg.data];
normal1, normal2, direction: Vector;
p0,p1,p2,p3: Point;
p0 ¬ data.path.cubics[0].b0;
p1 ¬ data.path.cubics[0].b1;
p2 ¬ data.path.cubics[0].b2;
p3 ¬ data.path.cubics[0].b3;
IF hi
THEN {
tangent ¬ Vectors2d.VectorFromPoints[p3,p2];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
}
ELSE {
tangent ¬ Vectors2d.VectorFromPoints[p0,p1];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
};
direction ¬ Vectors2d.VectorFromPoints[joint, point];
IF
ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] <
ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]]
THEN{
normal ¬ normal1; }
ELSE {normal ¬ normal2;}
};
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 ¬ CubicPaths.GetParam[bezier, pos];
[bezier1, bezier2] ¬ Cubic2.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,
sameShape: CSSameShape,
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,
asPolyline: CSAsPolyline,
cPNormal: CSCPNormal,
jointNormal: CSJointNormal,
Textual Description
describe: CSDescribe,
fileOut: CSFileOut,
fileIn: CSFileIn,
Editing
addJoint: CSAddJoint,
setStrokeWidth: CSSetStrokeWidth
]];
};
CSSameShape: GGSegmentTypes.SameShapeProc = {
This proc is incomplete as it checks no actual shape parameters. However, it is smart enough to tell that cubic splines of different type cannot be the same shape, even though they might have identical joints and control points.
data1: CubicSplineData ← NARROW[seg1.data];
data2: CubicSplineData ← NARROW[seg2.data];
RETURN[data1.type=data2.type];
};
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.
tightBox: 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, FALSE]; -- build a path. ignore bound box
UpdateCSBoundBox[seg];
};
CubicSplineGetParams:
PUBLIC
PROC [seg: Segment]
RETURNS [CubicPaths.Path] = {
data: CubicSplineData ¬ NARROW[seg.data];
RETURN[data.path];
};
CSBoundBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ¬ seg.bBox;
};
CSTightBox:
PROC [seg: Segment]
RETURNS [bBox: BoundBox] = {
bBox ¬ seg.tightBox;
};
UpdateCSBoundBox:
PROC [seg: Segment] = {
GGSegmentTypes.BoundBoxProc
data: CubicSplineData ¬ NARROW[seg.data];
path: CubicPaths.Path ¬ data.path;
pad: REAL;
IF seg.strokeEnd = square
THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0
ELSE pad ¬ seg.strokeWidth/2.0 + 1.0;
IF data.type = bspline
THEN {
KnotBoundBox[seg, seg.tightBox];
CurveBoundBox[seg, seg.bBox, FALSE];
GGBoundBox.EnlargeByOffset[seg.bBox, pad];
}
ELSE {
CurveBoundBox[seg, seg.tightBox, FALSE];
seg.bBox ¬ seg.tightBox;
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]];
scratchPath: CubicPaths.Path ¬ CubicPaths.CopyPath[data.path];
CubicPaths.TransformPath[scratchPath, transform, FALSE];
CubicPaths.EnumeratePath[scratchPath, moveTo, curveTo];
}
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=NIL OR 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];
CubicPaths.EnumeratePath[CubicPaths.PathFromCubic[coeffs, FALSE], moveTo, curveTo];
data.cps ¬ cpsCopy; -- restore old CPs
};
};
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, FALSE];
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, FALSE];
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, FALSE];
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.PutRope["Type: Natural "];
cyclicAL => f.PutRope["Type: CyclicNatural "];
bspline => f.PutRope["Type: BSpline "];
ENDCASE => ERROR;
f.PutF1["%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: BOOL;
IF version >= 8608.12
THEN {
GGParseIn.ReadRope[f, "Type:"];
typeWord ¬ GGParseIn.ReadWord[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.ReadRope[f, "Cyclic:"];
cyclic ¬ GGParseIn.ReadBool[f];
IF cyclic THEN type ¬ cyclicAL ELSE type ¬ naturalAL;
}
ELSE type ¬ naturalAL;
cps ¬ NEW[CubicSplines.KnotSequenceRec[GGParseIn.ReadNAT[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;
match => data.cpsSelected[controlPointNum].match ¬ 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;
match => selected ¬ data.cpsSelected[controlPointNum].match;
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, normal: Vector ¬ [0,-1], 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], [0,-1], 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];
IF success
THEN {
IF data.type = naturalAL
OR data.type = cyclicAL
THEN {
normal1, normal2, tangent, direction: Vector;
p2,p3: Point;
p2 ¬ data.path.cubics[controlPointNum].b2;
p3 ¬ data.path.cubics[controlPointNum].b3;
tangent ¬ Vectors2d.VectorFromPoints[p3,p2];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
direction ¬ Vectors2d.VectorFromPoints[point, testPoint];
IF
ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] <
ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]]
THEN{
normal ¬ normal1; }
ELSE {normal ¬ normal2;}
};
};
};
Polylines: TYPE = REF PolylinesObj;
PolylinesObj:
TYPE =
RECORD[
lines: SEQUENCE length: NAT OF GGSegmentTypes.PairSequence];
CSAsPolyline:
PROC [seg: Segment, tolerance:
REAL]
RETURNS [polyline: GGSegmentTypes.PairSequence] = {
data: CubicSplineData ¬ NARROW[seg.data];
polylines: Polylines;
lineCount, index: NAT ¬ 0;
polylines ¬ NEW[PolylinesObj[data.path.cubics.length]];
FOR i:
NAT
IN [0..data.path.cubics.length)
DO
polylines[i] ¬ GGCubic2.AsPolyline[data.path.cubics[i], tolerance];
lineCount ¬ lineCount+polylines[i].length-1;
ENDLOOP;
polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[lineCount+1]];
polyline.length ¬ lineCount+1;
polyline[index] ¬ polylines[0][0];
index ¬ index + 1;
FOR i:
NAT
IN [0..data.path.cubics.length)
DO
FOR j:
NAT
IN [1..polylines[i].length)
DO
polyline[index] ¬ polylines[i][j];
index ¬ index + 1;
ENDLOOP;
ENDLOOP;
};
CS
CPNormal:
PROC
[seg: Segment,
controlPointNum:
NAT, cPoint, testPoint: Point]
RETURNS [normal: Vector ¬ [0,-1]] = {
GGSegmentTypes.CPNormalProc
data: CubicSplineData ¬ NARROW[seg.data];
IF data.type = naturalAL
OR data.type = cyclicAL
THEN {
normal1, normal2, tangent, direction: Vector;
p2,p3: Point;
p2 ¬ data.path.cubics[controlPointNum].b2;
p3 ¬ data.path.cubics[controlPointNum].b3;
tangent ¬ Vectors2d.VectorFromPoints[p3,p2];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
direction ¬ Vectors2d.VectorFromPoints[cPoint, testPoint];
IF
ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] <
ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]]
THEN{
normal ¬ normal1; }
ELSE {normal ¬ normal2;}
};
};
CS
JointNormal:
PROC [seg: Segment, joint, point: Point, hi:
BOOL]
RETURNS [normal, tangent: Vector ¬ [0,-1]] = {
GGSegmentTypes.JointNormalProc
pnum: INT;
normal1, normal2, direction: Vector;
p0,p1,p2,p3: Point;
offset: INT;
data: CubicSplineData ¬ NARROW[seg.data];
path: CubicPaths.Path ¬ GetPath[seg];
pnum ← FindNearestPath[path, joint, GGUtility.plusInfinity];
IF data.type = bspline THEN {offset ¬ 1;}
ELSE {offset ¬ 0;};
p0 ¬ path.cubics[0 + offset].b0;
p1 ¬ path.cubics[0 + offset].b1;
p2 ¬ path.cubics[path.cubics.length - 1 - offset].b2;
p3 ¬ path.cubics[path.cubics.length - 1 - offset].b3;
IF hi
THEN {
tangent ¬ Vectors2d.VectorFromPoints[p3,p2];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
}
ELSE {
tangent ¬ Vectors2d.VectorFromPoints[p0,p1];
normal1.x ¬ tangent.y;
normal1.y ¬ -tangent.x;
normal2.x ¬ -tangent.y;
normal2.y ¬ tangent.x;
};
direction ¬ Vectors2d.VectorFromPoints[joint, point];
IF
ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] <
ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]]
THEN{
normal ¬ normal1; }
ELSE {normal ¬ normal2;}
};
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, FALSE];
--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, FALSE];
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];
CSFieldSet[newSeg, newCPIndex, FALSE, match];
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, FALSE];
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, FALSE];
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];
};
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: BOOL ¬ TRUE;
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: 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] ¬ CubicPaths.ClosestPointAnalytic[testPoint, path, tolerance];
CodeTimer.StopInt[$CurveClosestPoint, $Gargoyle];
};
PointIsInBox:
PROC [test: Point, box: BoundBoxObj]
RETURNS [
BOOL] = {
RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ];
};
KnotBoundBox:
PROC [seg: Segment, bBox: BoundBox] = {
GGSegmentTypes.BoundBoxProc
For B-Splines, the tight box is just the bounding box of the control points (misnamed knots).
The bound box is an enlargement of the path bounds.
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;
bBox ¬ [loX: minX, loY: minY, hiX: maxX, hiY: maxY, null: FALSE, infinite: FALSE];
};
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.