4
\$\begingroup\$

I've created a simple Perlin noise generator in JavaScript and am trying to find ways to optimize it. It lives inside a webpage and generates an image using nothing more than random number generator and a lot of arrays and algebra.

The end result is kind of slow. Not unusable, but a lot slower than a Java applet would have been. I'm wondering if I'm making some naive mistakes that could really speed things up or if JavaScript is not really up to this sort of task.

The code is on jsbin

function Random()
{
    this.m = 2147483647; //2^31 - 1
    this.a = 16807; //7^5; primitive root of m
    this.q = 127773; // m / a
    this.r = 2836; // m % a
    this.seed = 1;

    this.setSeed = function(seed)
    {
        if (seed <= 0)
        {
            seed = -(seed % (this.m - 1)) + 1;
        }
        if (seed > this.m - 1)
        {
            seed = this.m - 1;
        }
        this.seed = seed;
    };

    this.nextLong = function()
    {
        var res = this.a * (this.seed % this.q) - this.r * Math.floor(this.seed / this.q);
        if (res <= 0)
        {
            res += this.m;
        }
        this.seed = res;
        return res;
    };

    this.next = function()
    {
        var res = this.nextLong();
        return res / this.m;
    };
}

function Color(r, g, b, a)
{
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
}

function PerlinSampler2D(width, height, randseed)
{
    this.width = width;
    this.height = height;
    this.randseed = randseed;
    this.gradients = new Array(width * height * 2);

    var rand = new Random();
    rand.setSeed(randseed);
    for (var i = 0; i < this.gradients.length; i += 2)
    {
        var x, y;
        var angle = rand.next() * Math.PI * 2;
        x = Math.sin(angle);
        y = Math.cos(angle);

        this.gradients[i] = x;
        this.gradients[i + 1] = y;
    }

    this.dot = function(cellX, cellY, vx, vy)
    {
        var offset = (cellX + cellY * this.width) * 2;
        var wx = this.gradients[offset];
        var wy = this.gradients[offset + 1];
        return wx * vx + wy * vy;
    };

    this.lerp = function(a, b, t)
    {
        return a + t * (b - a);
    };

    this.sCurve = function(t)
    {
        return t * t * (3 - 2 * t);
    };

    this.getValue = function(x, y)
    {
        var xCell = Math.floor(x);
        var yCell = Math.floor(y);
        var xFrac = x - xCell;
        var yFrac = y - yCell;

        var x0 = xCell;
        var y0 = yCell;
        var x1 = xCell === this.width - 1 ? 0 : xCell + 1;
        var y1 = yCell === this.height - 1 ? 0 : yCell + 1;

        var v00 = this.dot(x0, y0, xFrac, yFrac);
        var v10 = this.dot(x1, y0, xFrac - 1, yFrac);
        var v01 = this.dot(x0, y1, xFrac, yFrac - 1);
        var v11 = this.dot(x1, y1, xFrac - 1, yFrac - 1);

        var vx0 = this.lerp(v00, v10, this.sCurve(xFrac));
        var vx1 = this.lerp(v01, v11, this.sCurve(xFrac));

        return this.lerp(vx0, vx1, this.sCurve(yFrac));
    };
}

function Image(width, height)
{
    this.width = width;
    this.height = height;

    this.data = new Array(width * height * 4);

    this.setColor = function(x, y, color)
    {
        var offset = ((y * this.width) + x) * 4;

        this.data[offset] = color.r;
        this.data[offset + 1] = color.g;
        this.data[offset + 2] = color.b;
        this.data[offset + 3] = color.a;
    };

    this.setRgba = function(x, y, r, g, b, a)
    {
        var offset = ((y * this.width) + x) * 4;

        this.data[offset] = r;
        this.data[offset + 1] = g;
        this.data[offset + 2] = b;
        this.data[offset + 3] = a;
    };

    this.createPerlinNoise = function(period, randseed)
    {
        var sampler = new PerlinSampler2D(Math.ceil(this.width / period), Math.ceil(this.height / period), randseed);

        for (var j = 0; j < this.height; ++j)
        {
            for (var i = 0; i < this.width; ++i)
            {
                var val = sampler.getValue(i / period, j / period);
                var b = (val + 1) / 2 * 256;

                this.setRgba(i, j, 
                b, b, b, 0xff);
            }
        }
    };

    this.createTurbulence = function(period, levels, randseed)
    {
        var layers = new Array(this.width * this.height);

        for (var j = 0; j < this.height; ++j)
        {
            for (var i = 0; i < this.width; ++i)
            {
                layers[i + j * this.width] = 0;
            }
        }

        var localPeriod = period;
        var atten = 1;
        for (var k = 0; k < levels; ++k)
        {
            var sampler = new PerlinSampler2D(Math.ceil(this.width / localPeriod), Math.ceil(this.height / localPeriod), randseed);

            for (var j = 0; j < this.height; ++j)
            {
                for (var i = 0; i < this.width; ++i)
                {
                    var val = sampler.getValue(i / localPeriod, j / localPeriod);
                    layers[i + j * this.width] += val * atten;
                }
            }
            localPeriod /= 2;
            atten /= 2;
        }

        for (var j = 0; j < this.height; ++j)
        {
            for (var i = 0; i < this.width; ++i)
            {
                var b = (layers[i + j * this.width] + 1) / 2 * 256;

                this.setRgba(i, j, 
                b, b, b, 0xff);
            }
        }
    };

    this.toImageContext = function(ctx)
    {
        var imgData = ctx.createImageData(this.width, this.height);

        for (var i = 0, len = width * height * 4; i < len; i++)
        {
            imgData.data[i] = this.data[i];
        }

        return imgData;
    };
}
\$\endgroup\$
3
  • \$\begingroup\$ Welcome to Code Review! That's an interesting question, I hope you get some good reviews! \$\endgroup\$
    – Phrancis
    Jan 29, 2015 at 17:26
  • \$\begingroup\$ "The end result is kind of slow" – how have you measured this? \$\endgroup\$
    – Pavlo
    Jan 29, 2015 at 20:12
  • \$\begingroup\$ I have run the program using Firefox and noticed that it takes a little less than a second to redraw the screen after I initiate a redraw event. \$\endgroup\$
    – kitfox
    Jan 29, 2015 at 21:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.