ontarget-metapost.tex /size: 27 Kb    last modification: 2024-01-16 10:21
1% language=us runpath=texruns:manuals/ontarget
2
3% Musical timestamp: while adding this to the engine and I listened to many John
4% Medeski (Martin and Wood) concert videos, many several times ... it kept me in
5% the flow!
6%
7% This wrapup was written with Dave Matthews Band - Seek Up - LIVE, 12/2/2018 in
8% the background, as I like these live acts.
9%
10% This is just a summary from my point of view. A real article is written by
11% Mikael Sundqvist who also explains how arctime and its companions actually
12% work.
13
14\startcomponent ontarget-metapost
15
16\environment ontarget-style
17
18\startchapter[title={To the point}]
19
20In the 2022 \NTG\ \MAPS\ 53 there is a visual very attractive article about
21generative graphics with \METAPOST\ by Fabrice Larribe. These graphics actually
22use very little \METAPOST\ code that use randomized paths and points and the
23magic is in getting the parameters right. This means that one has to process them
24a lot to figure out what looks best. Here is an example of such a graphic
25definition. I will show more variants so a rendering happens later on.
26
27\startbuffer
28\startMPdefinitions
29    vardef agitate_a(expr thepath, S, n, fn, t, ft) =
30        save R, nbpoints, noiselevel, rlength ;
31        path R ; nbpoints := n ; noiselevel := t ;
32        R := thepath ;
33        for s=1 upto S :
34            nbpoints := nbpoints * fn ;
35            noiselevel := noiselevel * ft ;
36            R := for i=1 upto nbpoints:
37                point (i/nbpoints) along R
38                    randomized noiselevel
39                ..
40            endfor cycle ;
41        endfor ;
42        R
43    enddef ;
44\stopMPdefinitions
45\stopbuffer
46
47\typebuffer[option=tex] \getbuffer
48
49I will not explain the working of this because there is the article. Instead, I
50will focus on something that came up when the \MAPS\ was prepared: performance.
51Not only are these graphics large (which is no real problem) but they also take a
52while to render (which is something that does matter when one wants to find the
53best a parameters). For the first few variants we keep the same names of
54variables as in the article.
55
56\startbuffer[final:definition]
57\startMPdefinitions
58    vardef agitator(expr pth, iterations, points, pointfactor, noise, noisefactor) =
59        save currentpath, currentpoints, currentnoise ; path currentpath ;
60        currentpath   := pth ;
61        currentpoints := points ;
62        currentnoise  := noise ;
63        for step = 1 upto iterations :
64            currentpath := arcpointlist currentpoints of currentpath ;
65            if currentnoise <> 0 :
66                currentpath :=
67                    for i within currentpath :
68                        pathpoint
69                        randomized currentnoise
70                        ..
71                    endfor
72                cycle ;
73            fi
74            currentnoise  := currentnoise  * noisefactor ;
75            currentpoints := currentpoints * pointfactor ;
76        endfor ;
77        currentpath
78    enddef ;
79\stopMPdefinitions
80\stopbuffer
81
82\startbuffer[final:graphic]
83\startMPcode
84  path pth ;
85  nofcircles  := 15 ; iterations  := 10 ;
86  points      := 10 ; pointfactor := 1.3 ;
87  noise       :=  5 ; noisefactor := 0.8 ;
88
89  nofcircles  :=  5 ; iterations  := 10 ;
90  points      :=  5 ; pointfactor := 1.3 ;
91
92% for c = nofcircles downto 1 :
93%   pth    := fullcircle scaled (c * 6.5) scaled 3 ;
94%   points := floor(arclength(pth) * 0.5) ;
95%   pth    := agitator(pth, iterations, points, pointfactor, noise, noisefactor) ;
96%   eofill pth
97%     withcolor darkred
98%     withtransparency(1,4/nofcircles) ;
99%   draw pth
100%     withpen pencircle scaled 0.1
101%     withtransparency(1,4/nofcircles) ;
102% endfor ;
103
104% currentpicture := currentpicture xsized TextWidth ;
105
106  for c = nofcircles downto 1 :
107    pth    := fullcircle scaled (c * 6.5) scaled 3 ;
108    points := floor(arclength(pth) * 0.5) ;
109    pth    := agitator(pth, iterations, points, pointfactor, noise, noisefactor) ;
110    draw pth
111      withpen pencircle scaled 1
112      withcolor (c/nofcircles)[darkgreen,darkred] ;
113  endfor ;
114
115  currentpicture := currentpicture xsized .5TextWidth ;
116\stopMPcode
117\stopbuffer
118
119\startplacefigure
120  [title={Fabrice's agitated circles, with reduced properties to keep this file small (see source).},
121   reference=fig:agitated]
122    \getbuffer[final:definition,final:graphic]
123\stopplacefigure
124
125In \in {figure} [fig:agitated] we show the (kind of) graphic that we are dealing
126with. Such an agitator is used in a loop so that we agitate multiple circles,
127where we go from large to small with for instance 4868, 4539, 4221, 3892, 3564,
1283245, 2917, 2599, 2270, 1941, 1623, 1294, 966, 647 and 319 points. The article
129uses a definition like below for the graphic where you can see the agitator being
130applied to each of the circles.
131
132\starttyping[option=mp,color=]
133path P ; numeric NbCircles, S, nzero, fn, tzero, ft ;
134
135randomseed   := 10 ;
136defaultscale := .05 ;
137
138NbCircles := 15 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
139
140for c = NbCircles downto 1 :
141    P := fullcircle scaled (c*6.5) scaled 3 ;
142    P := agitate_a(P, S, nzero, fn, tzero, ft) ;
143    eofill P
144        withcolor transparent(1,4/NbCircles,col) ;
145    draw P
146        withpen pencircle scaled 0.1
147        transparent(1,4/NbCircles,.90[black,col]) ;
148endfor ;
149\stoptyping
150
151The first we noticed is that the graphics processes faster when double mode is
152used: we gain 40--50\percent\ and the reason for this is that modern processors
153are very good at handling doubles while \METAPOST\ in scaled mode has to do a lot
154of juggling with pseudo fractions. In the timings shown later we leave that
155improvement out. Also, because of this observation \CONTEXT\ \LMTX\ now defaults
156its \METAPOST\ instances to method double.
157
158When I stared at the agitator code I noticed that the \type {along} macro was
159used. That macro returns a point at given percentage along a path. In order to do
160that the macro calculates the length of the path and then locates that point. The
161primitive operations involved are \type {arclength}, \type {arctime of} and \type
162{point of} and each these takes some time to complete. A first improvement is to
163inline the \type {along} and hoist the length calculation outside the loop.
164
165\startbuffer
166\startMPdefinitions
167    vardef agitate_b(expr thepath, S, n, fn, t, ft) =
168        save R, nbpoints, noiselevel, rlength ;
169        path R ; nbpoints := n ; noiselevel := t ;
170        R := thepath ;
171        for s=1 upto S :
172            nbpoints := nbpoints * fn ;
173            noiselevel := noiselevel * ft ;
174            rlength := (arclength R) / nbpoints;
175            R := for i=1 upto nbpoints:
176                (point (arctime (i * rlength) of R) of R)
177                    randomized noiselevel
178                ..
179            endfor cycle ;
180        endfor ;
181        R
182    enddef ;
183\stopMPdefinitions
184\stopbuffer
185
186\typebuffer[option=tex] \getbuffer
187
188There is not that much that we can improve here but because Mikael Sundqvist and
189I had just extended \METAPOST\ with some intersection improvements, it made sense
190to see what we could do in the engine. In the next variant the \type {arcpoint}
191combines \type {arctime of} and \type {point of}. The reason this is much
192faster is that we are already on the right spot when we got the time, and we save
193a sequential \type {point of} lookup, something that takes more time when paths
194are longer.
195
196\startbuffer
197\startMPdefinitions
198    vardef agitate_c(expr thepath, S, n, fn, t, ft) =
199        save R, nbpoints, noiselevel, rlength ;
200        path R ; nbpoints := n ; noiselevel := t ;
201        R := thepath ;
202        for s=1 upto S :
203            nbpoints := nbpoints * fn ;
204            noiselevel := noiselevel * ft ;
205            rlength := (arclength R) / nbpoints;
206            R := for i=1 upto nbpoints:
207                (arcpoint (i * rlength) of R)
208                    randomized noiselevel
209                ..
210            endfor cycle ;
211        endfor ;
212        R
213    enddef ;
214\stopMPdefinitions
215\stopbuffer
216
217\typebuffer[option=tex] \getbuffer
218
219At that stage we wondered if we could come up with a primitive like \typ
220{intersectiontimelist} for these points; here a list refers to a path in which we
221collect the points. Now, as with the intersection primitives, \METAPOST\ loops
222over the segments of a path and works within such a segment. That is why the
223following variant has an explicit start at point zero: we can now use offsets
224(discrete points).
225
226\startbuffer
227\startMPdefinitions
228    vardef agitate_d(expr thepath, S, n, fn, t, ft) =
229        save R, nbpoints, noiselevel, rlength ;
230        path R ; nbpoints := n ; noiselevel := t ;
231        R := thepath ;
232        for s=1 upto S :
233            nbpoints := nbpoints * fn ;
234            noiselevel := noiselevel * ft ;
235            rlength := (arclength R) / nbpoints;
236            R := for i=1 upto nbpoints:
237                (arcpoint (0, i * rlength) of R)
238                    randomized noiselevel
239                ..
240            endfor cycle ;
241        endfor ;
242        R
243    enddef ;
244\stopMPdefinitions
245\stopbuffer
246
247\typebuffer[option=tex] \getbuffer
248
249During an evening zooming Mikael and I figured out, by closely looking at the
250source, how the arc functions work and how we could indeed come up with a list
251primitive. The main issue was to use the right information. Mikael sat down to
252make a pure \METAPOST\ variant and I hacked the engine. Mikael came up with a
253first variant similar to the following, where we use a new primitive \typ
254{subarclength}.
255
256\startbuffer
257\startMPdefinitions
258    vardef arcpoints_a(expr thepath, cnt) =
259        save len, seg, tot, tim, stp, acc ;
260        numeric len ; len := length thepath ;
261        numeric seg ; seg := 0 ;
262        numeric tot ; tot := 0 ;
263        numeric tim ; tim := 0 ;
264        %
265        numeric acc[] ; acc[0] := 0 ;
266        for i = 1 upto len:
267            acc[i] := acc[i-1] + subarclength (i-1,i) of thepath ;
268        endfor;
269        %
270        numeric stp ; stp := acc[len] / cnt;
271        %
272        point 0 of thepath
273        for tot = stp step stp until acc[len] :
274            hide(
275                forever :
276                    exitif ((tim < tot) and (tot < acc[seg+1])) ;
277                    seg := seg + 1 ;
278                    tim := acc[seg] ;
279                endfor ;
280            )
281            -- (arcpoint (seg,tot-tim) of thepath)
282        endfor if cycle thepath : -- cycle fi
283    enddef ;
284\stopMPdefinitions
285\stopbuffer
286
287\typebuffer[option=tex] \getbuffer
288
289Getting points of a path is somewhat complicated by the fact that the length of a
290closed path is different from that of an open path even if they have the same
291number of so-called knots. Internally a path is always a closed loop. That way,
292when \METAPOST\ runs over a path, it can easily access the first point when it's
293at the end, something that is handy when that point has to be taken into account.
294Therefore, the end condition of a loop over a path is the arrival at the
295beginning. In the next graphic we show a bit how these first (zero) and last
296points are located. One reason why the previous macros start at point one and not
297at zero is that \type {arclength} can overflow due to the randomly growing path
298otherwise.
299
300\startlinecorrection
301\startMPcode
302    path p ; p := ((0,0) -- (1,0) -- (1,1) -- (0,1)) xyscaled (20EmWidth,5LineHeight) ;
303    path q ; q := p -- cycle ;
304
305    draw p withpen pencircle scaled 4mm withcolor .2white ;
306    draw q withpen pencircle scaled 2mm withcolor .6white ;
307
308    draw point length(p) of p withpen pencircle scaled 6mm withcolor green;
309    draw point length(q) of q withpen pencircle scaled 6mm withcolor blue ;
310
311    draw point 0         of p withpen pencircle scaled 4mm withcolor red ;
312    draw point 0         of q withpen pencircle scaled 2mm withcolor yellow ;
313
314    draw textext.lft("\strut\tttf point length of p") shifted (point length(p) of p) shifted (-6mm,0) ;
315    draw textext.lft("\strut\tttf point length of q") shifted (point length(q) of q) shifted (-6mm,LineHeight) ;
316    draw textext.lft("\strut\tttf point 0 of p")      shifted (point 0         of p) shifted (-6mm,0) ;
317    draw textext.lft("\strut\tttf point 0 of q")      shifted (point 0         of q) shifted (-6mm,-LineHeight) ;
318
319    draw textext("\strut\tttf p is open (dark)")    shifted (center p) shifted (0, LineHeight/2) ;
320    draw textext("\strut\tttf q is closed (light)") shifted (center q) shifted (0,-LineHeight/2) ;
321\stopMPcode
322\stoplinecorrection
323
324The difference between starting at zero or one for a cycle is show below, we get
325more and more points!
326
327\startlinecorrection
328\startMPcode
329    path p ; p := fullsquare ; path q ;
330
331    for i=1 upto 100 :
332        q := for j=1 upto length(p) : (point j of p) -- endfor cycle ;
333        p := q ;
334    endfor ;
335
336    draw            p scaled 2cm withpen pencircle scaled 1mm withcolor "darkred" ;
337    drawpointlabels p scaled 2cm ;
338
339    path p ; p := fullsquare shifted (3/2,0) ; path q ;
340
341    for i=1 upto 100 :
342        q := for j=0 upto length(p) : (point j of p) -- endfor cycle ;
343        p := q ;
344    endfor ;
345
346    draw            p scaled 2cm withpen pencircle scaled 1mm withcolor "darkred" ;
347    drawpointlabels p scaled 2cm ;
348\stopMPcode
349\stoplinecorrection
350
351% point of
352%
353%     if (mp_left_type(p) == mp_endpoint_knot) {
354%         set_number_to_unity(n);
355%         number_negate(n);
356%     } else {
357%         set_number_to_zero(n);
358%     }
359%     do {
360%         p = mp_next_knot(p);
361%         number_add(n, unity_t);
362%     } while (p != cur_exp_knot);
363
364% length:
365%
366%     mp_knot p = cur_exp_knot;
367%     int l = mp_left_type(p) == mp_endpoint_knot ? -1 : 0;
368%     do {
369%         p = mp_next_knot(p);
370%         ++l;
371%     } while (p != cur_exp_knot);
372%     set_number_from_int(*n, l);
373
374In the next variants we will not loop over points but step to the \type
375{arclength}. Watch the new \type {subarclength} primitive that starts at an
376offset. This is much faster than taking a \type {subpath of}. We can move the
377accumulator loop into the main loop:
378
379\startbuffer
380\startMPdefinitions
381    vardef arcpoints_b(expr thepath, cnt) =
382        save len, aln, seg, tot, tim, stp, acc ;
383        numeric len ; len := length thepath ;
384        numeric aln ; aln := arclength thepath ;
385        numeric seg ; seg := 0 ;
386        numeric tot ; tot := 0 ;
387        numeric tim ; tim := 0 ;
388        numeric stp ; stp := aln / cnt;
389        numeric acc ; acc := subarclength (0,1) of thepath ;
390        %
391        point 0 of thepath
392        for tot = stp step stp until aln :
393            hide(
394                forever :
395                    exitif tot < acc ;
396                    seg := seg + 1 ;
397                    tim := acc ;
398                    acc := acc + subarclength (seg,seg+1) of thepath ;
399                endfor ;
400            )
401            -- (arcpoint (seg,tot-tim) of thepath)
402        endfor if cycle thepath : -- cycle fi
403    enddef ;
404\stopMPdefinitions
405\stopbuffer
406
407\typebuffer[option=tex] \getbuffer
408
409If you don't like the \type {hide} the next variant also works okay:
410
411\startbuffer
412\startMPdefinitions
413    vardef mfun_arc_point(text tot)(text thepath) =
414        forever :
415            exitif tot < acc ;
416            seg := seg + 1 ;
417            tim := acc ;
418            acc := acc + subarclength (seg,seg+1) of thepath ;
419        endfor ;
420        (arcpoint (seg,tot-tim) of thepath)
421    enddef ;
422
423    vardef arcpoints_c(expr thepath, cnt) =
424        save len, aln, seg, tot, tim, stp, acc ;
425        numeric len ; len := length thepath ;
426        numeric aln ; aln := arclength thepath ;
427        numeric seg ; seg := 0 ;
428        numeric tot ; tot := 0 ;
429        numeric tim ; tim := 0 ;
430        numeric stp ; stp := aln / cnt;
431        numeric acc ; acc := subarclength (0,1) of thepath ;
432        %
433        point 0 of thepath
434        for tot = stp step stp until aln :
435            -- mfun_arc_point(tot)(thepath)
436        endfor if cycle thepath : -- cycle fi
437    enddef ;
438\stopMPdefinitions
439\stopbuffer
440
441\typebuffer[option=tex] \getbuffer
442
443This got applied in three test agitators
444
445\startbuffer
446\startMPdefinitions
447    vardef agitate_e_a(expr thepath, S, n, fn, t, ft) =
448        save R, nbpoints, noiselevel ;
449        path R ; nbpoints := n ; noiselevel := t ;
450        R := thepath ;
451        for s=1 upto S :
452            nbpoints := nbpoints * fn ;
453            noiselevel := noiselevel * ft ;
454            R := arcpoints_a(R, nbpoints) ; % original Mikael
455            R := for i=0 upto length R:
456                (point i of R)
457                    randomized noiselevel
458                ..
459            endfor cycle ;
460        endfor ;
461        R
462    enddef ;
463
464    vardef agitate_e_b(expr thepath, S, n, fn, t, ft) =
465        save R, nbpoints, noiselevel ;
466        path R ; nbpoints := n ; noiselevel := t ;
467        R := thepath ;
468        for s=1 upto S :
469            nbpoints := nbpoints * fn ;
470            noiselevel := noiselevel * ft ;
471            R := arcpoints_b(R, nbpoints) ; % merged Mikael
472            R := for i=0 upto length R:
473                (point i of R)
474                    randomized noiselevel
475                ..
476            endfor cycle ;
477        endfor ;
478        R
479    enddef ;
480
481    vardef agitate_e_c(expr thepath, S, n, fn, t, ft) =
482        save R, nbpoints, noiselevel ;
483        path R ; nbpoints := n ; noiselevel := t ;
484        R := thepath ;
485        for s=1 upto S :
486            nbpoints := nbpoints * fn ;
487            noiselevel := noiselevel * ft ;
488            R := arcpoints_c(R, nbpoints) ; % split Mikael
489            R := for i=0 upto length R:
490                (point i of R)
491                    randomized noiselevel
492                ..
493            endfor cycle ;
494        endfor ;
495        R
496    enddef ;
497\stopMPdefinitions
498\stopbuffer
499
500\typebuffer[option=tex] \getbuffer
501
502The new engine primitive shortens these agitators:
503
504\startbuffer
505\startMPdefinitions
506    vardef agitate_e_d(expr thepath, S, n, fn, t, ft) =
507        save R, nbpoints, noiselevel ;
508        path R ; nbpoints := n ; noiselevel := t ;
509        R := thepath ;
510        for s=1 upto S :
511            nbpoints := nbpoints * fn ;
512            noiselevel := noiselevel * ft ;
513            R := arcpointlist nbpoints of R;
514            R := for i=0 upto length R:
515                (point i of R)
516                    randomized noiselevel
517                ..
518            endfor cycle ;
519        endfor ;
520        R
521    enddef ;
522\stopMPdefinitions
523\stopbuffer
524
525\typebuffer[option=tex] \getbuffer
526
527So are we done? Did we get rid of all bottlenecks? The answer is no! We still
528loop over the list in order to randomize the points. For each point we start at
529the beginning of the list. Let's first rewrite the agitator a little:
530
531\startbuffer
532\startMPdefinitions
533    vardef agitate_f_a(expr pth, iterations, points, pointfactor, noise, noisefactor) =
534        save currentpath, currentpoints, currentnoise ; path currentpath ;
535        currentpath   := pth ;
536        currentpoints := points ;
537        currentnoise  := noise ;
538        for step = 1 upto iterations :
539            currentpath   := arcpointlist currentpoints of currentpath ;
540            currentnoise  := currentnoise  * noisefactor ;
541            currentpoints := currentpoints * pointfactor ;
542            if currentnoise <> 0 :
543                currentpath :=
544                    for i = 0 upto length currentpath:
545                        (point i of currentpath) randomized currentnoise ..
546                    endfor
547                cycle ;
548            fi
549        endfor ;
550        currentpath
551    enddef ;
552\stopMPdefinitions
553\stopbuffer
554
555\typebuffer[option=tex] \getbuffer
556
557One of the \LUAMETAFUN\ extensions is a fast path iterator. In the next variant
558the \type {inpath} macro sets up an iterator (regular loop) with the length as
559final value. In the process the given path gets passed to \LUA\ where we can
560access it as array. The \type {pointof} macro (again a \LUA\ call) injects a
561pair. You will be surprised that even with passing the path to \LUA\ and calling
562out to \LUA\ to inject the pair this is way faster than the built|-|in \type
563{point of}.
564
565\startbuffer
566\startMPdefinitions
567    vardef agitate_f_b(expr pth, iterations, points, pointfactor, noise, noisefactor) =
568        save currentpath, currentpoints, currentnoise ; path currentpath ;
569        currentpath   := pth ;
570        currentpoints := points ;
571        currentnoise  := noise ;
572        for step = 1 upto iterations :
573            currentnoise  := currentnoise  * noisefactor ;
574            currentpoints := currentpoints * pointfactor ;
575            currentpath   := arcpointlist currentpoints of currentpath ;
576            if currentnoise <> 0 :
577                currentpath :=
578                    for i inpath currentpath :
579                        (pointof i) randomized currentnoise ..
580                    endfor
581                cycle ;
582            fi
583        endfor ;
584        currentpath
585    enddef ;
586\stopMPdefinitions
587\stopbuffer
588
589\typebuffer[option=tex] \getbuffer
590
591It was tempting to see if a more native solution pays of. One problem there is
592that a path is not really suitable for that as we currently don't have a data
593type that represents a point. Okay, actually we sort of have because we can use
594the transform record that has six points but that is something I will look into
595later (it just got added to the todo list).
596
597The \typ {i within pth} iterator is no conceptual beauty but does the job. Just
598keep in mind that it is just means for this kind of applications: run over a path
599point by point. The \type {i} has the current point number. Because we run over a
600path following the links we only run forward.
601
602\startbuffer
603\startMPdefinitions
604    vardef agitate_f_c(expr pth, iterations, points, pointfactor, noise, noisefactor) =
605        save currentpath, currentpoints, currentnoise ; path currentpath ;
606        currentpath   := pth ;
607        currentpoints := points ;
608        currentnoise  := noise ;
609        for step = 1 upto iterations :
610            currentnoise  := currentnoise  * noisefactor ;
611            currentpoints := currentpoints * pointfactor ;
612            currentpath   := arcpointlist currentpoints of currentpath ;
613            if currentnoise <> 0 :
614                currentpath :=
615                    for i within currentpath :
616                        pathpoint
617                        randomized currentnoise
618                        ..
619                    endfor
620                cycle ;
621            fi
622        endfor ;
623        currentpath
624    enddef ;
625\stopMPdefinitions
626\stopbuffer
627
628\typebuffer[option=tex] \getbuffer
629
630Any primitive solution more complex than this, like first creating a fast access
631data structure, of having a double linked list, or using some iterator larger
632than a simple numeric is very likely to have no gain over the super fast \LUA\
633variant.
634
635\startMPdefinitions
636    vardef agitate_x(expr thepath, S, n, fn, t, ft) =
637        save R, nbpoints ;
638        path R ; nbpoints := n ;
639        R := thepath ;
640        for s=1 upto S :
641            nbpoints := nbpoints * fn ;
642            R := arcpointlist nbpoints of R ;
643        endfor ;
644        R
645    enddef ;
646\stopMPdefinitions
647
648\startMPdefinitions
649    def TestMe (expr method, col, justtest) =
650        path P ;
651
652        randomseed   := 10;
653      % maxknotpool  := 20000; % default is 1000
654        defaultscale := .05;
655
656      % NbCircles :=  1 ; S :=  1 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
657      % NbCircles :=  2 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
658      % NbCircles := 10 ; S :=  5 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
659      % NbCircles := 10 ; S := 10 ; nzero := 20 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
660        NbCircles := 15 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
661
662        for c = NbCircles downto 1 :
663            P := fullcircle scaled (c*6.5) scaled 3 ;
664            nzero := floor(arclength(P)*0.5);
665                if method ==  0 : P := agitate_x  (P, S, nzero, fn, tzero, ft) ;
666            elseif method ==  1 : P := agitate_a  (P, S, nzero, fn, tzero, ft) ;
667            elseif method ==  2 : P := agitate_b  (P, S, nzero, fn, tzero, ft) ;
668            elseif method ==  3 : P := agitate_c  (P, S, nzero, fn, tzero, ft) ;
669            elseif method ==  4 : P := agitate_d  (P, S, nzero, fn, tzero, ft) ;
670            elseif method == 51 : P := agitate_e_a(P, S, nzero, fn, tzero, ft) ;
671            elseif method == 52 : P := agitate_e_b(P, S, nzero, fn, tzero, ft) ;
672            elseif method == 53 : P := agitate_e_c(P, S, nzero, fn, tzero, ft) ;
673            elseif method == 54 : P := agitate_e_d(P, S, nzero, fn, tzero, ft) ;
674            elseif method == 61 : P := agitate_f_a(P, S, nzero, fn, tzero, ft) ;
675            elseif method == 62 : P := agitate_f_b(P, S, nzero, fn, tzero, ft) ;
676            elseif method == 63 : P := agitate_f_c(P, S, nzero, fn, tzero, ft) ;
677            else :
678            fi ;
679            if justtest :
680                % do nothing
681            elseif method == 0 :
682                draw textext("no draw") ;
683            else :
684                eofill          P                              withcolor transparent(1,4/NbCircles,col) ;
685                draw            P withpen pencircle scaled 0.1           transparent(1,4/NbCircles,.90[black,col]) ;
686             %  drawpoints      P withpen pencircle scaled 0.2 withcolor white ;
687             %  drawpointlabels P                              withcolor white;
688            fi ;
689        endfor ;
690    enddef ;
691\stopMPdefinitions
692
693We show the average runtime for three runs. Here we don't render the paths, which
694takes about one second, including conversion to \PDF. Of course measurements like
695this can change a bit over time. To these times you need to add about a second
696for the draw and fill operations as well as conversion to a \PDF\ stream with
697transparencies. The improvement in runtime makes it possible to use agitators like
698this at runtime especially because normally one will not use such (combinations
699of) large paths.
700
701\starttabulate[|T|r|i2T|r|i2T|r|]
702    \BC agitate_a \NC 776.26 \BC agitate_e_a \NC 291.99 \BC agitate_f_a \NC 10.82 \NC \NR %  1  51 61
703    \BC agitate_b \NC 276.43 \BC agitate_e_b \NC  76.06 \BC agitate_f_b \NC  2.55 \NC \NR %  2  52 62
704    \BC agitate_c \NC 259.89 \BC agitate_e_c \NC  77.27 \BC agitate_f_c \NC  2.17 \NC \NR %  3  53 63
705    \BC agitate_d \NC 260.41 \BC agitate_e_d \NC  18.67 \BC             \NC       \NC \NR %  4  54
706\stoptabulate
707
708The final version of the agitator is slightly different because it depends if we
709start at zero or one but gives similar results and adapt the noise before or
710after the loop.
711
712\typebuffer[final:definition][option=tex]
713
714We use a similar example as in the mentioned article but coded a bit differently:
715
716\typebuffer[final:graphic][option=tex]
717
718For Mikael and me, who both like \METAPOST, it was a nice distraction from
719working months on extending math in \LUAMETATEX, but it also opens up the
720possibilities to do more with rendering (math) functions and graphics, so in the
721end we get paid back anyway.
722
723\stopchapter
724
725\stopcomponent
726
727% see agitate-002.tex
728
729% \startluacode
730%     local t = {
731%         0, -- also initialization
732%         1, 2, 3, 4,
733%         51, 52, 53, 54,
734%         61, 62, 63,
735%     }
736%     for i=1,#t do
737%         context.writestatus("TEST RUN",t[i])
738%         context("\\testfeatureonce{3}{\\startMPcalculation TestMe (%i, blue, true) ; \\stopMPcalculation}",t[i])
739%     end
740% \stopluacode
741
742% \testfeatureonce{1}{\startMPpage TestMe ( 0, \MPcolor{darkblue}  , false) ; \stopMPpage}
743% \testfeatureonce{1}{\startMPpage TestMe ( 1, \MPcolor{darkblue}  , false) ; \stopMPpage}
744% \testfeatureonce{1}{\startMPpage TestMe ( 2, \MPcolor{darkblue}  , false) ; \stopMPpage}
745% \testfeatureonce{1}{\startMPpage TestMe ( 3, \MPcolor{darkred}   , false) ; \stopMPpage}
746% \testfeatureonce{1}{\startMPpage TestMe ( 4, \MPcolor{darkgreen} , false) ; \stopMPpage}
747% \testfeatureonce{1}{\startMPpage TestMe (51, \MPcolor{darkred}   , false) ; \stopMPpage}
748% \testfeatureonce{1}{\startMPpage TestMe (52, \MPcolor{darkgreen} , false) ; \stopMPpage}
749% \testfeatureonce{1}{\startMPpage TestMe (53, \MPcolor{darkblue}  , false) ; \stopMPpage}
750% \testfeatureonce{1}{\startMPpage TestMe (54, \MPcolor{darkblue}  , false) ; \stopMPpage}
751% \testfeatureonce{1}{\startMPpage TestMe (61, \MPcolor{darkyellow}, false) ; \stopMPpage}
752% \testfeatureonce{1}{\startMPpage TestMe (62, \MPcolor{darkyellow}, false) ; \stopMPpage}
753% \testfeatureonce{1}{\startMPpage TestMe (63, \MPcolor{darkyellow}, false) ; \stopMPpage}
754
755% \stoptext
756