#version 330 core
in vec2 UV;
out vec4 fragColor;
uniform sampler2D iChannel0; // Noise tex
uniform vec2 iResolution;
uniform float iGlobalTime;
uniform vec2 iMouse;


#define E 1e-3
#define FAR_CLIP 25
#define AO 6
#define STEPS 70
float time=iGlobalTime*0.8;

// various helper functions
float imp(float k,float x){return k*x*exp(1.0-k*x);}
// rotation matrixes
mat3 rx(float a){return mat3(1.0,0.0,0.0,0.0,cos(a),-sin(a),0.0,sin(a),cos(a));}
mat3 ry(float a){return mat3(cos(a),0,sin(a),0.0,1.0,0.0,-sin(a),0.0,cos(a));}
mat3 rz(float a){return mat3(cos(a),-sin(a),0.0,sin(a),cos(a),0.0,0.0,0.0,1.0);}
// white noise
float hash(float c){return fract(sin(dot(c,12.9898))*43758.5453);}

// cheap but sufficient fake perlin noise
float no(vec3 x){
	vec3 p=floor(x);
	vec3 f=fract(x);

	f=f*f*(3.0-2.0*f);
	float n=p.x+p.y*57.0+113.0*p.z;
	return mix(mix(mix(hash(n),hash(n+1.0),f.x),
				mix(hash(n+57.0),hash(n+58.0),f.x),f.y),
			mix(mix(hash(n+113.0),hash(n+114.0),f.x),
				mix(hash(n+170.0),hash(n+171.0),f.x),f.y),f.z);
}

float subs(float a, float b){
 	return max(-a, b);   
}

// join objects with iq's smooth minimum technique
float smin(float a, float b, float k){
	float h=clamp(.5+.5*(b-a)/k,0.0,1.0);
	return mix(b,a,h)-k*h*(1.0-h);
}

// join objects with a plane
float chamfer(float a, float b, float k){
    return min(min(a,b), (a-k+b) );
}

// join objects with a corner
float corner(float a, float b, float k){
    return max(min(a,b), -(a-(k+min(a,b))+b));
}

// join objects with a quarter circle
float join(float a, float b, float r){
    float m = min(a,b);
    if( a < r && b < r ){
        return min(m, r-sqrt( (a-r)*(a-r) + (b-r)*(b-r)) );
    }
    return m;
}

// join objects with an inverse quarter circle
float bulge(float a, float b, float r){
	float m = min(a,b);
    return min(m, (a*a - r*r+b*b)*sqrt(2.0));
}

// sphere distance field
float sp(vec3 p, float s){
	return length(p)-s;
}

// box distance field
float bx(vec3 p, vec3 b){
	return length(max(abs(p)-b,0.0));
}

// box with rounded corners
float rbx(vec3 p, vec3 b, float r){
	return length(max(abs(p)-b,0.0))-r;
}

// duh
float torus(vec3 p, vec2 t){
  vec2 q = vec2(length(p.xz)-t.x,p.y);
  return length(q)-t.y;
}

float capsule(vec3 p, vec3 a, vec3 b, float r){
    vec3 pa = p - a, ba = b - a;
    float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
    return length(pa - ba*h) - r;
}

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

// geometry of the scene, overall distance field
float geometry(vec3 r){
	// floor geometry
    //float f = 100.0;//bx( vec3(r.x+5.0, r.y-1.5, r.z+6.0), vec3(7.0,1.0,7.0) );
    
    vec3 ppos = r+vec3(5.0, 0.0, 6.5);
    float block1 = rbx( rx(time*0.78)*ry(time*1.02)*rz(time*1.07)*ppos+2.0*vec3(0,sin(1.7*time),0),     vec3(5.0, 0.5, 5.0), 0.05 );
    float block2 = rbx( rx(time*0.95)*ry(time)*rz(time*0.89)*ppos+2.0*vec3(0,sin(1.8*time+0.5),0), vec3(4.0, 1.5, 4.0), 0.05 );
    float block3 = rbx( rx(time*0.91)*ry(time*1.13)*rz(time*0.83)*ppos+2.0*vec3(0,sin(1.9*time+1.0),0), vec3(3.0, 2.5, 3.0), 0.05 );
    float block4 = rbx( rx(time)*ry(time*0.61)*rz(time)*ppos+2.0*vec3(0,sin(2.0*time+1.5),0), vec3(2.0, 3.5, 2.0), 0.05 );
    float block5 = rbx( rx(time*1.05)*ry(time*0.78)*rz(time)*ppos+2.0*vec3(0,sin(2.1*time+2.0),0), vec3(1.0, 4.5, 1.0), 0.05 );
    float pyr = corner(corner(corner(corner(block1, block2, 0.5), block3, 0.5), block4, 0.5), block5, 0.5);
    
    //float ent = rbx(r+vec3(5.0, 2.0, 2.5), vec3(1.2), 0.0);
    //pyr = subs(ent, pyr);
    return pyr;
    //return corner(pyr, f, 0.5);
}

// calculate normal at a point
vec3 normal(vec3 p){
	// make smaller for more precision
	// 1e-2 tends to give nice softness though :)
	float e=1e-3;
	return normalize(vec3(geometry(p+vec3(e,0,0))-geometry(p-vec3(e,0,0)),
	                      geometry(p+vec3(0,e,0))-geometry(p-vec3(0,e,0)),
	                      geometry(p+vec3(0,0,e))-geometry(p-vec3(0,0,e))));
}

// soft shadows
// ray origin, ray direction, min and max shadow length, penumbra size (softness)
float shadow(vec3 ro, vec3 rd, float mint, float maxt, float k){
	float res=1.0;
    float t = mint;
    for(int i = 0; i < 32; i++){
		float h=geometry(ro+rd*t);
		if(h<.001)return 0.0;
		res=min(res,k*h/t);
		t+=h;
	}
	return res;
}

float ao(vec3 spoint, vec3 norm){
    float ao = 6.0;
	float occlusion = ao;
	float factor = .6;
	for(int i = 0; i < 6; ++i){
		spoint += .06*norm;
		occlusion -= geometry(spoint)*factor;
		factor *= .6;
	}
	return clamp((ao*ao)*(1.0-occlusion/ao), 0.0, 1.0);
}

// global because this may be used elsewhere too
vec3 lightpos   = vec3(2.5+2.0*sin(time*0.91),11,-20.0+30.0*cos(time*1.07));
vec3 fogcolor    = vec3(.43,.61,.52);
vec3 shade(vec3 point, float distance){
	vec3 norm = normal(point);
	vec3 lightdir   = normalize(point - lightpos);
	float intensity = 0.2 + 0.8*max(0.0, dot(lightdir, norm) );

	float shadows   = 0.1 + 0.9*shadow(point, lightdir, .06, 19.0, 13.0);

	vec3 lightcolor  = vec3(.91,.84,.73);
	vec3 shadowcolor = vec3(1)-lightcolor;
	

	vec3 amb = vec3( 0.15+0.85*ao(point, norm) );
    
    lightcolor += 20.0*pow(max(dot(normalize(point-lightpos),norm),0.0),60.0);

	return mix( lightcolor*intensity * mix(shadowcolor, vec3(1.0), shadows) * amb, fogcolor, distance/float(FAR_CLIP));
}

vec3 refl2(vec3 origin, vec3 direction){
	vec3 position = vec3(0.0);
	float distance = 0.0;
	float length = 0.0;
	float glow = 0.0;
	for(int i=0; i < STEPS; ++i){
		position = origin + direction * length;
		distance = geometry(position);
		length  += distance;
		if(float(FAR_CLIP) < length || distance < E) break;
	}
    if(float(FAR_CLIP) < length) return fogcolor;
	return shade(position, length);
}

vec3 refl(vec3 origin, vec3 direction){
	vec3 position = vec3(0.0);
	float distance = 0.0;
	float length = 0.0;
	float glow = 0.0;
	for(int i=0; i < STEPS; ++i){
		position = origin + direction * length;
		distance = geometry(position);
		length  += distance;
		if(float(FAR_CLIP) < length || distance < E) break;
	}
    if(float(FAR_CLIP) < length) return fogcolor;
	vec3 norm = normal(position);
    return 0.3*refl2(position+0.01*norm, reflect(direction+0.1*norm, norm)) + 0.7*shade(position, length);
}

vec3 march(vec3 origin, vec3 direction){
	vec3 position = vec3(0.0);
	float distance = 0.0;
	float length = 0.0;
	float glow = 0.0;
	for(int i=0; i < STEPS; ++i){
		position = origin + direction * length;
		distance = geometry(position);
		length  += distance;
		if(float(FAR_CLIP) < length || distance < E) break;
	}
    
    if(float(FAR_CLIP) < length) return fogcolor;
    vec3 norm = normal(position);
    return 0.3*refl(position+0.01*norm, reflect(direction+0.1*norm, norm)) + 0.7*shade(position, length);
}

vec3 correct(vec3 c){
    c = c*1.1-0.05;
	c = smoothstep(.1, .8, c);
	c.z = .03+pow(c.z, 1.3)*1.2;
	c.x = pow(c.x,.8);
	return .07+1.8*c;
}

void main(){
    vec2 r = iResolution.xy;
	vec2 p = 1.-2.*UV;
    p.y *= r.y/r.x;

	// camera setup
	mat3 angle     = rx(-.25)*ry(2.0)*rz(.06);
    mat3 rotation  = rz(0.5*3.141-3.141*iMouse.y)*ry( 2.0*(3.141-3.141*iMouse.x) );
	vec3 camera    = vec3(-4.0, -1.9, 1.0);
	vec3 direction = normalize(vec3(p, .5)*angle*rotation);
	
	// render visuals
	vec3 color = march(camera, direction);
	
	// correct, post and output
	color = correct(color);
    color = mix(color, 0.6*vec3(pow(length(color),1.4)), 0.0);
    
	color = pow(color, vec3(1.0/2.6));
	float vignette = .6 / (.8 + 1.3*dot(p, p));
	float noise = .02*vec3(hash(length(p)*time)).x;
	fragColor = vec4(noise+color*vignette, 1.0);
}
