fractal terrain terrain generation midpoint displacement
Hi people! Free time == moar entries : D The generator is kinda fixed (as far as I can notice). Now I have to fix up all the parameters around and the river generation process too. Also, pictures... in 3D.
Memory is for the weak!
Well, honestly I half remember all the things I touched from the last entry, but I'll try to get to them. This is the base ridge (with sub ridges) map:
The indices of my squares were off. After analyzing how the Square class gets initialized and what the loops use for strides and sizes, I reorganized them so each square would be the right size.
After that I realized that a part of my implementation was wrong. The paper differentiates between DEM and C_DEM (Digital Elevation Model and Copy of Digital Elevation Model respectively) and there were a few spots where in my implementation, both got mixed and parts of the algorithm affected the DEM where it should have affected the C_DEM and vice versa.
Once I had that solved, the additional data got into the heightmap just fine.
Lost in translation?
The weird thing is that while I get similar results, the recursive MDI method in my case only generates the additional height data, I still have to do a "context-aware" midpoint displacement pass over the heightmap to fill in the missing heights. As far as I understand, the recursive MDI method should do all of that already.
This is the same map, blurred, with the (broken) river network generated, and with the (hopefully) correct MDI values:
You can see the dots that the MDI pass generates all around, the rest of the blurred heights are there just for reference, they shouldn't matter for the final passes. The river network turned out to be quite important since it defines the slopes of the ridges on many places though you can vary a lot by just messing with the max heights, base heights, amount of blurring, ridge/river quantity, etc.
In any case, an additional call to midpointDisplacementPass() was enough to fill in what was missing. The midpoint displacement pass showed a lot of squared artifacts, so I tried with a diamond square pass, which should in theory excibit less artifacts.
I used the diamond square algorithm to fill the remaining heights, working without big artifacts as expected but something was wrong...
Artifacts in my soup! Wait, already did that one...
See, when I tried to color the heightmap, I use the elevation state map as a reference to know if I have to color the vertex blue (river), or green/brown (land/ridge). When I did the coloring pass, turns out that most of the values were in NULL state (ie, greyscale color), yet the heightmap was created fine.
The final regular diamond square pass checked for already computed height states but, for the missing ones, it calculated the height without setting the new state as "LAND" (or DONE like in the paper). So it was "aware" of the previously computed states but it wasn't "self aware" in the sense that it didn't kept track of the missing heights it was filling.
I set up the thing to put the correct states and bam! Artifacts again everywhere. So the root of the squared (and now, "diamoned" ) artifacts wasn't the algorithm itself but its "self awareness".
I left it "broken", ie, without the state changes, and it works fine that way heh.
Here you can see how the ridge lines help to define high places whereas the river lines help to define the lower ones. Have in mind that the river network generation is pretty broken so the river lines don't make much sense.
And here is a colored version for your viewing pleasure ( "I can make a lerp with a gradient woot!" ).
From what I've experimented, it seems that for the algorithm to look kinda good, it needs to have nice ridge lines, and a good river network that makes sense. Terrain gets weird if the ridge lines are at odd positions or if the rivers get traced badly.
I can also show you the terrain in 3D with all of its per-vertex coloring glory (nevermind the Great Green Cube Overlord of the "Positive Y Axis" house watching over his realm):
The borders are messed up but it seems like its a common trait of the diamond square algorithms. You could make it have some checks and wrap it around but I think I'd just crop the borders (ie, instead of 513^2 map, cropping it to a 512^2 map).
What is left...
Now I'll have to fix up the river network generation, which means possibly trying later with a water erosion algorithm (since the river generation is just that, a water erosion algorithm applied to a few particles). Besides that, the generator is currently a big mess o' code for the most part, so I'm going to tidy it up and see if I can come up with something more useable.
For example, at first I had an "ElevationMap" class with a bunch of arrays with ElevationState, floats, Particles, etc, but now I think I'm going to repurpose my CmpMat matrix class to have all of them under a more homogeneous interface. ElevationMap has like 4 kinds of "getValueAt(x,y)" methods, one for each type of data it stored. Half the code is using the older ElevationMap, the other half is using the "newer" CmpMat matrix storing ElevationState and height values.
I'll have to come up with a bigger structure for all the generator if I want it to be useable. The good thing is that it was very useful for completing my math library, coming up with some generic components and so on.
Right now I have a lot of static stuff (I remind you I'm doing all of this in Java). For example, all of the midpoint displacement, diamond square and mid passes are static methods on an "UtilsTerrain" class, I have a image utils class that has a bunch of static methods to convert from ElevationMaps to ImageBuffers so I can store them in a .png.
There is also some cruft from my previous project, the renderer above, in the math utils and some array/matrix classes that I need to get rid of. For example, in Java you need to write your float arrays to FloatBuffers before passing them around to native libraries. For the OGL project it seemed perfectly fine for each matrix class to have its own FloatBuffer inside... Which suddenly stopped making any sense once I started using the matrices for non GL stuff. So for using the math library, i had to import the matrix/vector classes, which needed the buffer utils to work for no good reason. See what I did there?
I'm 80% done on fixing the whole math utils and classes, and once I fix them, I'll have to replace all the old code and banish those buffer utils / data array classes out of existence. And then retroimplement them on the OpenGL project where they came from.
What is left isn't right
I'll try to do all of that once I'm on my desktop again. Coding with an Atom N450 (1,6Ghz and single core mind you) isn't the most efficient thing in the world. Its better than no coding at all though See ya in the next entry!