DIRECTORY Atom, CodeTimer, Cubic2, CubicPaths, CubicSplines, Feedback, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGCubic2, GGInterfaceTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGTransform, GGUtility, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, List, Real, RealFns, Rope, Vectors2d; GGSegmentImplA: CEDAR PROGRAM IMPORTS Atom, CodeTimer, Cubic2, CubicPaths, CubicSplines, Feedback, GGBoundBox, GGCoreOps, GGCubic2, GGParseIn, GGParseOut, GGSegment, GGTransform, GGUtility, Imager, ImagerPath, ImagerTransformation, IO, List, Real, RealFns, Rope, Vectors2d EXPORTS GGSegment = BEGIN Bezier: TYPE = Cubic2.Bezier; BezierRef: TYPE = REF Bezier; BitVector: TYPE = GGModelTypes.BitVector; BoundBox: TYPE = REF BoundBoxObj; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; BuildPathProc: TYPE = GGSegmentTypes.BuildPathProc; BuildPathTransformProc: TYPE = GGSegmentTypes.BuildPathTransformProc; ClassDef: TYPE = REF ClassDefRec; ClassDefRec: TYPE = GGSegment.ClassDefRec; DefaultData: TYPE = GGInterfaceTypes.DefaultData; Circle: TYPE = GGBasicTypes.Circle; Edge: TYPE = GGBasicTypes.Edge; Line: TYPE = GGCoreTypes.Line; Point: TYPE = GGBasicTypes.Point; Segment: TYPE = GGSegmentTypes.Segment; SegmentClass: TYPE = REF SegmentClassObj; SegmentClassObj: TYPE = GGSegmentTypes.SegmentClassObj; SegmentObj: TYPE = GGSegmentTypes.SegmentObj; SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; StrokeEnd: TYPE = Imager.StrokeEnd; Vector: TYPE = GGBasicTypes.Vector; SequenceOfReal: TYPE = REF SequenceOfRealObj; SequenceOfRealObj: TYPE = GGCoreTypes.SequenceOfRealObj; VEC: TYPE = Imager.VEC; defaultStrokeWidth: REAL ¬ 2.0; defaultStrokeEnd: StrokeEnd ¬ round; segmentClasses: LIST OF ClassDef; X: NAT = CubicSplines.X; Y: NAT = CubicSplines.Y; Problem: ERROR [msg: Rope.ROPE] = Feedback.Problem; RegisterSegmentClass: PUBLIC PROC [classDef: ClassDef] = { segmentClasses ¬ CONS[classDef, segmentClasses]; }; FetchSegmentClass: PUBLIC PROC [type: ATOM] RETURNS [class: SegmentClass] = { FOR l: LIST OF ClassDef ¬ segmentClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; ERROR Problem[msg: IO.PutFR1["SegmentClass %g is unknown", [rope[Atom.GetPName[type]]] ]]; }; CopySegment: PUBLIC PROC [seg: Segment] RETURNS [copy: Segment] = { copyData: REF ANY; copyData ¬ seg.class.copyData[seg]; copy ¬ NEW[SegmentObj ¬ [ class: seg.class, looks: seg.looks, strokeWidth: seg.strokeWidth, strokeEnd: seg.strokeEnd, dashed: seg.dashed, pattern: GGUtility.CopyPattern[seg.pattern], -- this is a REF, so must copy offset: seg.offset, length: seg.length, color: seg.color, lo: seg.lo, hi: seg.hi, TselectedInFull: seg.TselectedInFull, bBox: GGBoundBox.CopyBoundBox[seg.bBox], tightBox: GGBoundBox.CopyBoundBox[seg.tightBox], data: copyData, props: List.Append[l1: seg.props, l2: NIL] -- Appends l2 to a copy of l1. ]]; }; CopyLooks: PUBLIC PROC [from, to: Segment] = { to.color ¬ from.color; to.strokeWidth ¬ from.strokeWidth; to.strokeEnd ¬ from.strokeEnd; to.dashed ¬ from.dashed; to.pattern ¬ GGUtility.CopyPattern[from.pattern]; -- this is a REF, so must copy to.offset ¬ from.offset; to.length ¬ from.length; }; SetDefaults: PUBLIC PROC [seg: Segment, defaults: DefaultData] = { seg.strokeEnd ¬ defaults.strokeEnd; seg.dashed ¬ defaults.dashed; seg.pattern ¬ GGUtility.CopyPattern[defaults.pattern]; -- this is a REF, so must copy seg.offset ¬ defaults.offset; seg.length ¬ defaults.length; seg.color ¬ defaults.strokeColor; seg.class.setStrokeWidth[seg, defaults.strokeWidth]; }; SameLooks: PUBLIC PROC [seg1: Segment, seg2: Segment] RETURNS [BOOL] = { IF seg1.dashed # seg2.dashed THEN RETURN[FALSE]; IF seg1.strokeEnd # seg2.strokeEnd THEN RETURN[FALSE]; IF seg1.strokeWidth # seg2.strokeWidth THEN RETURN[FALSE]; IF seg1.dashed THEN { IF seg1.offset # seg2.offset THEN RETURN[FALSE]; IF seg1.length # seg2.length THEN RETURN[FALSE]; IF NOT EqualSequence[seg1.pattern, seg2.pattern] THEN RETURN[FALSE]; }; IF NOT GGCoreOps.EquivalentColors[seg1.color, seg2.color] THEN RETURN[FALSE]; RETURN[TRUE]; }; SameShape: PUBLIC PROC [seg1: Segment, seg2: Segment] RETURNS [same: BOOL] = { RETURN [seg1.class.sameShape[seg1, seg2]]; }; EqualSequence: PROC [s1, s2: SequenceOfReal] RETURNS [BOOL] = { IF s1.len # s2.len THEN RETURN[FALSE]; FOR i: NAT IN [0..s1.len) DO IF s1[i] # s2[i] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; ReverseSegment: PUBLIC PROC [seg: Segment] = { temp: Point; temp ¬ seg.lo; seg.lo ¬ seg.hi; seg.hi ¬ temp; seg.class.reverse[seg]; }; OpenUpSegment: PUBLIC PROC [seg: Segment] = { 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.PutF1[" %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.ReadReal[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; floX, floY, fhiX, fhiY: REAL; ft1, ft2, ft3, ft4: REAL; ft1 ¬ cubics[0].b0.x; ft2 ¬ cubics[0].b1.x; ft3 ¬ cubics[0].b2.x; ft4 ¬ cubics[0].b3.x; floX ¬ MIN[ft1, ft2, ft3, ft4]; ft1 ¬ cubics[0].b0.y; ft2 ¬ cubics[0].b1.y; ft3 ¬ cubics[0].b2.y; ft4 ¬ cubics[0].b3.y; floY ¬ MIN[ft1, ft2, ft3, ft4]; ft1 ¬ cubics[0].b0.x; ft2 ¬ cubics[0].b1.x; ft3 ¬ cubics[0].b2.x; ft4 ¬ cubics[0].b3.x; fhiX ¬ MAX[ft1, ft2, ft3, ft4]; ft1 ¬ cubics[0].b0.y; ft2 ¬ cubics[0].b1.y; ft3 ¬ cubics[0].b2.y; ft4 ¬ cubics[0].b3.y; fhiY ¬ MAX[ft1, ft2, ft3, ft4]; GGBoundBox.UpdateBoundBox[bBox: seg.tightBox, loX: floX, loY: floY, hiX: fhiX, hiY: fhiY]; CubicPaths.UpdateBounds[data.path]; GGBoundBox.UpdateBoundBox[seg.bBox, data.path.bounds.x, data.path.bounds.y, data.path.bounds.x + data.path.bounds.w, data.path.bounds.y + data.path.bounds.h]; IF seg.strokeEnd = square THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0 ELSE pad ¬ seg.strokeWidth/2.0 + 1.0; GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; BZCopyData: GGSegmentTypes.CopyDataProc = { 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 ¬ CubicPaths.GetParam[bezier, pos]; [bezier1, bezier2] ¬ Cubic2.AlphaSplit[bezier, alpha]; seg1 ¬ MakeBezier[bezier1.b0, bezier1.b1, bezier1.b2, bezier1.b3, NIL]; seg2 ¬ MakeBezier[bezier2.b0, bezier2.b1, bezier2.b2, bezier2.b3, NIL]; }; BZDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { rope ¬ "Bezier"; }; BZFileOut: PROC [seg: Segment, f: IO.STREAM] = { 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: CSSameShape, 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 ]]; }; CSSameShape: GGSegmentTypes.SameShapeProc = { data1: CubicSplineData _ NARROW[seg1.data]; data2: CubicSplineData _ NARROW[seg2.data]; RETURN[data1.type=data2.type]; }; CSSetStrokeWidth: PROC [seg: Segment, strokeWidth: REAL] = { seg.strokeWidth ¬ strokeWidth; UpdateCSBoundBox[seg]; }; MakeCubicSpline: PUBLIC PROC [cps: CubicSplines.KnotSequence, type: CubicSplines.SplineType, props: LIST OF REF ANY] RETURNS[seg: Segment] = { 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.PutRope["Type: Natural "]; cyclicAL => f.PutRope["Type: CyclicNatural "]; bspline => f.PutRope["Type: BSpline "]; ENDCASE => ERROR; f.PutF1["%g ", [integer[cpCount]] ]; FOR i: INT IN [0..cpCount) DO GGParseOut.WritePoint[f, CSControlPointGet[seg, i]]; f.PutChar[' ]; ENDLOOP; }; CSFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { cps: CubicSplines.KnotSequence; type: CubicSplines.SplineType; typeWord: Rope.ROPE; cyclic: BOOL; IF version >= 8608.12 THEN { GGParseIn.ReadRope[f, "Type:"]; typeWord ¬ GGParseIn.ReadWord[f]; IF Rope.Equal[typeWord, "Natural"] THEN type ¬ naturalAL ELSE IF Rope.Equal[typeWord, "CyclicNatural"] THEN type ¬ cyclicAL ELSE IF Rope.Equal[typeWord, "BSpline"] THEN type ¬ bspline ELSE ERROR; } ELSE IF version >= 8607.22 THEN { GGParseIn.ReadRope[f, "Cyclic:"]; cyclic ¬ GGParseIn.ReadBool[f]; IF cyclic THEN type ¬ cyclicAL ELSE type ¬ naturalAL; } ELSE type ¬ naturalAL; cps ¬ NEW[CubicSplines.KnotSequenceRec[GGParseIn.ReadNAT[f]+2]]; FOR i: INT IN [1..cps.length-1) DO cps[i] ¬ PointToFP[GGParseIn.ReadPoint[f]]; ENDLOOP; IF type = cyclicAL AND (loPoint # hiPoint) THEN ERROR; -- both joints of cyclic spline should have the same position cps[0] ¬ PointToFP[loPoint]; cps[cps.length-1] ¬ PointToFP[hiPoint]; seg ¬ MakeCubicSpline[cps, type, NIL]; }; CSFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { 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] ¬ CubicPaths.ClosestPointAnalytic[testPoint, path, tolerance]; CodeTimer.StopInt[$CurveClosestPoint, $Gargoyle]; }; PointIsInBox: PROC [test: Point, box: BoundBoxObj] RETURNS [BOOL] = { RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; KnotBoundBox: PROC [seg: Segment, bBox: BoundBox] = { 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. "xGGSegmentImplA.mesa Copyright Σ 1985, 1986, 1987, 1988, 1990, 1992 by Xerox Corporation. All rights reserved. Eisenman, August 11, 1987 4:37:34 pm PDT Kurlander, August 21, 1987 0:30:49 am PDT Last edited by Pier on June 30, 1992 7:03 pm PDT Bier, November 30, 1992 5:23 pm PST Doug Wyatt, April 14, 1992 2:31 pm PDT Contents: Procedures which implement the Gargoyle segment classes. Conics, Beziers, and Cubics 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Ÿœ<Ÿœ˜ˆK˜—KšŸœ˜Kšœ˜—K˜šœ™K™—šž œŸœŸœŸœ˜TKšœ™KšœŸœ ˜$K˜(KšœŸœ˜ K˜K˜)K˜6KšœBŸœ˜GKšœBŸœ˜GK˜—K˜šœ™K™—š ž œŸœŸœŸœ Ÿœ˜aK˜K˜K˜—šž œŸœŸœŸœ˜0Kšœ™KšœŸœ ˜$Kšœ1˜1Kšœ ŸœŸœ˜Kšœ1˜1K˜K˜—š žœŸœŸœŸœ$ŸœŸœ˜`Kšœ™K˜K˜K˜Kšœ+Ÿœ˜0K˜—K˜šœ™K˜—KšœŸœŸœ˜/šœŸœŸœ˜#Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ K˜—K–s -- [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]šœŸœŸœ˜3šœŸœŸœ˜%KšœŸœŸœŸœ˜,K˜K˜—šžœŸœŸœ˜@Kš‘’ ˜šœŸœ˜K˜K˜K˜K˜K˜˜Kš’™—K˜K˜šœ'˜'Kš’™—Kšœ˜šœ)˜)Kš’™—Kšœ#˜#Kšœ'˜'Kšœ!˜!šœ!˜!Kš’ ™ —K˜ Kšœ+˜+Kšœ3˜3Kšœ'˜'Kšœ+˜+Kšœ!˜!Kšœ˜Kšœ˜šœ˜Kš’™—K˜Kšœ˜˜Kš’™—K˜Kšœ ˜ K˜—K˜K˜—šž œ#˜.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šŸœŸœŸœ<˜hK˜6J™-Kšœ5™5K™Kšœ: ™TJ™-Kšœ:Ÿœ˜SKšœ ˜&Kšœ˜—Kšœ˜K˜—šž œŸœC˜TKšœ™K™ƒKšœŸœ ˜)šŸœŸœŸœŸ˜%KšœŸœ$˜,K˜5K˜K˜KšŸœ˜—Kšœ/Ÿœ˜6Kšœ˜K˜K˜—šžœŸœŸœ˜CKšœ ™ KšœŸœ ˜)K˜$K˜!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šŸœŸœŸœ˜5K˜—KšŸœ˜KšœŸœ7˜@šŸœŸœŸœŸ˜"K˜+KšŸœ˜—Kš ŸœŸœŸœŸœ =˜tK˜K˜'Kšœ!Ÿœ˜&K˜K˜—šŸœ™K™—šž œ-˜7Kšœ/Ÿœ Ÿœ-™oKšœŸœ ˜)šŸœ Ÿ˜K˜>K˜8K˜>K˜K˜8K˜>K˜Ÿœ<Ÿœ˜ˆK˜—KšŸœ˜Kšœ˜—Kšœ˜—Kšœ˜—K˜K˜#šœŸœŸœ˜KšœŸœ ŸœŸœ˜=—šž œŸœŸœŸœ,˜fKšœŸœ ˜)K˜KšœŸœ˜K˜Kšœ Ÿœ(˜7šŸœŸœŸœŸ˜-K˜CK˜,KšŸœ˜—Kšœ Ÿœ.˜Ÿœ<Ÿœ˜ˆK˜—KšŸœ˜Kšœ˜—Kšœ˜K˜—K˜š ž£ œŸœ)ŸœŸœ'˜pKšœ™KšœŸœ™ Kšœ$˜$Kšœ˜KšœŸœ˜ KšœŸœ ˜)K˜%Kšœ<™Ÿœ<Ÿœ˜ˆK˜—KšŸœ˜—Kšœ˜K˜—šŸœ™ K™—šž œŸœŸœ˜MKšœ»™»KšœŸœ ˜)K˜Kšœ Ÿœ$˜3Kšœ Ÿœ#˜/Kšœ Ÿœ# )˜ZKšœ Ÿœ˜(Kšœ$˜$K˜K˜KšœŸœ ˜KšœŸœ ˜KšŸœŸœ& ˜\JšΠbc-™-Kšœ Ÿœ:˜IKšœŸœ"˜9KšœŸœ ˜"KšœŸœ˜ K˜šŸœŸœŸœŸ˜K˜6K˜+KšŸœ˜—K˜8Kšœ.Ÿœ˜5Jš€-™-Kšœ Ÿœ:˜IKšœŸœ"˜9KšœŸœ˜KšœŸœ ˜#K˜šŸœŸœŸœŸ˜'K˜@K˜5KšŸœ˜—K˜8Kšœ.Ÿœ˜5Kšœ "˜:Kšœ "˜:K˜K˜—šžœŸœŸœŸœ˜OKšœ©™©KšœŸœ ˜)Kšœ Ÿœ$˜3Kšœ Ÿœ#˜/Kšœ Ÿœ# T˜†K˜#KšœŸœ˜2Kšœ$˜$JšœŸœ™#KšœŸœ2˜FKšœŸœ%˜AKšœŸœ ˜$KšœŸœ ˜%šŸœŸœŸœŸ˜K˜8K˜0KšŸœ˜—K˜+KšœŸœ  #˜QKšœŸœ˜+KšœŸœ ˜.KšœŸœ ˜-šŸœŸœŸœŸ˜(K˜:K˜2KšŸœ˜—K˜BKšœ3Ÿœ˜:Kšœ "˜KšœŸœ ˜)šŸœ Ÿ˜˜KšŸœŸœ˜)KšŸœ0˜4K˜—K˜I˜ KšŸœŸœ;˜EKšŸœl˜pK˜—KšŸœŸœ˜—K˜—K˜šœ™K™—šž œŸœ*ŸœŸœ˜NKšœ™Kšœ-Ÿœ™IK˜%K˜KšŸœŸœ Ÿœ  ˜SK˜Kšœ<˜KšŸœ˜—KšŸœ˜ K˜K˜—š žœŸœŸœŸœ Ÿœ˜NJš _™_KšœŸœ ˜)Kšœ Ÿœ$˜0šŸœ Ÿ˜K˜)K˜KšŸœŸœ˜—KšŸœ Ÿœ ˜KšŸœŸœŸœ˜1KšŸœ˜K˜K˜—šžœŸœŸœ˜@šŸœ ŸœŸ˜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šŸœ˜K˜—…—ΛΊ)e