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.