Schlagwort-Archive: Landschaftsprofil

Landschaftsgenerator


Letzte Woche war ich auf der Suche nach Landschaftsgeneratoren. Irgendwie konnte ich jedoch nichts finden, was mir gefallen hätte und gleichzeitig kostenlos war. Aber dafür fand ich diesen Artikel hier:

http://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/

Erosion von Landschaften war etwas, das mich schon länger interessiert hat, aber ich war zu faul, so einen Algorithmus selbst zu kodieren. Der Beispielcode dort war C, schien mir aber einfach genug um ihn „schnell“ nach Java zu portieren. Ein paar Stunden hat es dann doch gedauert … und noch sind nicht alle Parameter über das UI editierbar. Aber es reicht für erste Experimente.

Terraingenerator v0.01
Terraingenerator v0.01

Das schaut jetzt noch nicht so grandios aus, aber in 3D sieht man die Effekte der Erosion sehr schön.

Terrain in 3D
Terrain in 3D

Der Algorithmus füllt die Senken auf sehr flache Weise, bislang habe ich es noch nicht geschafft, Parameter zu finden, die mehr rundlich gefüllte Senken ergeben würden und gleichzeitig die Bergkuppen weitgehend intakt lassen. Aber bis auf dieses Detail werden die Hänge gut abgetragen, und es bilden sich schöne Hochtäler und Plateaus.

Hier ein zweites Beispiel mit einem anderen Satz Parameter. Steilere Berge, und eine insgesamt welligere Basislandschaft.

Terrain in 3D
Terrain in 3D

Und zuletzt noch eine Variante mit 6 statt 3 Oktaven der Noise-Funktion, d.h. mehr kleinräumige Details in der Landschaft

Terrain in 3D, 6 Oktaven
Terrain in 3D, 6 Oktaven

Das sieht jetzt sehr nach den Fotos der Mars-Rover aus – ich vermute mal, das ist ein gutes Zeichen und zeigt, dass die Kombination der Noise-Funktion und des Erosions-Algortithmus mit diesem Satz Parameter schon recht nahe an der Natur ist.

Was fehlt sind Flüsse. Im Prinzip kann man den Algorithmus auch für Flüsse verwenden, da so ein „Droplet“ den Fluss von Wasser über die Landschaft simuliert. Flüsse transportieren das Geröll jedoch weiter, und, da sie stationär sind, schneiden sie sich tiefer in die Landschaft ein.  D.h. es wird ein zweiter Durchgang notwendig, mit einigen Stationären Droplet-Quellen und anderen Parametern, aber ich bin schon sehr gespannt, wie sich die Flussläufe präsentieren werden.

PS: Die Basislandschaft erzeuge ich per „Simplex Noise“, https://de.wikipedia.org/wiki/Simplex_Noise

PPS: Hier der Programmcode in Java – die einzige echte Änderung von mir zum Orginalcode ist, dass es für die Fließgeschwindigkeit jetzt einen Reibungswiderstand gibt und dass in der Iteration geprüft wird, ob ein Droplet den Bereich der Karte verlässt:

/*
* Based on public domain code from
*
* http://ranmantaru.com/blog/2011/10/08/water-erosion-on-heightmap-terrain/
*
*/
package heightfieldgenerator;

/**
* Note: In this code, the array is indexed with (x,z) and y is the height
*/
public class ErosionDeposit
{
private static final double LEVEL_EPSILON = 0.0001;

private Heightfield field;
private double [] erosion_r;
private double [] erosion_d;

private int hmapIndex(int x, int z)
{
return x + z * field.width;
}

private void depositAt(int x, int z, double w, double ds)
{
if(x >= 0 && z >= 0 && x < field.width && z < field.height)
{
float delta = (float)(ds * (w));
erosion_d[hmapIndex((x), (z))] += delta;
field.addHeight(x, z, delta);
}
}

private void deposit(int xi, int zi, double xf, double zf, double ds)
{
depositAt(xi, zi, (1 – xf) * (1 – zf), ds);
depositAt(xi + 1, zi, xf * (1 – zf), ds);
depositAt(xi, zi + 1, (1 – xf) * zf, ds);
depositAt(xi + 1, zi + 1, xf * zf, ds);
}

private void erode(int x, int z, double w, double ds)
{
if(x >= 0 && z >= 0 && x < field.width && z < field.height)
{
double delta = ds * (w);
field.addHeight(x, z, (float) -delta);

double r = erosion_r[hmapIndex((x), (z))];
double d = erosion_d[hmapIndex((x), (z))];
if (delta <= d)
{
d -= delta;
}
else
{
r += delta – d;
d = 0;
}

erosion_r[hmapIndex((x), (z))] = r;
erosion_d[hmapIndex((x), (z))] = d;
}
}

/**
* Height at location (x,z)
* @param x X
* @param z Z
* @return Height
*/
private double height(int x, int z)
{
// clamp coordinates
if(x < 0) x = 0;
if(z < 0) z = 0;
if(x >= field.width) x = field.width – 1;
if(z >= field.height) z = field.height – 1;

return field.getHeight(x, z);
}

public void dropletErosion(Heightfield field, int iterations, ErosionParameters params)
{
this.field = field;

double Kq = params.Kq;
double Kw = params.Kw;
double Kr = params.Kr;
double Kd = params.Kd;
double Ki = params.Ki;
double minSlope = params.minSlope;
double Kg = params.g;

erosion_r = new double [field.width * field.height];
erosion_d = new double [field.width * field.height];

int MAX_PATH_LEN = (field.width + field.height) * 4;

long t0 = System.currentTimeMillis();

long longPaths = 0, randomDirs = 0, sumLen = 0;

for(int iter = 0; iter<iterations; iter++)
{
if ((iter & 0x3FFF) == 0 && iter != 0)
{
System.err.println(„Calculating erosion:“ + (iter + 0.5) * 100 / iterations + „%“);
}

int xi = (int)(Math.random() * field.width);
int zi = (int)(Math.random() * field.height);

double xp = xi, zp = zi;
double xf = 0, zf = 0;

double h = height(xi, zi);
double s = 0, v = 0, w = 1;

double h00 = h;
double h10 = height(xi + 1, zi);
double h01 = height(xi, zi + 1);
double h11 = height(xi + 1, zi + 1);

double dx = 0, dz = 0;

int numMoves = 0;
for (; numMoves < MAX_PATH_LEN; ++numMoves)
{
// calc gradient
double gx = h00 + h01 – h10 – h11;
double gz = h00 + h10 – h01 – h11;
//== better interpolated gradient?

// calc next pos
dx = (dx – gx) * Ki + gx;
dz = (dz – gz) * Ki + gz;

double dl = Math.sqrt(dx * dx + dz * dz);
if (dl <= LEVEL_EPSILON)
{
// pick random dir
double a = Math.random() * Math.PI * 2;
dx = Math.cos(a);
dz = Math.sin(a);
++randomDirs;
}
else
{
dx /= dl;
dz /= dl;
}

double nxp = xp + dx;
double nzp = zp + dz;

// sample next height
int nxi = (int)(nxp);
int nzi = (int)(nzp);
double nxf = nxp – nxi;
double nzf = nzp – nzi;

double nh00 = height(nxi, nzi);
double nh10 = height(nxi + 1, nzi);
double nh01 = height(nxi, nzi + 1);
double nh11 = height(nxi + 1, nzi + 1);

double nh = (nh00 * (1 – nxf) + nh10 * nxf) * (1 – nzf) + (nh01 * (1 – nxf) + nh11 * nxf) * nzf;

// if higher than current, try to deposit sediment up to neighbour height
if (nh >= h)
{
double ds = (nh – h) + 0.001f;

if (ds >= s)
{
// deposit all sediment and stop
ds = s;
deposit(xi, zi, xf, zf, ds);
h += ds;
break;
}

deposit(xi, zi, xf, zf, ds);
h += ds;
s -= ds;
v = 0;
}

// compute transport capacity
double dh = h – nh;
double slope = dh;
//double slope=dh/sqrtf(dh*dh+1);

double q = Math.max(slope, minSlope) * v * w * Kq;

// deposit/erode (don’t erode more than dh)
double ds = s – q;
if (ds >= 0)
{
// deposit
ds *= Kd;
//ds=minval(ds, 1.0f);

deposit(xi, zi, xf, zf, ds);
dh += ds;
s -= ds;
}
else
{
// erode
ds *= -Kr;
ds = Math.min(ds, dh * 0.99f);

for(int z = zi – 1; z <= zi + 2; ++z)
{
double zo = z – zp;
double zo2 = zo * zo;

for (int x = xi – 1; x <= xi + 2; ++x)
{
double xo = x – xp;

double w1 = 1 – (xo * xo + zo2) * 0.25;
if (w1 <= 0)
{
continue;
}
w1 *= 0.1591549430918953;

erode(x, z, w1, ds);
}
}

dh -= ds;

s += ds;
}

// move to the neighbour
v += Math.sqrt(Kg * dh);

// Hajo: use friction
v -= v*v*0.05;

w *= 1 – Kw;

xp = nxp;
zp = nzp;
xi = nxi;
zi = nzi;
xf = nxf;
zf = nzf;

h = nh;
h00 = nh00;
h10 = nh10;
h01 = nh01;
h11 = nh11;

if(xi < 0 || zi < 0 || xi >= field.width || zi >= field.height)
{
// System.err.println(„Droplet #“ + iter + “ path left the field!“);
break;
}
}

if (numMoves >= MAX_PATH_LEN)
{
System.err.println(„Droplet #“ + iter + “ path is too long!“);
++longPaths;
}

sumLen += numMoves;
}

long t1 = System.currentTimeMillis();

System.err.print(„computed “ + iterations + “ erosion droplets in “ + (t1 – t0) + “ ms, %.0f droplets/s“);

System.err.println(“ “ + sumLen + “ average path length, “ + longPaths + “ long paths cut, “ + randomDirs + “ random directions picked“);
}

}

Advertisements

Projekt Marslandschaft: Sand


Nach dem Aufbau des groben Bodenprofils aus Styropor kam jetzt die Sandschicht dazu. Der Sand wird die Basis sein, auf dem Sand kommt dann die Marsstation und andere Dekoration.

Sandboden
Sandboden

Vorne rechts war eigentlich ein Hügel geplant, aber der passte dann doch nicht so recht ins Bild, und ich habe einen Krater mit Ringwall daraus gemacht.

Sandboden
Sandboden

Auf diese Weise hat die Station in der Mitte der Landschaft auch mehr Platz. Jetzt folgt der Aufbau der Station, und der anderen Deko-Elemente.

 

Projekt Marslandschaft, Fortschritte am Bodenprofil


Das Landschaftsprofil ist jetzt in großen Teilen fertig. Den Rand habe ich mit Acrylfarbe lackiert. Mit dem Farbton bin ich nicht ganz zufrieden, aber zumindest ist es rötlich wie der Mars auch. Das innere des Profils wird mit Sand bedeckt, und ist deshalb nicht lackiert.

Von links betrachtet:

Bodenrelief
Bodenrelief

Und von rechts betrachtet:

Bodenrelief
Bodenrelief

Ich freue mich schon darauf, mit dem Sand zu beginnen. Manchmal denke ich, ich bräuchte einfach eine große Sandkiste für meine Ideen 🙂

 

Projekt Marslandschaft, Bodenplatte


Seit der Ankündigung hat sich noch mal einiges an den Plänen geändert – vor allem der Aufstellungsort, und die Größe. Die Bodenplatte misst jetzt etwa 100cm x 55cm, und wird auch nicht Teil der Schrankwand sein, sondern einzeln stehen.

Marsboden
Marsboden

Das meiste des Landschaftsprofils ist inzwischen fertig.

Rechts, wo die Dose mit dem Klebstoff steht, soll ein höheres Bergmassiv enstehen, aber ich bin noch nicht ganz sicher wie ich das aufbauen werde, auch damit es nicht zu schwer wird. Mit der Gestaltung werde ich links beginnen, d.h. bis zur Entscheidung, wie das Bergmassiv konkret gebaut wird, kann ich mir noch etwas Zeit lassen.

In die Ebene in der Mitte soll eine Marstation kommen, einige Gebäude und ein Landefeld für Raketen, im Stil der 50er oder 60er Jahre Sci-Fi.