#version 450
// ============================================================================
// Stage 6: Voxel Classification
//
// For each voxel in the 32³ grid, check if it has ANY sign-change edge
// among its 12 edges (4 per axis shared with neighbors). If yes, this
// voxel is "active" and needs a QEF-solved vertex.
//
// Each voxel (x,y,z) owns 3 edges starting at it: +X, +Y, +Z.
// It also shares edges from neighbors:
// (x-1,y,z).X, (x,y-1,z).Y, (x,y,z-1).Z
// and the 6 remaining diagonal neighbors' edges.
//
// Simplified: just check the 12 edges of the cube [x,x+1]×[y,y+1]×[z,z+1].
//
// Input: edge_flags[32³ × 3]
// Output: voxel_flags[32³] — 1 if active, 0 otherwise
//
// Note: We check a (voxels-1)³ grid because the QEF vertex for voxel (x,y,z)
// sits inside the cube formed by corners (x,y,z) to (x+1,y+1,z+1), which
// requires edges up to (x+1,y+1,z+1) — only valid for x,y,z < voxels-1.
// Boundary voxels (x=voxels-1, y=voxels-1, z=voxels-1) are zeroed explicitly
// so no CPU buffer_update() clear is needed (Phase B).
//
// Dispatch: ceil(voxels³ / 256), 1, 1
// ============================================================================
#define THREADS 256
layout(local_size_x = THREADS, local_size_y = 1, local_size_z = 1) in;
layout(set = 0, binding = 1, std430) readonly buffer EdgeFlagsBuf { uint edge_flags[]; };
layout(set = 0, binding = 4, std430) writeonly buffer VoxelFlagsBuf { uint voxel_flags[]; };
layout(push_constant) uniform PushConstants {
vec3 cell_origin;
float voxel_size;
ivec3 grid_dims; // voxel grid dims (e.g. 16,16,16)
uint total_elements;
uint mesh_quality_flags;
uint _pc_pad0;
uint _pc_pad1;
uint _pc_pad2;
};
// Get edge flag for edge starting at (ex,ey,ez) in direction dir (0=X,1=Y,2=Z)
uint get_edge(int ex, int ey, int ez, int dir, int voxels) {
if (ex < 0 || ey < 0 || ez < 0 || ex >= voxels || ey >= voxels || ez >= voxels)
return 0u;
return edge_flags[(ez * voxels * voxels + ey * voxels + ex) * 3 + dir];
}
void main() {
uint gid = gl_GlobalInvocationID.x;
if (gid >= total_elements) return;
int voxels = grid_dims.x; // 16 (edge grid dimension)
int inner = voxels - 1; // 15 (range of valid QEF voxels)
// Decode linear index in full voxels³ grid
int z = int(gid) / (voxels * voxels);
int rem = int(gid) % (voxels * voxels);
int y = rem / voxels;
int x = rem % voxels;
// Boundary voxels: zero-fill (Phase B: eliminates CPU buffer_update clear)
if (x >= inner || y >= inner || z >= inner) {
voxel_flags[gid] = 0u;
return;
}
// Check all 12 edges of the cube [x..x+1, y..y+1, z..z+1]
// 4 X-axis edges
uint has_edge =
get_edge(x, y, z, 0, voxels) |
get_edge(x, y+1, z, 0, voxels) |
get_edge(x, y, z+1, 0, voxels) |
get_edge(x, y+1, z+1, 0, voxels) |
// 4 Y-axis edges
get_edge(x, y, z, 1, voxels) |
get_edge(x+1, y, z, 1, voxels) |
get_edge(x, y, z+1, 1, voxels) |
get_edge(x+1, y, z+1, 1, voxels) |
// 4 Z-axis edges
get_edge(x, y, z, 2, voxels) |
get_edge(x+1, y, z, 2, voxels) |
get_edge(x, y+1, z, 2, voxels) |
get_edge(x+1, y+1, z, 2, voxels);
// gid == z*voxels*voxels + y*voxels + x, so write directly
voxel_flags[gid] = (has_edge != 0u) ? 1u : 0u;
}