#version 330 core

in vec2 vUV;
out vec4 FragColor;

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

const int AA = 1;

#define res iResolution.xy
#define B vec3(0.2126, 0.7152, 0.0722)
#define of (0.1 * vec2(-iTime, iTime))

mat2 rotMat(float a) { float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }

vec3 camPath(float t) { return vec3(0.0, 15.0, 0.0); }

vec3 lightPath(float t) {
    return vec3( 0.15 * sin(3.0 * t) + 0.5 * sin(0.5 * t),
                 0.6 * sin(t / 0.6) + 0.3 * sin(t * 3.0),
                 0.8 * sin(t / 0.8) + 0.2 * sin(t * 5.0)) * 5.0;
}

float smax(float p, float k) { return 0.5 * (p + sqrt(p * p + k)); }
vec2 hyperbolicTransform(vec2 p) {
    float r = length(p);
    return p * log(abs((1.0 + r) / (1.0 - r))) / r;
}

float opExtrusion( in vec3 p, in float d, in float h )
{
    vec2 w = vec2( d, abs(p.y) - h );
    return min(max(w.x,w.y),0.0) + length(max(w,0.0));
}

float sdRoundBox( in vec2 p, in vec2 b, in float r )
{
    vec2 d = abs(p)-b+r;
    return length(max(d,0.0)) + min(max(d.x,d.y),0.0) - r;
}

float hash12(vec2 p)
{
    vec3 p3  = fract(vec3(p.xyx) * 0.1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

float sdTruchet(vec2 p, out vec2 f) {
    f = floor(p);
    vec2 g = fract(p) - 0.5;
    float h = hash12(f);
    if(h < 0.5) g.x *= -1.0;
    return abs(length(g - sign(g.x + g.y) * 0.5) - 0.5);
}

float map(vec3 p) {
    vec3 q = p;
    p.xz = hyperbolicTransform(p.xz) + of;

    float d;
    if(length(p.xz) > 1e5) { d = 1e20; }
    else {
        vec2 cell;
        d = (sdTruchet(p.xz / 0.5, cell) - 0.1) * 0.5;
        d = max(d - 0.1, -d);
    }

    d = opExtrusion(p, d, 2.0);
    d = min(d, p.y);
    return d;
}

float edge;
vec3 getNormal(vec3 p) {
    float t = 0.1;
    vec2 e = vec2(1./mix(400., iResolution.y, .5)*(1. + t*.5), 0) * 5.;

    float d1 = map(p + e.xyy), d2 = map(p - e.xyy);
    float d3 = map(p + e.yxy), d4 = map(p - e.yxy);
    float d5 = map(p + e.yyx), d6 = map(p - e.yyx);
    float d = map(p)*2.;

    edge = abs(d1 + d2 - d) + abs(d3 + d4 - d) + abs(d5 + d6 - d);
    edge = smoothstep(0., 1., sqrt(edge/e.x*2.));

    e = vec2(.002, 0);
    d1 = map(p + e.xyy), d2 = map(p - e.xyy);
    d3 = map(p + e.yxy), d4 = map(p - e.yxy);
    d5 = map(p + e.yyx), d6 = map(p - e.yyx);

    return normalize(vec3(d1 - d2, d3 - d4, d5 - d6));
}

float softShadow(vec3 ro, vec3 rd, float k) {
    float re = 1.0;
    float t = 0.01;
    for (int i = 0; i < 50; i++) {
        float h = map(ro + rd * t);
        if (h < 0.001) return 0.0;
        re = min(re, k * h / t);
        t += h;
        if (t > 10.0) break;
    }
    return tanh(re);
}

vec3 tex3D( sampler2D tex, in vec3 p, in vec3 n ){
    n = abs(n)/1.732051;
    n = max((abs(n) - 0.2)*7., 0.001);
    n /= (n.x + n.y + n.z );  
    
    return ( texture(tex, p.yz)*n.x + 
             texture(tex, p.zx)*n.y + 
             texture(tex, p.xy)*n.z   ).xyz;
}

float bumpSurf(vec3 p, vec3 n) {
    vec3 q = p;
    p.xz = hyperbolicTransform(p.xz) + of;
    if(dot(p.xz, p.xz) > 1e5) p.xz = q.xz;

    return dot(tex3D(iChannel0, 0.5 * p, n), B) + .25 * dot(tex3D(iChannel1, 0.5 * p, n), B);
}

vec3 doBumpMap(in vec3 p, in vec3 nor, float bumpfactor){
    const vec2 e = vec2(0.001, 0);
    float ref = bumpSurf(p, nor);                 
    vec3 grad = (vec3(bumpSurf(p - e.xyy, nor),
                      bumpSurf(p - e.yxy, nor),
                      bumpSurf(p - e.yyx, nor) )-ref)/e.x;                     
          
    grad -= nor*dot(nor, grad);          
                      
    return normalize( nor + grad*bumpfactor );
}

float hash(vec2 p) {vec3 p3 = fract(vec3(p.xyx) * 0.13); p3 += dot(p3, p3.yzx + 3.333); return fract((p3.x + p3.y) * p3.z); }
float noise(vec2 x) {
    vec2 i = floor(x);
    vec2 f = fract(x);

    float a = hash(i);
    float b = hash(i + vec2(1.0, 0.0));
    float c = hash(i + vec2(0.0, 1.0));
    float d = hash(i + vec2(1.0, 1.0));

    vec2 u = f * f * (3.0 - 2.0 * f);
    return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}

float stripes(vec2 p) {
    float n = noise(p * vec2(1.0, 10.0) * 10.);
    return n;
}

float hatch(vec2 p, float b) {
    float k = 1.0 - b;
    k = tanh(k * k * k);
    
    float f = 1.0;
    f = mix(f, f * stripes(p),                         smoothstep(0.3, 0.4, k));
    f = mix(f, f * stripes(p * rotMat(radians(45.0))), smoothstep(0.5, 0.6, k));
    f = mix(f, f * stripes(p * rotMat(radians(90.0))), smoothstep(0.7, 0.8, k));
    f = mix(f, f * stripes(p * rotMat(radians(135.0))), smoothstep(0.9, 1.0, k));
    f = mix(f, f * stripes(p * rotMat(radians(180.0))), smoothstep(1.1, 1.2, k));
    
    return f;
}

float hatch( in vec3 p, in vec3 n, in float b ){
    p.xz = hyperbolicTransform(p.xz) + of;

    n = abs(n)/1.732051;
    n = max((abs(n) - 0.2)*7., 0.001);
    n /= (n.x + n.y + n.z );  
    
    return hatch(p.yz, b) * n.x + hatch(p.zx, b) * n.y + hatch(p.xy, b) * n.z;
}

vec3 render(vec2 I) {
    vec2 p = (I - 0.5 * res) / res.y;
    
    float time = 0.5 * iTime;
    vec3 camPos = camPath(time);
    
    float n = 3.0;
    vec3 cpd = (camPath(time + n) - camPath(time)) / n;
    vec3 camDir = normalize(vec3(0.0, -1.0, 0.01));

    float fov = radians(45.0);
    vec3 worldUp = vec3(0.0, 1.0, 0.0);
    vec3 camRight = normalize(cross(camDir, worldUp));
    vec3 camUp = cross(camRight, camDir);

    float t = tan(fov * 0.5);
    
    vec3 rayOriginP = camPos;
    vec3 rayDirP = normalize(camDir + p.x * camRight * t + p.y * camUp * t);
    
    vec3 rayOriginO = camPos + p.x * camRight + p.y * camUp;
    vec3 rayDirO = camDir;
    
    vec3 rayOrigin = mix(rayOriginP, rayOriginO, 0.3);
    vec3 rayDir = mix(rayDirP, rayDirO, 0.1);
    
    bool hit = false;
    float td = 0.0;
    for(int i = 0; i < 900; i++) {
        vec3 rayPos = rayOrigin + rayDir * td;
        float d = map(rayPos);
        
        if(d < 0.0001) { hit = true; break; }
        
        td += d;
        if(td > 60.0) { td = 60.0; break; }
    }
    
    vec3 col;
    
    if(hit) {        
        vec3 hitPoint = rayOrigin + rayDir * td;
        vec3 normal = getNormal(hitPoint);
        
        vec3 lightPos = lightPath(0.5 * iTime) + camPos;
        lightPos.y += 5.0;
        
        vec3 lightDir = normalize(lightPos - hitPoint);
        float light = dot(normal, lightDir);
        
        normal = doBumpMap(hitPoint, normal, 0.8);
        float bumpLight = dot(normal, lightDir);
        
        light = mix(light, light * softShadow(hitPoint + 0.1 * normal, lightDir, 48.0), 1.0);
        
        light = light * light * 2.0;
        
        vec3 color;
        color = vec3(0.9, 0.65, 0.55);
        
        float r = length(hitPoint.xz);
        color *= tanh(abs(r - 1.0));
        
        if(r < 1.0) color *= vec3(2.0, 1.4, 1.2);
        if(r > 1.0) color *= vec3(1.2, 1.4, 2.0);
        
        color *= hatch(hitPoint, normal, light * (0.5 + 0.5 * bumpLight));
        color = mix(color, color * vec3(1.2, 1.05, 1.1), max(-normal.y, 0.0));
        color = mix(color, color * vec3(1.05, 1.1, 1.2), max(normal.y, 0.0));

        col = color * (0.5 + 0.5 * bumpLight);
        col = mix(col, vec3(0.0), edge*.95+.05);
    }
    else {
        col = vec3(1.0, 1.0, 1.0);
    }
    
    col = mix(col, vec3(1.0), 1.0 - exp(-0.0001 * td * td));
    
    return col;
}

void mainImage( out vec4 O, in vec2 I )
{
    vec3 col = vec3(0.0, 0.0, 0.0);
    
    for(int i = 0; i < AA; i++) {
        for(int j = 0; j < AA; j++) {
            float k = 1.0 / float(AA);
            
            vec2 q = I + k * (vec2(i, j) - 0.5 * float(AA));
            col += render(q);
        }
    }
    
    col /= float(AA * AA);
    
    col = pow(col, vec3(1) / 2.2);
    
    O = vec4(col, 1.0);
}

// main wrapper
void main()
{
    vec2 fragCoord = vUV * iResolution.xy;
    vec4 outCol;
    mainImage(outCol, fragCoord);
    outCol.a *= uAlpha;
    FragColor = outCol;
}
