#version 450
// ============================================================================
// Plane-set BAKE pass (the SDF plane-set brick).
//
// For every cell, capture the exact convex-brush plane-set (face/edge/corner)
// from the brush geometry and store it, so qef_solve READS the baked planes
// instead of re-deriving them per remesh. This is the mutability substrate:
// settled architecture is baked once; a remesh of a settled region does no
// O(edits) capture. (Re-bake-on-edit / persistence = a later increment; this
// pass currently bakes per mesh, moving the capture out of the hot QEF stage.)
//
// The capture logic is byte-for-byte the qef_solve Step-5b capture:
// - faces within T of the cell centre,
// - cell inside all OTHER planes of the SAME brush (onbrush),
// - csg4 cross-brush HIDDEN-FACE removal (a face is real only if a point just
// outside it lies outside every other brush),
// - dedup by normal (|dot|>0.999) into up to 3 planes,
// - material from the nearest face.
//
// Dispatch: ceil(grid_dims³ / 256), 1, 1 (per cell; inactive cells → cl_count 0)
// ============================================================================
#define THREADS 256
layout(local_size_x = THREADS, local_size_y = 1, local_size_z = 1) in;
struct GpuEdit {
uint edit_type;
float pos_x, pos_y, pos_z;
float half_x, half_y, half_z;
float radius;
float strength;
uint _pad0, _pad1, _pad2; // _pad0=material_id, _pad1=plane offset, _pad2=count
};
layout(set = 0, binding = 9, std430) readonly buffer EditBuf {
uint edit_count;
uint _edit_pad0, _edit_pad1, _edit_pad2;
GpuEdit gpu_edits[];
};
layout(set = 0, binding = 10, std430) readonly buffer ConvexPlaneBuf { vec4 convex_planes[]; };
// Per-face projection index, parallel to convex_planes (1-based; 0 = none). Stage-2 texturing.
layout(set = 0, binding = 12, std430) readonly buffer FaceProjBuf { uint convex_faceproj[]; };
// Per-cell baked plane-set. 64 bytes/cell (std430, 16-byte aligned).
struct CellPlaneSet {
uint cl_count; // 0..3
uint material; // material_id of the nearest face
uint proj0; // projection index of the cell's 1st captured face (0 = none)
uint proj1; // ...and its 2nd (a crease's other face; 0 if single-face). The shader
// picks between them per-fragment by normal, killing the edge-strip
// stretch where one face's projection bled across a sharp crease.
vec4 planes[3]; // (nx, ny, nz, d) — n·p = d on the face
};
layout(set = 0, binding = 11, std430) writeonly buffer PlaneSetBuf { CellPlaneSet cell_planeset[]; };
layout(push_constant) uniform PushConstants {
vec3 cell_origin;
float voxel_size;
ivec3 grid_dims;
uint total_elements;
uint mesh_quality_flags;
uint _pc_pad0;
uint _pc_pad1;
uint _pc_pad2;
};
void main() {
uint gid = gl_GlobalInvocationID.x;
if (gid >= total_elements) return;
int V = grid_dims.x;
int x = int(gid % uint(V));
int y = int((gid / uint(V)) % uint(V));
int z = int(gid / uint(V * V));
vec3 cell_ctr = cell_origin + (vec3(float(x), float(y), float(z)) + 0.5) * voxel_size;
vec3 cl_n[3];
float cl_d[3];
uint cl_proj[3]; // per-captured-face projection index (parallel to cl_n)
int cl_count = 0;
uint cell_material = 0u; // material of the DOMINANT brush (smallest union-SDF at the
float best_sdf = 1e30; // cell centre) = the outermost surface the cell is on. Picking
float T = voxel_size * 1.2; // by nearest-plane / first-captured bled coplanar merges.
for (uint ei = 0u; ei < edit_count; ei++) {
GpuEdit ce = gpu_edits[ei];
if (ce.edit_type != 8u && ce.edit_type != 9u) continue; // convex brushes only
vec3 dcv = cell_ctr - vec3(ce.pos_x, ce.pos_y, ce.pos_z);
float rr = ce.radius + T;
if (dot(dcv, dcv) > rr * rr) continue;
uint poff = ce._pad1;
uint pcnt = ce._pad2;
bool contributed = false; // did this brush contribute a real (onbrush, unburied) face?
for (uint pi = 0u; pi < pcnt; pi++) {
vec4 pl = convex_planes[poff + pi];
float fd = pl.x * cell_ctr.x + pl.y * cell_ctr.y + pl.z * cell_ctr.z - pl.w;
if (abs(fd) >= T) continue;
bool onbrush = true; // cell inside this brush's OTHER planes (~T)
for (uint pj = 0u; pj < pcnt; pj++) {
if (pj == pi) continue;
vec4 q = convex_planes[poff + pj];
if (q.x * cell_ctr.x + q.y * cell_ctr.y + q.z * cell_ctr.z - q.w > T) { onbrush = false; break; }
}
if (!onbrush) continue;
// csg4 cross-brush hidden-face removal
vec3 fpt = cell_ctr - pl.xyz * fd;
vec3 qp = fpt + pl.xyz * (voxel_size * 0.5);
bool buried = false;
for (uint ej = 0u; ej < edit_count && !buried; ej++) {
if (ej == ei) continue;
GpuEdit ce2 = gpu_edits[ej];
if (ce2.edit_type != 8u && ce2.edit_type != 9u) continue;
vec3 dcv2 = qp - vec3(ce2.pos_x, ce2.pos_y, ce2.pos_z);
if (dot(dcv2, dcv2) > ce2.radius * ce2.radius) continue;
uint po2 = ce2._pad1, pc2 = ce2._pad2;
float mx = -1e30;
for (uint pk = 0u; pk < pc2; pk++) {
vec4 pl2 = convex_planes[po2 + pk];
mx = max(mx, pl2.x * qp.x + pl2.y * qp.y + pl2.z * qp.z - pl2.w);
}
// Require GENUINE burial (>1 voxel inside the neighbor), not merely abutting. The
// outward point is pushed 0.5 voxel out; abutting arch wedges land it barely inside a
// neighbor (mx just <0) and wrongly hid the VISIBLE diagonal faces. A truly hidden face
// sits far deeper. Oracle (START.MAP): diagonal capture 86%% -> 99.7%%; material-bleed
// guard (tests/test_material_merge) unchanged.
if (mx < -voxel_size) buried = true;
}
if (buried) continue;
contributed = true;
int matched = -1; // merge only SAME-facing planes — NOT
for (int c = 0; c < cl_count; c++) // anti-parallel (abs()), which collapsed
if (dot(pl.xyz, cl_n[c]) > 0.999) { matched = c; break; } // a thin slab's two
// faces into one → one side stair-stepped.
// Oracle: tests/test_thin_slab.cpp.
if (matched < 0 && cl_count < 3) { // new face → capture its plane + projection
cl_n[cl_count] = pl.xyz; cl_d[cl_count] = pl.w;
cl_proj[cl_count] = convex_faceproj[poff + pi];
cl_count++;
}
}
// Material = the brush the cell is most INSIDE (smallest union-SDF). Restricted to
// brushes that actually contribute a face here, so an adjacent/overlapping brush
// with a COPLANAR face no longer steals the colour. Validated on CPU:
// tests/test_material_merge.cpp (adjacent bleed 1->0, overlap 6->4-in-ambiguous-band).
if (contributed) {
float bs = -1e30;
for (uint pk = 0u; pk < pcnt; pk++) {
vec4 plk = convex_planes[poff + pk];
bs = max(bs, plk.x * cell_ctr.x + plk.y * cell_ctr.y + plk.z * cell_ctr.z - plk.w);
}
if (bs < best_sdf) { best_sdf = bs; cell_material = ce._pad0; }
}
}
CellPlaneSet ps;
ps.cl_count = uint(cl_count);
ps.material = cell_material; // dominant-brush material (see loop above)
// Two candidate face projections (Stage-2). The fragment shader picks between them by
// matching each face's table-derived normal to the pixel normal, so a sharp crease's
// two faces each texture correctly instead of one bleeding across the edge.
ps.proj0 = (cl_count > 0) ? cl_proj[0] : 0u;
ps.proj1 = (cl_count > 1) ? cl_proj[1] : 0u;
ps.planes[0] = (cl_count > 0) ? vec4(cl_n[0], cl_d[0]) : vec4(0.0);
ps.planes[1] = (cl_count > 1) ? vec4(cl_n[1], cl_d[1]) : vec4(0.0);
ps.planes[2] = (cl_count > 2) ? vec4(cl_n[2], cl_d[2]) : vec4(0.0);
cell_planeset[gid] = ps;
}