2D Terrian Generator & diamond square algorithmus

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    2D Terrian Generator & diamond square algorithmus

    Hey, Mich würde mal interessieren ob jemand von euch Erfahrung damit hat 2D Karten zu generieren.Wie macht ihr das, welchen Algorithmus benutzt ihr? Ich habe mir mal den "diamond square algorithmus" angeschaut ihn aber nicht ganz verstanden , es wäre super wenn mir jemand von euch erklären könnte wie man sowas angeht , zumal mein Englisch nicht das beste ist aber die wenigen Tutorials die es gibt auf englisch sind.Bei solchen Themen ist es dann schwer für mich nach zu vollziehen was genau gemacht wird.Vielen Dank im Vorraus :)
    Also -auch wenn dein Englisch nicht so gut ist- eine sehr gute Tutorialreihe gibt es bei Brackeys: youtube.com/user/Brackeys

    Wo hast du dir den DSA angeschaut? Bei Wikipedia ist der -finde Ich- gut erklärt: de.wikipedia.org/wiki/Diamond-square_Algorithmus

    Wikipedia schrieb:

    Ausgangspunkt für die Generierung einer fraktalen Landschaft auf Basis des Diamond-square Algorithmus ist ein Quadrat. Jeder Ecke des Quadrats wird ein Höhenwert zugeordnet. Der Algorithmus zerlegt das Quadrat rekursiv in kleinere Quadrate, wobei der Höhenwert des Mittelpunkts als Mittelwert der vier Eckpunkte, plus einer zufälligen Verschiebung, definiert wird. Analog wird der Höhenwert der Seitenhalbierenden eines Quadrats als Mittelwert der vier horizontal umgebenden Punkte, plus einer zufälligen Verschiebung, definiert. Die Verschiebung ist Normalverteilt mit einem Mittelwert von 0 und nimmt mit der Größe der Rechtecke ab. Die Mittelpunkte und Seitenhalbierende bilden die Eckpunkte der neuen Rechtecke. Ausnahme von der Regel zur Generierung der neuen Punkte bilden die vier Außenseiten des ursprüngliche Rechtecks, die jeweils nach der eindimensionalen Mittelpunktverschiebung generiert werden.


    LG :)

    Thorstian schrieb:

    ich wüsste nicht wie ich es umsetzen sollte
    Warum willst Du einen Algorithmus umsetzen, wenn Du ihn nicht verstehst?
    Du bist so nicht in der Lage, Fehler selbstständig zu finden und zu beheben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hi
    aber es handelt sich in diesem Fall nicht um Hilfe zur Selbsthilfe, sondern um "Zeig' mir wie's geht und ich kann's kopieren". Kopieren und kapieren bringt für zukünftige Algorithmen wenig.

    "Ausgangspunkt für die Generierung einer fraktalen Landschaft auf Basis des Diamond-square Algorithmus ist ein Quadrat. Jeder Ecke des Quadrats wird ein Höhenwert zugeordnet."
    --> Stelle dir ein Quadrat vor, beachte im Speziellen die Eckpunkte

    "Der Algorithmus zerlegt das Quadrat rekursiv in kleinere Quadrate, wobei der Höhenwert des Mittelpunkts als Mittelwert der vier Eckpunkte, plus einer zufälligen Verschiebung, definiert wird."
    --> Füge einen Mittelpunkt hinzu, wodurch du das erste Quadrat zu 4 zerlegen kannst. Wenn du nun das 2D-Modell in ein 3D-Modell überträgst (wobei z die dritte Achse sei), wird z als Mittelwert der zs der anderen Eckpunkte aufgefasst (d.h. A1.Z/4 + A2.Z/4 + A3.Z/4 + A4.Z/4) und ein zufälliger "offset" draufaddiert, sodass eben eine Verschiebung stattfindet.

    "Analog wird der Höhenwert der Seitenhalbierenden eines Quadrats als Mittelwert der vier horizontal umgebenden Punkte, plus einer zufälligen Verschiebung, definiert."
    --> Mache das gleiche für die Seitenhalbierenden des Quadrats. Die Seitenhalbierenden ergeben sich eben, wenn du das Quadrat am Mittelpunkt viertelst. Es wird immer nur die z-Achse um ein zufälliges z verschoben
    Wiederhole den Algorithmus bis zu einer gewissen Tiefe (z.B. bei einer Tiefe von 8 hättest du bereits 2^8*2^8 = 2^9 Felder, bzw. eben bei einer Tiefe von k hättest du 2^k*2^k = 2^(k+1) Felder)

    "Die Verschiebung ist Normalverteilt mit einem Mittelwert von 0 und nimmt mit der Größe der Rechtecke ab."
    --> Der offset soll immer kleiner werden, je kleiner das betrachtete Quadrat wird, bzw. je tiefer du in das Rechteck reinwanderst. Normalverteilt heißt, dass, große Abweichungen immer unwahrscheinlicher werden (d.h. mit der Wahrscheinlichkeit e^-m^2 auftreten), siehe Anmerkungen.

    "Die Mittelpunkte und Seitenhalbierende bilden die Eckpunkte der neuen Rechtecke."
    --> Bereits erläutert

    "Ausnahme von der Regel zur Generierung der neuen Punkte bilden die vier Außenseiten des ursprüngliche Rechtecks, die jeweils nach der eindimensionalen Mittelpunktverschiebung generiert werden."
    --> Ist klar, denn irgendwoher musst du das ursprüngliche Rechteck ja auch bekommen. Die berechnest du aber nach dem gleichen Schema: Vier Eckpunkte, eine verschobene z-Koordinate (d.h. mit Offset). Wenn du bspw. einfach nur ein Rechteck mit festem z befüllen wollen würdest, müsstest du das nat. nicht weiter angeben.

    Ich kannte den Algorithmus btw. vorher nicht, aber so ähnlich hätte ich das ebenfalls angegangen.

    Anmerkungen:
    - Statt der normalverteilten Abweichung kannst du auch einfach 1/2^tiefe als maximale Abweichung nehmen und das mit einer Zufallszahl multiplizieren, glaube ich.
    Und für Ganzzahlen gilt: 2^k = 1 << k, wobei 0 <= k < n, n ist für Byte 8, für Short 15, für Integer 31, für Long 63 (für SByte 7, für UShort 16, für UInteger 32, für ULong 64). Um auf die Gleitkommazahl zu schließen, einfach konvertieren.
    Das sollte einfacher sein, als noch einen Algorithmus durchzuarbeiten ;p.
    Noch ein kleiner Schnipsel:

    VB.NET-Quellcode

    1. Dim k As Integer = 2
    2. Dim x As Integer = (1 << k)
    3. Dim xm1 As Double = 1.0/x


    - Ein Single-Array eignet sich sehr gut zur Darstellung eines solchen Systems. Das Array hat dann halt (2^k+1) * (2^k+1) = 2^(k+1)+2*(2^k)+1 = 2^(k+2)+1 Einträge (kann das sein?), wobei y*(2^k +1)+x, wobei 0 <= x < 2^k+1 gilt... Ich hoffe mal, du kennst diese Art der Notation. Du speicherst auf jeden Fall einfach nur den z-Wert der Koordinaten, x und y ergeben sich durch die zugehörigen x und y der Indexierung (y = index/(2^k+1), x = index mod (2^k+1)) Die 1 wird also aufaddiert, weil du ja nicht nur den Anfangspunkt, sondern auch den Endpunkt speichern musst.

    Gruß
    ~blaze~

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

    @~blaze~ vielen dank das hat mir jetzt schon sehr geholfen ich glaube ich habe es jetzt Grundlegend verstanden.Ich werde versuchen es um zu setzen. Bei Rückfragen melde ich mich wieder :)

    Edit: Wie kann ich denn ein Quadrat am besten rekursiv zerlegen?

    Mein bisheriger Code:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System.Math
    3. Public Class Form1
    4. Dim map(17, 17) As Double
    5. Dim rnd As New System.Random
    6. Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    7. Select Case e.KeyCode
    8. Case Keys.R
    9. gen()
    10. End Select
    11. End Sub
    12. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    13. Me.DoubleBuffered = True
    14. End Sub
    15. Private Sub gen()
    16. 'Eckpunkte
    17. map(1, 1) = 1.0
    18. map(17, 1) = 2.0
    19. map(1, 17) = 8.0
    20. map(17, 17) = 10.0
    21. 'Mitte
    22. map(9, 9) = Math.Round(map(1, 1) / 4 + map(17, 1) / 4 + map(1, 17) / 4 + map(17, 17) / 4 + rnd.NextDouble, 2)
    23. End Sub
    24. Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    25. With e.Graphics
    26. Dim s As Integer = 50
    27. For x = 1 To 16
    28. For y = 1 To 16
    29. .DrawRectangle(Pens.Red, x * s, y * s, s, s)
    30. Next
    31. Next
    32. For x = 1 To 17
    33. For y = 1 To 17
    34. If Not map(x, y) = 0 Then
    35. .FillEllipse(Brushes.Red, x * s - 5, y * s - 5, 10, 10)
    36. Else
    37. .FillEllipse(Brushes.Blue, x * s - 5, y * s - 5, 10, 10)
    38. End If
    39. Next
    40. Next
    41. For x = 1 To 17
    42. For y = 1 To 17
    43. .DrawString(map(x, y).ToString, Font, Brushes.Green, x * s, y * s + 10)
    44. Next
    45. Next
    46. End With
    47. End Sub
    48. Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
    49. Me.Invalidate()
    50. End Sub
    51. End Class

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Thorstian“ ()

    Du verwendest jetzt ein 18x18-Array. Vergiss nicht, dass Private map(17, 17) As Double ein Array mit 18 * 18 Elementen bereitstellt.
    Du würdest die Randwerte, d.h. map(0, 0) und map(0, 16), map(16, 16), map(16, 0) festlegen, dann eine rekursive Funktion aufrufen, die rekursiv alle festlegt, die dazwischen liegen, d.h. anfangs zwischen 0 und 16, dann zwischen 0 und 7, 7 und 16 (d.h. du berechnest genau welchen Wert hier?), dann zwischen 0 und 3 und 3 und 7, dann 7 und 11 und 11 und 16... irgendwas passt da nicht... :D Das ist mir gerade aber zu hoch; ich hab' für heute keine Lust mehr.

    Überleg' mal selber etwas, das Grundprinzip sollte ja eigentlich klar sein.

    Gruß
    ~blaze~
    Ahoi,
    also der Diamond Square ist wohl deine beste Wahl wenn es darum geht was zu lernen und zu verstehen. Allerdings habe ich mal für jemand anderen der eine recht ähnliche frage hatte folgende Lösung ins Auge gefasst: MyIsland UPDATE [Pre Beta 0.1] (Permalink zu meinem Beitrag zu der Frage)
    Das ist in keinem Fall eine wirklich gute oder besondere Lösung aber eine Möglichkeit voranzukommen und eine gute Basis zu schaffen, guck es dir an, versuche es zu verstehen und evtl. gibts es dir genug Ideen auf Grundlage vn der Methode deine eigene zu entwickeln die für dich am besten funktioniert ;)

    Mfg
    Paul
    "yippieh! it compiles - ship it!"

    VB.NET-Quellcode

    1. Dim y As Integer = halfstep
    2. While y < h + halfstep
    3. y += stepsize
    4. 'Rest Code...
    5. End While
    "Zwei Dinge sind unendlich, das Universum und die menschliche Dummheit, aber bei dem Universum bin ich mir noch nicht ganz sicher." Albert Einstein
    @hellmaster159
    Whoat? Ich würde das eher als For-Konstrukt übersetzen:

    Visual Basic-Quellcode

    1. For y = halfstep To h Step halfstep
    2. '...
    3. Next


    @Counterbug
    ;D

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „nafets“ ()

    geht auch als For-Schleife

    VB.NET-Quellcode

    1. For y As Integer = halfstep To h+halfstep-1 Step stepsize
    2. Next


    Edit: @nafets hat Recht, wahrscheinlich reicht To h
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Überleg' dir doch mal, was du tun müsstest, um ein Rechteck mit einer Karte zu befüllen. Stell dir vor, du nimmst ein Rechteck, halbierst es jeweils an beiden Kanten. Dann wiederholst du das ganze jeweils für jedes Unterrechteck, ab einer gewissen Tiefe hörst du auf. Jetzt überlege, wie du das auf einen rekursiven Algorithmus übertragen könntest. Von mir aus speichere auch erst mal alle Rechtecke und erst später die Eckpunkte. Danach ordnest du noch jedem Rechteck einen z-Wert zu, sodass eine bestimmte Tiefe enthalten ist. Wenn du das gemacht hast, erhöhst du die Komplexität und passt den Z-Wert an.
    Wenn du es dir einfach machen willst, verwende eine LinkedList(Of T), sobald das implementiert ist, kannst du überlegen, wie du es in ein Array packst.

    Gruß
    ~blaze~
    Ich habe jetzt mal folgendes Zusammengebastelt,habe ich etwas übersehen bis auf das was im Kommentar steht oder ist es soweit jetzt richtig @~blaze~ ?

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Math
    2. Public Class Form1
    3. Declare Function AllocConsole Lib "kernel32" () As Int32
    4. Declare Function FreeConsole Lib "kernel32" () As Int32
    5. Dim map(9, 9) As Double
    6. Dim map_x As Integer = 9
    7. Dim map_y As Integer = 9
    8. Dim rnd As New System.Random
    9. Dim A As Integer = 10
    10. Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
    11. Select Case e.KeyCode
    12. Case Keys.R
    13. map(0, 0) = rnd.Next(0, 11)
    14. map(8, 0) = rnd.Next(0, 11)
    15. map(0, 8) = rnd.Next(0, 11)
    16. map(8, 8) = rnd.Next(0, 11)
    17. DoAllTheWork(0, 8, 0, 8)
    18. End Select
    19. End Sub
    20. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    21. Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
    22. Me.Location = New Point(0, 0)
    23. Me.Size = New Point(500, 500)
    24. Me.DoubleBuffered = True
    25. MsgBox(rnd.NextDouble)
    26. AllocConsole()
    27. End Sub
    28. Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
    29. With e.Graphics
    30. Dim f As New Font("Arial", 16)
    31. For x = 0 To map_x - 1
    32. For y = 0 To map_y - 1
    33. .FillEllipse(Brushes.Red, x * 40 + 32, y * 40 + 32, 10, 10)
    34. .DrawString(map(x, y), f, Brushes.Green, x * 40 + 32, y * 40 + 40)
    35. Next
    36. Next
    37. End With
    38. End Sub
    39. Private Sub DoAllTheWork(ByVal x1 As Integer, ByVal x2 As Integer, ByVal y1 As Integer, ByVal y2 As Integer)
    40. 'Zufallswerte ggf plus oder minus und mit der Zeit geringer werdent
    41. Console.WriteLine("X1: " & x1)
    42. Console.WriteLine("X2: " & x2)
    43. Console.WriteLine("Y1: " & y1)
    44. Console.WriteLine("Y2: " & y2)
    45. 'MsgBox("")
    46. If x1 - x2 > 1 Or x1 - x2 < -1 Then
    47. map((x1 + x2) / 2, (y1 + y2) / 2) = Math.Round((map(x1, y1) + map(x1, y2) + map(x2, y1) + map(x2, y2)) / 4 + rnd.NextDouble, 1)
    48. map(x1, (y1 + y2) / 2) = Math.Round((map(x1, y1) + map(x1, y2)) / 2 + rnd.NextDouble, 1)
    49. map(x2, (y1 + y2) / 2) = Math.Round((map(x2, y1) + map(x2, y2)) / 2 + rnd.NextDouble, 1)
    50. map((x1 + x2) / 2, y1) = Math.Round((map(x1, y1) + map(x2, y1)) / 2 + rnd.NextDouble, 1)
    51. map((x1 + x2) / 2, y2) = Math.Round((map(x1, y2) + map(x2, y2)) / 2 + rnd.NextDouble, 1)
    52. DoAllTheWork(x1, (x1 + x2) / 2, y1, (y1 + y2) / 2)
    53. DoAllTheWork(x1, (x1 + x2) / 2, y2, (y1 + y2) / 2)
    54. DoAllTheWork(x2, (x1 + x2) / 2, y1, (y1 + y2) / 2)
    55. DoAllTheWork(x2, (x1 + x2) / 2, y2, (y1 + y2) / 2)
    56. End If
    57. End Sub
    58. Private Sub FPSMaker_Tick(sender As Object, e As EventArgs) Handles FPSMaker.Tick
    59. Me.Invalidate()
    60. End Sub
    61. End Class

    Sicher, dass Option Strict auf On ist? (x1 + x2) / 2 ist eigentlich ein Double. Es wäre eher (x1 + x2) \ 2 oder, zur Überlaufvermeidung x1 + (x2 - x1) \ 2. Ich glaube nicht, dass es ganz richtig ist. wenn du das Rechteck in Teile schneidest erhältst du folgende Rechtecke:

    VB.NET-Quellcode

    1. Dim rect As Rectangle = '...
    2. Dim tl As New Rectangle(rect.Left, rect.Top, rect.Width \ 2, rect.Height \ 2)
    3. Dim tr As New Rectangle(rect.Left, rect.Top + rect.Height \ 2, rect.Width \ 2, rect.Height \ 2)
    4. Dim bl As New Rectangle(rect.Left, rect.Top + rect.Height \ 2, rect.Width \ 2, rect.Height \ 2)
    5. Dim br As New Rectangle(rect.Left + rect.Width \ 2, rect.Top + rect.Height \ 2, rect.Width \ 2, rect.Height \ 2)

    Ohne die exakte Benennung blicke ich da aber nicht ganz durch. ;)
    Pro diesem Rechteck (pro Rekursionsschritt) definierst du dann das z. Übrigens: rnd.NextDouble() gibt einen Wert zwischen 0 und 1 zurück. Und gib doch beim Rekursionsschritt bereits ein z mit an und die Tiefe der Rekursion (brauchst du später)

    VB.NET-Quellcode

    1. Public Sub Initialize(bounds As Rectangle, depth As Integer, z As Double)

    Natürlich kannst du das Rechteck auch so angeben, wie du es jetzt gemacht hast.

    Sieht auf jeden Fall schon mal vielversprechend aus.

    Gruß
    ~blaze~