float saturate(float x) {
    return clamp(x, 0, 1);
}

float random(vec2 coords) {
    return fract(sin(dot(coords.xy, vec2(12.9898, 78.233))) * 43758.5453);
}

vec2 rotate(vec2 X, float Ang) {
    float s = sin(Ang);
    float c = cos(Ang);
    mat2 RotationMat = mat2(
            c, s,
            -s, c
        );
    return RotationMat * X;
}

float get_depth(vec2 ScreenPos, out bool IsDH) {
    float Depth = texture2D(depthtex0, ScreenPos).x;
    IsDH = false;
    #ifdef DISTANT_HORIZONS
    if (Depth >= 1) {
        Depth = texture2D(dhDepthTex0, ScreenPos).x;
        IsDH = true;
    }
    #endif
    return Depth;
}

float get_depth_solid(vec2 ScreenPos, out bool IsDH) {
    float Depth = texture2D(depthtex1, ScreenPos).x;
    IsDH = false;
    #ifdef DISTANT_HORIZONS
    if (Depth >= 1) {
        Depth = texture2D(dhDepthTex1, ScreenPos).x;
        IsDH = true;
    }
    #endif
    return Depth;
}


float l_depth(float depth, float Near, float Far) {
    return (Near * Far) / (depth * (Near - Far) + Far);
}

float l_depth(float depth) {
    return l_depth(depth, near, far);
}

float l_depth(float depth, bool IsDH) {
    if(IsDH)
        return l_depth(depth, dhNearPlane, dhRenderDistance);
    else
        return l_depth(depth, near, far);
}

float unl_depth(float ldepth, float Near, float Far) {
    return ((Near * Far) / ldepth - Far) / (Near - Far);
}

mat3 tbn_normal(vec3 normal) {
    vec3 tangent = normalize(cross(normal, vec3(0.0, 1.0, 1.0)));
    vec3 bitangent = (cross(tangent, normal));
    return mat3(tangent, bitangent, normal);
}

bool solveQuadratic(float a, float b, float c, out float x1, out float x2) {
    if (b == 0) {
        // Handle special case where the the two vector ray.dir and V are perpendicular
        // with V = ray.orig - sphere.centre
        if (a == 0) return false;
        x1 = 0;
        x2 = sqrt(-c / a);
        return true;
    }
    float discr = b * b - 4 * a * c;

    if (discr < 0) return false;

    float q = (b < 0.f) ? -0.5f * (b - sqrt(discr)) : -0.5f * (b + sqrt(discr));
    x1 = q / a;
    x2 = c / q;

    return true;
}

bool raySphereIntersect(vec3 orig, vec3 dir, float radius, out float t0, out float t1) {
    // They ray dir is normalized so A = 1
    float A = 1;
    float B = 2 * dot(dir, orig);
    float C = dot(orig, orig) - pow2(radius);

    if (!solveQuadratic(A, B, C, t0, t1)) return false;

    if (t0 > t1) {
        float aux = t0;
        t0 = t1;
        t1 = aux;
    }

    return true;
}

vec3 intersectRayWithPlane(vec3 rayOrigin, vec3 rayDirection, float PlaneHeight) {
    float denom = rayDirection.y;

    float pointToRay = PlaneHeight - rayOrigin.y;
    float t = pointToRay / denom;

    // Check if the intersection is in the positive direction of the ray
    if (t >= 0.0) {
        return t * rayDirection;
    }

    // No intersection or the intersection is behind the ray origin
    return vec3(0.0);
}

bool intersect_with_cloud_plane(vec3 CameraPos, inout vec3 StartPos, inout vec3 EndPos, vec3 RayDir, const float LOWER_PLANE, const float UPPER_PLANE) {
    if (CameraPos.y < LOWER_PLANE) {
        StartPos = intersectRayWithPlane(CameraPos, RayDir, LOWER_PLANE);
        EndPos = intersectRayWithPlane(CameraPos, RayDir, UPPER_PLANE);
        if (EndPos == vec3(0) || StartPos == vec3(0)) return false;
    }
    else if (CameraPos.y > UPPER_PLANE) {
        StartPos = intersectRayWithPlane(CameraPos, RayDir, UPPER_PLANE);
        EndPos = intersectRayWithPlane(CameraPos, RayDir, LOWER_PLANE);
        if (EndPos == vec3(0) || StartPos == vec3(0)) return false;
    }
    else {
        StartPos = vec3(0);
        if (RayDir.y > 0) {
            EndPos = intersectRayWithPlane(CameraPos, RayDir, UPPER_PLANE);
        }
        else {
            EndPos = intersectRayWithPlane(CameraPos, RayDir, LOWER_PLANE);
        }
    }

    return true;
}

float SmoothF(float x, float alpha) {
    return x > 0.0 ? pow(x / (x + pow(x, -1.0 / alpha)), alpha / (1.0 + alpha)) : x;
}

float SmoothMin(float a, float b, float alpha) {
    return b - 1.0 + SmoothF(a - b + 1.0, alpha);
}

float SmoothMax(float a, float b, float alpha) {
    return b + 1.0 - SmoothF(1.0 - a + b, alpha);
}

vec4 blur5x5(sampler2D image, vec2 uv, vec2 direction) {
    vec4 color = vec4(0.0);
    vec2 off1 = vec2(1.3333333333333333) * direction;
    color += texture2D(image, uv) * 0.29411764705882354;
    color += texture2D(image, uv + (off1 * resolutionInv)) * 0.35294117647058826;
    color += texture2D(image, uv - (off1 * resolutionInv)) * 0.35294117647058826;
    return color;
}

vec4 blur9x9(sampler2D image, vec2 uv, vec2 direction) {
    vec4 color = vec4(0.0);
    vec2 off1 = vec2(1.3846153846) * direction;
    vec2 off2 = vec2(3.2307692308) * direction;
    color += texture2D(image, uv) * 0.2270270270;
    color += texture2D(image, uv + (off1 * resolutionInv)) * 0.3162162162;
    color += texture2D(image, uv - (off1 * resolutionInv)) * 0.3162162162;
    color += texture2D(image, uv + (off2 * resolutionInv)) * 0.0702702703;
    color += texture2D(image, uv - (off2 * resolutionInv)) * 0.0702702703;
    return color;
}

vec3 rgb_to_xyz(vec3 rgb) {
    const mat3 XYZ_MATRIX = mat3(
            0.5149, 0.3244, 0.1607,
            0.3654, 0.6704, 0.0642,
            0.0248, 0.1248, 0.8504
        );
    return XYZ_MATRIX * rgb;
}

const float shadowTexSize = 1.0/shadowMapResolution;

// 5 texture samples instead of 9
vec4 blur3x3(sampler2D image, vec2 texcoord) {
    vec4 Color = vec4(0);
    vec2 Off1 = vec2(-0.33, 1.0) * resolutionInv;
    vec2 Off2 = vec2(1.0, 0.33) * resolutionInv;
    Color += textureLod(image, texcoord, 0) * 0.25;
    Color += textureLod(image, texcoord + Off1, 0) * 0.1875;
    Color += textureLod(image, texcoord - Off1, 0) * 0.1875;
    Color += textureLod(image, texcoord + Off2, 0) * 0.1875;
    Color += textureLod(image, texcoord - Off2, 0) * 0.1875;
    return Color;
}

float min_depth_4x4(vec2 texcoord) {
    float a = min_component(textureGatherOffset(depthtex0, texcoord, ivec2(-1, -1)));
    float b = min_component(textureGatherOffset(depthtex0, texcoord, ivec2(-1,  1)));
    float c = min_component(textureGatherOffset(depthtex0, texcoord, ivec2( 1, -1)));
    float d = min_component(textureGatherOffset(depthtex0, texcoord, ivec2( 1,  1)));

    return min_component(vec4(a, b, c, d));
}

float max_depth_4x4(vec2 texcoord, out bool IsDH) {
    #ifdef DISTANT_HORIZONS
        get_depth(texcoord, IsDH);
    #else
        IsDH = false;
    #endif
    
    float a, b, c, d;
    if(IsDH) {
        a = max_component(textureGatherOffset(dhDepthTex0, texcoord, ivec2(-1, -1)));
        b = max_component(textureGatherOffset(dhDepthTex0, texcoord, ivec2(-1,  1)));
        c = max_component(textureGatherOffset(dhDepthTex0, texcoord, ivec2( 1, -1)));
        d = max_component(textureGatherOffset(dhDepthTex0, texcoord, ivec2( 1,  1)));
    } else {
        a = max_component(textureGatherOffset(depthtex0, texcoord, ivec2(-1, -1)));
        b = max_component(textureGatherOffset(depthtex0, texcoord, ivec2(-1,  1)));
        c = max_component(textureGatherOffset(depthtex0, texcoord, ivec2( 1, -1)));
        d = max_component(textureGatherOffset(depthtex0, texcoord, ivec2( 1,  1)));
    }

    return max_component(vec4(a, b, c, d));
}