Hallo,
Edit: Ich habe endlich lesbaren Beispielcode gefunden. Vielleicht löst das mein Problem. Siehe
Terasology | Moving Blocks!
diesmal habe ich ein eher anspruchsvolles Problem, das mich schon länger ärgert. Ich hoffe, dass sich trotzdem jemand findet, der mir helfen kann. Als Belohnung für eine Lösung lobe ich meine ewige Dankbarkeit aus
Ich hatte vor einigen Monaten schonmal versucht per Marching Cubes Terrain prozedural zu generieren und bin kläglich gescheitert... Nicht an Marching Cubes, sondern an einer schönen Dichtefunktion - also der Funktion, die angibt, ob an einer Stelle etwas ist oder eben nicht. (Ein Screen siehe unten.. Gab auch andere, wos einfach nur alles Quaderförmig war, eher so wie im zweiten Screen)
Nun will ich das ganze in einer Minecraft-ähnlicheren Variante angehen und das ganze aus Blöcken aufbauen. Ich weiß, dass Notch für einige Punkte im Raum Perlin-3D-Noise nutzt und zwischen den Werten linear interpoliert. Ich hab auch viele Artikel gesehen, wo andere das ebenso machen:
Sehr zu empfehlender Artikel leider Beispiele in LUA-Skript
Beispiel in JavaScript und der Unity Engine (tut bei mir nicht!)
Meine bisherigen Versuche sehen so aus, wie die beiden letzteren angehängen Screenshots.
Mitlerweile glaube ich, dass ich irgendwo einfach ein Missverständnis drin hab, weils einfach nicht funktionieren will, egal wie viel ich rumprobiere.
Ich versuche das ganze per Fractal Brownian Motion zu lösen (also dem Übereinanderlegen mehrer Perlin-Noise Funktionen mit verschiedenen Frequenzen und Amplituden, so wie es auch bei Marching Cubes genutzt werden sollte (GPU GEMS 3 - Terrain Generation)).
Ich hab keine Ahnung was ich noch versuchen soll^^
Hier mal ein aktuelles (eher hässliches ) Codebeispiel, vor allem die letzten Funktionen vor dem Auskommentierten zum Schluss sind interessant und bilden Code aus den ganz oben genannten Artikeln ab (über bilinear() und lerp()), allerdings eher frustriert hingeschmiert.. Die erste Methode unter dem großen "CREATION" ist eigentlich das, was ich zu funktionieren erwartet hätte und was auch in den GPU GEMS beschrieben wird (Ergebnis ist Screen 2)...
Ein KSKB würde ich auf Anfrage auch wohl bauen, würde nur eben JME3 vorraussetzen.
Hilfsklasse Noise, die im ersten Versuch Verwendung findet (siehe zweiter Screenshot)
Edit: Ich habe endlich lesbaren Beispielcode gefunden. Vielleicht löst das mein Problem. Siehe
Terasology | Moving Blocks!
diesmal habe ich ein eher anspruchsvolles Problem, das mich schon länger ärgert. Ich hoffe, dass sich trotzdem jemand findet, der mir helfen kann. Als Belohnung für eine Lösung lobe ich meine ewige Dankbarkeit aus
Ich hatte vor einigen Monaten schonmal versucht per Marching Cubes Terrain prozedural zu generieren und bin kläglich gescheitert... Nicht an Marching Cubes, sondern an einer schönen Dichtefunktion - also der Funktion, die angibt, ob an einer Stelle etwas ist oder eben nicht. (Ein Screen siehe unten.. Gab auch andere, wos einfach nur alles Quaderförmig war, eher so wie im zweiten Screen)
Nun will ich das ganze in einer Minecraft-ähnlicheren Variante angehen und das ganze aus Blöcken aufbauen. Ich weiß, dass Notch für einige Punkte im Raum Perlin-3D-Noise nutzt und zwischen den Werten linear interpoliert. Ich hab auch viele Artikel gesehen, wo andere das ebenso machen:
Sehr zu empfehlender Artikel leider Beispiele in LUA-Skript
Beispiel in JavaScript und der Unity Engine (tut bei mir nicht!)
Meine bisherigen Versuche sehen so aus, wie die beiden letzteren angehängen Screenshots.
Mitlerweile glaube ich, dass ich irgendwo einfach ein Missverständnis drin hab, weils einfach nicht funktionieren will, egal wie viel ich rumprobiere.
Ich versuche das ganze per Fractal Brownian Motion zu lösen (also dem Übereinanderlegen mehrer Perlin-Noise Funktionen mit verschiedenen Frequenzen und Amplituden, so wie es auch bei Marching Cubes genutzt werden sollte (GPU GEMS 3 - Terrain Generation)).
Ich hab keine Ahnung was ich noch versuchen soll^^
Hier mal ein aktuelles (eher hässliches ) Codebeispiel, vor allem die letzten Funktionen vor dem Auskommentierten zum Schluss sind interessant und bilden Code aus den ganz oben genannten Artikeln ab (über bilinear() und lerp()), allerdings eher frustriert hingeschmiert.. Die erste Methode unter dem großen "CREATION" ist eigentlich das, was ich zu funktionieren erwartet hätte und was auch in den GPU GEMS beschrieben wird (Ergebnis ist Screen 2)...
Ein KSKB würde ich auf Anfrage auch wohl bauen, würde nur eben JME3 vorraussetzen.
Java:
package logic.world.generation;
import extern.SimplexNoise;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import java.util.Random;
import logic.GOD;
import logic.world.BlockTerrain;
import logic.world.Offset;
import models.MeshCreator;
import com.jme3.scene.Geometry;
/**
* May only be called by the BlockTerrain Loading Thread!
*
*/
public class WorldManager {
private final BlockTerrain bt;
private final int chunkSize = GOD.getChunkSize();
private static int[] boxes = new int[16 * 16 + 2];
private Noise[] noise = new Noise[3];
private final double SCALE_HORIZONTALLY = 8;
private final double SCALE_VERTICALLY = 4;
private final int octaves = 1;
private final double persistence = 0.5;
private final int terrainHeight = 32;
private final int terrainSize = 16;
private final int detailSize = 8;
private double yTurbulance = 1;
static {
Random rnd = new Random(0);
for(int i = 0; i < 16; i++) {
for(int n = 0; n < 16; n++) {
int v = (int) (-0.2 * ((n - 10) * (n - 10)) + 5);
v = v < 0 ? 0 : v;
boxes[i * 16 + n] = (i << 12) | (n << 8) | v;
//boxes[i * 20 + n] = rnd.nextInt(10);
}
}
boxes[256] = (5 | (2 << 8));
}
//************
//CONSTRUCTOR
//************
public WorldManager(final BlockTerrain bt) {
this.bt = bt;
noise[0] = new Noise(0, 0, 0);
noise[1] = new Noise(10, 10, 10);
noise[2] = new Noise(155, 255, 300);
} //End Contructor
//*******
//LOADING
//*******
public Geometry getChunk(final Offset o) {
int[] boxes = this.createNewChunk(o.getX(), o.getZ());
return MeshCreator.createFrom(boxes);
} //End getChunk();
//********
//CREATION
//********
//Erste Variante mithilfe von 3 3D-Noise-Maps. Ursache für den zweiten Screenshot
/*private int[] createNewChunk(int xOff, int zOff) {
TIntList list = new TIntArrayList(300);
for(int x = 0; x < chunkSize; x++) {
for(int z = 0; z < chunkSize; z++) {
for(int y = 63; y >= 0; y--) {
if(y == 0) { //Auf jeden Fall Boden einfügen
list.add((x << 12) | (z << 8) | y);
} else {
double d = -y;
d -= noise[0].get((x + xOff * chunkSize) / 20, y / 20, (z + zOff * chunkSize) / 20) * 15;
d -= noise[1].get((x + xOff * chunkSize) / 10, y / 10, (z + zOff * chunkSize) / 10) * 5;
d -= noise[2].get(x + xOff * chunkSize, y, z + zOff * chunkSize);
if(d + 10 > 0) { //Nur wenn ein gewisser dichtewert erfüllt ist soll ein Block creiert werden
list.add((x << 12) | (z << 8) | y);
}
}
}
}
}
return list.toArray();
} //End createNewChunk();*/
//Zweiter Versuch, angelehnt an den Artikel von Gamedev.net
private int[] createNewChunk(int xOff, int zOff) {
TIntList list = new TIntArrayList(300);
for(int x = 0; x < chunkSize; x++) {
for(int z = 0; z < chunkSize; z++) {
boolean ground = false;
for(int y = 0; y <= 20; y++) {
//Betrachte nur jeden achten Block und interpoliere die restlichen Werte "linear" um wegbareres Gelände zu erhalten. Funktioniert auch anders nicht.
double d = threshold(turbulance(groundGradient(y, 0, 20), shape(x + (xOff * chunkSize) / SCALE_HORIZONTALLY, y, z + (zOff * chunkSize) / SCALE_HORIZONTALLY)));
//double d = 1 - 2 * (y / terrainHeight) + perlin((x + xOff * terrainSize) / detailSize, y / detailSize, (z + zOff * terrainSize) / detailSize);
if(d == -1) {
list.add((x << 12) | (z << 8) | y);
ground = true;
}
}
}
}
return list.toArray();
}
//Gibt -1 aus, wenn ein Block gesetzt werden soll, sonst +1
private double threshold(double d) {
return d > 0 ? +1 : -1;
}
//Berechnet einen Wert zwischen -1 und 1, der der Position des y-wertes auf der linie zwischen start und end entspricht (siehe GameDev Artikel)
private double groundGradient(double y, double start, double end) {
return (y - start) / ((end - start) / 2) - 1;
}
//Ab hier vermute ich Unzulänglichkeiten meines Codes
private double turbulance(double d, double yVar) {
return d + yVar * yTurbulance;
}
private double shape(double x, double y, double z) {
double total = 0;
for(int i = 0; i < octaves; i++) {
double freq = Math.pow(1.75, i);
double amp = Math.pow((1 + persistence), i);
total += interpolateNoise(x, y, z) * amp;
}
return total;
}
//Hier ein Versuch nur jeden achten Block tatsächlich zu betrachten und den Rest zu interpolieren.
//Ein Block, der interpoliert werden soll
//Alles verzweifelte, eher hingeschmierte Versuche
private double interpolateNoise(double x, double y, double z) {
if(x == (int) x && y == (int) y && z == (int) z) {
return SimplexNoise.noise(x, y, z);
}
double[] cube = new double[8];
cube[0] = SimplexNoise.noise((int) x, (int) y, (int) z);
cube[1] = SimplexNoise.noise((int) x + 1, (int) y, (int) z);
cube[2] = SimplexNoise.noise((int) x + 1, (int) y + 1, (int) z);
cube[3] = SimplexNoise.noise((int) x, (int) y + 1, (int) z);
cube[4] = SimplexNoise.noise((int) x, (int) y, (int) z + 1);
cube[5] = SimplexNoise.noise((int) x + 1, (int) y, (int) z + 1);
cube[6] = SimplexNoise.noise((int) x + 1, (int) y + 1, (int) z + 1);
cube[7] = SimplexNoise.noise((int) x, (int) y + 1, (int) z + 1);
double[] corners = new double[4];
corners[0] = lerp(cube[0], cube[3], y - (int) y);
corners[1] = lerp(cube[1], cube[2], y - (int) y);
corners[2] = lerp(cube[4], cube[7], y - (int) y);
corners[3] = lerp(cube[5], cube[6], y - (int) y);
double v1 = lerp(corners[0], corners[1], x - ((int) x));
double v2 = lerp(corners[3], corners[2], x - ((int) x));
return lerp(v1, v2, z - ((int) z));
}
private double bilinear(double w1, double w2, double... a) {
double v1 = lerp(a[0], a[1], w1);
double v2 = lerp(a[3], a[2], w1);
return lerp(v1, v2, w2);
}
private double lerp(double a, double b, double w) {
return a + (b - a) * w;
}
//1:1 abgeschriebene JavaScript Variante
/*private double perlin(int x, int y, int z) {
double total = 0;
for(int i = 0; i < octaves; i++) {
double frq = Math.pow(2, i);
double amp = Math.pow(persistence, i);
total += interpolatedNoise(x * frq, y * frq, z * frq) * amp;
}
return total;
}
private double interpolatedNoise(double x, double y, double z) {
double ix = Math.floor(x);
double fx = x - ix;
double iy = Math.floor(y);
double fy = y - iy;
double iz = Math.floor(z);
double fz = z - iz;
double v1 = sNoise(ix, iy, iz); // ---
double v2 = sNoise(ix + 1, iy, iz); // +--
double v3 = sNoise(ix, iy + 1, iz);// -+-
double v4 = sNoise(ix + 1, iy + 1, iz);// ++-
double v5 = sNoise(ix, iy, iz + 1);// --+
double v6 = sNoise(ix + 1, iy, iz + 1);// +-+
double v7 = sNoise(ix, iy + 1, iz + 1);// -++
double v8 = sNoise(ix + 1, iy + 1, iz + 1);// +++
double i1 = curve(v1, v2, fx);
double i2 = curve(v3, v4, fx);
double i3 = curve(v5, v6, fx);
double i4 = curve(v7, v8, fx);
double i5 = curve(i1, i2, fy);
double i6 = curve(i3, i4, fy);
return curve(i5, i6, fz);
}
private double sNoise(double x, double y, double z) {
double sides = (noise(x - 1, y, z) + noise(x + 1, y, z) + noise(x, y - 1, z) + noise(x, y + 1, z) + noise(x, y, z - 1) + noise(x, y, z + 1)) / 12.0;
double center = noise(x, y, z) / 2.0;
return sides + center;
}
private double noise(double x, double y, double z) {
Random rnd = new Random((int) (x * 657 + y * 111 + z * 11));
return 2 * (rnd.nextDouble() - 0.5);
}
private double curve(double a, double b, double t) {
return a * (1 - t * t * (3.0 - 2.0 * t)) + b * t * t * (3.0 - 2.0 * t);
}*/
} //Ende der 1:1 abgeschriebenen JavaScript Variante
Hilfsklasse Noise, die im ersten Versuch Verwendung findet (siehe zweiter Screenshot)
Java:
import extern.SimplexNoise;
public class Noise {
private final int LENGTH = 20;
double[][][] values = new double[LENGTH][LENGTH][LENGTH];
//************
//CONSTRUCTOR
//************
public Noise(int xOff, int yOff, int zOff) {
for(int x = 0; x < values.length; x++) {
for(int y = 0; y < values.length; y++) {
for(int z = 0; z < values.length; z++) {
values[x][y][z] = SimplexNoise.noise(x + xOff, y + yOff, z + zOff);
}
}
}
} //End Constructor
public double get(int x, int y, int z) {
return values[Math.abs(x % LENGTH)][Math.abs(y % LENGTH)][Math.abs(z % LENGTH)];
}
} //End Noise
Zuletzt bearbeitet: