#version 450
// ============================================================================
// Stage 10: Quad Emit (GPU mesh generation)
//
// For each voxel in the 16³ grid, check its 3 owned edges (+X, +Y, +Z).
// For each sign-change edge, find the 4 cells sharing that edge and emit
// a quad (2 triangles = 6 indices) via atomic append. When only 3 of 4
// neighbors are valid (chunk boundary), emit a single degenerate triangle.
//
// The 4 cells sharing an edge are determined by the edge axis:
// X-edge at (x,y,z): cells (x,y,z), (x,y-1,z), (x,y,z-1), (x,y-1,z-1)
// Y-edge at (x,y,z): cells (x,y,z), (x-1,y,z), (x,y,z-1), (x-1,y,z-1)
// Z-edge at (x,y,z): cells (x,y,z), (x-1,y,z), (x,y-1,z), (x-1,y-1,z)
//
// Winding is determined by the density sign at the edge's starting corner.
//
// Input:
// density[17³] — density grid (DENSITY_STRIDE = 17)
// edge_flags[16³×3] — sign-change flags per edge
// voxel_flags[16³] — active voxel flags (1 = has QEF vertex)
// voxel_scan[16³] — exclusive prefix sum of voxel_flags (compacted index)
//
// Output:
// indices[] — triangle index buffer (atomic append)
// counts[1] — total index count (atomically incremented)
//
// Dispatch: ceil(4096 / 256), 1, 1
// ============================================================================
#define THREADS 256
layout(local_size_x = THREADS, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 0, std430) readonly buffer DensityBuf { float density[]; };
layout(set = 0, binding = 1, std430) readonly buffer EdgeFlagBuf { uint edge_flags[]; };
layout(set = 0, binding = 4, std430) readonly buffer VoxelFlagBuf { uint voxel_flags[]; };
layout(set = 0, binding = 5, std430) readonly buffer VoxelScanBuf { uint voxel_scan[]; };
layout(set = 0, binding = 6, std430) readonly buffer VertexBuf { float vertices[]; };
layout(set = 0, binding = 8, std430) writeonly buffer IndexBuf { uint indices[]; };
layout(set = 0, binding = 9, std430) buffer CountBuf { uint counts[]; };
layout(push_constant) uniform PushConstants {
vec3 cell_origin;
float voxel_size;
ivec3 grid_dims; // (16, 16, 16)
uint total_elements; // 4096
uint mesh_quality_flags;
uint _pc_pad0;
uint _pc_pad1;
uint _pc_pad2; // bit 0 = shortest_diagonal
};
// Neighbor cell offsets for the 4 cells sharing each edge type.
// Each edge axis has 4 neighbor cells; offsets are (dy, dz) for X-edge, etc.
//
// X-edge at (x,y,z): cells at (x, y+dy, z+dz) for each neighbor
const ivec2 X_NEIGHBORS[4] = ivec2[4](
ivec2( 0, 0), // (x, y, z )
ivec2(-1, 0), // (x, y-1, z )
ivec2( 0, -1), // (x, y, z-1)
ivec2(-1, -1) // (x, y-1, z-1)
);
// Y-edge at (x,y,z): cells at (x+dx, y, z+dz) for each neighbor
// IMPORTANT: Z first, then X (matches CPU edgeCellOffsets — swapped from
// naive X,Z order to get correct winding)
const ivec2 Y_NEIGHBORS[4] = ivec2[4](
ivec2( 0, 0), // (x, y, z )
ivec2( 0, -1), // (x, y, z-1)
ivec2(-1, 0), // (x-1, y, z )
ivec2(-1, -1) // (x-1, y, z-1)
);
// Z-edge at (x,y,z): cells at (x+dx, y+dy, z) for each neighbor
const ivec2 Z_NEIGHBORS[4] = ivec2[4](
ivec2( 0, 0), // (x, y, z)
ivec2(-1, 0), // (x-1, y, z)
ivec2( 0, -1), // (x, y-1, z)
ivec2(-1, -1) // (x-1, y-1, z)
);
void try_emit_quad(int x, int y, int z, int axis) {
int V = grid_dims.x; // 16
int S = V + 1; // 17 (density stride)
// Check edge flag
int voxel_idx = z * V * V + y * V + x;
if (edge_flags[voxel_idx * 3 + axis] == 0u) return;
// Gather the 4 neighbor cell indices.
// Track validity with a bitmask (bit i = neighbor i is valid).
uint cell_vi[4];
uint valid_mask = 0u;
int nv = 0; // count of valid neighbors
for (int i = 0; i < 4; i++) {
int cx, cy, cz;
if (axis == 0) {
cx = x;
cy = y + X_NEIGHBORS[i].x;
cz = z + X_NEIGHBORS[i].y;
} else if (axis == 1) {
cx = x + Y_NEIGHBORS[i].x;
cy = y;
cz = z + Y_NEIGHBORS[i].y;
} else {
cx = x + Z_NEIGHBORS[i].x;
cy = y + Z_NEIGHBORS[i].y;
cz = z;
}
// Bounds check: neighbor cell must be in [0, V-2] (QEF active region)
if (cx < 0 || cy < 0 || cz < 0 || cx > V - 2 || cy > V - 2 || cz > V - 2) {
cell_vi[i] = 0u;
continue;
}
int cell_idx = cz * V * V + cy * V + cx;
// Active check: cell must have a QEF vertex
if (voxel_flags[cell_idx] == 0u) {
cell_vi[i] = 0u;
continue;
}
cell_vi[i] = voxel_scan[cell_idx];
valid_mask |= (1u << uint(i));
nv++;
}
// Need at least 3 valid neighbors to form a triangle
if (nv < 3) return;
// Determine winding from density sign at edge starting corner.
// CPU convention: s0 = (density > 0) = corner bit set = outside/air.
// Must match edge_classify sign convention (d >= 0.0 = positive/outside).
float d = density[z * S * S + y * S + x];
bool s0 = (d >= 0.0); // must match edge_classify sign convention (>=)
if (nv == 4) {
// Full quad: emit 2 triangles.
// Choose split diagonal based on mesh_quality_flags:
// bit 0 set: split along shortest diagonal (better aspect ratio)
// bit 0 clear: always split along 0-3 diagonal (original behavior)
bool split_02 = false; // false = split 0-3, true = split 1-2
if ((mesh_quality_flags & 1u) != 0u) {
// Read vertex positions to compare diagonal lengths.
// Vertex layout: 12 floats per vertex (pos.xyz at offset 0-2).
vec3 p0 = vec3(vertices[cell_vi[0]*12u], vertices[cell_vi[0]*12u+1u], vertices[cell_vi[0]*12u+2u]);
vec3 p1 = vec3(vertices[cell_vi[1]*12u], vertices[cell_vi[1]*12u+1u], vertices[cell_vi[1]*12u+2u]);
vec3 p2 = vec3(vertices[cell_vi[2]*12u], vertices[cell_vi[2]*12u+1u], vertices[cell_vi[2]*12u+2u]);
vec3 p3 = vec3(vertices[cell_vi[3]*12u], vertices[cell_vi[3]*12u+1u], vertices[cell_vi[3]*12u+2u]);
float diag03_sq = dot(p3 - p0, p3 - p0);
float diag12_sq = dot(p2 - p1, p2 - p1);
split_02 = (diag12_sq < diag03_sq);
}
uint offset = atomicAdd(counts[1], 6u);
if (!split_02) {
// Split along 0-3 diagonal: triangles (0,1,3) + (0,3,2)
if (!s0) {
indices[offset + 0] = cell_vi[0];
indices[offset + 1] = cell_vi[1];
indices[offset + 2] = cell_vi[3];
indices[offset + 3] = cell_vi[0];
indices[offset + 4] = cell_vi[3];
indices[offset + 5] = cell_vi[2];
} else {
indices[offset + 0] = cell_vi[0];
indices[offset + 1] = cell_vi[3];
indices[offset + 2] = cell_vi[1];
indices[offset + 3] = cell_vi[0];
indices[offset + 4] = cell_vi[2];
indices[offset + 5] = cell_vi[3];
}
} else {
// Split along 1-2 diagonal: triangles (0,1,2) + (1,3,2)
if (!s0) {
indices[offset + 0] = cell_vi[0];
indices[offset + 1] = cell_vi[1];
indices[offset + 2] = cell_vi[2];
indices[offset + 3] = cell_vi[1];
indices[offset + 4] = cell_vi[3];
indices[offset + 5] = cell_vi[2];
} else {
indices[offset + 0] = cell_vi[0];
indices[offset + 1] = cell_vi[2];
indices[offset + 2] = cell_vi[1];
indices[offset + 3] = cell_vi[1];
indices[offset + 4] = cell_vi[2];
indices[offset + 5] = cell_vi[3];
}
}
} else {
// Degenerate quad (3 of 4 valid): emit single triangle.
// Collect the 3 valid vertex indices.
uint tri[3];
int t = 0;
for (int i = 0; i < 4; i++) {
if ((valid_mask & (1u << uint(i))) != 0u) {
tri[t] = cell_vi[i];
t++;
}
}
uint offset = atomicAdd(counts[1], 3u);
if (!s0) {
indices[offset + 0] = tri[0];
indices[offset + 1] = tri[1];
indices[offset + 2] = tri[2];
} else {
indices[offset + 0] = tri[0];
indices[offset + 1] = tri[2];
indices[offset + 2] = tri[1];
}
}
}
void main() {
uint gid = gl_GlobalInvocationID.x;
if (gid >= total_elements) return;
int V = grid_dims.x; // 16
// Decode linear voxel index to (x, y, z)
int z = int(gid) / (V * V);
int y = (int(gid) % (V * V)) / V;
int x = int(gid) % V;
// Check each of the 3 owned edges
try_emit_quad(x, y, z, 0); // +X edge
try_emit_quad(x, y, z, 1); // +Y edge
try_emit_quad(x, y, z, 2); // +Z edge
}