Context Free Art
Contents |
A 2D space can be used to give an illusion of 3D objects, although the illusion is not perfect but in very restricted areas. There are a few tricks to master. Basically:
startshape circles rule circles { CIRCLE { } circles { z -1 s .7 y .7 x -1.5 } circles { z -1 s .7 y .7 x 1.5 } }
In this picture, size ( s .7 ) and the y-coordinate ( y .7 ) produce the illusion in successive recursions. Varying the y increment value ( .7 ) would result in different points of view with respect to the objects. A very small value like .1, would indicate that the viewer eye is on the same level as the objects, for example.
The results get more interesting with variations in color and space, as well as when using more complex objects. In the following picture, a gradual increase in brightness ( b .1 ) produces a mist effect.
startshape forest rule forest { tree { } forest { z -1 s .7 y .7 x -1.5 b .1} forest { z -1 s .7 y .7 x 1.5 b .1} } rule tree { CIRCLE { s .5 1.2 } tree { s .97 y .3 r 3} } rule tree .1 { tree { flip 90 } } rule tree .1 { tree { r 10 } tree { r -30 s .6} }
In the following image, then, there is a hint of a landscape of alternating hills and valleys.
Now our perspective rule ( forest ) has four alternative definitions that produce a non-deterministic outcome. One of the rules is actually empty, in order to terminate some of the branches of recursion ( in the picture these branches appear where the trees end ). Also note the decrease of alpha saturation ( a -.1 )towards the fake perspective.
startshape forest rule forest { moss { y -1 s 12 5 hue 100 sat .6 b .8 } tree { } forest { z -10 x 7 y 1 s .9 a -.1 } forest { z -10 x -5 y 1.2 s .6 a -.1 } } rule forest { moss { y -1 s 12 5 hue 100 sat .6 b .8 } tree { } forest { z -10 x -10 y 1.1 s .9 a -.1} forest { z -10 x 12 y 1.1 s .7 a -.1} } rule forest { forest { y 1 } } // placing the next element higher up gives the eye an illusion of sloping ground rule forest .5 { } rule moss { CIRCLE { } } rule tree { CIRCLE { s .5 1.2 } tree { s .97 y .3 r 3} } rule tree .1 { tree { flip 90 } } rule tree .1 { tree { r 10 } tree { r -30 s .6} }
As the next step, different kinds of objects can be used in place of the uniform trees above. All that is needed is a common "wrapping" that distributes weights as is best ( see the rule forestobject below ).
startshape forest rule forest { moss { y -1 s 12 5 hue 100 sat .6 b .8 } forestobject { } forest { z -10 x 7 y 1 s .9 a -.1 } forest { z -10 x -5 y 1.2 s .6 a -.1 } } rule forest { moss { y -1 s 12 5 hue 100 sat .6 b .8 } forestobject { } forest { z -10 x -10 y 1.1 s .9 a -.1} forest { z -10 x 12 y 1.1 s .7 a -.1} } rule forest { forest { y 1 } } rule forest .5 { } rule moss { CIRCLE { } } rule forestobject { tree { hue 100 } } // make a tree like above rule forestobject { } // leave a piece of ground empty rule forestobject { TRIANGLE { s 3 } } // another kind of object rule tree { CIRCLE { s .5 1.2 } tree { s .97 y .3 r 1 sat .04 b .02} } rule tree .1 { tree { flip 90 } } rule tree .4 { tree { r 10 } tree { r -30 s .6} }
It is clear that this technique has its limitations, but with carefully weighed rules and some determination, quite satisfying pictures can be procured. In spite of occassional errors.
It might be inconvenient to give all the power to a recursive perspective rule. There are two techniques to increase your control over the depth of the perspective produced.
In the following picture, a village of some 12 buildings and some trees is portrayed. The houses seem to stand in irregular rows succeeding one another ( though there are some errors on closer look ). Rows it indeed is.
The whole code is not featured here. What we are chiefly interested in is the group of houses and how they are arranged as they are there.
rule village { // draw the village, made of 4* { s .9 y .8 x .5 z -10 } vilrow { } // four rows of houses, decreasing in size and z } // after one another rule vilrow { 3* { x 2.8 y -.1} makebase { z 200 s 1 .4} // each row of houses has three slots to place a house } rule makebase { makebase { y -.1 z .02} } // a slot might be brought "towards the viewer" rule makebase { makebase { x .1} } // or shifted a bit right rule makebase { makebase { x -.1} } // or left rule makebase .8 { makebase { x -.5 } // or sometimes a bit more left makebirch { x 1.5 y .1 z -1 s .1 .3} } // with a tree inserted in the available space // to the right from it rule makebase { house { } } // finally after these optional adjustments, // a house is created
Now it is obvious that this technique works for small villages and the like. Or let's think about an army.
A well ordered group of men. Any problems?
If we try to extend the lines, an obvious hindrance pops up.
The upper corners of the image appear curiously empty. Of course we could think about cropping it out of the picture like in the movies.
A context free grammar can not remember how many units it has used in a rule the previous round, so a special arrangement more sophisticated than that seems really impossible.
Above, two ways of defining a global perspective were used ( supposedly applying to the whole of the image canvas ).
But as Picasso showed (and as we should probably have remembered from the Middle ages), this kind of perspective is just a convention.
A local perspective can be used for our purposes. Most common is to use a unit object with well defined properties.
Think about a box.
rule CUBE {2*{s -1 1}SIDE{}TOP{}} rule SIDE {FACE{skew 0 30}} rule TOP {FACE[s 1.413 .816 r 135]} rule FACE {SQUARE{x .5 y -.5 a -.75}}
(The code above was gracefully submitted to the community by Guigui.)
How about making a few more?
rule cubes { CUBE { } cubes { z -1 s .99 x 1 y .58 } } rule cubes { CUBE { } cubes { s 1.01 z 1 y 1 } }
Although apparent only to close inspection, the boxes above increase slightly in size as they are piled on top of each other, and decrease in size when placed sideways "further away" from the viewer.
Let's make some towers!
rule cubes { CUBE { } cubes { s 1.01 z 1 y 1 } } rule cubes .1 { } rule towers { 10* { x 2 y .2 z -1} cubes { } }
This is actually pretty lame, but there's an idea.
Ok, let's make some really bloated towers.
startshape towers rule CUBE {2*{s -1 1 b .3}SIDE{}TOP{}} rule SIDE {FACE{skew 0 30 b .5}} rule TOP {FACE[s 1.413 .816 r 135 b .9]} rule FACE {SQUARE{x .5 y -.5 }} rule cubes { CUBE { } cubes { s 1.01 z 1 y 1 } spread { } } rule cubes .3 { } rule spread { cubes { s 0.99 z -1 x 1 y .58 } cubes { s 0.99 z -1 x -1 y .58 } } rule spread { cubes { s 1.01 z 1 x 1 y -.58 } cubes { s 1.01 z 1 x -1 y -.58 } } rule spread 15 { } rule towers { 10* { x 2 y .2 z -1 r -3} cubes { } }
Now we are getting there.. er, somewhere... Notice how the slight rotation of the pillars ( rule towers ) makes for a new kind of perspectival effect.
What is more enchanting than fractals, those self-similar things?
Actually context free fractals are a special subset of fractals, though amazing results can be produced with this subset already.
Fractal definitions can be used to fill in predefined shapes.
The houses in the section about 3D/fixed arrangements were painted using a simple fractal brush. Actually they were composed of relatively big squares and circles and nothing else.
Context free basic shapes pretty much define what kind of fractal shapes are most useful: the square, the triangle, and the circle.
With Context free, much about the mathematical qualities of fractals can be skipped. (Tutorials and other material should be available elsewhere.) A simple idea suffices: how to subdivide a shape into smaller portions. All kinds of graphical adjustments can be done on each subdivision round, ending in good results.
Fractal treatises often begin with the Sierpinski triangle. So be it.
startshape fract rule fract { TRIANGLE { } fract { y .288 s .5 b .1} fract { x -.25 y -.144 s .5 b .1} fract { x .25 y -.144 s .5 b .1} }
This is actually a curious alteration on Sierpinski. Each triangle is subdivided into three triangles of half the size (one fourth the area), leaving an upside-down triangle-formed hole in the center. Here, each round of subdivision brings about a gradual brightening ( b .1 ). That is what is strange in this Sierpinski. It is really only an approximate ( the figures could be more precise too, but for our purposes the geometry is really not needed ).
Lets try something else with the same triangle.
rule fract2 { TRIANGLE { } fract2 { y .288 s .5 b .1} fract2 { s .5 r 180 b .1} fract2 { x -.25 y -.144 s .5 b -.1} fract2 { x .25 y -.144 s .5 b .1} }
It is painted. Voilá. Bon voyage. One of the subdividing triangles was adjusted with ( b -.1), others with brightness increase.
One obvious objection pops up: it is awfully slow and inefficient most of the time (1,398,101 shapes for an image 442 x 390 pixels).
We don't really need to go to 'the bottom' all the way to get graphically appealing textures.
We'll try throwing in some randomness and examine two more advanced techniques.
Let's stick with the triangle form for some more time.
rule fract { TRIANGLE { } fract { y .288 s .5 b 1} fract { s .5 r 180 b -1 } fract { x -.25 y -.144 s .5 b 1} fract { x .25 y -.144 s .5 b 1} } rule fract .5 {}
Now, there was an additional ( fract ) rule, cropping some of the branches out of the execution. Shapes? 41,629 which is much much nicer. With this definition we most of the time get something like the following:
It's nice in its way, but if we need a more uniform surface texture than that, it's all crappy for our purposes. Should we go back to one million primitive shapes? Never, never, never! Uncle William instructs for something else.
Let's try placing some of these random-triangles on top of each other.
rule tripaint { 30* { } fract { hue 80 sat .6 a -.8 } } rule fract { TRIANGLE { } fract { y .288 s .5 b 1} fract { s .5 r 180 b -1 } fract { x -.25 y -.144 s .5 b 1} fract { x .25 y -.144 s .5 b 1} } rule fract .7 {}
Notice the ( 30* ) rule, with alpha set down to .2 . 175,661 shapes is not so bad, and it is not even optimized. Note that if you paint smaller areas, they will require less subdivisions, so these figures give a rough estimate for the overall screen area as well.
There has been some talk about ravishing shapes, but none were produced as yet. We'll take up a square with the same method and build some additional properties into the fractal brush.
In the following code, a square shape is filled with an irregular, grainy material. The brush is composed of three 'layers' of definition ( it is easy to conceive for example five too ). All the drawing is done with the rule somebox which, apparently, draws a box with some random properties: hue alteration, slight turning to either direction (this contributes to the roughness), a small chance for a bright spot of different color, and, finally, a rather dark altrnative or either a brighter and more saturated one.
This 'third layer' rule is called from the 'second layer' fractal rule ( like TRIANGLE from [ fract ] in the previous example ). Notice the ( z .01 ) directives in the recursive calls. They serve to ensure that smallest particles will be visible on the top, be there however many layers of the same fractal, so it in a sense sorts out the pile of primitive shapes according to our wishes.
The generating 'layer one' rule calls the previous rule sixty times in order to give a good chance for not too big grains.
startshape realmessfoury4 // 'layer 3' rule somebox3 {somebox3{hue -10}} rule somebox3 {somebox3{r 1}} rule somebox3 {somebox3{r -1}} rule somebox3 { SQUARE {hue 15} } rule somebox3 { SQUARE {hue -15} } rule somebox3 .1 { somebox3 {hue 50 b 1} } rule somebox3 { SQUARE {b -.5 sat -.1} } rule somebox3 { SQUARE {sat .9 b .3} } // 'layer 2' rule foury4 { somebox3{} foury4{x .25 y .25 s .5 r 1 z .01} foury4{x .25 y -.25 s .5 r 1 z .01} foury4{x -.25 y .25 s .5 r 1 z .01} foury4{x -.25 y -.25 s .5 r 1 z .01} } rule foury4 { foury4 {r 2 } } rule foury4 { foury4 {r -2 } } rule foury4 1.5 { } // 'layer 1' rule realmessfoury4{ 60*{} foury4 { } }
Now we'll try and marry our results with the towers from the 3D section, which should be very easy and straightforward.
startshape towers rule CUBE {2*{s -1 1 b .5 hue 10}SIDE{ b -.25}TOP{}} // increase the contrast between the cube sides slightly... rule SIDE {FACE{skew 0 30 b .5}} rule TOP {FACE[s 1.413 .816 r 135 b .9]} rule FACE {realmessfoury4{x .5 y -.5 }} // here is the only change we actually need to do rule cubes { CUBE { } cubes { s 1.01 z 1 y 1 } spread { } } rule cubes .3 { } rule spread { cubes { s 0.99 z -1 x 1 y .58 } cubes { s 0.99 z -1 x -1 y .58 } } rule spread { cubes { s 1.01 z 1 x 1 y -.58 } cubes { s 1.01 z 1 x -1 y -.58 } } rule spread 15 { } rule towers { 3* { x 2 y .2 z -1 r -3} cubes { hue 250 b .4 sat .8 a -.5} } // some additional color definition // the filling rule somebox3 {somebox3{hue -10}} rule somebox3 {somebox3{r 1}} rule somebox3 {somebox3{r -1}} rule somebox3 { SQUARE {hue 15} } rule somebox3 { SQUARE {hue -15} } rule somebox3 .1 { somebox3 {hue 50 b 2} } rule somebox3 { SQUARE {b -.5 sat -.1} } rule somebox3 { SQUARE {sat .9 b .3} } rule foury4 { somebox3{} foury4{x .25 y .25 s .5 r 1 z .01} foury4{x .25 y -.25 s .5 r -1 z .01} foury4{x -.25 y .25 s .5 r -1 z .01} foury4{x -.25 y -.25 s .5 r 1 z .01} } rule foury4 { foury4 {r 2 } } rule foury4 { foury4 {r -2 } } rule foury4 1.5 { } rule realmessfoury4{ 60*{} foury4 { } }
73,000 shapes, almost nothing at all...
There are the basic things to get started with fractal patterning. Experimentation will bring interesting results any time one cares to take the trouble.
Suppose you want to draw a shape rotated by a random angle -- let's say specifically by a random integer from 0 to 359. One approach would be to define 360 rules, each of which rotates the shape by a different angle:
rule RandomRotate { MyShape { } } rule RandomRotate { MyShape { r 1 } } rule RandomRotate { MyShape { r 2 } } rule RandomRotate { MyShape { r 3 } } rule RandomRotate { MyShape { r 4 } } /* * */ rule RandomRotate { MyShape { r 359 } }
That's a lot of rules to enter for just one effect! Fortunately, there's a better approach, based on the concept of a binary search. The idea is first to rotate either not at all or by half a circle (i.e., either 0 or 180 degrees), then to rotate the result either not at all or by a quarter of a circle (i.e., either 0 or 90 degrees), then to rotate the result either not at all or by an eighth of a circle (i.e., either 0 or 45 degrees). At this point, we've rotated by one of 0, 45, 90, 135, 180, 225, 270, or 315 degrees -- eight possible angles from only six rules. So far, so good.
If we divide by two again we're going to start seeing non-integer angles. However, nothing says we have to keep dividing by two. For the next rotation we can rotate by one of {0, 15, 30} degrees, giving us any of 24 possible angles as illustrated below:
The last steps are to rotate by one of {0, 5, 10} degrees and finally by one of {0, 1, 2, 3, 4} degrees. In the end, it takes only 18 rules to rotate a shape by any of 360 unique angles! The rotation is also very fast: only seven rules are actually executed no matter what angle is finally chosen.
Here's a sample picture that demonstrates the preceding technique:
startshape Begin background { h 200 sat 1 b 1 } rule DrawArrow { SQUARE { x 0.5 size 1 0.075 } TRIANGLE { x 1 r 30 size 0.2 } } rule RandomRotate1 { RandomRotate2 { } } rule RandomRotate1 { RandomRotate2 { r 180 } } rule RandomRotate2 { RandomRotate3 { } } rule RandomRotate2 { RandomRotate3 { r 90 } } rule RandomRotate3 { RandomRotate4 { } } rule RandomRotate3 { RandomRotate4 { r 45 } } rule RandomRotate4 { RandomRotate5 { } } rule RandomRotate4 { RandomRotate5 { r 15 } } rule RandomRotate4 { RandomRotate5 { r 30 } } rule RandomRotate5 { RandomRotate6 { } } rule RandomRotate5 { RandomRotate6 { r 5 } } rule RandomRotate5 { RandomRotate6 { r 10 } } rule RandomRotate6 { RandomRotate7 { } } rule RandomRotate6 { RandomRotate7 { r 1 } } rule RandomRotate6 { RandomRotate7 { r 2 } } rule RandomRotate6 { RandomRotate7 { r 3 } } rule RandomRotate6 { RandomRotate7 { r 4 } } rule RandomRotate7 { DrawArrow { } } rule DrawRandomArrows { 30*{ b -0.05 z 1 } RandomRotate1 { } } rule Begin { DrawRandomArrows { h 240 sat 1 b 1 } }
Note that this technique applies just as well to selecting random colors, random distances, random tower heights, etc. as it does to random angles.