DIRECTORY Feedback, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGTrajTypes, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2d, Lines2dTypes, RealFns, Rope, Rosary, SimpleFeedback, Vectors2d; GGTrajImpl: CEDAR PROGRAM IMPORTS Feedback, GGBoundBox, GGCoreOps, GGOutline, GGParent, GGSegment, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, Lines2d, RealFns, Rosary, SimpleFeedback, Vectors2d EXPORTS GGTraj = BEGIN BitVector: TYPE = GGBasicTypes.BitVector; BoundBox: TYPE = GGCoreTypes.BoundBox; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; Camera: TYPE = GGModelTypes.Camera; Edge: TYPE = Lines2dTypes.Edge; EditConstraints: TYPE = GGModelTypes.EditConstraints; FeatureData: TYPE = GGInterfaceTypes.FeatureData; FenceHoleOpen: TYPE = GGModelTypes.FenceHoleOpen; GGData: TYPE = GGInterfaceTypes.GGData; HitType: TYPE = GGModelTypes.TrajPartType; Joint: TYPE = GGSegmentTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; JointObj: TYPE = GGSegmentTypes.JointObj; MakeCurveProc: TYPE = GGTrajTypes.MakeCurveProc; OutlineData: TYPE = GGOutline.OutlineData; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; RunProc: TYPE = GGTrajTypes.RunProc; Scene: TYPE = GGModelTypes.Scene; SegAndIndex: TYPE = GGSequence.SegAndIndex; SegCPSequence: TYPE = GGTraj.SegCPSequence; SegCPSequenceObj: TYPE = GGTraj.SegCPSequenceObj; Segment: TYPE = GGSegmentTypes.Segment; SegmentClass: TYPE = GGSegmentTypes.SegmentClass; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; Sequence: TYPE = GGModelTypes.Sequence; SequenceGenerator: TYPE = GGSequence.SequenceGenerator; SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal; Slice: TYPE = GGModelTypes.Slice; SliceClass: TYPE = GGModelTypes.SliceClass; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceObj: TYPE = GGModelTypes.SliceObj; SliceParts: TYPE = GGModelTypes.SliceParts; Traj: TYPE = GGModelTypes.Traj; TrajData: TYPE = GGModelTypes.TrajData; TrajDataObj: TYPE = GGModelTypes.TrajDataObj; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajHitData: TYPE = GGTraj.TrajHitData; TrajParts: TYPE = GGModelTypes.TrajParts; TrajPartType: TYPE = GGModelTypes.TrajPartType; TriggerBag: TYPE = GGInterfaceTypes.TriggerBag; Vector: TYPE = GGBasicTypes.Vector; Problem: ERROR [msg: Rope.ROPE] = Feedback.Problem; CreateTrajFromData: PUBLIC PROC [role: FenceHoleOpen, segCount: NAT, segments: Rosary.ROSARY, joints: Rosary.ROSARY, extraPoints: LIST OF Joint, visibleJoints: BOOL ¬ TRUE, strokeJoint: Imager.StrokeJoint ¬ round, loArrow, hiArrow: BOOL ¬ FALSE, selectedInPart: SelectedObjectData ¬ [FALSE, FALSE, FALSE, FALSE]] RETURNS [slice: Slice] = { trajData: TrajData ¬ NEW[TrajDataObj ¬ [role, segCount, segments, joints, extraPoints, visibleJoints, strokeJoint, loArrow, hiArrow, TRUE, selectedInPart, NIL, 9999.0]]; slice ¬ NEW[SliceObj ¬ [class: GGSlice.FetchSliceClass[$Traj], data: trajData]]; slice.boundBox ¬ GGBoundBox.NullBoundBox[]; slice.tightBox ¬ GGBoundBox.NullBoundBox[]; slice.nullDescriptor ¬ GGSlice.DescriptorFromParts[slice, NIL]; }; CreateTraj: PUBLIC PROC [point: Point] RETURNS [slice: Slice] = { firstJoint: Joint ¬ NEW[JointObj ¬ [point: point]]; trajData: TrajData ¬ NEW[TrajDataObj ¬ [role: open, segCount: 0, segments: NIL, joints: Rosary.FromItem[firstJoint], extraPoints: NIL, visibleJoints: FALSE, polylineTolerance: 9999.0]]; slice ¬ NEW[SliceObj ¬ [class: GGSlice.FetchSliceClass[$Traj], data: trajData, parent: NIL, normalSelectedParts: NIL, hotSelectedParts: NIL, activeSelectedParts: NIL, matchSelectedParts: NIL, nullDescriptor: NIL, fullDescriptor: NIL, tightBox: GGBoundBox.NullBoundBox[], boundBox: GGBoundBox.NullBoundBox[], boxValid: FALSE]]; slice.nullDescriptor ¬ GGSlice.DescriptorFromParts[slice, NIL]; }; AddSegment: PUBLIC PROC [slice: Slice, trajEnd: TrajEnd, seg: Segment, segEnd: TrajEnd] RETURNS [success: BOOL ¬ TRUE] = { diff: Vector; loJoint, hiJoint: Joint; trajData: TrajData ¬ NARROW[slice.data]; IF trajData.role = fence OR trajData.role = hole THEN {success ¬ FALSE; RETURN}; IF trajEnd = segEnd THEN GGSegment.ReverseSegment[seg]; diff ¬ IF trajEnd = lo THEN Vectors2d.Sub[FetchJointPos[slice, 0], seg.hi] ELSE Vectors2d.Sub[FetchJointPos[slice, trajData.segCount], seg.lo]; GGSegment.TranslateSegment[seg, diff]; IF trajData.segCount = 0 THEN { trajData.segments ¬ Rosary.FromItem[seg]; loJoint ¬ NEW[JointObj ¬ [point: seg.lo]]; hiJoint ¬ NEW[JointObj ¬ [point: seg.hi]]; trajData.joints ¬ Rosary.FromList[LIST[loJoint, hiJoint]]; trajData.segCount ¬ 1; slice.boundBox­ ¬ seg.class.boundBox[seg]­; } ELSE { IF trajEnd = lo THEN { loJoint ¬ NEW[JointObj ¬ [point: seg.lo]]; trajData.joints ¬ Rosary.Concat[Rosary.FromItem[loJoint], trajData.joints]; trajData.segments ¬ Rosary.Concat[Rosary.FromItem[seg], trajData.segments]; } ELSE { hiJoint ¬ NEW[JointObj ¬ [point: seg.hi]]; trajData.joints ¬ Rosary.Concat[trajData.joints, Rosary.FromItem[hiJoint]]; trajData.segments ¬ Rosary.Concat[trajData.segments, Rosary.FromItem[seg]]; }; trajData.segCount ¬ trajData.segCount + 1; GGSlice.KillBoundBox[slice]; }; }; CloseWithSegment: PUBLIC PROC [slice: Slice, seg: Segment, segEnd: TrajEnd] = { diff: Vector; trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj IF trajData.segCount = 0 THEN ERROR Problem[msg: "single closed segment not implemented"]; IF segEnd = hi THEN GGSegment.ReverseSegment[seg]; diff ¬ Vectors2d.Sub[LastJointPos[slice], seg.lo]; GGSegment.TranslateSegment[seg, diff]; trajData.segments ¬ Rosary.Concat[trajData.segments, Rosary.FromItem[seg]]; trajData.segCount ¬ trajData.segCount + 1; trajData.role ¬ fence; GGSlice.KillBoundBox[slice]; }; CloseByDistorting: PUBLIC PROC [slice: Slice, distortEnd: TrajEnd] = { seg: Segment; loJoint, hiJoint: Joint; trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj loJoint ¬ FetchJoint[slice, 0]; hiJoint ¬ FetchJoint[slice, HiJoint[slice]]; SELECT distortEnd FROM lo => { seg ¬ FetchSegment[slice, 0]; loJoint.point ¬ hiJoint.point; seg.lo ¬ hiJoint.point; seg.class.endPointMoved[seg, TRUE, hiJoint.point]; }; hi => { seg ¬ FetchSegment[slice, HiSegment[slice]]; hiJoint.point ¬ loJoint.point; seg.hi ¬ loJoint.point; seg.class.endPointMoved[seg, FALSE, loJoint.point]; }; ENDCASE => ERROR; trajData.joints ¬ Rosary.Substr[trajData.joints, 0, HiJoint[slice]]; trajData.role ¬ fence; GGSlice.KillBoundBox[slice]; }; SetSelectionBits: PROC [traj: Slice, selected: BOOL, selectClass: SelectionClass] = { segGen: GGModelTypes.SegmentGenerator; jointGen: GGModelTypes.JointGenerator; joint: Joint; trajData: TrajData ¬ NARROW[traj.data]; segGen ¬ GGSequence.SegmentsInTraj[trajData]; FOR seg: Segment ¬ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO SetSegmentField[seg, selected, selectClass]; FOR i: NAT IN [0..seg.class.controlPointCount[seg]) DO seg.class.controlPointFieldSet[seg, i, selected, selectClass]; ENDLOOP; ENDLOOP; jointGen ¬ GGSequence.JointsInTraj[trajData]; FOR jointNum: INT ¬ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL jointNum = -1 DO joint ¬ FetchJoint[traj, jointNum]; SetJointField[joint, selected, selectClass]; ENDLOOP; }; ReplaceFirstRun: PUBLIC PROC [trajD: SliceDescriptor, runProc: RunProc, segmentsOnly: BOOL, selectNewRuns: BOOL] RETURNS [newOutline: Slice] = { trajParts: TrajParts ¬ NARROW[trajD.parts]; traj: Slice ¬ trajD.slice; run: SliceDescriptor; runParts: TrajParts; oldOutline: Slice ¬ GGParent.GetParent[traj]; IF GGSequence.IsComplete[trajParts] THEN { run ¬ trajD; runParts ¬ trajParts; } ELSE { runParts ¬ GGSequence.FirstRun[trajParts]; IF runParts = NIL THEN GOTO NothingToReplace; -- The sequence has only control points. run ¬ GGSlice.DescriptorFromParts[traj, runParts]; }; IF runParts.segCount>0 OR NOT segmentsOnly THEN { newRun: Slice ¬ runProc[run]; IF newRun#NIL THEN { SetSelectionBits[newRun, FALSE, active]; -- don't process run twice IF selectNewRuns THEN SetSelectionBits[newRun, TRUE, normal]; }; newOutline ¬ GGParent.GetParent[SpliceIn[run, newRun]]; } ELSE GOTO NothingToReplace; EXITS NothingToReplace => { SetSelectionBits[trajD.slice, FALSE, active]; -- nothing left to work on newOutline ¬ NIL; }; }; SegToSegMatchCp: PUBLIC PROC [seg: Segment, traj: Slice, makeCurve: MakeCurveProc, type: ATOM] = { tSeg: Segment; success: BOOL ¬ FALSE; IF seg.class.type = type THEN { -- no need to change type or control points IF type = $Conic THEN { tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props]; GGSegment.ConicSetControlPoint[tSeg, seg.class.controlPointGet[seg, 0]]; GGSegment.CopyLooks[seg, tSeg]; } ELSE { tSeg ¬ GGSegment.CopySegment[seg]; }; success ¬ AddSegment[traj, hi, tSeg, lo]; } ELSE IF type = $Arc AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types both have single control points, keep the control points the same tSeg ¬ GGSegment.MakeArc[seg.lo, seg.class.controlPointGet[seg, 0], seg.hi, seg.props]; GGSegment.CopyLooks[seg, tSeg]; success ¬ AddSegment[traj, hi, tSeg , lo] } ELSE IF type = $Conic AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types have single control points, keep the control points the same tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props]; GGSegment.ConicSetControlPoint[tSeg, seg.class.controlPointGet[seg, 0]]; GGSegment.CopyLooks[seg, tSeg]; success ¬ AddSegment[traj, hi, tSeg, lo] } ELSE { -- make segment of new type with old endpoint info tSeg ¬ makeCurve[seg.lo, seg.hi, seg.props]; GGSegment.CopyLooks[seg, tSeg]; success ¬ AddSegment[traj, hi, tSeg, lo]; }; IF NOT success THEN ERROR; }; SegAndCpsToSegs: PUBLIC PROC [seg: Segment, traj: Slice, makeCurve: MakeCurveProc, type: ATOM] = { success: BOOL ¬ FALSE; IF seg.class.type = type THEN -- if seg is already the right type, then we don't change it success ¬ AddSegment[traj, hi, GGSegment.CopySegment[seg], lo] ELSE { -- seg isn't the right type, so we convert all of its point/controlpoint spans tSeg: Segment; last, next: Point ¬ seg.lo; FOR i:INT IN [0..seg.class.controlPointCount[seg]) DO next ¬ seg.class.controlPointGet[seg, i]; tSeg ¬ makeCurve[last, next, seg.props]; GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST success ¬ AddSegment[traj, hi, tSeg, lo]; IF NOT success THEN ERROR; last ¬ next; ENDLOOP; tSeg ¬ makeCurve[last, seg.hi, seg.props]; GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST success ¬ AddSegment[traj, hi, tSeg, lo]; IF NOT success THEN ERROR; }; }; DeleteControlPoints: PUBLIC PROC [trajD: SliceDescriptor, scene: Scene] RETURNS [bBox: BoundBox] = { trajData: TrajData ¬ NARROW[trajD.slice.data]; trajParts: TrajParts ¬ NARROW[trajD.parts]; outlineOfTraj: Slice ¬ GGParent.GetParent[trajD.slice]; GGSlice.KillBoundBox[trajD.slice]; SaveSelection[trajD.slice, hot, scene]; -- to be restored by SubstituteForSegment SaveSelection[trajD.slice, normal, scene]; FOR i: INT IN [0..trajData.segCount) DO IF NOT GGUtility.AllFalse[trajParts.controlPoints[i]] AND NOT trajParts.segments[i] THEN { oldSeg: Segment ¬ FetchSegment[trajD.slice, i]; IF oldSeg.class.type = $CubicSpline THEN { newSeg: Segment ¬ GGSegment.CSControlPointDelete[oldSeg, trajParts.controlPoints[i]]; newRun: Slice ¬ CreateTraj[newSeg.lo]; GGSliceOps.SetStrokeJoint[newRun, NIL, trajData.strokeJoint, NIL]; IF NOT AddSegment[newRun, hi, newSeg, lo] THEN ERROR; [] ¬ GGSelect.SubstituteForSegment[trajD.slice, i, newRun, scene] } ELSE { SimpleFeedback.Append[$Gargoyle, oneLiner, $Apology, "Only delete control points from Cubic Splines"]; }; }; ENDLOOP; bBox ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[outlineOfTraj]]; }; DeleteSequence: PUBLIC PROC [seq: SliceDescriptor] RETURNS [smallerOutline: Slice, openTrajOutlines: LIST OF Slice] = { traj: Slice ¬ seq.slice; trajData: TrajData ¬ NARROW[traj.data]; trajParts: TrajParts ¬ NARROW[seq.parts]; oldOutline: Slice ¬ GGParent.GetParent[traj]; SELECT trajData.role FROM fence, hole => { openTrajOutlines ¬ OutlinesOfTrajMinusSequence[seq]; -- pieces of traj become outlines smallerOutline ¬ GGOutline.ReplaceChild[oldOutline, traj, NIL]; -- old outline gets smaller }; open => { openTrajOutlines ¬ OutlinesOfTrajMinusSequence[seq]; smallerOutline ¬ NIL; }; ENDCASE => ERROR; }; OutlinesOfTrajMinusSequence: PROC [seq: SliceDescriptor] RETURNS [newOutlines: LIST OF Slice] = { keepParts: SliceDescriptor; wholeTrajParts: TrajParts; traj: Slice ¬ seq.slice; trajData: TrajData ¬ NARROW[traj.data]; trajParts: TrajParts ¬ NARROW[seq.parts]; IF GGSequence.IsComplete[trajParts] THEN RETURN[NIL]; wholeTrajParts ¬ GGSequence.CreateComplete[trajData]; IF NOT GGSequence.IsEmpty[trajParts] AND trajData.segCount=1 THEN GGSegment.OpenUpSegment[FetchSegment[traj, 0]]; keepParts ¬ GGSequence.Difference[GGSlice.DescriptorFromParts[traj, wholeTrajParts], seq]; newOutlines ¬ GroupPieces[keepParts]; }; GroupPieces: PROC [seq: SliceDescriptor] RETURNS [newOutlines: LIST OF Slice] = { seqTraj: Slice ¬ seq.slice; seqData: TrajData ¬ NARROW[seqTraj.data]; seqParts: TrajParts ¬ NARROW[seq.parts]; seqGen: SequenceGenerator; newTraj: Slice; newTrajData: TrajData; newOutline, oldOutline: Slice; joint: Joint; oldOutline ¬ GGParent.GetParent[seqTraj]; [seqGen, ----] ¬ GGSequence.RunsInSequence[seqParts]; FOR run: TrajParts ¬ GGSequence.NextSequence[seqGen], GGSequence.NextSequence[seqGen] UNTIL run = NIL DO IF run.segCount=0 THEN LOOP; -- special case needed by BS command. newTraj ¬ CopyTrajFromRun[seqTraj, run]; joint ¬ FetchJoint[newTraj, 0]; joint.TselectedInFull.active ¬ FALSE; joint ¬ FetchJoint[newTraj, HiJoint[newTraj]]; joint.TselectedInFull.active ¬ FALSE; newTrajData ¬ NARROW[newTraj.data]; newTrajData.role ¬ open; newOutline ¬ GGOutline.CreateOutline[newTraj, GGSliceOps.GetFillColor[oldOutline, NIL].color]; newOutlines ¬ CONS[newOutline, newOutlines]; ENDLOOP; }; CopyTrajFromRun: PUBLIC PROC [slice: Slice, run: TrajParts] RETURNS [copy: Slice] = { trajData: TrajData ¬ NARROW[slice.data]; trajParts: TrajParts ¬ run; originalSegments: Rosary.ROSARY ¬ trajData.segments; desiredSegments: Rosary.Segment; loSegments, hiSegments, extractedSegments: Rosary.ROSARY; originalJoints: Rosary.ROSARY ¬ trajData.joints; desiredJoints: Rosary.Segment; loJoints, hiJoints, extractedJoints: Rosary.ROSARY; segGen: SegmentGenerator; next: GGSequence.SegAndIndex; s1, len1, s2, len2: INT; jointCount: NAT; newRole: GGModelTypes.FenceHoleOpen; CopyEachSegment: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = { CopySegmentAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ FALSE] = { copy, oldSeg: Segment; oldSeg ¬ NARROW[item]; copy ¬ GGSegment.CopySegment[oldSeg]; q[copy, 1]; }; [] ¬ Rosary.Map[desiredSegments, CopySegmentAndBuild]; }; CopyEachJoint: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = { CopyJointAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ FALSE] = { copy, oldJoint: Joint; oldJoint ¬ NARROW[item]; copy ¬ CopyJoint[oldJoint]; q[copy, 1]; }; [] ¬ Rosary.Map[desiredJoints, CopyJointAndBuild]; }; segGen ¬ GGSequence.OrderedSegmentsInSequence[trajData, trajParts]; next ¬ GGSequence.NextSegmentAndIndex[segGen]; [s1, len1, s2, len2] ¬ GGCoreOps.BreakIntervalMODLen[next.index, trajParts.segCount, trajData.segCount]; IF s2 # -1 THEN { desiredSegments ¬ [originalSegments, s1, len1]; loSegments ¬ Rosary.FromRuns[CopyEachSegment]; desiredSegments ¬ [originalSegments, s2, len2]; hiSegments ¬ Rosary.FromRuns[CopyEachSegment]; extractedSegments ¬ Rosary.Concat[hiSegments, loSegments]; } ELSE { desiredSegments ¬ [originalSegments, next.index, trajParts.segCount]; extractedSegments ¬ Rosary.FromRuns[CopyEachSegment]; }; jointCount ¬ trajParts.segCount + 1; -- ignore the joint information in the sequence. We just want all of the segments to have a joint on each side. This distinction is particularly important when a single joint of a closed trajectory is being deleted. [s1, len1, s2, len2] ¬ GGCoreOps.BreakIntervalMODLen[next.index, jointCount, HiJoint[slice]+1]; IF s2 # -1 THEN { desiredJoints ¬ [originalJoints, s1, len1]; loJoints ¬ Rosary.FromRuns[CopyEachJoint]; desiredJoints ¬ [originalJoints, s2, len2]; hiJoints ¬ Rosary.FromRuns[CopyEachJoint]; extractedJoints ¬ Rosary.Concat[hiJoints, loJoints]; } ELSE { desiredJoints ¬ [originalJoints, next.index, jointCount]; extractedJoints ¬ Rosary.FromRuns[CopyEachJoint]; }; newRole ¬ IF GGSequence.IsComplete[trajParts] AND trajData.role#open THEN fence ELSE open; copy ¬ CreateTrajFromData[newRole, trajParts.segCount, extractedSegments, extractedJoints, NIL, trajData.visibleJoints, trajData.strokeJoint]; copy.props ¬ slice.props; }; CopyTrajFromRange: PUBLIC PROC [slice: Slice, start: INT, len: INT] RETURNS [piece: Slice] = { original: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj originalSegments: Rosary.ROSARY ¬ original.segments; desiredSegments: Rosary.Segment ¬ [originalSegments, start, len]; extractedSegments: Rosary.ROSARY; originalJoints: Rosary.ROSARY ¬ original.joints; desiredJoints: Rosary.Segment ¬ [originalJoints, start, len]; extractedJoints: Rosary.ROSARY; CopyEachSegment: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = { CopySegmentAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ FALSE] = { copy, oldSeg: Segment; oldSeg ¬ NARROW[item]; copy ¬ GGSegment.CopySegment[oldSeg]; q[copy, 1]; }; [] ¬ Rosary.Map[desiredSegments, CopySegmentAndBuild]; }; CopyEachJoint: PROC[q: PROC[item: Rosary.Item, repeat: INT ¬ 1]] = { CopyJointAndBuild: PROC [item: Rosary.Item] RETURNS [quit: BOOLEAN ¬ FALSE] = { copy, oldJoint: Joint; oldJoint ¬ NARROW[item]; copy ¬ CopyJoint[oldJoint]; q[copy, 1]; }; [] ¬ Rosary.Map[desiredJoints, CopyJointAndBuild]; }; extractedSegments ¬ Rosary.FromRuns[CopyEachSegment]; extractedJoints ¬ Rosary.FromRuns[CopyEachJoint]; piece ¬ CreateTrajFromData[fence, len, extractedSegments, extractedJoints, NIL, original.visibleJoints, original.strokeJoint]; }; Concat: PUBLIC PROC [fixed: Slice, fixedEnd: TrajEnd, moving: Slice, movingEnd: TrajEnd] RETURNS [longer: Slice] = { diff: Vector; fixedTraj, movingTraj: TrajData; fixed ¬ GGSliceOps.Copy[fixed].first; moving ¬ GGSliceOps.Copy[moving].first; IF fixedEnd = movingEnd THEN ReverseTraj[moving]; IF fixedEnd = hi THEN { diff ¬ Vectors2d.Sub[FetchJointPos[fixed, HiJoint[fixed]], FetchJointPos[moving, 0]]; TranslateTraj[moving, diff]; fixedTraj ¬ NARROW[fixed.data]; movingTraj ¬ NARROW[moving.data]; longer ¬ CreateTrajFromData[ role: open, segCount: fixedTraj.segCount + movingTraj.segCount, segments: Rosary.Concat[fixedTraj.segments, movingTraj.segments], joints: Rosary.Concat[fixedTraj.joints, Rosary.Substr[movingTraj.joints, 1]], extraPoints: NIL, visibleJoints: fixedTraj.visibleJoints, strokeJoint: fixedTraj.strokeJoint, selectedInPart: [FALSE, FALSE, FALSE, FALSE] ]; } ELSE { -- fixedEnd = lo diff ¬ Vectors2d.Sub[FetchJointPos[fixed, 0], FetchJointPos[moving, HiJoint[moving]]]; TranslateTraj[moving, diff]; fixedTraj ¬ NARROW[fixed.data]; movingTraj ¬ NARROW[moving.data]; longer ¬ CreateTrajFromData[ role: open, segCount: fixedTraj.segCount + movingTraj.segCount, segments: Rosary.Concat[movingTraj.segments, fixedTraj.segments], joints: Rosary.Concat[movingTraj.joints, Rosary.Substr[fixedTraj.joints, 1]], extraPoints: NIL, visibleJoints: fixedTraj.visibleJoints, strokeJoint: fixedTraj.strokeJoint, selectedInPart: [FALSE, FALSE, FALSE, FALSE] ]; }; }; SpliceIn: PUBLIC PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = { oldTraj: TrajData ¬ NARROW[runDescriptor.slice.data]; -- traj to be replaced newOutline: Slice; oldOutline: Slice ¬ GGParent.GetParent[runDescriptor.slice]; SELECT oldTraj.role FROM hole => { newSlice ¬ SpliceInClosed[runDescriptor, slice]; NARROW[newSlice.data, TrajData].role ¬ hole; newOutline ¬ GGOutline.ReplaceChild[oldOutline, runDescriptor.slice, newSlice]; newSlice.parent ¬ newOutline; -- gotta patch this in for return value }; open => { newSlice ¬ SpliceInOpen[runDescriptor, slice]; NARROW[newSlice.data, TrajData].role ¬ open; newOutline ¬ GGOutline.CreateOutline[newSlice, GGSliceOps.GetFillColor[oldOutline, NIL].color]; newSlice.parent ¬ newOutline; -- gotta patch this in for return value }; fence => { newSlice ¬ SpliceInClosed[runDescriptor, slice]; NARROW[newSlice.data, TrajData].role ¬ fence; newOutline ¬ GGOutline.ReplaceFirstChild[oldOutline, newSlice]; newSlice.parent ¬ newOutline; -- gotta patch this in for return value }; ENDCASE => ERROR; NARROW[newSlice.data, TrajData].loArrow ¬ oldTraj.loArrow; NARROW[newSlice.data, TrajData].hiArrow ¬ oldTraj.hiArrow; GGSlice.KillBoundBox[oldOutline]; GGSlice.KillBoundBox[newSlice]; }; ResetEndSelectionBits: PROC [slice: Slice, lo, hi: BOOL ¬ TRUE] = { joint: Joint; IF lo THEN { joint ¬ FetchJoint[slice, 0]; joint.TselectedInFull.active ¬ FALSE; }; IF hi THEN { joint ¬ FetchJoint[slice, HiJoint[slice]]; joint.TselectedInFull.active ¬ FALSE; }; }; SpliceInOpen: PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = { runSlice: Slice ¬ runDescriptor.slice; -- contains traj to be replaced runData: TrajData ¬ NARROW[runSlice.data]; -- contains traj parts to be replaced runParts: TrajParts ¬ NARROW[runDescriptor.parts]; -- contains traj parts to be replaced runGen: GGSequence.SequenceGenerator; slice1, slice2: Slice; remainder: Sequence; remainderParts, run1, run2, run3: TrajParts; wholeParts: TrajParts ¬ GGSequence.CreateComplete[runData]; wholeDescriptor: SliceDescriptor ¬ GGSlice.DescriptorFromParts[runSlice, wholeParts]; remainder ¬ GGSequence.Difference[wholeDescriptor, runDescriptor]; remainderParts ¬ NARROW[remainder.parts]; GGSequence.FillInJoints[remainderParts]; [runGen,----] ¬ GGSequence.RunsInSequence[remainderParts]; run1 ¬ GGSequence.NextSequence[runGen]; run2 ¬ GGSequence.NextSequence[runGen]; run3 ¬ GGSequence.NextSequence[runGen]; IF run3 # NIL THEN ERROR; SELECT TRUE FROM run1 = NIL => { newSlice ¬ slice; }; run2 = NIL => { slice1 ¬ CopyTrajFromRun[runSlice, run1]; IF runParts.segments[0] THEN { -- includes lowest segment ResetEndSelectionBits[slice: slice1, lo: TRUE, hi: FALSE]; newSlice ¬ Concat[slice, hi, slice1, lo]; -- order is important. traj1's lo joint is kept } ELSE { ResetEndSelectionBits[slice: slice1, lo: FALSE, hi: TRUE]; newSlice ¬ Concat[slice, lo, slice1, hi]; -- order is important. traj1's hi joint is kept }; }; ENDCASE => { slice1 ¬ CopyTrajFromRun[runSlice, run1]; ResetEndSelectionBits[slice: slice1, lo: FALSE, hi: TRUE]; slice2 ¬ CopyTrajFromRun[runSlice, run2]; ResetEndSelectionBits[slice: slice2, lo: TRUE, hi: FALSE]; newSlice ¬ Concat[slice, lo, slice1, hi]; newSlice ¬ Concat[newSlice, hi, slice2, lo]; }; }; SpliceInClosed: PROC [runDescriptor: SliceDescriptor, slice: Slice] RETURNS [newSlice: Slice] = { runSlice: Slice ¬ runDescriptor.slice; -- contains traj to be replaced runData: TrajData ¬ NARROW[runSlice.data]; -- contains traj parts to be replaced runParts: TrajParts ¬ NARROW[runDescriptor.parts]; -- contains traj parts to be replaced runGen: GGSequence.SequenceGenerator; slice1: Slice; remainder: Sequence; wholeParts, remainderParts, run1, run2: TrajParts; wholeDescriptor: SliceDescriptor; IF GGSequence.IsComplete[runParts] THEN RETURN[slice]; -- no need to splice. wholeParts ¬ GGSequence.CreateComplete[runData]; wholeDescriptor ¬ GGSlice.DescriptorFromParts[runSlice, wholeParts]; remainder ¬ GGSequence.Difference[wholeDescriptor, runDescriptor]; remainderParts ¬ NARROW[remainder.parts]; GGSequence.FillInJoints[remainderParts]; [runGen, ----] ¬ GGSequence.RunsInSequence[remainderParts]; run1 ¬ GGSequence.NextSequence[runGen]; run2 ¬ GGSequence.NextSequence[runGen]; IF run1 = NIL OR run2 # NIL THEN ERROR; slice1 ¬ CopyTrajFromRun[runSlice, run1]; ResetEndSelectionBits[slice1]; newSlice ¬ Concat[slice, lo, slice1, hi]; -- order is important because traj1's joints are kept. CloseByDistorting[newSlice, lo]; }; ReverseTraj: PUBLIC PROC [slice: Slice] = { newSegments: Rosary.ROSARY; newJoints: Rosary.ROSARY; seg: Segment; joint: Joint; trajData: TrajData ¬ NARROW[slice.data]; FOR i: NAT IN [0..trajData.segCount) DO seg ¬ FetchSegment[slice, i]; GGSegment.ReverseSegment[seg]; newSegments ¬ Rosary.Concat[Rosary.FromItem[seg], newSegments]; ENDLOOP; FOR i: NAT IN [0..trajData.segCount] DO joint ¬ IF trajData.role=open THEN FetchJoint[slice, i] ELSE FetchJoint[slice, (i MOD trajData.segCount)]; newJoints ¬ Rosary.Concat[Rosary.FromItem[joint], newJoints]; ENDLOOP; trajData.segments ¬ newSegments; trajData.joints ¬ newJoints; }; IsClockwiseTraj: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = { RETURN [SignedArea[slice]>0]; }; IsClockwiseTrajTransformSeq: PUBLIC PROC [descriptor: SliceDescriptor, transform: ImagerTransformation.Transformation] RETURNS [BOOL] = { RETURN [SignedAreaTransformSeq[descriptor, transform]>0]; }; DrawPolyline: PUBLIC PROC [dc: Imager.Context, traj: Slice] = { trajData: TrajData ¬ NARROW[traj.data]; PolyPathProc: Imager.PathProc = { moveTo[wholePolyline[0]]; FOR i: NAT IN [1..wholePolyline.length) DO lineTo[wholePolyline[i]]; ENDLOOP; }; wholePolyline: GGSegmentTypes.PairSequence; success: BOOL ¬ FALSE; wholePolyline ¬ trajData.polyline; IF wholePolyline = NIL THEN RETURN; IF trajData.role = open THEN RETURN; Imager.SetColor[dc, Imager.black]; Imager.SetStrokeJoint[dc, bevel]; Imager.SetStrokeWidth[dc, 1.0]; Imager.MaskStroke[dc, PolyPathProc, TRUE]; }; <> TranslateTraj: PROC [slice: Slice, vector: Vector] = { transform: ImagerTransformation.Transformation ¬ ImagerTransformation.Translate[[vector.x, vector.y]]; seg: Segment; joint: Joint; trajData: TrajData ¬ NARROW[slice.data]; hiSegment: NAT ¬ HiSegment[slice]; hiJoint: NAT ¬ HiJoint[slice]; FOR i: NAT IN [0..hiSegment] DO seg ¬ FetchSegment[slice, i]; GGSegment.TransformSegment[seg, transform]; ENDLOOP; FOR i: NAT IN [0..hiJoint] DO joint ¬ NARROW[Rosary.Fetch[trajData.joints, i]]; joint.point ¬ GGTransform.Transform[transform, joint.point]; ENDLOOP; GGSlice.KillBoundBox[slice]; -- force bound box to update for now }; ConstrainJoint: PUBLIC PROC [slice: Slice, editConstraints: EditConstraints, jointNum: NAT] RETURNS [constrained: BOOL ¬ FALSE] ~ { newPos: Point; loSeg, hiSeg: Segment; cPointLo, cPointHi: Point; joint: Joint; trajData: TrajData ¬ NARROW[slice.data]; IF NOT IsEndJoint[slice, jointNum] THEN { joint ¬ FetchJoint[slice, jointNum]; hiSeg ¬ FetchSegment[slice, jointNum]; loSeg ¬ PreviousSegment[slice, jointNum]; IF hiSeg.class.type = $Bezier AND loSeg.class.type = $Bezier THEN { constrained ¬ TRUE; cPointLo ¬ loSeg.class.controlPointGet[loSeg, 1]; cPointHi ¬ hiSeg.class.controlPointGet[hiSeg, 0]; IF editConstraints = length THEN { newPos ¬ Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorFromPoints[cPointLo, cPointHi], 0.5], cPointLo]; } ELSE { -- tangent cpLine: Edge ¬ Lines2d.CreateEdge[cPointLo, cPointHi]; newPos ¬ Lines2d.NearestPointOnEdge[joint.point, cpLine]; }; joint.point ¬ newPos; GGSegment.MoveEndPointSegment[hiSeg, TRUE, newPos]; GGSegment.MoveEndPointSegment[loSeg, FALSE, newPos]; GGSlice.KillBoundBox[slice]; }; }; }; ConstrainCP: PUBLIC PROC [slice: Slice, editConstraints: EditConstraints, segNum: NAT, cpNum: NAT] RETURNS [constrained: BOOL ¬ FALSE] ~ { newPos: Point; thisSeg, otherSeg: Segment; thisCP, otherCP: Point; joint: Joint; otherSegNum, otherCPNum: INT; trajData: TrajData ¬ NARROW[slice.data]; IF cpNum = 0 THEN { otherSegNum ¬ PreviousSegmentNum[slice, segNum]; joint ¬ FetchJoint[slice, segNum]; otherCPNum ¬ 1; } ELSE { otherSegNum ¬ FollowingSegmentNum[slice, segNum]; joint ¬ FetchJoint[slice, FollowingJoint[slice, segNum]]; otherCPNum ¬ 0; }; IF otherSegNum # -1 THEN { thisSeg ¬ FetchSegment[slice, segNum]; otherSeg ¬ FetchSegment[slice, otherSegNum]; IF thisSeg.class.type = $Bezier AND otherSeg.class.type = $Bezier THEN { constrained ¬ TRUE; thisCP ¬ thisSeg.class.controlPointGet[thisSeg, cpNum]; otherCP ¬ otherSeg.class.controlPointGet[otherSeg, otherCPNum]; IF editConstraints = length THEN { newPos ¬ Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorFromPoints[otherCP, joint.point], 2.0], otherCP]; } ELSE { -- tangent, can do 1 of 2 ways [drop perpendicular or same length]. dirVec: Vector ¬ Vectors2d.VectorFromPoints[otherCP, joint.point]; distance: REAL ¬ Vectors2d.Distance[joint.point, thisCP]; IF dirVec # [0,0] THEN dirVec ¬ Vectors2d.Normalize[dirVec]; -- use exception dirVec ¬ Vectors2d.Scale[dirVec, distance]; newPos ¬ Vectors2d.Add[joint.point, dirVec]; }; GGSegment.BZControlPointMovedTo[thisSeg, newPos, cpNum]; GGSlice.KillBoundBox[slice]; }; }; }; closeness: INT ¬ -18; -- 18 bits of precision, determined by trial and error for GetIPEditable. KAP. July 6, 1992 MatchShape: PUBLIC PROC [traj1, traj2: Slice] RETURNS [BOOL] = { segCount1: NAT ¬ HiSegment[traj1]; segCount2: NAT ¬ HiSegment[traj2]; trajData1: TrajData ¬ NARROW[traj1.data]; trajData2: TrajData ¬ NARROW[traj1.data]; seg1, seg2: Segment; cpCount1: NAT; closed1: BOOL ¬ trajData1.role = hole OR trajData1.role = fence; closed2: BOOL ¬ trajData2.role = hole OR trajData2.role = fence; IF segCount1 # segCount2 THEN RETURN[FALSE]; IF closed1 # closed2 THEN RETURN[FALSE]; FOR i: NAT IN [0..segCount1] DO seg1 ¬ FetchSegment[traj1, i]; seg2 ¬ FetchSegment[traj2, i]; IF seg1.class # seg2.class THEN RETURN[FALSE]; IF NOT GGSegment.SameShape[seg1, seg2] THEN RETURN[FALSE]; IF seg1.hi # seg2.hi THEN RETURN[FALSE]; cpCount1 ¬ seg1.class.controlPointCount[seg1]; IF cpCount1 # seg2.class.controlPointCount[seg2] THEN RETURN[FALSE]; FOR j: NAT IN [0..cpCount1) DO point1, point2: Point; IF (point1 ¬ seg1.class.controlPointGet[seg1, j]) = (point2 ¬ seg2.class.controlPointGet[seg2, j]) THEN LOOP; IF RealFns.AlmostEqual[point1.x, point2.x, closeness] AND RealFns.AlmostEqual[point1.y, point2.y, closeness] THEN LOOP ELSE RETURN[FALSE]; ENDLOOP; ENDLOOP; RETURN[TRUE]; }; FetchSegment: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [seg: Segment] = { trajData: TrajData ¬ NARROW[slice.data]; seg ¬ NARROW[Rosary.Fetch[trajData.segments, index]]; }; FetchSegmentTraj: PUBLIC PROC [trajData: TrajData, index: NAT] RETURNS [seg: Segment] = { seg ¬ NARROW[Rosary.Fetch[trajData.segments, index]]; }; FetchJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [joint: Joint] = { trajData: TrajData ¬ NARROW[slice.data]; joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]]; }; FetchJointPos: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [point: Point] = { trajData: TrajData ¬ NARROW[slice.data]; joint: Joint; hiJoint: NAT ¬ SELECT trajData.role FROM open => trajData.segCount, fence, hole => trajData.segCount -1, ENDCASE => ERROR; IF index < 0 OR index > hiJoint THEN ERROR; joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]]; point ¬ joint.point; }; FetchJointPosTraj: PUBLIC PROC [trajData: TrajData, index: NAT] RETURNS [point: Point] = { joint: Joint; hiJoint: NAT ¬ SELECT trajData.role FROM open => trajData.segCount, fence, hole => trajData.segCount -1, ENDCASE => ERROR; IF index < 0 OR index > hiJoint THEN ERROR; joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]]; point ¬ joint.point; }; FetchJointNormal: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [normal: Vector] = { normal ¬ [0,-1]; -- for now }; LastJointPos: PUBLIC PROC [slice: Slice] RETURNS [point: Point] = { trajData: TrajData ¬ NARROW[slice.data]; joint: Joint ¬ NARROW[Rosary.Fetch[trajData.joints, trajData.segCount]]; point ¬ joint.point }; SetJointPos: PUBLIC PROC [slice: Slice, index: NAT, newPos: Point] = { trajData: TrajData ¬ NARROW[slice.data]; joint: Joint ¬ NARROW[Rosary.Fetch[trajData.joints, index]]; segLeft, segRight: Segment; joint.point ¬ newPos; IF index > 0 THEN { segLeft ¬ FetchSegment[slice, index-1]; segLeft.hi ¬ newPos; }; IF index < trajData.segCount THEN { segRight ¬ FetchSegment[slice, index]; segRight.lo ¬ newPos; }; }; HiSegment: PUBLIC PROC [slice: Slice] RETURNS [highestIndex: NAT] = { trajData: TrajData ¬ NARROW[slice.data]; highestIndex ¬ trajData.segCount - 1; }; HiSegmentTraj: PUBLIC PROC [trajData: TrajData] RETURNS [highestIndex: NAT] = { highestIndex ¬ trajData.segCount - 1; }; HiJoint: PUBLIC PROC [slice: Slice] RETURNS [highestIndex: NAT] = { trajData: TrajData ¬ NARROW[slice.data]; SELECT trajData.role FROM open => highestIndex ¬ trajData.segCount; fence, hole => highestIndex ¬ trajData.segCount -1; ENDCASE => ERROR; }; HiJointTraj: PUBLIC PROC [trajData: TrajData] RETURNS [highestIndex: NAT] = { SELECT trajData.role FROM open => highestIndex ¬ trajData.segCount; fence, hole => highestIndex ¬ trajData.segCount -1; ENDCASE => ERROR; }; PreviousSegment: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [prev: Segment] = { trajData: TrajData ¬ NARROW[slice.data]; IF trajData.role = open THEN { IF segNum = 0 THEN RETURN[NIL] ELSE prev ¬ FetchSegment[slice, segNum - 1]; } ELSE { prev ¬ FetchSegment[slice, (segNum-1+trajData.segCount) MOD trajData.segCount]; }; }; PreviousSegmentNum: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [prevNum: INT] = { trajData: TrajData ¬ NARROW[slice.data]; prevNum ¬ IF trajData.role = open THEN segNum - 1 ELSE (segNum-1+trajData.segCount) MOD trajData.segCount; }; FollowingSegmentNum: PUBLIC PROC [slice: Slice, segNum: NAT] RETURNS [followNum: INT] = { trajData: TrajData ¬ NARROW[slice.data]; IF trajData.role = open THEN followNum ¬ IF segNum = HiSegment[slice] THEN -1 ELSE segNum + 1 ELSE followNum ¬ (segNum+1) MOD trajData.segCount; }; FollowingJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [nextIndex: INT] = { trajData: TrajData ¬ NARROW[slice.data]; SELECT trajData.role FROM open => nextIndex ¬ IF index = trajData.segCount THEN -1 ELSE index + 1; fence, hole => nextIndex ¬ (index + 1) MOD trajData.segCount; ENDCASE => ERROR; }; IsEndJoint: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [BOOL] = { trajData: TrajData ¬ NARROW[slice.data]; SELECT trajData.role FROM open => RETURN[index = 0 OR index = trajData.segCount]; fence, hole => RETURN[FALSE]; ENDCASE => ERROR; }; SaveSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass, scene: Scene] = { seq: SliceDescriptor ¬ GGSelect.FindSelectedSlice[slice, selectClass]; IF seq=NIL THEN ClearSelection[slice, selectClass] ELSE SaveSelectionInSequence[seq, selectClass]; }; SaveSelectionInSequence: PUBLIC PROC [descriptor: SliceDescriptor, selectClass: SelectionClass] = { SaveSelectionInParts[descriptor.slice, descriptor.parts, selectClass]; }; SaveSelectionInParts: PUBLIC PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = { seg: Segment; joint: Joint; trajParts: TrajParts ¬ NARROW[parts]; hiSegment: NAT ¬ HiSegment[slice]; hiJoint: NAT ¬ HiJoint[slice]; FOR i: NAT IN [0..hiSegment] DO seg ¬ FetchSegment[slice, i]; SetSegmentField[seg, trajParts.segments[i], selectClass]; FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO SetControlPointField[seg, j, trajParts.controlPoints[i][j], selectClass]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..hiJoint] DO joint ¬ FetchJoint[slice, i]; SetJointField[joint, trajParts.joints[i], selectClass]; ENDLOOP; }; ClearSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass] = { seg: Segment; joint: Joint; hiSegment: NAT ¬ HiSegment[slice]; hiJoint: NAT ¬ HiJoint[slice]; FOR i: NAT IN [0..hiSegment] DO seg ¬ FetchSegment[slice, i]; SetSegmentField[seg, FALSE, selectClass]; FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO SetControlPointField[seg, j, FALSE, selectClass]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..hiJoint] DO joint ¬ FetchJoint[slice, i]; SetJointField[joint, FALSE, selectClass]; ENDLOOP; }; ClearSelections: PUBLIC PROC [slice: Slice] = { seg: Segment; joint: Joint; hiSegment: NAT ¬ HiSegment[slice]; hiJoint: NAT ¬ HiJoint[slice]; FOR i: NAT IN [0..hiSegment] DO seg ¬ FetchSegment[slice, i]; SetSegmentField[seg, FALSE, normal]; SetSegmentField[seg, FALSE, hot]; SetSegmentField[seg, FALSE, active]; SetSegmentField[seg, FALSE, match]; FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO SetControlPointField[seg, j, FALSE, normal]; SetControlPointField[seg, j, FALSE, hot]; SetControlPointField[seg, j, FALSE, active]; SetControlPointField[seg, j, FALSE, match]; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..hiJoint] DO joint ¬ FetchJoint[slice, i]; SetJointField[joint, FALSE, normal]; SetJointField[joint, FALSE, hot]; SetJointField[joint, FALSE, active]; SetJointField[joint, FALSE, match]; ENDLOOP; }; RemakeSelection: PUBLIC PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = { trajData: TrajData ¬ NARROW[slice.data]; -- SHOULD THIS BE A COPY ?? trajParts: TrajParts ¬ NARROW[GGSequence.CreateEmpty[trajData]]; seg: Segment; joint: Joint; hiSegment: NAT ¬ HiSegment[slice]; hiJoint: NAT ¬ HiJoint[slice]; FOR i: NAT IN [0..hiSegment] DO seg ¬ FetchSegment[slice, i]; trajParts.segments[i] ¬ GetSegmentField[seg, selectClass]; IF trajParts.segments[i] THEN trajParts.segCount ¬ trajParts.segCount + 1; FOR j: NAT IN [0..seg.class.controlPointCount[seg]) DO trajParts.controlPoints[i][j] ¬ GetControlPointField[seg, j, selectClass]; IF trajParts.controlPoints[i][j] THEN trajParts.controlPointCount ¬ trajParts.controlPointCount + 1; ENDLOOP; ENDLOOP; FOR i: NAT IN [0..hiJoint] DO joint ¬ FetchJoint[slice, i]; trajParts.joints[i] ¬ GetJointField[joint, selectClass]; IF trajParts.joints[i] THEN trajParts.jointCount ¬ trajParts.jointCount + 1; ENDLOOP; parts ¬ IF GGSequence.IsEmpty[trajParts] THEN NIL ELSE trajParts; }; SetControlPointField: PROC [seg: Segment, cpNum: NAT, selected: BOOL, selectClass: SelectionClass] = { seg.class.controlPointFieldSet[seg, cpNum, selected, selectClass]; }; GetControlPointField: PROC [seg: Segment, cpNum: NAT, selectClass: SelectionClass] RETURNS [selected: BOOL] = { selected ¬ seg.class.controlPointFieldGet[seg, cpNum, selectClass]; }; GetJointField: PROC [joint: Joint, selectClass: SelectionClass] RETURNS [selected: BOOL] = { SELECT selectClass FROM normal => selected ¬ joint.TselectedInFull.normal; hot => selected ¬ joint.TselectedInFull.hot; active => selected ¬ joint.TselectedInFull.active; match => selected ¬ joint.TselectedInFull.match; ENDCASE; }; GetSegmentField: PROC [seg: Segment, selectClass: SelectionClass] RETURNS [selected: BOOL] = { SELECT selectClass FROM normal => selected ¬ seg.TselectedInFull.normal; hot => selected ¬ seg.TselectedInFull.hot; active => selected ¬ seg.TselectedInFull.active; match => selected ¬ seg.TselectedInFull.match; ENDCASE; }; SetJointField: PROC [joint: Joint, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => joint.TselectedInFull.normal ¬ selected; hot => joint.TselectedInFull.hot ¬ selected; active => joint.TselectedInFull.active ¬ selected; match => joint.TselectedInFull.match ¬ selected; ENDCASE; }; SetSegmentField: PROC [seg: Segment, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => seg.TselectedInFull.normal ¬ selected; hot => seg.TselectedInFull.hot ¬ selected; active => seg.TselectedInFull.active ¬ selected; match => seg.TselectedInFull.match ¬ selected; ENDCASE; }; CopyJoint: PROC [joint: Joint] RETURNS [copy: Joint] = { copy ¬ NEW[JointObj ¬ [point: joint.point, TselectedInFull: joint.TselectedInFull] ]; }; SetTrajRole: PUBLIC PROC [traj: Slice, role: FenceHoleOpen] = { IF GGSliceOps.GetType[traj]=$Traj THEN NARROW[traj.data, TrajData].role ¬ role; }; GetTrajRole: PUBLIC PROC [traj: Slice] RETURNS [role: FenceHoleOpen] = { role ¬ NARROW[traj.data, TrajData].role; }; UnpackSimpleDescriptor: PUBLIC PROC [traj: Slice, parts: SliceParts] RETURNS [success: BOOL ¬ FALSE, partType: TrajPartType ¬ none, trajData: TrajData, joint: Joint ¬ NIL, jointNum: NAT ¬ 999, cp: Point ¬ [0,0], cpNum: NAT ¬ 999, seg: Segment ¬ NIL, segNum: NAT ¬ 999] = { IF traj = NIL OR GGSliceOps.GetType[traj]#$Traj THEN RETURN; [success, partType, trajData, joint, jointNum, cp, cpNum, seg, segNum] ¬ GGSequence.UnpackSimpleSequence[traj, parts]; }; UnpackHitData: PUBLIC PROC [hitData: REF ANY] RETURNS [hitType: HitType, segNum, cpNum, jointNum: INT, hitPoint: Point] = { trajHitData: TrajHitData ¬ NARROW[hitData]; hitType ¬ trajHitData.hitType; segNum ¬ trajHitData.segNum; cpNum ¬ trajHitData.cpNum; jointNum ¬ trajHitData.jointNum; hitPoint ¬ trajHitData.hitPoint; }; Vector3D: TYPE = ARRAY [1..3] OF REAL; PointToVector: PROC [point: Point] RETURNS [vector: Vector3D] = { vector[1] ¬ point.x; vector[2] ¬ point.y; vector[3] ¬ 0; }; CrossProduct: PROC [v1: Vector3D, v2: Vector3D] RETURNS [prodV: Vector3D] = { prodV[1] ¬ v1[2]*v2[3] - v1[3]*v2[2]; prodV[2] ¬ v1[3]*v2[1] - v1[1]*v2[3]; prodV[3] ¬ v1[1]*v2[2] - v1[2]*v2[1]; }; GetPartialArea: PROC [pt1: Point, pt2: Point] RETURNS [partial: REAL] = { D1: Vector3D ¬ PointToVector[pt1]; D2: Vector3D ¬ PointToVector[pt2]; areaVector: Vector3D ¬ CrossProduct[D2, D1];-- will only have a z component RETURN [areaVector[3]]; }; SignedArea: PROC [slice: Slice] RETURNS [area: REAL ¬ 0.0] = { lastPoint, thisPoint: Point ¬ FetchJointPos[slice, 0]; hiSeg: NAT ¬ HiSegment[slice]; thisSeg: Segment; FOR index: NAT IN [0..hiSeg] DO thisSeg ¬ FetchSegment[slice, index]; FOR cpIndex: NAT IN [0..thisSeg.class.controlPointCount[thisSeg]) DO area ¬ area + GetPartialArea[lastPoint, (thisPoint ¬ thisSeg.class.controlPointGet[thisSeg, cpIndex])]; lastPoint ¬ thisPoint; ENDLOOP; area ¬ area + GetPartialArea[lastPoint, (thisPoint ¬ FetchJointPos[slice, IF index=hiSeg THEN 0 ELSE index+1])]; lastPoint ¬ thisPoint; ENDLOOP; }; -- end of SignedArea SignedAreaTransformSeq: PROC [descriptor: SliceDescriptor, transform: ImagerTransformation.Transformation] RETURNS [area: REAL ¬ 0.0] = { lastPoint, thisPoint: Point; trajParts: TrajParts ¬ NARROW[descriptor.parts]; hiSeg: NAT ¬ HiSegment[descriptor.slice]; thisSeg: Segment; lastPoint ¬ FetchJointPos[descriptor.slice, 0]; IF trajParts.joints[0] THEN lastPoint ¬ ImagerTransformation.Transform[transform, lastPoint]; FOR index: NAT IN [0..hiSeg] DO thisSeg ¬ FetchSegment[descriptor.slice, index]; FOR cpIndex: NAT IN [0..thisSeg.class.controlPointCount[thisSeg]) DO thisPoint ¬ thisSeg.class.controlPointGet[thisSeg, cpIndex]; IF trajParts.controlPoints[index][cpIndex] THEN thisPoint ¬ ImagerTransformation.Transform[transform, thisPoint]; area ¬ area + GetPartialArea[lastPoint, thisPoint]; lastPoint ¬ thisPoint; ENDLOOP; thisPoint ¬ FetchJointPos[descriptor.slice, IF index=hiSeg THEN 0 ELSE index+1]; IF trajParts.joints[IF index=hiSeg THEN 0 ELSE index+1] THEN thisPoint ¬ ImagerTransformation.Transform[transform, thisPoint]; area ¬ area + GetPartialArea[lastPoint, thisPoint]; lastPoint ¬ thisPoint; ENDLOOP; }; -- end of SignedAreaTransformSeq useBBox: BOOL = TRUE; maxDistance: REAL = 99999.0; noJoint: NAT = 9999; noCP: NAT = 8888; noSeg: NAT = 7777; OnlyChild: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = { trajData: TrajData ¬ NARROW[slice.data]; -- NarrowRefFault if SliceType#$Traj outline: Slice ¬ slice.parent; outlineData: OutlineData ¬ NARROW[outline.data]; RETURN[outlineData.children.rest = NIL]; }; <> NearestSegment: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestSeg: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = { traj: Slice ¬ descriptor.slice; trajData: TrajData ¬ NARROW[traj.data]; trajParts: TrajParts ¬ NARROW[descriptor.parts]; thisDist2, bestDist2: REAL; thisPoint: Point; thisSuccess: BOOL ¬ FALSE; tolerance2: REAL ¬ tolerance*tolerance; ProcessSegment: GGSequence.SegmentWalkProc = { [thisPoint, thisSuccess] ¬ seg.class.closestPoint[seg, testPoint, tolerance]; IF thisSuccess THEN { thisDist2 ¬ Vectors2d.DistanceSquared[thisPoint, testPoint]; IF thisDist2 < bestDist2 THEN { bestDist2 ¬ thisDist2; bestSeg ¬ index; bestPoint ¬ thisPoint; success ¬ TRUE; }; }; }; IF useBBox THEN { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance] THEN RETURN; -- success=FALSE }; bestDist ¬ tolerance; bestDist2 ¬ tolerance2; bestSeg ¬ noSeg; -- magic number meaning no segment bestPoint ¬ [-1.0, -1.0]; [] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, ProcessSegment]; IF success THEN { -- compute the surface normal jointPoint: Point; diffX, diffY: REAL; bestDist ¬ RealFns.SqRt[bestDist2]; bestNormal ¬ Vectors2d.Sub[testPoint, bestPoint]; jointPoint ¬ FetchJointPos[traj, bestSeg]; diffX ¬ ABS[jointPoint.x - bestPoint.x]; diffY ¬ ABS[jointPoint.y - bestPoint.y]; IF trajData.role = open AND diffX < 0.01 AND diffY < 0.01 THEN { bestSegment: Segment ¬ FetchSegment[traj, bestSeg]; bestNormal ¬ bestSegment.class.jointNormal[bestSegment, jointPoint, testPoint, FALSE].normal; }; }; }; NearestJoint: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestJoint: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = { traj: Slice ¬ descriptor.slice; trajData: TrajData ¬ NARROW[traj.data]; trajParts: TrajParts ¬ NARROW[descriptor.parts]; tolerance2: REAL ¬ tolerance*tolerance; thisDist2, bestDist2: REAL; seg1, seg2: Segment; normal1, normal2, tangent1, tangent2, direction: Vector; ProcessJoint: PROC [traj: TrajData, jointPos: Point, index: NAT] RETURNS [done: BOOL ¬ FALSE] = { thisDist2 ¬ Vectors2d.DistanceSquared[jointPos, testPoint]; IF thisDist2 < bestDist2 THEN { bestDist2 ¬ thisDist2; bestJoint ¬ index; bestPoint ¬ jointPos; success ¬ TRUE; }; }; IF useBBox THEN { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance] THEN RETURN; -- success=FALSE }; bestDist ¬ tolerance; bestDist2 ¬ tolerance2; bestJoint ¬ noJoint; bestPoint ¬ [-1.0, -1.0]; GGSequence.WalkJointPositionsInSequence[trajData, trajParts, ProcessJoint]; IF success THEN { -- compute the normal bestDist ¬ RealFns.SqRt[bestDist2]; IF HiSegment[traj] < bestJoint THEN seg2 ¬ IF trajData.role = open THEN NIL ELSE FetchSegment[traj, 0] ELSE seg2 ¬ FetchSegment[traj, bestJoint]; seg1 ¬ PreviousSegment[traj, bestJoint]; IF seg1 # NIL AND seg2 # NIL THEN { [normal2, tangent2] ¬ seg2.class.jointNormal[seg2, bestPoint, testPoint , FALSE]; [normal1, tangent1] ¬ seg1.class.jointNormal[seg1, bestPoint, testPoint, TRUE]; direction ¬ Vectors2d.VectorFromPoints[bestPoint, testPoint]; bestNormal ¬ IF ABS[Vectors2d.SmallestAngleBetweenVectors[tangent1, direction]] < ABS[Vectors2d.SmallestAngleBetweenVectors[tangent2, direction]] THEN normal1 ELSE normal2; } ELSE { IF seg1 # NIL THEN [bestNormal, ----] ¬ seg1.class.jointNormal[seg1, bestPoint, testPoint, TRUE]; IF seg2 # NIL THEN [bestNormal, ----] ¬ seg2.class.jointNormal[seg2, bestPoint, testPoint, FALSE]; }; }; }; NearestControlPoint: PUBLIC PROC [testPoint: Point, descriptor: SliceDescriptor, tolerance: REAL] RETURNS [bestDist: REAL ¬ 0.0, bestSeg: NAT ¬ 0, bestControlPoint: NAT ¬ 0, bestPoint: Point ¬ [0.0, 0.0], bestNormal: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = { SomeCP: PROC [i: NAT] RETURNS [BOOL] = { cpCount: NAT ¬ trajParts.controlPoints[i].len; FOR j: NAT IN [0..cpCount) DO IF trajParts.controlPoints[i][j] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; traj: Slice ¬ descriptor.slice; trajData: TrajData ¬ NARROW[traj.data]; trajParts: TrajParts ¬ NARROW[descriptor.parts]; tolerance2: REAL ¬ tolerance*tolerance; thisDist2, bestDist2: REAL; thisControlPoint: NAT; thisPoint: Point; thisNormal: Vector; thisSuccess: BOOL ¬ FALSE; ProcessSegment: PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL ¬ FALSE] = { IF NOT SomeCP[index] THEN RETURN; [thisPoint, thisNormal, thisControlPoint, thisSuccess] ¬ seg.class.closestControlPoint[seg, testPoint, tolerance]; IF thisSuccess THEN { thisDist2 ¬ Vectors2d.DistanceSquared[thisPoint, testPoint]; IF thisDist2 < bestDist2 THEN { bestDist2 ¬ thisDist2; bestSeg ¬ index; bestControlPoint ¬ thisControlPoint; bestPoint ¬ thisPoint; bestNormal ¬ thisNormal; success ¬ TRUE; }; }; }; IF useBBox THEN { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[traj], tolerance] THEN RETURN; -- success=FALSE }; bestDist ¬ tolerance; bestDist2 ¬ tolerance2; bestSeg ¬ noSeg; bestControlPoint ¬ noCP; -- magic number meaning no CP bestPoint ¬ [-1.0, -1.0]; [] ¬ GGSequence.WalkSegmentsInSequence[trajData, trajParts, ProcessSegment]; IF success THEN bestDist ¬ RealFns.SqRt[bestDist2]; }; IndexOfJoint: PUBLIC PROC [joint: Joint, slice: Slice] RETURNS [index: INT] = { thisJoint: Joint; FOR i: NAT IN [0..HiJoint[slice]] DO thisJoint ¬ FetchJoint[slice, i]; IF thisJoint = joint THEN RETURN[i]; ENDLOOP; RETURN[-1]; }; IndexOfSegment: PUBLIC PROC [segment: Segment, slice: Slice] RETURNS [index: INT] = { thisSeg: Segment; FOR i: NAT IN [0..HiSegment[slice]] DO thisSeg ¬ FetchSegment[slice, i]; IF thisSeg = segment THEN RETURN[i]; ENDLOOP; RETURN[-1]; }; END. hGGTrajImpl.mesa Contents: Procedures to implement the Traj Slice Class. Trajectories consist of Segments. Copyright Ó 1986, 1987, 1989, 1991, 1992 by Xerox Corporation. All rights reserved. Bier, January 28, 1993 3:41 pm PST Pier, July 6, 1992 1:36 pm PDT Doug Wyatt, April 16, 1992 4:06 pm PDT Trajectory-Only Routines Creating Create a new trajectory from the given data. Used by various copy and concat routines. Create a new trajectory containing a single point. Slice must be of class $Traj. Translates seg so that the specified end of seg coincides with the specified end of traj. Mutates slice to add the new segment. Slice Descriptors must be updated by the caller. Orient and translate the segment Add segment to an empty traj Add segment to an existing traj Like AddSegment, except that here we rotate, scale, and translate seg as needed so that both of its endpoints coincide with the endpoints of traj (which must not already be closed). The endpoint correspondence is made so that the hi end of traj touches the named (segEnd) end of seg. For now, just translate into place and assume size is ok. Move either the first joint or the last joint (depending on the distortEnd argument) to coincide with the other end. This will reduce the number of joints in the trajectory by one (making all sequences which refer to this trajectory obsolete. Usually, no distortion will actually occur. The client is expected to call this routine when the joints already coincide. Get the first run of trajD and replace it with the results of runProc. IMPORTANT INVARIANT: When exiting from this loop, make sure that some part of trajD has been deselected from the active list. Otherwise, infinite loops are possible. Find the first run Replace the first run, as specified by the runProc. Replace segs of the old type with segs of the new type, maintaining control point data if possible Replace old segments with segments of the new type, but if the old segment had control points, use these also as joints of new segments. Deletes selected control points and returns bounding box of area requiring refresh. must make a copy because it may be mutated later Cyclic segments must become acyclic Compute a descriptor for the parts that should NOT be deleted. Make sure the end joints of the run are not active selected. They can be normal or hot or match selected. This is important for the delete operation. Create the new outline. Building from Parts runDescriptor should be a run (a single consecutive set of segments and joints). First we pick out the desired subsection. Then, Rosary will call CopyEachSegment, which in turn maps CopySegmentAndBuild to each of the desired elements. The copies are made and are passed to procedure q of Rosary, which assembles them into a new Rosary. Moves moving so that the specified end moving coincides with the specified end fixed. In other words, moving is translated. Run describes a part of its traj that is to be replaced by slice. The result is similar to Rope.Cat[Rope.Substr[...], newRope, Rope.Substr[...]] except that a Traj can be a circular structure. A hack to get arrows to work reasonably for now. [Artwork node; type 'ArtworkInterpress on' to command tool] To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected. [Artwork node; type 'ArtworkInterpress on' to command tool] To make sure the algorithm terminates, make sure the end joints of the run are not active selected. They can be normal or hot selected. Orientation Fundamental Routines The lifetime of a trajectory goes something like this: It begins as a single point. The sole purpose of this point is to be an "endpoint" to which the first segment can be attached. Subsequent segments are also added to endpoints, so this unifies the creation process. In Gargoyle, trajectories never appear at the top level of a scene; they are always part of an outline. It is suggested, then that each CreateTraj call be followed immediately by a CreateOutline call. Drawing Transforming Check segment types Check geometric properties FOR j: NAT IN [0..cpCount1) DO IF seg1.class.controlPointGet[seg1, j] # seg2.class.controlPointGet[seg2, j] THEN RETURN[FALSE]; ENDLOOP; Extra careful floating point fuzz check Accessing Parts Moves the given joint and tells all interested segments that it has moved. Use sparingly Parts Utilities Hit Testing Utilities | i j k | | v1x v1y v1z |=(v1y*v2z - v1z*v2y) i + (v1z*v2x - v1x*v2z) j | v2x v2y v2z |(v1x*v2y - v1y*v2x) k Choose an arbitrary point P in the plane. Choose two consecutive joints (v1, v2) on the polygon described by the traj. Call the vector P to v1, D1. Call the distance P to v2, D2. The area of the triangle made by these three points is |D1|*|D2|*sin(angle between D1 and D2)/2. This is just D2 x D1 where "x" is the cross product operator. This will be positive if D2 is clockwise of D1. If P is in the interior of poly for poly convex, and if we add up all of the areas we get by taking vertices pairwise in clockwise order, area will be positive. If we take them in counter-clockwise order, area will be negative. This turns out to be true whatever the position of P and even if poly is concave part of the time. PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL _ FALSE]; These values may be returned if the sequence is a single joint PROC [traj: TrajData, seg: Segment, index: NAT] RETURNS [done: BOOL _ FALSE]; These values may be returned if the sequence is a single joint Finds the nearest joint of traj to testPoint (and its distance from testPoint). These values may be returned if no joints are described in the descriptor. Same code as FindJointNormal in GGOutlineImplA: Finds the nearest control point of seq (within tolerance) to testPoint (and its distance from testPoint). Use sparingly: Returns -1 if it doesn't exist. Returns -1 if it doesn't exist. Ê?´–(cedarcode) style•NewlineDelimiter ˜codešœ™KšÏnœS™[Kšœ ÏeœI™TK™"K™K™&K™—šÏk ˜ J˜Ê—K˜š œŸœŸ˜KšŸœÖ˜ÝKšŸœ Ÿ˜K˜Kšœ Ÿœ˜)Kšœ Ÿœ˜&Kšœ Ÿœ˜,KšœŸœ˜#KšœŸœ˜KšœŸœ ˜5Kšœ Ÿœ ˜1IprocšœŸœ˜1KšœŸœ˜'Kšœ Ÿœ˜*KšœŸœ˜#KšœŸœ˜3Kšœ Ÿœ˜)KšœŸœ˜0Kšœ Ÿœ˜*KšœŸœ˜!KšœŸœ˜3KšœŸœ#˜;Kšœ Ÿœ˜$KšœŸœ˜!Kšœ Ÿœ˜+KšœŸœ˜+KšœŸœ˜1Kšœ Ÿœ˜'KšœŸœ˜1KšœŸœ!˜7KšœŸœ%˜=KšœŸœ!˜5Kšœ Ÿœ˜'KšœŸœ ˜7KšœŸœ˜2KšœŸœ˜!Kšœ Ÿœ˜+KšœŸœ ˜5Kšœ Ÿœ˜'Kšœ Ÿœ˜+KšœŸœ˜Kšœ Ÿœ˜'Kšœ Ÿœ˜-Kšœ Ÿœ˜%Kšœ Ÿœ˜'Kšœ Ÿœ˜)KšœŸœ˜/Kšœ Ÿœ˜/KšœŸœ˜#K˜KšœŸœ Ÿœ˜3—headšœ™K™—Kšœ™š œŸ œ!ŸœŸœŸœŸœŸœŸœŸœ=ŸœŸœ(ŸœŸœŸœŸœŸœ˜ÓKšœW™WKšœŸœmŸœŸœ ˜©KšœŸœE˜PK˜+K˜+Kšœ:Ÿœ˜?K˜K˜—š œŸœŸœŸœ˜BK™2KšœŸœ˜3Kš œŸœ3Ÿœ4ŸœŸœ˜¹KšœŸœLŸœŸœŸœŸœŸœŸœŸœWŸœ˜ÇKšœ:Ÿœ˜?K˜K˜—š œŸœŸœAŸœ ŸœŸœ˜{K˜ K˜KšœÑ™ÑKšœŸœ ˜(Kš ŸœŸœŸœ ŸœŸœ˜PKšÏb ™ KšŸœŸœ˜7šœŸœŸœ/˜JKšŸœ@˜D—Kšœ&˜&Kš ™šŸœŸœ˜K˜)Kšœ Ÿœ˜*Kšœ Ÿœ˜*Kšœ"Ÿœ˜:K˜K˜+K˜—Kš ™šŸœ˜šŸœŸœ˜Kšœ Ÿœ˜*K˜KK˜KK˜—šŸœ˜Kšœ Ÿœ˜*K˜KK˜KK˜—K˜*Kšœ˜K˜—K˜K˜—šœŸœŸœ2˜OKšœœ™œK˜ KšœŸœÏc$˜MšŸœŸœŸœ7˜ZK™9—KšŸœ Ÿœ˜2K˜2Kšœ&˜&K˜KK˜*K˜Kšœ˜K˜K™—šœŸœŸœ(˜FKšœñ™ñK˜ Kšœ˜KšœŸœ¡$˜MK˜K˜,KšŸœ Ÿ˜šœ˜K˜K˜K˜KšœŸœ˜2K˜—˜K˜,K˜K˜KšœŸœ˜3K˜—KšŸœŸœ˜K˜DK˜Kšœ˜K˜K˜—šœŸœŸœ"˜UKšœ&˜&Kšœ&˜&Kšœ ˜ KšœŸœ ˜'K˜-šŸœOŸœŸœŸ˜dKšœ,˜,šŸœŸœŸœ'Ÿ˜6Kšœ>˜>KšŸœ˜—KšŸœ˜—K˜-šŸœ ŸœBŸœŸ˜iK˜#Kšœ,˜,KšŸœ˜—K˜K˜—š œŸ œ:ŸœŸœŸœ˜KšœF™FKšŸ œŸ œ”™§KšœŸœ˜+K˜Kšœ˜Kšœ˜K˜-K˜Kš ™šŸœ"Ÿœ˜*K˜ K˜K˜—šŸœ˜K˜*Kš Ÿœ ŸœŸœŸœ¡(˜VK˜2K˜K˜—Kš 3™3šŸœŸœŸœŸœ˜1Kšœ œ˜šŸœŸœŸœ˜KšœŸœ œ¡˜CKšŸœŸœŸœ œ˜=K˜—K˜7K˜—KšŸœŸœ˜šŸ˜˜KšœŸœ œ¡˜HKšœ Ÿœ˜K˜——K˜K˜—šœŸ œ=Ÿœ˜bK™bK˜Kšœ ŸœŸœ˜šŸœŸœ¡+˜KšŸœŸœ˜K˜,KšœH˜HKšœ˜K˜—šŸœ˜K˜"K˜—K˜)K˜—š ŸœŸœ Ÿœ&Ÿœ¡a˜¥K˜WKšœ˜K˜)K˜—š ŸœŸœŸœ&Ÿœ¡\˜¢K˜,KšœH˜HKšœ˜K˜(K˜—šŸœ¡2˜9K˜,Kšœ˜K˜)K˜—KšŸœŸœ ŸœŸœ˜K˜K˜—šœŸ œ=Ÿœ˜bK™‰Kšœ ŸœŸœ˜šŸœŸœ¡<˜ZK˜>—šŸœ¡N˜UK˜K˜šŸœŸœŸœ'Ÿ˜5K˜)K˜(Kšœ ¡$˜DK˜)KšŸœŸœ ŸœŸœ˜K˜ KšŸœ˜—K˜*Kšœ ¡$˜DK˜)KšŸœŸœ ŸœŸœ˜K˜—K˜K˜—šœŸ œ(Ÿœ˜dK™SKšœŸœ˜.KšœŸœ˜+K˜7Kšœ"˜"Kšœ(¡)˜QKšœ*˜*šŸœŸœŸœŸ˜'š ŸœŸœ0ŸœŸœŸœ˜ZK˜/šŸœ"Ÿœ˜*K˜UK˜&Kšœ"ŸœŸœ˜BKšŸœŸœ$ŸœŸœ˜5Kšœ œ˜AK˜—šŸœ˜K˜fK˜—K˜—KšŸœ˜—šœ œ(˜FK™0—K˜—K˜š œŸœŸœŸœ+ŸœŸœ ˜wK˜KšœŸœ ˜'KšœŸœ ˜)K˜-K˜šŸœŸ˜šœ˜Kšœ5¡!˜VKšœ:Ÿœ¡˜[K˜—šœ ˜ K˜4KšœŸœ˜K˜—KšŸœŸœ˜—K˜K˜—š œŸœŸœŸœŸœ ˜aKšœ˜Kšœ˜K˜KšœŸœ ˜'KšœŸœ ˜)K˜KšŸœ"ŸœŸœŸœ˜5K˜5Kš Ðbc™#šŸœŸœŸœ˜™>K˜ZK˜%K˜K˜—š œŸœŸœŸœŸœ ˜QK˜KšœŸœ˜)KšœŸœ ˜(Kšœ˜K˜Kšœ˜Kšœ˜K˜ K˜K˜)Kšœ ¡œ(˜5šŸœSŸœŸœŸ˜hKšŸœŸœŸœ¡%˜BKšœ  œ˜(Kš —™—K˜KšœŸœ˜%K˜.KšœŸœ˜%Kš ™KšœŸœ˜#K˜KšœRŸœ ˜^KšœŸœ˜,KšŸœ˜—K˜K™—šœ™K˜—šœŸœŸœ Ÿœ˜UKšœP™PKšœŸœ ˜(K˜KšœŸœ˜4Kšœ ˜ Kšœ2Ÿœ˜9KšœŸœ˜0Kšœ˜Kšœ,Ÿœ˜3K˜Kšœ˜KšœŸœ˜Kšœ Ÿœ˜K˜$šœŸœŸœŸœ ˜Gš œŸœŸœŸœŸœ˜QKšœ˜Kšœ Ÿœ˜K˜%Kšœ ˜ K˜—K˜6K˜—š œŸœŸœŸœ ˜Eš œŸœŸœŸœŸœ˜OKšœ˜Kšœ Ÿœ˜K˜Kšœ ˜ K˜—K˜2K˜—K˜CK˜.K˜hšŸœ Ÿœ˜K˜/K˜.K˜/K˜.K˜:K˜—šŸœ˜K˜EK˜5K˜—Kšœ%¡Ù˜þK˜_šŸœ Ÿœ˜K˜+K˜*K˜+K˜*K˜4K˜—šŸœ˜K˜9K˜1K˜—Kš œ Ÿœ"ŸœŸœŸœ˜ZKšœ[Ÿœ0˜ŽK˜K˜K˜—š œŸœŸœŸœŸœŸœ˜^Kšœ€™€KšœŸœ¡$˜MKšœŸœ˜4K˜AKšœŸœ˜!KšœŸœ˜0K˜=KšœŸœ˜šœŸœŸœŸœ ˜Gš œŸœŸœŸœŸœ˜QKšœ˜Kšœ Ÿœ˜K˜%Kšœ ˜ K˜—K˜6K˜—š œŸœŸœŸœ ˜Eš œŸœŸœŸœŸœ˜OKšœ˜Kšœ Ÿœ˜K˜Kšœ ˜ K˜—K˜2K˜—K˜5K˜1KšœKŸœ0˜~Kšœ˜K˜—šœŸœŸœFŸœ˜tKšœ|™|K˜ K˜ K˜%K˜'KšŸœŸœ˜1šŸœŸœ˜K˜UKšœ˜Kšœ Ÿœ ˜Kšœ Ÿœ˜!˜K˜ Kšœ3˜3KšœA˜AKšœM˜MKšœ Ÿœ˜Kšœ'˜'Kšœ#˜#Kš œŸœŸœŸœŸœ˜,Kšœ˜—K˜—šŸœ¡˜K˜VKšœ˜Kšœ Ÿœ ˜Kšœ Ÿœ˜!˜K˜ Kšœ3˜3KšœA˜AKšœM˜MKšœ Ÿœ˜Kšœ'˜'Kšœ#˜#Kš œŸœŸœŸœŸœ˜,Kšœ˜—K˜—K˜K˜—šœŸœŸœ0Ÿœ˜bKšœÁ™ÁKšœŸœ¡˜LK˜K˜­¡’ÄÑÈÄZ|©Ä~¬KÄ`}2Zˆ¡’ÄÉœÄÿ½ˆÄ'ö1ÄtQ;z°¡’˜¢¯“¡¡¨¢·“¢°“ý™yzŽ˜ r j1Ĭôa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj– k é¢¯“¡¡¨¢·“¢°“Èo™g¢Žo˜ r jÄH/—Ä•?W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁwholeSeq– k é¢¯“¡¡¨¢·“¢°“ÈU™MþŽU˜¢¯“¡¡¨¢·“¢°“{U™M¢ŽU˜ r jÙÄW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun1– k é r jƒ? ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun2– k é¢¯“¡¡¨¢·“¢°“µ™ë³—˜¢¯“¡¡¨¢·“¢°“µ™y뎘 r jÆk ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj1– k é¢¯“¡¡¨¢·“¢°“Š°™±‰—˜¢¯“¡¡¨¢·“¢°“Š™y±Ž˜ r jj ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj2– k é k é k é k gšœ=™=K˜)Kšœ)ŸœŸœ˜:K˜)Kšœ)ŸœŸœ˜:K˜)K˜,K˜—Kšœˆ™ˆK˜K˜—šœŸœ0Ÿœ˜aKšœ'¡˜FKšœŸœ¡%˜PKšœŸœ¡%˜XKšœ%˜%Kšœ˜Kšœ˜Kšœ2˜2K˜!KšŸœ!ŸœŸœ ¡˜LK˜0K˜DK˜BKšœŸœ˜)Kšœ(˜(Kšœ ¡œ.˜;K˜'K˜'Kš ŸœŸœŸœŸœŸœŸœ˜'K˜)Kšœ˜Kšœ*¡6˜`Kšœ ˜ N–G78.66944 mm topLeading 78.66944 mm topIndent 1.411111 mm bottomLeading –:0.0 mm xmin 0.0 mm ymin 148.5194 mm xmax 75.84722 mm ymax – Interpress–¿ Interpress/Xerox/3.0  f j k j¡¥“ÄWB ¤ ¨  Ew¡£=ú ¢ ¨ r j % ¢ ¨¡¡¨ÄWB ¤ ¨ r j¡¥“ÄWB ¤ ¨¡¡¨Ä¡¨‰ÄSôA™­)—‰ÄsžA—AŽ)—AÄSôA—‰Ž¡¡¡¡™¢·“¢°“¢¯“¡¡¨‰ÄSôA™­)—˜¥¯“¡¡¨­)™‰ÄsžA—˜¥¯“¡¡¨‰ÄsžA™AŽ˜¥¯“¡¡¨AÄsžA™)—˜¢¯“¡¡¨)™AÄSôA—˜¢¯“¡¡¨AÄSôA™‰Ž˜¢¯“¡¡¨¢·“¢°“ÄYðÃ-™ÄQŸ¼1—AÄw®AÄß1Ê1¡“Äõ-—˜ r jÄûĦëW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun– k é¥¯“¡¡¨¢·“¢°“y)™Ä"˜ÄYÚ7ĠĢÓ^UÄsžA¡’ăùQħ{\ÄyÉQħ{\ ÄsžA¡’Äë«°Ä¢Ó^Äÿ·†ÄYÚ7é)¡’˜ r j"ƒ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj– k é¢¯“¡¡¨¢·“¢°“ÄYðÃ%™ÄQŸ¼!—Äx!ÑÄgYRÄr%O!¡“Äõ%—˜ r jÄ«Æ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Árun1– k é¢¯“¡¡¨¢·“¢°“é™ ÄQjA—UŽy—˜ r jÄ #Äq9W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Átraj1– k é r jÄ(  ¤ê$ ¢ ¥ ¨¡¡¨Ä¯“Ÿ ™¡ Ÿ ¡“¡¸ k é r jĉÈÄ‚ŽW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁConcat– k é¢¯“¡¡¨¢·“¢°“k ™ÄÐÒÄ-&ÄmbyÄ}²O¡“˜£¯“¡¡¨¢·“¢°“†5™Äèž ÄI¦—†5—ÄsŸÄ²¡ —˜ r jĉĚÄa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Álo– k é r jÄ;٠ĈUW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Áhi– k é r jÄ'y ÄzW ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Áhi– k é r jÄ;y ÄŠÚa ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Álo– k é r jÄ>!Ä‚7W ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁClose– k é k é k é k gšœ=™=Kšœˆ™ˆK˜K˜—K™šœ ™ K˜—š œŸœŸœ˜+KšœŸœ˜KšœŸœ˜Kšœ ˜ K˜ KšœŸœ ˜(šŸœŸœŸœŸ˜'K˜Kšœ˜K˜?KšŸœ˜—šŸœŸœŸœŸ˜'Kš œŸœŸœŸœŸœ˜jK˜=KšŸœ˜—K˜ K˜K˜K˜—š œŸœŸœŸœŸœ˜>KšŸœ˜K˜K˜—š œŸœŸœOŸœŸœ˜‰KšŸœ3˜9K˜—K™šœ™K™KšœÛ™Û—K˜K™š œŸœŸœ&˜?KšœŸœ ˜'K˜š œ˜!Kšœ˜šŸœŸœŸœŸ˜*Kšœ˜KšŸœ˜—K˜—Kšœ+˜+Kšœ ŸœŸœ˜K˜K˜"KšŸœŸœŸœŸœ˜#KšŸœŸœŸœ˜$K˜"Kšœ!˜!Kšœ˜Kšœ$Ÿœ˜*K˜K˜—šœŸœŸœ&˜AKšœŸœ ˜'Kšœ Ÿœ˜!Kšœ Ÿœ˜Kšœ&˜&K˜ K˜š œ˜!K˜K˜+K˜šŸœŸœŸœŸ˜%K˜KšŸœ˜—šŸœŸœŸœŸ˜K˜K˜+šŸœŸœŸœŸ˜%K˜KšŸœ˜—KšŸœ˜—K˜—KšŸœŸœŸœ˜$K˜"Kšœ$Ÿœ˜*Kšœ˜—K˜šœ ™ K˜—š œŸœ$˜7K˜fK˜ K˜ KšœŸœ ˜(Kšœ Ÿœ˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜K˜Kšœ+˜+KšŸœ˜—šŸœŸœŸœŸ˜KšœŸœ#˜1K˜˜XK˜FKšŸœŸœŸœ#˜2KšŸœœ˜/K˜K˜—šœŸœŸœ?˜cKšœF˜FK˜K˜—šœŸœŸœC˜dK˜ K˜ KšœŸœ˜%Kšœ Ÿœ˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜K˜Kšœ9˜9šŸœŸœŸœ'Ÿ˜6KšœI˜IKšŸœ˜—KšŸœ˜—šŸœŸœŸœŸ˜K˜Kšœ7˜7KšŸœ˜—K˜K˜—šœŸ œ0˜KKšœ ˜ Kšœ ˜ Kšœ Ÿœ˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜K˜KšœŸœ˜)šŸœŸœŸœ'Ÿ˜6KšœŸœ˜1KšŸœ˜—KšŸœ˜—šŸœŸœŸœŸ˜K˜KšœŸœ˜)KšŸœ˜—šœ˜K˜——šœŸœŸœ˜/Kšœ ˜ Kšœ ˜ Kšœ Ÿœ˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜K˜KšœŸœ ˜$KšœŸœ˜!KšœŸœ ˜$KšœŸœ ˜#šŸœŸœŸœ'Ÿ˜6KšœŸœ ˜,KšœŸœ˜)KšœŸœ ˜,KšœŸœ ˜+KšŸœ˜—KšŸœ˜—šŸœŸœŸœŸ˜K˜KšœŸœ ˜$KšœŸœ˜!KšœŸœ ˜$KšœŸœ ˜#KšŸœ˜—šœ˜K˜——šœŸœŸœ,Ÿœ˜hKšœŸœ¡˜DKšœŸœ#˜@K˜ K˜ Kšœ Ÿœ˜"Kšœ Ÿœ˜šŸœŸœŸœŸ˜K˜K˜:KšŸœŸœ-˜JšŸœŸœŸœ'Ÿ˜6K˜JKšŸœŸœ?˜dKšŸœ˜—KšŸœ˜—šŸœŸœŸœŸ˜K˜K˜8KšŸœŸœ1˜LKšŸœ˜—Kš œŸœŸœŸœŸœ ˜AK˜K˜—šœŸœŸœ Ÿœ#˜gKšœB˜BK˜K˜—š œŸœŸœŸœ Ÿœ˜pK˜CK˜K˜—š œŸœ-Ÿœ Ÿœ˜\šŸœ Ÿ˜K˜2K˜,K˜2K˜0KšŸœ˜—K˜K˜—šœŸœ-Ÿœ Ÿœ˜^šŸœ Ÿ˜K˜0K˜*K˜0K˜.KšŸœ˜—K˜K˜—š œŸœŸœ#˜TšŸœ Ÿ˜K˜2K˜,K˜2K˜0KšŸœ˜—K˜K˜—šœŸœŸœ#˜VšŸœ Ÿ˜K˜0K˜*K˜0K˜.KšŸœ˜—K˜—K˜K™ š œŸœŸœ˜8KšœŸœK˜UK˜K˜—š œŸœŸœ'˜?KšŸœ ŸœŸœ"˜OK˜K˜—š œŸœŸœŸœ˜HKšœŸœ˜(K˜—K™™ K˜—šœŸ œ"Ÿœ ŸœŸœDŸœ Ÿœ"ŸœŸœ Ÿœ ˜Kš ŸœŸœŸœ ŸœŸœ˜Lš œ’ŸœŸœ;ŸœŸœŸœŸœ™—Lšœ ŸœŸœ*™>LšœŸœŸœ™/LšœÈ™ÈL˜6LšœŸœ˜L˜šŸœŸœŸœ Ÿ˜L˜%šŸœ ŸœŸœ/Ÿ˜DL˜gL˜LšŸœ˜—LšœJŸœ ŸœŸœ ˜qL˜LšŸœ˜—Lšœ¡˜L˜—šœŸœOŸœŸœ ˜‰Lšœ˜LšœŸœ˜0LšœŸœ˜)L˜L˜/LšŸœŸœB˜]šŸœŸœŸœ Ÿ˜L˜0šŸœ ŸœŸœ/Ÿ˜DL˜K˜K˜Kšœ¡"˜4K˜K˜LK˜šŸœ Ÿœ¡˜/Kšœ˜Kšœ˜KšœŸœ˜K˜#K˜1šŸœŸœ˜K˜$KšœŸœ˜(KšœŸœ˜(K˜$šŸœŸœŸœŸœ˜AKšœ ¡œFŸœ˜^Kšœ˜—Kšœ˜—šŸœŸœ˜#K˜0KšœŸœ˜(KšœŸœ˜(K˜*šŸœŸœŸœŸœ˜AKšœ ¡œFŸœ˜]Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜Kš˜—šœŸœŸœ<ŸœŸœ ŸœŸœKŸœŸœ˜äK˜KšœŸœ ˜'KšœŸœ˜0KšœŸœ˜Kšœ˜Kšœ ŸœŸœ˜Kšœ Ÿœ˜'šœ ˜.Kš Ÿœ'ŸœŸœŸœŸœ™MK˜MšŸœ Ÿœ˜K˜<šŸœŸœ˜K˜K˜K˜Kšœ Ÿœ˜K˜—K˜—K˜K˜—šŸœ Ÿœ˜KšŸœŸœQ˜WKšŸœŸœ¡˜Kšœ˜—Kšœ$¡™>K˜K˜Kšœ¡"˜4K˜K˜LK˜šŸœ Ÿœ¡˜/Kšœ˜KšœŸœ˜K˜#K˜1K˜*KšœŸœ˜(KšœŸœ˜(šŸœŸœŸœŸœ˜@K˜3KšœOŸœ ˜]Kšœ˜—Kšœ˜—Kšœ˜K˜—š œŸœŸœ<ŸœŸœ ŸœŸœKŸœŸœ˜äKšœO™OK˜KšœŸœ ˜'KšœŸœ˜0Kšœ Ÿœ˜'KšœŸœ˜Kšœ˜Kšœ8˜8š œŸœ*ŸœŸœŸœŸœ˜aK˜;šŸœŸœ˜K˜K˜K˜Kšœ Ÿœ˜K˜—K˜—šŸœ Ÿœ˜KšŸœŸœQ˜WKšŸœŸœ¡˜Kšœ˜—KšœJ™JK˜K˜K˜K˜Kš K˜KK˜šŸœ Ÿœ˜'Kšœ/™/K˜#šŸœŸœ˜+KšŸœŸœŸœŸœ˜;—KšŸœ&˜*K˜(š ŸœŸœŸœŸœŸœ˜#KšœJŸœ˜QKšœIŸœ˜OK˜=Kš œ ŸœŸœ?Ÿœ=Ÿœ Ÿœ ˜®Kšœ˜—šŸœ˜Kš ŸœŸœŸœ¡œ7Ÿœ˜aKš ŸœŸœŸœ¡œ7Ÿœ˜bKšœ˜—K˜—Kšœ˜K˜—šœŸœŸœ<ŸœŸœ ŸœŸœŸœKŸœŸœ˜„Kšœi™iš œŸœŸœŸœŸœ˜(Kšœ Ÿœ"˜.šŸœŸœŸœŸ˜KšŸœŸœŸœŸœ˜3KšŸœ˜—KšŸœŸœ˜K˜—K˜KšœŸœ ˜'KšœŸœ˜0Kšœ Ÿœ˜'KšœŸœ˜KšœŸœ˜Kšœ˜Kšœ˜Kšœ ŸœŸœ˜š œŸœ'ŸœŸœŸœŸœ˜`KšŸœŸœŸœŸœ˜!K˜ršŸœ Ÿœ˜K˜<šŸœŸœ˜K˜K˜K˜$K˜K˜Kšœ Ÿœ˜K˜—K˜—K˜—šŸœ Ÿœ˜KšŸœŸœQ˜WKšŸœŸœ¡˜Kšœ˜—K˜K˜K˜Kšœ¡˜7K˜K˜LKšŸœ Ÿœ$˜3Kšœ˜—K™šœ™K˜—š œŸœŸœŸœ Ÿœ˜OK™Kšœ˜šŸœŸœŸœŸ˜$K˜!KšŸœŸœŸœ˜$KšŸœ˜—KšŸœ˜ ˜K˜——š œŸœŸœ"Ÿœ Ÿœ˜UK™K˜šŸœŸœŸœŸ˜&K˜!KšŸœŸœŸœ˜$KšŸœ˜—KšŸœ˜ K˜—K˜KšŸœ˜K˜—…—ÊÎ!ê