Project Labyrinth

Step 0

The morning sun begs me to venture out of my apartment and enjoy the outside world away from the stale air in my tiny apartment. But, itís a weekend, which means itís a good day for some recreational programming. There is no world away from the glow of the monitor and the clacking of the keyboard.

I think Iíll use Java to procedurally generate a new desktop image. Of course I have access to photo editing and 3D rendering software, but what fun is that? I want to do this from the ground up using only core Java. Iíll post each version of the program as it evolves along with its output image of course.

Iím envisioning an infinite labyrinth stretching to the horizon. The plan is to use a space-filling curve. Check out the following 6 paths.

−

|

r

L

7

J

Each path visits the 25 cells making up the grid exactly once. It always enters a cell through 1 wall and it exits through a different wall. There are 6 possible ways to visit a cell for which Iíve named: −, |, r, L, 7, J. Those symbols somewhat resemble the possibilities. Youíll notice that the paths above are named after the way the center cell is traversed. But, not only that, the path as a whole exhibits the same visitation behavior when you consider the boundaries of the grid. For instance, the L grid scaled to 1/5th its size could replace the center cell of the L grid itself. In this way, the paths above are self-similar. In theory, you could substitute cells at smaller-and-smaller scales indefinitely, creating a space-filling curve.

If you look carefully at the (hastily created by copying, scaling and pasting in a paint program) image above, the center cell is of type L, the center 5×5 cluster of cells is of type L, and the entire path itself is of type L. This means that instead of shrinking paths, you could construct an infinitely large version by building outward. No substitutions are required. It just keeps growing.

To represent a path traversing a cell, Iíll create a 3×3 grid. The tiles of those grids will either be solid or empty.

−

|

r

L

7

J

We can start with any type centered about the origin. Then, we build 2 layers around that to replicate the type that we started with. To build the successive layers, we recursively construct them from the 3×3 grids on up. The important thing is that it is always possible to rapidly figure out if a particular cell is solid or empty without actually building out that far. Instead, you keep subdividing the space around the point of interest until you are down to a single cell.

Step 1

I got Netbeans open and I created a new class.

public class Main {public static void main(String[] args) {}}

Step 2

I banged out some code to crank out an image.

0 hours, 0 minutes, 0 seconds, 359 milliseconds

Yep. Itís all black. My desktop is 1280×1024. To speed up testing, Iíll render images at half that resolution. The array called pixels will store pixel RGB values in the range from 0.0 (darkest) to 1.0 (lightest). The saveImage method applies gamma correction during saving. Actually, the color intensity values in pixels represents the sum of multiple samples. saveImage also divides by the sample count.

The main method computes the time required to generate the image. I am running this on an Intel i7 920 @ 2.67 GHz under 64-bit Vista. But, right now it is a single-threaded application. Only 1/8th of the potential of this machine is being tapped.

Step 5

Step 6

I modified the Fractional Brownian Motion methods to accept 3D coordinates as opposed to just a 2D plane. This version requires more memory because Iím storing many more random numbers. If you run this code, add a JVM argument like: -Xmx1024M.

Step 7

To verify that my FBM methods really are producing 3D sky, I modified the program to generate 64 slices through a 3D block. Itís almost like frames of a movie. Itís like a changing cloudscape. Since each successive slice resembles the last, Iíd say itís working.

Step 12

Step 13

Now, the rendering routine is multithreaded. On this machine, each thread works on 1/8th of the image. Hereís an image with 100 samples per pixel. Youíll notice that there is less noise on the horizon.

Step 14

I plugged in the space-filling curve algorithm discussed in Step 0. Iím using type r as the seed type. I only made one minor modification from what is discussed above. Instead of covering cells with 3×3 representations of the type, Iím using 5×5 representations. That change provides more empty space around the curve.

Step 29

Step 30

I rendered the image in Step 29 at 1280×1024 with 400 samples per pixel. It took: 0 hours, 39 minutes, 17 seconds, 488 milliseconds. You can view the image here and you can download the source here. Itís actually a pretty sweet desktop image.

Step 31

One final experiment: What if it werenít a labyrinth, but rather an Escheresque cityscape composed of an endless staircase? Nah.