#version 430 core

in layout(location = 0) vec3 normal;
in layout(location = 1) vec2 textureCoordinates;
in layout(location = 2) vec3 worldPosition;

struct LightSource {
    vec3 position;
    vec3 color;
};

uniform LightSource lights[3];
uniform vec3 cameraPosition;
uniform vec3 ballPosition;

out vec4 fragColor;

const vec3 baseColor = vec3(1.0);
const float ambientStrength = 0.1;
const float specularStrength = 0.5;
const float shininess = 32.0;

// attenuation: 1 / (la + lb*d + lc*d²)
const float la = 0.1;
const float lb = 0.01;
const float lc = 0.001;

// soft shadow radii
const float ballRadius = 1.0;
const float hardRadius = ballRadius;
const float softRadius = ballRadius * 2.0;

float rand(vec2 co) {
    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}
float dither(vec2 uv) {
    return (rand(uv) * 2.0 - 1.0) / 256.0;
}

void main()
{
    vec3 N = normalize(normal);
    vec3 V = normalize(cameraPosition - worldPosition);

    // start with ambience
    vec3 result = ambientStrength * baseColor;

    for (int i = 0; i < 3; i++) {
        vec3 toLight = lights[i].position - worldPosition;
        vec3 L = normalize(toLight);
        float dist = length(toLight);

        float attenuation = 1.0 / (la + lb * dist + lc * dist * dist);

        float diff = max(dot(N, L), 0.0);
        vec3 diffuse = diff * lights[i].color;

        // blinn-phong half-vector
        vec3 H = normalize(L + V);
        float spec = pow(max(dot(N, H), 0.0), shininess);
        vec3 specular = specularStrength * spec * lights[i].color;

        vec3 toBall = ballPosition - worldPosition;
        float projDist = dot(toBall, L); // distance along light ray
        float perpDist = length(toBall - projDist * L); // perpendicular distance to ray
        bool inShadowCone = (projDist > 0.0) && (projDist < dist);

        float shadow = 1.0;
        // skip ball's own light (index 2) to avoid self-shadowing the whole scene
        if (inShadowCone) {
            if (perpDist <= hardRadius) {
                shadow = 0.0;
            } else if (perpDist < softRadius) {
                shadow = (perpDist - hardRadius) / (softRadius - hardRadius);
            }
        }

        result += shadow * attenuation * (diffuse + specular) * baseColor;
    }

    fragColor = vec4(result + dither(textureCoordinates), 1.0);
}
