#define pi 3.14159

#define thc(a,b) tanh2(a*cos(b))/tanh2(a)
#define ths(a,b) tanh2(a*sin(b))/tanh2(a)
#define sabs(x) sqrt(x*x+1e-3)
//#define sabs(x, k) sqrt(x*x+k)

#define rot(a) mat2(cos(a), -sin(a), sin(a), cos(a))

// tanh approximation
#define tanh2(a) clamp(a*(27.+a*a)/(27.+9.*a*a),-1.,1.) 
                 

float cc(float a, float b) {
    float f = thc(a, b);
    return sign(f) * pow(abs(f), 0.25);
}

float cs(float a, float b) {
    float f = ths(a, b);
    return sign(f) * pow(abs(f), 0.25);
}

vec3 pal(in float t, in vec3 d) {
    return 0.5 + 0.5 * cos(2. * pi * (0.5 * t + d));
}

vec3 pal(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
    return a + b * cos(2. * pi * (c * t + d));
}

float h21(vec2 a) {
    return fract(sin(dot(a.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}

float mlength(vec2 uv) {
    return max(abs(uv.x), abs(uv.y));
}

float mlength(vec3 uv) {
    return max(max(abs(uv.x), abs(uv.y)), abs(uv.z));
}

float sfloor(float a, float b) {
    return floor(b) + 0.5 + 0.5 * tanh(a * (fract(b) - 0.5)) / tanh(0.5 * a);
}

// From iq, k = 0.12 is good
float smin(float a, float b, float k) {
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0., 1.);
    return mix(b, a, h) - k * h * (1. - h);
}

float smax(float a, float b, float k) {
    float h = clamp(0.5 - 0.5 * (b - a) / k, 0., 1.);
    return mix(b, a, h) + k * h * (1. - h); 
}

#define MAX_STEPS 400
#define MAX_DIST 100.
#define SURF_DIST .001

#define piston(p) min(length(p.xy), min(mlength(p.xz), length(p.yz)))

//nabbed from here: (i dont get it but it looks cool)
//https://www.shadertoy.com/view/7stGWj
//https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
vec3 ACESFilm(vec3 x){
    return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
}

vec3 rayOri() {
    vec2 m = iMouse.xy/iResolution.xy;
    vec3 ro = vec3(0, 6. * cos(0.25 * iTime), 10);
    ro.xz *= rot(pi/12. * thc(2., 0.5 * iTime));
    return ro;
}

float dist(vec3 p) {
    // rotate space + camera 
    // (rotating space changes reflections, rotating camera doesn't)
    p.xz *= rot(pi/12. * thc(2., 0.5 * iTime));
    
    // distance to 2 planes
    float pd = 2.5 * p.z + 7. + 0.5 - abs(p.x);  
    
    // fractal (i dont want to comment this =])
    float d = MAX_DIST;  
    float osc = 0.5 + 0.25 * cos(pi * exp(-length(p)) - iTime);
    float m = 1. - osc;
    float th = tanh(0.04 * iTime);
    for (float i = 0.; i < 4.; i++) {
        float io = pi * i / 4.;
        p = sabs(p) - sqrt(2.) * m;
        p.xy *= rot(io + 0.5 * iTime);
        p.yz *= rot(io + 0.25 * iTime);
        d = smin(d, 
                 smax(piston(p),
                      length(p) - (1. + th * ths(2., io + iTime)) * m, 
                      0.02) - 0.75 * m, 
                 0.1);
        m *= osc;
    }
    return min(pd, d);
}

float march(vec3 ro, vec3 rd, float z) {
    float dO=0.;
    float s = sign(z);
    for(int i=0; i<MAX_STEPS; i++) {
    	vec3 p = ro + rd*dO;
        float dS = dist(p);
        if (s != sign(dS)) { z *= 0.5; s = sign(dS); }
        if(abs(dS)<SURF_DIST || dO>MAX_DIST) break;
        dO += dS*z; 
    }    
    return min(dO, MAX_DIST);
}

vec3 normal(vec3 p) {
	float d = dist(p);
    vec2 e = vec2(.001, 0);    
    vec3 n = d - vec3(
        dist(p-e.xyy),
        dist(p-e.yxy),
        dist(p-e.yyx));
    
    return normalize(n);
}

vec3 rayDir(vec2 uv, vec3 p, vec3 l, float z) {
    vec3 f = normalize(l-p),
        r = normalize(cross(vec3(0,1,0), f)),
        u = cross(f,r),
        c = f*z,
        i = c + uv.x*r + uv.y*u,
        d = normalize(i);
    return d;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2  uv = (fragCoord-.5*iResolution.xy)/iResolution.y;	 
    vec3  ro = rayOri();  
    float th = tanh(0.1 * iTime);
    vec3  rd = rayDir(uv, ro, vec3(0), 0.75 + 1.25 * th);
    
    vec3 col = vec3(0);
      
    float d = march(ro, rd, 1.);
    vec3  p = ro + rd * d;
    
    if(d<MAX_DIST) {        
        vec3 n = normal(p);
        vec3 r = reflect(rd, n);

        vec3    ld = normalize(vec3(1,3,3));
        float  dif = 0.5 + 0.5 * dot(n,  ld);
        float spec = pow(dif, 32.);
        
        // specular and shadows (I think)
        vec3   q = p + 2. * SURF_DIST * n;
        float d2 = march(q, ld, 1.);
             col = spec * vec3(clamp(0.15 * d2, 0., 1.));
        
        // 50.->150. determines thickness of blue outline (sort of)       
        vec3  q2 = p + mix(50., 150., th) * SURF_DIST * r;
        float d3 = march(q2, rd, 1.);
        vec3  p2 = q2 + rd * d3;
        
        // triplanar texture map (makes it darker and redder)
        vec3 c1 = texture(iChannel0, p2.xy).rgb;
        vec3 c2 = texture(iChannel0, p2.yz).rgb;
        vec3 c3 = texture(iChannel0, p2.zx).rgb;
              n = abs(n);
        vec3  c = n.z * c1 + n.x * c2 + n.y * c3;
            col *= c;
        
        // cubemap reflections (looks okay without)
        vec3 tx3 = texture(iChannel1, r).rgb;
             col *= 2. * (1. + thc(4., pi * 1.1 + 4. * tx3)) * tx3;
             col = clamp(col, 0., 1.);

        // blue outline
        col = mix(col, vec3(1.-c), clamp(0.25 * d3, 0., 1.));
  
        // fog
        vec3 fogCol = mix(vec3(0), vec3(234,201,103)/255., 1.-th);
        col = mix(col, fogCol, 1.-1./cosh(.06*length(p.xy)));
        
        // tonemapping (probably)
        col = ACESFilm(col);
    }

    col = pow(col, vec3(1./2.6));	// gamma correction
    
    fragColor = vec4(col,1.0);
}
