1
2
3
4
5
6
7
8
9
10
11
12
13
14\startcomponent ontargetmetapost
15
16\environment ontargetstyle
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 (inbpoints) 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
93
94
95
96
97
98
99
100
101
102
103
104
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 (cnofcircles)[darkgreen,darkred] ;
113 endfor ;
114
115 currentpicture := currentpicture xsized .5TextWidth ;
116\stopMPcode
117\stopbuffer
118
119\startplacefigure
120 [title={Fabrices 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 := agitatea(P, S, nzero, fn, tzero, ft) ;
143 eofill P
144 withcolor transparent(1,4NbCircles,col) ;
145 draw P
146 withpen pencircle scaled 0.1
147 transparent(1,4NbCircles,.90[black,col]) ;
148endfor ;
149\stoptyping
150
151The first we noticed is that the graphics processes faster when double mode is
152used: we gain 4050\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,tottim) 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 socalled 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 its
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, LineHeight2) ;
320 draw textext("\strut\tttf q is closed (light)") shifted (center q) shifted (0,LineHeight2) ;
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 (32,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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
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,tottim) of thepath)
402 endfor if cycle thepath : -- cycle fi
403 enddef ;
404\stopMPdefinitions
405\stopbuffer
406
407\typebuffer[option=tex] \getbuffer
408
409If you dont 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,tottim) 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) ;
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) ;
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) ;
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. Lets 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 builtin \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 dont 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
654 defaultscale := .05;
655
656
657
658
659
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 (c6.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
681 elseif method == 0 :
682 draw textext("no draw") ;
683 else :
684 eofill P withcolor transparent(1,4NbCircles,col) ;
685 draw P withpen pencircle scaled 0.1 transparent(1,4NbCircles,.90[black,col]) ;
686
687
688 fi ;
689 endfor ;
690 enddef ;
691\stopMPdefinitions
692
693We show the average runtime for three runs. Here we dont 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[Tri2Tri2Tr]
702 \BC agitatea \NC 776.26 \BC agitateea \NC 291.99 \BC agitatefa \NC 10.82 \NC \NR
703 \BC agitateb \NC 276.43 \BC agitateeb \NC 76.06 \BC agitatefb \NC 2.55 \NC \NR
704 \BC agitatec \NC 259.89 \BC agitateec \NC 77.27 \BC agitatefc \NC 2.17 \NC \NR
705 \BC agitated \NC 260.41 \BC agitateed \NC 18.67 \BC \NC \NC \NR
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
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756 |