Шум Перлина в D

В этой статье мы покажем простой порт реализации шума Перлина в D. Реализация очень простая и ее легко повторить вообще без сторонних библиотек и иных сторонних компонентов. Если вам интересно, то на правах рецепта, мы прилагаем код ниже.

За основу взята неплохая статья с Habr — «Пишем настоящий шум Перлина», в которой неплохо все изложено и объяснен сам принцип работы предлагаемого кода. Именно из-за простоты, а также из-за схожести языков C# (который был использован в статье, если не ошибаемся) и D была выбрана реализация для портирования. По этим же причинам мы не приводим объяснения механики кода и если вас интересуют подробности, то советуем обратится к оригинальной статье.

Вот полный код класса для двумерного шума Перлина:

import std.math : floor;
import std.random;

class Perlin2D
{
    private byte[] permutationTable;

    this(int seed = unpredictableSeed)
    {
        permutationTable = new byte[1024];
        auto rng = Random(seed);

        foreach (i; 0..1024)
        {
			permutationTable[i] = cast(ubyte) uniform(0, 255, rng);
		}
    }

    private float[] getPseudoRandomGradientVector(int x, int y)
    {
		int v = cast(ulong) (((x * 1836311903) ^ (y * 2971215073) + 4807526976) & 1023);
        v = permutationTable[v] & 3;

        switch (v)
        {
            case 0:
				return cast(float[])  [1.0, 0.0 ];
			case 1:
				return cast(float[])  [-1.0, 0.0];
            case 2:
				return cast(float[])  [0.0, 1.0 ];
            default:
				return cast(float[])  [0.0, -1.0];
        }
    }

    static float quinticCurve(float t)
    {
		return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
    }

    static float lerp(float a, float b, float t)
    {
        return a * (t - 1.0) + b * t;
    }

    static float lerp2(float a, float b, float t)
    {
		return lerp(a, b, quinticCurve(t));
	}

    static float dot(float[] a, float[] b)
    {
        return a[0] * b[0] + a[1] * b[1];
    }

    public float generateNoise(float fx, float fy)
    {
        int left = cast(int) floor(fx);
        int top  = cast(int) floor(fy);
        float pointInQuadX = fx - cast(float) left;
        float pointInQuadY = fy - cast(float) top;

        float[] topLeftGradient     = getPseudoRandomGradientVector(left,     top    );
        float[] topRightGradient    = getPseudoRandomGradientVector(left + 1, top    );
        float[] bottomLeftGradient  = getPseudoRandomGradientVector(left,     top + 1);
        float[] bottomRightGradient = getPseudoRandomGradientVector(left + 1, top + 1);

		debug {
			writeln(topLeftGradient, " ", topRightGradient, " ", bottomLeftGradient, " ", bottomRightGradient);
		}

        float[] distanceToTopLeft     = [ pointInQuadX,     pointInQuadY     ];
        float[] distanceToTopRight    = [ pointInQuadX - 1, pointInQuadY     ];
        float[] distanceToBottomLeft  = [ pointInQuadX,     pointInQuadY - 1 ];
        float[] distanceToBottomRight = [ pointInQuadX - 1, pointInQuadY - 1 ];

		debug {
			writeln(distanceToTopLeft, " ", distanceToTopRight, " ", distanceToBottomLeft, " ", distanceToBottomRight);
		}

        float tx1 = dot(distanceToTopLeft,     topLeftGradient);
        float tx2 = dot(distanceToTopRight,    topRightGradient);
        float bx1 = dot(distanceToBottomLeft,  bottomLeftGradient);
        float bx2 = dot(distanceToBottomRight, bottomRightGradient);

        debug {
			writeln(tx1, " ", tx2, " ", bx1, " ", bx2);
		}

        pointInQuadX = quinticCurve(pointInQuadX);
        pointInQuadY = quinticCurve(pointInQuadY);

        float tx = lerp(tx1, tx2, pointInQuadX);
        float bx = lerp(bx1, bx2, pointInQuadX);
        float tb = lerp(bx, tx, pointInQuadY);

        debug {
			writeln(tx, " ", bx, " ", tb);
		}

        return tb;
    }

    public float generateNoise(float fx, float fy, int octaves, float persistence = 0.5f)
    {
        float amplitude = 1;
        float max = 0;
        float result = 0;

        while (octaves --> 0)
        {
            max += amplitude;
            result += generateNoise(fx, fy) * amplitude;
            amplitude *= persistence;
            fx *= 2;
            fy *= 2;
        }

        return result / max;
    }
}

Испытать код можно простым выводом в формате P3 или P6, который можно реализовать даже без нашей любимой ppmformats. К примеру можно сделать так:

import std.stdio;

void main()
{
	auto pn = new Perlin2D;

	writeln("P3\n512 512\n255\n");

	foreach (i; 0..512)
	{
		foreach (j; 0..512)
		{
			auto v = cast(int) (127 + 127 * pn.generateNoise(i, j, 10, 0.4));
			writeln(v, " ", v, " ", v);
		}
	}
}

Результат (очень мелко) выглядит примерно так:

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

aquaratixc

Программист-самоучка и программист-любитель

Добавить комментарий