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) {
    mat2 RotationMat = mat2(
            cos(Ang), sin(Ang),
            -sin(Ang), cos(Ang)
        );
    return RotationMat * X;
}

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

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

mat3 get_tbn_matrix(vec3 normal) {
    vec3 tangent = normal.y == 1.0 ? vec3(1.0, 0.0, 0.0) : normalize(cross(vec3(0.0, 1.0, 0.0), normal));
    vec3 bitangent = normalize(cross(tangent, normal));
    return mat3(tangent, bitangent, normal);
}

bool solveQuadratic(float a, float b, float c, inout float x1, inout 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, inout float t0, inout float t1) {
    // They ray dir is normalized so A = 1
    float A = 1;
    float B = 2 * (dir.x * orig.x + dir.y * orig.y + dir.z * orig.z);
    float C = orig.x * orig.x + orig.y * orig.y + orig.z * orig.z - radius * 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;

    // Check if the ray is parallel to the plane
    if (abs(denom) > 0.0001) {
        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);
}

// https://discord.com/channels/237199950235041794/960945132172111952/1200536680931790878
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);
}

// https://github.com/Experience-Monks/glsl-fast-gaussian-blur
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;
}

float acosf(float x)
{
    // GPGPU Programming for Games and Science
    float res = -0.156583 * abs(x) + PI / 2.0;
    res *= sqrt(1.0 - abs(x));
    return x >= 0 ? res : PI - res;
}

vec2 rescale(float l, float L, vec2 x) {
    return (x - l) / (L - l);
}

float min_component(vec2 a) {
    return min(a.x, a.y);
}

float min_component(vec3 a) {
    return min(a.x, min(a.y, a.z));
}

float min_component(vec4 a) {
    return min(a.x, min(a.y, min(a.z, a.w)));
}

float max_component(vec2 a) {
    return max(a.x, a.y);
}

float max_component(vec3 a) {
    return max(a.x, max(a.y, a.z));
}

float max_component(vec4 a) {
    return max(a.x, max(a.y, max(a.z, a.w)));
}

float lerp(float tl, float tr, float bl, float br, vec2 Coords) {
    vec2 f = fract(Coords * resolution);

    float a = mix(tl, tr, f.x);
    float b = mix(bl, br, f.y);
    return mix(a, b, f.y);
}

float pow2(float x) {
    return x * x;
}

float pow4(float x) {
    return pow2(pow2(x));
}

vec2 pow2(vec2 x) {
    return x * x;
}

vec2 pow4(vec2 x) {
    return pow2(pow2(x));
}

vec3 pow2(vec3 x) {
    return x * x;
}

vec3 pow4(vec3 x) {
    return pow2(pow2(x));
}

vec4 pow2(vec4 x) {
    return x * x;
}

vec4 pow4(vec4 x) {
    return pow2(pow2(x));
}
