Автоматически генерировать пиксельные планеты на холсте - PullRequest
1 голос
/ 04 мая 2019

Моя цель - создать случайную "реалистичную" планету с помощью JavaScript. Проблема, которую я имею, состоит в том, чтобы заставить это выглядеть хорошо, а не только случайные цвета. Мой текущий скрипт принимает цвета между 2 случайно сгенерированными цветами и заполняет сферу случайным цветом между 2 значениями. Моя цель - сделать что-то похожее на это enter image description here Я сделал это изображение в Photoshop, и да, я знаю, что не могу приблизиться к этому результату простым способом, но я все равно хочу улучшить код, чтобы он выглядел лучше. И я не знаю, как поступить, любые идеи о том, как улучшить это полезно.

 var colors;
    window.onload = function () {
        generatePlanet();
    }

    function generatePlanet() {
        colors = interpolateColors("rgb(" + getRndColor() + "," + getRndColor() + "," + getRndColor() + ")", "rgb(" + getRndColor() + "," + getRndColor() + "," + getRndColor() + ")", 6000);
        drawPlanet()
    }

    function getRndColor() {
        return Math.floor(Math.random() * 256)
    }

    function interpolateColors(color1, color2, steps) {
        var stepFactor = 1 / (steps - 1),
            interpolatedColorArray = [];

        color1 = color1.match(/\d+/g).map(Number);
        color2 = color2.match(/\d+/g).map(Number);

        for (var i = 0; i < steps; i++) {
            interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
        }

        return interpolatedColorArray;
    }

    function interpolateColor(color1, color2, factor) {
        if (arguments.length < 3) {
            factor = 0.5;
        }
        var result = color1.slice();
        for (var i = 0; i < 3; i++) {
            result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
        }
        return result;
    };

    function drawPlanet() {

        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        var i = 0, j = 0;
        function animate() {
            ctx.beginPath();
            ctx.fillRect(10 * j, 10 * i, 10, 10);
            ctx.fillStyle = 'rgb(' + colors[Math.floor(Math.random() * 6001)] + ')';
            ctx.fill();
            ctx.closePath();
            j += 1;
            if (j >= 70) {
                i += 1;
                j = 0;
            }
            if (i < 70) {
                animate();
            }
        }
        animate();
    }               
    #canvas {
        border: 10px solid #000000;
        border-radius: 50%;
    }
<button onclick="generatePlanet()">GENERATE</button>
<canvas id="canvas" width="700" height="700"></canvas>

Ответы [ 2 ]

2 голосов
/ 08 мая 2019

В коде есть комментарии, если вы заинтересованы в случайно сгенерированной планете.Я не установил никаких цветовых ограничений, поэтому некоторые цветовые соответствия выглядят немного странно.Если что-то неясно, просто спросите с комментарием.

var colors;
    var tileNum = 0;
    var tiles;
    var colorsLand;
    var colorsWater;
    var rndLandColor;
    var rndWaterColor;

    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    window.onload = function () {
        generatePlanet();
    }

    function generatePlanet() {
        //reset
        tileNum = 0;
        tiles = [{ x: 0, y: 0, land: false }];

        //Retrive colors
        colorsLand = interpolateColors("rgb(" + getColor(true) + ")", "rgb(" + getColor(true) + ")", 6000);
        colorsWater = interpolateColors("rgb(" + getColor(false) + ")", "rgb(" + getColor(false) + ")", 6000);

        //Creates a array of my tiles and sets either water or land to them and calculates the % of being water/land
        for (var i = 0; i < 5040; i++) {
            var currentTile = tiles[tiles.length - 1];            
            if (currentTile.x <= 69) {
                var isLand = false;
                if (currentTile.land == true || tiles.length > 70 && tiles[tiles.length - 70].land == true) {                    
                    isLand = (Math.floor(Math.random() * 100) + 1) > 35;
                }
                else if (currentTile.land == true || tiles.length > 70 &&
                    (tiles[tiles.length - 1].land == true ||
                        tiles[tiles.length - 70].land == true)) {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 70;
                }
                else {
                    isLand = (Math.floor(Math.random() * 100) + 1) > 99;
                }
                tiles.push({ x: currentTile.x + 1, y: currentTile.y, land: isLand });
            }
            else {
                tiles.push({ x: 0, y: currentTile.y + 1, land: isLand });
            }
        }
        drawPlanet()
    }

    //retrive a random color if it's a land tile i want it dark water i want light
    function getColor(land) {
        while (true) {
            var r = Math.floor(Math.random() * 256) + 1
            var g = Math.floor(Math.random() * 256) + 1
            var b = Math.floor(Math.random() * 256) + 1
            hsp = Math.sqrt(
                0.299 * (r * r) +
                0.587 * (g * g) +
                0.114 * (b * b)
            );
            //light color
            if (hsp > 127.5 && land == false) {
                return r + "," + g + "," + b;
            }
            //dark color
            else if (hsp < 127.5 && land == true) {

                return r + "," + g + "," + b;
            }
        }
    }

    //these 2 functions interpolateColor(s) takes 2 colors and gives me 'steps' colors between
    function interpolateColors(color1, color2, steps) {
        var stepFactor = 1 / (steps - 1),
            interpolatedColorArray = [];
        color1 = color1.match(/\d+/g).map(Number);
        color2 = color2.match(/\d+/g).map(Number);

        for (var i = 0; i < steps; i++) {
            interpolatedColorArray.push(interpolateColor(color1, color2, stepFactor * i));
        }
        return interpolatedColorArray;
    }

    function interpolateColor(color1, color2, factor) {
        if (arguments.length < 3) {
            factor = 0.5;
        }
        var result = color1.slice();
        for (var i = 0; i < 3; i++) {
            result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
        }
        return result;
    };

    //retrives a random color for land
    function rndLandColor() {
        return 'rgb(' + colorsLand[Math.floor(Math.random() * 5999) + 1] + ')';
    }
    //retrives a random color for water
    function rndWaterColor() {
        return 'rgb(' + colorsWater[Math.floor(Math.random() * 5999) + 1] + ')';
    }

    function drawPlanet() {
        var i = 0, j = 0;
        function animate() {
            ctx.beginPath();

            //fill in holes in the land that is bigger then 1
            var score = 0;
            if (tiles[tileNum - 71] !== undefined && tiles[tileNum + 71] !== undefined) {
                if (tiles[tileNum].land == false) {
                    score++;
                }
                if (tiles[tileNum - 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + 1].land == true) {
                    score++;
                }
                if (tiles[tileNum + 71].land == true) {
                    score++;
                }
                if (tiles[tileNum - 71].land == true) {
                    score++;
                }
            }

            if (score >= 3) {
                ctx.fillStyle = rndLandColor;
            }

            //cover single land tiles with water (if land tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - 71] !== undefined &&
                tiles[tileNum + 71] !== undefined &&
                tiles[tileNum - 1].land == false &&
                tiles[tileNum + 1].land == false &&
                tiles[tileNum - 71].land == false &&
                tiles[tileNum + 71].land == false) {
                ctx.fillStyle = rndWaterColor();
            }

            //cover single water tiles with land (if water tile is up,down,left and right of this tile)
            else if (
                tiles[tileNum - 71] !== undefined &&
                tiles[tileNum + 71] !== undefined &&
                tiles[tileNum - 1].land == true &&
                tiles[tileNum + 1].land == true &&
                tiles[tileNum - 71].land == true &&
                tiles[tileNum + 71].land == true) {
                ctx.fillStyle = rndLandColor();
            }
            //cover tile with land
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == true) {
                ctx.fillStyle = rndLandColor();
            }

            //cover tile with water
            else if (tiles[tileNum] !== undefined && tiles[tileNum].land == false) {
                ctx.fillStyle = rndWaterColor();
            }
            tileNum++;

            ctx.fill();
            ctx.closePath();
            ctx.fillRect(10 * j, 10 * i, 10, 10);

            j++;
            if (j >= 71) {
                i++;
                j = 0;
            }
            if (i <= 71) {
                animate();
            }
        }
        animate();
    }
#canvas {
        border: 10px solid #000000;
        border-radius: 50%;
        background-color: aquamarine;
    }

    .container {
        width: 720px;
        height: 720px;
        position: relative;
    }

    .gradient {
        position: absolute;
        height: 730px;
        width: 730px;
        top: 0;
        left: 0;
        border-radius: 50%;
        opacity: 0.8;
    }
<button onclick="generatePlanet()">GENERATE</button>
<div class="container">
    <img class="gradient" src="https://www.mediafire.com/convkey/1f5a/cgu50lw1ehcp4fq6g.jpg" />
    <canvas id="canvas" width="710" height="710"></canvas>
</div>
1 голос
/ 04 мая 2019

Это может быть более сложной задачей, чем выглядит.Но я могу предложить вам использовать следующие два шага, чтобы сделать это

  1. некоторый алгоритм шума (например, Perlin Noise ) для получения основной текстуры планеты.Это может дать вам совершенно разные результаты с разными параметрами запуска.
  2. Добавьте теневой слой для текстуры, полученной на шаге 2, чтобы сделать ее более реалистичной.Это может быть реализовано двумя более очевидными способами: используя предопределенную текстуру теней или используя вычисленные тени в один / несколько шагов.
...