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

#define E 3e-4
#define FAR_CLIP 30
#define AO 6
#define STEPS 100
float time=iGlobalTime;
vec3 gdir;

// 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 snoise(vec3 x) {
    vec3 p = floor(x);
    vec3 f = fract(x);
	f = f*f*(3.0-2.0*f);
	
	vec2 uv = (p.xy+vec2(37.0,17.0)*p.z) + f.xy;
	vec2 rg = texture2D( iChannel0, (uv+ 0.5)/256.0, -100.0 ).yx;
	return mix( rg.x, rg.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 min(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));
}

float pyr(vec3 p, float h){
    p.y=-p.y;
	p.y-=h/3.0;
	vec3 q=abs(p);
	return max(p.y, (q.x+q.y/1.2+q.z-h)/3.0);
}

// geometry of the scene, overall distance field
float geometry(vec3 r){
	// floor geometry
    float f = bx( vec3(r.x, r.y-2.2, r.z), vec3(90.0,1.0,90.0) );
    
    float bl = pyr(ry(3.141/4.0+time*0.7)*r+vec3(0.0, 0.6+iMouse.y, 0.0), 0.45);
    //float bl = pyr(r+vec3(1.0, 0.6, 1.0), 1.0);
    
    float wf = 1.0-pow(smoothstep(0.1, 0.7, iMouse.y), 2.0);
    float modul = wf*pow( 0.5+0.5*sin(-11.0*time+length(r.xz*60.0))/sqrt(2.5+length(r.xz*15.0)), 4.0-2.5*wf);
    modul += wf*0.03*sin(80.0*dot(cos(r.x),sin(r.z))+time);
	float bd = rbx(r,    vec3(.4, .75+0.02*modul, .4), 0.008);
	return min(min(f, bd),bl);
}

// calculate normal at a point
vec3 normal(vec3 p){
	// make smaller for more precision
	// 1e-2 tends to give nice softness though :)
	float e=4e-4;
	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(0,15,12);
vec3 fogcolor    = vec3(0.1);

vec3 shade(vec3 point, float distance){
    //lightpos.xz *= 3.0*vec2(sin(time*0.91), cos(time*1.07));
	vec3 norm = normal(point);
	vec3 lightdir   = normalize(point - lightpos);
	float intensity = pow(max(0.0, dot(lightdir, norm) ), 0.5);
    //vec3 ld2 = normalize(point-vec3(lightpos.xy,-lightpos.z));
    intensity += 0.5*pow(max(0.0, dot(normalize(point - lightpos.z-2.0*lightpos.z), norm) ), 0.5);

	float shadows   = 0.2 + 0.8*shadow(point, lightdir, .05, 9.0, 20.0);
    shadows += 0.4 + 0.6*shadow(point, normalize(point - lightpos.z-2.0*lightpos.z), .05, 9.0, 20.0);
    shadows *= 0.5;

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

	vec3 amb = vec3( 0.15+0.85*ao(point, norm) );
    intensity *= 1.0+10.0*pow(dot(-gdir, norm), 2.0);

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

vec3 secrefl(vec3 origin, vec3 direction){
	vec3 position = vec3(0.0);
	float distance = 0.0;
	float length = 0.0;
	float glow = 0.0;

	// marching loop
	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 reflection(vec3 origin, vec3 direction){
	vec3 position = vec3(0.0);
	float distance = 0.0;
	float length = 0.0;
	float glow = 0.0;

	// marching loop
	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 n = normal(position);
    return 0.5*secrefl(position+0.01*n, reflect(direction, n))+0.5*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 n = normal(position);
    return 0.5*reflection(position+0.01*n, reflect(direction, n))+0.5*shade(position, length);
}

vec3 correct(vec3 cc){
    float c = length(cc);
    
    c = c*1.1-0.05;
	c = smoothstep(.4, 1.2, c);
	c = .03+pow(c, 1.3)*1.2;
	c = pow(c,.9);
	return vec3(1.5*c);
}

float rp1 = 1.5*(iMouse.x);

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

	// camera setup
	mat3 angle     = rx(-.12)*ry(3.141);
    //mat3 rotation  = rz(0.5*3.141-3.141*iMouse.y/r.y)*ry( 2.0*(3.141-3.141*iMouse.x/r.x) );
	vec3 camera    = vec3(0.0, -1.4+0.1*rp1, 3.0+rp1*0.5);
	vec3 direction = normalize(vec3(p, 4.0+2.0*(rp1))*angle);
    gdir = direction;
	
	// render visuals
	vec3 color = march(camera, direction);
	
	// correct, post and output
	color = correct(color);
	color = pow(color, vec3(1.0/2.2));
	float vignette = 0.8 / (1.0 + 2.2*dot(p, p));
	float noise = .02*vec3(hash(length(p)*time)).x;
	fragColor = vec4(noise+color*vignette, 1.0);
}