Como posso gerar massas terrestres flutuantes para um mecanismo semelhante ao Minecraft?

19

Estou criando um mecanismo semelhante ao Minecraft no XNA. O que eu quero fazer é criar ilhas flutuantes semelhantes à mostrada neste vídeo:

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

Como eu replicaria isso usando um gerador mundial? Eu precisaria usar algum algoritmo de ruído Perlin? Não sei como isso me ajudaria a formar massas de terra assim.

Aqui está o código para o gerador de ruído perlin que estou 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; } }

Mas o problema é que o autor do código usa o seguinte para gerar ruído, e eu não o entendo nem um pouco.

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

E aqui está o site que estou usando: http://lotsacode.wordpress.com/2010/02/24/perlin-noise-in-c/ .

E estou tentando implementar o ruído perlin da maneira especificada por Martin Sojka.

Ok, então é isso que eu tenho até agora:

insira a descrição da imagem aqui

Darestium
fonte

Respostas:

21

Para o terreno base, faça dois campos de ruído contínuos em 2D (Perlin, Simplex, Wavelet, uma combinação deles - o que for melhor para você), um com a maioria de baixa frequência. partes de baixa amplitude para o limite superior da terra, a outra com alta frequência, partes de alta amplitude e baixa frequência, alta amplitude para o limite inferior da terra. Onde o limite inferior estiver acima do limite superior, não inclua voxels terrestres (ou o que o seu jogo usará para representar o terreno). O resultado final é mais ou menos assim ...

Martin Sojka
fonte
Mas isso é para 2D, não é?
Darestium
Mas eu gosto bastante :)
Darestium
4
2D / 3D - a mesma coisa
Gavin Williams
OK, mal tentar implementá-lo amanhã ... me desejem sorte;)
Darestium
@ Darestium: É um exemplo 2D para facilitar a visualização. O mesmo método funciona para qualquer número de dimensões (algébricas) maiores que uma.
Martin Sojka
15

Algo assim seria suficiente?

insira a descrição da imagem aqui

Nesse caso, consulte este artigo . Citando as partes mais relevantes:

Para obter um ruído mais interessante, várias oitavas de ruído simplex podem ser adicionadas. [...] Como eu quero obter uma espécie de rocha flutuante aproximadamente esférica, preciso multiplicar o ruído pela distância do centro. [...] eu também quero que a rocha seja mais plana no topo do que no fundo, portanto, um segundo fator de multiplicação é um gradiente na direção y. Combinando-os e estendendo y para o ruído enquanto comprime x e za, obtemos algo como uma rocha flutuante. [...] Escavar cavernas com outro exemplo de ruído compensado também o torna mais interessante.

  • Então, basicamente, você começará com um conjunto de dados gerado a partir de ruído simplex ou perlin (ou melhor, várias oitavas de ruído adicionadas ).
  • Em seguida, modele-o em algo mais próximo a uma massa de terra flutuante, tornando-a mais esférica (multiplicando o ruído pela distância do centro ).
  • E crie um terreno, tornando-o mais plano perto do topo (multiplicando-o por um gradiente vertical, ou seja, começando com valores baixos no topo e aumentando na direção do fundo).
  • Combine esses três e ajuste a forma dimensionando o ruído ao longo dos eixos X / Y / Z (o artigo sugere o alongamento no eixo Y e a compressão nos eixos X e Z ).
  • Uma passagem adicional de ruído pode ser usada para escavar cavernas .
David Gouveia
fonte
Sim, acho que algo assim é desafiadoramente o que eu quero. O fato é que tenho pouca experiência com ruído perlin, de modo que a única coisa que posso gerar são montanhas realmente básicas e não teria nenhuma idéia sobre como adicionar "várias oitavas de ruído). Para a geração de ruído perlin, estou usando o código que saí de stackoverflow.com/questions/4753055/… e a portado para C #. Vou adicionar minha versão no post original ... Você gostaria de me dar um exemplo de como eu alcançaria uma massa de terra com esse código?
Darestium
2
Foi por isso que vinculei o artigo. Ele tem uma explicação de todas as etapas e o código fonte no final. Você deveria tentar estudar isso.
David Gouveia
4
  1. Usando sua grade 3D existente, decida a altura em que você deseja que os topos das ilhas estejam. Crie um conjunto de ilhas nesse plano 2D (vamos chamá-lo de plano XY) espalhando pontos pelo plano e colocando cubos nesses pontos. Use a coesão para aproximá-los em grupos. Preencha todos os buracos e você terá um conjunto de topos de ilha.
  2. Use uma autoridade de certificação-similar método para crescer as ilhas para baixo. (a) Começando no nível Z em que você plotou seus pontos iniciais, para cada célula nesse nível Z atual, determine a chance de se estender até o próximo nível inferior, considerando o número de vizinhos no plano XY, de 0 a 8 ( vizinhos diagonais estão incluídos), por exemplo, atribua 10% de chance a cada vizinho, até no máximo 80% de chance. Calcule isso para cada célula no plano inicial. (b) Em seguida, escolha aleatoriamente essa chance e estenda para baixo se você estiver dentro do intervalo percentual. Enxágüe, repita a etapa 2 (vá para o próximo nível, determine vizinhos para cada voxel, estenda para baixo para esse voxel) até que não ocorram mais extensões. Sua extensão descendente deve formar um cone devido à abordagem do número de vizinhos, porque esses voxels em direção ao centro XY da ilha geralmente terão mais vizinhos.

Pseudocódigo para a etapa 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

Depois que suas ilhas terminarem de gerar, você poderá alterná-las para cima e para baixo no espaço para tê-las em diferentes alturas.

Engenheiro
fonte
OK, tive uma rachadura no seu método e parece estar aumentando o terreno para fora em vez de para dentro. Vou postar o código ...
Darestium