

// Struct definitions (unchanged)
struct deferredMaterialData {
    vec3 baseColor;
    vec3 diffuse;
    vec3 specularReflectance;
    vec3 normal;
    vec3 vertex;
    float roughness;
    float metallic;
    float ambientOcclusion;
    float alpha;
    float clearcoat;
    float clearcoatRoughness;
#if ANISOTROPY == 1
    vec3 tangent;
#endif
#if TRANSLUCENT_MATERIAL == 1
    float translucency;
    float translucencyDistortion;
    float translucencyPower;
    float translucencyScale;
    float translucencyAmbient;
#endif
    float geometricRoughness;
};

struct deferredLightData {
    vec3 direction;
    float length;
    float radius;
    float inverseFalloff;
    int lightType;
    vec3 color;
};

// ————————— Optimized BRDF & utilities —————————

float D_GGX(float NoH, float a) {
    float a2 = a * a;
    float f = (NoH * a2 - NoH) * NoH + 1.0;
    return a2 / max(PI * f * f, 1e-5);
}

float D_GGX_Anisotropy(float NoH, vec3 h, vec3 x, vec3 y, float ax, float ay) {
    float XoH = dot(x, h), YoH = dot(y, h);
    float d = XoH * XoH * (ax*ax) + YoH * YoH * (ay*ay) + NoH * NoH;
    return (ax * ay) / max(PI * d * d, 1e-5);
}

vec3 F_Schlick(float VoH, vec3 F0, float f90) {
    float pow5 = pow(1.0 - VoH, 5.0);
    return F0 + (vec3(f90) - F0) * pow5;
}

float V_SmithGGXCorrelated(float NoV, float NoL, float a) {
    float a2 = a * a;
    float gv = sqrt(max((-NoV*a2 + NoV) * NoV + a2, 0.0));
    float gl = sqrt(max((-NoL*a2 + NoL) * NoL + a2, 0.0));
    return 0.5 / max(NoL * gv + NoV * gl, 1e-5);
}

float Fd_Lambert() {
    return 1.0 / PI;
}

float F_Schlick_Scalar(float VoH, float F0, float f90) {
    float pow5 = pow(1.0 - VoH, 5.0);
    return F0 + (f90 - F0) * pow5;
}

vec3 irradianceSH(vec3 n) {
    float nx = n.x, ny = n.y, nz = n.z;
    float nymnx = ny * nx, nynz = ny * nz, nz2 = nz * nz, nxx_nyy = nx*nx - ny*ny;
    return
        vec3(0.7545535, 0.7485417, 0.7909225)
        + vec3(-0.08385509, 0.09253634, 0.3227673) * ny
        + vec3(0.3081546, 0.3667994, 0.4667058) * nz
        + vec3(-0.1888876, -0.2774037, -0.3778448) * nx
        + vec3(-0.2527824, -0.3160516, -0.396141) * nymnx
        + vec3(0.07136245, 0.1597891, 0.2905936) * nynz
        + vec3(-0.03104042) * (3.0 * nz2 - 1.0)      // all three components identical
        + vec3(-0.1610019, -0.2036495, -0.246641) * (nz * nx)
        + vec3(0.04571093, 0.04812178, 0.04632638) * nxx_nyy;
}

vec2 prefilteredDFGKaris(float NoV, float roughness) {
    const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
    const vec4 c1 = vec4( 1.0,  0.0425,  1.040, -0.040);
    vec4 r = roughness * c0 + c1;
    float expTerm = exp2(-9.28 * NoV);
    float a004 = min(r.x * r.x, expTerm) * r.x + r.y;
    return vec2(-1.04, 1.04) * a004 + r.zw;
}

vec3 decodeEnvironmentMap(vec4 c) {
    return c.rgb;  // assume linear
}

float getSquareFalloffAttenuation(float dist2, float invFalloff) {
    float sf = max(1.0 - (dist2 * invFalloff) * (dist2 * invFalloff), 0.0);
    return sf * sf;
}

vec3 fixCubemapLookup(vec3 v, float lod) {
    vec3 a = abs(v);
    float M = max(max(a.x, a.y), a.z);
    float sc = 1.0 - exp2(lod) * (1.0/256.0);
    if (a.x != M) v.x *= sc;
    if (a.y != M) v.y *= sc;
    if (a.z != M) v.z *= sc;
    return v;
}

vec3 evaluateSpecularIBL(vec3 r, float roughness) {
    float lod = 5.0 * roughness;
    return decodeEnvironmentMap(textureLod(reflectionSampler, fixCubemapLookup(r, lod), lod)) * 1.8;
}

float computeSpecularao(float NoV, float ao, float roughness) {
    float powe = exp2(-16.0 * roughness - 1.0);
    return clamp(pow(NoV + ao, powe) - 1.0 + ao, 0.0, 1.0);
}

vec3 getSpecularDominantDirection(vec3 n, vec3 r, float roughness2) {
    float s = 1.0 - roughness2;
    return mix(n, r, s * (sqrt(s) + sqrt(roughness2)));
}

// ————————— Lighting functions —————————

vec3 indirectLight(in deferredMaterialData m) {
    float rough2 = m.roughness * m.roughness;
    float NoV = max(dot(m.normal, m.vertex), 0.0);
    vec3 r = reflect(-m.vertex, m.normal);
    r = getSpecularDominantDirection(m.normal, r, rough2);

    vec3 diff = m.diffuse * irradianceSH(m.normal) * (1.0 - m.metallic) * m.ambientOcclusion;
    vec3 spec = evaluateSpecularIBL(r, m.roughness)
              * computeSpecularao(NoV, m.ambientOcclusion, m.roughness);
    return diff + spec;
}

vec3 directLight(in deferredLightData light, in deferredMaterialData m) {
    vec3 n = m.normal;
    vec3 v = m.vertex;

    vec3 L = normalize(-light.direction);
    float att = 1.0;
    if (light.lightType == 1) {
        float Nl = max(dot(n, L), 0.0);
        att = smoothstep(0.9999, 1.0, Nl);
    }

    float NoL = max(dot(n, L), 0.0), NoV = max(dot(n, v), 0.0);
    vec3 diff = m.diffuse * NoL;

    vec3 H = normalize(L + v);
    float NoH = max(dot(n, H), 0.0), VoH = max(dot(v, H), 0.0);

#if ANISOTROPY == 1
    vec3 t = normalize(m.tangent);
    vec3 b = normalize(cross(t, n));
    float ax = sqrt(m.roughness);
    float ay = ax;
    float D = D_GGX_Anisotropy(NoH, H, t, b, ax, ay);
#else
    float D = D_GGX(NoH, m.roughness * m.roughness);
#endif

    float V = V_SmithGGXCorrelated(NoV, NoL, m.roughness * m.roughness);
    vec3 F = F_Schlick(VoH, m.specularReflectance, 1.0);
    vec3 spec = (D * V) * F / max(4.0 * NoV * NoL, 1e-5);

    if (m.clearcoat > 0.0) {
        float Cr2 = m.clearcoatRoughness * m.clearcoatRoughness;
        float Dc = D_GGX(NoH, Cr2);
        float Vc = V_SmithGGXCorrelated(NoV, NoL, Cr2);
        float Fc = F_Schlick_Scalar(VoH, 0.04, 1.0);
        spec += m.clearcoat * (Dc * Vc * Fc / max(4.0 * NoV * NoL, 1e-5));
    }

#if TRANSLUCENT_MATERIAL == 1
    vec3 dL = normalize(L + m.translucencyDistortion * vec3(0.1));
    float Nt = max(dot(n, dL), 0.0);
    float trs = pow(Nt, m.translucencyPower) * m.translucencyScale;
    trs = clamp(trs + m.translucencyAmbient, 0.0, 1.0);
    diff += m.translucency * trs * light.color;
#endif

    return att * light.color * (diff * (1.0 - m.metallic) + spec);
}

// ————————— Main shading entry —————————

vec4 physically_based_shading(deferredLightData light, deferredMaterialData m) {
    m.diffuse = m.baseColor * (1.0 - m.metallic);
    m.specularReflectance = mix(vec3(0.04), m.baseColor, m.metallic);
    m.geometricRoughness = m.roughness * m.roughness;
    m.vertex = normalize(cameraPosition - m.vertex);

    vec3 color = indirectLight(m) + directLight(light, m);
    return vec4(color, m.alpha);
}
