Drawing Hexmaps

The other day, Thomas Guest talked about drawing chessboards, and ended with a challenge. I wanted to answer a different challenge, however. What if, instead of drawing on a rectangular grid, we wanted to draw on a hexagonal grid? The following is my slapdash answer. For real-world use I’d make nice classes and pass more parameters to the methods, but to demonstrate the math I’m just going to use global constants and functions.

Like Thomas’ article, I will show solutions using several different tools, in this case Apple’s Core Graphics, PyGame, Python Imaging Library (PIL), and SVG. All of these solutions will use the same constants and math:

# Constants used by each solution

from math import sin, cos, pi, sqrt
THETA = pi / 3.0 # Angle from one point to the next
HEXES_HIGH = 8 # How many rows of hexes
HEXES_WIDE = 5 # How many hexes in a row
RADIUS = 30 # Size of a hex

# Functions (generators) used by each solution

def hex_points(x,y):
    '''Given x and y of the origin, return the six points around the origin of RADIUS distance'''
    for i in range(6):
        yield cos(THETA * i) * RADIUS + x, sin(THETA * i) * RADIUS + y

def hex_centres():
    for x in range(HEXES_WIDE):
        for y in range(HEXES_HIGH):
            yield (x * 3 + 1) * RADIUS + RADIUS * 1.5 * (y % 2), (y + 1) * HALF_HEX_HEIGHT

Now, given the above, what does the code look like to draw the hexes? Because each library handles colours slightly differently, we will need a generator for colours (and we will need more than just black and white as the chessboard used, because each hex borders on six others). I haven’t given a lot of thought to optimal colouring schemes: each colour generator simply produces red, yellow, blue, and green in a cycle. Here is the image produced by the Core Graphics solution, followed by the code:

Hex Image 1

def quartz_colours():
    while True:
        yield 1,0,0,1 # red
        yield 1,1,0,1 # yellow
        yield 0,0,1,1 # blue
        yield 0,1,0,1 # green

def quartz_hex():
    '''Requires a Mac with OS 10.4 or better and the Developer Tools installed'''
    import CoreGraphics as cg
    colours = quartz_colours()
    cs = cg.CGColorSpaceCreateDeviceRGB()
    c = cg.CGBitmapContextCreateWithColor(IMAGE_WIDTH, IMAGE_HEIGHT, cs, (0,0,0,.2))
    for x,y in hex_centres():
        points = list(hex_points(x,y))
        [c.addLineToPoint(*pt) for pt in points]
    c.writeToFile("quartz_hexes.png", cg.kCGImageFormatPNG)

Now for some cross-platform examples. Here is the image generated by PyGame, followed by that code:

Hex Image 2

def pygame_colours():
    while True:
        yield 255, 0, 0 # red
        yield 255, 255, 0 # yellow
        yield 0, 0, 255 # blue
        yield 0, 255, 0 # green

def pygame_hex():
    '''Requires PyGame 1.8 or better to save as PNG'''
    import pygame
    screen = pygame.display.set_mode((IMAGE_WIDTH, IMAGE_HEIGHT))
    colours = pygame_colours()
    for x,y in hex_centres():
        pygame.draw.polygon(screen, colours.next(), list(hex_points(x,y)))
    pygame.image.save(screen, 'pygame_hexes.png')

When you run the PyGame script, it will actually pop up a window very briefly, draw into the window, save the result, and close the window. I also didn’t get the PyGame script to add transparency for the background, although I think it could be added fairly easily. Now, for the web, here is a solution in SVG, with the image captured by screenshot in Safari, followed by the Python code, and the resulting SVG code:

Hex Image 3

def svg_colours():
    while True:
        yield 'rgb(255, 0, 0)'
        yield 'rgb(255, 255, 0)'
        yield 'rgb(0, 0, 255)'
        yield 'rgb(0, 255, 0)'

def svg_hex():
    out = open('svg_hexes.svg', 'w')
    print >> out, '''<?xml version="1.0" standalone="no"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg width="%spx" height="%spx" version="1.1" xmlns="http://www.w3.org/2000/svg">''' % (IMAGE_WIDTH, IMAGE_HEIGHT)
    colours = svg_colours()
    for pt in hex_centres():
        print >> out, '<polygon fill="%s" stroke-width="0" points="%s" />' % (colours.next(),  ' '.join(["%s,%s" % (x,y) for (x,y) in hex_points(*pt)]))
    print >> out, '</svg>'

And here is the SVG created by the above script: SVG Hexes

Finally, one library which overlaps with the ones used by the Chessboard example: Python Imaging Library.

Hex Image 4

pil_colours = pygame_colours  # same format works, so we'll re-use it

def pil_hex():
    import Image, ImageDraw
    image = Image.new("RGBA", (IMAGE_WIDTH,IMAGE_HEIGHT), (0,0,0,0))
    colours = pil_colours()
    draw = ImageDraw.Draw(image)
    for x,y in hex_centres():
        draw.polygon(list(hex_points(x,y)), fill=colours.next())
    image.save('pil_hexes.png', 'PNG')

That’s it for my examples. Thomas ended with a challenge for displaying chess, and for describing the position. To describe the position, I would use a standard chess notation, such as described here. For my challenge, what other formats would be useful to create hex maps in? POVRay? Flash? Any other examples out there?

3D, it’s not just for breakfast anymore

I’ve been fooling around with 3D lately. First off, my third article as guest-writer for David Mertz’s XML Matters column, The Web ain’t just for 2D anymore went live on IBM’s developerWorks site today. It’s about X3D (3D in XML), successor to VRML, and the possibility of it being relevant today. I have moderate hope for it, now that SVG is starting to be a player. The funny thing is, I think X3D is probably less complicated to implement than SVG is. The real coolness starts when you can combine them, but that is still a ways off.

Years ago, I was the lead programmer for Antarcti.ca’s 3D web client (which was discontinued awhile back), and before that I implemented a simple 3D renderer in Java AWT (this was before Swing, and way before Java3D. So I’ve been tinkering around with 3D for awhile. Lately I’ve volunteered to take a stab at porting VPython over to OS X Aqua (it can be built for OS X, but only under X Windows, which doesn’t appeal to me). So far, the build process for it has been stumping me, and soaking up what little time I have to devote to my hobby coding, but I still plug away at it from time to time. It’s a C++ extension for Python which relies on boost, glib, and OpenGL libraries, and it uses autoconf in a fairly non-auto way. I’ve never been expert at build systems, most python projects I’ve needed were either .configure;make;make install or python setup.py install, so the struggle to port this really bugs me, but VPython is a very cool project and I want to use it (and I don’t want to give in and rely on fink and X). Sometimes I’m too stubborn for my own good.

In a previous post I mentioned that I was thinking of writing a tool for screencasting from OS X. It turns out that while Apple has included more advanced Cocoa libraries for Quicktime in Tiger, there isn’t a convenient way to create new, writable movies from Cocoa, so that project has stalled, for the time being. There is a solution, but I’m trying to wrap up other things before I delve into it.

I have two projects nearly ready to release which are both larger examples of using PyObjC. One is my own project, DrawingBoard, which is being tested right now by both my kids and my friend Michael’s kids. I’m about ready to let other people see it, rough as it still is. The other project is a port of Apple’s Sketch example code from Objective-C into Python, which gives examples of how to use Core Data, undo/redo, and many other things. Both will be coming soon.

Declarative animation with SVG

One of the things which has been taking up my time from releasing Drawing Board is that I’ve been writing another guest edition of my friend David Mertz’s XML Matters column for IBM developerWorks. The latest, SVG and the Scriptless Script went live yesterday.

My last article, on using the DOM, was something I know well and have used for years. This one was on a subject I’ve wanted to learn more about, so getting the examples all working the way I wanted was challenging, but I’m really happy with the result.

Another reason I haven’t released Drawing Board yet is that I want to put up a screencast of it in use, but there doesn’t appear to be a good solution for creating screencasts on OS X. I think I’ve got a solution for that.