0

I implemented the diamond square algorithm in Java, but i'm not entirely satisfied with the results as a height map. It forms a lot of "lakes" - small areas of low height. The heights are generated using the diamond square algorithm, then normalized. In the example below, white = high, black = low and blue is anything below height 15: a placeholder for oceans.

height map example

This image shows the uncolored height map enter image description here

How can I smooth the terrain to reduce the number of lakes?

I've investigated a simple box blurring function (setting each pixel to the average of its neighbors), but this causes strange artifacts, possibly because of the square step of the diamond square.

Would a different (perhaps gaussian) blur be appropriate, or is this a problem with my implementation? This link says the diamond square has some inherent issues, but these don't seem to be regularly spaced artifacts, and my heightmap is seeded with 16 (not 4) values.

Community
  • 1
  • 1
Jon Takagi
  • 101
  • 3
  • look into morphological operations, in particular 'closing' – chris Nov 24 '16 at 15:27
  • 1
    Why not just apply a blur/averaging of neighbors for a pixel if it's blue and none of its neighbors are? That should be easy to do and reduce that "dotting" effect, i.e. any blue pixel which hasn't at least one immediate blue neighbor gets the average color of all neighbors. – Thomas Nov 24 '16 at 15:30
  • I hadn't thought of that. Because lakes aren't regularly spaced, detecting them is O(n^2), I think. I'm interested in better solutions, but that should work. – Jon Takagi Nov 24 '16 at 16:07
  • I don't think your results look right. The non-blue part looks OK, so I am guessing that your lake coloring algorithm is wrong. – Matt Timmermans Nov 24 '16 at 16:14
  • I simply color all pixels with a height less than 15 on a scale from 0 to 255. Would it be beneficial to link to / post raw height values? – Jon Takagi Nov 24 '16 at 16:15
  • Ah, that's an "ocean coloring algorithm", but again that doesn't look like what you did. Are you saying that if you turned off the blue coloring then the blue areas would be black? If so, then your diamond-square implementation is very broken. But from the image it looks more like you are coloring a range of heights that doesn't extend to the bottom -- you have lakes on the sides of mountains. – Matt Timmermans Nov 24 '16 at 16:19
  • All the blue areas are shades of dark grey. I will edit my question to include an image of the unshaded height map. – Jon Takagi Nov 24 '16 at 16:20
  • Then it is as I said. There are heights (the black ones) that are lower than your lakes, but you are not coloring them blue. – Matt Timmermans Nov 24 '16 at 16:22
  • My bet is that you got bug in your diamond&square somewhere. Take a look at this related QA [how to create a branching vein/river like structure on a square grid](http://stackoverflow.com/a/39631673/2521214) and also the link inside. So you got something for comparison. Either you are not changing randomness along the subdivision or have some bug somewhere. Anyway you can tweak the output by selecting initial conditions of the diamond & square just like I did in the linked Island generator. PS lakes are created by downhill watter flow not by altitude... – Spektre Nov 25 '16 at 16:24

1 Answers1

0

Your threshold algorithm needs to be more logical. You need to actually specify what is to be removed in terms of size, not just height. Basically the simple threshold sets "sea level" and anything below this level will be water. The problem is that because the algorithm used to generate the terrain is does so in a haphazard way, small areas could be filled by water.

To fix this you need to essentially determine the size of regions of water and only allow larger areas.

One simple way to do this is to not allow single "pixels" to represent water. Essentially either do not set them as water(could use a bitmap where each bit represents if there is water or not) or simply raise the level up. This should get most of the single pixels out of your image and clear it up quite a bit.

You can extend this for N pixels(essentially representing area). Basically you have to identify the size of the regions of water by counting connected pixels. The problem is this, is that it allows long thin regions(which could represent rivers).

So it it is better to take it one step further and count the width and length separate.

e.g., to detect a simple single pixel

if map[i,j] < threshold && (map[i-1,j-1] > threshold && ... && map[i+1,j+1] > threshold) then Area = 1

will detect isolated pixels.

You can modify this to detect larger groups and write a generic algorithm to measure any size of potential "oceans"... then it should be simple to get generate any height map with any minimum(and maximum) size oceans you want. The next step is to "fix" up(or use a bitmap) the parts of the map that may be below sea level but did not convert to actual water. i.e., since we generally expect things below sea level to contain water. By using a bitmap you can allow for water in water or water in land, etc.

If you use smoothing, it might work just as well but you still will always run in to such problems. Smoothing reduces the size of the "oceans" but a large ocean might turn in to a small one and a small one eventually in to a single pixel. Depending on the overall average of the map, you might end up with all water or all land after enough iterations. Blurring also reduces the detail of the map.

The good news is, that if you design your algorithm with controllable parameters then you can control things like how many oceans are in the map, or how large they are, how square they are(or how circular if you want), or how much total water can be used, etc).

The more effort you put in to this you more accurate you can simulate reality. Ultimately, if you want to be infinitely complex you can take in to account how terrains are actually formed, etc... but, of course, the whole point of these simple algorithms is to allow them to be computable in reasonable amounts of time.

AbstractDissonance
  • 1
  • 2
  • 16
  • 31
  • hm... I'm not sure i understand what you're saying. Essentially you start from the lowest point, filling all its neighbors that are lower than "sea level" and repeating until all the neighbors are above sea level? – Jon Takagi Nov 24 '16 at 16:54
  • @JonTakagi No, What I mean is that you essentially count adjacent blue pixels. A single blue pixel means that the 8 surrounding pixels cannot be blue. Blue means blue some threshold. So, you can easily determine if an isolated pixel is blue and if it is you can then figure out how to remove it(e.g., set it's value just above the threshold). – AbstractDissonance Nov 24 '16 at 20:11
  • For groups of blue pixels larger than 1, it is somewhat more complicated but essentially you just count the number of blue pixels that are connected(there are algorithms out there for this). But this count can be a metric for the "area" of the ocean. You then essentially set a threshold value for ocean sizes and if they fall below this(say 10 pixels in area or greater will be considered oceans) and if it falls below the value you have to remove them somehow(e.g., clamp to the threshold value). This will help with get rid of "small" oceans. – AbstractDissonance Nov 24 '16 at 20:14
  • A simple way to do this is essentially find the bounding box of any connected region of blue pixels. If the bounding box area is "small" then fix up the pixels so they don't represent water any more(add some value to the pixels so it pushes them above the threshold). – AbstractDissonance Nov 24 '16 at 20:16