Hex.hs is written in a pretty imperative fashion, it mostly happens inside Gtk2Hs's Cairo Render monad. The core of the top-level drawing loop works like this:

mapM_ (\y -> do

mapM_ (drawHexagons rotation cylinderRadius rowCount col) [col*2 .. col*2+rowCount/6-4])

[0..columns-1]

It draws a row of hexagons around a cylinder at y-offset

`col`

, for x-offsets from `col*2`

to `col*2 + rowCount/6 - 4`

. As the x-offset grows with the y-offset, the rows are offset from each other, forming a diagonal stripe moving down and to the right. But because we are drawing the hexagons on a cylinder, the stripe moves down and *around*the cylinder.

The

`rotation`

parameter gives the initial rotation of the cylinder coordinate system, and is based on the current time. As time changes, the rotation does too. And as we draw a new frame with a new time after having shown the previous one, we get an animation.drawHexagons calls drawHexagon twice, drawing a \-segment of a hexagon row. The drawHexagon call does all the actual drawing and goes as follows:

drawHexagon rotation cylinderRadius rowCount col row = do

--offset odd rows down (remember that we draw like \\\\)

let y = if (floor row) `mod` 2 == 0 then 0 else 1.732

--transform the hexagon from [-1..1] coordinates to the cylinder coordinate system

--read from bottom up

let rhex = map (

scaleP (2*pi*r/rowCount) . --scale up so that rowCount hexagons go around the cylinder

translateP (rowCount*rot/(2*pi) + row) (y+col*1.732*2) . --move it to the wanted position

rotateP (pi/2) --rotate the hexagon 90 degrees

) hexagon

--project the hexagon from cylinder coordinates over to screen coordinates

let hex = map (cylinderProjection r) rhex

--and draw the hexagon

save

newPath

uncurry moveTo $ head hex --move to the first point of the hexagon

mapM_ (uncurry lineTo) $ tail hex --apply lineTo to the rest of the points

closePath --and close the path

setLineWidth 1

--fill some of the hexagons and stroke the rest

if (floor (row+col)) `mod` 4 == 0

then fill

else stroke

restore

Then we need the definition of a hexagon:

--ngon creates a regular polygon as a list of (x,y)-tuples in [-1..1] coordinate space.

ngon n =

map nrot [0..n-1]

where nrot i = let a = 2*pi*i/n in

(cos a, sin a)

hexagon = ngon 6

The function to project cylinder coordinates to display space:

--maps the x-coordinate around a cylinder, growing right so that

--0 => 0, 0.5*pi*r => r, pi*r => 0, 1.5*pi*r => -r and 2*pi*r => 0

cylinderProjection r (x, y) = (r * sin (x/r), y)

And the 2D point transformations:

scaleP f (x,y) = (x*f, y*f)

translateP u v (x,y) = (x+u, y+v)

rotateP a (x,y) = (cos a * x - sin a * y, sin a * x + cos a * y)

And there we have it. Create coordinates for the objects you want to draw, project them to the screen space, and draw them. Simple as pie.