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)
}