Friday, December 28, 2012

Two animation scripts - and why Movimentum is still necessary

Here is a small tool chain to create animations for mechanical explanations. And here is an explanation of the compiler that creates ImageMagick (or other) calls from animation scripts.

Is there still a need to complete the Movimentum project (which is getting more and more complex)?

Well, there is! Let me explain this with two very simple mechanisms,
  • the "Scotch yoke mechanism" and
  • the simple crank (mechanism)
which also serve as examples of animation scripts in my "tiny animation language." Here are diagrams of both:

Scotch-yoke mechanism

Crank mechanism

Getting these mechanisms to turn their wheels will also be a short tutorial about how to write animation scripts.

Scotch-yoke is easy


Let us start is the animation for Scotch-yoke. Here is the result of what we'll develop:



The "model" for this gear is easy. We overlay three pieces:
  • the fixed blue block (file Block.PNG),
  • the turning red wheel (file Wheel.PNG), and
  • the orange slider (file Slider.PNG).

The blue block is always at the same place:

  Block.PNG -geometry +0+0 -composite

The wheel is simply rotated by some angle !A (whose actual values will be set later). The center of the rotation must be found by inspecting the image with some graphics viewer that can show coordinates:

  ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite

Finally, we have to move the slider by some x distance. This distance must vary like a sine wave. My little language can create sine waves only for vector variables, therefore we define such a variable %S, but use only its x component:

  Slider.PNG -geometry +%S:x+0 -composite

Overlaying these parts yields the following call to convert—the result is written to f_*.PNG:

$ ... convert -size 824x528 xc:white \
  Block.PNG -geometry +0+0 -composite \
  ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite \
  Slider.PNG -geometry +%S:x+0 -composite \
  f_!T.PNG

When I created this animation the first time, I used 825x529 as size. The libx264 codec, however, complained that width and height were not even, so I changed them to 824x528—and it worked!
What about the variables' values?

Increasing !A is easy. We choose a rotation of 5 degrees per frame, starting at 0:

  !A 0 5 360

Moving %S is a little awkward. Here is one(!) way of doing it: %S starts at x coordinate 0 (because this is the initial shift of the slider), and has the same y as the rotation center of the wheel. Moreoever, we want to rotate it about some point which we call %C. Therefore, we have

  %S 0 0 %C 5

Now, we need to place %C. As the %S anchor of the slider is at its left end (the red circle), %C must be the rotation center of that end. Therefore, its coordinates are not (619,0)—the center of the wheel—, but (R,0), where R is the pixel radius of the wheel. Measuring this in some graphics program, I found R=118, and so the coordinates for %C are:

  %C 118 0 0 0

How many frames should we produce? 360 divided by 5 is 72, so 72 frames make one revolution. Two rotations would be fine, so we create 150 frames. Thus, our animation script is:

  !T 10000 1 .

  !A 0 5 360
  %C 118 0 0 0
  %S 0 0 %C 5
  $ 150 convert -size 824x528 xc:white \
    Block.PNG -geometry +0+0 -composite \
    ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite \
    Slider.PNG -geometry +%S:x+0 -composite \
    e\f_!T.png

... and this is exactly what produces the animation shown above!

Actually, I like to start animations without any movement; and then accelerate the mechanism. Here is a script that does this:

!T 20000 1 .

# We start with 12 frames with increments of 0 degrees, i.e., nothing moves:
!A 0 0 360
%C 118 0 0 0
%S 0 0 %C 0
$ 12 convert -size 824x528 xc:white \
  Block.PNG -geometry +0+0 -composite \
  ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite \
  Slider.PNG -geometry +%S:x+0 -composite \
  e\f_!T.png

# Next, we set the increment to 1 degrees for 4 frames ...
!A . 1 .
%S . . %C 1
$ 4

# ... then to 2 ...
!A . 2 .
%S . . %C 2
$ 4

# ... then to 3 ...
!A . 3 .
%S . . %C 3
$ 4

# ... then to 4:
!A . 4 .
%S . . %C 4
$ 4

# Then, we run with increments of 5 degrees for 150 frames.
!A . 5 .
%S . . %C 5
$ 150

# For deceleration, we reduce the increments to 3 for 4 frames ...
!A . 3 .
%S . . %C 3
$ 4

# ... then to 1 ...
!A . 1 .
%S . . %C 1
$ 4

# Finally, we end with 12 frames without movement:
!A . 0 .
%S . . %C 0
$ 12

And here is the result:



So far so good. But what about the crank?

The crank is difficult


We place the block and the wheel as before:

  Block.PNG -geometry +0+0 -composite
  ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite

The driving rod must turn at its right end; and moreover, it must change its angle. In my script language, we need something like

  ( Rod.PNG -distort SRT "619,265,!R" ) -geometry +%S:x+%S:y -composite

where !R is the rod's angle. We can reuse %S from the Scotch-yoke mechanism ... I won't explain why, just try it out. Leaving !R at zero for the moment, we get a crank with "flying rod":

  !T 30000 1 .

  !R 0 0 360
  !A 0 5 360
  %C 118 0 0 0
  %S 0 0 %C 5
  $ 50 convert -size 824x528 xc:white \
    Block.PNG -geometry +0+0 -composite \
    ( Wheel.PNG -distort SRT "619,265,!A" ) -geometry +0+0 -composite \
    ( Rod.PNG -distort SRT "501,265,!R" ) -geometry +%S:x+%S:y -composite \
    e\f_!T.png

But what can we do about the rod's angle? Well—nothing. The "piston motion equations" for the crank mechanism are not simple linear or rotational formulas. Here is the equation for x in the "angle domain":


I could add functionality for this equation (and maybe I will, if I create the third animation with a crank). But of course there will be another linkage which still cannot be computed; and if I add that, there will be one more, and so on. Movimentum, on the other hand, as a general equation solver should solve almost all equations for mechanical linkages, without any need to patch the animation compiler.

What can we do right now? We can interpolate. Let us divide the circle into eight segments, each of which has 72/8 = 9 frames. At the end of the first segment, the right end of the rod will be 0.707r above the center line. The angle in the last frame must be asin(0.707 · r / d), where r is the radius of the crank and d the length of the rod. In our case, we find an angle of asin(0.707 · 118 / 447) = 10.75°. If we distribute this evenly over the 9 frames, we find an angle of 1.19° per frame. So we try the following:

!T 40000 1 .

!R 0 -1.19 360
!A 0 5 360
%C 118 0 0 0
%S 0 0 %C 5
$ 9

# stop at end
!R . 0 .
!A . 0 .
%S . . %C 0
$ 10




That looks almost acceptable. Ok, the left end goes a little bit up and down—but we'll leave it at that.

At the end of the next octant, the angle is asin(118 / 447) = 15.3°. We have to distribute 15.3° – 10.75° over 9 frames, which gives us 0.51° per frame for that octant:

!T 50000 1 .

!R 0 -1.19 360
!A 0 5 360
%C 118 0 0 0
%S 0 0 %C 5
$ 9

!R . -0.51 .
$ 9

# stop at end
!R . 0 .
!A . 0 .
%S . . %C 0
$ 10


For the next octants, we can copy the results which gives us the following animation:

!T 60000 1 .

!R 0 -1.19 360
!A 0 5 360
%C 118 0 0 0
%S 0 0 %C 5
$ 9

!R . -0.51 .
$ 9

!R . 0.51 .
$ 9

!R . 1.19 .
$ 18

!R . 0.51 .
$ 9

!R . -0.51 .
$ 9

!R . -1.19 .
$ 9

# stop at end
!R . 0 .
!A . 0 .
%S . . %C 0
$ 10



Mhm—not as good as needed. Of course, subdividing the circle in more segments is possible—but becomes more and more cumbersome. So, maybe a new version of an animation language would be helpful if cranks appear at more and more places.

I hope that this short introduction to my tool chain for simple mechaincal animations has been interesting. Of course, there is much more capable software around (ImageMagick itself can do some animation, then there's Flash programming, modern 3D CAD tools and what-have-you), but for me, it was fun to develop this tooling with a 100-line compiler, some existing command-line tools, and a simple CAD software at the front end!

Thursday, December 27, 2012

A tiny language for mechanical animations - the AWK script

The AWK compiler for my little animation language is quite simple. Right now, it has 104 lines, and I do not intend to expand it. You can download the 3kB file here: www.haraldmmueller.de/eb/animate.awk.

1. Overall structure


The overall structure of the script is simple: There is a single block for each of the 5 possible commands.

/^#/      { ...
          }
/^!/      { ...
          }
/^%/      { ...
          }
/^~/      { ...
          }
/^\$.../  { ...
          }


The state of the script is contained in the following eleven variables:
  1. rem: the prefix for shell comments, to be set in the gawk call.
  2. valuS[v]: value of scalar variable v.
  3. stepS[v]: increment of scalar variable v.
  4. moduS[v]: modulus of scalar variable v.
  5. frmtS[v]: print format of scalar variable v (necessary because modulus controls leading zeros).
  6. valuX[v]: x value of vector variable v.
  7. valuY[v]: x value of vector variable v.
  8. stepXOrM[v]: either a number → increment for x value of vector variable v; or a string starting with % → anchor for second format of vector increment.
  9. stepYOrA[v]: increment for y value of vector variable v (if stepXOrM[v] is a number); or angle increment (if stepXOrM[v] starts with %).
  10. currcmd: The previous command. 
  11. out: The flag remembering the ~ state.
The following sections explain the five code pieces.

2. Explicit Comments


This is easy: We print rem, the line number, and the text; and then continue with the next line ("next"):

/^#/  { print rem " (" NR ":" $0 ")"
        next
      }



3.Scalar variable assignment


This straightforward code consists of four if statements:
  • The first one checks that there are exactly three parameters present (a rudimentary attempt at error handling—more of this is needed ...). The print statements uses gawk's extension > "/dev/stderr" which works also under Windows.
  • The other three store the respective values in valuS, stepS, and moduS, unless a dot was specified. frmtS is set when the value or the modulus is set.0 + ... is the AWK idiom for converting a string to a number:
 
/^!/  { if (NF != 4) {
          print NR ": ERROR - exactly 3 parameters expected in " $0 > "/dev/stderr"
          next
        }
        if ($2 != ".") {
          valuS[$1] = 0 + $2
          frmtS[$1] = "%0d"
        }
        if ($3 != ".") {
          stepS[$1] = 0 + $3
        }
        if ($4 != ".") {
          moduS[$1] = 0 + $4
          frmtS[$1] = "%0" int(log(moduS[$1]) / log(10) + 1) "d"
        }
        print rem " v=" valuS[$1]
...more debugging output...
        next
      }


4. Vector variable assignment


Also vector variable assignments just store the values:

/^%/  { if (NF != 5) {
          print NR ": ERROR - exactly 4 parameters expected in " $0 > "/dev/stderr"
          next
        }
        if ($2 != ".") {
          valuX[$1] = 0 + $2
        }
        if ($3 != ".") {
          valuY[$1] = 0 + $3
        }
        if ($4 != ".") {
          stepXOrM[$1] = $4
        }
        if ($5 != ".") {
          stepYOrA[$1] = 0 + $5
        }

        print rem " v=" valuS[$1] ":" valuY[$1] " ...more debugging output...
        next
      }


5. Handling ~


This is simple:

/^~/    { out = $2
          next
        }



6. Command handling


Obviously, this is the interesting part. A $ line must contain a subsequent number of steps. Therefore, the script checks not only for a $ at the beginning of the line, but also for a subsequent number. The steps are remembered, then we collect the command if it is present. At last, the command is emitted multiple times. Here is the overall structure:

/^\$[ \t]+[0-9]+[ \t]+/    {
        steps = 0 + $2               
        ...remove leading $ and $2
        if ($0 != "") {
          ...collect lines with trailing \ into currcmd
        }
        for (i = 0; i < steps; i++) {
          cmd = currcmd
          ...interpolate scalar variables in cmd
          ...interpolate vector variables in cmd
          print cmd
          ...increment scalar variables
          ...increment vector variables
        }
      }
      

Removing the leading $ and count is easy in AWK:

        sub(/^\$[ \t]+[0-9]+[ \t]+/, "")

If there is a command after the count, we collect it in variable currcmd. Handling trailing \ for command concatenation requires five more lines:

        if ($0 != "") {
          currcmd = $0
          while (/[\\]$/) {
            sub(/[\\]$/, " ")     
            getline
            currcmd = currcmd $0
          }
        }


Inside the emitting loop, we handle variables, print the output line, and finally increment the variables.

Replacing scalars has a few special points:
  • First, all values are rounded to the nearest integer. The reason is that the images work with pixels.
  • If modulus is set, we take the variable value modulo that modulus
  • And finally, we format the value using the current format.

          for (v in valuS) {
            vv = int(valuS[v]+0.5)
            if (moduS[v] > 0) vv %= moduS[v]
            vv = sprintf(frmtS[v], vv);
            gsub(v, vv, cmd)
          }
         

Replacing vectors is actually easier than scalars, as there are no formats and modulus for vectors. Also here, we round to the nearest integer:
         
          for (v in valuX) {
            vx = int(valuX[v]+0.5);
            vy = int(valuY[v]+0.5);
            gsub(v ":x", vx, cmd)
            gsub(v ":y", vy, cmd)
          }


After (possibly) printing the command, we have to increment the values. For scalars, this is a "one-liner":

          for (v in valuS) {
            valuS[v] += stepS[v]
          }


For vectors, we have two variants. One is easy—we just add the increments. The 0 + is needed here because stepXOrM is not coerced to a number when reading because it might be a %name:

          for (v in valuX) {
            if (stepXOrM[v] ~ /^%/) {
              ...angle increment - see below...
            } else {
              valuX[v] += 0 + stepXOrM[v]
              valuY[v] += stepYOrA[v]
            }
          }


Finally, we need some simple trigonometry for the angle increment. Here is the algorithm:
  • First, we need the coordinates of the rotation center (mx and my).
  • Then, we compute the delta vector between the current location and the rotation center (dx and dy).
  • We convert this to angular form (r and phi) and immediately add the angle increment, after converting it from degrees to radians.
  • Finally, we compute the new location by transforming and adding the new delta vector to the rotation center:

              mx = valuX[stepXOrM[v]]
              my = valuY[stepXOrM[v]]
              dx = valuX[v] - mx
              dy = valuY[v] - my
              r = sqrt(dx*dx + dy*dy)
              phi = atan2(dy, dx) + (stepYOrA[v] / 180 * 3.1416)
              valuX[v] = mx + r * cos(phi)
              valuY[v] = my + r * sin(phi)


And this is it!

The next posting will contain two examples of simple animations and, additionally, some arguments on why cranks are difficult.

    Wednesday, December 26, 2012

    A tiny language for mechanical animations using ImageMagick

    My Movimentum project is currently fast asleep—but in a discussion about railroad barriers, I felt that I needed to explain a certain mechanical linkage with animations. Christmas season is a good time to experiment with new ideas (at least new to me), so I created three "mechanical explanations" for an old Austrian gear that drove (and drives) railroad barriers. You can see the results in that posting (in German). If you are interested in what it explains, just ask here—then I'll give you a quick overview about what this is all about. However, this posting here is really about the tool chain I used to create these animations.

    Here is the second animation I made:


    Here is an overview of my process. Below, I will share my experiences for each step:
    1. I create the parts of the animation with a CAD tool (I use an older version of Caddy++).
    2. The result is exported ("printed") into a single PNG file with PDFCreator.
    3. The PNG file is split into multiple PNGs, each of which gets background transparency (I use an old version of Paint Shop Pro here).
    4. Next, the images need "location calibration": In step 3., they do not come out exactly overlapping. The small corrections to move them to the right position are done here, using ImageMagick.
    5. Next, I write the animation script. For this, I defined a very simple language that is interpreted by a 100-line AWK program (I use Gnu's Gawk for this). The description of this language is the major part of this posting.
    6. The result of this is a batch file that calls ImageMagick's convert program to assemble the images into frames.
    7. At last, ffmpeg is used to create an animation file from the frames.
    For the record, I work with Windows. However, I think most of this also applies e.g. to Linux.

    1. Creating the parts


    Most probably, the parts have to be aligned in the animation. For this, it is very useful to add cross hairs as anchor points at critical locations, e.g., at the center of rotating items.

    Moreover, to place the anchors at a predictable point, I put each part into a box of equal size, where the anchors are at the same position relative to the left upper box corner. In addition, I create an empty box that contains only the cross hairs of an anchor.

    Here is a part of such a drawing. The lower left box is the empty one.



    2. Creating PNG files


    Instead of snipping the parts images from the CAD program or a PDF rendering, PDFCreator can be installed as a virtual printer that directly creates PNG files (despite its name). This avoids color and other distortions. Currently, I create a single PNG file with all the parts—this is faster than creating a single PNG file per part by printing from the CAD program, because I have to adjust each parts file anyway in the next step.

    Already here, I select the correct scaling of the PNG—at least with Caddy++, this is possible in the print dialog. This avoids any later scalings which will destroy the cross hairs of the anchor points.

    PDFCreator creates the PNG in portrait orientation even if you "print" in landscape—I do not know whether this is a fault of PDFCreator or Caddy++ or whatever else. In Windows, the created PNG is immediately opened in the Photo Viewer, so it is only a single click to rotate it to the intended orientation.

    3. Splitting the PNG image and adding global transparency


    Using Paint Shop Pro, I select the content of each box in the result and paste each part into a new PNG file. Unfortunately, box selection uses screen coordinates in Paint Shop Pro. Therefore, in the new files, the pasted parts may not lie at perfectly the same relative spot—I consistently got offsets of ±1 for both coordinates. In step 4 I explain how I correct this—but maybe there is a better way ... Because of this correction, I save the parts in files called ...Approx.PNG; the corrected files will have names without Approx.

    While I have the new part file open, I make the background transparent, because this is what is needed for stacking the parts in the animation. Also the empty box gets its own "part file" with transparent background.

    4. "Location calibration"


    As explained above, the anchor points of the parts files will not be exactly at the same relative position to the upper left corner. This is a nuisance, as ImageMagick uses the upper left corner as a reference point for its computations, whereas we want to use our anchors. I repair this problem manually as follows:
    • With ImageMagick, I overlay the the empty box over each part.
    • In the resulting image, I check the distance of the part anchors vs. the empty box anchors.
    • Again using ImageMagick, I correct the location of the part.
    Here are the concrete steps for this on the command line, using ImageMagick's convert:
    • Overlay and view with convert Part1Approx.PNG Empty.PNG -geometry +0+0 -composite temp.png
    • Viewing temp.png reveals that the anchors can e.g. be superimposed precisely with +1+-2 instead of +0+0. This is a trial-and-error process. Nicely enough, Windows's Photo Viewer will refresh the image immediately, so this is a very quick process.
    • Knowing the size of the empty box (e.g. use ImageMagick's identify—here I use 565x612 as example), invert the correction from +1+-2 to +-1+2, because we now want to shift the part's image, not the empty box. Then create the perfect part file via convert -size 565x612 xc:none Part1Approx.PNG -geometry +-1+2 -composite Part1.PNG.

    5. The animation script


    Now we come to the central piece: The animation script. The basic idea is that we create a separate ImageMagick convert command for each frame. Each such command will place the parts at specific places. But of course, instead of writing single commands for thousands of frames, I write patterns that contain variables that are expanded many times. The script is then run through an Gnu AWK program that emits the convert commands that compute the frames. Here is the call I use:

      gawk -v rem=REM -f animate.awk script.txt > script.bat

    I will explain the "script compiler" animate.awk in a separate posting.

    Update: During documenting the small compiler, I found I could make the language a little bit terser: The = step definition has been merged into the $ and + commands.

    5.1. Command patterns and variables - overview


    Here is such a command pattern that overlays a Part1 at a fixed place and a Part2 after a rotation and a shift (you can look up convert's options in ImageMagick's documentation):

    ... convert -size 900x800 xc:white \
        Part1.PNG -geometry +50+600 -composite \

        ( Part2.PNG -distort SRT "333,329,-!A2" ) \
          -geometry +%2:x+%2:y -composite \
      out\f_!T.png

    The variables in this pattern are the strings starting with ! or %:
    • !... is a scalar variable, Very often this is used for angles (like !A2 above), but sometimes also for numbering of files (like the !T at the end of the command).
    • %... is a vector variable. Its components are accessed with %...:x and %...:y. In the command above, a vector variable %2 is used to shift the rotated Part2.

      5.2. Script segments


      A complete script consists of script segments where each segment describes a homogeneous movement. Each segment consists of two parts:
      • A (possibly empty) sequence of variable assignments.
      • A command pattern (convert patterns usually become very long, therefore they can be broken over multiple lines with trailing backslashes, as shown above).
      Here is an example of such a segment:

        !T 10000 1 .
        $ 4 convert -size 900x800 xc:white \

            Text1.PNG -geometry +50+600 -composite out\f_!T.png
      • !T 10000 1 . is an assignment to the variable !T: Its initial value is 10000, and it is incremented by 1 for each emitted convert. The last parameter (which is a single dot here) will be explained later.
      • $ 4 convert ... means that the convert command is to be emitted 4 times, with variables substituted in each command.
      This script segment will emit four lines where the variable !T is replaced with 10000, 10001, 10002 and 10003 in turn:

        convert -size .... out\f_10000.png
        convert -size .... out\f_10001.png
        convert -size .... out\f_10002.png
        convert -size .... out\f_10003.png  

      5.3. The details of scalar variables


      The assignment to a scalar variable has the following form:

        !name  initial  increment  modulus

      name can consist of any characters you like that are not special in regexps (reason: In the AWK script, regexp replacement is used to interpolate the variables).

      The three parameters can either be numbers (decimals are ok) or single dots (.). The dot means that the value remains as it was before. This is a central concept that helps to write short scripts. E.g., here is how you move something linearly, then let it stop:

        # 15 frames that reduce !X from 200 to some value.
        !X 200 -10 . 
        $ 15 convert ...

        # 10 frames without movement
        !X . 0 .
        $ 10 convert ...

      In the second assignment, we left !X at whatever value it had after the first segment completed by specifying initial as . (dot). However, we set the increment to 0, so that this variable does not change its value in the 10 frames of this segment.

      The modulus is sometimes useful for angle variables: The replacement value for a pattern is the variable's value modulo the modulus. I needed this because I precomputed rotated images of a part into 360 files called something like Part1_000.PNG, Part1_001.PNG,..., Part1_359.PNG. Each file contained the part rotated by so-and-so many degrees. For rotating the part, I could now write something like

        !A 350 2 360
        $ 20 convert ... Part_!A.PNG -geometry +0+0 -composite ...

      Starting at 350 (degrees), the variable !A will be incremented by 2 in each of 20 frames. After 5 frames, !A will be 360 and continue with 362, 364 etc. up to 390. Without 360, the calls using Part_360.PNG etc. will fail, as these files do not exist. However, with modules 360, the emitted commands will contain Part_000.PNG, Part_002.PNG etc. The modulus, if defined, also determines the number of digits in the output number: Because modulus is 360 here, we get three-digit numbers.

      5.4. The details of vector variables


      A vector variable consists of two components, x and y. There are two possible assignments to vector variables. The first form does what you expect:

        %name  initialX  initialY  incrementX  incrementY

      The variable is set and/or incremented as specified. Instead of a number, each argument can again be a single dot, which keeps the current value.

      The other form is necessary for rotational movements:

        %name  initialX  initialY  %other  degrees

      This means that the change to %name is a rotation around %other by the angle specified as the last argument. %other cannot be a dot here (the % is the marker that this form is intended), but the other three values can be numbers or dots. If  %other itself changes, the results are somewhat unpredictable, as the order of variable computations is not defined. If someone has the idea to try this—well, it's on your own risk ...

      5.5. The details of commands


      There are two possible forms for commands. One is

        $ count command-text

      This creates count instances of command-text. The variables are replaced in each instance, and the result is written to the output. As explained above, long commands can be wrapped with \ at the lines' ends (the \ must actually be the last character on the line—no trailing white space!).

      text can be anything your operating systems understands. I have, up to now, only called ImageMagick's convert here. However, one could e.g. write intermediary files and use them later in the frame sequence, e.g. to improve the performance of frame generation (thousands of calls to convert are not that fast ...).

      It makes much sense to write the output of commands—especially the created frame images—to a specific output directory, to keep things cleanly separated.

      The other command form omits the command-text:

        $ count

      This means that the previous command is to be repeated in this segment. This is a very important concept: It allows one to specify the "model" in a single convert command that places all the parts at various places; and then have this assembly move simply by maniulating variables. One could even argue that a good animation script sets up a model once at the beginning; and then only modifies variables.

      In addition to the $ lines, it is possible to switch off the output with

        ~ -

      and switch it on at a specific point with

        ~ +

      This is very helpful for debugging: It creates a script that will start "later in the animation", so that the script will only compute certain frames that are to be fine-tuned. This is preferable to (and much quicker than) editing the script each time it has been written before running it.

      5.6. Comments


      There are two possible forms of comments. One is

        # text

      The text of such a comment is copied into the output, with a leading comment marker that must be passed to the compiler. This is helpful for debugging, because you can line up the script with the output.

      The other form is everything else which is not understood by animation.awk, i.e., every line that does not start with !, %, ~, $, or #. Such lines are simply ignored. I use this feature to embed the script into a text that explains the animations, which will later become a web page. However, one could call this an unfortunate idea (even though it is along the lines of Donald Knuth's Literate Programming).

      6. Running the batch file


      This is as easy as it gets. You just run it.

      7. Creating the animation


      At last, ffmpeg is used to create an animation file from the frames. Here is the command line I use:

      ffmpeg -f image2 -r 12 -i f_1%04d.png 
                   -vcodec libx264 -pix_fmt yuv420p -y out.mp4

      Of course, you can use other formats than mp4 for your animation (especially if you do not like proprietory formats). For mp4, the -pix_fmt yuv420p is important. The default pixel format, whose name I forgot, is not understood by the majority of programs or web sites out there.

      If you want to create multiple animations from a single script (which happens if you embed your script code into running text, as I do), it makes sense to start the frame files with new numbers for each script, using !T 10000 . ., !T 20000 . ., !T 30000 . . etc. Then, you can run ffmpeg with file patterns f_1%04d, f_2%04d, f_3%04d etc.

      In my next posting, I explain the small compiler (which you can download here).
      In a subsequent posting, I will give a few animation examples and some background on why cranks are difficult.