#include "/lib/fog/fog_common.glsl"
#include "/lib/fog/fogloop.glsl"
vec3 shadowSpace0()
{
    vec3 p3 = gbufferModelViewInverse[3].xyz;
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;
    return p3;
}
vec3 blendTranslucencies(vec3 sceneColor, vec4 translucents)
{
    return (translucents.rgb * translucents.a) + (sceneColor.rgb * (1.0 - translucents.a));
}
float GetBorderFogMixFactor(in vec3 eyePlayerPos, in float far, in float z0)
{

    float eyeDist = length(eyePlayerPos);
    float borderFogFactor = smoothstep(far * 0.5, far, eyeDist);
    borderFogFactor *= borderFogFactor * borderFogFactor;
    return (eyeDist > far * 0.5) ? borderFogFactor : 0.0;
}

vec3 getVolumetricRays3(
    vec3 solid,
    vec4 translucent,
    inout vec3 sky,
    vec3 fragpos,
    vec3 fragpos0,
    bool iswater)
{
    // --- Precompute eye brightness and depth parameters ---
    float eyeBright = clamp(eyeBrightnessSmooth.y / 255.0, 0.0, 1.0);
    // estEyeDark is identical to eyeBright; use eyeBright directly.
    float estEyeDepth = clamp((14.0 - eyeBright * 16.0) / 14.0, 0.0, 1.0);
    // Replace pow(estEyeDepth, 3.0) with multiplication.
    estEyeDepth = estEyeDepth * estEyeDepth * estEyeDepth * 32.0;

    // --- Transform positions from view to world space ---
    // If gbufferModelViewInverse is constant for the pass, consider caching invMat and trans externally.
    mat3 invMat = mat3(gbufferModelViewInverse);
    vec3 trans = gbufferModelViewInverse[3].xyz;
    vec3 p31 = invMat * fragpos + trans;
    vec3 p30 = invMat * fragpos0 + trans;

    // --- Compute distance and directional parameters ---
    float lenPos = length(fragpos);
    float lenPos0 = length(fragpos0);
    float Vdiff = distance(fragpos, fragpos0);
    // Instead of normalizing the full vector, compute the y component of the normalized p31.
    float VdotU = abs(p31.y / max(length(p31), 1e-8));
    float estimatedDepth_a = Vdiff * mix(1.0, VdotU, eyeBright);

    // --- Determine water status ---
    // isEyeInWater is assumed to be a global variable (likely an int/uniform).
    bool eyeInWater = (isEyeInWater == 1);

    // --- Initialize volumetric variables ---
    vec3 absorbance_w = vec3(1.0);
    vec3 vL_a = vec3(0.0);
    vec3 vL_w = vec3(0.0);
    float density = 0.0, density2 = 0.0;

    // --- Shadow-space and sun vector calculations ---
    vec3 start = shadowSpace0();
    vec3 end = toShadowSpaceProjected(fragpos);
    vec3 refractedSunVec = refract(lightPos, vec3(0.0, -1.0, 0.0), 0.9);

    // --- Compute initial ray lengths ---
    float rayLength = lenPos;
    float rayLength0 = lenPos0;
    vec3 dV = end - start;
    // Reuse the computed world-space position: note that p31 = invMat*fragpos + trans so (p31 - trans) equals invMat*fragpos.
    vec3 dVWorld = p31 - trans;

    // --- Environment-dependent parameters ---
    vec3 ambient, sunColor;
    float fogAmount;
#ifdef NETHER
    ambient = vec3(fogColor);
    sunColor = vec3(fogColor);
    fogAmount = 8.0;
#elif defined(END)
    ambient = vec3(fogColor) * 0.25;
    sunColor = vec3(fogColor);
    fogAmount = 5.0;
#elif defined(OVERWORLD)
    ambient = atmosphereUp;
    sunColor = sunLight;
    fogAmount = getFogDensity();
#else
    ambient = vec3(0.0);
    sunColor = vec3(0.0);
    fogAmount = 0.0;
#endif

    float maxZ = min(rayLength, 32.0) / (rayLength + 1e-8);

#ifdef OVERWORLD
    // --- Underwater and surface adjustments for ray lengths ---
    if (eyeInWater)
    {
        rayLength = lenPos * maxZ;
        rayLength0 = lenPos0 * maxZ;
    }
    else
    {
        float gbfFrag = gbf(fragpos, far, 0);
        float gbfFrag0 = gbf(fragpos0, far, 0);
        rayLength = gbfFrag * 1028.0;
        rayLength0 = gbfFrag0 * 1028.0;
        if (iswater)
        {
            rayLength = mix(estimatedDepth_a, 14.0, gbfFrag0);
            rayLength0 = rayLength;
        }
    }
#endif

    // --- Choose the effective ray length ---
    // When iswater is true, choose rayLength; otherwise, choose rayLength0.
    float rayLength2 = iswater ? rayLength : rayLength0;

    // --- Phase functions for scattering ---
    // Manually compute normalized y component of dVWorld.
    float norm_dVWorld = length(dVWorld);
    float dY = (norm_dVWorld > 0.0 ? dVWorld.y / norm_dVWorld : 0.0) * rayLength;
    float sh0 = 1.0;

#ifdef VOLUMETRIC_LIGHTING
    AtmosphereLoop(
        rayLength2,
        start,
        dV * maxZ,
        dY,
        estEyeDepth,
        refractedSunVec,
        ambient,
        sunColor,
        sh0,
        absorbance_w,
        vL_w,
        density,
        p31,
        fogAmount);
#else
    waterNoLoop(
        rayLength2,
        dY,
        estEyeDepth,
        refractedSunVec,
        ambient,
        sunColor,
        absorbance_w,
        vL_w,
        p31);
    density = fogAmount;
#endif

#ifdef VL_NOISE_DENSITY
    density2 = density * 256.0 + fogAmount * 6.0;
#else
    density2 = fogAmount * 6.0;
    density = 0.0;
#endif

    vec3 transmittance; // Updated by fogAtmosphereALT2.

    // --- Atmospheric lighting calculations ---
    float rayLengthGround0 = pow(lenPos0, FOG_FALLOFF) * density2;
    float rayLengthGround1 = pow(lenPos, FOG_FALLOFF) * density2;
    vec3 vL_a0 = fogAtmosphereALT2(p30, rayLengthGround0, rayLengthGround0, transmittance, density * 0.5, sh0);
    vec3 vL_a1 = fogAtmosphereALT2(p31, rayLengthGround1, rayLengthGround1, transmittance, density * 0.5, sh0);

    // --- Sky blending for non-deep underwater views ---
    // Using the global isEyeInWater to check for shallow water conditions.
    if (isEyeInWater <= 1)
    {
        applySkyBlend2(vL_a0, sky, p30, far);
        applySkyBlend2(vL_a1, sky, p31, far);
    }

#ifdef VL_NOISE_DENSITY
    vL_a = (!iswater || eyeInWater) ? mix(vL_a0, vL_a1, translucent.a) : vL_a1;
#else
    vL_a = (!iswater || eyeInWater) ? mix(vL_a0, vL_a1, translucent.a) * sh0 : vL_a1;
#endif

    // Apply brightness adjustments.
    vL_a *= eyeBright;
    if (!eyeInWater)
        vL_w *= eyeBright;

    // --- Final water and sky effects ---
    applyWaterEffects(
        solid,
        translucent,
        sky,
        vL_w,
        vL_a,
        absorbance_w,
        transmittance,
        fogColor,
        fragpos,
        p30,
        isEyeInWater,
        iswater);

    if (isEyeInWater <= 1)
        applySkyBlend(solid, translucent, sky, vL_w, p31, p30, far, iswater);

    // --- Final color composition ---
    return blendTranslucencies(solid, translucent);
}
