DIRECTORY Complex, Cubic, DynFit, LSPiece, FitState, FitBasic USING [SampleHandle], Highlight USING [ShowPt, Context, CleanUp], FitStateUtils, Seq USING [ComplexSequence, ComplexSequenceRec, RealSequence, RealSequenceRec, JointSequence, BooleanSequence, BooleanSequenceRec], Vector, Real USING [RoundLI], SampledCurves, PotentialKnots, RealFns USING [SqRt, AlmostZero, CosDeg]; PotentialKnotsImpl: CEDAR PROGRAM IMPORTS Complex, Cubic, DynFit, LSPiece, Vector, RealFns, FitStateUtils, FitState, Highlight, Real, SampledCurves EXPORTS PotentialKnots = BEGIN OPEN PotentialKnots; SampledCurve: TYPE = SampledCurves.SampledCurve; FindCorners: PUBLIC PROC [state: FitState.Handle, minK: REAL, forceKnot: BOOLEAN _ FALSE] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, state.closed]; index: NAT _ 0; set: FitStateUtils.SampleProc = { IF ABS[sc[index].k] >= minK THEN s.jointType _ IF forceKnot THEN forced ELSE potential; index _ index+1; }; FitStateUtils.ForAllSamples[state.slist, set]; }; FindInflections: PUBLIC PROC [state: FitState.Handle, flat: REAL, forceKnot: BOOLEAN _ FALSE] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, state.closed]; infl: Seq.BooleanSequence _ NEW[Seq.BooleanSequenceRec[sc.length]]; index: NAT _ 0; set: FitStateUtils.SampleProc = { IF infl[index] THEN s.jointType _ IF forceKnot THEN forced ELSE potential; index _ index+1; }; FOR i: NAT IN (0..infl.length-1) DO IF sc[i-1].k*sc[i+1].k<0 THEN infl[i] _ TRUE ELSE infl[i] _ FALSE; ENDLOOP; IF sc.closed THEN { last: NAT _ sc.length-1; infl[0] _ IF sc[last].k*sc[1].k < 0 OR ABS[sc[0].k]1 THEN { --we have a sequence of inflections. Set the one in the middle FOR i: NAT IN [ft..lt] DO infl[i] _ FALSE; ENDLOOP; infl[(lt+ft)/2] _ TRUE; }; ENDLOOP; index _ 0; FitStateUtils.ForAllSamples[state.slist, set]; }; HVLines: PUBLIC PROC [state: FitState.Handle, long, tol: REAL, forceKnot: BOOLEAN _ FALSE, highlight: Highlight.Context _ NIL] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, state.closed]; lines: Seq.BooleanSequence _ NEW[Seq.BooleanSequenceRec[sc.length]]; start: FitBasic.SampleHandle _ NIL; index, startIndex: NAT _ 0; set: FitStateUtils.SampleProc = { IF lines[index] THEN { IF start=NIL THEN {start _ s; startIndex _ index} ELSE { --found second end of the line start.jointType _ s.jointType _ IF forceKnot THEN forced ELSE potential; start _ NIL; }; }; index _ index+1; }; isFlat: SampledCurves.ConditionProc = { IF highlight#NIL THEN Highlight.ShowPt[highlight, point.p]; RETURN[ABS[point.k] <= tol]; }; DO length: REAL; dir: Vector.Vec; ft, lt, from, to: NAT; [ft, lt] _ SampledCurves.FindSequence[sc, index, isFlat ! SampledCurves.None => EXIT]; index _ lt + 1; [from, to, length, dir] _ GetLength[sc, ft, lt]; IF length >= long THEN { dir _ [ABS[dir.x], ABS[dir.y]]; IF dir=[1,0] OR dir=[0,1] THEN lines[from] _ lines[to] _ TRUE; }; IF index > sc.length THEN EXIT; ENDLOOP; index _ 0; FitStateUtils.ForAllSamples[state.slist, set]; }; GetLength: PROC [sc: SampledCurves.SampledCurve, ft, lt: NAT] RETURNS [from, to: NAT, length: REAL, dir: Vector.Vec] = { prev, next: NAT; from _ SampledCurves.Wrap[sc, ft]; to _ SampledCurves.Wrap[sc, lt]; prev _ SampledCurves.Wrap[sc, ft-1]; next _ SampledCurves.Wrap[sc, lt+1]; dir _ Vector.Unit[Vector.Sub[sc[to].p,sc[from].p]]; IF Vector.Unit[Vector.Sub[sc[from].p,sc[prev].p]]=dir THEN from _ prev; IF Vector.Unit[Vector.Sub[sc[next].p,sc[to].p]]=dir THEN to _ next; length _ SampledCurves.ArcLength[sc, from, to] }; Find90: PUBLIC PROC [state: FitState.Handle, long, tol: REAL, forceKnot: BOOLEAN _ FALSE, highlight: Highlight.Context _ NIL] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, state.closed]; isCorner: Seq.BooleanSequence _ NEW[Seq.BooleanSequenceRec[sc.length]]; HV: TYPE = {none, h, v}; pk: NAT _ 0; foundHV: HV _ none; index, startIndex: NAT _ 0; found: BOOLEAN _ FALSE; set: FitStateUtils.SampleProc = { IF isCorner[index] THEN { s.tanIn _ [sc[index].t.x, 0]; --assume one direction s.tanOut _ [0, sc[index].t.y]; IF sc[index].k*SampledCurves.DThetaDegrees[s.tanIn, s.tanOut] < 0 THEN { --switch them s.tanOut _ s.tanIn; s.tanIn _ [0, sc[index].t.y]; }; s.jointType _ IF forceKnot THEN forced ELSE potential; }; index _ index+1; }; isFlat: SampledCurves.ConditionProc = { IF highlight#NIL THEN Highlight.ShowPt[highlight, point.p]; RETURN[ABS[point.k] <= tol]; }; lastTime: BOOLEAN _ FALSE; DO length: REAL; ft, lt, pk: NAT; dir: Vector.Vec; currentHV: HV _ none; from, to: NAT; [ft, lt] _ SampledCurves.FindSequence[sc, index, isFlat ! SampledCurves.None => EXIT]; [from, to, length, dir] _ GetLength[sc, ft, lt]; IF length >= long THEN { dir _ [ABS[dir.x], ABS[dir.y]]; currentHV _ IF dir=[1,0] THEN v ELSE IF dir=[0,1] THEN h ELSE none; IF pk=from AND ((foundHV=h AND currentHV=v) OR (foundHV=v AND currentHV=h)) THEN isCorner[pk] _ TRUE; --mark corner pk _ to; }; IF lastTime THEN EXIT; index _ lt+1; foundHV _ currentHV; --update foundHV IF index > sc.length THEN lastTime _ TRUE; --need one last loop ENDLOOP; index _ 0; FitStateUtils.ForAllSamples[state.slist, set]; }; FindDeltaKs: PUBLIC PROC [state: FitState.Handle, minDK: REAL, forceKnot: BOOLEAN _ FALSE] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, state.closed]; dk: Seq.RealSequence _ NEW[Seq.RealSequenceRec[sc.length]]; index: NAT _ 0; set: FitStateUtils.SampleProc = { IF ABS[dk[index]]>= minDK THEN { s.jointType _ IF forceKnot THEN forced ELSE potential; IF s.tanIn=[0,0] THEN s.tanIn _ sc[index].t; IF s.tanOut=[0,0] THEN s.tanOut _ sc[index].t; }; index _ index+1; }; FitStateUtils.ForAllSamples[state.slist, set]; }; DynPoly: PUBLIC PROC [state: FitState.Handle, penalty, lineLength, maxAngle: REAL] = { samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; closed: BOOLEAN _ FitState.GetClosed[state]; sc: SampledCurve _ SampledCurves.SampledCurveFromSamples[samples, closed]; bool: Seq.BooleanSequence _ NEW[Seq.BooleanSequenceRec[sc.length]]; endpoints: DynFit.Segments; tans: Seq.ComplexSequence; sn, ep, njoints: NAT _ 0; set: FitStateUtils.SampleProc = { IF sn=endpoints[ep] THEN { IF s.jointType=none THEN { IF bool[sn] THEN { s.jointType _ forced; s.tanIn _ tans[ep]; s.tanOut _ tans[ep+1]; } ELSE s.jointType _ potential; }; ep _ ep+1; IF ep=njoints THEN RETURN[TRUE]; --no more joints }; sn _ sn+1; }; oDir: Vector.Vec _ [0,0]; [segments: endpoints] _ DynFit.FitSegments[samples, closed, penalty]; njoints _ IF closed THEN endpoints.length-1 ELSE endpoints.length; tans _ NEW[Seq.ComplexSequenceRec[2*njoints]]; FOR i: NAT IN [0..njoints] DO --want to wrap around wrap: PROC[old: INT] RETURNS[new: INT] = { IF old IN [0..njoints) THEN RETURN[old]; IF closed THEN { new _ old MOD njoints; IF new<0 THEN new _ njoints} ELSE new _ IF new<0 THEN 0 ELSE njoints-1; }; cDir: Vector.Vec _ [0,0]; from: NAT _ endpoints[wrap[i-1]]; --want to wrap in endpoints to: NAT _ endpoints[wrap[i]]; --from and to are sample indices IF SampledCurves.ArcLength[sc, from, to] >= lineLength THEN { angle: REAL; cDir _ Vector.Unit[Vector.Sub[sc[to].p,sc[from].p]]; angle _ ABS[SampledCurves.FindAngleDegrees[oDir, cDir]]; IF oDir#[0,0] AND angle >= maxAngle THEN { t: NAT _ wrap[i-1]; --index into tangent sequence bool[from] _ TRUE; tans[t] _ oDir; tans[t+1] _ cDir; }; }; oDir _ cDir; ENDLOOP; FitStateUtils.ForAllSamples[state.slist, set]; }; CubicTangents: PUBLIC PROC [state: FitState.Handle, metrics: LSPiece.Metrics, progress: Progress _ NIL] = { joints: Seq.JointSequence; tangents: Seq.ComplexSequence; samples: Seq.ComplexSequence _ FitState.CurrentSamples[state]; closed: BOOLEAN _ FitState.GetClosed[state]; n: NAT _ samples.length; t: Seq.RealSequence _ NEW[Seq.RealSequenceRec[n]]; lastTangent: Complex.Vec; bezier: Cubic.Bezier; prev: PROC [index: NAT] RETURNS [NAT] = { IF closed THEN RETURN[IF index=0 THEN joints.length-1 ELSE index-1] ELSE RETURN[index]; }; next: PROC [index: NAT] RETURNS [NAT] = { IF closed THEN RETURN[IF index=joints.length-1 THEN 1 ELSE index+1] ELSE RETURN[index]; }; pointsBetween: PROC [from, to: NAT] RETURNS [NAT] = { IF closed AND to metrics.sumErr THEN [0,0] ELSE TangentVector[bezier,t[q]]; }; IF progress#NIL THEN IF progress[lastTangent,bezier] THEN EXIT; tanIn _ IF tangents[2*i]=[0,0] THEN lastTangent ELSE tangents[2*i]; tanOut _ IF tangents[2*i+1]=[0,0] THEN lastTangent ELSE tangents[2*i+1]; FitState.SetJoint[state,joints[i].index,(IF joints[i].forced THEN forced ELSE potential), tanIn, tanOut]; ENDLOOP; }; freeEnds: BOOLEAN _ TRUE; minPoints: NAT _ 4; maxSin: REAL _ 0.5; TangentVector: PROC [b: Cubic.Bezier, t: REAL] RETURNS [tang: Complex.Vec] = { c: Cubic.Coeffs _ Cubic.BezierToCoeffs[b]; tang.x _ (3*c.c3.x*t+2*c.c2.x)*t + c.c1.x; tang.y _ (3*c.c3.y*t+2*c.c2.y)*t + c.c1.y; }; CircleTangents: PUBLIC PROC [state: FitState.Handle, maxAngle: REAL] = { DiffTangents[state, maxAngle, FALSE, CircleTan]; }; QuickTangents: PUBLIC PROC [state: FitState.Handle, maxAngle: REAL, setCorners: BOOLEAN _ FALSE] = { DiffTangents[state, maxAngle, setCorners, CheapTangent]; }; SquareTangents: PUBLIC PROC [state: FitState.Handle, maxAngle: REAL, setCorners: BOOLEAN _ FALSE] = { DiffTangents[state, maxAngle, setCorners, SquareTangent]; }; TangentProc: TYPE = PROC[a, b, c: Complex.Vec, maxAngle: REAL] RETURNS [t: Complex.Vec]; TooSharp: SIGNAL[d1,d2: Complex.Vec] = CODE; DiffTangents: PROC [state: FitState.Handle, maxAngle: REAL, setCorners: BOOLEAN, tangentProc: TangentProc] = { first: BOOLEAN _ TRUE; next,prev, tI, tO: Complex.Vec; nextJoint: PROC [sample: FitBasic.SampleHandle] RETURNS[FitBasic.SampleHandle]= { l: FitBasic.SampleHandle _ sample; DO l _ FitStateUtils.NextSa[state.slist, l, state.closed]; IF l.jointType#none THEN EXIT; ENDLOOP; RETURN[l]; }; prevJoint: PROC [sample: FitBasic.SampleHandle] RETURNS[FitBasic.SampleHandle]= { l: FitBasic.SampleHandle _ sample; DO l _ FitStateUtils.PrevSa[state.slist, l, state.closed]; IF l.jointType#none THEN EXIT; ENDLOOP; RETURN[l]; }; do: FitStateUtils.SampleProc = { setZero: BOOLEAN _ FALSE; IF s.jointType=none THEN RETURN; IF first THEN { prev _ prevJoint[s ! FitStateUtils.AtEnd => {setZero _ TRUE; CONTINUE}].xy; first _ FALSE; }; next _ nextJoint[s ! FitStateUtils.AtEnd => {setZero _ TRUE; CONTINUE}].xy; tI _ tO _ IF setZero THEN [0,0] ELSE tangentProc[prev, s.xy, next, maxAngle ! TooSharp => { IF setCorners THEN {tI _ d1; tO _ d2} ELSE tI _ tO _ [0,0]; CONTINUE}]; IF s.tanIn=[0,0] THEN s.tanIn _ tI; IF s.tanOut=[0,0] THEN s.tanOut _ tO; prev _ s.xy; }; FitStateUtils.ForAllSamples[state.slist, do]; }; TestCorner: PROC [a, b, c: Vector.Vec, maxAngle: REAL] RETURNS [in, out: Vector.Vec] = { angle, magIn, magOut: REAL; in _ Vector.Sub[b,a]; out _ Vector.Sub[c,b]; magIn _ Vector.Mag[in]; magOut _ Vector.Mag[out]; angle _ ABS[SampledCurves.FindAngleDegrees[in,out]]; IF angle > maxAngle THEN SIGNAL TooSharp[Vector.Div[in, magIn],Vector.Div[out, magOut]] ELSE RETURN[in,out]; }; CheapTangent: PROC [a, b, c: Complex.Vec, maxAngle: REAL] RETURNS [t: Complex.Vec] = { in, out: Vector.Vec; [in,out] _ TestCorner[a, b, c, maxAngle]; --will signal TooSharp if it's a corner t _ Vector.Unit[Vector.Add[in,out]]; }; SquareTangent: PROC [a, b, c: Complex.Vec, maxAngle: REAL] RETURNS [t: Complex.Vec] = { in, out: Vector.Vec; [in,out] _ TestCorner[a, b, c, maxAngle]; t _ Vector.Unit[Vector.Add[Vector.Mul[in, Vector.Mag[in]], Vector.Mul[out, Vector.Mag[out]]]]; }; CircleTan: PROC [a, b, c: Complex.Vec, maxAngle: REAL] RETURNS [t: Complex.Vec] = { d1, d2: Vector.Vec; a1,b1,c1,a2,b2,c2,det: REAL; midAB,midBC,center: Complex.Vec; [d1,d2] _ TestCorner[a, b, c, maxAngle]; --will signal TooSharp if it's a corner midAB _ Vector.Div[Vector.Add[a,b],2]; midBC _ Vector.Div[Vector.Add[b,c],2]; a1 _ - d1.x; b1 _ - d1.y; a2 _ - d2.x; b2 _ - d2.y; det _ a1*b2-a2*b1; IF RealFns.AlmostZero[det, -100] THEN { --3 pts nearly colinear t _ Vector.Unit[Complex.Sub[c,a]]; } ELSE { c1 _ a1*midAB.x+b1*midAB.y; c2 _ a2*midBC.x+b2*midBC.y; center.x _ (c1*b2-c2*b1)/det; center.y _ (c2*a1-c1*a2)/det; t _ Vector.Unit[Vector.Normal[Vector.Sub[b,center]]]; }; }; SelectSample: PROC [state: FitState.Handle, z: Complex.Vec] = { --should be in FitStateImpl closest,d: REAL _ 10.0E+30; found: FitBasic.SampleHandle; index: NAT; i: NAT _ 0; do: FitStateUtils.SampleProc = { IF ABS[s.xy.x-z.x]=length THEN index-length ELSE index]}; i: INT _ 0; addJoint: PROC [from, to: INT, tan: Complex.Vec] = { range: INT _ IF to>=from THEN from+to ELSE from+to+length; k: INT _ wrap[range/2]; IF range MOD 2 = 0 THEN FitState.SetJoint[state, k, (IF forceKnot THEN forced ELSE potential), tan, tan] ELSE { l: INT _ wrap[k+1]; addList _ CONS[NEW[addSample _ [ index: l, x: (s[k].x+s[l].x)/2.0, y: (s[k].y+s[l].y)/2.0, tan: tan]], addList]; }; }; ExtractProc: TYPE = PROC [v: Complex.Vec] RETURNS[REAL]; xProc: ExtractProc = {RETURN[v.x]}; yProc: ExtractProc = {RETURN[v.y]}; TangentProc: TYPE = PROC[d: REAL] RETURNS[Complex.Vec]; hTan: TangentProc = {RETURN[[d,0]]}; vTan: TangentProc = {RETURN[[0,d]]}; loop: PROC [val: REAL, compare, direction: ExtractProc, tangent: TangentProc] = { j,first: INT _ -1; d: REAL; i: INT_ 0; DO UNTIL compare[s[i]]#val DO i _ wrap[i+1]; ENDLOOP; --worry about wrap around values UNTIL compare[s[i]]=val DO i _ wrap[i+1]; ENDLOOP; --find first value IF first=-1 THEN first _ i ELSE IF first=i THEN EXIT; --want to find all extremes j _ i; UNTIL compare[s[wrap[j+1]]]#val DO j _ wrap[j+1]; ENDLOOP; --find last one d _ direction[s[j]]-direction[s[i]]; --get the direction right d _ IF RealFns.AlmostZero[d,-10] THEN 0 ELSE d/ABS[d]; --normalize unless d=0 addJoint[i,j,tangent[d]]; ENDLOOP; }; FOR i: INT IN [0..s.length) DO IF s[i].x>maxX THEN maxX _ s[i].x; IF s[i].y>maxY THEN maxY _ s[i].y; IF s[i].x angle THEN s.jointType_forced; }; FitStateUtils.ForAllSamples[state.slist, do]; }; QuickPoly: PUBLIC PROC [state: FitState.Handle, highlight: Highlight.Context _ NIL] = { lpk: FitBasic.SampleHandle; wrap: BOOLEAN _ FALSE; first: BOOLEAN _ TRUE; evenDir, oddDir,newDir: INT _ 10; --impossible number for init do: FitStateUtils.SampleProc = { IF first THEN { first _ FALSE; lpk _ s; IF highlight#NIL THEN Highlight.ShowPt[highlight, lpk.xy]; IF NOT state.closed THEN s.jointType _ potential;} ELSE { dx: REAL _ s.xy.x - s.prev.xy.x; dy: REAL _ s.xy.y-s.prev.xy.y; max: REAL _ MAX[ABS[dx],ABS[dy]]; IF RealFns.AlmostZero[max, -100] THEN GOTO tooSmall; newDir _ Real.RoundLI[dx/max]+3*Real.RoundLI[dy/max]; IF newDir#evenDir AND newDir#oddDir THEN { --found a change in direction lpk.jointType _ potential; lpk _ s.prev; IF highlight#NIL THEN Highlight.ShowPt[highlight, lpk.xy]; IF newDir MOD 2 = 0 THEN evenDir _ newDir ELSE oddDir _ newDir; IF wrap THEN RETURN[TRUE]; }; EXITS tooSmall => NULL; }; }; FitStateUtils.ForAllSamples[state.slist, do]; wrap _ TRUE; --we're now wrapping around FitStateUtils.ForAllSamples[state.slist, do]; IF highlight#NIL THEN Highlight.CleanUp[highlight]; }; END. ÄPotentialKnotsImpl.mesa last edited by Maureen Stone December 17, 1984 3:59:09 pm PST last edited by Michael Plass August 30, 1982 2:06 pm These routines use SampledCurves to find the tangents and curvature at each point. They then build a sequence of booleans to mark the joints. Takes local curvatures. Defines as a corner any value with K>=minK. Takes local curvatures. Looks for zero crossings. now reduce sequences of zero crossings now index is the first TRUE value now index is the first FALSE value (filtering out changebind fit s that are too close) Takes local curvatures. Looks for flat spots crossings. start.tanOut _ sc[startIndex].t; s.tanIn _ sc[index].t; Test for horizontal or vertical. If so, set each end see if "line" extends to next samples Takes local curvatures. Looks for horizontal and vertical lines within tol. defines combination of hline sample vline as a 90 degree corner. The sc[index].t is tanIn + tanOut; We need to rediscover tanIn and tanOut We discriminate between the two possibilites by testing the sign of the curvature. we now have a sequence of TRUE for each flat spot A 90 corner is a horizontal sequence followed by a vertical sequence OR a vertical sequence followed by a horizontal sequence Takes local curvatures. Mark as a potential knot any place the change in curvature is greater than minDK. Set tangents using QuickTangent algorithm finds nodes by fitting a polygon using DynFit. Forces joints at lines > lineLength sn is the sample number, ep is the next index into endpoints IF the curve is open, endpoints will always contain the first and last sample points. IF the curve is closed, the last point in endpoints will always be samples.length, indicating that the last line connects back to the first point. Progress: PROC[tangent: Complex.Vec, cubic: Cubic.Bezier] RETURNS [stop: BOOLEAN]; Sets tangents at joints by fitting a cubic between neighboring joints computes tangents at node points by finding a circle through 3 adjacent nodes. Ignore joints where angle between neighbors is greater than maxAngle. computes tangents by differencing neighbors computes tangents by differencing neighbors The center of the circle is the intersection of the two perpendicular bisectors of d1 and d2 ax+by = c is a line. Find two lines, use Cramer's rule to find intersection. Normal to b-center is tangent vector. Finds the horizontal and vertical extremes in a set of samples and puts a joint and tangent there. now find the range of samples with min and max values. Put joints in centers of each range Run this after setting the tangents to "true" the nearly horizontal and vertical tangents. tol=angle. Run this to make nearly equal left and right tangents the same. tol=angle. Split the difference. Run this to force joints on potential knots whose tangents differ by angle. tests the lines between joints for horizontal or vertical within tol=dmin/dmax. Puts joints with horizontal or vertical tangents at the end of each line. ForceKnot sets the "corner" bit to force a knot but sets both tangents to be the same. 3*dy+dx is an id for the direction. runs [-4..4] for dx, dy in [-1..1]; HVTangents: PUBLIC PROC [state: FitState.Handle, tol: REAL, forceKnot: BOOLEAN _ FALSE] = { Run this after setting the tangents to "true" the nearly horizontal and vertical tangents. tol=angle. smallTan: REAL _ RealFns.TanDeg[tol]; t: Complex.Vec; --temporary variable do: FitStateUtils.SampleProc = { set: BOOLEAN _ FALSE; IF s.jointType=none THEN RETURN; t _ [ABS[s.tanIn.x],ABS[s.tanIn.y]]; --we want positive quantities for compare IF t.x > t.y AND t.y < t.x*smallTan THEN {set _ TRUE; s.tanIn _ [1,0]}; IF t.y > t.x AND t.x < t.y*smallTan THEN {set _ TRUE; s.tanIn _ [0,1]}; t_ [ABS[s.tanOut.x],ABS[s.tanOut.y]]; IF t.x > t.y AND t.y < t.x*smallTan THEN {set _ TRUE; s.tanOut _ [1,0]}; IF t.y > t.x AND t.x < t.y*smallTan THEN {set _ TRUE; s.tanOut _ [0,1]}; IF set AND forceKnot THEN s.jointType _ forced; }; FitStateUtils.ForAllSamples[state.slist, do]; }; ʽ˜J˜Jšœ™Jšœ=™=Jšœ5™5J˜šÏk ˜ Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜ Jšœ œ˜Jšœ œ˜+J˜Jšœœz˜ƒJšœ˜Jšœœ ˜J˜Jšœ˜Jšœœ˜)—J˜Jšœœœœj˜“Jšœ˜Jš œ˜J˜J™Jšœœ˜0•StartOfExpansion[]š Ïn œœœ œ œœ˜]JšÏcE™EJšœ>˜>JšœP˜PJšœœ˜˜!Jš œœœœ œœ ˜WJ˜J˜—J˜.J˜—š žœ œ œ œœ˜aJšŸ2™2Jšœ>˜>JšœP˜PJšœœ$˜CJšœœ˜˜!Jš œ œœ œœ ˜JJ˜J˜—šœœœ˜#Jš œœ œœ œ˜BJšœ˜—šœ œ˜Jšœœ˜Jšœ œœœœœœœ˜NJšœ œœœœœœœ˜TJ˜—J™&šœ˜Jšœœ˜ Jšœœœ œœœœ˜TJ™!Jšœ ˜ šœ˜Jšœœ œ˜Jšœœ˜—J™VJšœ ˜ šœ œŸ>˜Qšœœœ œ˜Jšœ œœ˜—Jšœœ˜J˜—Jšœ˜—J˜ J˜.J˜—š žœ œ%œ œœ!œ˜‚JšŸ8™8Jšœ>˜>JšœP˜PJšœœ$˜DJšœœ˜#Jšœœ˜˜!šœœ˜Jšœœœ ˜1šœŸ˜%Jšœ œ œœ ˜HJ™ J™Jšœœ˜ J˜—J˜—J˜J˜—šœ'˜'Jšœ œœ&˜;Jšœœ˜Jšœ˜—š˜Jšœœ˜ J˜Jšœœ˜JšœPœ˜VJ˜J˜0šœœ˜Jšœœ œ ˜Jšœ Ÿ œŸœ™5Jšœ œ œœ˜>J˜—Jšœœœ˜Jšœ˜—J˜ J˜.J˜J˜—š Ïb œœ*œœ œ œ˜xJšœ œ˜J™%Jšœ"˜"Jšœ ˜ Jšœ$˜$Jšœ%˜%Jšœ3˜3Jšœ4œ ˜GJšœ2œ ˜CJšœ.˜.J˜J˜—š žœ œ%œ œœ!œ˜JšŸŽ™ŽJšœ>˜>JšœP˜PJšœ œ$˜GJšœœ˜Jšœœ˜ Jšœ œ˜Jšœœ˜Jšœœœ˜˜!J™JJ™Tšœœ˜JšœŸ˜4J˜šœ@œŸ ˜VJ˜J˜J˜—Jšœœ œœ ˜6J˜—J˜J˜—šœ'˜'Jšœ œœ&˜;Jšœœ˜Jšœ˜—J™1J™DJ™8Jšœ œœ˜š˜Jšœœ˜ Jšœ œ˜J˜Jšœ œ˜Jšœ œ˜JšœPœ˜VJ˜0šœœ˜Jšœœ œ ˜Jš œ œ œœœ œœ˜Cšœ˜ Jšœ œœ œ˜@JšœœŸ ˜'—J˜J˜—Jšœ œœ˜J˜ JšœŸ˜%Jšœœ œŸ˜?Jšœ˜—J˜ J˜.J˜J˜—š ž œ œ!œ œœ˜^JšŸ–™–Jšœ>˜>JšœP˜PJšœœ!˜;Jšœœ˜˜!šœœœ˜ Jšœœ œœ ˜7Jšœœ˜-Jšœœ˜/J˜—J˜J˜—J˜.J˜—J˜šžœ œ9œ˜VJšŸS™SJšœ>˜>Jšœ œ˜,JšœJ˜JJšœœ$˜CJ˜Jšœ˜Jšœœ˜J™<˜!šœ˜šœœ˜šœ œ˜Jšœ˜J˜J˜J˜—Jšœ˜J˜—J˜ Jš œ œœœŸ˜1J˜—J˜ J˜—Jšœ˜J˜EJ™UJ™’Jšœ œœœ˜BJšœœ$˜.š œœœœŸ˜3š œœœœœ˜*Jšœœœœ˜(šœœ˜Jšœ œ ˜Jšœœ˜—Jšœœœœ ˜*J˜—J˜JšœœŸ˜=JšœœŸ ˜>šœ5œ˜=Jšœœ˜ J˜4Jšœœ-˜8šœ œœ˜*JšœœŸ˜1Jšœ œ˜J˜J˜J˜—J˜—J˜ Jšœ˜—J˜.J˜J˜—Jšžœœ,œœ™Ršž œ œFœ˜kJšŸE™EJšœ˜J˜Jšœ>˜>Jšœ œ˜,Jšœœ˜Jšœœ˜2J˜J˜š œœ œœœ˜)Jš œœœœ œœ ˜CJšœœ˜J˜—š œœ œœœ˜)Jš œœœœœœ ˜CJšœœ˜J˜—š œœ œœœ˜5Jšœœ œœ˜;Jšœœ ˜J˜—Jšœ2˜2šœœœ˜#Jšœœ˜Jšœœ˜Jšœœ˜J˜Jšœ œ˜"Jšœœ˜šœ˜JšœF˜F—šœ˜˜MJ˜;—Jšœ œœœ˜NJ˜—Jš œ œœœœœ˜?Jšœœœ œ˜DJšœ œœ œ˜IJšœ)œœœ˜iJšœ˜—J˜J˜—Jšœ œœ˜Jšœ œ˜Jšœœ˜J˜š ž œœœœ˜NJ˜*J˜*J˜*Jšœ˜J˜—šžœ œ$œ˜HJšŸŒœŸ™•Jšœœ ˜0J˜J˜—š ž œ œ$œœœ˜dJšŸ+™+Jšœ8˜8J˜—J˜š žœ œ$œ œœ˜eJšŸ+™+Jšœ9˜9J˜—J˜Jš ž œœœ!œœ˜XJšœ œœ˜,šž œœ$œœ˜nJšœœœ˜Jšœ˜šœ œ!œ˜QJ˜"šœ˜Jšœ7˜7Jšœœœ˜Jšœ˜—Jšœ˜ J˜—šœ œ"œ˜RJ˜"šœ˜Jšœ7˜7Jšœœœ˜Jšœ˜—Jšœ˜ J˜—˜ Jšœ œœ˜Jšœœœ˜ šœœ˜Jšœ7œœ˜KJšœœ˜J˜—Jšœ7œœ˜Kšœ œ œœ7˜[Jšœ œœ œ˜H—Jšœœ˜#Jšœœ˜%Jšœ ˜ J˜—J˜-J˜J˜—šž œœ!œœ˜XJšœœ˜J˜J˜J˜J˜Jšœœ)˜4Jšœœœ8˜WJšœœ ˜J˜J˜—š  œœ"œœ˜VJ˜Jšœ*Ÿ'˜QJšœ$˜$J˜J˜—šž œœ"œœ˜WJ˜Jšœ*˜*Jšœ^˜^J˜J˜—šž œœ"œœ˜SJ˜Jšœœ˜J˜ Jšœ)Ÿ'˜PJ™\J™MJ™%J˜&J˜&J˜J˜J˜šœœŸ˜?J˜"J˜—šœ˜J˜J˜J˜J˜J˜5J˜—J˜J˜—šž œœ/Ÿ˜\Jšœ œ ˜Jšœ˜Jšœ˜ Jšœœ˜ šœ ˜ šœœœœ˜6šœ0˜9Jšœ"˜"Jšœ˜——J˜J˜—Jšœœ˜ Jšœ,˜,Jšœœœ$˜5J˜J˜—š ž œœœ%œœ˜PJšŸc™cJšœ œœœ˜Jšœ œœœ˜%Jšœ8˜8Jšœœ ˜Jš œ œœœœ˜AJšœ œœœ ˜š œœœœœ˜'Jšœœœœ ˜7—Jšœœ˜ šœ œ œ˜4Jš œœœ œ œ˜:Jšœœ˜šœœœ˜Jšœœ œœ˜P—šœ˜Jšœœ ˜šœ œœ˜ JšœO˜O—J˜—J˜—Jš œ œœœœ˜8Jšœœ˜#Jšœœ˜#Jš œ œœœœ˜7Jšœœ ˜$Jšœœ ˜$šœœœ<˜QJšœ œ˜Jšœœ˜Jšœœ˜ šœ˜JšœœœŸ ˜SJšœœœŸ˜EJš œ œ œœ œœŸ˜QJšœ˜JšœœœŸ˜JJšœ%Ÿ˜?Jš œœœœœŸ˜MJšœ˜Jšœ˜—J˜—šœœœ˜Jšœ œ˜"Jšœ œ˜"Jšœ œ˜"Jšœ œ˜"Jšœ˜—J™[Jšœ˜Jšœ˜Jšœ˜Jšœ˜š œ œ œœ˜FJšœœ˜J˜4šœ˜Jšœ˜Jšœ˜Jšœœ œœ ˜/Jšœ˜—Jšœ˜—J˜—š ž œœœœ œœ˜[JšŸe™e˜ Jšœœœ˜šœœœ˜;Jšœœœ*˜;Jšœ œ˜Jšœœœ˜CJšœ ˜Jšœ˜ J˜—Jš œœœœœ˜EJšœ#Ÿ˜?Jšœ"˜"Jšœ$˜$Jšœ$˜$Jšœœ œ˜/J˜—J˜-J˜—šž œœœœ˜@JšŸa™a˜ šœœœœ˜?Jšœœœ3˜DJšœ œK˜\J˜—J˜—J˜-J˜—šž œœœ!œ˜CJšŸK™KJšœœ˜"˜ Jšœœ˜Jš œœœœœ˜DJšœ5˜5Jšœœ œ˜*J˜—J˜-J˜—šž œœœ9œ˜WJšŸò™òJ˜Jšœœœ˜Jšœœœ˜JšœJ™JJšœœŸ˜>˜ šœœ˜Jšœœ˜Jšœ˜Jšœ œœ%˜:Jšœœœ˜2—šœ˜Jšœœ˜ Jšœœ˜Jš œœœœœ˜!Jšœœœ ˜4Jšœ6˜6šœœœŸ˜IJšœ˜J˜ Jšœ œœ%˜:Jšœœœœ˜?Jšœœœœ˜Jšœ˜—Jšœ œ˜J˜—J˜—J˜-JšœœŸ˜(J˜-Jšœ œœ˜3J˜—Jšœ˜J˜š ž œœœœ œœ™[JšŸe™eJšœ œ™%JšœŸ™%™ Jšœœœ™Jšœœœ™ Jšœœ œŸ)™NJšœ œœœ™GJšœ œœœ™GJšœœ œ™%Jšœ œœœ™HJšœ œœœ™HJšœœ œ™/J™—J™-J™—J˜J˜—…—I€q