Download Game! Currently 106 players and visitors. Last logged in:NbergsenFimirTikkuMulam

Blitzer's Blog >> 72153

Back to blogs index
Posted: 06 Sep 2026 14:03 [ permalink ]
Below is the updated `generateCubeMesh` function for `mesh_worker.ts`,
implementing face culling based on neighbor checks. The code assumes:

- `chunkData` is a flat `Uint8Array` (or `number[]`) of length `chunkSizeB3`,
where `1` means solid and `0` means air.
- Chunk coordinates are indexed as `x + z * chunkSize + y * chunkSize *
chunkSize` (x = width, z = depth, y = height, with y as up). Adjust the index
calculation if your layout differs.
- The function returns a flat `Float32Array` of vertex positions (3 floats per
vertex). If you need normals, UVs, or colors, extend the vertex definition
accordingly.

The function checks all 6 neighbors for each solid block and only emits faces
that are exposed to air or outside the chunk boundary.

```typescript
/ mesh_worker.ts

 * Generates a mesh for a chunk by culling hidden faces.
 * @param chunkData Flat array of voxel data (1 = solid, 0 = air).
 * @param chunkSize Size of the chunk (assumed equal in all dimensions).
 * @returns Float32Array containing vertex positions (3 floats per vertex).
 */
export function generateCubeMesh(chunkData: Uint8Array | number[], chunkSize:
number): Float32Array {
    const vertices: number[] = [];

    // Predefined face vertex data for a unit cube from (0,0,0) to (1,1,1).
    // Each face uses 6 vertices (two triangles). Winding is counter-clockwise
    // when viewed from outside the cube.
    // Format: [x, y, z,  x, y, z,  ...] for 6 vertices.
    const FACES: Record<string, number[]> = {
        // +X face (right)
        right: [
            1, 0, 0,  1, 1, 0,  1, 0, 1,
            1, 0, 1,  1, 1, 0,  1, 1, 1
        ],
        // -X face (left)
        left: [
            0, 0, 0,  0, 0, 1,  0, 1, 0,
            0, 0, 1,  0, 1, 1,  0, 1, 0
        ],
        // +Y face (top)
        top: [
            0, 1, 0,  1, 1, 0,  0, 1, 1,
            0, 1, 1,  1, 1, 0,  1, 1, 1
        ],
        // -Y face (bottom)
        bottom: [
            0, 0, 0,  0, 0, 1,  1, 0, 0,
            0, 0, 1,  1, 0, 1,  1, 0, 0
        ],
        // +Z face (front)
        front: [
            0, 0, 1,  1, 0, 1,  0, 1, 1,
            0, 1, 1,  1, 0, 1,  1, 1, 1
        ],
        // -Z face (back)
        back: [
            0, 0, 0,  0, 1, 0,  1, 0, 0,
            0, 1, 0,  1, 1, 0,  1, 0, 0
        ]
    };

    // Helper to get voxel value at (x, y, z), returns 0 if out of bounds
    const getVoxel = (x: number, y: number, z: number): number => {
        if (x < 0 || x >= chunkSize ||
            y < 0 || y >= chunkSize ||
            z < 0 || z >= chunkSize) {
            return 0; // treat outside as air
        }
        const index = x + z * chunkSize + y * chunkSize * chunkSize;
        return chunkData[index] || 0;
    };

    for (let y = 0; y < chunkSize; y++) {
        for (let z = 0; z < chunkSize; z++) {
            for (let x = 0; x < chunkSize; x++) {
                const index = x + z * chunkSize + y * chunkSize * chunkSize;
                if (chunkData[index] !== 1) continue; // only solid blocks

                // Check each neighbor and emit corresponding face if exposed
                // Right (+X)
                if (getVoxel(x + 1, y, z) !== 1) {
                    addFace(FACES.right, x, y, z);
                }
                // Left (-X)
                if (getVoxel(x - 1, y, z) !== 1) {
                    addFace(FACES.left, x, y, z);
                }
                // Top (+Y)
                if (getVoxel(x, y + 1, z) !== 1) {
                    addFace(FACES.top, x, y, z);
                }
                // Bottom (-Y)
                if (getVoxel(x, y - 1, z) !== 1) {
                    addFace(FACES.bottom, x, y, z);
                }
                // Front (+Z)
                if (getVoxel(x, y, z + 1) !== 1) {
                    addFace(FACES.front, x, y, z);
                }
                // Back (-Z)
                if (getVoxel(x, y, z - 1) !== 1) {
                    addFace(FACES.back, x, y, z);
                }
            }
        }
    }

    // Inner helper to push face vertices with block offset
    function addFace(face: number[], offsetX: number, offsetY: number,
offsetZ: number) {
        for (let i = 0; i < face.length; i += 3) {
            vertices.push(
                face[i]     + offsetX,
                face[i + 1] + offsetY,
                face[i + 2] + offsetZ
            );
        }
    }

    return new Float32Array(vertices);
}
```

**Key points:**
- `getVoxel` handles outofbounds by returning `0` (air), so boundary faces are
always drawn.
- Face definitions are hardcoded for a unit cube; they are offset by the
blocks coordinates.
- Only faces adjacent to air (or outside) are added, drastically reducing
vertex count for solid interiors.
- The winding order is consistent for backface culling if you enable it in
WebGL.

**If your indexing differs** (e.g., `x + y * chunkSize + z * chunkSizeB2`),
adjust the `getVoxel` and main loop index calculations accordingly, and swap
the `y` and `z` in the neighbor checks as needed.