Tiefenkarte mit Diamond Square

    • VB.NET
    • .NET (FX) 3.0–3.5

    Es gibt 5 Antworten in diesem Thema. Der letzte Beitrag () ist von Thorstian.

      Tiefenkarte mit Diamond Square

      Hi
      ich habe mich im Rahmen eines Benutzerwunsches dazu bereiterklärt, den Diamond-Square-Algorithmus zu implementieren. Hierbei verwende ich ein iteratives Vorgehen. Es wird ein Raster mit 2^n x 2^n Punkten in festem Abstand verwendet, denen jeweils ein Höhenwert zugeordnet wird, wobei n die Länge einer Seite angibt. tiefe sei hierbei der maximale Ausschlag der Tiefe. Alle Höhenwerte liegen zwischen -tiefe und tiefe.
      Das Verfahren ermöglicht bspw. das Generieren von Terrain auf Basis von Zufallszahlen, ohne hierbei hochgradige Abweichungen zwischen zwei einzelnen Punkten zur Folge zu haben.
      Die Vorgehensweise ist besonders anschaulich durch die rekursive Vorgehensweise ersichtlich:
      Man möchte ein Quadrat mit zufälligen, aber "glatten" Höhenwerten effizient befüllen. Das Quadrat soll eine Seitenlänge, die eine Potenz von 2 darstellt, besitzen, wodurch der Algorithmus weiter vereinfacht wird.
      Jedem Paar (x, y) wird hierbei ein Höhenwert zugeordnet, wobei gilt, dass x und y je zwischen 0 und 2^n-1 liegen (jew. inklusive).
      Es wird ein Quadrat mit zufällig gesetzten Höhenwerten der Eckpunkte genommen und anschließend folgender Algorithmus ausgeführt:
      Das Quadrat wird, sofern die Seitenlänge größer als 0 ist, in 4 gleichgroße Quadrate zerteilt, für die der Algorithmus ebenfalls aufgerufen wird. Nun werden jeweils die ungesetzten fünf Punkte (jeweils die Seitenhalbierenden aller Seiten und der Mittelpunkt des ursprünglichen Quadrats) mit zufälligen Werten befüllt. Die zufälligen Werte sollen nicht zu weit von den Höhenwerten des ursprünglichen Quadrats abweichen, da es sonst zu einer hohen Höhendifferenz zwischen zwei Nachbarn kommen kann. Hierdurch erklärt sich auch das rekursive Vorgehen, denn es bietet sich an, die Tiefenunterschiede durch tiefe/2^i zu beschränken, wodurch solch ein glatter Übergang zwischen zwei Nachbarn möglich ist. Der neu generierte Wert wird daher auf den Mittelwert der Höhenwerte der jeweiligen Quadrateckpunkte aufaddiert und anschließend gespeichert.

      Spoiler anzeigen

      VB.NET-Quellcode

      1. Partial Class Map
      2. Public Shared Function DiamondSquare(power As Integer, depth As Double, random As Random) As Map
      3. If power >= 30 Then Throw New ArgumentOutOfRangeException("power", "Unsupported buffer size.")
      4. If random Is Nothing Then random = New Random()
      5. Dim nextRandom As Func(Of Double) = Function() MapRandom(random.NextDouble()) * depth
      6. Dim w As Integer = (1 << power) + 1
      7. Dim buffer(w * w - 1) As Double
      8. DiamondSquareHelper(New Context(buffer, w, w), w - 1, nextRandom)
      9. Return New Map(buffer, w, w)
      10. End Function
      11. Private Shared Sub DiamondSquareHelper(context As Context, a As Integer, nextRandom As Func(Of Double))
      12. Dim v As Integer = a
      13. Dim d As Integer = 1
      14. context.SetValue(0, 0, nextRandom())
      15. context.SetValue(v, 0, nextRandom())
      16. context.SetValue(0, v, nextRandom())
      17. context.SetValue(v, v, nextRandom())
      18. 'v is the edge length of the divided square
      19. While v > 0
      20. For i As Integer = 0 To d - 1
      21. For j As Integer = 0 To d - 1
      22. Dim x = i * v
      23. Dim y = j * v
      24. Dim ul = context.GetValue(x, y)
      25. Dim ur = context.GetValue((x + v), y)
      26. Dim ll = context.GetValue(x, (y + v))
      27. Dim lr = context.GetValue((x + v), (y + v))
      28. 'set diamond
      29. '->top center
      30. context.SetValue(x + v \ 2, y, (ul + ur) / 2 + nextRandom() / d)
      31. '->left center
      32. context.SetValue(x, y + v \ 2, (ul + ll) / 2 + nextRandom() / d)
      33. 'right center
      34. context.SetValue(x + v, y + v \ 2, (ur + lr) / 2 + nextRandom() / d)
      35. 'bottom center
      36. context.SetValue(x + v \ 2, y + v, (ll + lr) / 2 + nextRandom() / d)
      37. 'set center point
      38. context.SetValue(x + v \ 2, y + v \ 2, (ul + ur + ll + lr) / 4 + nextRandom() / d)
      39. Next
      40. Next
      41. v \= 2
      42. d *= 2
      43. End While
      44. End Sub
      45. Private Shared Function MapRandom(random As Double) As Double
      46. Return 2 * random - 1
      47. End Function
      48. Private Structure Context
      49. Public Width, Height As Integer
      50. Public Buffer As Double()
      51. Public Sub SetValue(x As Integer, y As Integer, value As Double)
      52. Buffer(x + y * Width) = value
      53. End Sub
      54. Public Function GetValue(x As Integer, y As Integer) As Double
      55. Return Buffer(x + y * Width)
      56. End Function
      57. Public Sub New(buffer As Double(), width As Integer, height As Integer)
      58. Me.Buffer = buffer
      59. Me.Width = width
      60. Me.Height = height
      61. End Sub
      62. End Structure
      63. End Class
      64. Partial Public Class Map
      65. Private Shared ReadOnly Empty As New Map(New Double() {}, 0, 0)
      66. Private ReadOnly _array As Double()
      67. Private ReadOnly _width, _height As Integer
      68. Public ReadOnly Property Width As Integer
      69. Get
      70. Return _width
      71. End Get
      72. End Property
      73. Public ReadOnly Property Height As Integer
      74. Get
      75. Return _height
      76. End Get
      77. End Property
      78. Private Sub New(array As Double(), width As Integer, height As Integer)
      79. _array = array
      80. _width = width
      81. _height = height
      82. End Sub
      83. Public Sub New(width As Integer, height As Integer)
      84. _array = New Double(width * height - 1) {}
      85. End Sub
      86. Default Public Property Value(x As Integer, y As Integer) As Double
      87. Get
      88. If x < 0 OrElse x >= _width OrElse y < 0 OrElse y >= _height Then
      89. Throw New ArgumentException("The specified point is not within bounds.")
      90. End If
      91. Return _array(x + y * _height)
      92. End Get
      93. Set(value As Double)
      94. If x < 0 OrElse x >= _width OrElse y < 0 OrElse y >= _height Then
      95. Throw New ArgumentException("The specified point is not within bounds.")
      96. End If
      97. _array(x + y * _height) = value
      98. End Set
      99. End Property
      100. End Class

      Das Ergebnis kann bspw. wie folgt sein:


      Für die Verwendung einfach Map.DiamondSquare aufrufen, wobei power n entspricht und depth der Tiefe. Der Parameter Random ermöglicht es, einen eigenen Zufallszahlengenerator zu übergeben. Hierbei wird lediglich NextDouble aufgerufen. Wird Nothing übergeben, so wird einfach eine neue Instanz von System.Random verwendet.
      Viel Spaß damit!

      Viele Grüße
      ~blaze~

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

      Also ich generiere damit ganz komische Grafiken (siehe Anhang). Solche "harten" Übergänge sollten doch nicht generiert werden, oder?
      Aufruf:

      C#-Quellcode

      1. int p = 9;
      2. int s = 1 << p;
      3. Map m = Map.DiamondSquare(p, 0.8, null);
      4. Bitmap b = new Bitmap(s, s);
      5. for (int x = 0; x < s; x++)
      6. {
      7. for (int y = 0; y < s; y++)
      8. {
      9. byte c = (byte)(m[x, y] * 256);
      10. b.SetPixel(x, y, Color.FromArgb(c, c, c));
      11. }
      12. }
      Bilder
      • image.png

        217,2 kB, 512×512, 166 mal angesehen
      Mit freundlichen Grüßen,
      Thunderbolt
      Ahja. Ich wusste gar nicht mal, dass es checked/unchecked gibt ^^
      Also habe ich schnell ein ​checked darumgeschrieben und es fliegt eine OverflowException, weil ein Wert in m -1.8​ beträgt.
      So funktioniert es:

      C#-Quellcode

      1. int power = 9;
      2. double depth = 1;
      3. Map m = Map.DiamondSquare(power, depth, null);
      4. Bitmap b = new Bitmap(m.Width, m.Height);
      5. checked
      6. {
      7. for (int x = 0; x < m.Width; x++)
      8. {
      9. for (int y = 0; y < m.Height; y++)
      10. {
      11. byte c = (byte)Math.Max(Math.Min((m[x, y] + depth) / (2 * depth) * 255, 255), 0);
      12. b.SetPixel(x, y, Color.FromArgb(c, c, c));
      13. }
      14. }
      15. }
      Mit freundlichen Grüßen,
      Thunderbolt