#include "inc/uniforms.glsl"
#include "inc/utils.glsl"
#line 4 1

#ifdef VERTEX
#include "inc/geometry_io.vs"

#define pi (3.141592653589)

out vec3 ex_ModelPosition;
out vec3 ex_RayDir; // ray direction in model space

void process() 
{
	vec4 p = in_Position;
	ex_ModelPosition = p.xyz;
	vec4 viewPos = modelView * p;
	
	vec4 dir = vec4(viewPos.xyz, 0.0);
	ex_RayDir = (inverseModelView * dir).xyz;
	
	ex_Position = viewPos;
	gl_Position = modelViewProject * p;
}

#define PROCESSING_FUNCTION process
#include "inc/geometry.vs"

#endif

 
#ifdef FRAGMENT
#include "inc/geometry_io.fs"
#define pi (3.141592653589)

in vec3 ex_ModelPosition;
in vec3 ex_RayDir; // modelspace

uniform sampler2D colorbuffer;
uniform sampler2D depthbuffer;

#define pi (3.141592653589)
uniform vec4 diffuseColor;
uniform vec4 specularColor;
uniform float colorBoost;
uniform float roughness;
uniform float metallic;

float sphere(vec3 pos, float size){
	return length(pos)-size;
}

float scene(vec3 pos){
	float sum=0.;
	float num=10.;
	float size=0.05;
	for(int i=0;i<num;i++){
		vec3 spherepos=vec3(sin(i+p_time),sin(i*i+p_time),sin(i*i*i+p_time))*0.5  ;
		sum+=1./(length(spherepos+pos)-i*size*0.5);
	}
	
	return 1./sum-size;
}

float epsilon=0.0001;

vec3 getGradient(vec3 pos,float scale){
	vec3 e1=vec3(epsilon*scale,0.0,0.0);
	vec3 e2=vec3(0.0,epsilon*scale,0.0);
	vec3 e3=vec3(0.0,0.0,epsilon*scale);
	return (scene(pos+e1)*e1+scene(pos+e2)*e2+scene(pos+e3)*e3)-
			(scene(pos-e1)*e1+scene(pos-e2)*e2+scene(pos-e3)*e3);
}

vec4 march(vec3 ray, out float out_min_dist, in out vec3 pos){
	float dist=0.;
	float surf_dist;
	float min_dist = 99999.0;
	for(int i=0;i<75;i++){
		surf_dist=scene(pos);
		min_dist = min(min_dist, surf_dist);
		dist+=surf_dist;
		pos+=ray*surf_dist;
	}
	out_min_dist = min_dist;
	if(surf_dist>0.05) return vec4(1000.);
	
	vec4 ret=vec4(normalize(getGradient(pos,1.)),dist);
	
	return ret;
}

void main()
{

	vec3 pos = ex_ModelPosition; // tai sitte tää: (inv*vec4(ex_Position.xyz, 1.0)).xyz;
	vec3 dir = normalize(ex_RayDir);
	vec3 hit_point = vec3(0.0);
	
	float surface_min_dist = 99999.0;
	vec4 march_result=march(dir, surface_min_dist, pos);
	
	// http://stackoverflow.com/a/12904072
	vec4 clip_space_pos  = modelViewProject * vec4(pos, 1.0);
	float far = gl_DepthRange.far; float near=gl_DepthRange.near;
	float ndc_depth = clip_space_pos.z / clip_space_pos.w;
	float depth = (((far-near) * ndc_depth) + near + far) / 2.0;
	
	
	
	vec3 normal=(modelMatrix*vec4(march_result.xyz,0.)).xyz;
	
	vec3 result = vec3(0.5);
	
	vec3 V = normalize(-clip_space_pos.xyz);
	vec3 N = normalize(normal); // viewspace normal
		
	{
		vec3 lightDirs[2];
		vec3 lightIntensities[2];
		lightDirs[0] = normalize(vec3(-0.5, -0.2, -0.1));
		lightIntensities[0] = vec3(1.0,0.7,0.5)*5.0;
		
		lightDirs[1] = -normalize(vec3(-0.5, -0.2, -0.1));
		lightIntensities[1] = vec3(0.6, 0.2, 0.7)*3.0;
		#define LIGHTS_DEFINED
		vec4 sampld = texture(tex, ex_UV.xy);
	vec3 albedo = sampld.rgb * diffuseColor.rgb;
	float sampledRoughness = texture(roughnessMap, ex_UV.xy).r;
	vec3 spec = mix(specularColor.rgb, diffuseColor.rgb, metallic);
	vec3 col = vec3(0.0);
	
		vec3 final = vec3(0.0);
		for (int i = 0;i < 2 ; i++) {
			vec3 lightdir = lightDirs[i];
			lightdir = (viewMatrix*vec4(lightdir, 0.0)).xyz;
			vec3 L = -lightdir; // vector to light
			
			// these two are defined outside
			//vec3 V = normalize(-ex_Position.xyz);
			//vec3 N = ex_Normal.xyz; // viewspace normal
			
			float NdotL = max(0.0, dot(L, N)*0.5+0.5); //tweak here!
			vec3 H = normalize(V + L);
			float HdotN = max(0.0, dot(H, N));
			float NdotV = max(0.0, dot(N, V));
			float HdotV = max(0.0, dot(H, V));
			
			vec3 diffuse = vec3((1.0/pi) * max(0.0, 1.0-length(spec)));
			diffuse *= albedo * NdotL;
			
			// Unreal Engine 4 shading
			float rough = roughness*min(1.0,max(0.0, 1.0 - 0.7*sampledRoughness));
			float alpha = rough*rough;
			vec3 F0 = spec;

			// GGX/Trowbridge-Reitz
			float D = (alpha*alpha) / (pi * pow(HdotN*HdotN * (alpha*alpha - 1.0) + 1.0, 2.0));
			float k = pow(rough + 1.0, 2.0) / 8.0;
			float GL = NdotL / (NdotL*(1.0 - k) + k);
			float GV = NdotV / (NdotV*(1.0 - k) + k);
			float G = GL * GV;
			//spherical approximation and shit THANKS DICE
			vec3 F = F0 + (vec3(1.0) - F0) * pow(2.0, (-5.55473 * HdotV - 6.98316) * HdotV);
			
			float attenuation = 1.0;
			vec3 specular = vec3(D*F*G) * spec * specularColor.a;
			vec3 lightIntensity = lightIntensities[i];
			vec3 Li = lightIntensity * (diffuse + specular) * attenuation;
			
			final += Li * colorBoost;
		}
	
		result = pow(final*3.0, vec3(3.0)) * vec3(0.2, 0.2, 1.0) * 4.0;
		result += vec3(0.05);
	}
	
	float scenedepth = texelFetch(depthbuffer, ivec2(gl_FragCoord.xy), 0).r;
	float diff = scenedepth - depth;
	bool hit_surface = (abs(march_result.w)!=1000.);
	vec3 hot = vec3(5.0, 2.0, 5.0);
	float brighten = max(0.0, log(abs(diff)*2500.0));
	

	if (diff < 0) {
		// depth test failed
		vec3 c = texelFetch(colorbuffer, ivec2(gl_FragCoord.xy), 0).rgb;
		gl_FragDepth = scenedepth;
		
		float scene_ndc_depth = (2.0*scenedepth - near - far)/(far - near);
		
		if (brighten > 1.0) discard;
		
		if (hit_surface) {
			c = mix(hot, c, clamp(brighten, 0.0, 1.0));
		}
		
		out_Color.rgb = c;
		
	} else {
		if (!hit_surface) discard;
		result.rgb = mix(hot, result.rgb, clamp(brighten*0.5, 0.0, 1.0));
		out_Color = vec4(result.rgb, 1.0);
		gl_FragDepth = depth;
	}
}

#endif
