Context Free Art

Control flow elements

From Context Free Art

Jump to: navigation, search

In a shape rule it is possible to put a control structure anywhere that it is legal to have a shape replacement. In a path it is possible to put a control structure anywhere that it is legal to have a path operation or a path command. The list of supported control structure are: loops, if statements, transform statements, and switch statements.

Contents

Loops

A loop structure takes a block of code (shape replacements, path operations, or path commands) and iterates over it several times. A loop consists of a loop header, a loop body, and an optional finally body. A loop header has an optional loop variable, the loop count, and the loop transform. If a loop index variable is declared in the loop header then the current loop value is made available through that variable inside the loop body (and finally body). These kinds of loops are called named loops. If no loop index variable is declared in the loop header then the loop is an anonymous loop and the current loop value is not available. After each iteration of the loop the current world state is adjusted by the loop transform.

shape flower {
// petals
loop 6 [r 60] CIRCLE [[ r 30 x 0.5 s 1 0.25 ]]
//center
CIRCLE [ s 0.25 b 1 ]
}

is equivalent to

shape flower {
// petals
CIRCLE [[ r 30 x 0.5 s 1 0.25 ]]
CIRCLE [[ r 60 r 30 x 0.5 s 1 0.25 ]]
CIRCLE [[ r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
CIRCLE [[ r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
CIRCLE [[ r 60 r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
CIRCLE [[ r 60 r 60 r 60 r 60 r 60 r 30 x 0.5 s 1 0.25 ]]
//center
CIRCLE [ s 0.25 b 1 ]
}

Loop Index Variable

shape rightTriangle {
loop i = 10 [x 1] // named loop, index variable is i, loops from 0 to 9
loop i+1 [y 1] // anonymous loop, loops from 0 to i
CIRCLE []
}

The example has the outer loop declaring a loop index variable by putting i = before the loop count. The inner loop does not have a variable = before the loop count, so it is anonymous. Note that the outer loop's variable is used in the loop count for the inner loop. We could even use it in the loop transform of the inner loop. The only place that we cannot use the loop variable is in the loop transform for the outer loop. The scope for the loop index variable is the loop transform and the body of the loop.

Loop Count

The loop count is an expression of one, two, or three numbers. If all three numbers are specified then they are the initial loop value, the terminal loop value, and the loop step. The loop value starts with the initial value for the first iteration. The loop step is added to the loop value after each iteration. The loop terminates when the loop value is ≥ the terminal value (or ≤ the terminal value if step < 0). If two numbers are specified then they are the initial value and the terminal value, the loop step defaults to 1. If only one number is specified then it is the terminal value, the initial value defaults to zero and the loop step defaults to 1.

Loop Transform

The body of the loop inherits the world state for the shape or path that contains the loop. After each loop iteration the loop's world state is adjusted using the loop transform.

Finally Body

If the loop has a finally body then the code in the finally body will execute once after the last iteration of the loop. The world state for the finally body is the world state for the loop with one last adjustment by the loop transform. If the loop is a named loop then the loop index variable has scope inside the finally body.

shape foo2 {
SQUARE [[r -22.5 s 0.1 14.5 y 0.5]]
loop 10 [[x 0.5 s 0.99 x 0.5 r 3]]
CIRCLE []
finally
foo2 []
}

If Statements

An if statement evaluates an expression and executes some code (replacements, path elements, other control structures) if the expression value is non-zero. An is statement can also have an else clause that is executed if the expression value is zero. The syntax for an if statement is if (expression) then-body or if (expression) then-body else else-body. then-body and else-body can be simple bodies or compound bodies.

path flower(number petals, number filled)
{
MOVETO(cos(-180/petals), sin(-180/petals))
loop petals [r (360/petals)]
ARCTO(cos(180/petals), sin(180/petals),
0.4, 0.4 + 0.2 * (petals - 5), 90)
CLOSEPOLY(CF::Align)
if (filled) FILL[a -0.5]
MOVETO(0.65, 0)
ARCTO(-0.65, 0, 0.65, CF::ArcCW)
ARCTO( 0.65, 0, 0.65, CF::ArcCW)
CLOSEPOLY()
FILL[a -0.5]
}

Switch Statements

A switch statement allows one of several blocks of code to be executed based on the evaluation of of a selector expression. The switch statement contains one or more case elements. Each case element contains a constant case integer and a case body, which is a simple or compound statement. The case integer is derived from a constant case expression, which is evaluated and rounded to the next lowest integer. There may also be an else element which also has a case body, but no expression. When a switch statement is executed the selector expression is evaluated and rounded to the next lowest integer. If there is a case element with a case integer matching this integer then the corresponding case body is executed. If there is no matching case element then the else body is executed, if there is one.

shape randShape {
switch (rand(3)) {
case 0:
SQUARE []
case 1:
TRIANGLE []
case 2: {
CIRCLE []
}
else:
FILL []
}
}
Programmers note: This is not a C-style switch statement. Each piece of code has exactly one case header. There is no fall-through and no break statement.


Transform Statements

A transform statement has a transform and a body. The transform body is executed with a world state that has been adjusted by the transform. The transform body can be simple or compound.

TTOP = sin(30)/sqrt(3)
HEXS = sqrt(3)/4
 
path triangle {
MOVETO(-0.5, -TTOP)
loop 3 [r 120]{
CURVETO(0, -TTOP, -0.25, 0.05)
CURVETO(0.5, -TTOP, CF::Continuous)
}
CLOSEPOLY(CF::Align)
transform[s 0.41] {
MOVETO(0.25, HEXS)
loop 5 [r -60]
LINETO(0.5, 0)
CLOSEPOLY()
}
FILL[]
}

In this example the designer wanted to knock-out a hexagonal hole in a path. Figuring out exactly how to position the hexagon path points is tricky. The transform statement allows the designer to plug in simple code for a hexagon and then tweak the entire hexagon path to achieve the desired effect.

Clone Statements

A clone statement has the same syntax and behavior as a transform statement except that all of the shapes executed inside a clone statement look the same, even if they are random. This is a violation of context-free purity, so you must set CF::Impure to 1.

CF::Impure = 1
 
shape SPIKE
rule {
SQUARE []
SPIKE [y 0.95 s 0.97]
}
rule 0.03 {
SQUARE []
SPIKE [r 60]
SPIKE [r -60]
SPIKE [y 0.95 s 0.97]
}
 
shape snowflake {
clone [] [r 60] [r 120] [r 180] [r 240] [r 300]
[f 30][f 90] [f 150] [f 210] [f 270] [f 330]
{
SPIKE []
}
}

Simple vs. Compound Bodies

The body of a control structure (loop-main, loop-finally, if, else, switch-case, transform, or clone) can have one of two forms: simple or compound. A simple loop body is a single shape replacement, path operation, path command, or another control structure. A compound loop body has an open curly brace {; any number of replacements, path elements, or control structures; and then a closing curly brace }.

shape rightTriangle {
loop i = 10 [x 1] // simple body
loop i+1 [y 1] { // compound body
SQUARE []
CIRCLE [b 1]
}
}
 
TTOP = sin(30)/sqrt(3)
HEXS = sqrt(3)/4
 
path triangle {
MOVETO(-0.5, -TTOP)
loop 3 [r 120] { // compound body
CURVETO(0, -TTOP, -0.25, 0.05)
CURVETO(0.5, -TTOP, CF::Continuous)
}
CLOSEPOLY(CF::Align)
transform[s 0.41] { // compound body
MOVETO(0.25, HEXS)
loop 5 [r -60]
LINETO(0.5, 0) // simple body
CLOSEPOLY()
}
FILL[]
}
 
shape randShape {
switch (rand(3)) {
case 0:
SQUARE [] // simple body
case 1:
TRIANGLE [] // simple body
case 2: { // compound body
CIRCLE []
}
else: // simple body
FILL []
}
}
Views
Personal tools
Navigation
Toolbox
Powered by MediaWiki
Attribution-Share Alike 2.5
book coverSee our book:
Community of Variation