Terrain Help

I’m just here to check-in. This post seems to be inactive. How is everybody? I hope all is going well with your projects!

It’s goin well. Hopefully this weekend I can finish phase 1 of the minibossfight I’m working on. How are your projects?

could be better, to be honest. still having trouble with the chunk generator with my Minecraft clone. Glad your projects are going as planned! I see them from time to time and you are making great progress! Love your work!

Thanks! Do you mind if, when I get some time, I fork your project to see if I can get it to work?

1 Like

I don’t mind at all! Have fun with it! take your time, I just wanted to check up on everybody, been busy. Sorry for the late response, been working lol.

1 Like

Oh and remember you also have ownership of the project too. It is titled “Minecraft” if you are looking for it.

1 Like

What are your thoughts on this script? Chat GPT and I were working on it, and there is still an error. (I know I was using AI; DON’T JUDGE ME!) There is an error and chunks don’t sawn in when the player moves. On top of that the greedy meshing system is not working as well as deleting faces the player camera is not seeing. I used Chat GPT as something to work off of.

var TerrainGenerator = pc.createScript('terrainGenerator');

// Attributes
TerrainGenerator.attributes.add('chunkSize', { type: 'number', default: 16 });
TerrainGenerator.attributes.add('maxHeight', { type: 'number', default: 32 });
TerrainGenerator.attributes.add('player', { type: 'entity' }); // Player entity
TerrainGenerator.attributes.add('viewDistance', { type: 'number', default: 3 }); // Chunk view distance

// Initialize
TerrainGenerator.prototype.initialize = function () {
    this.chunkCache = {}; // Cache for storing generated chunks
    this.lastChunkPosition = new pc.Vec2(0, 0); // Last recorded player chunk position
    this.blockSize = 1; // Size of each block in world units

    // Initialize Perlin noise
    this.perm = [...Array(512)].map((_, i) => i % 256);
    this.shuffleArray(this.perm);

    // Generate initial terrain
    this.generateTerrain();
};

// Shuffle for Perlin noise
TerrainGenerator.prototype.shuffleArray = function (array) {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
};

// Perlin Noise Function
TerrainGenerator.prototype.perlinNoise = function (x, z) {
    var X = Math.floor(x) & 255;
    var Z = Math.floor(z) & 255;
    var fx = x - Math.floor(x);
    var fz = z - Math.floor(z);

    var grad00 = this.grad(this.pseudoRandom(X, Z), fx, fz);
    var grad01 = this.grad(this.pseudoRandom(X, Z + 1), fx, fz - 1);
    var grad10 = this.grad(this.pseudoRandom(X + 1, Z), fx - 1, fz);
    var grad11 = this.grad(this.pseudoRandom(X + 1, Z + 1), fx - 1, fz - 1);

    var u = this.fade(fx);
    var v = this.fade(fz);

    var lerpX1 = this.lerp(grad00, grad10, u);
    var lerpX2 = this.lerp(grad01, grad11, u);

    return this.lerp(lerpX1, lerpX2, v);
};

// Noise Helpers
TerrainGenerator.prototype.fade = function (t) {
    return t * t * t * (t * (t * 6 - 15) + 10);
};

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

TerrainGenerator.prototype.grad = function (hash, x, y) {
    var h = hash & 3;
    var u = h < 2 ? x : y;
    var v = h < 2 ? y : x;
    return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
};

TerrainGenerator.prototype.pseudoRandom = function (x, y) {
    return this.perm[(x + this.perm[y & 255]) & 255];
};

// Generate terrain around player
TerrainGenerator.prototype.generateTerrain = function () {
    var playerPos = this.player.getPosition();
    var playerChunkX = Math.floor(playerPos.x / (this.chunkSize * this.blockSize));
    var playerChunkZ = Math.floor(playerPos.z / (this.chunkSize * this.blockSize));
    var currentChunkPosition = new pc.Vec2(playerChunkX, playerChunkZ);

    // Check if the player has moved to a new chunk
    if (!this.lastChunkPosition.equals(currentChunkPosition)) {
        this.lastChunkPosition.copy(currentChunkPosition);

        for (var x = -this.viewDistance; x <= this.viewDistance; x++) {
            for (var z = -this.viewDistance; z <= this.viewDistance; z++) {
                var chunkX = playerChunkX + x;
                var chunkZ = playerChunkZ + z;
                var chunkKey = `${chunkX}_${chunkZ}`;

                if (!this.chunkCache[chunkKey]) {
                    this.generateChunk(chunkX, chunkZ);
                    this.chunkCache[chunkKey] = true;
                }
            }
        }
    }
};

// Generate a single chunk with greedy meshing
TerrainGenerator.prototype.generateChunk = function (chunkX, chunkZ) {
    var chunkEntity = new pc.Entity();
    chunkEntity.setName(`Chunk_${chunkX}_${chunkZ}`);
    this.app.root.addChild(chunkEntity);

    var blocks = []; // Store all blocks in the chunk

    for (var x = 0; x < this.chunkSize; x++) {
        for (var z = 0; z < this.chunkSize; z++) {
            var worldX = chunkX * this.chunkSize + x;
            var worldZ = chunkZ * this.chunkSize + z;
            var height = Math.floor(this.perlinNoise(worldX * 0.1, worldZ * 0.1) * this.maxHeight);

            for (var y = 0; y <= height; y++) {
                blocks.push({ x, y, z });
            }
        }
    }

    // Apply greedy meshing
    var meshData = this.greedyMesh(blocks);
    this.createMesh(chunkEntity, meshData);
};

// Greedy meshing implementation
TerrainGenerator.prototype.greedyMesh = function (blocks) {
    var meshData = [];
    var grid = {};

    blocks.forEach((block) => {
        var key = `${block.x}_${block.y}_${block.z}`;
        grid[key] = true;
    });

    blocks.forEach((block) => {
        // Add only visible faces
        ['x+1', 'x-1', 'y+1', 'y-1', 'z+1', 'z-1'].forEach((dir) => {
            var neighborKey = this.getNeighborKey(block, dir);
            if (!grid[neighborKey]) {
                meshData.push({ block, dir });
            }
        });
    });

    return meshData;
};

// Create mesh
TerrainGenerator.prototype.createMesh = function (parent, meshData) {
    var mesh = new pc.Entity();
    mesh.addComponent('model', { type: 'box' });

    meshData.forEach(({ block, dir }) => {
        var blockEntity = new pc.Entity();
        blockEntity.addComponent('model', { type: 'box' });
        blockEntity.setPosition(
            block.x * this.blockSize,
            block.y * this.blockSize,
            block.z * this.blockSize
        );
        parent.addChild(blockEntity);
    });
};

// Cull faces based on camera
TerrainGenerator.prototype.cullFaces = function () {
    var cameraForward = this.player.camera.forward.clone();

    // Check face normals and disable invisible faces
    // TODO: Implementation
};

// Update terrain generation
TerrainGenerator.prototype.update = function (dt) {
    this.generateTerrain();
};

// Get the key for a neighboring block based on direction
TerrainGenerator.prototype.getNeighborKey = function (block, direction) {
    var neighbor = { x: block.x, y: block.y, z: block.z };

    // Adjust the neighbor position based on the direction
    switch (direction) {
        case 'x+1': neighbor.x += 1; break;
        case 'x-1': neighbor.x -= 1; break;
        case 'y+1': neighbor.y += 1; break;
        case 'y-1': neighbor.y -= 1; break;
        case 'z+1': neighbor.z += 1; break;
        case 'z-1': neighbor.z -= 1; break;
    }

    // Return the key for the neighbor
    return `${neighbor.x}_${neighbor.y}_${neighbor.z}`;
};

Can you share your project so i can debug it?

Sure thing!
https://playcanvas.com/project/1278649/overview/123

It runs perfectly fine for me, just incredibly laggy. Consider generating the terrain every 10 or so frames, or never regenerating it.

So it is rendering new chunks when the player is moving? I am fixing the lag but the optimization part of the code is not working.

Its regenerating all of the terrain every frame. Just remove the update function from the terrain generation script and it will be less laggy.

That did help with some of the lag but I think part of the problem is the game is trying to render all of the blocks in that chunk individually. With the chunk you land on there is empty space for there are blocks that don’t need to be rendered. I can try to apply this to the Terrain gen code, but I am unsure how to go about that.

Enable occulsion culling in your camera settings.

Gotta be honest, I don’t know how to do that. Never had to before.

My bad, it was frustum culling.

I SEE! LOL :joy: will do.

It is turned on and does work, but It will still be laggy when I walk back to the chunk.

Good morning everyone! I hope you had a great Thanksgiving! I have some good news! I did it! I remade a Perlin noise system and I made it so it generates around the player! I still have to do some things before I move on to the next thing, so I will keep you posted!