Как я могу создать плавающие массы земли для двигателя, похожего на Minecraft?

19

Я создаю Minecraft-подобный движок в XNA. Я хочу создать плавающие острова, похожие на те, что показаны в этом видео:

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

Как бы я повторил это с помощью генератора мира? Должен ли я использовать какой-нибудь алгоритм шума Перлина? Я не знаю, как это помогло бы мне создать такие массы земли.

Вот код для генератора шума Perlin, который я использую:

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

Но дело в том, что автор кода использует следующее для генерации шума, и я совсем не понимаю его.

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

И вот сайт, который я использую: http://lotsacode.wordpress.com/2010/02/24/perlin-noise-in-c/ .

И я пытаюсь реализовать перлин-шум способом, указанным Мартином Сойкой.

Итак, вот что я получил:

введите описание изображения здесь

Darestium
источник

Ответы:

21

Для базовой земли создайте два двумерных непрерывных шумовых поля (Perlin, Simplex, Wavelet, их комбинация - все, что вам подходит), одно из которых имеет в основном низкую частоту. части с низкой амплитудой для верхнего предела земли, другие с высокой частотой, частями с высокой амплитудой и низкой частотой, высокой амплитудой для нижнего предела земли. Если нижний предел превышает верхний предел, не включайте наземные вокселы (или то, что ваша игра будет использовать для представления местности). Конечный результат выглядит примерно так ...

Мартин Сойка
источник
Но это для 2D не так ли?
Darestium
Но мне это очень нравится :)
Darestium
4
2D / 3D - то же самое
Гэвин Уильямс
Хорошо, плохая попытка осуществить это завтра ... Пожелайте мне удачи;)
Darestium
@Darestium: это 2D пример для упрощения визуализации. Тот же метод работает для любого числа (алгебраических) измерений, превышающего единицу.
Мартин Сойка
15

Будет ли что-то подобное достаточно?

введите описание изображения здесь

Если это так, проверьте эту статью . Цитируем наиболее актуальные части:

Чтобы получить более интересный шум, можно сложить несколько октав симплексного шума. [...] Поскольку я хочу получить примерно сферическую плавающую породу, мне нужно умножить шум на расстояние от центра. [...] Я также хочу, чтобы скала была более плоской сверху, чем снизу, поэтому второй коэффициент умножения - это градиент в направлении y. Комбинируя их вместе и растягивая y для шума при сжатии x и z, мы получаем нечто вроде плавающего камня. [...] Раскопки пещер с небольшим смещением шума также делают его более интересным.

  • Таким образом, в основном вы начнете с набора данных, сгенерированных из симплексного или перлинового шума (или, скорее, нескольких октав шума, сложенного вместе ).
  • Затем сделайте его ближе к плавучей суше, сделав его более сферическим (умножив шум на расстояние от центра ).
  • И создайте почву, сделав ее более плоской вблизи вершины (умножив ее на вертикальный градиент, то есть начните с низких значений в верхней части и поднимитесь к нижней части).
  • Объедините эти три и отрегулируйте форму, масштабируя шум по осям X / Y / Z (в статье предлагается растягивать по оси Y и сжимать по осям X и Z ).
  • Дополнительный проход шума может быть использован для раскопок пещер .
Дэвид Гувея
источник
Да, я думаю, что-то подобное это вызывающе то, что я хочу. Дело в том, что у меня мало опыта с перлин-шумом, поэтому единственное, что я могу сгенерировать, это действительно простые горы, и у меня не было бы никаких идей о том, как добавить «несколько октав шума вместе». Для генерации перлин-шума я использую код что я вышел stackoverflow.com/questions/4753055/…… и портировал его на C #. Я добавлю свою версию в исходное сообщение ... Не могли бы вы привести пример того, как я мог бы достичь такой массы земли с этим код?
Darestium
2
Вот почему я связал статью. Он имеет объяснение всех шагов и исходный код в конце. Вы должны попытаться изучить это.
Дэвид Гувея
4
  1. Используя существующую трехмерную сетку, определите высоту, на которой вы хотите, чтобы вершины острова были. Создайте набор островков в этой 2D-плоскости (назовем это плоскостью XY), рассеивая точки по плоскости, а затем размещая кубы в этих точках. Используйте сцепление, чтобы сблизить их друг с другом в глыбах. Заполните любые отверстия, и у вас есть набор вершин острова.
  2. Использовать CA- аналогичный способ выращивания островов вниз. (а) Начиная с уровня Z, на котором вы наносили начальные точки, для каждой ячейки на текущем уровне Z определите возможность продления вниз до следующего более низкого уровня с учетом числа соседей в плоскости XY от 0 до 8 ( диагональные соседи включены), например, назначьте 10% -ый шанс для каждого соседа, максимум до 80%. Рассчитайте это для каждой ячейки в начальной плоскости. (b) Затем сделайте рандомизацию против этого шанса и продолжите движение вниз, если вы находитесь в пределах процентного диапазона. Промойте, повторите шаг 2 (перейдите на следующий уровень, определите соседей для каждого вокселя, простирайтесь вниз для этого вокселя), пока больше не будет расширений. Ваше нисходящее расширение должно образовывать конус из-за подхода числа соседей, потому что у этих вокселей к XY-центру острова обычно будет больше соседей.

Псевдокод для шага 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

Как только ваши острова закончили генерировать, вы можете по желанию сдвинуть их вверх и вниз в пространстве, чтобы они были на разной высоте.

инженер
источник
Хорошо, у меня есть трещина в вашем методе, и кажется, что он растёт на местности, а не внутрь. Я
выложу