#version 450 core
// Purpose: Shared prototype lit-model fragment shader; applies texture/color material, directional light, point light, and shadowing.

in vec3 vWorldPos;
in vec3 vWorldNormal;
in vec2 vUv;
in vec4 vLightClipPos;

uniform sampler2D uBaseColorTex;
uniform vec4 uBaseColorFactor;
uniform int uHasTexture;

uniform vec3 uCameraPos;

uniform vec3 uDirLightDirection;
uniform vec3 uDirLightColor;
uniform float uDirLightIntensity;

uniform vec3 uPointLightPosition;
uniform vec3 uPointLightColor;
uniform float uPointLightIntensity;
uniform float uPointLightRange;

uniform sampler2D uShadowMap;

out vec4 FragColor;

float ComputeShadowPCF(vec4 lightClipPos, vec3 normal, vec3 lightDir)
{
    vec3 proj = lightClipPos.xyz / lightClipPos.w;
    proj = proj * 0.5 + 0.5;

    if (proj.x < 0.0 || proj.x > 1.0 ||
    proj.y < 0.0 || proj.y > 1.0 ||
    proj.z < 0.0 || proj.z > 1.0)
    {
        return 1.0;
    }

    float currentDepth = proj.z;
    float bias = max(0.0008, 0.003 * (1.0 - max(dot(normal, lightDir), 0.0)));

    vec2 texelSize = 1.0 / vec2(textureSize(uShadowMap, 0));

    float lit = 0.0;
    for (int y = -1; y <= 1; ++y)
    {
        for (int x = -1; x <= 1; ++x)
        {
            float sampledDepth = texture(uShadowMap, proj.xy + vec2(x, y) * texelSize).r;
            lit += ((currentDepth - bias) <= sampledDepth) ? 1.0 : 0.0;
        }
    }

    return lit / 9.0;
}

void main()
{
    vec4 albedo4 = uBaseColorFactor;
    if (uHasTexture != 0)
    albedo4 *= texture(uBaseColorTex, vUv);

    vec3 albedo = albedo4.rgb;
    vec3 N = normalize(vWorldNormal);
    vec3 V = normalize(uCameraPos - vWorldPos);

    vec3 ambient = 0.08 * albedo;

    vec3 Ld = normalize(-uDirLightDirection);
    float ndl = max(dot(N, Ld), 0.0);
    float shadow = ComputeShadowPCF(vLightClipPos, N, Ld);

    vec3 dirDiffuse = albedo * uDirLightColor * (uDirLightIntensity * ndl * shadow);

    vec3 Hd = normalize(Ld + V);
    float ndh = max(dot(N, Hd), 0.0);
    float dirSpec = pow(ndh, 32.0) * step(0.0001, ndl);
    vec3 dirSpecular = uDirLightColor * (0.15 * uDirLightIntensity * dirSpec * shadow);

    vec3 LpVec = uPointLightPosition - vWorldPos;
    float dist = length(LpVec);
    vec3 Lp = (dist > 0.0001) ? (LpVec / dist) : vec3(0.0, 1.0, 0.0);

    float atten = clamp(1.0 - dist / uPointLightRange, 0.0, 1.0);
    atten *= atten;

    float npl = max(dot(N, Lp), 0.0);
    vec3 pointDiffuse = albedo * uPointLightColor * (uPointLightIntensity * npl * atten);

    vec3 Hp = normalize(Lp + V);
    float nph = max(dot(N, Hp), 0.0);
    float pointSpec = pow(nph, 24.0) * step(0.0001, npl);
    vec3 pointSpecular = uPointLightColor * (0.12 * uPointLightIntensity * pointSpec * atten);

    vec3 color = ambient + dirDiffuse + dirSpecular + pointDiffuse + pointSpecular;
    FragColor = vec4(color, albedo4.a);
}