2

I am trying to figure out a way to have a fixed scale for the:

https://en.wikipedia.org/wiki/Diamond-square_algorithm

I see that the algorithm requires a power of 2 (+1) size of the array.

The problem I am having is that I would like to have the same heightmap produced regardless of the resolution. So if I have a resolution of 512 it would look the same as with the resolution 256 but just have less detail. I just can't figure out how to do this with.

My initial thought was to always create the heightmap in a certain dimension e.g. 1024 and downsample to the res I would like. Problem is I would like the upper resolution to be quite high (say 4096) and this severely reduces the performance at lower resolutions as we have to run the algo at the highest possible resolution.

Currently the algorithm is in javascript here is a snippet:

function Advanced() {
    var adv = {},
    res, max, heightmap, roughness;

    adv.heightmap = function() {
        // heightmap has one extra pixel this is ot remove it.
        var hm = create2DArray(res-1, res-1);
        for(var x = 0;x< res-1;x++) {
            for(var y = 0;y< res-1;y++) {
                hm[x][y] = heightmap[x][y];
            }
        }
        return hm;
    }

    adv.get = function(x,y) {
        if (x < 0 || x > max || y < 0 || y > max) return -1;
        return heightmap[x][y];
    }

    adv.set = function(x,y,val) {
        if(val < 0) {
            val = 0;
        }

        heightmap[x][y] = val;

    }

    adv.divide = function(size) {
        var x, y, half = size / 2;
        var scale = roughness * size;
        if (half < 1) return;

        for (y = half; y < max; y += size) {
            for (x = half; x < max; x += size) {
                adv.square(x, y, half, Math.random() * scale * 2 - scale);
            }
        }
        for (y = 0; y <= max; y += half) {
            for (x = (y + half) % size; x <= max; x += size) {
                adv.diamond(x, y, half, Math.random() * scale * 2 - scale);
            }
        }
        adv.divide(size / 2);
    }

    adv.average = function(values) {
        var valid = values.filter(function(val) {
            return val !== -1;
        });
        var total = valid.reduce(function(sum, val) {
            return sum + val;
        }, 0);
        return total / valid.length;
    }

    adv.square = function(x, y, size, offset) {
        var ave = adv.average([
            adv.get(x - size, y - size), // upper left
            adv.get(x + size, y - size), // upper right
            adv.get(x + size, y + size), // lower right
            adv.get(x - size, y + size) // lower left
        ]);
        adv.set(x, y, ave + offset);
    }

    adv.diamond = function(x, y, size, offset) {

        var ave = adv.average([
            adv.get(x, y - size), // top
            adv.get(x + size, y), // right
            adv.get(x, y + size), // bottom
            adv.get(x - size, y) // left
        ]);

        adv.set(x, y, Math.abs(ave + offset));
    }

    adv.generate = function(properties, resolution) {
        Math.seedrandom(properties.seed);

        res = resolution + 1;
        max = res - 1;
        heightmap = create2DArray(res, res);

        roughness = properties.roughness;

        adv.set(0, 0, max);
        adv.set(max, 0, max / 2);
        adv.set(max, max, 0);
        adv.set(0, max, max / 2);

        adv.divide(max);
    }

    function create2DArray(d1, d2) {
        var x = new Array(d1),
        i = 0,
        j = 0;

        for (i = 0; i < d1; i += 1) {
            x[i] = new Array(d2);
        }

        for (i=0; i < d1; i += 1) {
            for (j = 0; j < d2; j += 1) {
                x[i][j] = 0;
            }
        }

        return x;
    }

    return adv;
}

Anyone ever done this before ?

Dave3of5
  • 688
  • 5
  • 23

2 Answers2

0

Not quite sure if I understand your question yet but I'll provide further clarification if I can.

You've described a case where you want a diamond-square heightmap with a resolution of 256 to be used at a size of 512 without scaling it up. I'll go through an example using a 2x2 heightmap to a "size" of 4x4.

A diamond-square heightmap is really a set of vertices rather than tiles or squares, so a heightmap with a size of 2x2 is really a set of 3x3 vertices as shown:

enter image description here

You could either render this using the heights of the corners, or you might turn it into a 2x2 set of squares by taking the average of the four surrounding points - really this is just the "square" step of the algorithm without the displacement step.

enter image description here

So in this case the "height" of the top-left square would be the average of the (0, 0), (0, 1), (1, 1) and (1, 0) points.

If you wanted to draw this at a higher resolution, you could split each square up into a smaller set of 4 squares, adjusting the average based on how close it is to each point.

enter image description here

So now the value of the top-left-most square would be a sample of the 4 sub-points around it or a sample of its position relative to the points around it. But really this is just the diamond square algorithm applied again without any displacement (no roughness) so you may as well apply the algorithm again and go to the larger size.

You've said that going to the size you wish to go to would be too much for the processor to handle, so you may want to go with this sampling approach on the smaller size. An efficient way would be to render the heightmap to a texture and sample from it and the position required.

Ross Taylor-Turner
  • 3,687
  • 2
  • 24
  • 33
0

Properly implemented diamond & square algorithm has the same first N steps regardless of map resolution so the only thing to ensure the same look is use of some specified seed for pseudo random generator.

To make this work you need:

  1. set seed
  2. allocate arrays and set base randomness magnitude
  3. Diamond
  4. Square
  5. lower base randomness magnitude
  6. loop #3 until lowest resolution hit

If you are not lowering the randomness magnitude properly then the lower recursion/iteration layers can override the shape of the result of the upper layers making this not work.

Here see how I do it just add the seed:

see the line:

r=(r*220)>>8; if (r<2) r=2;

The r is the base randomness magnitude. The way you are lowering it will determine the shape of the result as you can see I am not dividing it by two but multiplying by 220/256 instead so the lower resolution has bigger bumps which suite my needs.

Now if you want to use non 2^x+1 resolutions then choose the closer bigger resolution and then scale down to make this work for them too. The scaling down should be done carefully to preserve them main grid points of the first few recursion/iteration steps or use bi-cubic ...

If you're interested take a look on more up to date generator based on the linked one:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380