Dungeon floors. You've seen 'em, I've seen 'em. We've all of us died on 'em at some point in our lives. Here is a fairly simple method I've come up with for creating a rough-stone paved floor texture that tiles, suitable for entry in the category of Programmer Art.
I start with a Voronoi cell diagram. A Voronoi diagram is an image that is partitioned into discrete regions or areas based on a collection of randomly generated seed points. The image space is populated with a number of these seed points, then each pixel of the image is given a color based on which point it is nearest to. So all pixels nearest a white point will be colored white. This fractures the image space into a number of polygonal shapes that form the basis of our tile floor. However, there is a condition: the cell diagram must tile with itself. In order to ensure this, we need to construct the Voronoi diagram with a specially constructed seed set. We populate an image area the size of our final texture with a set of seed points, then we copy this set 8 times, and "arrange" the sets in a 3x3 block pattern. Then we calculate the diagram for the central section of the 3x3. Since the neighboring areas have the same seed point pattern, the cells are constructed such that the final diagram tiles perfectly with itself.
A sample bit of Lua code to generate such a diagram (and the code I used to generate the diagram for this article) is:
valuearray=CArray2Dd()distarray=CArray2Dd()pointlist={}function populate_point_list(numpoints, width, height) -- Each point is duplicated 8 times local count pointlist={} for count=1,numpoints,1 do local point={} point.id = count point.x = randRange(0,width-1) point.y = randRange(0,height-1) local x,y for x=-1,1,1 do for y=-1,1,1 do local newpoint={} newpoint.id=point.id newpoint.x=point.x+x*width newpoint.y=point.y+y*height table.insert(pointlist, newpoint) end end end endfunction find_closest_point(x,y) local i local mindist=1000000 local id=0 for i=1,#pointlist,1 do local p=pointlist local dx,dy = p.x-x, p.y-y local dist=math.sqrt(dx*dx + dy*dy) if dist mindist=dist id=p.id end end return mindist,idendfunction tiling_voronoi(numpoints, width, height) populate_point_list(numpoints, width, height) valuearray:init(width,height) distarray:init(width,height) local x,y for x=0,width-1,1 do for y=0,height-1,1 do local dist,id = find_closest_point(x,y) distarray:set(x,y,dist) valuearray:set(x,y,id) end endend
In the above source, CArray2Dd() is a type that simply encapsulates a 2-dimensional array of double values. To generate a diagram, the function tiling_voronoi is called with the number of seed points desired, as well as the dimensions of the image. It first creates the list of seed points, and sets up the 3x3 array of duplicate sets. Then it initializes the arrays to hold distance values and cell id values (colors). Then it cycles through each pixel in the range, finds the closest point, and assigns an id based on the closest point. Find closest point functions by testing the distance to each point in the 3x3 block of point lists.
The above code was written for my own personal toolset, but can be adapted to other applications.
The result is something that looks like this:
If you load the above into an image editor and apply a 1/2 offset in each direction, you can see that it tiles with itself perfectly. And while it doesn't look much like a stone floor, it gives us a place to start. The cells will be the stones in our floor. We just need to delineate stones from mortar lines.
To do this, we load the image up into the GIMP. (Or Photoshop, but since I don't have Photoshop, the GIMP it is). Using the Fuzzy Select tool, we select each region in turn. HINT: If one of the regions is pure black (RGB (0,0,0)) select it first; since the mortar lines will be solid black, if you wait to do the solid black cell, you will end up selecting all of the lines when you select the black cell. Select a region using Fuzzy Select with no feathering or antialiasing of the region, then flood-fill the cell with pure black. Then go to Select->Shrink to shrink the selection by some arbitrary (say, 5) number of pixels. Uncheck the Shrink From Edge box, so that the selection will not "pull away" from the edges when shrunk. This creates a smaller region within the cell; floodfill this region with pure white. Repeat for all of the cells, and the result should be an image of white cells delineated by sharp black borders, like this:
We can see our floor starting to take shape. But, of course, it's too sharp and regular. First of all, let's apply a Gaussian blur to fuzzy it up a bit. Go to Filters->Blur->Tileable Blur and select an arbitrarily large (I used 26) blur radius. It needs to be a Tileable Blur, rather than a normal Gaussian Blur, since we need to ensure that the edges will still tile. After the filter runs, you should see something like this:
That looks pretty good. We can apply some tweaks to the brightness and contrast of the image, to sort of play with the contrast of the regions:
That looks better, but those stones are just way too smooth. So let's apply some turbulence to noisify it a little.
We'll start with some fBm fractal noise. I've discussed using fractal noise turbulence as a tool quite a bit in previous journal entries, so I won't go into too much detail here. Just some things to remember: The fractal noise sources you use to perturb the pixels must be tiling. I typically sample 2 fBm fractal modules into separate buffers, using a seamless sampling algorithm to ensure the buffers tile, then use those buffers with a scaling factor to perturb or offset the pixels in the X and Y directions. This turbulence has the effect of roughing the edges and making it seem more like natural stonework:
There, that looks pretty good. We have rough stones, rough mortar lines between them. Now we need to get down to constructing the actual texture.
First, we need to select the colormaps we will use for the stone and the mortar surfaces. Where you get these sources matters little, although you must ensure that the textures you use tile with themselves. I typically manufacture texture sources from digital photographs. I'll photograph a surface (stone, dirt, asphalt, you name it; I take my camera with me everywhere I go) then later I will crop and scale and filter and edit pieces to construct seamless tiling textures. You can also synthesize textures using any of a bajillion procedural texture synthesis routines. At any rate, the textures I selected are these, which were edited from two of my stock of library photos. (Both taken in Flagstaff, at the Sunset Crater Volcano)
The image on the left will be our stones, the one on the right will be our mortar. Starting with the mortar image, I create another Layer and copy the stone image into this layer. Then I right click the new layer in the Layers box, and Add Layer Mask. I copy the perturbed rock tile image we just created into this layer mask so that it acts as a blend agent between the mortar (where the tile mask is black) and rock (where the tile mask is white):
This outlines our mortar areas from the stone areas.
Now all we need to do is apply a bump-map for shading and we are done. However, I hate GIMPS built-in bump map generator. Typically, I construct my own bump maps. I treat the tile image as a heightmap, construct an array of vertex normals based on the height data, and calculate the bump map by performing a dot product between the normal at each pixel and an arbitrary light direction vector. Simple and effective:
Once I've generated the bump map, I load it into the GIMP, copy it, and paste it on top of the color map. Then I change the layer settings of the pasted map to Multiply. This has the effect of scaling the colormap pixel based on the shading information in the bump map. And voila:
There you have it: a tiling stone floor that looks at least halfway decent, that any programmer can make.