#version 450 core

/**
Maximum number of lights per cluster
*/
#define MAX_LIGHTS_PER_CLUSTER 100

/**
The number of "threads" to run
*/
#define CULL_LOCAL_SIZE 128

/**
Bind points for different SSBOs
*/
#define CLUSTER_SSBO_BIND_POINT 1
#define POINT_LIGHT_SSBO_BIND_POINT 2
#define DIRECT_LIGHT_SSBO_BIND_POINT 3


layout(local_size_x = CULL_LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in;

struct PointLight {
    vec4 position;
    vec4 color;
    float constant;
    float linear;
    float quadratic;
    float radius;
};

struct Cluster {
    vec4 minPoint;
    vec4 maxPoint;
    uint count;
    uint lightIndices[MAX_LIGHTS_PER_CLUSTER];
};

layout(std430, binding = CLUSTER_SSBO_BIND_POINT) restrict buffer clusterSSBO {
    Cluster clusters[];
};

layout(std430, binding = POINT_LIGHT_SSBO_BIND_POINT) restrict buffer lightSSBO {
    PointLight pointLight[];
};

uniform mat4 viewMatrix;

/**
 * Number of lights in the scene
 */
uniform int lightCount;

bool testSphereAABB(uint i, Cluster c);

// each invocation of main() is a thread processing a cluster
void main() {
    uint index = gl_WorkGroupID.x * CULL_LOCAL_SIZE + gl_LocalInvocationID.x;
    Cluster cluster = clusters[index];

    // we need to reset count because culling runs every frame.
    // otherwise it would accumulate.
    cluster.count = 0;

    for (uint i = 0; i < lightCount; ++i){
        if (testSphereAABB(i, cluster) && cluster.count < 100){
            cluster.lightIndices[cluster.count] = i;
            cluster.count++;
        }
    }
    clusters[index] = cluster;
}

bool sphereAABBIntersection(vec3 center, float radius, vec3 aabbMin, vec3 aabbMax) {
    // closest point on the AABB to the sphere center
    vec3 closestPoint = clamp(center, aabbMin, aabbMax);
    // squared distance between the sphere center and closest point
    float distanceSquared = dot(closestPoint - center, closestPoint - center);
    return distanceSquared <= radius * radius;
}

// this just unpacks data for sphereAABBIntersection
bool testSphereAABB(uint i, Cluster cluster) {
    vec3 center = vec3(viewMatrix * pointLight[i].position);
    float radius = pointLight[i].radius;

    vec3 aabbMin = cluster.minPoint.xyz;
    vec3 aabbMax = cluster.maxPoint.xyz;

    return sphereAABBIntersection(center, radius, aabbMin, aabbMax);
}