Sunday, June 30, 2013

Animate3 - the commands (part 1)

In the previous posting, I presented a system for creating two-dimensional technical animations.

The major description for such an animation is written in a small language called Animate3, whose elements I will start to explain in this posting.

The language has only nine different commands that are used to create a batch file (or shell script) that creates the actual animation.

Here is an overview of the contents of this posting:


The execution model


The semantics of the language can—as for any language—only be explained by referring to an "execution model," i.e., some interpreter that reacts to the given commands. The previous posting already contained a brief description of this execution model, but I will repeat it here for convenience and extend it with a few details.

An Animate3 script is executed in the following context:
  • There is a file system where files can be read and written.
  • There is an operating system that has at least the following commands: (a) A no-op (comment) command; (b) a check for file and directory existence; (c) creation of directories.
  • ImageMagick is installed in version 6.8.1 or higher.
In addition, each execution knows how to produce five "effects" in the operating system:
  • (A) Execute the no-op (comment) command with some additional information.
  • (B) Call some command if a file is missing.
  • (C) Call ImageMagick's convert command.
  • (D) Create a directory if it is missing.
  • (E) Create a file path from a directory name and a file name.
The semantics of the language is described in the following model: At every time, the state of the execution consists of
  • a set of scalar value generators, where each (a) is either initialized or uninitialized; and has (b) a current real-number value; (c) a generating command; and (d) an output format for its value;
  • a set of vector value generators, where each (a) is either initialized or uninitialized; and has (b) a pair of real numbers, called X and Y, as its value; and (c) a generating command;
  • a set of active actions, where each has (a) a list of commands; (b) a current command; (d) a number of "open frames;" (e) a number of "open repetitions;" and (f) possibly (and usually) a parent action;
  • possibly a selected "scene action," which defines the size of the scene's view port and, additionally, the name of a frame file;
  • a flag "frame generation is on/off."
The interpretation of each command in each active action happens in an endless frame-generation loop whose body consists of three steps:
  • (STEP EXECUTE) First, each active action executes all its commands up to the first "blocking command." This can either be a frame-emitting command, or it is the final command of the action when the number of open repetitions of the action has been exhausted.
  • (STEP DRAW) Some frame-emitting commands (specifically, & commands) may have created "pieces," i.e., parts from the input graphics files in some position or other transformation. In the second step of the frame generation cycle, these pieces are assembled into the frame file.
  • (STEP INCREMENT) Finally, in the third step, all value generators (scalar and vector) are "incremented," i.e., their current values are updated by the respective generating command.
Of course, there is one command that stops this "endless" loop so that frame generation comes to an end.

The following description of all Animate3 commands will explain, for each command, which part of the state of execution is changed in what way during which of three steps of the frame-generation loop.

Some remarks about the current implementation


The current implementation of the Animate3 system consists of a "compiler" that reads one or more Animate3 scripts and emits a batch file or shell script. By default, this emitted file is a Windows .bat file; however, it is easy to pass a few parameters to the compiler to emit other sorts of command scripts.
Because of the intermediate batch file, the state described above has an additional component, namely the current name of the batch file written. A different implementation of Animate3 might not need this.
The resulting batch file is then executed on the operating system, which creates the frames of the animation. These frames can be assembled into some animation file by a call e.g. to FFmpeg.

The current Animate3 compiler is written as a Gnu-AWK-3.1.6 script of exactly 1000 lines of code. Of course, it would be possible to implement it in any other programming language—and I even think that this would be a nice exercise: On the one hand, Animate3 is quite small. On the other hand, Animate3 contains interesting and important basic concepts from at least three areas of computer science, namely (a) computer graphics, (b) task scheduling and (c) language parsing. Therefore, any new implementation should be interesting to write. Also, it should be possible to tackle the small language with formal methods—formal semantics, formal correctness and termination proofs of an implementation, development of covering test cases etc. But I digress—let me return to the language proper.

The Animate3 compiler is called using the Gnu-AWK executable as follows:
gawk -f animate3.awk optional_variable_assignments script_file ...
Five variables control the output of the Animate3 compiler.
  • REM is a prefix for emitted lines that are comments—used for effect (A) (see above). Its default value is @REM.
  • CREATE is a C-style format that is used to call a command if a file is not existing, i.e., for effect (B). It must contain %s exactly twice. The first %s is replaced with the name of the file to be created, the second %s is replaced with a command that (usually) creates that tile. CREATE's default value is if not exist %s %s\n.
  • CONVERT is a string that calls ImageMagick's convert command—effect (C). Its default value is convert.
  • MKDIR is a C-style format that is used to create a non-existing directory, i.e., for effect (D). It must contain %s exactly once or twice. Each %s is replaced with the name of the directory to be created. MKDIR's default value is if not exist %s mkdir %s\n.
  • PATHSEP is a string that separates a directory name from a file name—used for effect (E). Its default value is \.
Here is an example call that replaces some of these values:
gawk -f animate3.awk -v REM="#" -v CREATE="if [ -f %s ] then %s fi" -v MKDIR="if [ -d %s ] then md %s fi" -v PATHSEP=/ script.an3
Moreover, there are two variables that control debugging output:
  • DEBUGOUT is a file where quite a lot of debugging output is written. Its default value is the empty string.
  • DEBUGDETAIL is a regular expression that can be used to filter debugging output. Its default value is also the empty string. Useful values might be function names or generator names—but I never used it myself; instead, I worked with the complete debugging output.

All the Animate3 commands


Animate3 has nine different commands:
  • # ... a comment line that is copied to the result script.
  • ! ... definition of a scalar value generator
  • % ... definition of a vector value generator
  • <... reading a parts file
  • ~ ... controlling output
  • | ... starting parallel subactions; and stopping the frame-generation loop
  • = ... defining the view of a scene
  • & ... creating frames from parts
  • $ ... creating frames with shell commands (useful to execute arbitrary code)
Lines that start with one of these nine characters are interpreted. All other lines in a script are ignored.

A command can be broken over more lines by appending a \ (backslash) to each line that is continued in the next line. This is especially useful for the | and & commands.

Here follows a description of all nine commands, including some simple example usages. For each command, the following is provided:
  • The syntax explains what valid commands look like. Italic parts indicate variable parts; the allowed values are explained later. | is used to denote alternatives, * for repetitions (including zero occurrences), and brackets [ and ] define the scope of | and *.
  • The legitimate values for the variable parts are explained next.
  • The subsequent explanation explains what the command does in the three phases of the frame-generating loop.


# ... a comment that is copied to the result script.

 

Syntax:

# arbitrary text to end of line

 

Explanation:


# is a non-blocking command.

The text is copied into the resulting batch script via effect (A) above. This is helpful for script debugging, as one can line up the input script with the generated batch commands.


! ... definition of a scalar value generator

 

Syntax:

!name [ init | . ] step [ modulus | . ]
or
!name init

 

Valid parameters:

  • name must be a non-empty sequence containing only the letters A...Z and a...z, digits 0...9, and underscores.
  • init, step, and modulus must be real numbers or value expressions—see here for the latter.

 

Explanation:


! is a non-blocking command.

After a definition—also a re-definition—, a value generator is in the uninitialized state, with one exception: When a dot is given as the first parameter, and additionally the generator was already initialized, it remains in the initialized state.

In STEP EXECUTE, the definition will only have the effect that the definition is remembered.

In STEP INCREMENT, the generator will be initialized (see below) if it is not yet initialized. Then, the definition will have the following effect:
  • If it is the first form, the generator's value will be replaced with ((value + step) modulo modulus), if modulus is given; or (value + step) if there is a dot at the last position.
  • If it is of the second form, the generator's value will be replaced with the value of init.
The second form is of course not useful when init is a fixed number. However, it is very useful if init is an expression that uses other values that are modified in STEP INCREMENT. The access definition below guarantees that a value generator is incremented before it is accessed during STEP INCREMENT.
The value of a scalar value generator can be accessed in other commands using the syntax !name.
Also, the change to the value in the last STEP INCREMENT can be accessed in other commands using the syntax !name' (with a trailing single quote).

Such an access can happen either in STEP EXECUTE (in =, $, and & commands) or in STEP INCREMENT because of its usage in the command of a scalar or vector generator. Each such access has two consequences:

 a. First, if the value generator is uninitialized, the value will be set to the value of the expression passed as init (for expressions, see later). When this expression in turn contains accesses to other value generators (scalar or vector), this will recursively have the same effects on the accessed generators before the current access. If there is a cycle in this dependency graph, the effect is undefined.
The actual implementation will initialize some arbitrary generator to zero and continue from there.
If instead of init a dot is present, the value will remain as it was before (also an "uninitialized" generator can have a value!). If it had no value up to now, the value is set to zero.
Moreover, if a modulus is provided, the format of the generator is set to "integer output with the number of digits of the modulus's integral part, using leading zeros."
For example, a modulus of 137 will create a format "integer output with three digits and leading zeros." A value of 27 will then be output as 027.
If no modulus is given, the format of the generator is "real number output with as few digits as possible".
The actual implementation uses C-style formats %0md for the first case, where m is the number of digits of the modulus' integral part; and .6g for the second case.
 b. Then, the value is "interpolated" (inserted) in the accessing command using the format.


% ... definition of a vector value generator

 

Syntax:

%Name [ initX | . ] [ initY | . ] stepX stepY
or
%Name [ initX | . ] [ initY | . ] %vectorpivot angleStep
or
%Name [ initX | . ] [ initY | . ] partpivot angleStep
or
%Name initX initY

 

Valid parameters:

  • name must be a non-empty sequence containing only the letters A...Z and a...z, digits 0...9, and underscores.
  • %vectorpivot must be a defined vector value generator.
  • partpivot must be the name of a part that has a pivot point (see < command for reading a parts file).
  • All other parameters must be real numbers or value expressions—see here for the latter.

 

Explanation:


% is a non-blocking command.

After a definition—also a re-definition—, a value generator is in the uninitialized state, with one exception: When dots are given as the first two parameters, and additionally the generator was already initialized, it remains in the initialized state.

In STEP EXECUTE, the definition will only have the effect that the definition is remembered.

In STEP INCREMENT, the generator will be initialized (see below) if it is not yet initialized. Then, the definition will have the following effect:
  • If it is the first form, the generator's X value will be replaced with the value of (X + stepX); and its Y value will be replaced with the value of (Y + stepY).
  • If it is of the second form, the generator's (X,Y) pair will be replaced with the pair arrived when rotating (X,Y) by angleStep degrees around the current (X,Y) pair of the %vectorpivot vector value generator. A positive angleStep will rotate (X,Y) clockwise.
  • If it is of the third form, the generator's (X,Y) pair will be replaced with the pair arrived when rotating (X,Y) by angleStep degrees around the pivot point of part partpivot according to a previously read partsfile (see command <). Also here, a positive angleStep will rotate (X,Y) clockwise.
  • If it is of the fourth form, the generator's (X,Y) pair will be replaced with the values of the pair (initX, initY).
The fourth form is not useful when initX and initY are fixed numbers. However, it is very useful if initX or initY are expressions that use other values that are modified in STEP INCREMENT. The access definition below guarantees that a value generator is incremented before it is accessed during STEP INCREMENT.
The values of a vector value generator can be accessed in other commands using the syntax %name:x (for the X value) and %name:y (for the Y value).
Also, the changes to these values in the last STEP INCREMENT can be accessed in other commands using the syntax %name:x' or %name:y' (with added single quotes).

Such an access can happen either in STEP EXECUTE (in =, $, and & commands) or in STEP INCREMENT because of its usage in the command of a scalar or vector generator. Each such access has two consequences:

a. First, if the value generator is uninitialized, the value pair will be set to the pair of values of the expressions passed as initX and initY (for expressions, see later). When one of these expression in turn contains accesses to other value generators (scalar or vector), this will recursively have the same effects on the accessed generators before the current access. If there is a cycle in this dependency graph, the effect is undefined.
The actual implementation will initialize some arbitrary generator to zero and continue from there.
If instead of initX a dot is present, the X value will remain as it was before (also an "uninitialized" generator can have a value!). If it had no value up to now, X is set to zero. The same holds for the Y value.

b. Then, the value is "interpolated" (inserted) in the accessing command as a real number.


Expressions


Commands can contain expressions that compute values from other values. The value of such an expression is always a single real-valued number.

Simple expressions are direct references to values in value generators or to coordinates of parts. They can be used directly in commands:
  • !name for the value of scalar generator !name
  • !name' for the last value change of scalar generator !name
  • %name:x for the X value of vector generator %name
  • %name:y for the Y value of vector generator %name
  • %name:x' for the last change of the X value of vector generator %name
  • %name:y' for the last change of the Y value of vector generator %name
  • name:x for the x coordinate of the left upper anchor point of part name
  • name:y for the y coordinate of the left upper anchor point of part name
  • name:w for the width of the bounding box of part name
  • name:h for the height of the bounding box of part name
  • name~x for the x coordinate of the pivot point of part name
  • name~y for the y coordinate of the pivot point of part name
  • name~w for the horizontal offset of the pivot point from the left upper anchor point of part name
  • name~h for the vertical offset of the pivot point from the left upper anchor point of part name
However, it is also possible to write more complex expressions. Such a complex expression must be enclosed in curly braces { and }. Because no part of the expression language itself contains braces, an expression always extends from a { to a }. Here is the syntax and semantics of the expression language:

Expression


An expression can consist of the following symbols:
  • simple numbers, consisting of digits (0...9) and at most one decimal point;
  • one of the six possible simple expressions from a value generator;
  • one of the eight possible simple expressions from a part's coordinates;
  • operators +, –, *, /, \, and the combined operator ?: with range symbol ..;
  • parentheses ( and );
  • the symbols sin, cos, tan, asin, acos and atan2.
In an expression, there can be whitespace between symbols, but it is ok if there is no whitespace at all between two symbols. Thus, the following are valid (and equivalent) expressions:
{90*sin!lever_A}
{ 90 * sin   !lever_A }
The syntax of an expression is as follows:
expression ::=
    sum |
    sum ? range : sum [ ? range : sum ]* : sum
where range is defined as follows:
range ::= [ sum ] .. [ sum ]
An expression is
  • either a simple sum; in this case the value of the expression is the value of the sum;
  • or a sum with following range-sum pairs. In this case, the sum is checked against all ranges in order. If it is in a range, the sum after the corresponding colon is the value of the expression. If it does not fall into any range, the expression's value is the value of the last sum.
For ranges, the semantics is as follows:
  • If the first sum is missing, its value is assumed to be –1 ⋅ 1099.
  • If the second sum is missing, its value is assumed to be +1 ⋅ 1099.
  • A sum is "in" the range if its value is at least as large as the first sum; and at most as large as the second sum. However, if the second sum is smaller than the first, both are reversed before the check is made.

Sum


The syntax of a sum is as follows:
sum ::= term [ [ + | – ] term ]*
A sum is a sequence of terms, connected with + and –. The value of the sum is the usual arithmetic value of this sequence, i.e., the terms are evaluated and summed or subtracted from left to right.

Term


The syntax of a term is (here, the first asterisk is meant to be the character *, whereas the second one is the meta symbol for repetition, as in other syntax rules in this posting):
term ::= factor [ [ * | / | \ ] factor ]*
A term is a sequence of factors, connected with *, /, and \ (modulus). The value of the term is the usual arithmetic value of this sequence, i.e., the factors are evaluated and multiplied, divided, or "remaindered" into the first factor.

Factor


The syntax of a factor is:
factor ::=
    number |
    value_generator_value |
    part_value |
    ( expression ) |
    – factor |
    sin factor |
    cos factor |
    tan factor |
    atan2 factor factor |
    asin factor |
    acos factor
A factor's value is determined as follows:
  • simple number: The value of the factor is the decimal value of the number;
  • one of the six simple expressions from a value generator: The value of the factor is as defined above.
  • one of the eight simple expressions from part's coordinates: The value of the factor is as defined above.
  • parenthesized expression: The value of the factor is the value of the expression.
  • factor with a preceding minus: The value of the whole factor is the negative value of the inner factor.
  • sine expression: The value of the factor is the sine of the value of the inner factor, interpreted as a degree value. Thus, sin 90 is the value 1 (one).
  • cosine expression: The value of the factor is the cosine of the value of the inner factor, interpreted as a degree value. Thus, cos 180 is the value  –1 (minus one).
  • tangent expression: The value of the factor is the tangent of the value of the inner factor, interpreted as a degree value. Thus, tan 45 is the value  1 (one).
  • arc tangent expression: The value of the factor is the arc tangent of the first factor divided by the second as an angle in degrees. Thus, atan2 3 3 is 45.
  • arc sine expression: The value of the factor is the arc sine of the inner factor. Thus, asin 1 is 90.
  • arc cosine expression: The value of the factor is the arc cosine of the inner factor. Thus, acos 1 is 0.
That is all that can be said about expressions.

The description of Animate3 is continued in this posting.

No comments:

Post a Comment