2011-05-31

Minecraft mapping – Diagnostic texture

(Part of a series on rendering a top-down Minecraft map.)

As we will soon see, terrain.png isn't great for diagnosing problems if something goes wrong. For one thing, it has lots of gaps, so if we accidentally get the wrong textures there's a good chance we'll just end up rendering blank space and not being able to figure out how our calculation is wrong. Also, many of the textures in it don't have an obvious orientation, so if we flip them or rotate them we might not notice. To avoid these problems, it's useful to make ourselves a diagnostic texture without these problems.

Rather than spend hours of tedious work in a paint package, we can do this programmatically with Pygame. On Ubuntu, Python 2.6 was already installed and I installed Pygame with:
sudo apt-get install python-pygame
We're making a 512×512 texture that's a 16×16 grid of 32×32 pixel squares. Each square has a number between 0 and 255 printed in the middle and a solid grey background with intensity equal to the value printed on it. In Pygame, you render text to a new surface and then blit that surface onto your target. Also note that we need to initialize Pygame before we do any font rendering. Other than that, there's not a lot to comment on here.
import pygame

def make_numbered_texture_atlas():
    surface = pygame.Surface((512,512))
    font = pygame.font.SysFont("arial",14,bold=True)
    for i in xrange(256):
        y = 15 - (i // 16)
        x = i % 16
        fg = (255,255,255) if i<=127 else (0,0,0)
        bg = (i,i,i)
        textimage = font.render(str(i), True, fg)
        w,h = textimage.get_size()
        surface.fill(bg, (32*x, 32*y, 32, 32))
        surface.blit(textimage, (32*x+16-w//2, 32*y+16-h//2))
    return surface

def main():
    pygame.init()
    pygame.image.save(
        make_numbered_texture_atlas(),
        "numbered_texture_atlas.png")

if __name__ == "__main__":
    main()
Next time we'll use PyOpenGL to render something on-screen.

2011-05-27

Minecraft mapping – 2D rendering strategy

This is the first in a series of articles about writing a Minecraft mapping tool in Python. I'm aiming for somewhere between a tutorial and a general discussion of the things that I think are cool or interesting. Today I'll describe how we're going to render the map.
To understand what we're doing, you'll probably want to be familiar with Minecraft. To recap briefly, playing Minecraft involves exploring and building in a world of axis-aligned 1m side cubic blocks. Each cube might be filled with one of various materials, such as earth, air, stone, water or sand. The world extends indefinitely in both horizontal directions and has a vertical extent of 128m, with mostly impenetrable rock blocking access to the lower boundary and merely empty space into which blocks may not be placed above the upper boundary.
In subsequent posts we'll talk more about exactly how we're going to take this 3D world and flatten it into a 2D map. For now, we're going to suppose that we have a 2D grid of square cells and in each cell we have the Minecraft block ID that describes the content of that cell and we want to render the corresponding Minecraft block texture in it. (We will also ignore the fact that some Minecraft blocks are partially transparent for now.)
There are various ways we could do this:
  1. Load the Minecraft textures into a Pygame Surface, loop through the map of block IDs and for each one blit the corresponding texture onto the corresponding position on-screen. Pretty easy, possibly quite slow, and not enough fun.
  2. Load the Minecraft textures into an OpenGL texture as a "texture atlas", loop through the map of block IDs and create a pair of triangles for each with appropriate texture coordinates to apply the right texture from the atlas. This is a bit more tricky, but once we've created the vertices and indices that make up the triangles, it should be pretty fast, because we can render everything frame after frame without needing to send any large amount of data to the graphics card. However, it's still not very fun.
  3. Load the Minecraft textures into one OpenGL texture as above, but load the block ID map into another texture. Render one big quad and write a pixel shader that first looks up the block ID map and then uses the value it finds there to look up the block texture atlas. This is still quite tricky, but it also lets us do some other interesting stuff, and it's fun. Since I have very little experience writing shaders, I thought this would be a great idea to try.

To get a bit more concrete, we're going to feed in two textures. One is a texture atlas containing a 16x16 grid of textures – so 256×256 pixels if each texture is 16x16 - which will be very similar to the terrain.png file from Minecraft. You can see mine on the right. (As I mentioned before, I'm using the awesome Painterly Pack. All the cool textures are from that. Only a few special top-down views in the bottom-right were drawn by me.) The other texture that we will feed in will be a specially encoded 32-bit RGBA format texture where the values in the red channel (between 0 and 255) specify one of the textures in the texture atlas. (Later on we'll use the other channels for various other things.) We'll number the texture atlas textures from 0 in the top left through to 15 in the top right and in rows down to 255 in the bottom right. The dimensions of this texture will depend on what size of map we want to render in one go. A Minecraft region is 512×512 and this is a convenient enough size for us, so we'll go with that for now. The next article will be some preparatory groundwork – creating a useful diagnostic texture which we can use in place of terrain.png to properly understand what's going on and more easily spot when we've made a mistake. After that, we'll move on to doing OpenGL using PyOpenGL.




2011-05-24

Minecraft mapping with Python, Pygame and OpenGL


When playing Minecraft, I love doing stuff underground: mining, spelunking and digging great big tunnels. I really wanted to map out the mines I have dug and the caverns I have explored. There are a lot of great Minecraft mapping tools, but not many of them that seem oriented to underground mapping. I also thought it would be fun to write my own, and I have a few ideas for distinct directions to develop it in when it gets far enough. This series of articles will cover all the code that I've written and will span a range of topics including Pygame, OpenGL, Numpy and the structure of Minecraft maps.
You can see some teaser images of the sort of things I have working right now over on the right. The textures are from the Painterly Pack, which I thoroughly recommend as a Minecraft texture pack.
Here's the rough plan for the series:
  1. 2D rendering strategy
  2. Pygame – Making a diagnostic texture
  3. PyOpenGL – Rendering with a pixel shader
  4. PyOpenGL – Tiled rendering with a pixel shader
  5. Pygame – Zooming and panning
  6. NBT – Reading Minecraft files
  7. Numpy – Flattening a Minecraft map
  8. Ambient occlusion part 1 and part 2
  9. Rotating and flipping to render mine-cart tracks
  10. Lighting (Never finished this one.)
The tentative schedule is going to be Tuesdays and Fridays. This Friday we'll start with an overview of the basic rendering strategy for our maps.