Fractal Terrain

class Example extends Phaser.Scene
{
    graphics;
    detail = 7;
    size = Math.pow(2, this.detail) + 1;
    max = this.size - 1;
    map = new Float32Array(this.size * this.size);
    roughness = 0.6;
    width = 0;
    height = 0;

    create ()
    {
        this.graphics = this.add.graphics({ x: 400, y: 100 });

        this.generate();
        this.draw();
    }

    update ()
    {
        // draw();
    }

    get (x, y)
    {
        if (x < 0 || x > this.max || y < 0 || y > this.max)
        {
            return -1;
        }

        return this.map[x + this.size * y];
    }

    set (x, y, val)
    {
        this.map[x + this.size * y] = val;
    }

    generate ()
    {
        this.set(0, 0, this.max);
        this.set(this.max, 0, this.max / 2);
        this.set(this.max, this.max, 0);
        this.set(0, this.max, this.max / 2);

        this.divide(this.max);
    }

    divide (size)
    {

        const half = size / 2;
        const scale = this.roughness * size;

        if (half < 1)
        {
            return;
        }

        for (let y = half; y < this.max; y += size)
        {
            for (let x = half; x < this.max; x += size)
            {
                this.square(x, y, half, Math.random() * scale * 2 - scale);
            }
        }

        for (let y = 0; y <= this.max; y += half)
        {
            for (let x = (y + half) % size; x <= this.max; x += size)
            {
                this.diamond(x, y, half, Math.random() * scale * 2 - scale);
            }
        }

        this.divide(size / 2);
    }

    average (values)
    {
        const valid = values.filter(function(val) { return val !== -1; });
        const total = valid.reduce(function(sum, val) { return sum + val; }, 0);

        return total / valid.length;
    }

    square (x, y, size, offset)
    {
        const avg = this.average([
            this.get(x - size, y - size),   // upper left
            this.get(x + size, y - size),   // upper right
            this.get(x + size, y + size),   // lower right
            this.get(x - size, y + size)    // lower left
        ]);

        this.set(x, y, avg + offset);
    }

    diamond (x, y, size, offset)
    {
        const avg = this.average([
            this.get(x, y - size),      // top
            this.get(x + size, y),      // right
            this.get(x, y + size),      // bottom
            this.get(x - size, y)       // left
        ]);

        this.set(x, y, avg + offset);
    }

    draw ()
    {
        this.graphics.clear();

        const waterVal = this.size * 0.3;

        for (let y = 0; y < this.size; y++)
        {
            for (let x = 0; x < this.size; x++)
            {
                const val = this.get(x, y);
                const top = this.project(x, y, val);
                const bottom = this.project(x + 1, y, 0);
                const style = this.brightness(x, y, this.get(x + 1, y) - val);

                this.rect(top, bottom, style);

                // var water = project(x, y, waterVal);
                // rect(water, bottom, 'rgba(50, 150, 200, 0.15)');
            }
        }
    }

    rect (a, b, style)
    {
        if (b.y < a.y)
        {
            return;
        }

        const rgb = Phaser.Display.Color.RGBStringToColor(style);

        this.graphics.fillStyle(rgb.color, rgb.alphaGL);
        // graphics.fillRect(a.x * 2, a.y * 2, (b.x - a.x) * 2, (b.y - a.y) * 2);
        // graphics.fillRect(a.x, a.y, (b.x - a.x) * 1, (b.y - a.y) * 1);
        this.graphics.fillRect(a.x, a.y, b.x - a.x, b.y - a.y);
    }

    brightness (x, y, slope)
    {
        if (y === this.max || x === this.max)
        {
            return '#000';
        }

        const b = ~~(slope * 50) + 128;

        return ['rgba(', b, ',', b, ',', b, ',1)'].join('');
    }

    iso (x, y)
    {
        // return {
        //     x: x,
        //     y: y
        // };
        return {
            x: 0.5 * (this.size + x - y),
            y: 0.5 * (x + y)
        };
    }

    project (flatX, flatY, flatZ)
    {
        const point = this.iso(flatX, flatY);
        const x0 = this.width * 0.5;
        const y0 = this.height * 0.2;

        //  Original
        // var z = size * 0.5 - flatZ + point.y * 0.75;
        // var x = (point.x - size * 0.5) * 6;
        // var y = (size - point.y) * 0.005 + 1;

        const z = this.size * 0.5 - flatZ + point.y * 0.75;
        const x = (point.x - this.size * 0.5) * 6;
        const y = (this.size - point.y) * 0.005 + 1;

        return {
            x: x0 + x / y,
            y: y0 + z / y
        };
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    backgroundColor: '#3d3d89',
    scene: Example
};

const game = new Phaser.Game(config);