% 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
