// BVLE Voxels - GPU Smooth Mesher Pass 1: Centroid + Solid Grid
// Computes centroid position + material for each surface cell and writes
// to a per-chunk centroid grid buffer (34^3 entries for cells [-1..32]).
//
// IMPORTANT: Also writes a "solid" flag for ALL cells (surface or not).
// The emit shader (pass 2) reads ONLY from this grid — no voxel buffer access.
// This makes the emit shader much simpler and faster to compile.
//
// Grid format (float4 per cell):
//   xyz = chunk-local position (only valid for surface cells)
//   w   = asfloat(packed):
//         bits [7:0]   = primaryMat
//         bits [15:8]  = secondaryMat
//         bits [23:16] = blendWeight
//         bit  24      = valid (has centroid, is surface cell)
//         bit  25      = solid (readVox != 0 at this cell position)
//
// Dispatch: 5x5x5 groups of 8x8x8 threads per chunk (covers 40^3, clipped to 34^3)

#include "voxelCommon.hlsli"

struct SmoothPush {
    uint chunkIndex;
    uint voxelBufferOffset;
    uint maxOutputVerts;
    uint centroidGridOffset;
    uint pad[8];
};
[[vk::push_constant]] ConstantBuffer<SmoothPush> push : register(b999);

StructuredBuffer<uint> voxelData : register(t0);
StructuredBuffer<GPUChunkInfo> chunkInfo : register(t1);
RWStructuredBuffer<float4> centroidGrid : register(u0);

static const uint CSIZE = 32;
static const uint GRID_DIM = 34;
static const uint WORDS_PER_CHUNK = CSIZE * CSIZE * CSIZE / 2;

static const int3 cornerOff[8] = {
    int3(0,0,0), int3(1,0,0), int3(0,1,0), int3(1,1,0),
    int3(0,0,1), int3(1,0,1), int3(0,1,1), int3(1,1,1)
};
static const float3 cornerOffF[8] = {
    float3(0,0,0), float3(1,0,0), float3(0,1,0), float3(1,1,0),
    float3(0,0,1), float3(1,0,1), float3(0,1,1), float3(1,1,1)
};
static const uint2 edgePairs[12] = {
    uint2(0,1), uint2(2,3), uint2(4,5), uint2(6,7),
    uint2(0,2), uint2(1,3), uint2(4,6), uint2(5,7),
    uint2(0,4), uint2(1,5), uint2(2,6), uint2(3,7)
};
static const int3 dirs6[6] = {
    int3(1,0,0), int3(-1,0,0), int3(0,1,0), int3(0,-1,0), int3(0,0,1), int3(0,0,-1)
};

// ── Voxel reading (with cross-chunk neighbor support) ───────────────
uint readVoxelAt(uint bufferOffset, uint flatIndex) {
    uint pairIndex = flatIndex >> 1;
    uint shift = (flatIndex & 1) * 16;
    return (voxelData[bufferOffset + pairIndex] >> shift) & 0xFFFF;
}

uint readVoxel(uint flatIndex) {
    return readVoxelAt(push.voxelBufferOffset, flatIndex);
}

uint readVox(int3 p) {
    int3 localP = p;
    int3 chunkOff = int3(0, 0, 0);
    if (p.x < 0)                { chunkOff.x = -1; localP.x += CSIZE; }
    else if (p.x >= (int)CSIZE) { chunkOff.x =  1; localP.x -= CSIZE; }
    if (p.y < 0)                { chunkOff.y = -1; localP.y += CSIZE; }
    else if (p.y >= (int)CSIZE) { chunkOff.y =  1; localP.y -= CSIZE; }
    if (p.z < 0)                { chunkOff.z = -1; localP.z += CSIZE; }
    else if (p.z >= (int)CSIZE) { chunkOff.z =  1; localP.z -= CSIZE; }

    if (chunkOff.x == 0 && chunkOff.y == 0 && chunkOff.z == 0) {
        uint fi = (uint)localP.x + (uint)localP.y * CSIZE + (uint)localP.z * CSIZE * CSIZE;
        return readVoxel(fi);
    }
    int axisCount = abs(chunkOff.x) + abs(chunkOff.y) + abs(chunkOff.z);
    if (axisCount > 1) return 0;

    uint neighborFace;
    if      (chunkOff.x > 0) neighborFace = 0;
    else if (chunkOff.x < 0) neighborFace = 1;
    else if (chunkOff.y > 0) neighborFace = 2;
    else if (chunkOff.y < 0) neighborFace = 3;
    else if (chunkOff.z > 0) neighborFace = 4;
    else                      neighborFace = 5;

    GPUChunkInfo ci = chunkInfo[push.chunkIndex];
    uint nIdx = getNeighborIdx(ci, neighborFace);
    if (nIdx == 0xFFFFFFFF) return 0;

    uint fi = (uint)localP.x + (uint)localP.y * CSIZE + (uint)localP.z * CSIZE * CSIZE;
    return readVoxelAt(nIdx * WORDS_PER_CHUNK, fi);
}

bool isSmooth(uint voxel) { return (voxel != 0) && ((voxel >> 4) & 0x1); }
uint getMatID(uint voxel) { return (voxel >> 8) & 0xFF; }

uint gridIndex(int3 cellPos) {
    return push.centroidGridOffset +
           (uint)(cellPos.z + 1) * GRID_DIM * GRID_DIM +
           (uint)(cellPos.y + 1) * GRID_DIM +
           (uint)(cellPos.x + 1);
}

// Flags byte (bits 24-31 of packed w):
//   bit 0 (24): valid centroid
//   bit 1 (25): cell is solid
static const uint FLAG_VALID = 1u;
static const uint FLAG_SOLID = 2u;

[RootSignature(VOXEL_ROOTSIG)]
[numthreads(8, 8, 8)]
void main(uint3 DTid : SV_DispatchThreadID)
{
    int3 cellPos = int3(DTid) - 1;
    if (any(cellPos > 32)) return;

    uint idx = gridIndex(cellPos);

    // Determine if cell center is solid (needed by emit shader for edge sign checks)
    uint voxAtCell = readVox(cellPos);
    uint solidFlag = (voxAtCell != 0) ? FLAG_SOLID : 0u;

    // Read SDF at 8 corners
    float corner[8];
    bool hasPos = false, hasNeg = false;
    bool hasSmoothFlag = false;

    [unroll]
    for (uint c = 0; c < 8; c++) {
        int3 cp = cellPos + cornerOff[c];
        uint vox = readVox(cp);
        corner[c] = (vox == 0) ? 1.0 : -1.0;
        if (corner[c] < 0.0) hasNeg = true;
        else hasPos = true;
        if (isSmooth(vox)) hasSmoothFlag = true;
    }

    // Not a surface cell → write only solid flag
    if (!hasPos || !hasNeg) {
        centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
        return;
    }

    // Must be near smooth voxels
    if (!hasSmoothFlag) {
        bool nearSmooth = false;
        [unroll]
        for (uint c = 0; c < 8 && !nearSmooth; c++) {
            int3 cp = cellPos + cornerOff[c];
            [unroll]
            for (uint d = 0; d < 6 && !nearSmooth; d++) {
                uint nv = readVox(cp + dirs6[d]);
                if (isSmooth(nv)) nearSmooth = true;
            }
        }
        if (!nearSmooth) {
            centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
            return;
        }
    }

    // Compute centroid from edge crossings
    float3 sum = float3(0, 0, 0);
    uint crossCount = 0;
    [unroll]
    for (uint e = 0; e < 12; e++) {
        float s0 = corner[edgePairs[e].x];
        float s1 = corner[edgePairs[e].y];
        if ((s0 < 0.0) == (s1 < 0.0)) continue;
        float t = clamp(s0 / (s0 - s1), 0.01, 0.99);
        sum += cornerOffF[edgePairs[e].x] + t * (cornerOffF[edgePairs[e].y] - cornerOffF[edgePairs[e].x]);
        crossCount++;
    }
    if (crossCount == 0) {
        centroidGrid[idx] = float4(0, 0, 0, asfloat(solidFlag << 24));
        return;
    }

    float3 cen = sum / (float)crossCount;

    // Boundary clamping
    [unroll]
    for (uint c = 0; c < 8; c++) {
        if (corner[c] >= 0.0) continue;
        int3 cp = cellPos + cornerOff[c];
        uint vox = readVox(cp);
        if (vox != 0 && !isSmooth(vox)) {
            if (cornerOff[c].x == 0) cen.x = max(cen.x, 0.5);
            else                     cen.x = min(cen.x, 0.5);
            if (cornerOff[c].y == 0) cen.y = max(cen.y, 0.5);
            else                     cen.y = min(cen.y, 0.5);
            if (cornerOff[c].z == 0) cen.z = max(cen.z, 0.5);
            else                     cen.z = min(cen.z, 0.5);
        }
    }

    float3 localPos = float3(cellPos) + float3(0.5, 0.5, 0.5) + cen;

    // Material determination
    uint matIDs[8];
    bool matIsSmooth[8];
    uint solidCount = 0;
    [unroll]
    for (uint cc = 0; cc < 8; cc++) {
        if (corner[cc] >= 0.0) continue;
        int3 cp = cellPos + cornerOff[cc];
        uint vox = readVox(cp);
        if (vox == 0) continue;
        matIDs[solidCount] = getMatID(vox);
        matIsSmooth[solidCount] = isSmooth(vox);
        solidCount++;
    }

    uint bestMat = 6, bestCount = 0, smoothSolidCount = 0;
    [unroll] for (uint i = 0; i < 8; i++) {
        if (i >= solidCount) break;
        if (matIsSmooth[i]) smoothSolidCount++;
    }
    [unroll] for (uint i = 0; i < 8; i++) {
        if (i >= solidCount) break;
        bool useSmooth = (smoothSolidCount > 0);
        if (useSmooth && !matIsSmooth[i]) continue;
        uint mat = matIDs[i];
        uint cnt = 0;
        [unroll] for (uint j = 0; j < 8; j++) {
            if (j >= solidCount) break;
            if (useSmooth && !matIsSmooth[j]) continue;
            if (matIDs[j] == mat) cnt++;
        }
        if (cnt > bestCount) { bestCount = cnt; bestMat = mat; }
    }

    uint secMat = bestMat, secCount = 0;
    [unroll] for (uint i = 0; i < 8; i++) {
        if (i >= solidCount) break;
        if (matIDs[i] == bestMat) continue;
        uint mat = matIDs[i];
        uint cnt = 0;
        [unroll] for (uint j = 0; j < 8; j++) {
            if (j >= solidCount) break;
            if (matIDs[j] == mat && mat != bestMat) cnt++;
        }
        if (cnt > secCount) { secCount = cnt; secMat = mat; }
    }

    uint blendW = (secCount > 0 && secMat != bestMat) ? 255 : 0;
    uint flags = FLAG_VALID | solidFlag;
    uint packed = (bestMat & 0xFF) | ((secMat & 0xFF) << 8) | ((blendW & 0xFF) << 16) | (flags << 24);
    centroidGrid[idx] = float4(localPos, asfloat(packed));
}
