<> <> <> <> <> <<>> DIRECTORY Atom, Cubic2, CubicPaths, CubicSplines, Feedback, GGBasicTypes, GGBoundBox, GGCubic2, GGInterfaceTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, CodeTimer, GGTransform, GGUtility, Vectors2d, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, RealFns, Rope; GGSegmentImplA: CEDAR PROGRAM IMPORTS Atom, CubicSplines, CubicPaths, GGBoundBox, GGCubic2, Feedback, GGParseIn, GGParseOut, GGSegment, CodeTimer, GGTransform, GGUtility, Vectors2d, IO, Imager, ImagerPath, ImagerTransformation, RealFns, Rope EXPORTS GGSegment = BEGIN VEC: TYPE = Imager.VEC; StrokeEnd: TYPE = Imager.StrokeEnd; X: NAT = CubicSplines.X; Y: NAT = CubicSplines.Y; Bezier: TYPE = Cubic2.Bezier; BezierRef: TYPE = REF Cubic2.Bezier; BitVector: TYPE = GGModelTypes.BitVector; BoundBox: TYPE = GGModelTypes.BoundBox; BuildPathProc: TYPE = GGSegmentTypes.BuildPathProc; BuildPathTransformProc: TYPE = GGSegmentTypes.BuildPathTransformProc; ClassDef: TYPE = REF ClassDefRec; ClassDefRec: TYPE = GGSegment.ClassDefRec; DefaultData: TYPE = GGInterfaceTypes.DefaultData; Circle: TYPE = GGBasicTypes.Circle; Edge: TYPE = GGBasicTypes.Edge; Line: TYPE = GGBasicTypes.Line; Point: TYPE = GGBasicTypes.Point; Segment: TYPE = GGSegmentTypes.Segment; SegmentClass: TYPE = REF SegmentClassObj; SegmentClassObj: TYPE = GGSegmentTypes.SegmentClassObj; SegmentObj: TYPE = GGSegmentTypes.SegmentObj; SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; Vector: TYPE = GGBasicTypes.Vector; SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal; SequenceOfRealObj: TYPE = GGBasicTypes.SequenceOfRealObj; defaultStrokeWidth: REAL _ 2.0; defaultStrokeEnd: StrokeEnd _ round; segmentClasses: LIST OF ClassDef; Problem: ERROR [msg: Rope.ROPE] = Feedback.Problem; <> RegisterSegmentClass: PUBLIC PROC [classDef: ClassDef] = { segmentClasses _ CONS[classDef, segmentClasses]; }; FetchSegmentClass: PUBLIC PROC [type: ATOM] RETURNS [class: SegmentClass] = { FOR l: LIST OF ClassDef _ segmentClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; ERROR Problem[msg: IO.PutFR["SegmentClass %g is unknown", [rope[Atom.GetPName[type]]] ]]; }; CopySegment: PUBLIC PROC [seg: Segment] RETURNS [copy: Segment] = { copyData: REF ANY; copyData _ seg.class.copyData[seg]; copy _ NEW[SegmentObj _ [ class: seg.class, looks: seg.looks, strokeWidth: seg.strokeWidth, strokeEnd: seg.strokeEnd, dashed: seg.dashed, pattern: GGUtility.CopyPattern[seg.pattern], -- this is a REF, so must copy offset: seg.offset, length: seg.length, color: seg.color, lo: seg.lo, hi: seg.hi, TselectedInFull: seg.TselectedInFull, bBox: GGBoundBox.CopyBoundBox[seg.bBox], data: copyData, props: seg.props ]]; }; CopyLooks: PUBLIC PROC [from, to: Segment] = { to.color _ from.color; to.strokeWidth _ from.strokeWidth; to.strokeEnd _ from.strokeEnd; to.dashed _ from.dashed; to.pattern _ GGUtility.CopyPattern[from.pattern]; -- this is a REF, so must copy to.offset _ from.offset; to.length _ from.length; }; SetDefaults: PUBLIC PROC [seg: Segment, defaults: DefaultData] = { seg.strokeEnd _ defaults.strokeEnd; seg.dashed _ defaults.dashed; seg.pattern _ GGUtility.CopyPattern[defaults.pattern]; -- this is a REF, so must copy seg.offset _ defaults.offset; seg.length _ defaults.length; seg.color _ defaults.strokeColor; seg.class.setStrokeWidth[seg, defaults.strokeWidth]; }; SameLooks: PUBLIC PROC [seg1: Segment, seg2: Segment] RETURNS [BOOL] = { IF seg1.dashed # seg2.dashed THEN RETURN[FALSE]; IF seg1.strokeEnd # seg2.strokeEnd THEN RETURN[FALSE]; IF seg1.strokeWidth # seg2.strokeWidth THEN RETURN[FALSE]; IF seg1.dashed THEN { IF seg1.offset # seg2.offset THEN RETURN[FALSE]; IF seg1.length # seg2.length THEN RETURN[FALSE]; IF NOT EqualSequence[seg1.pattern, seg2.pattern] THEN RETURN[FALSE]; }; IF NOT GGUtility.EquivalentColors[seg1.color, seg2.color] THEN RETURN[FALSE]; RETURN[TRUE]; }; EqualSequence: PROC [s1, s2: SequenceOfReal] RETURNS [BOOL] = { IF s1.len # s2.len THEN RETURN[FALSE]; FOR i: NAT IN [0..s1.len) DO IF s1[i] # s2[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; ReverseSegment: PUBLIC PROC [seg: Segment] = { temp: Point; temp _ seg.lo; seg.lo _ seg.hi; seg.hi _ temp; seg.class.reverse[seg]; }; OpenUpSegment: PUBLIC PROC [seg: Segment] = { <> 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]; }; <<>> <> 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, <> 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, <> 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. data: data, props: props ]]; UpdateConicBoundBox[seg]; }; ConicBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { <> bBox _ seg.bBox; }; ConicTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { <> bBox _ GGBoundBox.NullBoundBox[]; UpdateConicTightBox[seg, bBox]; }; UpdateConicTightBox: PROC [seg: Segment, bBox: BoundBox] = { <> data: ConicData _ NARROW[seg.data]; CurveBoundBox[seg, bBox]; }; UpdateConicBoundBox: PROC [seg: Segment] = { <> data: ConicData _ NARROW[seg.data]; cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ seg.strokeWidth; pad: REAL _ MAX[cpHalf, strokeWidth]; UpdateConicTightBox[seg, seg.bBox]; GGBoundBox.EnlargeByPoint[seg.bBox, [data.p1.x, data.p1.y] ]; -- CP is far from curve GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; ConicCopyData: GGSegmentTypes.CopyDataProc = { <> <> 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[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]; 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.ReadBlankAndReal[f]; seg _ MakeConic[loPoint, p1, hiPoint, s, NIL]; }; <> <<>> ConicFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]>> data: ConicData _ NARROW[seg.data]; IF controlPointNum # 0 THEN ERROR; SELECT selectClass FROM normal => data.p1Selected.normal _ selected; hot => data.p1Selected.hot _ selected; active => data.p1Selected.active _ selected; ENDCASE => ERROR; }; ConicFieldGet: GGSegmentTypes.ControlPointFieldGetProc = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]>> data: ConicData _ NARROW[seg.data]; IF controlPointNum # 0 THEN ERROR; SELECT selectClass FROM normal => selected _ data.p1Selected.normal; hot => selected _ data.p1Selected.hot; active => selected _ data.p1Selected.active; ENDCASE => ERROR; }; ConicControlPointGet: PROC [seg: Segment, controlPointNum: NAT] RETURNS [point: Point] = { <> 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, controlPointNum: NAT, success: BOOL] = { <> data: ConicData _ NARROW[seg.data]; success _ TRUE; controlPointNum _ 0; point _ [data.p1.x, data.p1.y]; }; <<>> <> BezierData: TYPE = REF BezierDataRec; BezierDataRec: TYPE = RECORD [ b1Selected, b2Selected: SelectedObjectData, path: CubicPaths.Path]; BuildBezierClass: PROC [] RETURNS [class: SegmentClass] = { OPEN GGSegment; class _ NEW[SegmentClassObj _ [ type: $Bezier, copyData: BZCopyData, reverse: BZReverse, boundBox: BZBoundBox, tightBox: BZTightBox, <> 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: NoOpAsSimpleCurve, <> 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]; CubicPaths.UpdateBounds[data.path]; seg _ NEW[SegmentObj _ [ class: FetchSegmentClass[$Bezier], looks: NIL, strokeWidth: defaultStrokeWidth, strokeEnd: defaultStrokeEnd, color: Imager.black, lo: p0, hi: p3, bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down. data: data, props: props ]]; UpdateBZBoundBox[seg]; }; BZBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox _ seg.bBox; }; BZTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox _ GGBoundBox.NullBoundBox[]; UpdateBZTightBox[seg, bBox]; }; UpdateBZBoundBox: PROC [seg: Segment] = { <> <> cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ seg.strokeWidth; pad: REAL _ MAX[cpHalf, strokeWidth]; data: BezierData _ NARROW[seg.data]; cubics: REF CubicPaths.PathSequence _ data.path.cubics; seg.bBox^ _ [ loX: MIN[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x], loY: MIN[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y], hiX: MAX[cubics[0].b0.x, cubics[0].b1.x, cubics[0].b2.x, cubics[0].b3.x], hiY: MAX[cubics[0].b0.y, cubics[0].b1.y, cubics[0].b2.y, cubics[0].b3.y], null: FALSE, infinite: FALSE]; GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; UpdateBZTightBox: PROC [seg: Segment, bBox: BoundBox] = { <> CurveBoundBox[seg, bBox]; }; 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]; 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]; }; <> <<>> 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 { CubicPaths.TransformPath[path, transform]; CubicPaths.EnumeratePath[path, moveTo, curveTo]; CubicPaths.TransformPath[path, ImagerTransformation.Invert[transform]]; } ELSE { oldLo, oldHi, oldC1, oldC2: VEC; [oldLo, oldHi, oldC1, oldC2] _ BZTransformPath[path, transform, lo, hi, controlPoints]; CubicPaths.EnumeratePath[path, moveTo, curveTo]; BZRestorePath[path, oldLo, oldHi, oldC1, oldC2]; }; }; <<>> BZTransformPath: PROC [path: CubicPaths.Path, transform: ImagerTransformation.Transformation, lo, hi: BOOL, controlPoints: BitVector] RETURNS [oldLo, oldHi, oldC1, oldC2: VEC] ~ { oldLo _ path.cubics[0].b0; oldHi _ path.cubics[0].b3; oldC1 _ path.cubics[0].b1; oldC2 _ path.cubics[0].b2; IF lo THEN path.cubics[0].b0 _ ImagerTransformation.Transform[transform, oldLo]; IF hi THEN path.cubics[0].b3 _ ImagerTransformation.Transform[transform, oldHi]; IF controlPoints[0] THEN path.cubics[0].b1 _ ImagerTransformation.Transform[transform, path.cubics[0].b1]; IF controlPoints[1] THEN path.cubics[0].b2 _ ImagerTransformation.Transform[transform, path.cubics[0].b2]; }; BZRestorePath: PROC [path: CubicPaths.Path, oldLo, oldHi, oldC1, oldC2: VEC] ~ { path.cubics[0].b0 _ oldLo; path.cubics[0].b3 _ oldHi; path.cubics[0].b1 _ oldC1; path.cubics[0].b2 _ oldC2; }; <> <<>> 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 = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]>> data: BezierData _ NARROW[seg.data]; SELECT controlPointNum FROM 0 => { SELECT selectClass FROM normal => data.b1Selected.normal _ selected; hot => data.b1Selected.hot _ selected; active => data.b1Selected.active _ selected; ENDCASE => ERROR; }; 1 => { SELECT selectClass FROM normal => data.b2Selected.normal _ selected; hot => data.b2Selected.hot _ selected; active => data.b2Selected.active _ selected; ENDCASE => ERROR; }; ENDCASE => ERROR; }; BZFieldGet: GGSegmentTypes.ControlPointFieldGetProc = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]>> data: BezierData _ NARROW[seg.data]; SELECT controlPointNum FROM 0 => { SELECT selectClass FROM normal => selected _ data.b1Selected.normal; hot => selected _ data.b1Selected.hot; active => selected _ data.b1Selected.active; ENDCASE => ERROR; }; 1 => { SELECT selectClass FROM normal => selected _ data.b2Selected.normal; hot => selected _ data.b2Selected.hot; active => selected _ data.b2Selected.active; ENDCASE => ERROR; }; ENDCASE => ERROR; }; <<>> <> <<>> BZClosestControlPoint: PROC [seg: Segment, testPoint: Point, tolerance: REAL] RETURNS [point: Point, controlPointNum: NAT, success: BOOL] = { <> data: BezierData _ NARROW[seg.data]; <<-- Consider only the middle two control points -- other two are treated as joints>> d0: REAL _ Vectors2d.DistanceSquared[testPoint, data.path.cubics[0].b1]; d1: REAL _ Vectors2d.DistanceSquared[testPoint, data.path.cubics[0].b2]; IF d0 < d1 THEN { d0 _ RealFns.SqRt[d0]; point _ data.path.cubics[0].b1; controlPointNum _ 0; success _ (d0 <= tolerance); } ELSE { d1 _ RealFns.SqRt[d1]; point _ data.path.cubics[0].b2; controlPointNum _ 1; success _ (d1 <= tolerance); }; }; <> <<>> BZAddJoint: PUBLIC PROC [seg: Segment, pos: Point] RETURNS [seg1, seg2: Segment] = { <> data: BezierData _ NARROW[seg.data]; bezier, bezier1, bezier2: Cubic2.Bezier; alpha: REAL; bezier _ data.path.cubics[0]; alpha _ GGCubic2.GetParam[bezier, pos]; [bezier1, bezier2] _ GGCubic2.AlphaSplit[bezier, alpha]; seg1 _ MakeBezier[bezier1.b0, bezier1.b1, bezier1.b2, bezier1.b3, NIL]; seg2 _ MakeBezier[bezier2.b0, bezier2.b1, bezier2.b2, bezier2.b3, NIL]; }; <> <<>> BZDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { rope _ "Bezier"; }; BZFileOut: PROC [seg: Segment, f: IO.STREAM] = { <> 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, <> 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, <> 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. 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]; -- build a path of beziers and compute bboxes. <> UpdateCSBoundBox[seg: seg, boundsOK: TRUE]; }; CSBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox _ seg.bBox; }; CSTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { <> bBox _ GGBoundBox.NullBoundBox[]; CurveBoundBox[seg, bBox]; }; UpdateCSBoundBox: PROC [seg: Segment, boundsOK: BOOL _ FALSE] = { <> data: CubicSplineData _ NARROW[seg.data]; cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ seg.strokeWidth; pad: REAL _ MAX[cpHalf, strokeWidth]; IF data.type = bspline THEN KnotBoundBox[seg] ELSE { CurveBoundBox[seg, seg.bBox, boundsOK]; GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; }; CSCopyData: GGSegmentTypes.CopyDataProc = { <> <> 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 { CubicPaths.TransformPath[data.path, transform]; CubicPaths.EnumeratePath[data.path, moveTo, curveTo]; CubicPaths.TransformPath[data.path, ImagerTransformation.Invert[transform]]; } ELSE { coeffs: CubicSplines.CoeffsSequence; cpsCopy: CubicSplines.KnotSequence _ NEW[CubicSplines.KnotSequenceRec[data.cps.length]]; -- save copy of cps FOR i:INT IN [0..data.cps.length) DO cpsCopy[i] _ data.cps[i]; ENDLOOP; FOR i:INT IN [0..seg.class.controlPointCount[seg]) DO IF controlPoints[i] THEN CSControlPointSet[seg, i, ImagerTransformation.Transform[transform, CSControlPointGet[seg, i]]]; ENDLOOP; IF lo THEN CSEndPointSet[seg: seg, lo: TRUE, point: ImagerTransformation.Transform[transform, seg.lo]]; IF hi THEN CSEndPointSet[seg: seg, lo: FALSE, point: ImagerTransformation.Transform[transform, seg.hi]]; coeffs _ CubicSplines.MakeSpline[data.cps, data.type]; data.path _ CubicPaths.PathFromCubic[coeffs]; CubicPaths.EnumeratePath[data.path, moveTo, curveTo]; data.cps _ cpsCopy; coeffs _ CubicSplines.MakeSpline[data.cps, data.type]; -- restore old spline data data.path _ CubicPaths.PathFromCubic[coeffs]; }; }; CSTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation] = { <> <> data: CubicSplineData _ NARROW[seg.data]; FOR i: NAT IN [0..data.cps.length) DO vec: VEC _ [data.cps[i][X], data.cps[i][Y]]; vec _ ImagerTransformation.Transform[transform, vec]; data.cps[i][X] _ vec.x; data.cps[i][Y] _ vec.y; ENDLOOP; CubicPaths.TransformPath[data.path, transform]; UpdateCSBoundBox[seg]; }; CSEndPointMoved: PROC [seg: Segment, lo: BOOL, newPoint: Point] = { <> data: CubicSplineData _ NARROW[seg.data]; coeffs: CubicSplines.CoeffsSequence; CSEndPointSet[seg, lo, newPoint]; coeffs _ CubicSplines.MakeSpline[data.cps, data.type]; -- must recompute coeffs & path data.path _ CubicPaths.PathFromCubic[coeffs]; UpdateCSBoundBox[seg]; }; CSControlPointMoved: PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum: NAT] = { <> data: CubicSplineData _ NARROW[seg.data]; coeffs: CubicSplines.CoeffsSequence; CSControlPointSet[seg, controlPointNum, ImagerTransformation.Transform[transform, CSControlPointGet[seg, controlPointNum]]]; coeffs _ CubicSplines.MakeSpline[data.cps, data.type]; data.path _ CubicPaths.PathFromCubic[coeffs]; UpdateCSBoundBox[seg]; }; <> <<>> CSDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { data: CubicSplineData _ NARROW[seg.data]; SELECT data.type FROM naturalAL => rope _ "Natural"; cyclicAL => rope _ "Cyclic Natural"; bspline => rope _ "BSpline"; ENDCASE => ERROR; }; CSFileOut: PROC [seg: Segment, f: IO.STREAM] = { <> 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.ReadBlankAndRope[f, "Type:"]; typeWord _ GGParseIn.ReadBlankAndWord[f]; IF Rope.Equal[typeWord, "Natural"] THEN type _ naturalAL ELSE IF Rope.Equal[typeWord, "CyclicNatural"] THEN type _ cyclicAL ELSE IF Rope.Equal[typeWord, "BSpline"] THEN type _ bspline ELSE ERROR; } ELSE IF version >= 8607.22 THEN { GGParseIn.ReadBlankAndRope[f, "Cyclic:"]; [truth: cyclic, good: ok] _ GGParseIn.ReadBOOL[f, version]; IF NOT ok THEN ERROR; -- Bad format in input file. Expecting BOOL. IF cyclic THEN type _ cyclicAL ELSE type _ naturalAL; } ELSE type _ naturalAL; cps _ NEW[CubicSplines.KnotSequenceRec[GGParseIn.ReadBlankAndNAT[f]+2]]; FOR i: INT IN [1..cps.length-1) DO cps[i] _ PointToFP[GGParseIn.ReadPoint[f]]; ENDLOOP; IF type = cyclicAL AND (loPoint # hiPoint) THEN ERROR; -- both joints of cyclic spline should have the same position cps[0] _ PointToFP[loPoint]; cps[cps.length-1] _ PointToFP[hiPoint]; seg _ MakeCubicSpline[cps, type, NIL]; }; <> <<>> CSFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]>> data: CubicSplineData _ NARROW[seg.data]; SELECT selectClass FROM normal => data.cpsSelected[controlPointNum].normal _ selected; hot => data.cpsSelected[controlPointNum].hot _ selected; active => data.cpsSelected[controlPointNum].active _ selected; ENDCASE => ERROR; }; CSFieldGet: GGSegmentTypes.ControlPointFieldGetProc = { <<[seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]>> data: CubicSplineData _ NARROW[seg.data]; SELECT selectClass FROM normal => selected _ data.cpsSelected[controlPointNum].normal; hot => selected _ data.cpsSelected[controlPointNum].hot; active => selected _ data.cpsSelected[controlPointNum].active; ENDCASE => ERROR; }; <> <<>> CSControlPointGet: PROC [seg: Segment, controlPointNum: NAT] RETURNS [point: Point] = { <> 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, controlPointNum: NAT, success: BOOL] = { <> data: CubicSplineData _ NARROW[seg.data]; <<-- Consider only the inner control points (edgepoints are treated as joints)>> nextDist: REAL; minDist: REAL; IF seg.class.controlPointCount[seg] = 0 THEN RETURN[[0,0], 999, FALSE]; minDist _ Vectors2d.DistanceSquared[testPoint, CSControlPointGet[seg, 0]]; controlPointNum _ 0; FOR i:INT IN [1..seg.class.controlPointCount[seg]) DO IF (nextDist _ Vectors2d.DistanceSquared[testPoint, CSControlPointGet[seg, i]]) < minDist THEN { minDist _ nextDist; controlPointNum _ i; }; ENDLOOP; minDist _ RealFns.SqRt[minDist]; success _ (minDist <= tolerance); point _ CSControlPointGet[seg, controlPointNum]; }; <> <<>> CSAddJoint: PROC [seg: Segment, pos: Point] RETURNS [seg1, seg2: Segment] = { <> data: CubicSplineData _ NARROW[seg.data]; data1, data2: CubicSplineData; oldCPCount: INT _ seg.class.controlPointCount[seg]; nearest: INT _ FindNearestPath[data.path, pos]; cpsInSeg1: INT _ PathNumToCPIndex[seg, nearest]; -- The number of cps that will be in seg1 cpsInSeg2: INT _ oldCPCount - cpsInSeg1; coeffs: CubicSplines.CoeffsSequence; seg1 _ CopySegment[seg]; seg2 _ CopySegment[seg]; data1 _ NARROW[seg1.data]; data2 _ NARROW[seg2.data]; IF data.type=cyclicAL THEN data1.type _ data2.type _ naturalAL; --cyclic splines are changed <<--create and fill in the new CP array of seg1>> data1.cps _ NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpsInSeg2]]; data1.cpsSelected _ NEW[SelectionSequenceObj[cpsInSeg1]]; CSEndPointSet[seg1, TRUE, seg.lo]; CSEndPointSet[seg1, FALSE, pos]; seg1.hi _ pos; FOR i:INT IN [0..cpsInSeg1) DO CSControlPointSet[seg1, i, CSControlPointGet[seg, i]]; data1.cpsSelected[i] _ data.cpsSelected[i]; ENDLOOP; coeffs _ CubicSplines.MakeSpline[data1.cps, data1.type]; data1.path _ CubicPaths.PathFromCubic[coeffs]; <<--create and fill in the new CP array of seg2>> data2.cps _ NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpsInSeg1]]; data2.cpsSelected _ NEW[SelectionSequenceObj[cpsInSeg2]]; CSEndPointSet[seg2, TRUE, pos]; CSEndPointSet[seg2, FALSE, seg.hi]; seg2.lo _ pos; FOR i:INT IN [cpsInSeg1..oldCPCount) DO CSControlPointSet[seg2, i-cpsInSeg1, CSControlPointGet[seg, i]]; data2.cpsSelected[i-cpsInSeg1] _ data.cpsSelected[i]; ENDLOOP; coeffs _ CubicSplines.MakeSpline[data2.cps, data2.type]; data2.path _ CubicPaths.PathFromCubic[coeffs]; UpdateCSBoundBox[seg1]; -- added by KAP. December 22, 1986 UpdateCSBoundBox[seg2]; -- added by KAP. December 22, 1986 }; CSControlPointAdd: PUBLIC PROC [seg: Segment, pos: Point] RETURNS [Segment] = { <> 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]; FOR i:INT IN [newCPIndex..oldCPCount) DO CSControlPointSet[newSeg, i+1, CSControlPointGet[seg, i]]; newSegData.cpsSelected[i+1] _ data.cpsSelected[i]; ENDLOOP; coeffs _ CubicSplines.MakeSpline[newSegData.cps, newSegData.type]; newSegData.path _ CubicPaths.PathFromCubic[coeffs]; UpdateCSBoundBox[newSeg]; -- added by KAP. December 22, 1986 RETURN [newSeg]; }; CSControlPointDelete: PUBLIC PROC [seg: Segment, cpVec: BitVector] RETURNS [Segment] = { <> CountSelected: PROC [bitVec: BitVector] RETURNS [selectedCount: INT _ 0] = { FOR i:INT IN [0..bitVec.len) DO IF bitVec[i] THEN selectedCount _ selectedCount + 1; ENDLOOP; }; data: CubicSplineData _ NARROW[seg.data]; currentCPIndex: INT _ 0; oldCPCount: INT _ seg.class.controlPointCount[seg]; cpSelectedCount: INT _ CountSelected[cpVec]; newSeg: Segment _ CopySegment[seg]; coeffs: CubicSplines.CoeffsSequence; newSegData: CubicSplineData _ NARROW[newSeg.data]; newSegData.cps _ NEW[CubicSplines.KnotSequenceRec[data.cps.length-cpSelectedCount]]; newSegData.cpsSelected _ NEW[SelectionSequenceObj[data.cps.length-cpSelectedCount]]; CSEndPointSet[newSeg, TRUE, seg.lo]; CSEndPointSet[newSeg, FALSE, seg.hi]; FOR i:INT IN [0..oldCPCount) DO IF NOT cpVec[i] THEN { CSControlPointSet[newSeg, currentCPIndex, CSControlPointGet[seg, i]]; newSegData.cpsSelected[currentCPIndex] _ data.cpsSelected[i]; currentCPIndex _ currentCPIndex + 1; }; ENDLOOP; coeffs _ CubicSplines.MakeSpline[newSegData.cps, newSegData.type]; newSegData.path _ CubicPaths.PathFromCubic[coeffs]; UpdateCSBoundBox[newSeg]; -- added by KAP. December 22, 1986 RETURN [newSeg]; }; <<>> CSControlPointSet: PROC [seg: Segment, controlPointNum: NAT, point: Point] = { data: CubicSplineData _ NARROW[seg.data]; SELECT data.type FROM naturalAL, cyclicAL => data.cps[controlPointNum+1] _ PointToFP[point]; bspline => data.cps[controlPointNum+3] _ PointToFP[point]; ENDCASE => ERROR; }; CSEndPointSet: PROC [seg: Segment, lo: BOOL, point: Point] = { data: CubicSplineData _ NARROW[seg.data]; SELECT data.type FROM naturalAL => { IF lo THEN data.cps[0] _ PointToFP[point] ELSE data.cps[data.cps.length-1] _ PointToFP[point]; }; cyclicAL => data.cps[0] _ data.cps[data.cps.length-1] _ PointToFP[point]; bspline => { IF lo THEN data.cps[0] _ data.cps[1] _ data.cps[2] _ PointToFP[point] ELSE data.cps[data.cps.length-1] _ data.cps[data.cps.length-2] _ data.cps[data.cps.length-3] _ PointToFP[point]; }; ENDCASE => ERROR; }; <> <<>> 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: GGBasicTypes.BoundBoxObj _ [seg.bBox.loX-tolerance, seg.bBox.loY-tolerance, seg.bBox.hiX+tolerance, seg.bBox.hiY+tolerance, FALSE, FALSE]; IF NOT PointIsInBox[testPoint, bigBox] THEN RETURN[[0,0], FALSE]; }; CodeTimer.StartInt[$CurveClosestPoint, $Gargoyle]; path _ GetPath[seg]; [point, success] _ GGCubic2.ClosestPointAnalytic[testPoint, path, tolerance]; CodeTimer.StopInt[$CurveClosestPoint, $Gargoyle]; }; PointIsInBox: PROC [test: Point, box: GGBasicTypes.BoundBoxObj] RETURNS [BOOL] = { RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; KnotBoundBox: PROC [seg: Segment] = { <> <> data: CubicSplineData _ NARROW[seg.data]; cps: CubicSplines.KnotSequence _ data.cps; minX, maxX: REAL _ cps[0][X]; minY, maxY: REAL _ cps[0][Y]; FOR i:INT IN [1..cps.length) DO IF maxX < cps[i][X] THEN maxX _ cps[i][X]; IF minX > cps[i][X] THEN minX _ cps[i][X]; IF maxY < cps[i][Y] THEN maxY _ cps[i][Y]; IF minY > cps[i][Y] THEN minY _ cps[i][Y]; ENDLOOP; seg.bBox^ _ [loX: minX, loY: minY, hiX: maxX, hiY: maxY, null: FALSE, infinite: FALSE]; GGBoundBox.AllowForJoints[seg.bBox]; }; <> <<>> FPToPoint: PROC [FP: CubicSplines.FPCoords] RETURNS [point: Point] = { RETURN [[FP[X], FP[Y]]]; }; PointToFP: PROC [point: Point] RETURNS [CubicSplines.FPCoords] = { RETURN [[point.x, point.y]]; }; FindNearestPath: PROC [path: CubicPaths.Path, pos: Point] RETURNS [INT] = { <> 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]]; <> FOR l: LIST OF BezierRef _ list, l.rest UNTIL l=NIL DO count _ count-1; --count starts at number of entries, not last entry path.cubics[count] _ l.first^; ENDLOOP; CubicPaths.UpdateBounds[path]; RETURN[path]; }; <> <<>> Init: PROC [] = { classDef: ClassDef _ NEW[ClassDefRec _ [type: $Bezier, class: BuildBezierClass[]]]; RegisterSegmentClass[classDef]; classDef _ NEW[ClassDefRec _ [type: $CubicSpline, class: BuildCubicSplineClass[]]]; RegisterSegmentClass[classDef]; classDef _ NEW[ClassDefRec _ [type: $Conic, class: BuildConicClass[]]]; RegisterSegmentClass[classDef]; }; InitStats: PROC [] = { interval: CodeTimer.Interval; interval _ CodeTimer.CreateInterval[$CurveClosestPoint]; CodeTimer.AddInt[interval, $Gargoyle]; }; Init[]; InitStats[]; END.