### 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:

**rem**: the prefix for shell comments, to be set in the gawk call.**valuS[**: value of scalar variable*v*]*v.***stepS[**: increment of scalar variable*v*]*v.***moduS[**: modulus of scalar variable*v*]*v.***frmtS[**: print format of scalar variable*v*]*v*(necessary because*modulus*controls leading zeros).**valuX[**: x value of vector variable v.*v*]**valuY[**: x value of vector variable v.*v*]**stepXOrM[**: either a number → increment for x value of vector variable*v*]*v*; or a string starting with % → anchor for second format of vector increment.**stepYOrA[**: increment for y value of vector variable*v*]*v*(if*stepXOrM*[*v*] is a number); or angle increment (if*stepXOrM*[*v*] starts with %).**currcmd**: The previous command.**out**: The flag remembering the ~ state.

### 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.

## No comments:

## Post a Comment