2021-10-28

Procedurally Generating a Map

I've been trying to procedurally generate a planet for the game I'm making in Javascript. It generated a random noise grid and then iterates through it using cellular automata rules. This takes longer than 30 seconds for worlds larger than 1000x1000, so I was wondering if there was a way to generate it from a seed when it is loaded, like in Minecraft. Is there a way to generate the planet without having to do it all at once, or at least a faster way?

Here's the code, it generates the terrain and then displays it on the canvas.

scale = 0.25;

rngCount = 0;
function* RNG() {
    let seed = Math.floor(Math.random() * 10000) / 10000;
    console.log(seed);
    let factor = 297;
    while(true) {
        seed = (seed * factor) - Math.floor(seed * factor);
        yield Math.floor(seed * 10000) / 10000;
    };
};

let generationRNG = RNG();

function clamp(min, max, value) {
    if(value < min) {
        return min;
    } else if(value > max) {
        return max;
    } else {
        return value;
    };
};

let map = [];
let mapW = 4096;
let mapH = 4096;

function iterate(e, map) {
    let height = map.length;
    let width = map[0].length;
    let updatedMap;
    let mapRow;
    let neighborCount; 
    let iy;
    let ix;

    for(i = 0; i < e; i++) {
        updatedMap = [];
        for(y = 0; y < height; y++) {
            mapRow = [];
            for(x = 0; x < width; x++) {
                neighborCount = 0;
                for(k = -1; k <= 1; k++) {
                    for(l = -1 ; l <= 1; l++) {
                        if(y + k < 0) {
                            iy = height - 1;
                        } else if(y + k >= height) {
                            iy = 0;
                        } else {
                            iy = y + k;
                        };

                        if(x + l < 0) {
                            ix = width - 1;
                        } else if(x + l >= width) {
                            ix = 0;
                        } else {
                            ix = x + l;
                        };

                        if(!(l == 0 && k == 0)) {
                            neighborCount += map[iy][ix];
                        };
                    };
                };
                if(neighborCount >= 5) {
                    mapRow.push(1);
                } else if(neighborCount <= 3) {
                    mapRow.push(0);
                } else {
                    mapRow.push(map[y][x]);
                };
            };
            updatedMap.push(mapRow);
        };
        map = updatedMap;
    };

    return map;
};

function terrainGeneration() {
    let width = mapW / 16;
    let height = mapH / 16;
    let mapRow;
    let factor = 4;
    let fillPercent = 0.45
    let updatedMap;
    let map = [];

    for(y = 0; y < height; y++) {
        mapRow = [];
        for(x = 0; x < width; x++) {
            
            if(generationRNG.next().value <= fillPercent) {
                mapRow.push(1);
            } else {
                mapRow.push(0);
            };
        };
        map.push(mapRow);
    };
    
    map = iterate(10, map);

    for(a = 0; a < 2; a++) {
        if(a == 0) {
            fillPercent = 0.33;
        } else if (a == 1) {
            fillPercent = 0.2;
        };

        width *= factor;
        height *= factor;
        updatedMap = [];
        for(y = 0; y < height; y++) {
            mapRow = [];
            for(x = 0; x < width; x++) {
                mapRow.push(map[Math.floor(y / factor)][Math.floor(x / factor)]);
            };
            updatedMap.push(mapRow);
        };
        
        map = updatedMap;

        updatedMap = [];
        for(y = 0; y < height; y++) {
            mapRow = [];
            for(x = 0; x < width; x++) {

                if(map[y][x] == 1) {
                    if(generationRNG.next().value <= fillPercent) {
                        mapRow.push(0);
                    } else {
                        mapRow.push(1);
                    };
                } else {
                    if(generationRNG.next().value <= fillPercent) {
                        mapRow.push(1);
                    } else {
                        mapRow.push(0);
                    };
                };
            };
            updatedMap.push(mapRow);
        };
        map = updatedMap;

        map = iterate(5, map);
    };

    return map;
};

map = terrainGeneration();

for(i = 0; i < map.length; i++) {
    for(j = 0; j < map[i].length; j++) {
        c.fillStyle = "rgba(" + map[i][j] * 255 + ", " + map[i][j] * 255 + ", "  + map[i][j] * 255 + ", 1)";
        c.fillRect(j * scale, i * scale, scale, scale);
    };
};
c.fillStyle = "rgba(0, 0, 255, 1)";
c.fillRect(0, 0, 40 * scale, 40 * scale);

is there a way to generate only a section of it from a seed and still have it coherently fit together? After I add biomes, the generation will take even longer, so I want to try to fix it now before I add more code. To test out the section generation I was going to have it generate the area around the cursor only.

here's an example of a 4096x4096 map that took 3 and a half minutes to generate.

map image



from Recent Questions - Stack Overflow https://ift.tt/3bh2Dsf
https://ift.tt/3GtYQGr

No comments:

Post a Comment