#version 330 core
out vec4 FragColor;

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
uniform sampler2D iChannel0;

#define PI 3.14159265

float smin(float a, float b, float k)
{
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

float smax(float a, float b, float k)
{
    return smin(a, b, -k);
}

vec3 path(float p)
{
    return vec3(sin(p * 0.05) * cos(p * 0.05) * 18.0, sin(p * 0.1 + 2.0) * 10.0, 0.0);
}

vec2 hash2(vec2 p)
{
    const vec2 k = vec2(0.3183099, 0.3678794);
    p = p * k + k.yx;
    return fract(16.0 * k * fract(p.x * p.y * (p.x + p.y)));
}

float noise(vec3 p)
{
    const vec3 s = vec3(7.0, 157.0, 113.0);
    vec3 ip = floor(p);
    vec4 h = vec4(0.0, s.yz, s.y + s.z) + dot(ip, s);
    p -= ip;
    p = p * p * (3.0 - 2.0 * p);
    h = mix(fract(sin(h) * 43758.5453), fract(sin(h + s.x) * 43758.5453), p.x);
    h.xy = mix(h.xz, h.yw, p.y);
    return mix(h.x, h.y, p.z);
}

const mat3 m = mat3(0.00, 0.80, 0.60,
                    -0.80, 0.36, -0.48,
                    -0.60, -0.48, 0.64);

float fbm(vec3 x)
{
    float f = 2.0;
    float s = 0.5;
    float a = 0.0;
    float b = 0.5;
    for (int i = 0; i < 4; i++)
    {
        float n = noise(x);
        a += b * n;
        b *= s;
        x = f * m * x;
    }
    return a;
}

float tunnel(vec3 p)
{
    return 3.0 - length(p.xy + vec2(sin((iTime + p.z) * 1.8) * 0.2, 0.0));
}

float cone(vec3 p, vec3 c)
{
    vec2 q = vec2(length(p.xz), p.y);
    float d1 = -q.y - c.z;
    float d2 = max(dot(q, c.xy), q.y);
    return length(max(vec2(d1, d2), 0.0)) + min(max(d1, d2), 0.0);
}

float torus(vec3 p, vec2 t)
{
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q) - t.y;
}

vec3 thorn_texcoord;

float thorn(vec3 p, float noi, float base, out float dhole, out float dtorus)
{
    float d = 10.0, dh = 10.0, dt = 10.0;
    vec2 k = normalize(p.xy);
    vec2 n = floor(vec2(asin(k.y) * 2.5, p.z));
    vec2 f = fract(vec2(asin(k.y) * 2.5, p.z));

    for (int i = -1; i <= 1; ++i)
    {
        for (int j = -1; j <= 1; ++j)
        {
            vec2 g = vec2(float(i), float(j));
            vec2 o = hash2(n + g);
            vec2 v = hash2(n + g + vec2(13.1, 71.7));
            vec2 r = g - f + o;
            vec3 q = vec3(r.x, base, r.y);
            vec3 qq = q + vec3(0.0, -1.0 + 0.6 * sin(4.0 * (iTime + 3.0 * v.x + 5.0 * v.y)), 0.0);

            float d0 = cone(qq, vec3(1.0, 0.1, 4.0));
            float d1 = cone(q + vec3(0.0, -1.3, 0.0), vec3(1.0, 0.1, 3.0));
            float d2 = torus(q + vec3(0.0, -0.08, 0.0), vec2(0.25, 0.07));
            if (d0 < d)
                thorn_texcoord = qq;

            d = min(d, d0);
            dh = min(d1, dh);
            dt = min(d2, dt);
        }
    }

    dhole = dh;
    dtorus = dt;
    d += noi * noi * 0.11;
    return d;
}

float infi_box(vec3 p, vec2 b)
{
    vec2 d = abs(p.xy) - b;
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}

int mateid = 0;

float map(vec3 p)
{
    p -= path(p.z);
    mateid = 0;
    float d0 = tunnel(p);
    float dh, dt;
    float d1 = thorn(p, 0.0, d0, dh, dt);
    d0 = smax(d0, -dh + 0.05, 0.2);
    d0 = smin(dt, d0, 0.3);
    if (d1 < d0)
    {
        d0 = d1;
        mateid = 1;
    }
    return d0;
}

vec3 get_normal(in vec3 p)
{
    const vec2 e = vec2(0.002, 0.0);
    return normalize(vec3(
        map(p + e.xyy) - map(p - e.xyy),
        map(p + e.yxy) - map(p - e.yxy),
        map(p + e.yyx) - map(p - e.yyx)));
}

float intersect2(vec3 ro, vec3 rd)
{
    float t = 0.01;
    float d = map(ro + t * rd);
    for (int i = 0; i < 96; ++i)
    {
        if (abs(d) < 0.002 * (1.0 + t) || t > 50.0)
            break;
        t += step(d, 1.0) * d * 0.2 + d * 0.5;
        d = map(ro + t * rd);
    }
    return (t > 50.0) ? -1.0 : t;
}

vec4 texcube(sampler2D sam, vec3 p, vec3 n)
{
    vec4 p1 = texture(sam, p.xy);
    vec4 p2 = texture(sam, p.xz);
    vec4 p3 = texture(sam, p.yz);
    return p1 * abs(n.z) + p2 * abs(n.y) + p3 * abs(n.x);
}

float bump(vec3 p, vec3 n)
{
    return dot(texcube(iChannel0, 0.25 * p, n).xyz, vec3(0.299, 0.587, 0.114));
}

vec3 bump_mapping(vec3 p, vec3 n, float weight)
{
    vec2 e = vec2(2.0 / iResolution.y, 0.0);
    vec3 g = vec3(
        bump(p - e.xyy, n) - bump(p + e.xyy, n),
        bump(p - e.yxy, n) - bump(p + e.yxy, n),
        bump(p - e.yyx, n) - bump(p + e.yyx, n)) / (e.x * 2.0);
    g = (g - n * dot(g, n));
    return normalize(n + g * weight);
}

float shadow(vec3 ro, vec3 rd, float dist)
{
    float res = 1.0;
    float t = 0.05;
    for (int i = 0; i < 10; i++)
    {
        if (t > dist - 0.2)
            break;
        float h = map(ro + rd * t);
        res = min(6.0 * h / max(t, 0.001), res);
        t += h;
    }
    return max(res, 0.0);
}

float density(vec3 p, float ms)
{
    vec3 n = get_normal(p);
    return map(p - n * ms) / ms;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 q = fragCoord.xy / iResolution.xy;
    vec2 p = q * 2.0 - 1.0;
    p.x *= iResolution.x / iResolution.y;
    vec3 ro = vec3(0.5, 0.0, -iTime * 0.6);
    vec3 ta = ro + vec3(0.0, 0.0, -1.0);
    vec3 lp0 = ro + vec3(0.0, 0.0, -3.0);

    ro += path(ro.z);
    ta += path(ta.z);
    lp0 += path(lp0.z);

    vec3 f = normalize(ta - ro);
    vec3 r = normalize(cross(f, vec3(0.0, 1.0, 0.0)));
    vec3 u = normalize(cross(r, f));

    vec3 rd = normalize(mat3(r, u, f) * vec3(p.xy, PI / 2.0));
    vec3 col = vec3(0.6, 0.8, 1.1);

    float t = intersect2(ro, rd);

    if (t > -0.5)
    {
        vec3 pos = ro + t * rd;
        vec3 n = get_normal(pos);

        if (mateid == 0)
            n = bump_mapping(pos * 0.05, n, 0.08);
        else
            n = mix(n, bump_mapping(thorn_texcoord.xyz, n, 0.03), 1.0 - smoothstep(-1.5, -0.5, thorn_texcoord.y));

        vec3 ld0 = lp0 - pos;
        float ldist = length(ld0);
        ld0 /= ldist;
        vec3 lc0 = vec3(1.1, 0.8, 0.7);

        float sha = shadow(pos + 0.1 * n, ld0, ldist);
        float dif = max(0.0, dot(ld0, n));
        float bac = max(0.0, dot(-ld0, n));
        float amb = max(0.0, dot(n, vec3(0.0, 1.0, 0.0)));
        float spe = max(0.0, pow(clamp(dot(ld0, reflect(rd, n)), 0.0, 1.0), 128.0));
        float fre = clamp(1.0 + dot(rd, n), 0.0, 1.0);
        float sca = 1.0 - density(pos, 0.5);

        vec3 Lo = 4.0 * dif * lc0 * sha;
        Lo += 0.5 * bac * lc0;
        Lo += 2.0 * spe * vec3(1.0);
        Lo += 1.5 * pow(fre, 8.0) * vec3(0.6, 0.1, 0.1);
        Lo /= ldist;

        map(pos);
        float tex = fbm(pos * 3.5);
        vec3 mate = 0.5 * vec3(1.5, 0.2, 0.0) + tex * tex * 3.2;

        if (mateid == 1)
        {
            float tt = fbm(thorn_texcoord * 15.0);
            tt = pow(smoothstep(0.1, 0.5, tt), 5.0);
            mate = mix(vec3(3.0), vec3(3.0) - vec3(0.5, 1.4 * tt, 3.0 * tt), 1.0 - smoothstep(-1.5, -0.5, thorn_texcoord.y));
            Lo += vec3(0.4) * (1.0 - sca) * (1.0 - sca);
        }

        if (mateid == 0)
        {
            Lo += 5.0 * spe * vec3(1.0, 0.6, 0.2) / ldist * sha;
            Lo += 0.3 * vec3(0.3, 0.07, 0.1) * sca * sca * sca;
        }
        Lo += 0.2 * amb;
        col = Lo * mate * 0.2;
    }

    col = mix(col, vec3(0.0), 1.0 - exp(-0.015 * t * t));
    col = pow(clamp(col, 0.0, 1.0), vec3(0.45));
    col = pow(col, vec3(0.9, 1.0, 0.95));
    col *= pow(16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.1);
    fragColor = vec4(col, uAlpha);
}

void main()
{
    mainImage(FragColor, gl_FragCoord.xy);
}
