¿Cómo puedo generar masas de tierra flotantes para un motor similar a Minecraft?

19

Estoy creando un motor similar a Minecraft en XNA. Lo que quiero hacer es crear islas flotantes similares a la que se muestra en este video:

http://www.youtube.com/watch?v=gqHVOEPQK5g&feature=related

¿Cómo replicaría esto usando un generador mundial? ¿Tendría que usar algún algoritmo de ruido Perlin? No sé cómo eso me ayudaría a hacer masas de tierra así.

Aquí está el código para el generador de ruido perlin que estoy usando:

    private double[,] noiseValues;
    private float amplitude = 1;    // Max amplitude of the function
    private int frequency = 1;      // Frequency of the function

    /// <summary>
    /// Constructor
    /// </summary>
    /// 
    public PerlinNoise(int freq, float _amp)
    {
        Random rand = new Random(System.Environment.TickCount);
        noiseValues = new double[freq, freq];
        amplitude = _amp;
        frequency = freq;

        // Generate our noise values
        for (int i = 0; i < freq; i++)
        {
            for (int k = 0; k < freq; k++)
            {
                noiseValues[i, k] = rand.NextDouble();
            }
        }
    }

    /// <summary>
    /// Get the interpolated point from the noise graph using cosine interpolation
    /// </summary>
    /// <returns></returns>
    public double getInterpolatedPoint(int _xa, int _xb, int _ya, int _yb, double x, double y)
    {
        double i1 = interpolate(
            noiseValues[_xa % Frequency, _ya % frequency],
            noiseValues[_xb % Frequency, _ya % frequency]
            , x);

        double i2 = interpolate(
            noiseValues[_xa % Frequency, _yb % frequency],
            noiseValues[_xb % Frequency, _yb % frequency]
            , x);

        return interpolate(i1, i2, y);
    }

    public static double[,] SumNoiseFunctions(int width, int height, List<PerlinNoise> noiseFunctions)
    {
        double[,] summedValues = new double[width, height];

        // Sum each of the noise functions
        for (int i = 0; i < noiseFunctions.Count; i++)
        {
            double x_step = (float)width / (float)noiseFunctions[i].Frequency;
            double y_step = (float)height / (float)noiseFunctions[i].Frequency;

            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    int a = (int)(x / x_step);
                    int b = a + 1;
                    int c = (int)(y / y_step);
                    int d = c + 1;

                    double intpl_val = noiseFunctions[i].getInterpolatedPoint(a, b, c, d, (x / x_step) - a, (y / y_step) - c);
                    summedValues[x, y] += intpl_val * noiseFunctions[i].Amplitude;
                }
            }
        }
        return summedValues;
    }

    /// <summary>
    /// Get the interpolated point from the noise graph using cosine interpolation
    /// </summary>
    /// <returns></returns>
    private double interpolate(double a, double b, double x)
    {
        double ft = x * Math.PI;
        double f = (1 - Math.Cos(ft)) * .5;

        // Returns a Y value between 0 and 1
        return a * (1 - f) + b * f;
    }

    public float Amplitude { get { return amplitude; } }
    public int Frequency { get { return frequency; } }

Pero la cuestión es que el autor del código usa lo siguiente para generar ruido, y no lo entiendo en lo más mínimo.

    private Block[, ,] GenerateLandmass()
    {
        Block[, ,] blocks = new Block[300, 400, 300];

        List<PerlinNoise> perlins = new List<PerlinNoise>();
        perlins.Add(new PerlinNoise(36, 29));
        perlins.Add(new PerlinNoise(4, 33));

        double[,] noisemap = PerlinNoise.SumNoiseFunctions(300, 300, perlins); 

        int centrey = 400 / 2;

        for (short x = 0; x < blocks.GetLength(0); x++)
        {
            for (short y = 0; y < blocks.GetLength(1); y++)
            {
                for (short z = 0; z < blocks.GetLength(2); z++)
                {
                    blocks[x, y, z] = new Block(BlockType.none);
                }
            }
        }

        for (short x = 0; x < blocks.GetLength(0); x++)
        {
            for (short z = 0; z < blocks.GetLength(2); z++)
            {
                blocks[x, centrey - (int)noisemap[x, z], z].BlockType = BlockType.stone; 
            }
        }

        //blocks = GrowLandmass(blocks);

        return blocks;
    }

Y aquí está el sitio que estoy usando: http://lotsacode.wordpress.com/2010/02/24/perlin-noise-in-c/ .

Y estoy tratando de implementar el ruido perlin de la manera especificada por Martin Sojka.

Ok, esto es lo que tengo hasta ahora:

ingrese la descripción de la imagen aquí

Darestium
fuente

Respuestas:

21

Para la tierra base, haga dos campos de ruido continuo en 2D (Perlin, Simplex, Wavelet, una combinación de los mismos, lo que sea que funcione para usted), uno con frecuencia mayormente baja. partes de baja amplitud para el límite superior de la tierra, el otro con partes de alta frecuencia, alta amplitud y baja frecuencia, alta amplitud para el límite inferior de la tierra. Cuando el límite inferior esté por encima del límite superior, no incluya vóxeles terrestres (o lo que sea que use su juego para representar el terreno). El resultado final se ve más o menos así ...

Martin Sojka
fuente
Pero esto es para 2D, ¿no?
Darestium
Pero me gusta bastante :)
Darestium
44
2D / 3D - lo mismo
Gavin Williams
OK, intentaré implementarlo mañana ...
deséame
@Darestium: es un ejemplo en 2D para una visualización más fácil. El mismo método funciona para cualquier cantidad de dimensiones (algebraicas) superiores a una.
Martin Sojka
15

¿Sería suficiente algo como esto?

ingrese la descripción de la imagen aquí

Si es así, consulte este artículo . Citando las partes más relevantes:

Para obtener un ruido más interesante, se pueden agregar varias octavas de ruido simplex. [...] Como quiero obtener una especie de roca flotante más o menos esférica, necesito multiplicar el ruido con su distancia desde el centro. [...] También quiero que la roca sea más plana en la parte superior que en la inferior, por lo tanto, un segundo factor de multiplicación es un gradiente en la dirección y. Combinando estos elementos y estirando y para obtener ruido mientras se comprime xy za, obtenemos algo así como una roca flotante. [...] Excavando cuevas con otra instancia de compensación de ruido también lo hace más interesante.

  • Básicamente, comenzará con un conjunto de datos generado a partir de ruido simplex o perlin (o más bien múltiples octavas de ruido sumadas ).
  • Luego, conviértalo en algo más cercano a una masa de tierra flotante haciéndolo más esférico (multiplicando el ruido por su distancia desde el centro ).
  • Y cree terreno haciéndolo más plano cerca de la parte superior (multiplicándolo por un gradiente vertical, es decir, comenzando con valores bajos en la parte superior y aumentando más hacia la parte inferior).
  • Combine estos tres y ajuste la forma escalando el ruido a lo largo de los ejes X / Y / Z (el artículo sugiere estirar en el eje Y y comprimir en los ejes X y Z ).
  • Se puede usar un paso adicional de ruido para excavar cuevas .
David Gouveia
fuente
Sí, creo que algo como esto es definitivamente lo que quiero. El caso es que tengo poca experiencia con el ruido perlin, por lo que lo único que puedo generar son montañas realmente básicas y no tendría ninguna idea sobre cómo agregar "octavas múltiples de ruido juntas". Para la generación de ruido perlin estoy usando el código que me bajé de stackoverflow.com/questions/4753055/... y lo porté a C #. Agregaré mi versión en la publicación original ... ¿Estaría dispuesto a darme un ejemplo de cómo lograría tal masa de tierra con eso? código?
Darestium
2
Por eso vinculé el artículo. Tiene una explicación de todos los pasos y el código fuente al final. Deberías intentar estudiar eso.
David Gouveia
4
  1. Usando su cuadrícula 3D existente, decida la altura a la que desea que estén las partes superiores de las islas. Cree un conjunto de islas en ese plano 2D (llamémoslo el plano XY) dispersando puntos a través del plano, luego colocando cubos en esos puntos. Usa la cohesión para acercarlos en grupos. Rellene los agujeros y tendrá un conjunto de tapas de isla.
  2. Use una CA-metodo similar para hacer crecer las islas hacia abajo. (a) Comenzando en el nivel Z donde trazó sus puntos iniciales, para cada celda en ese nivel Z actual, determine la posibilidad de extenderse hasta el siguiente nivel inferior dada la cantidad de vecinos en el plano XY, de 0 a 8 ( se incluyen vecinos diagonales), por ejemplo, asigne una probabilidad del 10% para cada vecino, hasta un máximo del 80% de posibilidades. Calcule esto para cada celda en el plano inicial. (b) Luego aleatorice contra esta posibilidad y extienda hacia abajo si está dentro del rango de porcentaje. Enjuague, repita el paso 2 (vaya al siguiente nivel, determine vecinos para cada vóxel, extienda hacia abajo para ese vóxel) hasta que no se produzcan más extensiones. Su extensión hacia abajo debe formar un cono debido al enfoque de número de vecinos, porque esos vóxeles hacia el centro XY de la isla generalmente tendrán más vecinos.

Pseudocódigo para el paso 2:

int planeNeighbours[x][y]; //stores how many neighbours each voxel in this plane has

for each z level (starting at level where you plotted your points)
    for each x, y voxel in z level
        for each neighbour space bordering this voxel
            if neighbour exists
                ++planeNeighbours[x][y];
    for each x, y voxel in z level
        chance = random(0,8); //depends on your RNG implementation
        if chance < planeNeighbours[x][y]
            worldGrid[x][y][z+1] = new cube

Una vez que sus islas hayan terminado de generar, opcionalmente puede moverlas hacia arriba y hacia abajo en el espacio para tenerlas a diferentes alturas.

Ingeniero
fuente
OK, tuve una grieta en su método y parece estar creciendo el terreno hacia afuera en lugar de hacia adentro. Publicaré el código ...
Darestium