Hi! So I wrote a little bit more. And this time I have pictures! Shiny pictures! Have in mind that I might jump around a lot since its kinda hard to summarize everything and have in mind I haven't finished this project so every decision I made it might be subject to change in the future.
Also, sometimes the links look black on my end so it might be hard to distinguish them from normal text.
One of the various things that either the paper doesn't describes properly or I just don't understand them (4 page long paper? It's a trap!) is the secondary ridge line generation.
After some steps (say, 20 advance() method calls), the ridge particles are supposed to spawn two more ridge lines with opposite directions, with half the deviation from the main one in perpendicular direction to the main ridge that spawns them.
Spawning them isn't difficult. If the main ridge direction is (X,Y), their two child ridge particles would have (-Y,X) direction and (Y,-X) direction. They would proceed just like the main ridge particles but with half (one quarter in my case) the deviation of its parent.
This is the heightmap with the main and secondary ridge lines:
The paper mentions "blending" the ridge lines to compute the river network. It doesn't says with what criteria, nor if it means just blending the ridge lines or blurring all the height values, to end up with an image like the paper shows. I choose to do a gaussian blur pass over the heights to blend them all together.
Plain ridge lines don't look right and it isn't enough height information for the next steps, we need to spread them around:
I found a very nice library for image filters Jhlabs.com. It has all sort of filters, though we're interested in the blurring ones.
The Java library operates on top of BufferedImage objects. Which means first and foremost that they're very easy to use. The first blurring passes I did were made with that library by "transforming" the height array into a BufferedImage, blurring it, then bringing out the new height values again to a float array.
It worked pretty well at first but after I while I had some problems. Pixel range is quite... limited. I could go only from 0 to 255 on fixed integer steps. So no matter how detailed were my height calculations, after some integer conversions they looked quite bad.
Say that you want to have 1m resolution, so one pixel is one meter. Thats quite great, you could compute a heightmap with a 2km x 2km surface and have some fun with it in no time. But on the height side, you're limited to 0-255 values (255m tall mountain isn't the definition of scary precisely) if you decide to go for a grayscale heightmap using the previous library.
So in the end I had to knock off my own gaussian blur pass which would work with the float array directly. Turns out not only works quite well, but it also takes half the time to compute than doing a filter pass over the BufferedImage. While integer operations on pixel colors should be quite cheap, there is a lot of bitshifting around plus many integer to float conversions (and viceversa), thus the blurring pass over the float array was a better option.
Family, Ridges, and their limits
Something that isn't very clear (to me at least) is that if the ridge lines really should stop only when they reach "null" height (I guess the author meant 0.0f ?). See, if the ridges are supposed to reach 0.0f, which would be the lowest point of your heightmap, then the river lines wouldn't be able to carve their way around when you spawn them later. They'd carve their way around the mountain side, then reach 0.0f immediately after and stop since you can't carve a river line anymore.
If you start your heightmap with a middle value, say, your heights go from 0.0f to 1000.0f, and you set all the initial height values to 500.0f. As it is, the ridge lines would carve their way all down to 0.0f before stopping, that is, the mountains would go under your landscape. Which not only looks bad, but it produces plain pools of river particles later on, which isn't helpful. Though I don't discard the possibility of doing canyons this way sometime in the future, it should work with some tweaking I guess.
For now, and as you might see on the previous pictures, I decided to set up an initial height value for all the map, the ridge lines go from their highest points all down until they reach the initial height of the height map instead of the lowest point. This leaves a lot of terrain for the river particles to produce more interesting lines. I made a few helper classes to map back the height values to BufferedImages so I can visualize the results at each stage, only generating the terrain without doing this in the middle should be quite fast.
So for now the ridge particles stop on any of these 3 conditions:
- They reached the "bottom" of the heightmap, which is an arbitrary value, one quarter of the maximum height in my case.
- They collided with another ridge particle (first one to discover it has collided stops, the other keeps going). Collisions within the same particle aren't considered.
- They reached the limits of the heightmap.
So that does it for now There may be some ridge particle stuff that I forgot, I'll add it up to the next journal entry if I remember something valuable. Bye!