summaryrefslogtreecommitdiff
path: root/doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex
diff options
context:
space:
mode:
Diffstat (limited to 'doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex')
-rw-r--r--doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex755
1 files changed, 755 insertions, 0 deletions
diff --git a/doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex b/doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex
new file mode 100644
index 000000000..521b90b63
--- /dev/null
+++ b/doc/context/sources/general/manuals/ontarget/ontarget-metapost.tex
@@ -0,0 +1,755 @@
+% language=us runpath=texruns:manuals/ontarget
+
+% Musical timestamp: while adding this to the engine and I listened to many John
+% Medeski (Martin and Wood) concert videos, many several times ... it kept me in
+% the flow!
+%
+% This wrapup was written with Dave Matthews Band - Seek Up - LIVE, 12/2/2018 in
+% the background, as I like these live acts.
+%
+% This is just a summary from my point of view. A real article is written by
+% Mikael Sundqvist who also explains how arctime and its companions actually
+% work.
+
+\startcomponent ontarget-metapost
+
+\environment ontarget-style
+
+\startchapter[title={To the point}]
+
+In the 2022 \NTG\ \MAPS\ 53 there is a visual very attractive article about
+generative graphics with \METAPOST\ by Fabrice Larribe. These graphics actually
+use very little \METAPOST\ code that use randomized paths and points and the
+magic is in getting the parameters right. This means that one has to process them
+a lot to figure out what looks best. Here is an example of such a graphic
+definition. I will show more variants so a rendering happens later on.
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_a(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel, rlength ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ R := for i=1 upto nbpoints:
+ point (i/nbpoints) along R
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+I will not explain the working of this because there is the article. Instead, I
+will focus on something that came up when the \MAPS\ was prepared: performance.
+Not only are these graphics large (which is no real problem) but they also take a
+while to render (which is something that does matter when one wants to find the
+best a parameters). For the first few variants we keep the same names of
+variables as in the article.
+
+\startbuffer[final:definition]
+\startMPdefinitions
+ vardef agitator(expr pth, iterations, points, pointfactor, noise, noisefactor) =
+ save currentpath, currentpoints, currentnoise ; path currentpath ;
+ currentpath := pth ;
+ currentpoints := points ;
+ currentnoise := noise ;
+ for step = 1 upto iterations :
+ currentpath := arcpointlist currentpoints of currentpath ;
+ if currentnoise <> 0 :
+ currentpath :=
+ for i within currentpath :
+ pathpoint
+ randomized currentnoise
+ ..
+ endfor
+ cycle ;
+ fi
+ currentnoise := currentnoise * noisefactor ;
+ currentpoints := currentpoints * pointfactor ;
+ endfor ;
+ currentpath
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\startbuffer[final:graphic]
+\startMPcode
+ path pth ;
+ nofcircles := 15 ; iterations := 10 ;
+ points := 10 ; pointfactor := 1.3 ;
+ noise := 5 ; noisefactor := 0.8 ;
+
+ nofcircles := 5 ; iterations := 10 ;
+ points := 5 ; pointfactor := 1.3 ;
+
+% for c = nofcircles downto 1 :
+% pth := fullcircle scaled (c * 6.5) scaled 3 ;
+% points := floor(arclength(pth) * 0.5) ;
+% pth := agitator(pth, iterations, points, pointfactor, noise, noisefactor) ;
+% eofill pth
+% withcolor darkred
+% withtransparency(1,4/nofcircles) ;
+% draw pth
+% withpen pencircle scaled 0.1
+% withtransparency(1,4/nofcircles) ;
+% endfor ;
+
+% currentpicture := currentpicture xsized TextWidth ;
+
+ for c = nofcircles downto 1 :
+ pth := fullcircle scaled (c * 6.5) scaled 3 ;
+ points := floor(arclength(pth) * 0.5) ;
+ pth := agitator(pth, iterations, points, pointfactor, noise, noisefactor) ;
+ draw pth
+ withpen pencircle scaled 1
+ withcolor (c/nofcircles)[darkgreen,darkred] ;
+ endfor ;
+
+ currentpicture := currentpicture xsized .5TextWidth ;
+\stopMPcode
+\stopbuffer
+
+\startplacefigure
+ [title={Fabrice's agitated circles, with reduced properties to keep this file small (see source).},
+ reference=fig:agitated]
+ \getbuffer[final:definition,final:graphic]
+\stopplacefigure
+
+In \in {figure} [fig:agitated] we show the (kind of) graphic that we are dealing
+with. Such an agitator is used in a loop so that we agitate multiple circles,
+where we go from large to small with for instance 4868, 4539, 4221, 3892, 3564,
+3245, 2917, 2599, 2270, 1941, 1623, 1294, 966, 647 and 319 points. The article
+uses a definition like below for the graphic where you can see the agitator being
+applied to each of the circles.
+
+\starttyping[option=mp,color=]
+path P ; numeric NbCircles, S, nzero, fn, tzero, ft ;
+
+randomseed := 10 ;
+defaultscale := .05 ;
+
+NbCircles := 15 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+
+for c = NbCircles downto 1 :
+ P := fullcircle scaled (c*6.5) scaled 3 ;
+ P := agitate_a(P, S, nzero, fn, tzero, ft) ;
+ eofill P
+ withcolor transparent(1,4/NbCircles,col) ;
+ draw P
+ withpen pencircle scaled 0.1
+ transparent(1,4/NbCircles,.90[black,col]) ;
+endfor ;
+\stoptyping
+
+The first we noticed is that the graphics processes faster when double mode is
+used: we gain 40--50\percent\ and the reason for this is that modern processors
+are very good at handling doubles while \METAPOST\ in scaled mode has to do a lot
+of juggling with pseudo fractions. In the timings shown later we leave that
+improvement out. Also, because of this observation \CONTEXT\ \LMTX\ now defaults
+its \METAPOST\ instances to method double.
+
+When I stared at the agitator code I noticed that the \type {along} macro was
+used. That macro returns a point at given percentage along a path. In order to do
+that the macro calculates the length of the path and then locates that point. The
+primitive operations involved are \type {arclength}, \type {arctime of} and \type
+{point of} and each these takes some time to complete. A first improvement is to
+inline the \type {along} and hoist the length calculation outside the loop.
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_b(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel, rlength ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ rlength := (arclength R) / nbpoints;
+ R := for i=1 upto nbpoints:
+ (point (arctime (i * rlength) of R) of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+There is not that much that we can improve here but because Mikael Sundqvist and
+I had just extended \METAPOST\ with some intersection improvements, it made sense
+to see what we could do in the engine. In the next variant the \type {arcpoint}
+combines \type {arctime of} and \type {point of}. The reason this is much
+faster is that we are already on the right spot when we got the time, and we save
+a sequential \type {point of} lookup, something that takes more time when paths
+are longer.
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_c(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel, rlength ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ rlength := (arclength R) / nbpoints;
+ R := for i=1 upto nbpoints:
+ (arcpoint (i * rlength) of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+At that stage we wondered if we could come up with a primitive like \typ
+{intersectiontimelist} for these points; here a list refers to a path in which we
+collect the points. Now, as with the intersection primitives, \METAPOST\ loops
+over the segments of a path and works within such a segment. That is why the
+following variant has an explicit start at point zero: we can now use offsets
+(discrete points).
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_d(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel, rlength ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ rlength := (arclength R) / nbpoints;
+ R := for i=1 upto nbpoints:
+ (arcpoint (0, i * rlength) of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+During an evening zooming Mikael and I figured out, by closely looking at the
+source, how the arc functions work and how we could indeed come up with a list
+primitive. The main issue was to use the right information. Mikael sat down to
+make a pure \METAPOST\ variant and I hacked the engine. Mikael came up with a
+first variant similar to the following, where we use a new primitive \typ
+{subarclength}.
+
+\startbuffer
+\startMPdefinitions
+ vardef arcpoints_a(expr thepath, cnt) =
+ save len, seg, tot, tim, stp, acc ;
+ numeric len ; len := length thepath ;
+ numeric seg ; seg := 0 ;
+ numeric tot ; tot := 0 ;
+ numeric tim ; tim := 0 ;
+ %
+ numeric acc[] ; acc[0] := 0 ;
+ for i = 1 upto len:
+ acc[i] := acc[i-1] + subarclength (i-1,i) of thepath ;
+ endfor;
+ %
+ numeric stp ; stp := acc[len] / cnt;
+ %
+ point 0 of thepath
+ for tot = stp step stp until acc[len] :
+ hide(
+ forever :
+ exitif ((tim < tot) and (tot < acc[seg+1])) ;
+ seg := seg + 1 ;
+ tim := acc[seg] ;
+ endfor ;
+ )
+ -- (arcpoint (seg,tot-tim) of thepath)
+ endfor if cycle thepath : -- cycle fi
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+Getting points of a path is somewhat complicated by the fact that the length of a
+closed path is different from that of an open path even if they have the same
+number of so-called knots. Internally a path is always a closed loop. That way,
+when \METAPOST\ runs over a path, it can easily access the first point when it's
+at the end, something that is handy when that point has to be taken into account.
+Therefore, the end condition of a loop over a path is the arrival at the
+beginning. In the next graphic we show a bit how these first (zero) and last
+points are located. One reason why the previous macros start at point one and not
+at zero is that \type {arclength} can overflow due to the randomly growing path
+otherwise.
+
+\startlinecorrection
+\startMPcode
+ path p ; p := ((0,0) -- (1,0) -- (1,1) -- (0,1)) xyscaled (20EmWidth,5LineHeight) ;
+ path q ; q := p -- cycle ;
+
+ draw p withpen pencircle scaled 4mm withcolor .2white ;
+ draw q withpen pencircle scaled 2mm withcolor .6white ;
+
+ draw point length(p) of p withpen pencircle scaled 6mm withcolor green;
+ draw point length(q) of q withpen pencircle scaled 6mm withcolor blue ;
+
+ draw point 0 of p withpen pencircle scaled 4mm withcolor red ;
+ draw point 0 of q withpen pencircle scaled 2mm withcolor yellow ;
+
+ draw textext.lft("\strut\tttf point length of p") shifted (point length(p) of p) shifted (-6mm,0) ;
+ draw textext.lft("\strut\tttf point length of q") shifted (point length(q) of q) shifted (-6mm,LineHeight) ;
+ draw textext.lft("\strut\tttf point 0 of p") shifted (point 0 of p) shifted (-6mm,0) ;
+ draw textext.lft("\strut\tttf point 0 of q") shifted (point 0 of q) shifted (-6mm,-LineHeight) ;
+
+ draw textext("\strut\tttf p is open (dark)") shifted (center p) shifted (0, LineHeight/2) ;
+ draw textext("\strut\tttf q is closed (light)") shifted (center q) shifted (0,-LineHeight/2) ;
+\stopMPcode
+\stoplinecorrection
+
+The difference between starting at zero or one for a cycle is show below, we get
+more and more points!
+
+\startlinecorrection
+\startMPcode
+ path p ; p := fullsquare ; path q ;
+
+ for i=1 upto 100 :
+ q := for j=1 upto length(p) : (point j of p) -- endfor cycle ;
+ p := q ;
+ endfor ;
+
+ draw p scaled 2cm withpen pencircle scaled 1mm withcolor "darkred" ;
+ drawpointlabels p scaled 2cm ;
+
+ path p ; p := fullsquare shifted (3/2,0) ; path q ;
+
+ for i=1 upto 100 :
+ q := for j=0 upto length(p) : (point j of p) -- endfor cycle ;
+ p := q ;
+ endfor ;
+
+ draw p scaled 2cm withpen pencircle scaled 1mm withcolor "darkred" ;
+ drawpointlabels p scaled 2cm ;
+\stopMPcode
+\stoplinecorrection
+
+% point of
+%
+% if (mp_left_type(p) == mp_endpoint_knot) {
+% set_number_to_unity(n);
+% number_negate(n);
+% } else {
+% set_number_to_zero(n);
+% }
+% do {
+% p = mp_next_knot(p);
+% number_add(n, unity_t);
+% } while (p != cur_exp_knot);
+
+% length:
+%
+% mp_knot p = cur_exp_knot;
+% int l = mp_left_type(p) == mp_endpoint_knot ? -1 : 0;
+% do {
+% p = mp_next_knot(p);
+% ++l;
+% } while (p != cur_exp_knot);
+% set_number_from_int(*n, l);
+
+In the next variants we will not loop over points but step to the \type
+{arclength}. Watch the new \type {subarclength} primitive that starts at an
+offset. This is much faster than taking a \type {subpath of}. We can move the
+accumulator loop into the main loop:
+
+\startbuffer
+\startMPdefinitions
+ vardef arcpoints_b(expr thepath, cnt) =
+ save len, aln, seg, tot, tim, stp, acc ;
+ numeric len ; len := length thepath ;
+ numeric aln ; aln := arclength thepath ;
+ numeric seg ; seg := 0 ;
+ numeric tot ; tot := 0 ;
+ numeric tim ; tim := 0 ;
+ numeric stp ; stp := aln / cnt;
+ numeric acc ; acc := subarclength (0,1) of thepath ;
+ %
+ point 0 of thepath
+ for tot = stp step stp until aln :
+ hide(
+ forever :
+ exitif tot < acc ;
+ seg := seg + 1 ;
+ tim := acc ;
+ acc := acc + subarclength (seg,seg+1) of thepath ;
+ endfor ;
+ )
+ -- (arcpoint (seg,tot-tim) of thepath)
+ endfor if cycle thepath : -- cycle fi
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+If you don't like the \type {hide} the next variant also works okay:
+
+\startbuffer
+\startMPdefinitions
+ vardef mfun_arc_point(text tot)(text thepath) =
+ forever :
+ exitif tot < acc ;
+ seg := seg + 1 ;
+ tim := acc ;
+ acc := acc + subarclength (seg,seg+1) of thepath ;
+ endfor ;
+ (arcpoint (seg,tot-tim) of thepath)
+ enddef ;
+
+ vardef arcpoints_c(expr thepath, cnt) =
+ save len, aln, seg, tot, tim, stp, acc ;
+ numeric len ; len := length thepath ;
+ numeric aln ; aln := arclength thepath ;
+ numeric seg ; seg := 0 ;
+ numeric tot ; tot := 0 ;
+ numeric tim ; tim := 0 ;
+ numeric stp ; stp := aln / cnt;
+ numeric acc ; acc := subarclength (0,1) of thepath ;
+ %
+ point 0 of thepath
+ for tot = stp step stp until aln :
+ -- mfun_arc_point(tot)(thepath)
+ endfor if cycle thepath : -- cycle fi
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+This got applied in three test agitators
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_e_a(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ R := arcpoints_a(R, nbpoints) ; % original Mikael
+ R := for i=0 upto length R:
+ (point i of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+
+ vardef agitate_e_b(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ R := arcpoints_b(R, nbpoints) ; % merged Mikael
+ R := for i=0 upto length R:
+ (point i of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+
+ vardef agitate_e_c(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ R := arcpoints_c(R, nbpoints) ; % split Mikael
+ R := for i=0 upto length R:
+ (point i of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+The new engine primitive shortens these agitators:
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_e_d(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints, noiselevel ;
+ path R ; nbpoints := n ; noiselevel := t ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ noiselevel := noiselevel * ft ;
+ R := arcpointlist nbpoints of R;
+ R := for i=0 upto length R:
+ (point i of R)
+ randomized noiselevel
+ ..
+ endfor cycle ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+So are we done? Did we get rid of all bottlenecks? The answer is no! We still
+loop over the list in order to randomize the points. For each point we start at
+the beginning of the list. Let's first rewrite the agitator a little:
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_f_a(expr pth, iterations, points, pointfactor, noise, noisefactor) =
+ save currentpath, currentpoints, currentnoise ; path currentpath ;
+ currentpath := pth ;
+ currentpoints := points ;
+ currentnoise := noise ;
+ for step = 1 upto iterations :
+ currentpath := arcpointlist currentpoints of currentpath ;
+ currentnoise := currentnoise * noisefactor ;
+ currentpoints := currentpoints * pointfactor ;
+ if currentnoise <> 0 :
+ currentpath :=
+ for i = 0 upto length currentpath:
+ (point i of currentpath) randomized currentnoise ..
+ endfor
+ cycle ;
+ fi
+ endfor ;
+ currentpath
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+One of the \LUAMETAFUN\ extensions is a fast path iterator. In the next variant
+the \type {inpath} macro sets up an iterator (regular loop) with the length as
+final value. In the process the given path gets passed to \LUA\ where we can
+access it as array. The \type {pointof} macro (again a \LUA\ call) injects a
+pair. You will be surprised that even with passing the path to \LUA\ and calling
+out to \LUA\ to inject the pair this is way faster than the built|-|in \type
+{point of}.
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_f_b(expr pth, iterations, points, pointfactor, noise, noisefactor) =
+ save currentpath, currentpoints, currentnoise ; path currentpath ;
+ currentpath := pth ;
+ currentpoints := points ;
+ currentnoise := noise ;
+ for step = 1 upto iterations :
+ currentnoise := currentnoise * noisefactor ;
+ currentpoints := currentpoints * pointfactor ;
+ currentpath := arcpointlist currentpoints of currentpath ;
+ if currentnoise <> 0 :
+ currentpath :=
+ for i inpath currentpath :
+ (pointof i) randomized currentnoise ..
+ endfor
+ cycle ;
+ fi
+ endfor ;
+ currentpath
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+It was tempting to see if a more native solution pays of. One problem there is
+that a path is not really suitable for that as we currently don't have a data
+type that represents a point. Okay, actually we sort of have because we can use
+the transform record that has six points but that is something I will look into
+later (it just got added to the todo list).
+
+The \typ {i within pth} iterator is no conceptual beauty but does the job. Just
+keep in mind that it is just means for this kind of applications: run over a path
+point by point. The \type {i} has the current point number. Because we run over a
+path following the links we only run forward.
+
+\startbuffer
+\startMPdefinitions
+ vardef agitate_f_c(expr pth, iterations, points, pointfactor, noise, noisefactor) =
+ save currentpath, currentpoints, currentnoise ; path currentpath ;
+ currentpath := pth ;
+ currentpoints := points ;
+ currentnoise := noise ;
+ for step = 1 upto iterations :
+ currentnoise := currentnoise * noisefactor ;
+ currentpoints := currentpoints * pointfactor ;
+ currentpath := arcpointlist currentpoints of currentpath ;
+ if currentnoise <> 0 :
+ currentpath :=
+ for i within currentpath :
+ pathpoint
+ randomized currentnoise
+ ..
+ endfor
+ cycle ;
+ fi
+ endfor ;
+ currentpath
+ enddef ;
+\stopMPdefinitions
+\stopbuffer
+
+\typebuffer[option=tex] \getbuffer
+
+Any primitive solution more complex than this, like first creating a fast access
+data structure, of having a double linked list, or using some iterator larger
+than a simple numeric is very likely to have no gain over the super fast \LUA\
+variant.
+
+\startMPdefinitions
+ vardef agitate_x(expr thepath, S, n, fn, t, ft) =
+ save R, nbpoints ;
+ path R ; nbpoints := n ;
+ R := thepath ;
+ for s=1 upto S :
+ nbpoints := nbpoints * fn ;
+ R := arcpointlist nbpoints of R ;
+ endfor ;
+ R
+ enddef ;
+\stopMPdefinitions
+
+\startMPdefinitions
+ def TestMe (expr method, col, justtest) =
+ path P ;
+
+ randomseed := 10;
+ % maxknotpool := 20000; % default is 1000
+ defaultscale := .05;
+
+ % NbCircles := 1 ; S := 1 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+ % NbCircles := 2 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+ % NbCircles := 10 ; S := 5 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+ % NbCircles := 10 ; S := 10 ; nzero := 20 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+ NbCircles := 15 ; S := 10 ; nzero := 10 ; fn := 1.3 ; tzero := 5 ; ft := 0.8 ;
+
+ for c = NbCircles downto 1 :
+ P := fullcircle scaled (c*6.5) scaled 3 ;
+ nzero := floor(arclength(P)*0.5);
+ if method == 0 : P := agitate_x (P, S, nzero, fn, tzero, ft) ;
+ elseif method == 1 : P := agitate_a (P, S, nzero, fn, tzero, ft) ;
+ elseif method == 2 : P := agitate_b (P, S, nzero, fn, tzero, ft) ;
+ elseif method == 3 : P := agitate_c (P, S, nzero, fn, tzero, ft) ;
+ elseif method == 4 : P := agitate_d (P, S, nzero, fn, tzero, ft) ;
+ elseif method == 51 : P := agitate_e_a(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 52 : P := agitate_e_b(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 53 : P := agitate_e_c(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 54 : P := agitate_e_d(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 61 : P := agitate_f_a(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 62 : P := agitate_f_b(P, S, nzero, fn, tzero, ft) ;
+ elseif method == 63 : P := agitate_f_c(P, S, nzero, fn, tzero, ft) ;
+ else :
+ fi ;
+ if justtest :
+ % do nothing
+ elseif method == 0 :
+ draw textext("no draw") ;
+ else :
+ eofill P withcolor transparent(1,4/NbCircles,col) ;
+ draw P withpen pencircle scaled 0.1 transparent(1,4/NbCircles,.90[black,col]) ;
+ % drawpoints P withpen pencircle scaled 0.2 withcolor white ;
+ % drawpointlabels P withcolor white;
+ fi ;
+ endfor ;
+ enddef ;
+\stopMPdefinitions
+
+We show the average runtime for three runs. Here we don't render the paths, which
+takes about one second, including conversion to \PDF. Of course measurements like
+this can change a bit over time. To these times you need to add about a second
+for the draw and fill operations as well as conversion to a \PDF\ stream with
+transparencies. The improvement in runtime makes it possible to use agitators like
+this at runtime especially because normally one will not use such (combinations
+of) large paths.
+
+\starttabulate[|T|r|i2T|r|i2T|r|]
+ \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
+ \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
+ \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
+ \BC agitate_d \NC 260.41 \BC agitate_e_d \NC 18.67 \BC \NC \NC \NR % 4 54
+\stoptabulate
+
+The final version of the agitator is slightly different because it depends if we
+start at zero or one but gives similar results and adapt the noise before or
+after the loop.
+
+\typebuffer[final:definition][option=tex]
+
+We use a similar example as in the mentioned article but coded a bit differently:
+
+\typebuffer[final:graphic][option=tex]
+
+For Mikael and me, who both like \METAPOST, it was a nice distraction from
+working months on extending math in \LUAMETATEX, but it also opens up the
+possibilities to do more with rendering (math) functions and graphics, so in the
+end we get paid back anyway.
+
+\stopchapter
+
+\stopcomponent
+
+% see agitate-002.tex
+
+% \startluacode
+% local t = {
+% 0, -- also initialization
+% 1, 2, 3, 4,
+% 51, 52, 53, 54,
+% 61, 62, 63,
+% }
+% for i=1,#t do
+% context.writestatus("TEST RUN",t[i])
+% context("\\testfeatureonce{3}{\\startMPcalculation TestMe (%i, blue, true) ; \\stopMPcalculation}",t[i])
+% end
+% \stopluacode
+
+% \testfeatureonce{1}{\startMPpage TestMe ( 0, \MPcolor{darkblue} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe ( 1, \MPcolor{darkblue} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe ( 2, \MPcolor{darkblue} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe ( 3, \MPcolor{darkred} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe ( 4, \MPcolor{darkgreen} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (51, \MPcolor{darkred} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (52, \MPcolor{darkgreen} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (53, \MPcolor{darkblue} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (54, \MPcolor{darkblue} , false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (61, \MPcolor{darkyellow}, false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (62, \MPcolor{darkyellow}, false) ; \stopMPpage}
+% \testfeatureonce{1}{\startMPpage TestMe (63, \MPcolor{darkyellow}, false) ; \stopMPpage}
+
+% \stoptext