Jak wygenerować pływające masy lądowe dla silnika podobnego do Minecrafta?

19

Tworzę silnik podobny do Minecrafta w XNA. Chcę stworzyć pływające wyspy podobne do pokazanych na tym filmie:

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

Jak mam to powtórzyć za pomocą generatora świata? Czy musiałbym użyć algorytmu szumu Perlina? Nie wiem, jak to pomogłoby mi stworzyć takie masy lądowe.

Oto kod generatora hałasu Perlin, którego używam:

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

Ale rzecz w tym, że autor kodu wykorzystuje następujące czynniki do generowania szumu, a ja nie rozumiem tego w najmniejszym stopniu.

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

I oto strona, której używam: http://lotsacode.wordpress.com/2010/02/24/perlin-noise-in-c/ .

I staram się realizować hałas perlin w sposób określony przez Martina Sojkę.

Ok, więc oto co mam do tej pory:

wprowadź opis zdjęcia tutaj

Darestium
źródło

Odpowiedzi:

21

Na ziemi bazowej wykonaj dwa ciągłe pola szumów 2D (Perlin, Simplex, Wavelet, ich kombinacja - cokolwiek Ci odpowiada), jedno o przeważnie niskiej częstotliwości. części o niskiej amplitudzie dla górnej granicy lądu, druga z częściami zarówno o wysokiej częstotliwości, o wysokiej amplitudzie, jak i o niskiej częstotliwości, o wysokiej amplitudzie dla dolnej granicy lądu. Tam, gdzie dolny limit jest powyżej górnego limitu, nie uwzględniaj wokseli lądowych (lub czegokolwiek, co twoja gra będzie używana do reprezentowania terenu). Wynik końcowy wygląda mniej więcej tak ...

Martin Sojka
źródło
Ale to nie jest dla 2D, prawda?
Darestium,
Ale bardzo mi się podoba :)
Darestium
4
2D / 3D - to samo
Gavin Williams
OK, zła próba zaimplementowania go jutro ... Życz mi powodzenia;)
Darestium
@Darestium: To przykład 2D dla łatwiejszej wizualizacji. Ta sama metoda działa dla dowolnej liczby (algebraicznych) wymiarów wyższych niż jeden.
Martin Sojka,
15

Czy coś takiego wystarczyłoby?

wprowadź opis zdjęcia tutaj

Jeśli tak, sprawdź ten artykuł . Cytując najważniejsze części:

Aby uzyskać bardziej interesujący hałas, można dodać wiele oktaw szumu simpleks. [...] Ponieważ chcę uzyskać mniej więcej kulistą pływającą skałę, muszę pomnożyć hałas przez jego odległość od centrum. [...] Chcę również, aby skała była bardziej płaska na górze niż na dole, dlatego drugim współczynnikiem mnożenia jest gradient w kierunku y. Łącząc je ze sobą i rozciągając y na hałas podczas kompresji x i za bit, otrzymujemy coś w rodzaju pływającej skały. [...] Wydobycie jaskiń z innym przesunięciem hałasu powoduje, że jest bardziej interesujący.

  • Zasadniczo zaczniesz od zestawu danych wygenerowanego z szumu simpleks lub perlin (a raczej zsumowanych kilku oktaw hałasu ).
  • Następnie uformuj go w coś bliższego pływającej masie lądowej, czyniąc go bardziej kulistym (mnożąc hałas przez jego odległość od centrum ).
  • I stwórz ziemię, spłaszczając ją u góry (mnożąc ją przez gradient pionowy, tj. Zaczynając od niskich wartości u góry i podnosząc się wyżej w dół).
  • Połącz te trzy i dostosuj kształt, skalując hałas wzdłuż osi X / Y / Z (artykuł sugeruje rozciąganie na osi Y i ściskanie na osiach X i Z ).
  • Dodatkowy hałas można wykorzystać do wykopania jaskiń .
David Gouveia
źródło
Tak, myślę, że coś takiego jest zdecydowanie tym, czego chcę. Chodzi o to, że mam niewielkie doświadczenie z hałasem perlin, więc jedyne, co mogę wygenerować, to naprawdę podstawowe góry i nie miałbym żadnych pomysłów, jak dodać „wiele oktaw hałasu razem”. Do generowania hałasu perlin używam kodu że wysiadłem z stackoverflow.com/questions/4753055/... i przeniosłem go na język C #. Dodam swoją wersję w oryginalnym poście ... Czy zechciałbyś dać mi przykład, jak osiągnęłbym taką masę lądową z tym kod?
Darestium
2
Dlatego połączyłem artykuł. Zawiera wyjaśnienie wszystkich kroków oraz kod źródłowy na końcu. Powinieneś spróbować to przestudiować.
David Gouveia,
4
  1. Korzystając z istniejącej siatki 3D, wybierz wysokość, na której mają znajdować się wierzchołki wysp. Utwórz zestaw wysp na tej płaszczyźnie 2D (nazwijmy to płaszczyzną XY), rozpraszając punkty przez płaszczyznę, a następnie umieszczając kostki w tych punktach. Użyj spójności, aby zbliżyć je do siebie w kępach. Wypełnij dowolne dziury, a otrzymasz zestaw wysp na szczycie.
  2. Użyj urzędu certyfikacji- podobna metoda uprawy wysp w dół. (a) Zaczynając od poziomu Z, gdzie narysowałeś swoje początkowe punkty, dla każdej komórki na bieżącym poziomie Z określ szansę na przejście do następnego niższego poziomu, biorąc pod uwagę liczbę sąsiadów na płaszczyźnie XY, od 0 do 8 ( uwzględniono diagonalnych sąsiadów), np. przydzielono 10% szansy każdemu sąsiadowi, maksymalnie do 80% szansy. Oblicz to dla każdej komórki w płaszczyźnie początkowej. (b) Następnie losuj według tej szansy i rozciągnij w dół, jeśli znajdziesz się w przedziale procentowym. Opłucz, powtórz krok 2 (przejdź do następnego poziomu, określ sąsiadów dla każdego woksela, rozciągnij w dół dla tego woksela), aż nie będzie już żadnych rozszerzeń. Twoje przedłużenie w dół powinno tworzyć stożek ze względu na podejście liczby sąsiadów, ponieważ woksele w kierunku centrum XY wyspy zwykle mają więcej sąsiadów.

Pseudokod dla kroku 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

Po zakończeniu generowania wysp możesz opcjonalnie przesunąć je w górę i w dół w przestrzeni, aby mieć je na różnych wysokościach.

Inżynier
źródło
OK, miałem do czynienia z twoją metodą i wydaje się, że teren rośnie na zewnątrz zamiast do wewnątrz.
Wyślę