Small Update: Updated the GUI so it doesn't "forgets" to repaint the heightmap and it uses a single instance of the map unless you change its size. Download section has been updated. Also, link to the last entry if you want to check it out. Talked about various things that popped out while coding the height filling algorithm (diamond-square based).
Apps n' Rivers
Decided it would be a nice thing to have a GUI to use the generator rather than commenting/uncommenting stuff all around to test it. Having a GUI attached to it is nice not only for usage but for the general shape of the code.
When I was using just code, many steps in the generation consisted on arbitrary array copying or calling various methods on each generator., everything depends on the assumptions that I made some specific call earlier.
When you use a GUI you have to think "How would I use that from a single button?". You can't just pop method calls from nowhere since that would be cumbersome to an user. "Now press the copy blurredHeights float array to riverMap float array button to continue!" is too ugly. Implementing that in more concise steps, that encapsulate better what is being done on the terrain should prove a good influence on the generator code.
The UI is implemented with two classes only: The Swing frame and a "Mastermind" that pulls the strings to make the generators do what the UI wants.
For now, besides the map size, you can have a few settings:
- How many ridge lines you want.
- How big is their deviation
- How often the ridge lines change in direction, in steps.
- How broad is the rotation arc they can have.
- How big is the blur factor of the ridge lines.
- How many river particles are going to be spawned.
- How rough is going to be the midpoint pass.
Ridge amount is self explanatory, deviation not so much. Remember that ridge particles start at an arbitrary highest point, and from there they descend describing a gaussian/normal distribution. The deviation indicates how far the particles are going to get from the mean (middle/highest) value. Its a pretty heavy statistics subject to just use it on a ridge line but oh well
Ridge lines are supposed to follow a "fractional brownian motion", since at the time didn't knew how to implement it (and to be fair, I still don't know ) I just made it so after a certain amount of steps, the ridge particles would rotate their directions by a randomly selected degree amount, in an arbitrary arc (say, if the arc is 90º, you'd rotate between -45º and 45º).
Blur factor of ridge lines determines the terrain in which the rivers will be generated. Since we don't have a complete terrain at this point, the river generator uses a heightmap with just the ridge lines traced on it, blurred by a determined factor. More blurring means that the river lines will take longer to reach the bottom of the heightmap.
River particle amount is self explanatory.
And last, midpoint displacement roughness. Midpoint displacement works by averaging the heights of the corners of a given subsquare in the map, and then applying a random offset to it, this offset is given by the maximum height variation at the time of the iteration multiplied by the roughness factor. If the roughness factor is 1, and the maximum height is 1024, then you'd have big variations in height, if the roughness factor is 0.25 instead, your heights would vary by just 256 values.
So in the end the generator is pretty configurable, and there are a few constants that probably I missed out
(3D viewer not included )
Download and how to use it
First and foremost, it needs Java 7 to work. 32 or 64 bit. Second, it uses a LOT of ram, so choose a reasonable map size (513x513, or 1025x1025).
Once the heightmap is generated, ideally you'd have several options for saving it. As a 8bit greyscale png, as a plain array of floats, as a color map, etc. For now, the app just plain saves whatever step you're on, save buttons don't work. So you'll end up with a .png of each stage, plus an extra colored one for the last one, in the same directory as the .jar is located.
And finally, since it is in a very early state, you need to press the buttons in order, from top to bottom for it to work at all, also, don't put letters on the numerial field please
Update: Now you have a "New Map" button. You have to click it to create a new map and select its size. As a side effect of the changes now you can recalculate the ridge lines without having to create a new map. Besides, now the UI actually tries to repaint the image accordingly so now you can resize the window freely without loosing the view on the heightmap (plus other repaint glitches got solved too).
First you create the map selecting it size, then you generate the ridges, third, you blur the heights, fourth, you generate the rivers, fifth you do the MDI pass, and last, you do the regular diamond square pass.
Here it is TerrainGeneratorUpdt.rar (56.6KB)
downloads: 67 Enjoy.
The rivers look "fine". There is this major thing I'd like them to do, and that thing is join streams. River particles have no notion of collision nor other river particles, so they just wander in the map until I arbitrarily say "NO MOAR RIVER CALCULATION" (literally, there is this while loop that stops after a second or so).
I'd like them to be able to join other river particles. The paper more or less describes a way to do it, you check for collisions with other river particles, stop the particle that is colliding, and grab the other river particle. With that particle you erase its path until the point of collision, and then set it free again but with increased river width.
I'm on my way to implement this behavior but I'm having a few problems that I simply don't know how they even work. For some reason the entire map gets sunk into the black void of nothingness if any particle dares to touch other particle.
In any case, if I manage it to work, next step is implementing some kind of friction that makes the particles stop naturally. The original velocity field erosion paper has some complex (to me) "energy impact" calculation when the particle's velocity vector collides with the surface of the heightmap. Long story short, I don't understand how it works, nor why a simple direction vector wouldn't work well enough, so I'll follow my simpler road and just try with a friction factor.
And that's it for now. Bye! Until the next entry