#define AA 2 // antialiasing, set it to 1 if you have a slow computer
#define SHUTTER_SPEED 24. // motion blur amount

// ACES tonemapping
vec3 ACES(vec3 x) {
    float a = 2.51;
    float b =  .03;
    float c = 2.43;
    float d =  .59;
    float e =  .14;
    return (x*(a*x+b))/(x*(c*x+d)+e);
}

float time; // time value

// ray box intersection function
// thanks to iq: https://iquilezles.org/articles/intersectors/
vec2 boxIntersect(vec3 ro, vec3 rd, vec3 ce, vec3 ra) {
    vec3 oc = ro - ce;
    vec3 m = 1./rd;
    vec3 n = -m*oc;
    vec3 k = abs(m)*ra;

    vec3 t1 = n - k;
    vec3 t2 = n + k;

	float tN = max(max(t1.x, t1.y), t1.z);
	float tF = min(min(t2.x, t2.y), t2.z);

	if(tN>tF || tF<0.) return vec2(-1);

    return vec2(tN, tF);
}

// 2d rotation function
mat2 rot(float a) {
    float s = sin(a), c = cos(a); // sine and cosine
    return mat2(c, -s, s, c);
}

// random number between 0 and 1
float hash(float n) {return fract(sin(n)*43758.5453123);}

// 3d noise function by iq
float noise(vec3 x) {
    vec3 p = floor(x);
    vec3 f = fract(x);
    f = f*f*(3.-2.*f); // S curve

    float n = p.x + p.y*157. + 113.*p.z;

    return mix(mix(mix(hash(n+  0.), hash(n+  1.),f.x),
                   mix(hash(n+157.), hash(n+158.),f.x),f.y),
               mix(mix(hash(n+113.), hash(n+114.),f.x),
                   mix(hash(n+270.), hash(n+271.),f.x),f.y),f.z);
}

// 3d fractal noise
float fbm(vec3 p) {
    float f = 0.;
    f += .5*noise(p);
    f += .25*noise(2.*p);
    f += .125*noise(4.*p);
    f += .0625*noise(8.*p);
    return f;
}

// box sdf
float sdBox(vec3 p, vec3 b) {
    vec3 q = abs(p) - b;
    return length(max(q,0.)) + min(max(q.x,max(q.y,q.z)),0.);
}

// vec3 to vec3 hash (between 0 and 1)
vec3 hash(vec3 p) {
	p = vec3(dot(p,vec3(127.1,311.7, 74.7)),
			 dot(p,vec3(269.5,183.3,246.1)),
			 dot(p,vec3(113.5,271.9,124.6)));

	return fract(sin(p)*43758.5453123);
}

// scene sdf
float map(vec3 p) {
    float d = 1e10; // distance

    for (int i=0; i<8; i++) { // generate eight boxes
        vec3 n = hash(vec3(2+i)); // random vec3

        vec3 q = p-sin(n*time)*vec3(1.25,1,1.25); // box position
        q.yz *= rot(3.*sin(n.x*time-2.)); // rotation around the x axis
        q.xz *= rot(2.*sin(n.x*time+7.)); // rotation around the y axis

        // add the box
        float b = max(sdBox(q,vec3(.3,.15,.3))-.01, -sdBox(q,vec3(.2)));
        d = min(d, b);
    }

    // floor
    d = min(d, sdBox(p-vec3(0,-1.3,0),vec3(1.8,.2,1.8)-.02)-.02);

    return d;
}

// raymarching function
float intersect(vec3 ro, vec3 rd, float tmin, float tmax) {
    float t = tmin; // distance travelled

    for (int i=0; i<256; i++) { // marching loop
        vec3 p = ro + rd*t; // current point

        float h = map(p); // distance to the scene
        if (h<.001) return t; // we hit the surface

        t += h; // march
        if (t>tmax) break;
    }
    return -1.;
}

// normal estimation
vec3 calcNormal(vec3 p) {
    float h = map(p);
    const vec2 e = vec2(.0001,0); // epsilon

    return normalize(h - vec3(map(p-e.xyy),
                              map(p-e.yxy),
                              map(p-e.yyx)));
}

// soft shadow function
// thanks to iq: https://iquilezles.org/articles/rmshadows/
// k is the softness of the shadow
float shadow(vec3 ro, vec3 rd, float tmax, float k) {
    float res = 1.; // final result
    for (float t=0.; t<tmax;) {
        vec3 p = ro + rd*t;

        float h = map(p);
        if (h<.001) return 0.;

        res = min(res, k*h/t); // get the closest penumbra
        t += h;
    }
    return res*res*(3.-2.*res); // S curve
}

// ambient occlusion function by me
// ra is the occlusion distance
float occlusion(vec3 p, vec3 n, float ra) {
    float res = 0.;
    const int N = 16; // samples

    for (int i=0; i<N; i++) {
        float h = ra * float(i)/float(N); // sampling distance
        res += clamp(.5+.5*map(p + n*h)/h,0.,1.);
    }
    res /= float(N);

    return res*res*res;
}

float pattern(vec3 p) {
    return noise(p*1.5+3.*fbm(p*3.));
}

// cubemap texture
vec3 skybox(vec3 rd) {
    return vec3(1,.8,.8)*pow(texture(iChannel0, rd+.5*fbm(rd*5.)).rgb, 1.7*vec3(.5,1,1));
}

// rendering function
vec3 render(vec3 ro, vec3 rd) {
    vec3 col = .35*skybox(rd); // background

    // bounding volume
    vec2 bound = boxIntersect(ro, rd, vec3(0), vec3(1.8,1.5,1.8));
    float tmin = bound.x;
    float tmax = bound.y;
    float t = intersect(ro, rd, tmin, tmax); // distance

    if (t>0.) { // we hit the surface
        vec3 p = ro + rd*t; // hit point
        vec3 n = calcNormal(p); // surface normal
        vec3 r = reflect(rd, n); // reflected vector

        float occ = occlusion(p, n, .5); // ambient occlusion
        float fre = 1.+dot(rd, n); // fresnel
        float ref = shadow(p+n*.002, r, 4., 16.); // reflection

        // final color
        col = vec3(0);
        col += pattern(p*3.)*.1*occ*(.5+.5*n.y); // diffuse
        col += .9*(.04+.96*pow(fre,5.))*ref*skybox(r); // specular
    }
    // output
    return col*1.8;
}

// camera function
mat3 setCamera(vec3 ro, vec3 ta) {
    vec3 w = normalize(ta - ro); // forward vector
    vec3 u = normalize(cross(w, vec3(0,1,0))); // side vector
    vec3 v = cross(u, w); // up vector
    return mat3(u, v, w);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec3 tot = vec3(0); // final color

    // AA for loops
    for (int m=0; m<AA; m++)
    for (int n=0; n<AA; n++) {
        vec2 off = vec2(m,n) / float(AA) - .5; // AA offset
        // pixel coordinates centered at the origin
        vec2 p = (fragCoord+off - .5*iResolution.xy) / iResolution.y;

        // motion blur
        float mb = float(m + n*AA);
        mb += texelFetch(iChannel1, ivec2(fragCoord)&1023, 0).x; // blue noise
        time = 6.+iTime - mb/float(AA*AA) / SHUTTER_SPEED; // time value

        float an = .3*time; // camera angle
        vec3 ro = vec3(7.*sin(an),2.5,7.*cos(an)); // ray origin
        vec3 ta = vec3(0); // target
        mat3 ca = setCamera(ro, ta); // camera matrix

        vec3 rd = ca * normalize(vec3(p,2.1)); // ray direction

        // render
        vec3 col = render(ro, rd);

        col = pow(col, vec3(.4545)); // gamma correction
        col = ACES(col);

        tot += col;
    }
    tot /= float(AA*AA);

    tot = tot*tot*(3.-2.*tot); // contrast

    // vignette
    vec2 q = fragCoord/iResolution.xy;
    tot *= .2+.8*pow(32. * q.x*q.y*(1.-q.x)*(1.-q.y), .1);

    tot = tot*vec3(1.3,1.2,1) - vec3(.15,.1,0); // color grading

    // output
    fragColor = vec4(tot,1.0);
}