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];
};
};
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,
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];
};
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=NIL OR 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, 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];
};
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;
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];
};
Conic Hit Testing
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;
};
};
};
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,
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];
};
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, 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];
};
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[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;
};
Bezier Hit Testing
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;}
};
Bezier 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 ¬ 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];
};
Bezier 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,
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];
};
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]];
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];
};
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.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];
};
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;
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;
};
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, 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;
};
CSCPNormal: 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;}
};
};
CSJointNormal: 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;}
};
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, 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];
};
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.