skip to Main Content

My goal is to generate a random “realistic” planet with JavaScript. The problem i have is to make it look good and not just random colors. My current script takes colors between 2 random generated colors and fills the sphere with a random color between the 2 values.
My goal is to have something that looks close(er) to this enter image description here
I made that image in Photoshop and yes I know I cant get anywhere close to that result in a easy way, but I want to improve the code in anyway so it looks better. And I dont know how to proceed, any ideas on how to improve is helpful.

 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

Answers


  1. Chosen as BEST ANSWER

    There is comments in the code if you are interested in a random generated planet. I have not set any color restrictions so some color matches looks a little weird. If something is unclear just ask with a comment.

    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>


  2. It’s can be more complex task then is looks like. But I can suggest you use next two step way to do it

    1. some noise algorithm(e.g Perlin Noise) for getting main texture of planet. It can take you definitely different results with different start parameters.
    2. Add shadow layer for received from step 2 texture for getting it more realistic. This can be implemented in two more obvious ways: using predefined shadow texture or using computed shadows in one/several steps.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search