Tiling and tessellation with a Context Free Grammar - Part 2

In my previous blog post we looked at how to use a Context Free Grammar to make simple grids of circles and then use colour variation to produce a rainbow effect. Now let's look at making more interesting patterns of shapes.

Simple spirals

First let's draw a series of ever decreasing, spiralling triangles...

#lang s-exp stamps/lang

(define-shape spiral
  (triangle)
  (spiral [r 22]
          [x .8]
          [s .95]))
          
(maximum-render-cycles 1000)
(start-shape spiral)

Play with those numbers in square brackets to produce something you like. Remember, r=rotation, x=x translation, s=scale.

Recursive loops

This example draws a hexagon with a circle of hexagons around it, and for each of those hexagons, it draws a circle of hexagons around it, recursively building up a pattern that fills the artwork.

#lang s-exp stamps/lang

(define-shape hex-circle
  (hexagon)
  ((loop ([i 6])
         (hex-circle 
           [r (* 360 (/ i 6))]
           [y .9])))
  )

(define-shape scene
  (hex-circle [alpha -.7]
              [b .2])
  )

(maximum-render-cycles 1000)
(start-shape scene)

There's a few new concepts in this code:
  • A loop construct to draw a circle of 6 hexagons. There's a lot of brackets in there! These denote the loop; the binding, in this case i takes the values from 1 to 6; then the loop body, in this case a call to hex-circle with attributes to draw each at the right position around the central hexagon.
  • A bit of lisp-style maths: (* 360 (/ i 6)) means 360*(i/6), in other words, rotate by a sixth of a circle for each loop iteration. 
  • We use a scene function to set up the basic attributes, for everything drawn: reduced alpha (transparency) and reduced brightness.
As with previous examples, play with the numbers, or add in extra adjustments to create something you like.

Recursive branching

You may have noticed in the previous example that we drew many hexagons on top of other hexagons, resulting in dark hexagons in the centre of the pattern. As an alternative we can instead draw just two outer shapes, positioned at alternative angles (in this case -60˚ and 60˚), the effect of recursion then fills the plane.

#lang s-exp stamps/lang 

(define-shape C
  (circle)
  
  (C [r -60] [y .5])
  (C [r 60] [y .5])
)

(define-shape scene
  (C 
     [alpha -.9]
     [b .2]))

(maximum-render-cycles 1000)
(start-shape scene)

Let's introduce a few variables to make playing with the numbers easier. This code is the pretty much the same as the above, but with the angle and space set at the start of the program (and red dots):

#lang s-exp stamps/lang 

(define angle (/ 360 8))
(define space 3)

(define-shape C
  (circle)
  
  (C [r (- angle)] [y space])
  (C [r angle] [y space])
)

(define-shape scene
  (C 
     [alpha -.9]
     [b 1]
     [sat 1]
     [hue 0]))

(maximum-render-cycles 50000)
(start-shape scene)

What next? Let's explore tessellation with regular shapes such as pentagons, hexagons and heptagons... (coming soon)

Tiling and tessellation with a Context Free Grammar

There's something pleasing about filling a space with shapes that fit together and overlap in interesting ways. Using a Context Free Grammar we can use a simple language to express relations between shapes and make a large composition with a few rules.



In this series of blog post I explore tiling with different shapes, starting with simple grids and circular patterns with single shapes, then exploring more complex combinations of shapes. 

The tool I'm using to explore these patterns is Racket Stamps, which runs on the Racket language. Download both for free by following these links. 

What's a Context Free Grammar?

From Wikipedia a CFG is: "a set of production rules that describe all possible [compositions] in a given language... production rules are simple replacements."

So a CFG allows you to represent drawings by simple shape compositions, for example this Racket Stamps code draws an infinite line of circles by defining circles as a circle followed by circles to the left:

#lang s-exp stamps/lang
                        ;; Comments:
(define-shape circles   ;; define circles to be:
  (circle)              ;; a circle
  (circles [x 1]))      ;; followed by circles to the left

If you run that in Racket Stamps (and do check out the tutorial first) you'll see that the line is actually not infinite, just very long. We can control how far the rendering runs, and therefore how many shapes are drawn using the setting maximum-render-cycles -- you'll see this in the examples below.

Tiling a plane

So we can draw a line, but how would we fill a 2D space? The first method to try is to make a grid of shapes. A grid is a line of shapes, with a grid placed above it, which is a line of shapes with a grid placed above it... thanks to recursion we can fill the space, here's the code:

#lang s-exp stamps/lang

(define-shape line-of-circles
  (circle)
  (line-of-circles [x 1]))

(define-shape grid
  (line-of-circles)
  (grid [y 1]))

(maximum-render-cycles 1000)
(start-shape grid)



Because of the way the rendering works (and in particular how the maximum-render-cycles setting works), we actually get a triangle of circles! But we can crop into a square, so code like the above works well to help us explore patterns. Try this by adding this setting to the above example:

  (bounding '(-16 -12 -1 0.50))

Making adjustments

Do you see those xs and ys in the examples above, enclosed in square brackets? They adjust the position of shapes so that we don't draw everything on top of each other. There are lots of other adjustments you can use, try out the following:
  • rotate (or r) by a number of degrees
  • scale (or s) by a factor, 1 is same size, .9 is 10% smaller, 1.1 is 10% bigger 
  • shear by a factor between -1 and +1, 0 is no shear
  • hue (or h) change colour by degrees on the colour wheel 
  • saturation (or sat) between 0 and 1
  • brightness (or b) between 0 and 1
  • alpha (or a) between 0 and 1
In many cases the adjustment modifies the attribute by whatever you specify, so that you can gradually make things bigger or smaller, or change the colour. Try changing the definition of grid to the following:

(define-shape grid
  (line-of-circles [sat 1] [b 1]) ;; Full saturation and brightness
  (grid [y 1]
        [hue 10] ;; Move through the colour wheel by 10 degrees
))


Next...

Let's try to make something more interesting than a grid of circles... Tiling and Tessellation part 2.