uniform vec2 iResolution;
uniform float iTime;
out vec4 fragColor;

#define EPS 0.005
#define MAX_DIST 210.0
#define MAX_ITERATIONS 250

float displacement(vec3 p, float k)
{
	return sin(k*p.x)*sin(k*p.y)*sin(k*p.z);
}

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

float rand(float x)
{
    return fract(sin(dot(x, 12.9898)) * 43758.5453123);
}

float noise(vec2 uv, float intensity, float n)
{
    pR(uv, n + 1.);
    return min(1., (1. / (rand(uv.x * 20. + 1.) + rand(uv.y * 40.))) * intensity);
}

// noise: thanks to morgan3d
// https://www.shadertoy.com/view/4dS3Wd
float hash(float n) { return fract(sin(n) * 1e4); }
float hash(vec2 p) { return fract(1e4 * sin(17.0 * p.x + p.y * 0.1) * (0.1 + abs(sin(p.y * 13.0 + p.x)))); }
float noise(float x) {
    float i = floor(x);
    float f = fract(x);
    float u = f * f * (3.0 - 2.0 * f);
    return mix(hash(i), hash(i + 1.0), u);
}
float noise(vec3 x) {
    const vec3 step = vec3(110, 241, 171);
    vec3 i = floor(x);
    vec3 f = fract(x);
    float n = dot(i, step);
    vec3 u = f * f * (3.0 - 2.0 * f);
    return mix(mix(mix( hash(n + dot(step, vec3(0, 0, 0))), hash(n + dot(step, vec3(1, 0, 0))), u.x),
                   mix( hash(n + dot(step, vec3(0, 1, 0))), hash(n + dot(step, vec3(1, 1, 0))), u.x), u.y),
               mix(mix( hash(n + dot(step, vec3(0, 0, 1))), hash(n + dot(step, vec3(1, 0, 1))), u.x),
                   mix( hash(n + dot(step, vec3(0, 1, 1))), hash(n + dot(step, vec3(1, 1, 1))), u.x), u.y), u.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;
}

// returns a polygon distance field with x sides
float poly(vec2 uv, float sides)
{
    float mirrayOriginr = (step(0., uv.x) * 2.) - 1.;

    float a = atan(uv.x, uv.y) + PI * mirrayOriginr;
    float r = TAU / sides;
    return cos(floor(.5 + a / r) * r - a) * length(uv);
}

void camR(inout vec3 p) {
	pR(p.xz, iTime * 0.2);
}

// http://www.iquilezles.org/www/articles/spheredensity/spheredensity.htm
float computeFog(vec3 rayOrigin, vec3 rayDirection, vec3 sphereCenter, float sphereRadius, float dbuffer)
{
    // camR(sphereCenter.zyx);

    float ndbuffer = dbuffer / sphereRadius;
    vec3 rc = (rayOrigin - sphereCenter) / sphereRadius;
	
    float b = dot(rayDirection,rc);
    float c = dot(rc,rc) - 1.;
    float h = b * b - c;

    if(h<0.) return 0.;
	
    h = sqrt(h);
    float t1 = -b - h;
    float t2 = -b + h;

    if( t2 < 0.0f || t1 > ndbuffer ) return 0.;

    t1 = max(t1, 0.);
    t2 = min(t2, ndbuffer);

    float i1 = -(c*t1 + b*t1*t1 + t1*t1*t1/3.);
    float i2 = -(c*t2 + b*t2*t2 + t2*t2*t2/3.);
    return (i2 - i1) * (3./4.);
}

vec4 scene(vec3 p)
{
    vec3 c = vec3(0.);
    float r = 1. / 0.;;
    
    // p *= rotX(-0.1);
    // p *= rotY(-iTime * 3.14);

    pR(p.zy, -0.12);

    vec3 p_p = p;
    pR(p_p.zy, -0.2);
    pR(p_p.xz, iTime * 3.);

    // pillar
    float p_noise1 = noise(p_p + vec3(0., -iTime * 15., 0.));
    float p_noise2 = noise((p_p + vec3(0., -iTime * 15., 0.)) * 4.);
    float pillar = fBox(p_p + vec3(0., 0., 2.) + sin(p_p.y + iTime) / (sin(iTime) * 2. + 8.), vec3(0.2, 100., 0.2));
    pillar -= p_noise1 * 1.3 - 0.1;
    float pillar_w = 1. - smoothstep(EPS, EPS + 1., pillar);
    vec3 pillar_c = vec3(0.75, 0.2, 0.2) * (0.3 + p_noise2 * 0.5);
    c += pillar_w * pillar_c;
    r = min(r, pillar);

    // movement
    p.z -= iTime * 50.;

    // terrain
    float t_noise1 = noise(p.xz * 0.4);
    float t_noise2 = noise(p * 7.);
    float terrain = -p.y + t_noise2 * 0.05 + t_noise1 * 1.5 + sin(1.57 + p.x * 0.4) * 2.;
    float terrain_w = 1. - smoothstep(EPS, EPS + .1, terrain);
    vec3 terrain_c = vec3(0.7, 0.2, 0.2) * (t_noise2 * 0.5 + 0.3);
    c += terrain_w * terrain_c;
    r = fOpUnionSoft(r, terrain, 0.9);
    
    vec3 t_p = p;
    
    p.y += 10.;
    float cell = pMod1(p.z, 15.);
    float c_noise = noise(cell);

    pR(p.yx, sin(p.x * 0.2 + c_noise * 10.) * 0.5 * c_noise);
    float s_dir = (step(0.5, sin(cell)) - 0.5) * 2.;
    p.x += s_dir * iTime * 30.;
    p.x += (c_noise - .5) * iTime * 1.;
    p.x += (sin(cell) - .5) * 1.;
    pR45(p.xy);
    pR45(p.xy);
    float cell2 = pMod1(p.y, c_noise * 25. + 55.);
    // cell2 = floor(noise(t_p.z));
    p.x += (noise(cell * 1. + cell2) - 0.5) * 20. + 10.;
    // sneks
    // pR(p.xy, sin(p.y * 0.2) * 0.2);
    float snek = fCapsule(p, 2., 18.);
    float snek_w = 1. - smoothstep(EPS, EPS + .0, snek);
    vec3 snek_c = vec3(0.0);
    c += snek_w * snek_c;
    r = min(snek, r);



    c = clamp(c, 0., 1.);
    return vec4(c, r);
}

float overlay(vec2 p)
{
    float ltrbox = step(0.35, abs(p.y));
    float vignette = 
        smoothstep(0.5, 1.2, abs(p.x)) + 
        smoothstep(0.2, 0.6, abs(p.y));
    float ambient = sin(iTime * 0.4) * 0.05;
    
    return 0. -ltrbox -vignette*0.6 +ambient;
}

// 64k ??
void main()
{
    float panic = 1. + iTime * 0.05;
    vec2 uv = (gl_FragCoord.xy - (iResolution.xy * 0.5)) / iResolution.yy;
    float ol = overlay(uv);
    // uv += (vec2(rand(iTime), rand(iTime + 1.0)) - 0.5) * iTime * 0.0004; // shake
    vec3 cameraOrigin = vec3(0., -0.5, 13.);
    cameraOrigin += vec3(sin(iTime) * 3., sin(iTime * 0.4) * 0.5, sin(iTime) * 1.) * 0.5; // gungis
    vec3 cameraTarget = vec3(0., 0., -10.);
    vec3 upDir = vec3(.0, 1.0, .0);
    // upDir *= rotZ(sin(iTime * 2.) * 0.1); // gungis
    pR(upDir.xy, sin(iTime * 2.) * 0.1);
    // upDir *= rotX(-abs(sin(iTime * 0.3)) * 0.7);
    pR(upDir.zy, -abs(sin(iTime * 0.3)) * 0.7);
    vec3 cameraDir = normalize(cameraTarget - cameraOrigin);
    vec3 cameraRight = normalize(cross(upDir, cameraOrigin));
    vec3 cameraUp = cross(cameraDir, cameraRight);
    
    uv += (vec2(rand(iTime), rand(iTime + 1.0)) - 0.5) * 0.01;

    vec3 rayDir = normalize(cameraRight * uv.x + cameraUp * uv.y + cameraDir);
    float dist = EPS;
    float totalDist = 0.0;
    vec3 p = cameraOrigin;
    vec3 c = vec3(0.);

    for(int i = 0; i < MAX_ITERATIONS && dist >= EPS && totalDist < MAX_DIST; i++)
    {
        vec4 result = scene(p);
        c = result.rgb;
        dist = result.w;

        totalDist += dist;
        
        dist = min(dist, 4.);
        p += dist * rayDir;
    }

    if(dist < EPS)
    {
        vec2 eps = vec2(0.0, EPS);
        vec3 normal = normalize(vec3(
            scene(p + eps.yxx).a - scene(p - eps.yxx).a,
            scene(p + eps.xyx).a - scene(p - eps.xyx).a,
            scene(p + eps.xxy).a - scene(p - eps.xxy).a));

        vec3 lightOffset = vec3(0., 0., 0.4);
        float diffuse = max(.2, dot(-normalize(rayDir + lightOffset) * 1., normal * 1.));
        float specular = pow(diffuse, 10.);
        c *= min(1., diffuse + specular);
    }
    
    vec3 bgc = vec3(0.15);
    float bgc_w = min(1., totalDist / MAX_DIST);
    float c_w = 1. - bgc_w;
    c = c * c_w + bgc * bgc_w;

    c += noise(floor(uv * 100.), 0.005, floor(iTime * 10.)) * vec3(1., 0., 0.); // rain
    c += noise(floor(uv * 10.), 0.003, floor(iTime * 10.)) * vec3(1., 0., 0.); // rain
    c *= computeFog(cameraOrigin, rayDir, vec3(0., -2., 5.), 14. - iTime * 0.05, 32) * 1.; // vinjett

    c *= 1. - smoothstep(27., 32, iTime); // fade out
    c *= max(0., min(1., iTime * 0.15 - 0.75));

    c *= 1. + ol;
    
    c *= 1.5;

    c += (0.5 - noise(uv, 0.5, iTime)) * 0.008; // demoire noise
    fragColor = vec4(c, 1.);
}
