

float mix5(float x, float val1, float val2, float val3, float val4, float val5)
{
    float result;

    if (x <= 0.45)
    {
        float t = smoothstep(0.0, 0.45, x);
        result = mix(val1, val2, t);
    }
    else if (x <= 0.5)
    {
        float t = smoothstep(0.45, 0.5, x);
        result = mix(val2, val3, t);
    }
    else if (x <= 0.6)
    {
        float t = smoothstep(0.5, 0.6, x);
        result = mix(val3, val4, t);
    }
    else
    {
        float t = smoothstep(0.6, 1.0, x);
        result = mix(val4, val5, t);
    }

    return result;
}
float getFogDensity()
{
    // Constants for adjustable peaks
    const float DAWN_PEAK_TIME = 6.0;   // Time of peak fog density at dawn (6 AM)
    const float DAWN_PEAK_AMOUNT = 0.6; // Maximum fog density at dawn
    const float DAWN_DECAY_RATE = 0.5;  // Decay rate for fog density around dawn

    const float DUSK_PEAK_TIME = 18.0;  // Time of peak fog density at dusk (6 PM)
    const float DUSK_PEAK_AMOUNT = 0.55; // Maximum fog density at dusk
    const float DUSK_DECAY_RATE = 0.15; // Decay rate for fog density around dusk

    const float MIDNIGHT_PEAK_AMOUNT = 0.7; // Maximum fog density at midnight
    const float MIDNIGHT_DECAY_RATE = 0.1;  // Decay rate for fog density around midnight

    const float DAYTIME_PEAK_AMOUNT = 0.0; // Minimum fog density during the day at noon
    const float DAYTIME_DECAY_RATE = 0.05; // Decay rate for fog density during the day
    // Time in hours, adjusted for in-game time
    float timeOfDay = mod((worldTime + 6000) / 1000.0, 24.0);

    // Calculate fog density contributions from each peak using Gaussian-like functions
    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));
    
    // Adjust midnight fog calculation for proper transition across midnight
    float midnightFog = MIDNIGHT_PEAK_AMOUNT * exp(-MIDNIGHT_DECAY_RATE * min(pow(timeOfDay, 2), pow(timeOfDay - 24.0, 2)));

    float dayFog = DAYTIME_PEAK_AMOUNT * exp(-DAYTIME_DECAY_RATE * pow(timeOfDay - 12.0, 2)); // Noon is 12 PM

    // Combine contributions from all peaks to calculate total fog density
    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;
}

vec3 calculateAbsorbance(vec3 scatterCoeff, float attenuationDepth)
{
    return clamp(exp(-scatterCoeff * attenuationDepth), 0.0, 1.0);
}

#define diagonal3(m) vec3((m)[0].x, (m)[1].y, m[2].z)

vec3 toShadowSpaceProjected(vec3 p3)
{
    p3 = mat3(gbufferModelViewInverse) * p3 + gbufferModelViewInverse[3].xyz;
    p3 = mat3(shadowModelView) * p3 + shadowModelView[3].xyz;
    p3 = diagonal3(shadowProjection) * p3 + shadowProjection[3].xyz;

    return p3;
}

#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);

float safety = 0.00000001;

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

vec3 fog_coefficientRayleigh = vec3(5.8 * 1e-6, 1.35 * 1e-5, 3.31 * 1e-5);
vec3 fog_coefficientMie = vec3(0.0000030);

float linear_fog_fade(float vertexDistance, float fogStart, float fogEnd)
{
    if (vertexDistance <= fogStart)
    {
        return 1.0;
    }
    else if (vertexDistance >= fogEnd)
    {
        return 0.0;
    }

    return smoothstep(fogEnd, fogStart, vertexDistance);
}

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 * borderFogFactor;
    return borderFogFactor;
#else

    float borderFogFactor = smoothstep(far * 0.5, far, eyeDist);
    borderFogFactor *= 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 hash(vec3 p)
{
    p = fract(p * 0.3183099 + 0.1) * 17.0;
    return fract(dot(p, p.yzx + vec3(1.0)));
}

float customMix(vec3 i, vec3 f)
{
    float n000 = hash(i + vec3(0.0, 0.0, 0.0));
    float n100 = hash(i + vec3(1.0, 0.0, 0.0));
    float n010 = hash(i + vec3(0.0, 1.0, 0.0));
    float n110 = hash(i + vec3(1.0, 1.0, 0.0));
    float n001 = hash(i + vec3(0.0, 0.0, 1.0));
    float n101 = hash(i + vec3(1.0, 0.0, 1.0));
    float n011 = hash(i + vec3(0.0, 1.0, 1.0));
    float n111 = hash(i + vec3(1.0, 1.0, 1.0));

    float nx00 = mix(n000, n100, f.x);
    float nx10 = mix(n010, n110, f.x);

    float nx01 = mix(n001, n101, f.x);
    float nx11 = mix(n011, n111, f.x);

    float nxy0 = mix(nx00, nx10, f.y);
    float nxy1 = mix(nx01, nx11, f.y);

    return mix(nxy0, nxy1, f.z);
}

float noisem(vec3 x)
{
    vec3 i = floor(x);
    vec3 f = fract(x);
    f = f * f * (3.0 - 2.0 * f);

    return customMix(i, f);
}

float cloud_density0(in vec3 x, float fogAmount)
{
    // Calculate uniform coverage
    float unifCov = exp2(-max(x.y - 60.0, 0.0) / 32.0);
    unifCov = pow(unifCov, 4) * 32.0 * fogAmount;
    if (unifCov <= 0.01)
        return 0.0;

    // Scale coordinates
    x *= vec3(12.0, 24.0, 12.0);

    // Initialize noise parameters
    float result = 0.0;
    float w = 1.0;
    float w_sum = 0.0;
    vec3 x_oct = x / 256.0;

    // Accumulate noise
    for (int i = 0; i < 4; i++)
    {
        result += w * noisem(x_oct + 0.02 * frameTimeCounter);
        x_oct *= 2.0;
        w_sum += w;
        w *= 0.6;
        if (result <= 0.1)
            return 0.0;
    }

    // Normalize result
    result /= w_sum;

    // Apply bias and adjust result
    float bias_noise = max(0.0, 0.5 - 0.1 * exp(-(x.y - 400.0) / 8192.0));
    float bias = 0.7 * (1 - pow(bias_noise, 1.5));
    result = max(0.0, (result - bias) / (1.0 - bias));

    // Final adjustment and return
    return pow(result, 0.5) * unifCov;
}
float cloud_density(in vec3 x, float fogAmount)
{
#ifdef OVERWORLD
    // Calculate uniform coverage
    float unifCov = exp2(-max(x.y - 60.0, 0.0) / 32.0);
    unifCov = pow(unifCov, 4) * 32.0 * fogAmount;
    if (unifCov <= 0.05)
        return fogAmount;
#else

    float unifCov = 1;

#endif

    // Scale coordinates
    x *= vec3(12.0, 24.0, 12.0);

    // Initialize noise parameters
    float result = 0.0;
    float w = 1.0;
    float w_sum = 0.0;
    vec3 x_oct = x / 256.0;

    // Accumulate noise
    for (int i = 0; i < 4; i++)
    {
        result += w * noisem(x_oct + 0.02 * frameTimeCounter);
        if (result <= 0.1)
            return 1.0;
        x_oct *= 2.0;
        w_sum += w;
        w *= 0.7;
    }

    // Normalize result
    result /= w_sum;

    // Apply bias and adjust result
    float bias_noise = max(0.0, 0.5 - 0.1 * exp(-(x.y - 400.0) / 8192.0));
    float bias = 0.7 * (1 - pow(bias_noise, 1.5));
    result = max(0.0, (result - bias) / (1.0 - bias));

    // Final adjustment and return
    return clamp(pow(result * 2.0, 0.5) * unifCov, fogAmount, 10);
}
float densityAtPosFog(in vec3 pos)
{

    pos /= 18.;
    pos.xz *= 0.5;

    vec3 p = floor(pos);
    vec3 f = fract(pos);

    f = (f * f) * (3. - 2. * f);

    vec2 uv = p.xz + f.xz + p.y * vec2(0.0, 193.0);

    vec2 coord = uv / 512.0;

    vec2 xy = texture2D(noisetex, coord).yx;

    return mix(xy.r, xy.g, f.y);
}
float cloudVol(in vec3 pos, float fogAmount)
{
#ifdef OVERWORLD
    // Calculate uniform coverage
    float unifCov = exp2(-max(pos.y - 60.0, 0.0) / 32.0);
    unifCov = pow(unifCov, 4) * 32.0 * fogAmount;
    if (unifCov <= 0.05)
        return fogAmount;
#else

    float unifCov = 1;

#endif
    // Precompute some values
    vec3 samplePos = pos * vec3(1.0, 1. / 16., 1.0) + frameTimeCounter * vec3(0.5, 0., 0.5) * 1;

    // Sample noise texture
    float noise = densityAtPosFog(samplePos * 64) * 2.0;

    float bias_noise = max(0.0, 0.5 - 0.1 * exp(-(pos.y - 400.0) / 8192.0));
    float bias = 0.7 * (1 - pow(bias_noise, 1.5));
    noise = max(0.0, (noise - bias) / (1.0 - bias));

    // Apply fog amount and return
    return clamp(pow(noise * 2.0, 0.5) * unifCov, fogAmount, 10);
}
vec4 GetNoise(sampler3D noiseSampler, vec3 position)
{
    return texture(noiseSampler, fract(position));
}
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));
}
const vec4 turbulenceTable[17] = vec4[17](
    vec4(0.031000, -0.015000, 0.022500, 1.080000),
    vec4(0.041500, 0.062500, -0.004500, 0.895000),
    vec4(-0.073500, 0.061000, 0.067500, 0.575000),
    vec4(0.058000, 0.060500, -0.081000, 0.755000),
    vec4(0.026500, 0.037500, -0.069000, 0.545000),
    vec4(0.058500, -0.039000, -0.050500, 1.125000),
    vec4(0.074500, -0.035500, -0.052500, 0.940000),
    vec4(0.009000, 0.043500, 0.055500, 0.620000),
    vec4(-0.061000, 0.048000, 0.003000, 0.805000),
    vec4(0.082000, -0.021000, 0.070000, 1.020000),
    vec4(0.018000, 0.048000, -0.052000, 0.700000),
    vec4(0.013000, -0.052500, -0.032500, 0.530000),
    vec4(0.059500, -0.003500, -0.059500, 0.890000),
    vec4(-0.080500, 0.005500, -0.050000, 0.615000),
    vec4(0.022000, -0.066500, 0.085000, 0.825000),
    vec4(0.048500, 0.063000, -0.049000, 0.980000),
    vec4(-0.021500, -0.054000, 0.061500, 0.650000));

float calculateCloudDensity(vec3 position, float fogAmount)
{

    float unifCov = exp2(-max(position.y - 70.0, 0.0) / 16.0);
    float heightFraction = pow(unifCov, 4) * fogAmount;
    if (heightFraction == 0.0)
        return fogAmount;
    vec2 windDirection = vec2(1.0);
    float currentTime = frameTimeCounter * 0;
    vec3 currentPosition = position;

    // Apply wind influence to the position
    currentPosition.xz += currentTime * 20.0 * 0.6;
    currentPosition.y *= 8.0;

    // Get noise values
    vec2 noise = GetNoise(colortex15, currentPosition.xyz * 0.0003).xy;

    float baseCloudDensity = remap(noise.x, -(1.0 - noise.y * noise.x), 1.0, 0.0, 1.0);

    // Adjust cloud coverage
    float weatherCoverage = 0.3;
    baseCloudDensity = remap(baseCloudDensity, 1.0 - (weatherCoverage), 1.0, 0.0, 1.0);
    baseCloudDensity *= weatherCoverage;

    // Adjust position for time
    currentPosition.xz -= currentTime * 5.0;
    currentPosition.y -= currentTime * 5.0;

    // Initialize variables for fractal brownian motion (FBM)
    currentPosition *= 0.006;
    currentPosition.y *= 0.25;

    float fractalNoise = dot(GetNoise(colortex15, currentPosition + 1.8 * (currentTime * 0.05)).xy, vec2(0.5, 2.0));

    // Blend fractal noise based on height
    fractalNoise = mix(fractalNoise, 1.0 - fractalNoise, clamp(heightFraction * 4.0, 0.0, 1.0));
    baseCloudDensity = remap(baseCloudDensity, fractalNoise * heightFraction, 1.0, 0.0, 1.0);

    // return gradient;
    return clamp(pow(clamp(baseCloudDensity, 0.0, 1.0), (1.0 - heightFraction) * 0.8 + 0.5) * 32 * unifCov, fogAmount, 10);
}
float cloud_density2(in vec3 x, float fogAmount)
{
#ifdef OVERWORLD
    // Calculate uniform coverage
    float unifCov = exp2(-max(x.y - 60.0, 0.0) / 32.0);
    unifCov = clamp(unifCov * fogAmount, 0, 1);
    if (unifCov <= 0.00)
        return 0;
#else

    float unifCov = 1;

#endif

    // Scale coordinates
    x *= vec3(12.0, 24.0, 12.0);

    // Initialize noise parameters
    float result = 0.0;
    float w = 1.0;
    float w_sum = 0.0;
    vec3 x_oct = x / 8000.0;

    result += dot(GetNoise(colortex15, x_oct * 1.0 + 1.8 * (frameTimeCounter * 0.001)).xy, vec2(0.5, 2.0)) * 1.05;
    result *= dot(GetNoise(colortex15, x_oct * 4.0 + 1.8 * (frameTimeCounter * 0.002)).xy, vec2(0.5, 2.0)) * 1.01;
    result *= unifCov;
    // Apply bias and adjust result
    float bias_noise = max(0.0, 0.5 - 0.1 * exp(-(x.y - 400.0) / 8192.0));
    float bias = 0.7 * (1 - pow(bias_noise, 1.5));
    result = max(0.0, (result - bias) / (1.0 - bias));

    // Final adjustment and return
    return clamp(result * unifCov, 0, 1) * 256;
}
#include "/lib/fog/fognoloop.glsl"
