MODULE SmoothShape EXPORTS Main; (* Applies smoothing heuristics to a given ".top" file. Also writes an ".st" file with a movie of the smoothing; a ".plot" file with the evolution of various energies during the smoothing; and a ".gnuplot" file with the "gnuplot" commands to show the latter. Created 1994 by Rober M. Rosi and J. Stolfi. *) IMPORT Thread, Text, Wr, Fmt, FileWr, OSError, Process, ParseParams; IMPORT LR3, LR3Extras, Triang, Heuristics, Energy, MixedEnergy, ParseEnergyParams; IMPORT Random, Debug; IMPORT ScreenPlot; FROM Energy IMPORT Gradient; FROM Triang IMPORT Arc, OrgV, LeftF, Topology, Coords; FROM FileFmt IMPORT WriteComment; FROM Oct IMPORT Sym, Rot, Onext; FROM Stdio IMPORT stderr; TYPE BOOL = BOOLEAN; BOOLS = ARRAY OF BOOLEAN; TYPE Options = RECORD (* Input file name (minus ".top" extension) *) inName: TEXT; (* Output file name prefix *) outName: TEXT; (* Number of configurations to generate *) jitterN: CARDINAL; (* Num of randomization passes *) jitterS: LONGREAL; (* How much to randomize *) flattenN: CARDINAL; (* Num passes of the "flatten" heuristic *) flattenS: LONGREAL; (* Strengh of the "flatten" heuristic *) spreadN: CARDINAL; (* Num passes of the "spread" heuristic *) spreadS: LONGREAL; (* Strengh of the "spread" heuristic *) roundN: CARDINAL; (* Num passes of the "round" heuristic *) roundS: LONGREAL; (* Strengh of the "round" heuristic *) comboN: CARDINAL; (* Num passes of the "combo" heuristic *) comboS: LONGREAL; (* Strengh of the "combo" heuristic *) seed: CARDINAL; (* Random number seed *) normalize: BOOL; (* Keep vertex coordinates normalized *) eFunction: MixedEnergy.T; (* Optional: energy to watch *) writeEvery: CARDINAL; (* When to write to the ".st" file. *) showPasses: BOOL; (* TRUE to display after each pass. *) showSteps: BOOL; (* TRUE to display after each step, and wait click. *) END; VAR vDebug: CARDINAL; (* Vertex to debug, or LAST(CARDINAL) *) VAR window: ScreenPlot.T; VAR showSteps, showPasses: BOOL; PROCEDURE DoIt() = VAR jitterCt: CARDINAL := 0; flattenCt: CARDINAL := 0; spreadCt: CARDINAL := 0; roundCt: CARDINAL := 0; comboCt: CARDINAL := 0; e: LONGREAL; passCt: CARDINAL; (* Pass count *) passType: CARDINAL; <* FATAL Thread.Alerted, Wr.Failure, OSError.E *> BEGIN WITH o = GetOptions(), tc = Triang.Read(o.inName), top = tc.top, c = tc.c^, NV = top.NV, coins = NEW(Random.Default).init(TRUE), g = NEW(REF Gradient, NV)^, exists = NEW(REF BOOLS, NV)^, variable = NEW(REF BOOLS, NV)^, apply = NEW(REF BOOLS, NV)^, plotWr = FileWr.Open(o.outName & ".plot"), stWr = FileWr.Open(o.outName & ".st"), NPasses = o.jitterN + o.flattenN + o.spreadN + o.roundN + o.comboN, runComment = OptionsComment(o) & "\n" & tc.comment, vStart = RandomVertexEnum(top, coins.integer(0, NV-1), coins)^ DO showSteps := o.showSteps; showPasses := o.showPasses; IF o.showSteps OR o.showPasses THEN window := MakeWindow(top, c, 0) END; (* Initialize the generator: *) FOR i := 1 TO o.seed DO EVAL coins.integer() END; (* Write triangles for animation: *) IF o.writeEvery < LAST(CARDINAL) THEN WITH su = FileWr.Open(o.outName & ".su"), ma = FileWr.Open(o.outName & ".ma") DO Triang.WriteRLuisSuMa(su, ma, top, all := FALSE, comment := tc.comment) END END; (* Get non-fixed vertices: *) Triang.GetVariableVertices(top, variable); (* Get existing vertices: *) Triang.GetExistingVertices(top, exists); (* Write energy plot header and "gnuplot" commands: *) IF o.eFunction # NIL THEN o.eFunction.defTop(top); o.eFunction.defVar(variable); WritePlotComment(plotWr, runComment, o.eFunction); WITH gnuWr = FileWr.Open(o.outName & ".gnuplot") DO WriteGNUPlotCommands(gnuWr, o.outName, o.eFunction.term^); Wr.Close(gnuWr) END; END; passCt := 0; passType := 0; LOOP IF o.normalize THEN NormalizeVertices(top, c) END; IF o.eFunction # NIL THEN o.eFunction.eval(c, e, FALSE, g); PlotEnergy( plotWr, passCt, e, passType, o.eFunction.termValue^, o.eFunction.weight^ ); END; IF passCt = 0 OR passCt = NPasses THEN WITH name = o.outName & ARRAY BOOL OF TEXT{"-final", "-initial"}[passCt = 0] DO WriteConfiguration(name, top, c, jitterCt, flattenCt, spreadCt, roundCt, comboCt, runComment) END END; IF NicePass(passCt, NPasses, o.writeEvery) THEN WriteCoords(stWr, c, g, jitterCt, flattenCt, spreadCt, roundCt, comboCt, runComment) END; IF passCt = NPasses THEN EXIT END; (* Choose the heuristic to use next: *) WITH jitterR = Schedule(jitterCt, o.jitterN, 0.0), flattenR = Schedule(flattenCt, o.flattenN, 1.0), spreadR = Schedule(spreadCt, o.spreadN, 0.25), roundR = Schedule(roundCt, o.roundN, 0.75), comboR = Schedule(comboCt, o.comboN, 0.50), minR = MIN(MIN(MIN(jitterR, flattenR), MIN(spreadR, roundR)), comboR), iv0 = (NV-1) - ABS((passCt-1) MOD (2*NV-1) - (NV-1)), v0 = vStart[iv0] DO (* Apply the heuristic: *) FOR i := 0 TO LAST(variable) DO apply[i] := variable[i] AND exists[i] END; IF minR = jitterR THEN JitterVertices(top, c, exists, apply, o.jitterS, coins); INC(jitterCt); passType := 1; ELSIF minR = flattenR THEN FlattenVertices(top, c, exists, apply, o.flattenS, v0, coins); INC(flattenCt); passType := 2; ELSIF minR = spreadR THEN SpreadVertices(top, c, exists, apply, o.spreadS, v0, coins); INC(spreadCt); passType := 3; ELSIF minR = roundR THEN RoundVertices(top, c, exists, apply, o.roundS, v0, coins); INC(roundCt); passType := 4; ELSIF minR = comboR THEN ComboVertices(top, c, exists, apply, o.roundS, v0, coins); INC(comboCt); passType := 5; ELSE <* ASSERT FALSE *> END; INC(passCt); END; END; Wr.Close(plotWr); Wr.Close(stWr) END END DoIt; PROCEDURE Schedule(count, tot: CARDINAL; phase: REAL): REAL = BEGIN IF tot <= 0 THEN RETURN 2.0 ELSE RETURN (FLOAT(count) + phase)/FLOAT(tot) END END Schedule; PROCEDURE NicePass( passCt: CARDINAL; NPasses: CARDINAL; every: CARDINAL; ): BOOL = BEGIN RETURN (every < LAST(CARDINAL)) AND (passCt MOD every = 0 OR passCt = NPasses) END NicePass; PROCEDURE WriteConfiguration( name: TEXT; READONLY top: Topology; READONLY c: Coords; jitterCt, flattenCt, spreadCt, roundCt, comboCt: CARDINAL; comment: TEXT; ) = BEGIN WITH cmts = "Output of SmoothShape after " & Fmt.Int(jitterCt) & " \"jitter\", " & Fmt.Int(flattenCt) & " \"flatten\", " & Fmt.Int(spreadCt) & " \"spread\", " & Fmt.Int(roundCt) & " \"round\", and " & Fmt.Int(comboCt) & " \"combo\" passes." & "\n" & comment DO Triang.Write(name, top, c, cmts) END END WriteConfiguration; PROCEDURE WriteCoords( wr: Wr.T; READONLY c: Coords; READONLY g: Gradient; jitterCt, flattenCt, spreadCt, roundCt, comboCt: CARDINAL; comment: TEXT; ) = BEGIN WITH cmts = "Output of SmoothShape after " & Fmt.Int(jitterCt) & " \"jitter\", " & Fmt.Int(flattenCt) & " \"flatten\", " & Fmt.Int(spreadCt) & " \"spread\", " & Fmt.Int(roundCt) & " \"round\", and " & Fmt.Int(comboCt) & " \"combo\" passes." & "\n" & comment DO Triang.WriteRLuisSt(wr, c, g, comment := cmts) END END WriteCoords; PROCEDURE PlotEnergy( wr: Wr.T; passCt: CARDINAL; e: LONGREAL; passType: CARDINAL; READONLY termValue: ARRAY OF LONGREAL; READONLY weight: ARRAY OF REAL; ) = <* FATAL Thread.Alerted, Wr.Failure *> BEGIN Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad(Fmt.Int(passCt), 8)); Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad(Fmt.LongReal(e, Fmt.Style.Fix, 5), 10)); FOR i := 0 TO LAST(termValue) DO Wr.PutText(wr, " "); WITH t = termValue[i] * FLOAT(weight[i], LONGREAL) DO Wr.PutText(wr, Fmt.Pad(Fmt.LongReal(t, Fmt.Style.Fix, 5), 10)) END; END; Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad(Fmt.Int(passType), 1)); Wr.PutText(wr, "\n"); Wr.Flush(wr); END PlotEnergy; PROCEDURE WritePlotComment(wr: Wr.T; oComment: TEXT; eFunction: MixedEnergy.T) = <* FATAL Thread.Alerted, Wr.Failure *> BEGIN WriteComment(wr, oComment, '#'); WritePlotHeader(wr, eFunction); Wr.Flush(wr); END WritePlotComment; PROCEDURE WritePlotHeader(wr: Wr.T; eFunction: MixedEnergy.T) = <* FATAL Thread.Alerted, Wr.Failure *> BEGIN Wr.PutText(wr, "# h is the code of the heuristic just applied:\n"); Wr.PutText(wr, "# 0=none, 1=jitter, 2=flatten, 3=spread, 4=round, 5=combo)\n"); Wr.PutText(wr, "#"); Wr.PutText(wr, Fmt.Pad("pass", 8)); Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad("energy", 10)); FOR i := 0 TO LAST(eFunction.term^) DO Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad(EnergyTag(eFunction.term[i].name()), 10)); END; Wr.PutText(wr, " "); Wr.PutText(wr, Fmt.Pad("h", 1)); Wr.PutText(wr, "\n"); Wr.Flush(wr); END WritePlotHeader; PROCEDURE WriteGNUPlotCommands( wr: Wr.T; outName: TEXT; READONLY term: ARRAY OF Energy.T; ) = <* FATAL Thread.Alerted, Wr.Failure *> BEGIN Wr.PutText(wr, "set terminal X11\n"); Wr.PutText(wr, "set title \"" & outName & "\"\n"); Wr.PutText(wr, "plot \"" & outName & ".plot\" using 1:2 title \"etot\" with lines, \\\n" ); FOR i := 0 TO LAST(term) DO WITH col = i + 3 DO Wr.PutText(wr, " \"" & outName & ".plot\" using 1:" & Fmt.Int(col) & " title \"" & EnergyTag(term[i].name()) & "\" with linespoints" ); IF i < LAST(term) THEN Wr.PutText(wr, ", \\") END; Wr.PutText(wr, "\n"); END END; Wr.PutText(wr, "pause 300\n"); Wr.PutText(wr, "quit\n"); Wr.Flush(wr); END WriteGNUPlotCommands; PROCEDURE EnergyTag(name: TEXT): TEXT = BEGIN WITH n = Text.FindChar(name, '(') DO IF n = -1 THEN RETURN Text.Sub(name, 0, 8) ELSE RETURN Text.Sub(name, 0, MIN(n, 8)) END END END EnergyTag; PROCEDURE NormalizeVertices( READONLY top: Topology; VAR c: Coords; ) = BEGIN Triang.NormalizeVertexNorms(top, c); IF showPasses OR showSteps THEN Debug.Line("NormalizeVertexNorms"); DisplayConfiguration(c, 0) END END NormalizeVertices; PROCEDURE JitterVertices( READONLY top: Topology; VAR c: Coords; READONLY exists: BOOLS; READONLY apply: BOOLS; strength: LONGREAL; coins: Random.T; ) = VAR R: LONGREAL := Triang.MeanVertexNorm(top, c); BEGIN IF strength = 0.0d0 THEN RETURN END; FOR v := 0 TO LAST(c) DO IF exists[v] AND apply[v] THEN Heuristics.JitterVertex(top.out[v], c, apply, strength, R, coins, v = vDebug); END; END; IF showPasses OR showSteps THEN Debug.Line("JitterVertices"); DisplayConfiguration(c, 0) END END JitterVertices; PROCEDURE FlattenVertices( READONLY top: Topology; VAR c: Coords; READONLY exists: BOOLS; VAR apply: BOOLS; strength: LONGREAL; vStart: CARDINAL; coins: Random.T; ) = BEGIN WITH perm = RandomVertexEnum(top, vStart, coins)^, idealArea = AverageTriangleArea(top, c) DO FOR i := 0 TO top.NV-1 DO WITH v = perm[i] DO IF exists[v] AND apply[v] THEN Heuristics.FlattenVertex( top.out[v], c, apply, strength, idealArea, v = vDebug ); apply[v] := FALSE; IF showSteps THEN Debug.Line("FlattenVertex(" & Fmt.Int(v) & ")"); DisplayNeighborhood(top.out[v], c) END END; END END; END; IF showPasses THEN Debug.Line("FlattenVertices"); DisplayConfiguration(c, vStart) END END FlattenVertices; PROCEDURE AverageTriangleArea( READONLY top: Topology; READONLY c: Coords; ): LONGREAL = CONST FourPi = 4.0d0 * 3.1415926535897932385d0; VAR aSum: LONGREAL := 0.0d0; nt: CARDINAL := 0; BEGIN FOR i := 0 TO top.NF - 1 DO WITH e = top.side[i], face = LeftF(Rot(e)), f = Onext(e), g = Onext(f) DO <* ASSERT face.num = i *> IF face.exists AND Onext(g) = e THEN WITH p = OrgV(Rot(e)).num, q = OrgV(Rot(f)).num, r = OrgV(Rot(g)).num, u = LR3.Sub(c[q], c[p]), v = LR3.Sub(c[r], c[p]), area = 0.5d0 * LR3.Norm(LR3Extras.Cross(u, v)) DO aSum := aSum + area; INC(nt) END END END END; IF nt > 0 THEN RETURN aSum/FLOAT(nt, LONGREAL) ELSE RETURN FourPi/FLOAT(top.NF, LONGREAL) END END AverageTriangleArea; PROCEDURE SpreadVertices( READONLY top: Topology; VAR c: Coords; READONLY exists: BOOLS; VAR apply: BOOLS; strength: LONGREAL; vStart: CARDINAL; coins: Random.T; ) = BEGIN WITH perm = RandomVertexEnum(top, vStart, coins)^ DO FOR i := 0 TO top.NV-1 DO WITH v = perm[i] DO IF exists[v] THEN Heuristics.SpreadVertex(top.out[v], c, apply, strength, coins, v = vDebug); apply[v] := FALSE; UnmarkNeighbors(top.out[v], apply); IF showSteps THEN Debug.Line("SpreadVertex(" & Fmt.Int(v) & ")"); DisplayNeighborhood(top.out[v], c) END END; END END; END; IF showPasses THEN Debug.Line("SpreadVertices"); DisplayConfiguration(c, vStart) END END SpreadVertices; PROCEDURE RoundVertices( READONLY top: Topology; VAR c: Coords; READONLY exists: BOOLS; VAR apply: BOOLS; strength: LONGREAL; vStart: CARDINAL; coins: Random.T; ) = BEGIN WITH perm = RandomVertexEnum(top, vStart, coins)^ DO FOR i := 0 TO top.NV-1 DO WITH v = perm[i] DO IF exists[v] THEN Heuristics.RoundVertex(top.out[v], c, apply, strength, v = vDebug); apply[v] := FALSE; UnmarkNeighbors(top.out[v], apply); IF showSteps THEN Debug.Line("RoundVertex(" & Fmt.Int(v) & ")"); DisplayNeighborhood(top.out[v], c) END END; END END; END; IF showPasses THEN Debug.Line("RoundVertices"); DisplayConfiguration(c, vStart) END END RoundVertices; PROCEDURE ComboVertices( READONLY top: Topology; VAR c: Coords; READONLY exists: BOOLS; VAR apply: BOOLS; strength: LONGREAL; vStart: CARDINAL; coins: Random.T; ) = BEGIN WITH perm = RandomVertexEnum(top, vStart, coins)^, idealArea = AverageTriangleArea(top, c) DO FOR i := 0 TO top.NV-1 DO WITH v = perm[i] DO IF exists[v] THEN Heuristics.RoundVertex(top.out[v], c, apply, strength, v = vDebug); Heuristics.SpreadVertex(top.out[v], c, apply, strength, coins, v = vDebug); Heuristics.FlattenVertex( top.out[v], c, apply, strength, idealArea, v = vDebug ); apply[v] := FALSE; UnmarkNeighbors(top.out[v], apply); IF showSteps THEN Debug.Line("ComboVertex(" & Fmt.Int(v) & ")"); DisplayNeighborhood(top.out[v], c) END END; END END; END; IF showPasses THEN Debug.Line("ComboVertices"); DisplayConfiguration(c, vStart) END END ComboVertices; PROCEDURE UnmarkNeighbors(a: Arc; VAR mark: BOOLS) = VAR b: Arc := a; BEGIN REPEAT WITH vx = OrgV(Sym(b)), v = vx.num DO mark[v] := FALSE END; b := Onext(b) UNTIL b = a END UnmarkNeighbors; PROCEDURE RandomVertexEnum( READONLY top:Topology; vStart: CARDINAL; coins: Random.T; ): REF ARRAY OF CARDINAL = (* Returns a list of the vertices reachable from arc "vStart", in a randomized hybrid between BFS and DFS enumeration. *) VAR nDone, nSeen: CARDINAL; BEGIN WITH NV = top.NV, rv = NEW(REF ARRAY OF CARDINAL, NV), v = rv^, seen = NEW(REF BOOLS, NV)^ DO FOR w := 0 TO LAST(seen) DO seen[w] := FALSE END; v[0] := vStart; nSeen := 1; seen[vStart] := TRUE; nDone := 0; WHILE nDone < nSeen DO WITH i = coins.integer(nDone, MIN(nDone + 1, nSeen - 1)) DO <* ASSERT nDone <= i AND i < nSeen *> VAR t := v[nDone]; BEGIN v[nDone] := v[i]; v[i] := t; END; VAR a: Arc := top.out[v[nDone]]; b: Arc := a; BEGIN REPEAT WITH wx = OrgV(Sym(b)), w = wx.num DO IF NOT seen[w] THEN v[nSeen] := w; INC(nSeen); seen[w] := TRUE END; END; b := Onext(b) UNTIL b = a; END; INC(nDone) END END; RETURN rv END END RandomVertexEnum; PROCEDURE MakeWindow( READONLY top: Topology; READONLY c: Coords; vRoot: CARDINAL; ): ScreenPlot.T = BEGIN WITH NV = top.NV, w = NEW(ScreenPlot.T).init(NV+1) DO w.pause(); w.setCoords(0, c); w.setCoord(NV, c[vRoot]); FOR i := 0 TO top.NE-1 DO WITH e = top.edge[i], u = OrgV(e).num, v = OrgV(Sym(e)).num DO w.segment(u, v, width := 0.0) END END; w.point(NV, size := 5.0); w.resume(); EVAL w.waitKey(); RETURN w END END MakeWindow; PROCEDURE DisplayConfiguration(READONLY c: Coords; v: CARDINAL) = BEGIN window.pause(); WITH NV = NUMBER(c) DO window.setCoords(0, c); window.setCoord(NV, c[v]); END; window.resume(); IF showSteps THEN EVAL window.waitKey() ELSE window.waitDone() END; END DisplayConfiguration; PROCEDURE DisplayNeighborhood(a: Arc; READONLY c: Coords) = VAR b: Arc := a; BEGIN window.pause(); WITH NV = NUMBER(c), u = OrgV(a).num DO window.setCoord(u, c[u]); window.setCoord(NV, c[u]); END; REPEAT WITH v = OrgV(Sym(b)).num DO window.setCoord(v, c[v]) END; b := Onext(b) UNTIL b = a; window.resume(); EVAL window.waitKey(); END DisplayNeighborhood; PROCEDURE GetOptions (): Options = <* FATAL Thread.Alerted, Wr.Failure *> VAR o: Options; BEGIN WITH pp = NEW(ParseParams.T).init(stderr) DO TRY pp.getKeyword("-inName"); o.inName := pp.getNext(); pp.getKeyword("-outName"); o.outName := pp.getNext(); IF pp.keywordPresent("-seed") THEN o.seed := pp.getNextInt(0, 999) ELSE o.seed := 0 END; IF pp.keywordPresent("-jitter") THEN o.jitterN := pp.getNextInt(0, 1000000); o.jitterS := pp.getNextLongReal(0.0d0, 1.0d0); ELSE o.jitterN := 0; o.jitterS := 0.0d0 END; IF pp.keywordPresent("-flatten") THEN o.flattenN := pp.getNextInt(0, 1000000); o.flattenS := pp.getNextLongReal(0.0d0, 1.0d0); ELSE o.flattenN := 0; o.flattenS := 0.0d0 END; IF pp.keywordPresent("-spread") THEN o.spreadN := pp.getNextInt(0, 1000000); o.spreadS := pp.getNextLongReal(0.0d0, 1.0d0); ELSE o.spreadN := 0; o.spreadS := 0.0d0 END; IF pp.keywordPresent("-round") THEN o.roundN := pp.getNextInt(0, 1000000); o.roundS := pp.getNextLongReal(0.0d0, 1.0d0); ELSE o.roundN := 0; o.roundS := 0.0d0 END; IF pp.keywordPresent("-combo") THEN o.comboN := pp.getNextInt(0, 1000000); o.comboS := pp.getNextLongReal(0.0d0, 1.0d0); ELSE o.comboN := 0; o.comboS := 0.0d0 END; IF o.jitterN + o.flattenN + o.spreadN + o.roundN + o.comboN = 0 THEN pp.error("no heuristic specified"); END; o.normalize := pp.keywordPresent("-normalize"); o.eFunction := ParseEnergyParams.Parse(pp); o.showPasses := pp.keywordPresent("-showPasses"); o.showSteps := pp.keywordPresent("-showSteps"); IF pp.keywordPresent("-writeEvery") THEN o.writeEvery := pp.getNextInt(0, 1000000) ELSIF pp.keywordPresent("-writeAll") THEN o.writeEvery := 1 ELSE o.writeEvery := LAST(CARDINAL) END; IF pp.keywordPresent("-vDebug") THEN vDebug := pp.getNextInt(0, 1000000) ELSE vDebug := LAST(CARDINAL) END; pp.finish(); EXCEPT | ParseParams.Error => Wr.PutText(stderr, "Usage: SmoothShape \\\n"); Wr.PutText(stderr, " -inName NAME -outName NAME \\\n"); Wr.PutText(stderr, " [ -jitter NPASSES STRENGTH ] \\\n"); Wr.PutText(stderr, " [ -flatten NPASSES STRENGTH ] \\\n"); Wr.PutText(stderr, " [ -spread NPASSES STRENGTH ] \\\n"); Wr.PutText(stderr, " [ -round NPASSES STRENGTH ] \\\n"); Wr.PutText(stderr, " [ -combo NPASSES STRENGTH ] \\\n"); Wr.PutText(stderr, " [ -seed NUM ] [ -normalize ] \\\n"); Wr.PutText(stderr, " [ -showSteps ] [ -showPasses ] \\\n"); Wr.PutText(stderr, " [ -writeEvery NPASSES | -writeAll ] \\\n"); Wr.PutText(stderr, " [ -vDebug VERTEX ]\\\n"); Wr.PutText(stderr, ParseEnergyParams.Help); Wr.PutText(stderr, "\n"); Process.Exit (1); END END; RETURN o END GetOptions; PROCEDURE OptionsComment(READONLY o: Options): TEXT = PROCEDURE FI(x: INTEGER): TEXT = BEGIN RETURN " " & Fmt.Int(x) END FI; PROCEDURE FL(x: LONGREAL; prec: CARDINAL): TEXT = BEGIN RETURN " " & Fmt.LongReal(x, Fmt.Style.Fix, prec := prec) END FL; BEGIN WITH cmts = " SmoothShape " & " \\\n" & " -jitter" & FI(o.jitterN) & FL(o.jitterS, 3) & " \\\n" & " -flatten" & FI(o.flattenN) & FL(o.flattenS, 3) & " \\\n" & " -spread" & FI(o.spreadN) & FL(o.spreadS, 3) & " \\\n" & " -round" & FI(o.roundN) & FL(o.roundS, 3) & " \\\n" & " -combo" & FI(o.comboN) & FL(o.comboS, 3) & " \\\n" & " -seed" & FI(o.seed) & " \\\n" & ARRAY BOOL OF TEXT{"", " -normalize"}[o.normalize] & "\n\n" & " energy function: " & o.eFunction.name() DO RETURN cmts END END OptionsComment; BEGIN DoIt() END SmoothShape.