// BVLE Voxels - Frustum + Backface Culling Compute Shader
// 1 thread per chunk: tests AABB vs 6 frustum planes, then emits up to 6 draws
// (one per visible face group, back-facing groups are culled).

#include "voxelCommon.hlsli"

StructuredBuffer<GPUChunkInfo> chunkInfoBuffer : register(t2);
RWStructuredBuffer<IndirectDrawArgsInstanced> indirectArgs : register(u0);
RWByteAddressBuffer drawCount : register(u1);

// Test AABB against 6 frustum planes (returns true if visible)
bool frustumTestAABB(float3 aabbMin, float3 aabbMax)
{
    [unroll]
    for (uint i = 0; i < 6; i++)
    {
        float4 plane = frustumPlanes[i];
        float3 pVertex;
        pVertex.x = (plane.x >= 0.0) ? aabbMax.x : aabbMin.x;
        pVertex.y = (plane.y >= 0.0) ? aabbMax.y : aabbMin.y;
        pVertex.z = (plane.z >= 0.0) ? aabbMax.z : aabbMin.z;

        if (dot(plane.xyz, pVertex) + plane.w < 0.0)
            return false;
    }
    return true;
}

// Face normals: +X, -X, +Y, -Y, +Z, -Z
static const float3 faceNormals[6] = {
    float3( 1, 0, 0), float3(-1, 0, 0),
    float3( 0, 1, 0), float3( 0,-1, 0),
    float3( 0, 0, 1), float3( 0, 0,-1)
};

[RootSignature(VOXEL_ROOTSIG)]
[numthreads(64, 1, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
    uint chunkIdx = DTid.x;
    if (chunkIdx >= chunkCount) return;

    GPUChunkInfo info = chunkInfoBuffer[chunkIdx];
    if (info.quadCount == 0) return;

    float3 aabbMin = info.worldPos.xyz;
    float3 aabbMax = aabbMin + (float3)chunkSize;

    if (!frustumTestAABB(aabbMin, aabbMax)) return;

    // Camera-to-chunk vector for backface test
    float3 chunkCenter = (aabbMin + aabbMax) * 0.5;
    float3 viewDir = chunkCenter - cameraPosition.xyz;

    // Emit one draw per visible face group
    [unroll]
    for (uint f = 0; f < 6; f++)
    {
        uint fCnt = getFaceCount(info, f);
        if (fCnt == 0) continue;

        // Backface cull: if camera sees the back of this face group, skip it.
        // A face group with normal N is back-facing if dot(viewDir, N) > 0.
        // But we need a per-face test relative to the chunk AABB, not just center:
        //   face +X: back-facing if camera.x < aabbMin.x (camera is on -X side)
        //   face -X: back-facing if camera.x > aabbMax.x (camera is on +X side)
        // This is more conservative and correct than dot product with center.
        bool backFacing = false;
        switch (f)
        {
        case 0: backFacing = (cameraPosition.x < aabbMin.x); break; // +X
        case 1: backFacing = (cameraPosition.x > aabbMax.x); break; // -X
        case 2: backFacing = (cameraPosition.y < aabbMin.y); break; // +Y
        case 3: backFacing = (cameraPosition.y > aabbMax.y); break; // -Y
        case 4: backFacing = (cameraPosition.z < aabbMin.z); break; // +Z
        case 5: backFacing = (cameraPosition.z > aabbMax.z); break; // -Z
        }
        if (backFacing) continue;

        uint drawIdx;
        drawCount.InterlockedAdd(0, 1, drawIdx);

        // Pack chunkIndex (low 16 bits) + faceIndex (high 16 bits) into push constant.
        // The VS unpacks this to look up quadOffset from GPUChunkInfo.
        // startVertexLocation = 0 because SV_VertexID does not reliably include it
        // with ExecuteIndirect (see CLAUDE.md point 8).
        IndirectDrawArgsInstanced args;
        args.pushConstant = chunkIdx | (f << 16);
        args.vertexCountPerInstance = fCnt * 6;
        args.instanceCount = 1;
        args.startVertexLocation = 0;
        args.startInstanceLocation = 0;
        indirectArgs[drawIdx] = args;
    }
}
