// frag.glsl
#version 330

//distance field functions are from http://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm

#define M_PI 3.141592653589

uniform float u_time_s = 30;
uniform vec2 u_resolution;

uniform float u_scene;

uniform vec3 u_camPos;
uniform vec3 u_camLook;
uniform vec3 u_camUp;

uniform float u_houseHight;
uniform float u_sphereHight;

const float MAX_DIST = 4000;
const int maxSteps = 100;
const float minDist = 0.01;

const vec3 color_sky = vec3(0.0,0.9,1.0);

struct SceneResult{
	float dist;
	float material;
};

struct RaymarchResult{
	float dist;
	float material;
	vec3 collision;
};

//list of matrials, keep in > order, must resolve in same order
const float m_background = 0.0;
const float m_ground = 1.0;
const float m_cubes = 2.0;
const float m_mountain = 3.0;
const float m_house = 4.0;
const float m_sphere = 5.0;
//list of materials end

//http://www.neilmendoza.com/glsl-rotation-about-an-arbitrary-axis/
mat4 rotationMatrix(vec3 axis, float angle)
{
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;
    
    return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,
                oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,
                oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,
                0.0,                                0.0,                                0.0,                                1.0);
}

vec3 rotate(vec3 pos, vec3 axis, float angle){
 	vec4 pos4 = vec4(pos.xzy, 1.0);
    mat4 rot = rotationMatrix(axis, angle);
    vec4 rp = rot * pos4;
    return rp.xyz;
}

//signed sphere
float sphere( vec3 p, float s )
{
  return length(p)-s;
}

//signed box
float box(vec3 pos, vec3 b){ 
	vec3 d = abs(pos) - b;
	return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));	
}

float opTwist(vec3 pos, vec3 b){
   	float c = cos(20.0*pos.y);
    float s = sin(20.0*pos.y);
    mat2  m = mat2(c,-s,s,c);
    vec3  q = vec3(m*pos.xz,pos.y);
    return box(q, b);
}

float displacement(vec3 p){
    float t = u_time_s; 
    float val = sin(t*0.5)*2.0;
    float spd = 2.0;
    float amnt = 0.05;
    float disp = 0.0;
    //float disp = sin(val*p.x*spd)*sin(val*p.y*spd)*sin(val*p.z*spd)*amnt;
    //float disp = sin(tan(p.x));
    //float disp = tan(val*p.x)*tan(val*p.y)*tan(val*p.z);
    return disp;
    //return max(-1.0, min(disp, 1.0));
    //return sin(val*p.x)*sin(val*p.y)*sin(val*p.z);
}

float opDisplace(vec3 pos, vec3 b){
 	float d1 = opTwist(pos, b);
    float d2 = displacement(pos); //sin(t)*sin(t); //sin(pos.x*0.5*t) + 0.1;
    //float d2 = sin(pos.x*0.5*t) + 0.1;
    return d1 + d2;
}

float opTx(vec3 pos, vec3 b){
    //float t = u_time_s; 
 	//vec3 q = rotate(pos, vec3(0,0,1), t*2.0);
    //vec3 q = rotate(pos, vec3(sin(t)*5.0,tan(t),0), sin(t)*2.0);

    //return opDisplace(q, b);
    return box(pos, vec3(1,1,1));
}

float opRep2(vec3 pos, vec3 c, vec3 b){
	vec3 q = mod(pos,c)-0.5*c;
    return opTx(q, b);
}

float opRep(vec3 pos, vec3 c){
	vec3 q = mod(pos,c)-0.5*c;
    return box(q, vec3(1,1,1));
}

vec3 helix(float t, vec2 params){
  float a = params.x;
  float b = params.y;
  float x = a * cos(t);
  float y = a * sin(t);
  float z = b * t;
  return vec3(x,y,z);
}

float helixSphere(vec3 pos, float t){
	vec3 p0 = helix(t, vec2(80, 5));
	vec3 p = pos;
	p.x -= p0.y;
	p.y -= p0.z;
	p.z -= p0.x;
	return sphere(p, 10);	
}

SceneResult s_spheres(vec3 pos){
	//X spheres, delayed by virtual time
	float t0 = u_sphereHight;
	float dist = 100000.0;
	for(float i = 0.0; i<40.0; i+=1.0){
		float d = helixSphere(pos, t0 - i*2);
		dist = min(dist,d);
	}
	return SceneResult(dist, m_sphere);
}

SceneResult house(vec3 pos){
	float height = u_houseHight;
	float dist = box(pos, vec3(40.0,height, 40.0));
	
	//tower top
	pos.y -= 500;
	float tower1 = box(pos, vec3(20.0, 20.0, 60.0));
	float tower2 = box(pos, vec3(60.0, 20.0, 20.0));
	dist = max(dist, -tower1);
	dist = max(dist, -tower2);
	
	//windows
	pos.y += 200; //relative to last
	float w1 = box(pos, vec3(20.0, 40.0, 60.0));
	float w2 = box(pos, vec3(60.0, 40.0, 20.0));
	dist = max(dist, -w1);
	dist = max(dist, -w2);
	
	return SceneResult(dist, m_house);
}

vec3 splitSpace(vec3 pos){
	float d = 130;
	vec3 p = pos;
	
	if(p.z > 0){
		p.z -= d;
	}else{
		p.z += d;
	}
	if(p.x > 0){
		p.x -= d;
	}else{
		p.x += d;
	}
	
	return p;
}

SceneResult mountain(vec3 pos){
	pos.y += 400;
	float dist = sphere(pos, 500);
	return SceneResult(dist, m_mountain);
}

SceneResult cubes(vec3 pos){
	float d = opRep2(pos, vec3(20,20,20), vec3(1,1,1));
	return SceneResult(d, m_cubes);
}

SceneResult ground(vec3 pos){
	float t = u_time_s;
	vec3 p = pos;
	p.x = p.x+t*10;
	p.z = p.z-t;
	float height = 10+sin(p.x*0.5)*sin(t*0.01);
	float dist = pos.y - height * sin(p.x*0.1)*sin(p.z*0.1);
	return SceneResult(dist, m_ground);
}

SceneResult combineScenes(SceneResult s1, SceneResult s2){
	if(s1.dist < s2.dist){
		return s1;
	}else{
		return s2;
	}
}

SceneResult estimateDist(vec3 pos){
   	float scene = u_scene;
   	
   	//scene 1, water + mountain 
   	SceneResult comb = ground(pos);
   	if(scene > 0){
   		SceneResult d = ground(pos);
   		comb = combineScenes(comb, d);
   	}
   	
   	if(scene > 0){
   		SceneResult d = mountain(pos);
   		comb = combineScenes(comb, d);
   	}
   	
   	//scene 2, house
   	if(scene > 1 && scene < 5){
   		SceneResult d = house(pos);
   		comb = combineScenes(comb, d);
   	}
   	
   	//scene 3, spheres, NOTE only shown during scene 3
   	if(scene > 2 && scene < 4){
   		SceneResult d = s_spheres(pos);
   		comb = combineScenes(comb, d);
   	}
   	
   	//scene 4, house down, drawn by scene 3 handler
   	
   	//scene 5, multiple houses
   	if(scene > 4){
   		vec3 p = splitSpace(pos);
   		SceneResult d = house(p);
   		comb = combineScenes(comb, d);
   	}
   	
   	//scene 6, multiple spheres?
   	if(scene > 5){
   		vec3 p = splitSpace(pos);
   		SceneResult d = s_spheres(p);
   		comb = combineScenes(comb, d);
   	}
   	
   	
   	return comb;
}

//a ray marching algorithm
RaymarchResult raymarch(vec3 origin, vec3 dir, out float totalDist, out int steps, vec2 uv){
	totalDist = 0.0;
	RaymarchResult scene = RaymarchResult(0.0, 0.0, vec3(0,0,0));
	for(int i = 0; i < maxSteps; i++){
		steps = i;
        vec3 p = origin + totalDist * dir;		
    
    	SceneResult sr = estimateDist(p);
    	scene = RaymarchResult(sr.dist, sr.material, p);
        float dist = scene.dist;
		totalDist += dist;
		if(dist < minDist){
			break; //too close 
		}else{
			//revert background if we do not have hit something yet..
			//ONLY if the total dist is over max dist!
			if(totalDist > MAX_DIST){
				scene = RaymarchResult(MAX_DIST, 0.0, vec3(0,0,0));
			}
		}			
	}
	return scene;
}

//calc camera position and look direction
void calcCam(vec3 camLocation, vec3 camPointing, vec3 camScreenUp, 
             out vec3 camDirectionF, out vec3 camDirectionR, out vec3 camDirectionU){
	camDirectionF =  normalize(camPointing - camLocation); 
	camDirectionR = normalize(cross(camDirectionF, camScreenUp) );
	camDirectionU = normalize(cross(camDirectionR, camDirectionF));
}


vec3 materialColor(RaymarchResult scene, float relativeSteps){
		float t = u_time_s;
		
		//background - sky
		if(scene.material < 1){
			return color_sky;
		}
		
		//ground - water
		if(scene.material < 2){
			float red = 0.1;
			float green = 0.1;
			float blue = 1.0;
			return vec3(red, green, blue) * relativeSteps;
		}
		
		//cubes - skipped for now..
		if(scene.material < 3){
			return vec3(1.0, 1.0, 0.0) * relativeSteps;
		}
		
		//mountain - brown
		if(scene.material < 4){
			return vec3(0.8, 0.5, 0) * relativeSteps;
		}
		
		//house - red
		if(scene.material < 5){
			return vec3(1.0, 0.0, 0.0) * relativeSteps;
		}
		
		//spheres - todo color
		if(scene.material < 6){
			return vec3(0.0, 1.0, 0.0) * relativeSteps;
		}
		
		//should not reach this point
		return vec3(0,0,0);
}

void main(){
  	float t = u_time_s;
    vec3 camPos = u_camPos;
	vec3 camLook = u_camLook;
	vec3 camScreenUp = u_camUp;
   	
    
    
    //raymarch below
    
    vec3 camDir;
	vec3 camRight;
	vec3 camUp;
	calcCam(camPos, camLook, camScreenUp, camDir, camRight, camUp);
    
    //resolution independend coords + aspect ratio
	vec2 uv = gl_FragCoord.xy * 2.0 / u_resolution.xy - 1.0;
	uv.y *= u_resolution.y / u_resolution.x;
    
    float camDistFromImagePlane = 1.0;
    
    vec3 ro = camPos + camDistFromImagePlane * camDir + uv.x*camRight + uv.y*camUp;
    vec3 rd = normalize(ro - camPos);
    
    float totalDist;
	int steps;
	RaymarchResult sr = raymarch(ro, rd, totalDist, steps , uv);
	float relativeSteps = float(steps) / float(maxSteps);
        
    //color based on raymarch dir
    float o = 1.0 - relativeSteps;
	vec3 color;
	//if max distance or max step size 
	//if(steps >= 100){
	//	//render a "sky" color
	//	color = color_sky;
	//}else{
		//render color based on distance or steps
		color = materialColor(sr, o);
		
		//color = vec3(o,o,0);
	//}
    gl_FragColor = vec4(color ,1.0);
}