[Technikvorschlag] Kartentool

Es lässt mich nichtz los, das Kartentool. Deswegen poste ich mal die Idee, um Meinungen zu erfahren.

Es geht dabei um ein Programm mit dessen hilfe nach und nach immer mehr Details zu einer Weltkarte hinzugefügt werden können.

Die bisherige Idee generiert schattierte Heightmaps mit Hilfe sogenannter Constraints. Die Constraints können vom Benutzer festgelegt werden und stellen feste Eigenschaften dar, die für die Generierung der Landschaft genutz werden. Folgende Constraints sind angedacht:

  • Höhenpunkte (Punkte mit festgelegter Höhe)
  • Höhenkreise (kreisförmige Bereiche mit einstellbarem Durchmesser, deren Höhe im inneren fest gelegt ist)
  • Steigungsbereichskreise (kreisförmige Bereiche mit einstellbarem Durchmesser und Richtung, deren Steigung in die eingestellte Richtung sich im angegebenen Bereich bewegt)
  • Höhenbereiche (Punkte mit einer Höhe, die auf einen Bereich fest gelegt ist)
  • Höhenlinien (Polygone in deren innerem die angegebene Höhe überschritten oder unterschritten wird)
  • Flüsse (Punktlisten bei denen zwischen den Punkten immer eine abfallende Steigung existiert)

Das Programm soll die Liste der Constraints in Etappen von oben nach unten beherrsachen. Flüsse sind also erst für eine späte Version im Endausbau geplant.

Zusätzlich soll eine Materialkarte die Beschaffenheit des Untergrunds fest legen.

Die Idee des Programms ist es alle Constraints einzuhalten und mithilfe von Perlin Noise eine realistische Höhenmap zu erzeugen.

Für eine zweite version wäre auch eine Errosionsfunktion wünschenswert.

Vorarbeiten

Python bietet mit der imageLIB eine komfortable Möglichkeit ein Testprogramm umzusetzen. Einige nützliche Codeschnipsel:

import Image
i = Image.new("RGB",(800,600))
i.putpixel((12,14),(0,255,133))
i.save("output.jpg")
i.show() <-- nützlich für Debugging

Idee

  • Erzeugen einer Minmap und einer Maxmap beim Aufruf des Renderbefehls.
  • Das Rauschen muss zwischen min- und maxMap bleiben.
  • Verwalten einer Liste der Constraints (die Liste enthält Objekte der abstrakten Klasse Constraint)
  • Jede von Constraint vererbte Klasse implementiert eine Funktion, die es erlaubt min und max für eine gegebene Koordinate zu bestimmen.

Jonny, 2009/08/14 20:30

Idee 2

  1. Minmap und Maxmap erzeugen
  2. Perlin Noise zwischen Min- und Maxmap legen (dabei entstehen unangenehm große Steigungen)
  3. Erstellen einer Flusslandschaft anhand der Noisemap
  4. Updaten der Minmap, um Seen zu vermeiden (Minmap muss dafür stellenweise angehoben werden)
  5. Einpassen der fertigen Heightmap zwischen Maxmap und geupdateter Minmap unter Berücksichtigung der Flussläufe

Jonny, 2010/06/21 22:02

Idee 3

A) Wasserkarte erstellen

  1. Initialisieren einer Wasserkarte (Matrix mit so vielen Feldern wie Pixel im Heigtmap-Render sein sollen) mit folgenden Eigenschaften:
    • Fließrichtung (Oben, Unten, Rechts, Links und die 4 Diagonalen dazwischen)
    • Wassermenge (Ganzzahl)
  2. Setzen der Fließrichtung zufällig für jeden Pixel.
    An diesem Punkt können Seen entstehen. Das ist nicht wünschenswert (Seen sollen nur per Hand definiert werden). Daher werden jetzt für alle Seen Abflüsse in die Karte eingebaut.
  3. Wenn alle 8 umliegenden Pixel in die Mitte zeigen, ist es ein See, der entfernt werden muss. Dafür wird einer der 8 Nachbarn ausgewählt und die Richtung umgedreht.
  4. Beginnend oben links in der Ecke und zeilenweise voranschreitend wird jetzt für jeden Pixel die Wassermenge bestimmt:
    • 1 Für den Pixel selbst
    • Es werden die 8 umliegenden Pixel überprüft und für jeden Pixel der in die Richtung des ursprünglichen Pixels zeigt, wird nach dem gleichen Verfahren die Wassermenge bestimmt.
    • Für Pixel die außerhalb des Rands der Karte liegen würden wird ein zufälliger Wert für die Wassermenge zwischen 1 und 10 gewählt.
  5. Wenn für einen Pixel die Wassermenge bestimmt wurde, ist dieser Wert mindestens 1. Pixel mit einer Wassermenge > 1 können übersprungen werden, da sie bereits bestimmt sind.
    An diesem Punkt kann es zwar keine Seen mehr geben, jedoch können spiralförmige Flussläufe entstehen, die die topologie zu stark einschränken. Insbesondere durch das entfernen der Seen können sich diese Konstellationen ergeben. Abhilfe schafft es, wenn in Umgebung zu großen Wassermengen das Wasser immer in Richtung des größeren Flusses fließt.
  6. In einer zweiten Iteration über alle Pixel wird für jeden Pixel überprüft:
    1. Für je 5 Punkte (aktuell gesetzte) Wassermenge wird ein Suchfeld um den aktuellen Pixel um 1 vergrößert.
    2. Innerhalb dieses Suchfelds werden alle Richtungen umgedreht, die vom aktuellen Pixel weg zeigen.
      Da hier mehrmaliges Umdrehen der Richtungen vorkommen kann, sollte der Wert von 5 unter Umständen noch angepasst werden.
    3. Da die Wassermengen durch diese Operation ohnehin ungültig geworden sind, können sie auf 0 gesetzt werden.
  7. Jetzt wird in einer weiteren Iteration für jeden Pixel wieder die korrekte Wassermenge bestimmt.

B) Höhenkarte bestimmen

Die Höhenkarte wird mit einer Min- und einer Maxmap bestimmt, die für jeden Schritt angepasst werden.

  1. Es werden zufällige Pixel der Höhenkarte gewählt und für diese innerhalb der durch Min- und Maxmap festgelegten Grenzen eine Höhe zufällig gewählt.
  2. Für das Wählen der Höhe wird die Wassermenge verwendet, um die Wahrscheinlichkeitsverteilung anzupassen. Je geringer die Wassermenge, desto höher sollte der zufällige Wert liegen.
    Da größere Flüsse sich bereits weit eingegraben haben, sollte sich die Kurve des Erwartungswerts der Höhe mit wachsender Wassermenge nicht-linear der Minmap annähern.
  3. Immer wenn ein Höhenpunkt festgelegt wurde, wird folgendermaßen die Min- und Maxmap aktualisiert:
    1. Entsprechend der Maximalsteigung (Schüttwinkel 60°) wird für die 8 Umliegenden Pixel überprüft, ob die Minmap angehoben und/oder die Maxmap abgesenkt werden muss. Wenn dies nötig ist, wird die Überprüfung und Anpassung für jeden angepassten Pixel wiederholt.
    2. Da Flüsse niemals Bergauf Fließen, kann die Minmap auf die für den Festgelegten Pixel gefundene Höhe bei jedem Pixel angehoben werden, der im Flusslauf oberhalb liegt, also in der Wasserkarte in Richtung des Pixels zeigt.
    3. Aus dem gleichen Grund kann die Maxmap für alle Pixel eingeschränkt werden, die im Flusslauf abwärts liegen.
  4. Es werden so lange Pixel zufällig festgelegt, bis alle Pixel definiert sind.

Derzeitiger Entwicklungsstand

Code in Python:

#! /usr/bin/env python
# coding=utf-8

import cv
from opencv.cv import CV_32FC1
from random import random, choice, randint, randrange

mapwidth = 200
mapheight = 200

class WMPix():
    dir = 0
    amount = 0


def updatemaxmap(maxmap, watermap, pix, height):
    maxmap[pix[0], pix[1]] = height
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 0: neighborheight = height + 20
    testpix = (pix[0] - 1, pix[1])
    if testpix[0] >= 0:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 1: neighborheight = height + 35
    testpix = (pix[0] - 1, pix[1] - 1)
    if testpix[0] >= 0 and testpix[1] >= 0:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 2: neighborheight = height + 20
    testpix = (pix[0], pix[1] - 1)
    if testpix[1] >= 0:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 3: neighborheight = height + 35
    testpix = (pix[0] + 1, pix[1] - 1)
    if testpix[0] < maxmap.height and testpix[1] >= 0:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 4: neighborheight = height + 20
    testpix = (pix[0] + 1, pix[1])
    if testpix[0] < maxmap.height:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 5: neighborheight = height + 35
    testpix = (pix[0] + 1, pix[1] + 1)
    if testpix[0] < maxmap.height and testpix[1] < maxmap.width:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 6: neighborheight = height + 20
    testpix = (pix[0], pix[1] + 1)
    if testpix[1] < maxmap.width:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)
    neighborheight = height
    if watermap[pix[0]][pix[1]].dir != 7: neighborheight = height + 35
    testpix = (pix[0] - 1, pix[1] + 1)
    if testpix[0] >= 0 and testpix[1] < maxmap.width:
        if maxmap[testpix[0], testpix[1]] > neighborheight:
            updatemaxmap(maxmap, watermap, testpix, neighborheight)


def updateminmap(minmap, watermap, pix, height):
    #print "Pos: "+str(pix[0])+","+str(pix[1])
    minmap[pix[0], pix[1]] = height
    neighborheight = height
    testpix = (pix[0] - 1, pix[1])
    if testpix[0] >= 0:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 0: neighborheight = height - 20
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0] - 1, pix[1] - 1)
    if testpix[0] >= 0 and testpix[1] >= 0:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 1: neighborheight = height - 35
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0], pix[1] - 1)
    if testpix[1] >= 0:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 2: neighborheight = height - 20
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0] + 1, pix[1] - 1)
    if testpix[0] < minmap.width - 1 and testpix[1] >= 0:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 3: neighborheight = height - 35
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0] + 1, pix[1])
    if testpix[0] < minmap.height - 1:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 4: neighborheight = height - 20
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0] + 1, pix[1] + 1)
    if testpix[0] < minmap.height - 1 and testpix[1] < minmap.width - 1:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 5: neighborheight = height - 35
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0], pix[1] + 1)
    if testpix[1] < minmap.width - 1:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 6: neighborheight = height - 20
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)
    neighborheight = height
    testpix = (pix[0] - 1, pix[1] + 1)
    if testpix[0] >= 0 and testpix[1] < minmap.width - 1:
        #print "Watermap x|y: "+str(testpix[0])+"|"+str(testpix[1])
        #print " :: "+str(watermap[testpix[0]][testpix[1]].dir)
        if watermap[testpix[0]][testpix[1]].dir != 7: neighborheight = height - 35
        if minmap[testpix[0], testpix[1]] < neighborheight:
            updateminmap(minmap, watermap, testpix, neighborheight)

#Wasserkarte erstellen
watermap = []
for y in range(0, mapheight - 1):
    line = []
    for x in range(0, mapwidth - 1):
        wmpx = WMPix()
        wmpx.dir = randint(0, 7)
        #wmpx.dir = 0
        line.append(wmpx)
    watermap.append(line)
print "Wasserkarte fertig"

#Höhenkarte erstellen
heightmap = cv.CreateImage((mapwidth, mapheight), 8, 1)
minmap = cv.CreateImage((mapwidth, mapheight), 8, 1)
maxmap = cv.CreateImage((mapwidth, mapheight), 8, 1)
watermapimage = cv.CreateImage((mapwidth, mapheight), 8, 1)
hpixel = []
#Die Liste der Pixel erstellen und die maxmap initialisieren
for y in range(0, mapwidth - 1):
    for x in range(0, mapheight - 1):
        hpixel.append((x, y))
        maxmap[x, y] = 255
        watermapimage[x, y] = watermap[x][y].dir * 22
print "Pixelliste erstellt"
while len(hpixel) > 0:
    #einen zufälligen Pixel auswählen
    pix = choice(hpixel)
    #print "Bearbeite Pixel (" + str(pix[0]) + "|" + str(pix[1]) + "). Noch " + str(len(hpixel)) + " Pixel zu bearbeiten."
    #die Höhe des Pixels setzen
    #print "Wähle neue Höhe zwischen " + str(minmap[pix[0], pix[1]]) + " und " + str(maxmap[pix[0], pix[1]]) + "."
    if minmap[pix[0], pix[1]] <= maxmap[pix[0], pix[1]]:
        height = randint(minmap[pix[0], pix[1]], maxmap[pix[0], pix[1]])
    else:
        height = maxmap[pix[0], pix[1]]
        print "Oops! Minmap ist " + str(minmap[pix[0], pix[1]]) + ", Maxmap ist " + str(maxmap[pix[0], pix[1]]) + "."
    heightmap[pix[0], pix[1]] = height
    #min- und maxmap updaten
    updatemaxmap(maxmap, watermap, pix, height)
    updateminmap(minmap, watermap, pix, height)
    #entfernen des Pixels von der Liste
    hpixel.remove(pix)


    #Anzeige
    cv.NamedWindow("heightmap", cv.CV_WINDOW_AUTOSIZE)
    cv.ShowImage("heightmap", heightmap)
    cv.MoveWindow("heightmap", 100, 200)
    cv.NamedWindow("minmap", cv.CV_WINDOW_AUTOSIZE)
    cv.ShowImage("minmap", minmap)
    cv.MoveWindow("minmap", 100 + mapwidth + 10, 200)
    cv.NamedWindow("maxmap", cv.CV_WINDOW_AUTOSIZE)
    cv.ShowImage("maxmap", maxmap)
    cv.MoveWindow("maxmap", 100 + 2 * mapwidth + 20, 200)
    cv.NamedWindow("watermap", cv.CV_WINDOW_AUTOSIZE)
    cv.ShowImage("watermap", watermapimage)
    cv.MoveWindow("watermap", 100 + 3 * mapwidth + 30, 200)
    if cv.WaitKey(10) == 27:
        break

cv.WaitKey(0)

Der Code stürzt leider wegen falscher Randrange ab.

Die korrekten Richtungen und alles was mit Wassermengen zu tun hat ist noch nicht implementiert.

So sieht es momentan aus: heightmaprenderer_v0.1

Melde dich an um einen Kommentar zu erstellen.
 
 
 
     
 
forum/welt/technikvorschlag_kartentool.txt · Zuletzt geändert: 27.11.2016 00:24 (Externe Bearbeitung)