Generating a Fractal LandscapeMy FractalMesh class utilizes a plasma fractal to generate a mesh of Point3d objects, centered at (0, 0) on the (x, z) plane, at intervals of 1 unit, extending out to WORLD_LEN/2 units in the positive and negative x- and z-directions. (In my code, WORLD_LEN is 64 units.) The y-coordinates of these points become their heights in the scene. The objects are stored in a 2D array called mesh, with mesh[0][0] storing the back, left-most point in the scene. A row in mesh[][] stores all the points for a given z-value. The mesh is generated using the algorithm described by Jason Shankel in "Fractal Terrain GenerationMidpoint Displacement" from Game Programming Gems. The mesh is seeded with four corner points, and a two-stage process is repeated until sufficient extra points have been created. In the first stage (the diamond step), the height of the midpoint of the four corner points is calculated by averaging their heights and adding a random displacement in the range -dHeight/2 to dHeight/2. For example, the height of the E point in Figure 26-8 is calculated this way:
Figure 26-8. Mesh creation: first iterationThe next stage (the square step) is to calculate the heights of the midpoints of the four sides (F, G, H, and I in Figure 26-8). For example, G's height is:
If a point is on an edge (as G is), then we can use a neighbor from the opposite edge by thinking of the mesh as wrapping around from left to right, and from top to bottom. That's why G's calculation uses E twice: once as the left neighbor of G and once as its right neighbor. At the end of the two stages, the mesh can be viewed as four quarter-size squares (AFEG, FBHE, GEIC, EHDI). Now the process begins again, on each of the four smaller squares, as shown in Figure 26-9. The difference is that the sides of the squares are half the length of the original, and dHeight is divided by the flatness value (the number entered by the user at the start of FractalLand3D). Figure 26-9. Mesh creation: second iterationWhen flatness is > 2, dHeight will decrease faster than the sides of the squares, so after the initial creation of hills and valleys, the rest of the terrain will generally consist of smooth slopes between those features. When flatness < 2, the randomness will be a significant component of the height calculationshills and valleys will be affected by this randomness, resulting in a rockier landscape. The creation of the corner points is done by makeMesh( ):
randomHeight( ) selects a random number between the maximum and minimum heights fixed in the class. divideMesh( ) carries out the diamond and square steps outlined abov, and continues the process by recursively calling itself. Here is the code in outline:
divideMesh( )'s stepSize value starts at WORLD_LEN/2 and keeps being divided by 2 until it reaches 1. In order for the points to be equally spaced over the XZ plane, WORLD_LEN should be a power of 2 (it's 64 in my code). divideMesh( ) stores the generated points as Point3d objects in mesh[][]. The Landscape object accesses the points by calling FractalMesh's getVertices( ) method. getVertices( ) creates vertices[] and stores references to mesh[][]'s points inside it, in counterclockwise quad order, starting with the bottom-left corner of the quad. For instance, when considering coordinate (x, z), it will copy the points in the order (x, z + 1), (x + 1, z + 1), (x + 1, z), (x, z). This is somewhat clearer by considering Figure 26-10. The getVertices( ) method is:
Figure 26-10. Quad ordering for point (x, z)
Printing the MeshFractalMesh contains a printMesh( ) method for debugging purposes: it prints either the x-, y- or z-values stored in mesh[][] to a text file. This method could easily be extended to store the complete mesh to a file. Landscape could then have the option to create its floor by reading in the mesh from the file rather than by using FractalMesh. This is similar to the way that Maze3D in Chapter 25 reads its maze information from a file created by the MazeGen application. The advantage of this approach is that FractalLand3D could choose to reuse an existing landscape instead of generating a new one every time it was called. Fixing the RandomnessThe diamond and square steps use a random displacement in the range of -dHeight/2 to dHeight/2. This is implemented in two places in FractalMesh, randomRange( ) and randomHeight( ), using Math.random( ) suitably scaled (e.g., in randomRange( )):
This approach means that the landscape is different every time FractalLand3D is called, even when the same flatness value is supplied by the user. An interesting alternative, suggested to me by Tom Egan, is to employ the Random.nextDouble( ) method instead. The advantage is that a Random object can be created with a specific seed, which is a number used to generate the sequence of random numbers. If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate identical sequences of numbers. This means that the fractal will be the same each time, making the landscape the same as well. This property would be useful in games where the landscape must be fixed. The code changes would require a global Random object in FractalMesh:
Then randomRange( ) and randomHeight( ) would need their calls to Math.random( ) replaced by rnd.nextDouble( ). For example, randomRange( ) would become:
Now when FractalLand3D is called with a given flatness value, the same landscape will be generated. However, the terrain will appear to be be random. You can still modify it by adjusting the flatness number. |
Friday, January 8, 2010
Generating a Fractal Landscape
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment