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“);
}

}

Nessi macht Urlaub in Pakistan


Nessie wurde ja schon lange nicht mehr im Loch Ness gesehen. Durch Zufall fand ich jetzt heraus, warum!

https://www.heise.de/scale/geometry/1050/q75/tp/imgs/89/2/2/0/5/2/6/3/552f63ed781d0588.jpeg

Quelle:
https://www.heise.de/tp/features/China-kauft-Pakistan-3718941.html

Laut dem Artikel zeigt das Bild einen nicht näher benannten Ort in Pakistan. Und dort ist ganz deutlich die bekannte Nessi-Silhouette zu erkennen!

Darum findet sich keine Spur mehr von Nessi im Loch Ness. Nessi ist die Touristik zu viel geworden und sie (oder er?) ist nach Pakistan in einen ruhigen Bergsee umgezogen.

 

Ladenhüter, Teil 2


Letzte Woche hatte ich Zeit, den alten Programmcode mit den neuen Ladengrafiken zum Funktionieren zu bringen. Die neuen Regalfächer sind etwas kleiner als die alten, so dass die Bilder der Waren skaliert werden müssen – das ist nicht gut für die Bildqualität, aber ich denke, insgesamt immer noch akzeptabel.

Laden, Waren und Kunden
Laden, Waren und Kunden

Kunden gibt es jetzt auch und ebenfalls einen ersten Entwurf für den Handelsdialog.

Handelsdialog
Handelsdialog

Der Screenshot ist nicht ganz aktuell, sondern noch von einer Version, in der die Kunden nur gebrauchte Waren verkaufen wollten. Inzwischen wollen sie auch kaufen.

Ich bin noch am überlegen, wie man den Einkauf interessanter machen könnte. Irgendwo schwirrt die Idee eines Beratungsgessprächs in meinen Kopf herum, aber ich weiss nicht wie ich das im Spiel umsetzen könnte. Etwas in der Art:

Kunde: Ich suche Schätze in der alten Schlangengrube, und brauche dazu eine Ausrüstung mit hohem Giftwiderstand.

Händler: Bist du Magier? Dürfen es Metallgegenstände sein, oder eher Leder und Holz?

Kunde: Ich bin kein Magier. Metall ist in Ordnung, so lange der Preis stimmt.

Händler: Gut, ich hätte da folgendes im Angebot … (Hier müsste dann ein Dialogfenster kommen, mit einer Auswahl an Gegenständen, die eine gewisse Überlebenschance in der Schlangengrube gewähren).

Oder vielleicht auch so:

Kunde: Ich habe vom König den Auftrag seine Tochter zu befreien, und das Verließ wird von einem Feuer speienden Drachen bewacht.

Händler: Dann brauchst Du etwas mit Widerstand gegen Feuer. Bist Du eher Nah- oder Fernkämpfer?

Kunde: Sicherlich, ich will ja nicht gegrillt werden. Und Nahkämpfer.

Händler: Ich hätte da folgendes (Hier wieder der Dialog mit dem Angebot, Helm, Schild, Schwert, Rüstung usw. und einer Schätzung wie gut das gegen das Drachenfeuer hilft.)

Nicht, dass es dann eine Woche später so kommt:

Händler: Tut mir leid, Mumien werden hier nicht bedient.

Kunde (bandagiert): Scherzkeks. Ich bin der, dem Du die angeblich zu 98% feursichere Ausrüstung gegen den Drachen verkauft hast. Erinnerst Du dich noch?

Händler: Oh weißt Du, hier kaufen jeden Tag so viele Leute …

Kunde: Red keinen Unfug! Du weisst das ganz genau. Ich werde das öffentlich machen, was Du mir da für einen Schrott verkauft hast, dann kauft hier keiner mehr!

Händler: Ähem … was hälts Du von ein paar kostenlosen Heiltinkturen und einem Rabatt von 50% auf den nächsten Kauf? Und das Mißgeschick mit dem Drachen bleibt unter uns?

Schade, dass ich keine Vorstellung habe, wie man solche Dialoge von einem Programm erzeugen lassen kann …

PS: Die Kundengrafiken und der Zeichensatz sind nicht von mir:

Grafiken

Human silhouettes by Rejon (CC0/Public Domain) (https://openclipart.org/user-detail/rejon):
https://openclipart.org/detail/21966/person-outline-1
https://openclipart.org/detail/21968/person-outline-2
https://openclipart.org/detail/21970/person-outline-3
https://openclipart.org/detail/21972/long-haired-woman-outline

Monk by CDmir (CC0/Public Domain) http://opengameart.org/users/cdmir:
http://opengameart.org/content/monk

Brigand by Anthony Myers (CC-By 3.0) http://opengameart.org/users/anthonymyers:
http://opengameart.org/content/brigand

Mudeater by CDmir (CC0/Public Domain) http://opengameart.org/users/cdmir:
http://opengameart.org/content/mudeater-animated

Font

https://fontlibrary.org/en/font/medievalsharp

Copyright (c) 2011, wmk69 (wmk69@o2.pl),
with Reserved Font Names ‚MedievalSharp‘, ‚Medieval Sharp‘, ‚Medieval Sharp Bold‘
‚Medieval Sharp Bold Oblique‘, ‚Medieval Sharp Oblique‘.

This Font Software is licensed under the SIL Open Font License, Version 1.1.

 

Meersalz mit Plastikmüll


Immer mal wieder hört man von Fischen, Schildkröten und anderen Meeresbewohnern, die an Plastikmüll verenden.

Daneben dann seit einer Zeit auch, dass der Plastikmüll in den Meeren langsam zerrieben wird, zu sehr kleinen Pastikteilchen, die dennoch nur sehr langsam abgebaut werden.

Und jetzt ist das feine Plastik wieder auf unserem Teller:

https://arstechnica.com/science/2017/05/sullied-seasoning-seas-salts-come-with-a-dash-of-microplastics/

Naja, nicht ganz, aber zumindest Reste den Plastikmülls wurden in den Untersuchten Meersalzproben gefunden:

Zitat: In a survey of 16 sea salts from eight countries, researchers found microplastic particles lurking in all but one. In total, the researchers collected 72 particles from the salts and used micro-Raman spectroscopy to identify their components, which were mainly plastic polymers and pigments.

Grob übersetzt: In einer Untersuchung von 16 Sorten Meersalz aus acht Ländern fanden die Forscher versteckte mikroskopische Plastikpartikel in allen Proben außer einer. Insgesamt fanden die Forscher 72 Plastikteilchen und konnten  durch Micro Raman Spektroskopie herausfinden, dass sie hautpsächlich aus Plastikpolymeren und Pigmenten bestanden.

Nett. Unser Dreck hat uns wieder. Er kommt jetzt auf den Teller, und das auch noch in der Verpackung als Gewürz 🙂

PS: Ganz neu ist das nicht, schon vorher gab es Funde von Plastik in Muscheln und Speisefisch. Es nur ein weiteres Zeichen, dass wir dringend mehr darauf achten müssen, unseren Dreck sorgfältiger wiederzuzverwerten, und den nicht verwertbaren Rest so zu Lagern, dass er keinen Schaden anrichten kann.

 

Ladenhüter statt Abenteurer


Üblicherweise funktioniert mein Gedächtnis gar nicht schlecht, aber ich kann mich beim besten Willen nicht daran erinnern, ob ich hier im Blog schon mal über diese Idee geschrieben hatte …

In den meisten (Computer-) Rollenspielen übernimmt der Spieler die Rolle eines Abenteurers. Oft gibt es in diesen Spielen Läden, in denen der Spieler Ausrüstung erwerben kann.

Schon seit einiger Zeit geistert die Idee in meinem Kopf herum, hier einmal die Rollen zu tauschen, und den Spieler einen Händler spielen zu lassen. Letzten Sommer habe ich dann auf opengameart.org eine Diskussion zu dem Thema gestartet.

https://opengameart.org/forumtopic/need-game-design-help-with-shopkeeper-type-game

Die Entwicklung des Projekts ging langsam aber sie lief relativ gut. Ein kleineres Projekt, das tatsächlich die Chance hat, als Ein-Mann-Projekt auch fertig zu werden, vor allem, da ich viele Grafiken aus meinem vorigen „Juwelensuche“ Projekt wiederverwenden kann. Leider kam dann wieder einmal eine psychische Krise und eine lange Phase langsamer Erholung, und das Projekt lag vorerst mal auf Eis.

Inzwischen fühle ich mich wieder fit genug, daran weiterzumachen. Zumindest fühlte ich mich inspiriert, an besseren Grafiken für den Laden zu arbeiten, und habe zwei Designs entworfen. Zum einen einen Laden in einem Blockhaus:

Laden im Blockhaus
Laden im Blockhaus

Und einen Laden in einem aus Ziegeln oder Steinen gemauerten Haus:

Laden in Ziegelgebäude
Laden in Ziegelgebäude

Da ich den Laden im Blockhaus als zweiten angegangen bin, hat er bessere Grafiken für die Ladentheke und noch ein Holzfass ald Deko. Die Idee dabei ist, dass der Spieler als Händler nach und nach in bessere Gegenden der Stadt umziuehen kann, d.h. auch in höherwertige Gebäude, die mehr Miete kosten. Eine bessere Ladeneinrichtung soll dabei mehr Kunden und zahlungskräftigere Kunden anlocken.

Im Moment bin ich daran, weitere Deko-Gegenstände wie den Besen und das Fass zu entwerfen, damit der Laden nicht ganz so leer wirkt. Ein Buch für die Aufträge/Lieferungen und eine Art Kasse stehen als nächsten auf meiner List.

Wenn dann tatsächlich mal Waren zum Verkauf stehen, dürften die Läden dann auch interessanter aussehen. Hier noch ein Test mit der alten Ladengrafik und einem magischen Waldhorn für Barden.

Waren im Regal
Waren im Regal

Mit dem Programmieren läuft es noch nicht so gut wie mit den Grafiken, aber ich denke, da komme ich auch bald wieder hin.

Mathematische Brösel


PovRay bietet mit seinem „Isosurface“ Objekt eine interessante Möglichkeit zur Gestaltung von vielerlei Arten von Steinen.

Prozedural erzeugte Asteroiden
Prozedural erzeugte Asteroiden

Technisch gesehen ist eine Isosurface die Fläche im Raum an der eine gegebene Funktion Null ist. Für eine Kugel also x² + y² + z² – r² = 0.  Ausgehend von einer elliptischen Form kann man durch hinzufügen geeigneter Zufallsfunktionen schön unregelmässige aber reproduzierbare Formen gestalten, die z.B. Asteroiden oder auch irdischen Steinen verblüffend ähnlich sehen. Passenderweise bietet PovRay auch gleich einen großen Fundus an geeigneten zufalls-basierten Funktionen, um die Oberfläche der Steine passend zu formen.

Die hier gezeigten Exemplare in je 12 rotieren Ansichten, und etliche weitere, kleinere, habe ich auf opengameart.org als Creative Commons veröffentlicht:

 

Abstrakte Stadtlandschaften


Während der Arbeit an der prozeduralen Synthese von Pflanzen kamen mir auch Ideen für andere Strukturen, die sich prozedural erzeugen lassen müssten. Korallen waren naheliegend, da sie eine Zweigstruktur ähnlich wie Büsche oder Bäume aufweisen, aber auch moderne Städte haben oft einen sehr regelmässigen Aufbau. Hier zwei Ergebnisse meiner Experimente.

Abstrakte Stadtlandschaft
Abstrakte Stadtlandschaft

Ich habe darauf verzichtet, die Gebäude im Detail zu gestalten, weil ich mich auf den grundlegenden Aufbau der Stadt konzentrieren wollte. Die erzeugten Städte sind zwar reichlich klischeehaft, aber deutlich als Stadtlandschaft zu erkennen, d.h. die wesentlichen Strukturen sind wohl enthalten.

Abstrakte Stadtlandschaft
Abstrakte Stadtlandschaft

Ich habe den Computer insgesamt 24 Städte erzeugen lassen, und die Grafiken auf opengameart.org als Creative Commons zur Verfügung gestellt:

http://opengameart.org/content/24-abstract-isometric-cityscapes

Ich könnte mir vorstellen, dass die Grafiken trotz der geringen Detailtiefe für Projekte, die Städte auf einer Landkarte andeuten wollen, nützlich sind.

Neues, Altes und Gedanken darüber