

float getFogDensity()
{

    float timeOfDay = mod((worldTime + 6000) / 1000.0, 24.0);
    float dawnFog = DAWN_PEAK_AMOUNT * exp(-DAWN_DECAY_RATE * pow(timeOfDay - DAWN_PEAK_TIME, 2));
    float duskFog = DUSK_PEAK_AMOUNT * exp(-DUSK_DECAY_RATE * pow(timeOfDay - DUSK_PEAK_TIME, 2));
    float midnightFog = MIDNIGHT_PEAK_AMOUNT * exp(-MIDNIGHT_DECAY_RATE * min(timeOfDay*timeOfDay, pow(timeOfDay - 24.0, 2)));
    float dayFog = DAYTIME_PEAK_AMOUNT * exp(-DAYTIME_DECAY_RATE * pow(timeOfDay - 12.0, 2)); // Noon is 12 PM
    float fogDensity = dawnFog + duskFog + midnightFog + dayFog;

    return fogDensity;
}


float phaseRayleigh(float cosTheta)
{
    return cosTheta * 0.18 + 0.51;
}

float phaseg(float x, float g)
{
    float gg = g * g;
    return (gg * -0.25 + 0.25) * pow(-2.0 * (g * x) + (gg + 1.0), -1.5) / PI;
}

#include "/lib/sky_function.glsl"

// const vec3 totEpsilon = vec3(0.69, 0.15, 0.18);

// const vec3 dirtScatterCoef = 0.1 * vec3(0.2, 0.6, 0.8);

const vec3 totEpsilon = vec3(0.69, 0.15, 0.18) * WATER_FOG_MOD;
const vec3 dirtScatterCoef = 0.1 * vec3(0.2, 0.6, 0.8) * WATER_FOG_MOD;

const vec3 water_coefficientRayleigh = vec3(1.0 * 1e-6, 2.5 * 1e-6, 3.5 * 1e-6);

float gbf(in vec3 eyePlayerPos, in float far, in float z0)
{
    float distXZ = length((vec3(eyePlayerPos.x, 0.0, eyePlayerPos.z)).xyz);
    float distY = length((vec3(0.0, eyePlayerPos.y, 0.0)).xyz);
    float eyeDist = max(distXZ, distY);
#ifdef DISTANT_HORIZONS
    float borderFogFactor = smoothstep(0.0, dhRenderDistance, eyeDist);
    borderFogFactor *= borderFogFactor;
    return borderFogFactor;
#else

    float borderFogFactor = smoothstep(far * 0.6, far, eyeDist);
    borderFogFactor *= borderFogFactor;

    return borderFogFactor;

#endif
}

float waterCaustics(vec3 wPos, vec3 lightSource)
{

    vec2 pos = wPos.xz - (lightSource.xz / lightSource.y * wPos.y);
    float caustic = 0.0;
    mat2 rotationMatrix = mat2(vec2(-0.737367, -0.675493), vec2(0.675493, -0.737367));

    const vec2 wave_size[4] = vec2[](vec2(64.0), vec2(32.0, 16.0), vec2(16.0, 32.0), vec2(48.0));

    for (int i = 0; i < 4; i++)
    {

        float small_wave = texture2D(colortex7, mod(rotationMatrix * pos / wave_size[i], 1.0)).x;

        caustic += max(1.0 - sin(1.0 - pow(0.5 + sin(small_wave * 15.0) * 0.5, 25.0)), 0);
    }
    return (caustic / 4) * 0.5;
}

float remap(const float originalValue, const float originalMin, const float originalMax, const float newMin, const float newMax)
{
    return newMin + (((originalValue - originalMin) / (originalMax - originalMin)) * (newMax - newMin));
}

void waterNoLoop(
    float rayLength, float dY, float estEyeDepth,
    vec3 refractedSunVec, vec3 ambient, vec3 sunColor,
    inout vec3 absorbance_w, inout vec3 vL_w, vec3 dVWorld)
{
    // Compute normalized dVWorld once.
    vec3 dVWorldNorm = normalize(dVWorld);
    float SdotV = dot(sunPosWorld, dVWorldNorm);

    // Predefined constant vectors.
    const vec3 rL_Water = water_coefficientRayleigh;
    const vec3 mieScatterFactor_Water = dirtScatterCoef;
    const vec3 ScatterCoeff_water = totEpsilon + mieScatterFactor_Water;

    // Phase functions.
    float mie_water = phaseg(SdotV, 0.4);
    float Rayleigh = phaseRayleigh(SdotV);

    // Compute depth factor and common exponential factors.
    float depthFactor = max(estEyeDepth - 0.5 * dY, 0.0);
    vec3 depthExpFactor = exp(-depthFactor * rL_Water);
    vec3 sunExpFactor = exp(-depthFactor * totEpsilon / abs(refractedSunVec.y));

    // Calculate scattering contributions.
    vec3 mieContribution = mieScatterFactor_Water * mie_water * sunExpFactor;
    vec3 totalScatter = Rayleigh * rL_Water + mieContribution;

    // Combine ambient contributions (factor common terms).
    vec3 ambientContrib = ambient * depthExpFactor * (rL_Water + mieScatterFactor_Water);

    vec3 light_water = (sunColor * 0.25) * totalScatter + ambientContrib;

    // Compute absorbance and scattering factor.
    vec3 absorbance = exp(-ScatterCoeff_water * rayLength);
    vec3 scatterFactor = (light_water * (1.0 - absorbance)) / (ScatterCoeff_water + 1e-8);

    // Accumulate results.
    vL_w += scatterFactor * absorbance_w;
    absorbance_w *= absorbance;
}

// Function to apply sky blending
void applySkyBlend(inout vec3 solid, inout vec4 translucent, vec3 sky, vec3 vL_w, vec3 p3, vec3 p30, float far, bool iswater)
{
#ifdef SKYBLEND
    if (isEyeInWater == 0)
    {
        float blend_factor1 = gbf(p3, far, 0.0);
        float blend_factor2 = gbf(p30, far, 0.0);
        if (iswater)
        {
            solid = mix(solid, sky, blend_factor1);
        }
        else
        {
            solid = mix(solid, sky, blend_factor2);
        }
        translucent.rgb = mix(translucent.rgb, sky, blend_factor1) * float(translucent.a > 0);
    }
    else
    {
        float blend_factor = gbf(p30, far, 0.0);

        solid = mix(solid, sky, blend_factor);
    }

#endif
}
void applySkyBlend2(inout vec3 solid, vec3 sky, vec3 p3, float far)
{
#ifdef SKYBLEND

    float blend_factor = gbf(p3, far, 0.0);

    solid = mix(solid, sky, blend_factor);

#endif
}

void applyWaterEffects(
    inout vec3 solid,
    inout vec4 translucent,
    inout vec3 sky,
    vec3 vL_w,
    vec3 vL_a,
    vec3 absorbance_w,
    vec3 transmittance,
    vec3 fogColor,
    vec3 fragpos,
    vec3 p30,
    int isEyeInWater,
    bool iswater)
{
    if (isEyeInWater == 0)
    {
        // CASE 0: Eye not underwater.
        if (iswater)
        {
            float blend_factor2 = gbf(p30, far, 0.0);
            // Apply water effects then blend with vL_w based on blend factor.
            solid = mix(solid * absorbance_w + vL_w, vL_w, blend_factor2);
        }
        translucent.rgb = translucent.rgb * transmittance + vL_a;
        solid = solid * transmittance + vL_a;
    }
    else if (isEyeInWater == 1)
    {
        // CASE 1: Eye partially underwater.
        solid = solid * absorbance_w + vL_w;
#if WATER_FOG_MOD == 1
        sky = sky * absorbance_w + vL_w;
        translucent.rgb = translucent.rgb * absorbance_w + vL_w;
#else
        // Multiply twice by absorbance_w (i.e. square it) for stronger attenuation.
        sky = sky * (absorbance_w * absorbance_w) + vL_w;
        translucent.rgb = translucent.rgb * (absorbance_w * absorbance_w) + vL_w;
#endif
    }
    else if (isEyeInWater == 2 || isEyeInWater == 3)
    {
        // CASE 2 & 3: Deep underwater or extreme fog conditions.
        float fogFactor = exp(-length(fragpos));
        solid = solid * fogFactor + fogColor;
    }
}
