3

I recently put together an implementation of the Diamond-Square procedural generation algorithm in C#. However, the generated noise has very apparent borders between the "squares" that are worked with. The pseudocode looks something like this

gen()
{
    This takes the two outer corners (upper left and lower right) as parameters (i.e. (0,0) and (4,4))

    Change center point of square using average of outer four corners and a random weight change.    

    Change four "diamond point" midpoints of the four sides of the square using the same idea.

        gen(topRightCorner, centerPoint);
        gen(topMidpoint,rightMidpoint);
        gen(leftMidpoint,bottomMidpoint);
        gen(centerPoint, bottomRightCorner);
    }

The algorithm initially starts with the top left and bottom right corner of the whole image, and works its way through (depth-first).

I used this article to design the algorithm. The example they give looks like this:

enter image description here

Here is what one of my images looks like:

enter image description here

Here is my complete code:

public class _3DMapGenerator
{
    public _3DMapGenerator(int powerOf2)
    {
        sideLength = (int)Math.Pow(2, powerOf2) + 1;

        for (int x = 0; x < sideLength; x++)
        {
            for (int y = 0; y < sideLength; y++)
            {
                data.Add(new Point(x, y), 0.5M);
            }
        }

    }

    int sideLength;
    Random r = new Random();
    public Dictionary<Point, decimal> data = new Dictionary<Point, decimal>();

    public void genMap(Point p1 = null,Point p2 = null)
    {
        if(p1 == null || p2 == null)
        {
            p1 = new Point(0, 0);
            p2 = new Point(sideLength - 1, sideLength - 1);
        }

        Point centerPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);

        if (p2.x - p1.x < 2 || p2.y - p1.y < 2)
        {
            return;
        }

        decimal swing = ((decimal)(1+p2.x - p1.x))/sideLength;

        Point p1_2 = new Point(p2.x, p1.y);
        Point p2_1 = new Point(p1.x, p2.y);

        Console.WriteLine("Points: " + p1 + "   " + p1_2 + "   " + p2_1 + "   " + p2);
        //Console.ReadLine();
        data[centerPoint] = ((decimal)(data[p1] + data[p2] + data[p1_2] + data[p2_1])) / 4 + ((decimal)r.NextDouble() * swing) - (swing / 2);


        Point mP1 = Point.getMidpoint(p1, p1_2);
        Point mP2 = Point.getMidpoint(p1, p2_1);
        Point mP3 = Point.getMidpoint(p1_2, p2);
        Point mP4 = Point.getMidpoint(p2_1, p2);

        swing /= 2;
        data[mP1] = ((decimal)(data[p1]  + data[p1_2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
        data[mP2] = ((decimal)(data[p1] + data[p2_1])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
        data[mP3] = ((decimal)(data[p1_2] + data[p2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
        data[mP4] = ((decimal)(data[p2_1] + data[p2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);

        genMap(p1, centerPoint);
        genMap(mP1, mP3);
        genMap(mP2, mP4);
        genMap(centerPoint, p2);
    }

    public void printToImage(string fileName)
    {
        Bitmap bmp = DrawFilledRectangle(sideLength,sideLength);
        foreach(var o in data)
        {
            bmp.SetPixel(o.Key.x, o.Key.y, Color.FromArgb((int)(255 * o.Value), (int)(255 * o.Value), (int)(255 * o.Value)));
        }
        bmp.Save(fileName);
    }

    private static Bitmap DrawFilledRectangle(int x, int y)
    {
        Bitmap bmp = new Bitmap(x, y);
        using (Graphics graph = Graphics.FromImage(bmp))
        {
            Rectangle ImageSize = new Rectangle(0, 0, x, y);
            graph.FillRectangle(Brushes.White, ImageSize);
        }
        return bmp;
    }
}

After averaging values on midpoints shared between squares:

enter image description here

Updated iterative code:

public Dictionary<Point, List<decimal>> data = new Dictionary<Point, List<decimal>>();

    static Random r = new Random();

    public int sideLength;

    public void genMap()
    {
        for (int sideLen = sideLength; sideLen >= 3; sideLen = sideLen / 2 + 1)
        { 
            for (int yOff = 0; yOff + sideLen < sideLength + 1; yOff += sideLen - 1)
            {
                for (int xOff = 0; xOff + sideLen < sideLength + 1; xOff += sideLen - 1)
                {

                    Point upL = new Point(xOff, yOff);
                    Point upR = new Point(xOff + sideLen - 1, yOff);
                    Point lowL = new Point(xOff, yOff + sideLen - 1);
                    Point lowR = new Point(xOff + sideLen - 1, yOff + sideLen - 1);

                    Point centerPoint = new Point((upL.x + lowR.x) / 2, (upL.y + lowR.y) / 2);

                    Point mPTop = Point.getMidpoint(upL, upR);
                    Point mPLeft = Point.getMidpoint(upL, lowL);
                    Point mPRight = Point.getMidpoint(upR, lowR);
                    Point mPBottom = Point.getMidpoint(lowL, lowR);

                    decimal swing = ((decimal)(1 + sideLen)) / (2 * sideLength);


                    set(mPTop, ((decimal)(get(upL) + get(upR))) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2));
                    set(mPLeft, ((decimal)(get(upL) + get(lowL))) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2));
                    set(mPRight, ((decimal)(get(upR) + get(lowR))) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2));
                    set(mPBottom, ((decimal)(get(lowL) + get(lowR))) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2));


                    swing *= 2;
                    set(centerPoint, ((decimal)(get(upL) + get(upR) + get(lowL) + get(lowR))) / 4 + ((decimal)r.NextDouble() * swing) - (swing / 2));
                }
            }
        }



    }

    void set(int x, int y, decimal d)
    {
        set(new Point(x, y), d);
    }

    void set(Point p, decimal d)
    {
        data[p].Add(d);
    }

    Decimal get(int x, int y)
    {
        return get(new Point(x, y));
    }
    Decimal get(Point p)
    {
        if (data[p].Count == 0)
        {
            Console.WriteLine("No elements.");
            return 0;
        }
        return data[p].Average();
    }
Wilson
  • 8,570
  • 20
  • 66
  • 101
  • How are 0,0 and 5,5 coordinates of a square of size 5? – Eric Lippert Feb 21 '14 at 01:59
  • Sorry, I meant (0,0) and (4,4). – Wilson Feb 21 '14 at 02:25
  • I don't think the given answer is correct. You shouldn't ever be setting any point twice. **Don't set a point until you have all four neighbor points calculated**. – Eric Lippert Feb 21 '14 at 14:15
  • Wait, **why are you doing this recursively**? This isn't an algorithm that easily admits to recursion, and it is easy to do iteratively. – Eric Lippert Feb 21 '14 at 16:31
  • I rewrote it iteratively yesterday and it works, but I'm still not sure how to avoid multiple assignments to some of the midpoints. I added my code above. – Wilson Feb 21 '14 at 16:54

2 Answers2

0

Here's my guess as I don't have access to ide right now.

You are rewriting values to the points between new squares here:

    data[mP1] = ((decimal)(data[p1]  + data[p1_2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
    data[mP2] = ((decimal)(data[p1] + data[p2_1])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
    data[mP3] = ((decimal)(data[p1_2] + data[p2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);
    data[mP4] = ((decimal)(data[p2_1] + data[p2])) / 2 + ((decimal)r.NextDouble() * swing) - (swing / 2);

Those midpoints are shared among several squares and should not be reset by the last one to call the method, but averaged instead.

Grozz
  • 8,317
  • 4
  • 38
  • 53
  • So I changed the values in `data` to being `List`. Every time I set a value it simply adds the new value to the List. At the end, it uses the list's averages to output the image. I added a new image to my original question showing what the new output looks like. It helped, but the borders are still apparent. – Wilson Feb 20 '14 at 23:34
0

I had a similar problem not to long ago and the solution was actually rather simple. The mistake is that you are averaging only the corner that surround a midpoint rather than averaging the entire "diamond" that surround it. Therefore it leaves the square artifacts behind so the simple solution is to find the average of the two corners you are already averaging and the centers of the squares to the left and to the right, if there is no square to the left or right then you can let it be or add the average plus a random number, in addition you are calling genMap four times therfore causing it to reach the bottom of a node and then continue to the next, currently this is not an issue but once you implement the complete diamond step you will find that their is never a neighbor to left or right sense you do not calculate each level once at a time. To fix this just call the function once and have a for loop go through each "sector" of the specific size.

egg
  • 115
  • 1
  • 9