uniform float time;
uniform vec4 resolution;

uniform float voxelCam;
uniform float voxelLens;

float vecmin(vec3 x){
    return min(x.x, min(x.y, x.z));
}

float seed;
float hash(){return fract(sin(seed+=0.177)*54531.2322578);}
vec2 hash2(){return vec2(hash(),hash());}

float hash(vec2 p){
    vec3 p3 = fract(vec3(p.xyx)*.1031);
    p3 += dot(p3,p3.yzx+19.19);
    return fract((p3.x+p3.y)*p3.z);
}

float hash(vec3 p3){
    p3 = fract(p3*.1031);
    p3 += dot(p3,p3.yzx+19.19);
    return fract((p3.x+p3.y)*p3.z);
}

vec2 hash2(float p){
    vec3 p3 = fract(vec3(p)*vec3(.1031,.1030,.0973));
    p3 += dot(p3,p3.yzx+19.19);
    return fract((p3.xx+p3.yz)*p3.zy);
}

vec3 cosw(const vec3 n, float seed){
  	vec2 rv2 = hash2(seed);
	vec3  uu = normalize(cross(n, vec3(0.0,1.0,1.0)));
	vec3  vv = normalize(cross(uu, n));
	float ra = sqrt(rv2.y);
	float rx = ra*cos(6.2831*rv2.x); 
	float ry = ra*sin(6.2831*rv2.x);
	float rz = sqrt(1.0-rv2.y);
	vec3  rr = vec3(rx*uu + ry*vv + rz*n);
    return normalize(rr);
}

bool geometry(vec3 cell){
    return cell.y < 10.0*hash(vec3(cell.xz, 0.0002*floor(-time*12.0)/12.0 ));
}

vec3 intersect(inout vec3 pos, inout vec3 dir){
    const int far = 120;
    
    vec3 cam = pos;
    vec3 idist;
    for(int i = 0; i < far; ++i){
        vec3 cell = floor(pos);
        
        if(geometry(cell))
            break;
        
        vec3 planes = cell+min(sign(dir),1.0);
        idist = (planes-cam)/dir;
                
        float t = vecmin(idist);
        
        pos = cam+dir*t + sign(dir)*0.00001;
    }
    
    return idist;
}

float indirect(vec3 pos, vec3 dir){
    const int far = 15;
    
    vec3 cam = pos;
    vec3 idist;
    for(int i = 0; i < far; ++i){
        vec3 cell = floor(pos);
        
        if(geometry(cell))
            break;
        
        vec3 planes = cell+max(sign(dir), 0.0);
        idist = (planes-cam)/dir;
                
        float t = vecmin(idist);
        
        pos = cam+dir*t + sign(dir)*0.00001;
    }
    
    if(pos.y > 10.0) return 1.0;
    return 0.0;
}

float ambient_occlusion(vec3 pos, vec3 normal){
    const int samples = 4;
    float cum = 0.0;
    vec3 ro = pos;
    float f = hash(pos);
    
    for(int i = 0; i < samples; ++i){
        float seed2 = float(i)+f+seed;
        vec3 ro = pos+0.001*normal;
        vec3 dir = cosw(normal, seed2);
        //vec3 idist = intersect(ro, dir);
        //cum += length(idist)*0.01;
        cum += indirect(ro, dir);
    }
    
    return cum/float(samples);
}

vec3 render(vec3 pos, vec3 dir){
    vec3 idist = intersect(pos, dir);
    vec3 normal = step(idist, idist.yzx) * step(idist, idist.zxy) * -sign(dir);
    vec3 color = vec3(ambient_occlusion(pos, normal));
    return color;
}

vec3 tonemap(vec3 color){
	float A = 0.15;
	float B = 0.50;
	float C = 0.10;
	float D = 0.20;
	float E = 0.01;
	float F = 0.30;
	float W = 90.0;
	float exposure = 0.9;
	color *= exposure;
	color = ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F;
	float white = ((W * (A * W + C * B) + D * E) / (W * (A * W + B) + D * F)) - E / F;
	color /= white;
	
	return color;
}

void main(){
    vec2 uv = gl_FragCoord.xy/resolution.xy;
	
    vec3 cam = vec3(0,20,50);
    vec3 dir = normalize(vec3(uv, -2.0));
    
    float a = time*0.1;
    const float b = 0.75;
    cam.xz *= mat2(sin(a), cos(a), -cos(a), sin(a));
    dir.yz *= mat2(sin(b), cos(b), -cos(b), sin(b));
    dir.xz *= mat2(sin(a), cos(a), -cos(a), sin(a));
    
    vec3 pos = cam;
    vec3 idist = intersect(pos, dir);
    
    seed = uv.x + uv.y * 3.43121412313 + fract(11.2345314312*time);
    
    vec3 cum = vec3(0);
    
    float j = voxelCam;
    float k = -(-1.0+2.0*voxelLens)*0.25;
    
    float height = mix(-60.0, -10.0, j);
    float lens = mix(14.0, 5.2, pow(j, 0.25)+k);
    float zoom = mix(1.0, 4.0, j);
    
    const int samples = 20;
    for(int s = 0; s < samples; s++){
        vec3 ww = normalize(vec3(0,height,0)-cam);
        vec3 uu = normalize(cross(normalize(vec3(0,1,0)),ww));
        vec3 vv = normalize(cross(ww,uu));
        
        vec2 rand = hash2();
		vec2 p = -1.0 + 2.0 * (uv + 1.0*(-1.0+2.0*rand)/resolution.xy);
        p.x *= resolution.x/resolution.y;
        
        vec2 lens_sample = vec2(cos(rand.x*3.14159*2.0),sin(rand.x*3.14159*2.0))*sqrt(rand.y);
        vec3 lens_pos = cam + (lens_sample.x*uu + lens_sample.y*vv) * 0.4;
        cum += render(lens_pos, normalize(cam+(p.x*uu + p.y*vv + zoom*ww)*lens-lens_pos));
    }
    
    uv = -1.0+2.0*uv;
    float rf = sqrt(dot(uv, uv)) * 0.7;
    float rf2_1 = rf * rf + 1.0;
    cum *= 1.0 / (rf2_1 * rf2_1);
    
    cum = tonemap(8.0*cum/float(samples));
    gl_FragColor.rgb = pow(cum, vec3(1.0/2.2));
    gl_FragColor.rgb = smoothstep(-0.1,0.9, gl_FragColor.rgb);
    gl_FragColor.a = 1.0;
}