DIRECTORY Atom, CodeTimer, Cubic2, Cubic2Extras, CubicPaths, CubicPathsExtras, 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, Cubic2Extras, CubicPaths, CubicPathsExtras, 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; RegisterSegmentClass: PUBLIC PROC [classDef: ClassDef] = { segmentClasses _ CONS[classDef, segmentClasses]; }; FetchSegmentClass: PUBLIC PROC [type: ATOM] RETURNS [class: SegmentClass] = { FOR l: LIST OF ClassDef _ segmentClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; ERROR Problem[msg: IO.PutFR["SegmentClass %g is unknown", [rope[Atom.GetPName[type]]] ]]; }; CopySegment: PUBLIC PROC [seg: Segment] RETURNS [copy: Segment] = { copyData: REF ANY; copyData _ seg.class.copyData[seg]; copy _ NEW[SegmentObj _ [ class: seg.class, looks: seg.looks, strokeWidth: seg.strokeWidth, strokeEnd: seg.strokeEnd, dashed: seg.dashed, pattern: GGUtility.CopyPattern[seg.pattern], -- this is a REF, so must copy offset: seg.offset, length: seg.length, color: seg.color, lo: seg.lo, hi: seg.hi, TselectedInFull: seg.TselectedInFull, bBox: GGBoundBox.CopyBoundBox[seg.bBox], 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] = { 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] = { 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]; }; 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, transform: ConicTransform, endPointMoved: ConicEndPointMoved, controlPointMoved: ConicControlPointMoved, buildPath: ConicBuildPath, buildPathTransform: ConicBuildPathTransform, controlPointGet: ConicControlPointGet, controlPointCount: ConicControlPointCount, controlPointFieldSet: ConicFieldSet, controlPointFieldGet: ConicFieldGet, closestPoint: CurveClosestPoint, closestControlPoint: ConicClosestControlPoint, closestPointAndTangent: NoOpClosestPointAndTangent, lineIntersection: NoOpLineIntersection, circleIntersection: NoOpCircleIntersection, asSimpleCurve: NoOpAsSimpleCurve, asPolyline: ConicAsPolyline, cPNormal: NoOpCPNormal, jointNormal: NoOpJointNormal, addJoint: GGSegment.NoOpAddJoint, describe: ConicDescribe, fileOut: ConicFileOut, fileIn: ConicFileIn, 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] = { 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] = { bBox _ seg.bBox; }; ConicTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { 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] = { 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 = { 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] = { 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] = { 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] = { 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] = { 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] = { 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] = { 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]; }; ConicDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { rope _ "Parabolic Conic"; }; ConicFileOut: PROC [seg: Segment, f: IO.STREAM] = { data: ConicData _ NARROW[seg.data]; p1: Point _ [data.p1.x, data.p1.y]; GGParseOut.WritePoint[f, p1]; f.PutF[" %g", [real[data.s]]]; }; ConicFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { s: REAL; p1: Point; p1 _ GGParseIn.ReadPoint[f]; s _ GGParseIn.ReadWReal[f]; seg _ MakeConic[loPoint, p1, hiPoint, s, NIL]; }; ConicFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { 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 = { 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] = { data: ConicData _ NARROW[seg.data]; IF controlPointNum#0 THEN ERROR; RETURN[ [data.p1.x, data.p1.y] ]; }; ConicControlPointCount: PROC [seg: Segment] RETURNS [controlPointCount: NAT] = { 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] = { 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] = { 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, transform: BZTransform, endPointMoved: BZEndPointMoved, controlPointMoved: BZControlPointMoved, buildPath: CurveBuildPath, buildPathTransform: BZBuildPathTransform, controlPointGet: BZControlPointGet, controlPointCount: BZControlPointCount, controlPointFieldSet: BZFieldSet, controlPointFieldGet: BZFieldGet, closestPoint: CurveClosestPoint, closestControlPoint: BZClosestControlPoint, closestPointAndTangent: GGSegment.NoOpClosestPointAndTangent, lineIntersection: GGSegment.NoOpLineIntersection, circleIntersection: GGSegment.NoOpCircleIntersection, asSimpleCurve: BZAsSimpleCurve, asPolyline: BZAsPolyline, cPNormal: NoOpCPNormal, jointNormal: BZJointNormal, addJoint: BZAddJoint, describe: BZDescribe, fileOut: BZFileOut, fileIn: BZFileIn, 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] = { data: BezierData _ NEW[BezierDataRec _ [ path: NEW[CubicPaths.PathRec] ]]; data.path.cubics _ NEW[CubicPaths.PathSequence[1]]; data.path.cubics[0] _ [p0, p1, p2, p3]; 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] = { pad: REAL; data: BezierData _ NARROW[seg.data]; cubics: REF CubicPaths.PathSequence _ data.path.cubics; GGBoundBox.UpdateBoundBox[bBox: seg.tightBox, loX: MIN[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x], loY: MIN[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y], hiX: MAX[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x], hiY: MAX[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y]]; 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 = { 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] = { data: BezierData _ NARROW[seg.data]; CubicPaths.ReversePath[data.path]; }; BZTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation] = { data: BezierData _ NARROW[seg.data]; CubicPaths.TransformPath[data.path, transform, FALSE]; UpdateBZBoundBox[seg]; }; BZEndPointMoved: PROC [seg: Segment, lo: BOOL, newPoint: Point] = { 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] = { 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] = { path: CubicPaths.Path _ GetPath[seg]; moveTo: ImagerPath.MoveToProc _ {}; IF entire THEN { 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]; }; }; 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; }; BZControlPointGet: PROC [seg: Segment, controlPointNum: NAT] RETURNS [point: Point] = { 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] = { data: BezierData _ NARROW[seg.data]; -- just for NarrowRefFault if there is a problem RETURN[2]; }; BZFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { 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 = { 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] = { data: BezierData _ NARROW[seg.data]; 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] = { 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] = { data: BezierData _ NARROW[seg.data]; bezier, bezier1, bezier2: Cubic2.Bezier; alpha: REAL; bezier _ data.path.cubics[0]; alpha _ CubicPathsExtras.GetParam[bezier, pos]; [bezier1, bezier2] _ Cubic2Extras.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] = { 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] = { 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: GGSegment.NoOpSameShapeProc, transform: CSTransform, endPointMoved: CSEndPointMoved, controlPointMoved: CSControlPointMoved, buildPath: CurveBuildPath, buildPathTransform: CSBuildPathTransform, controlPointGet: CSControlPointGet, controlPointCount: CSControlPointCount, controlPointFieldSet: CSFieldSet, controlPointFieldGet: CSFieldGet, closestPoint: CurveClosestPoint, closestControlPoint: CSClosestControlPoint, closestPointAndTangent: NoOpClosestPointAndTangent, lineIntersection: NoOpLineIntersection, circleIntersection: NoOpCircleIntersection, asSimpleCurve: NoOpAsSimpleCurve, asPolyline: CSAsPolyline, cPNormal: CSCPNormal, jointNormal: CSJointNormal, describe: CSDescribe, fileOut: CSFileOut, fileIn: CSFileIn, addJoint: CSAddJoint, setStrokeWidth: CSSetStrokeWidth ]]; }; CSSetStrokeWidth: PROC [seg: Segment, strokeWidth: REAL] = { seg.strokeWidth _ strokeWidth; UpdateCSBoundBox[seg]; }; MakeCubicSpline: PUBLIC PROC [cps: CubicSplines.KnotSequence, type: CubicSplines.SplineType, props: LIST OF REF ANY] RETURNS[seg: Segment] = { 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; 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 ]]; 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] = { 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 = { 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] = { 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] = { data: CubicSplineData _ NARROW[seg.data]; moveTo: ImagerPath.MoveToProc _ {}; IF entire THEN { 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]; CubicPaths.EnumeratePath[CubicPaths.PathFromCubic[coeffs, FALSE], moveTo, curveTo]; data.cps _ cpsCopy; -- restore old CPs }; }; CSTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation] = { 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] = { 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] = { 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] = { data: CubicSplineData _ NARROW[seg.data]; cpCount: NAT _ seg.class.controlPointCount[seg]; SELECT data.type FROM naturalAL => f.PutF["Type: Natural "]; cyclicAL => f.PutF["Type: CyclicNatural "]; bspline => f.PutF["Type: BSpline "]; ENDCASE => ERROR; f.PutF["%g ", [integer[cpCount]] ]; FOR i: INT IN [0..cpCount) DO GGParseOut.WritePoint[f, CSControlPointGet[seg, i]]; f.PutChar[' ]; ENDLOOP; }; CSFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { cps: CubicSplines.KnotSequence; type: CubicSplines.SplineType; typeWord: Rope.ROPE; cyclic, ok: BOOL; IF version >= 8608.12 THEN { GGParseIn.ReadWRope[f, "Type:"]; typeWord _ GGParseIn.ReadWWord[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.ReadWRope[f, "Cyclic:"]; [truth: cyclic, good: ok] _ GGParseIn.ReadBool[f, version]; IF NOT ok THEN ERROR; -- Bad format in input file. Expecting BOOL. IF cyclic THEN type _ cyclicAL ELSE type _ naturalAL; } ELSE type _ naturalAL; cps _ NEW[CubicSplines.KnotSequenceRec[GGParseIn.ReadWNAT[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 = { 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 = { 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] = { 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] = { 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] = { data: CubicSplineData _ NARROW[seg.data]; 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]] = { 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]] = { normal1, normal2, direction: Vector; p0,p1,p2,p3: Point; offset: INT; data: CubicSplineData _ NARROW[seg.data]; path: CubicPaths.Path _ GetPath[seg]; 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] = { 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 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]; 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] = { 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; 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] = { 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; }; CurveBoundBox: PROC [seg: Segment, bBox: BoundBox, boundsOK: BOOL _ FALSE] = { 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] = { 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] = { 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] _ CubicPathsExtras.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] = { 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] = { 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] = { 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]]; 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]; }; Init[]; END. όGGSegmentImplA.mesa Copyright Σ 1985, 1986, 1987, 1988, 1990 by Xerox Corporation. All rights reserved. Last edited by Pier on January 27, 1988 3:43:28 pm PST Bier, April 11, 1990 2:40:42 pm PDT Eisenman, August 11, 1987 4:37:34 pm PDT Kurlander, August 21, 1987 0:30:49 am PDT Doug Wyatt, January 3, 1990 3:32:35 pm PST Contents: Procedures which implement the Gargoyle segment classes. Conics, Beziers, and Cubics Generic Segment Class Procedures Changes all of the cyclic segments in the traj to non-cyclic segs A convenience routine which does a TransformSegment, where transform is a simple translation. The Line Class The Conic Class Transforming Drawing Control Point Access Hit Testing Editing Textual Description Editing 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 •NewlineDelimiter – "cedar" style˜codešœ™KšœT™TKšœ6™6Kšœ#™#Kšœ(™(K™)K™*KšΟnœ;™C—K™K™K˜šΟk ˜ Jšœ½žœ'˜ζ—K˜šœžœž˜JšžœΫžœ&˜ŠKšžœ ž˜K˜Kšœžœ˜Kšœ žœžœ˜Kšœ žœ˜)Kšœ žœžœ ˜!Kšœ žœ˜,Kšœžœ ˜3Kšœžœ)˜EKšœ žœžœ ˜!Kšœ žœ˜*Kšœ žœ ˜1Kšœžœ˜#Kšœžœ˜Kšœžœ˜Kšœžœ˜!Kšœ žœ˜'Kšœžœžœ˜)Kšœžœ"˜7Kšœ žœ˜-Kšœžœ%˜=Kšœžœ!˜5Kšœ žœ˜#Kšœžœ˜#Kšœžœžœ˜-Kšœžœ!˜8Kšžœžœ žœ˜K˜Kšœžœ˜Kšœ$˜$Kšœžœžœ ˜!Kšœžœ˜Kšœžœ˜—K˜Kšœžœ žœ˜3K˜šœ ™ K˜—šœžœžœ˜:Kšœžœ˜0Kšœ˜K˜—š œžœžœžœžœ˜Mš žœžœžœ#žœžœž˜?Kšžœžœžœ˜0Kšžœ˜—KšžœžœD˜YK˜K˜—š œžœžœžœ˜CKšœ žœžœ˜Kšœ#˜#šœžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ-Οc˜KKšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ%˜%Kšœ(˜(Kšœ0˜0Kšœ˜Kšœ&žœŸ˜IKšœ˜—K˜K˜—š œžœžœ˜.K˜Kšœ"˜"Kšœ˜Kšœ˜Kšœ2Ÿ˜PKšœ˜Kšœ˜K˜K˜—š œžœžœ*˜BKšœ#˜#Kšœ˜Kšœ7Ÿ˜UKšœ˜Kšœ˜Kšœ!˜!Kšœ4˜4K˜K˜—š  œžœžœ žœžœ˜HKšžœžœžœžœ˜0Kšžœ!žœžœžœ˜6Kšžœ%žœžœžœ˜:šžœ žœ˜Kšžœžœžœžœ˜0Kšžœžœžœžœ˜0Kš žœžœ+žœžœžœ˜DK˜—Kš žœžœ4žœžœžœ˜MKšžœžœ˜ K˜K˜—š  œžœžœ žœžœ˜NKšžœ$˜*K˜K˜—š œžœžœžœ˜?Kšžœžœžœžœ˜&šžœžœžœ ž˜Kšžœžœžœžœ˜$Kšžœ˜Kšžœžœ˜ —K˜K˜—šœžœžœ˜.K˜ K˜K˜K˜K˜K˜K˜—š œžœžœ˜.K™Ašžœžœ˜%Jšœžœ ˜)šžœžœ˜Jšœ˜JšœTŸ˜gJ˜—J˜—J˜J˜—šœžœžœD˜aKšœ2˜2Kšœ2˜2Kšœ$˜$K˜K˜—šœžœžœ$˜AKšœ]™]K˜/KšœA˜AKšœ!˜!K˜K˜—šœžœžœžœ˜NKšžœžœ˜Kšžœ˜Kšœ+˜+K˜—K™Kšœ™šœžœžœ6˜KKš žœ žœžœžœž˜3šžœ˜Kšœ+˜+Kšœ'˜'Kšœ˜šžœ žœ˜Kš œ/˜;Kš  œžœžœžœžœžœ˜AKšœ&˜&Kšœ\˜\Kšœ˜—Kšžœ˜#K˜—K˜K˜—K™šœ™K˜—Kšœ žœžœ˜#šœžœžœ˜Kšœžœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜K˜—šœžœžœ˜:KšΠbkΟb ˜šœžœ˜K˜ K˜K˜Kšœ˜K˜˜Kš‘ ™ —K˜K˜"šœ*˜*Kš‘™—Kšœ˜šœ,˜,Kš‘™—Kšœ&˜&Kšœ*˜*K˜$˜$Kš‘ ™ —K˜ Kšœ.˜.Kšœ3˜3Jšœ'˜'Jšœ+˜+Kšœ!˜!Kšœ˜Kšœ˜šœ˜Kš‘™—˜!Kš‘™—K˜Kšœ˜˜K™—Kšœ#˜#K˜—K˜K˜—šœžœžœ˜?Kšœ˜Kšœ˜K˜K˜—š œžœžœžœ žœžœžœžœžœ˜fKšœ"™"KšœG™GKšœ9™9KšœX™XKšœ2™2šœžœ˜&Kšœ$žœžœžœ˜9Kšœ"˜"K˜—šœžœ˜K˜+Kšœžœ˜ Kšœ ˜ Kšœ˜K˜Kšœ˜Kšœ*Ÿ˜HKšœ.Ÿ˜LKšœ ˜ Kšœ ˜ Kšœ˜—Kšœ˜K˜K˜—š œžœžœžœžœ˜SKšœžœ ˜#K˜ K˜ K˜ K˜ K˜K˜—š œžœžœžœ˜6Kšœžœ ˜#K˜ Kšœ;˜;Kšœ˜K˜K˜—šœžœžœ˜?Kšœžœ ˜#K˜ Kšœ;˜;Kšœ˜K˜K˜—š œžœžœ˜?Kšœ™Kšœ˜Kšœ˜K˜—š œžœžœ˜?Kšœ™Kšœ˜Kšœ˜—š œžœžœžœžœ˜JKšœžœ ˜(Kšœžœ ˜(Kšœ˜Kšœ˜K˜K˜—Kšœ žœ˜šœžœ˜,Kšœ™Kšœžœ ˜#Kšœž˜ Kšœžœ˜Kšœžœ ˜*Kšœžœ ˜*Kšœžœ ˜*Kšœžœ ˜*Kšœ@˜@Kšœžœ˜$šžœžœ'˜EKšžœ!˜%—Kšœ*˜*Kšœ˜K˜—š œ!˜.Kšœ™K™JKšœžœ ˜(Kšœžœ˜#K˜K˜Kšœ&˜&K˜/Kšžœ˜ K˜K˜—š œžœ˜%Kšœ™K™TKšœžœ ˜#K˜"K˜—K˜šœ ™ K™—šœžœ‘˜₯Kšœ™Kšœžœ ˜#Kšœ!˜!K˜K˜—šœžœPžœ˜ŽKšœ%™%Kšœfžœ#žœ*™»Kšœžœ ˜#Kšœ žœ˜šžœžœ˜Kšœ.žœžœ ˜EKšœ8˜8K˜—šžœ˜KšœA˜AKš œžœžœžœžœ3˜eKšžœ ˜ K˜—K˜K˜—K™šœ™K™—šœžœC˜WKšœ™K™ƒKšœžœ ˜#Kšœ=˜=Kšœ/žœ˜6Kšœ˜K˜K˜—šœžœžœ˜FKšœ ™ Kšœžœ ˜#Kšœ žœžœ žœ˜,Kšœ#˜#Kšœ žœžœžœ ˜,Kšœ.˜.Kšœ˜Kšœ˜K˜—šœžœQžœ˜uKšœ$™$Kšœžœ˜ Kšœ ˜ Kšœžœ ˜#Kšžœžœžœ˜ Kšœ;˜;Kšœ˜Kšœ6˜6Kšœ˜Kšœ˜Kšœ˜K˜—š œžœžœ2žœžœ˜ŠKšžœžœC˜MKšžœ˜KšžœžœC˜MKšžœ˜K˜—™K™—K™Kšœ™š  œžœžœžœ žœ˜dKšœ˜K˜K˜—š œžœžœžœ˜3Kšœ™Kšœžœ ˜#Kšœ#˜#Kšœ˜Kšœ˜K˜K˜—•StartOfExpansionm -- [f: STREAM, loPoint: GGBasicTypes.Point, hiPoint: GGBasicTypes.Point] RETURNS [seg: GGModelTypes.Segment]š  œžœžœžœ$žœžœ˜cKšœ™Kšœžœ˜Kšœ ˜ Kšœ˜K˜Kšœ)žœ˜.K˜K˜—šœ ™ K™—–s -- [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]š œ-˜:Kšœ/žœ žœ-™oKšœžœ ˜#Kšžœžœžœ˜"šžœ ž˜Kšœ,˜,Kšœ&˜&Kšœ,˜,K˜*Kšžœžœ˜—K˜K˜—–| -- [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]š œ-˜:Kšœ/žœ.žœ žœ™xKšœžœ ˜#Kšžœžœžœ˜"šžœ ž˜Kšœ,˜,Kšœ&˜&Kšœ,˜,K˜*Kšžœžœ˜—K˜K˜—šœžœ!žœžœ˜ZKšœ"™"Kšœžœ ˜#Kšžœžœžœ˜ Kšžœ˜!Kšœ˜K˜—šœžœžœžœ˜PJšœ$™$Kšœžœ Ÿ0˜TKšžœ˜ Kšœ˜—K™šœ™K™—š œžœ-žœžœ:žœ žœ˜©Kšœ&™&Kšœžœ ˜#Kšœ žœ˜Kšœ˜Kšœ˜K˜—K™šœžœžœžœ,˜iK™QKšœžœ ˜#Kšœ žœ˜Kšœžœ˜K˜ Kšœ ˜ K˜ K˜ K˜Kšœ3˜3K˜K˜—š œžœžœžœžœžœ˜.Kšžœ!˜'Kšœ˜K˜—š œžœžœžœžœžœ˜.Kšžœ˜Kšœ˜K˜—š  œžœžœžœ žœžœ,˜sšžœž˜ šœ ˜ Kšœ žœ$˜2KšœJ˜JKšœ˜—šœ ˜ Kšœ žœ$˜2Kšœ8˜8Kšœ˜—šžœ˜ Kšœžœ˜Kšœžœ˜"š žœžœžœžœŸ$˜QKšœ žœ$˜2Kšœ8˜8K˜—šžœ˜Kšœžœ˜$Kšœžœ%˜/Kšœ*˜*Kšœ žœ˜KšœX˜XKšœX˜XKšœ*˜*Kšœ žœ.˜žœ<žœ˜ˆKšœ˜—Kšžœ˜Kšœ˜—K˜šœ™K™—š œžœžœžœ˜TKšœ™Kšœžœ ˜$K˜(Kšœžœ˜ Kšœ˜Kšœ/˜/Kšœ<˜Kšœ žœ+˜NKšœžœ-˜DKšžœžœ˜—Kš‘™šœžœ˜!Kšœ˜Kšœ žœ"˜2K˜—šœžœ˜K˜'Kšœžœ˜ Kšœ ˜ Kšœ˜K˜K˜K˜!Kšœ*Ÿ˜HKšœ.Ÿ˜LKšœ ˜ Kšœ ˜ Kšœ˜—K™IKšœžœ˜˜>Kšœ1žœ˜8Kšœ7˜7K˜—šžœ˜Kšœ$˜$Kšœ%žœ1Ÿ˜lšžœžœžœž˜$K˜Kšžœ˜—šžœžœžœ'ž˜5Kšžœžœžœžœa˜Ž—Kšžœ˜Kšžœžœžœ<˜gKšžœžœžœ<˜hJ˜6J™-Kšœ5™5K™Kšœ:Ÿ™TJ™-Kšœ:žœ˜SJšœŸ˜&Kšœ˜—Kšœ˜K˜—š œžœC˜TKšœ™K™ƒKšœžœ ˜)šžœžœžœž˜%Kšœžœ$˜,Kšœ5˜5Kšœ˜Kšœ˜Kšžœ˜—Kšœ/žœ˜6Kšœ˜K˜K˜—šœžœžœ˜CKšœ ™ Kšœžœ ˜)K˜$J˜!Kšœ8Ÿ˜WKšœ-žœ˜4Kšœ˜K˜K˜—šœžœQžœ˜rKšœ$™$Kšœžœ ˜)K˜$K˜|K˜6Kšœ-žœ˜4Kšœ˜Kšœ˜K˜—šžœ ™K™—–m -- [f: STREAM, loPoint: GGBasicTypes.Point, hiPoint: GGBasicTypes.Point] RETURNS [seg: GGModelTypes.Segment]š  œžœžœžœ žœ˜aKšœžœ ˜)šžœ ž˜Kšœ˜Kšœ$˜$Kšœ˜Kšžœžœ˜—K˜K˜—š œžœžœžœ˜0Kšœ™Kšœžœ ˜)Kšœ žœ$˜0šžœ ž˜K˜&K˜+K˜$Kšžœžœ˜—Kšœ#˜#šžœžœžœž˜K˜4K˜Kšžœ˜—K˜K˜—–m -- [f: STREAM, loPoint: GGBasicTypes.Point, hiPoint: GGBasicTypes.Point] RETURNS [seg: GGModelTypes.Segment]š œžœžœžœ$žœžœ˜`Kšœ™Kšœ˜K˜Kšœžœ˜Kšœ žœ˜šžœžœ˜K˜ K˜"Kšžœ!žœ˜8Kšžœžœ'žœ˜BKšžœžœ!žœ˜;Kšžœžœ˜ K˜—šžœžœžœ˜!K˜"K˜;Kš žœžœžœžœŸ-˜CKšžœžœžœ˜5K˜—Kšžœ˜Kšœžœ8˜Ašžœžœžœž˜"Kšœ+˜+Kšžœ˜—Kš žœžœžœžœŸ=˜tKšœ˜Kšœ'˜'Kšœ!žœ˜&K˜K˜—šžœ™K™—š œ-˜7Kšœ/žœ žœ-™oKšœžœ ˜)šžœ ž˜Kšœ>˜>Kšœ8˜8Kšœ>˜>Kšœ<˜˜>Kšœ8˜8Kšœ>˜>Kšœ<˜žœ<žœ˜ˆKšœ˜—Kšžœ˜Kšœ˜—Kšœ˜—Kšœ˜—K˜K˜#šœžœžœ˜Kšœžœ žœžœ˜=—š œžœžœžœ,˜fKšœžœ ˜)K˜Kšœžœ˜K˜Kšœ žœ(˜7šžœžœžœž˜-JšœC˜CJšœ,˜,Jšžœ˜—Jšœ žœ.˜žœ<žœ˜ˆKšœ˜—Kšžœ˜Kšœ˜—Kšœ˜K˜—K˜š ’ œžœ)žœžœ'˜pKšœ™Kšœžœ™ Kšœ$˜$Kšœ˜Kšœžœ˜ Kšœžœ ˜)Kšœ%˜%Kšœ<™žœ<žœ˜ˆKšœ˜—Kšžœ˜—Kšœ˜K˜—šžœ™ K™—š œžœžœ˜MKšœ»™»Jšœžœ ˜)J˜Jšœ žœ$˜3Jšœ žœ#˜/Jšœ žœ#Ÿ)˜ZJšœ žœ˜(Kšœ$˜$J˜J˜Jšœžœ ˜Jšœžœ ˜Jšžœžœ&Ÿ˜\JšΠbc-™-Jšœ žœ:˜IJšœžœ"˜9Jšœžœ ˜"Jšœžœ˜ J˜šžœžœžœž˜J˜6J˜+Jšžœ˜—J˜8Jšœ.žœ˜5Jš£-™-Jšœ žœ:˜IJšœžœ"˜9Jšœžœ˜Jšœžœ ˜#J˜šžœžœžœž˜'J˜@J˜5Jšžœ˜—J˜8Jšœ.žœ˜5KšœŸ"˜:KšœŸ"˜:J˜J˜—šœžœžœžœ˜OKšœ©™©Jšœžœ ˜)Jšœ žœ$˜3Jšœ žœ#˜/Jšœ žœ#ŸT˜†J˜#Jšœžœ˜2Kšœ$˜$Jšœžœ™#Jšœžœ2˜FJšœžœ%˜AJšœžœ ˜$Jšœžœ ˜%šžœžœžœž˜J˜8J˜0Jšžœ˜—J˜+Jšœžœ Ÿ#˜QJšœžœ˜+Jšœžœ ˜.Jšœžœ ˜-šžœžœžœž˜(J˜:J˜2Jšžœ˜—J˜BJšœ3žœ˜:KšœŸ"˜Kšœžœ ˜)šžœ ž˜˜Kšžœžœ˜)Kšžœ0˜4K˜—K˜I˜ Kšžœžœ;˜EKšžœl˜pK˜—Kšžœžœ˜—K˜—K˜šœ™K™—š œžœ*žœžœ˜NKšœ™Kšœ-žœ™IKšœ%˜%K˜Kšžœžœ žœ Ÿ˜SKšœ˜Kšœ<˜Jšžœ˜—Jšžœ˜ J˜J˜—š œžœžœžœ žœ˜NJšŸ_™_Jšœžœ ˜)Jšœ žœ$˜0šžœ ž˜J˜)J˜Jšžœžœ˜—Jšžœ žœ ˜Jšžœžœžœ˜1Jšžœ˜J˜J˜—šœžœžœ˜@šžœ žœž˜Kšœ%˜%Kšœ$˜$Kšœ*˜*Kšžœžœ˜—Kšžœ˜ K˜K˜—š œžœžœžœ˜TKšœžœžœ ˜Kšœžœ˜˜!Kšœžœ&˜:Kšœžœ ˜Kšœ ˜ K˜—KšœJ˜JK˜Kšžœ˜ K˜K˜—š  œžœžœžœ žœ˜JKšœžœ˜Kšœžœ˜0š žœžœžœžœžœž˜6K˜Kšžœ˜—Kšœžœ!˜2Kšœ!žœ!™Fš žœžœžœžœžœž˜6KšœŸ3˜DKšœ˜Kšžœ˜—Kšœ˜Kšžœ˜ K˜—K˜š‘ ™ K™—šœžœ˜Kšœžœ;˜SKšœ˜Kšœ žœE˜SKšœ˜Kšœ žœ9˜GKšœ˜K˜K˜—š œžœ™Kšœ™Kšœ8™8Kšœ&™&K™—K˜K˜Kšœ ™ K˜Kšžœ˜J˜—…—Κͺ(³