#include "VoxelMesher.h" #include #include namespace voxel { // ── Build binary masks per axis ───────────────────────────────── // For each axis, solid[u][v] is a 32-bit mask where bit i=1 means // voxel at position i along that axis is solid. void VoxelMesher::buildAxisMasks(const Chunk& chunk, AxisMasks masks[3]) { std::memset(masks, 0, sizeof(AxisMasks) * 3); for (int z = 0; z < CHUNK_SIZE; z++) { for (int y = 0; y < CHUNK_SIZE; y++) { for (int x = 0; x < CHUNK_SIZE; x++) { if (!chunk.at(x, y, z).isEmpty()) { // X-axis: march along X, indexed by [Y][Z] masks[0].solid[y][z] |= (1u << x); // Y-axis: march along Y, indexed by [X][Z] masks[1].solid[x][z] |= (1u << y); // Z-axis: march along Z, indexed by [X][Y] masks[2].solid[x][y] |= (1u << z); } } } } } // Helper: get voxel, considering neighbor chunks for boundary faces static VoxelData getVoxelSafe(const Chunk& chunk, const VoxelWorld& world, int x, int y, int z) { if (chunk.isInBounds(x, y, z)) { return chunk.at(x, y, z); } // Cross-chunk lookup int wx = chunk.pos.x * CHUNK_SIZE + x; int wy = chunk.pos.y * CHUNK_SIZE + y; int wz = chunk.pos.z * CHUNK_SIZE + z; return world.getVoxel(wx, wy, wz); } // ── Greedy Merge ──────────────────────────────────────────────── // For a given face direction, merge visible faces of the same material // into maximal rectangular quads. void VoxelMesher::greedyMerge( const Chunk& chunk, const VoxelWorld& world, uint8_t face, const uint32_t faceMasks[CHUNK_SIZE][CHUNK_SIZE], std::vector& outQuads ) { // Determine axis mapping based on face // face 0,1 = X axis -> iterate over Y,Z slices // face 2,3 = Y axis -> iterate over X,Z slices // face 4,5 = Z axis -> iterate over X,Y slices // For each slice along the face normal axis for (int depth = 0; depth < CHUNK_SIZE; depth++) { // Build a 2D grid of material IDs for this slice uint8_t matGrid[CHUNK_SIZE][CHUNK_SIZE]; bool visited[CHUNK_SIZE][CHUNK_SIZE]; std::memset(matGrid, 0, sizeof(matGrid)); std::memset(visited, 0, sizeof(visited)); int faceCount = 0; for (int v = 0; v < CHUNK_SIZE; v++) { for (int u = 0; u < CHUNK_SIZE; u++) { // Check if this face is visible at this depth bool faceVisible = false; int x, y, z; switch (face) { case FACE_POS_X: x = depth; y = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; case FACE_NEG_X: x = depth; y = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; case FACE_POS_Y: y = depth; x = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; case FACE_NEG_Y: y = depth; x = u; z = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; case FACE_POS_Z: z = depth; x = u; y = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; case FACE_NEG_Z: z = depth; x = u; y = v; faceVisible = (faceMasks[u][v] >> depth) & 1; break; } if (faceVisible) { matGrid[v][u] = chunk.at(x, y, z).getMaterialID(); faceCount++; } } } if (faceCount == 0) continue; // Greedy merge: scan row by row, merge same-material quads for (int v = 0; v < CHUNK_SIZE; v++) { for (int u = 0; u < CHUNK_SIZE; u++) { if (visited[v][u] || matGrid[v][u] == 0) continue; uint8_t mat = matGrid[v][u]; // Expand width (along u) int w = 1; while (u + w < CHUNK_SIZE && !visited[v][u + w] && matGrid[v][u + w] == mat) { w++; } // Expand height (along v) int h = 1; bool canExpand = true; while (v + h < CHUNK_SIZE && canExpand) { for (int du = 0; du < w; du++) { if (visited[v + h][u + du] || matGrid[v + h][u + du] != mat) { canExpand = false; break; } } if (canExpand) h++; } // Mark as visited for (int dv = 0; dv < h; dv++) { for (int du = 0; du < w; du++) { visited[v + dv][u + du] = true; } } // Compute the actual position in chunk-local coords uint8_t px, py, pz; switch (face) { case FACE_POS_X: px = (uint8_t)depth; py = (uint8_t)u; pz = (uint8_t)v; break; case FACE_NEG_X: px = (uint8_t)depth; py = (uint8_t)u; pz = (uint8_t)v; break; case FACE_POS_Y: px = (uint8_t)u; py = (uint8_t)depth; pz = (uint8_t)v; break; case FACE_NEG_Y: px = (uint8_t)u; py = (uint8_t)depth; pz = (uint8_t)v; break; case FACE_POS_Z: px = (uint8_t)u; py = (uint8_t)v; pz = (uint8_t)depth; break; case FACE_NEG_Z: px = (uint8_t)u; py = (uint8_t)v; pz = (uint8_t)depth; break; default: px = py = pz = 0; break; } // Width/height in the quad's local UV space uint8_t qw, qh; switch (face) { case FACE_POS_X: case FACE_NEG_X: qw = (uint8_t)w; qh = (uint8_t)h; break; case FACE_POS_Y: case FACE_NEG_Y: qw = (uint8_t)w; qh = (uint8_t)h; break; case FACE_POS_Z: case FACE_NEG_Z: qw = (uint8_t)w; qh = (uint8_t)h; break; default: qw = qh = 1; break; } outQuads.push_back(PackedQuad::create( px, py, pz, qw, qh, face, mat, 0, 0 )); } } } } uint32_t VoxelMesher::meshChunk(Chunk& chunk, const VoxelWorld& world) { chunk.quads.clear(); // Step 1: Build binary solid masks per axis AxisMasks axisMasks[3]; buildAxisMasks(chunk, axisMasks); // Step 2: For each face direction, compute visible face masks // then do greedy merge for (uint8_t face = 0; face < FACE_COUNT; face++) { int axis = face / 2; // 0=X, 1=Y, 2=Z bool positive = (face % 2 == 0); // Compute visible face masks // A face is visible if the voxel is solid and the neighbor in the face direction is air uint32_t faceMasks[CHUNK_SIZE][CHUNK_SIZE]; std::memset(faceMasks, 0, sizeof(faceMasks)); for (int v = 0; v < CHUNK_SIZE; v++) { for (int u = 0; u < CHUNK_SIZE; u++) { uint32_t solid = axisMasks[axis].solid[u][v]; if (solid == 0) continue; uint32_t visible; if (positive) { // +dir: face visible if solid here and NOT solid at pos+1 // Shift right: neighbor is at bit+1 uint32_t neighbor = (solid >> 1); // The highest bit has no neighbor in this chunk - check boundary visible = solid & ~neighbor; // Bit 31 (chunk boundary): need to check neighbor chunk // For now, always show boundary faces if (solid & (1u << (CHUNK_SIZE - 1))) { // Check if neighbor chunk's voxel is empty int nx, ny, nz; switch (axis) { case 0: nx = CHUNK_SIZE; ny = u; nz = v; break; // X+ case 1: nx = u; ny = CHUNK_SIZE; nz = v; break; // Y+ case 2: nx = u; ny = v; nz = CHUNK_SIZE; break; // Z+ default: nx = ny = nz = 0; } if (!getVoxelSafe(chunk, world, nx, ny, nz).isEmpty()) { visible &= ~(1u << (CHUNK_SIZE - 1)); // hide boundary face } } } else { // -dir: face visible if solid here and NOT solid at pos-1 uint32_t neighbor = (solid << 1); visible = solid & ~neighbor; // Bit 0 (chunk boundary) if (solid & 1u) { int nx, ny, nz; switch (axis) { case 0: nx = -1; ny = u; nz = v; break; case 1: nx = u; ny = -1; nz = v; break; case 2: nx = u; ny = v; nz = -1; break; default: nx = ny = nz = 0; } if (!getVoxelSafe(chunk, world, nx, ny, nz).isEmpty()) { visible &= ~1u; // hide boundary face } } } faceMasks[u][v] = visible; } } uint32_t beforeCount = (uint32_t)chunk.quads.size(); greedyMerge(chunk, world, face, faceMasks, chunk.quads); chunk.faceOffsets[face] = beforeCount; chunk.faceCounts[face] = (uint32_t)chunk.quads.size() - beforeCount; } chunk.quadCount = (uint32_t)chunk.quads.size(); chunk.dirty = false; return chunk.quadCount; } uint8_t VoxelMesher::calcAO(const VoxelWorld& world, const ChunkPos& cpos, int x, int y, int z, uint8_t face) { // Simplified AO: count occluding neighbors around the vertex // Returns packed 4x2-bit AO for the 4 corners // TODO: implement proper per-corner AO return 0; } } // namespace voxel