Code Sketch

Spiral of hexagon tiles with alternating tile designs and changing color

By: Mike

Category: Art

// The plane can be tiled with a "honeycomb" of hexagons. The only other // regular polygons that can do so are squares and equilateral triangles // (and the latter only work if half of them are oriented "upside-down"). // // This script shows how the turtle can cover the plane with hexagonal tiles // by winding around an initial hexagon in a continuous spiral. Two different // but interlocking tile designs are alternated on successive loops of the // spiral, and the colors used are gradually altered. // // Why not try redesigning the tyle types, or create a new one and try adding // it to the pattern? (There are some hints in the comments.) Make your design // a similar size to the hexagon, and remember when drawing a tile that // the turtle starts and ends at the bottom-left vertex, facing north! clear() import scala.math._ //gives access to square root and trig functions // Length of the sides of the hexagons we're tiling val tileSide: Double = 40 // How many times should the spiral loop around the initial hexagon? val loops = 4 // How fast do you want the turtle to move? 1000 means normal speed, // 500 means double-speed, 10 means hundred times faster than normal // and use 0 if you want the turtle to draw instantly val speed = 100 // Initial ink colors and tyle type to use // These will be changed later so use var not val var firstColor = color(235, 0, 20) var secondColor = color(20, 0, 235) var tyleType = 1 // In your own tile pattern, remember to start and end in the bottom-left, // facing north, and all sides of the hexagon are tileSide long. // Here two types of tile are defined: def drawTileType1{ //draw equilateral triangles setPenColor(firstColor) penDown left(30) //facing direction of bottom-left side repeat(6){ //for each side of the hexagon, draw an equilateral triangle repeat(3){ forward(tileSide) right(120) } //join midpoints of the two sides of the triangle other than the base //this creates a smaller equilateral triangle in the middle of the tile right(60) repeat(3){ forward(tileSide/2) left(60) } right(180) } penUp right(30) //facing north again penUp // draw pentagon pattern setPenColor(secondColor) penDown left(30) //facing direction of bottom-left side repeat(6){ repeat(5){ //draw a pentagon using this side of the hexagon as a base forward(tileSide) right(72) } forward(tileSide) //move on to next side of hexagon right(60) } penUp right(30) //face north again } def drawTileType2{ // Draw the circle inscribing the hexagon. // Centered at at center of tile, radius tileSide*sqrt(3)/2 // Must first move to be tangent to circle, with center on the left. // (Another way to visualize this: the turtle must be lie on the end of // a radius of the circle at right angles to the turtle's heading, and // the turtle's heading must move it clockwise around the circle.) right() forward(tileSide/2) //to be tangent to circle, with center on left setPenColor(secondColor) penDown circle(tileSide*sqrt(3.0)/2) penUp back(tileSide/2) left() //now back in bottom-left vertex facing north // draw intersecting square pattern inside tile setPenColor(firstColor) penDown repeat(6){ forward(tileSide) right() forward(tileSide) right() forward(tileSide) right(120) } penUp // Draw circle centered at center of tile, radius tileSide/2 // this is the circumcircle of the the smaller hexagon formed by // the intersecting square pattern. Draw it last as otherwise covered over! right(30) //oriented towards center of the hexagon forward(tileSide/2) //gets half way to the center right(90)//to be tangent to circle with center on the left setPenColor(secondColor) penDown circle(tileSide/2) penUp left(90) back(tileSide/2) left(30) //now back in bottom-left vertex facing north } // drawTile(i) should draw a tile with design i // We can use matching to make sure the right tile drawing is called. // If you add a 3rd or 4th design what will you have to change here? def drawTile(tyleType: Int) = tyleType match { case 1 => drawTileType1 case 2 => drawTileType2 } // Each time the spiral winds around, it draws six sides ("arms") around // the existing tiles. To draw an arm we need to know how many tiles long // it should be. We will draw each tile, starting and finishing in the // bottom-left vertex of its hexagon, and move into position for the next tile. // Finally the turtle turns to face the direction of the next arm. // // To draw each tile consistently we must start and end facing north. So it's // helpful to know the orientation of each arm, measured by the angle the turtle // has to turn through to face north. // // Arms in different windings will use a different tyle type. def drawArm(tiles: Int, orientation: Double, tyleType: Integer) { repeat(tiles) { left(orientation) // now facing north drawTile(tyleType) //starts and finishes in bottom-left of tile's hexagon right(orientation) //now facing in direction of arm again // Trigonometry tells us the turtle should move in the arm's direction // in a straight line of distance square root 3 times the tile side. // So we could use: forward(tileSide * 1.73205081) // But that path doesn't follow any of the sides of the hexagons in // our grid, so to show the grid more clearly let's get to the // bottom-left vertex of the next tile by traversing sides only. // For arms at a bearing of 0, 120 or 240 degrees the side most closely // matching the arm's direction is 30 degrees to the left, for the other // arms it is 30 degrees to the right. We can use division remainder: // orientation%120-30 is -30 or 30 for the two groups of arms. right(orientation%120-30) //align with nearest side forward(tileSide) //move to next vertex left(2*(orientation%120-30)) //align with next side forward(tileSide) //move to destination vertex right(orientation%120-30) //realign with arm direction } right(60) // arms wrap around clockwise, so turn right to start next arm } // We wind each loop of the spiral clockwise by selecting a tyle type to use // and drawing the loop's six arms Arms have different lengths, and orientations // change by 60 degrees. We use the the number of tiles in the shortest (first) // arm to specify how large to draw the loop. def windSpiral(shortArm: Int) { // The tyle design used can be changed between loops. The initial tile was // type 1. The first loop around it should be type 2. Hence, even loops // should be type 2 and odd loops type 1. // // A remainder function can tell us which type's turn it is! shortArm takes // values 0, 1, 2, 3... on successive loops so shortArm%2 is 0 on first and // other odd loops, 1 on even loops. We could use the if expession: // tyleType = if (shortArm%2==0) 2 else 1 // // But in case you want to add more tile designs, let's use match instead. // If you add a third design, you'll have to use divisor 3 and will have // three possible remainders (0, 1 and 2) to deal with. tyleType = (shortArm%2) match { case 0 => 2 case 1 => 1 } // Initially the turtle is ready to draw the tile immediately above the // lowest tile of the left side (which is in the 6th arm, so the 1st arm is // unusually short). Continue north along the left side, finishing in // position to start the highest tile of the left side. (On the very first // loop this is just the initial position, so the arm has length 0). drawArm(shortArm, 0, tyleType) // draw top tile of left side (bottom-left tile of upper-left side) // head on bearing of 060 degrees along upper-left side, finish in position // to start top tile drawArm(shortArm + 1, 60, tyleType) // draw top tile then head on bearing of 120 degrees along upper-right side. // finish in position to start bottom-right tile of upper-right side. drawArm(shortArm + 1, 120, tyleType) // draw top tile of right side (bottom-right of upper-right side) // head south along right side // finish in position to start bottom tile of right side drawArm(shortArm + 1, 180, tyleType) //draw bottom tile of right side (top-right tile of lower-right side) //head on bearing of 240 degrees along lower-right side //finish in position to start the bottom tile drawArm(shortArm + 1, 240, tyleType) // Draw bottom tile and head on bearing of 300 degrees along bottom-left // side, drawing all tiles on this side including the upper-left (the // bottom tile of left side) - hence this side is unusually long. // Finish in the bottom-left vertex of the tile to the upper-left of the // final tile drawn, facing north - this is the starting position for the // first arm of the next loop of the spiral. drawArm(shortArm + 2, 300, tyleType) } //with all the definitions complete, let's start tiling! setAnimationDelay(speed) // speed up turtle penUp() // only when drawing the tiles should the pen be down // draw the initial tile, finish in bottom-left vertex facing north drawTile(tyleType) //move to what will become bottom-left vertex of hexagon to the upper-left of //the initial tile, and face north: in correct position and orientation for //the first (northbound) arm of the spiral. Note that in the first winding //around the initial hexagon, the northbound arm has length zero, so this tile //actually gets drawn as the first tile of the second arm! left(30) forward(tileSide) left(60) forward(tileSide) right() // Now wind the spiral around the initial hexagon! // with each winding, the size of the short arm increases by 1 for (i <- 0 to loops - 1) { //as loops have shortest arms 0, 1, 2, etc // We can gradually alter the colors each loop. // The colors will be (127, 128, 0) and (0, 128, 127) at final loop. if (loops != 0) { //prevents division by 0 errors firstColor = color(235 - 128/loops*(i+1), 128/loops*(i+1), 20) secondColor = color(20, 128/loops*(i+1), 255 - 128/loops*(i+1)) } windSpiral(i) }

Mike said:

A couple of typos in the commented section unfortunately. When trying to move the turtle into position to draw a circle,

```
// (Another way to visualize this: the turtle must lie on the end of
// a radius of the circle, at right angles to the turtle's heading, and
// the turtle's heading must move it anticlockwise around the circle.)
```

This pattern is very pretty at high resolution - it's better with a couple more loops to the spiral, so that the color change is more gradual and the inner and outer hexagons blend together better.

**Your Comment:**