#version 400

uniform mat4 P;
uniform mat4 V;
uniform mat4 M;
uniform vec3 cam_pos;
uniform int window_width;
uniform int window_height;

uniform vec3 la;
uniform vec3 ld;
uniform vec3 ls;
uniform vec3 ka;
uniform vec3 kd;
uniform vec3 ks;

uniform float smoothing;
uniform vec3 sphere_center;

out vec4 FragColor;

struct Torus
{
	float R;
	float r;
};

Torus torus;

struct Sphere
{
	vec3 center;
	float r;
};

Sphere sphere;

void initTorus()
{
	torus.R = 0.5;
	torus.r = 0.15;
}

void initSphere()
{
	sphere.center = sphere_center;
	sphere.r = 0.25;
}

float sphereSDF(vec3 p, vec3 center, float r) {
    return length(p - center) - r;
}

float torusSDF(vec3 p, float R, float r) {
	vec2 q = vec2(length(p.xz) - R, p.y);
	return length(q) - r;
}

vec3 estimateNormalsSphere(vec3 p)
{
	float epsilon = 0.001;
	float x = sphereSDF(vec3(p.x + epsilon, p.y, p.z), sphere.center, sphere.r) - sphereSDF(vec3(p.x - epsilon, p.y, p.z), sphere.center, sphere.r);
	float y = sphereSDF(vec3(p.x, p.y + epsilon, p.z), sphere.center, sphere.r) - sphereSDF(vec3(p.x, p.y - epsilon, p.z), sphere.center, sphere.r);
	float z = sphereSDF(vec3(p.x, p.y, p.z + epsilon), sphere.center, sphere.r) - sphereSDF(vec3(p.x, p.y, p.z - epsilon), sphere.center, sphere.r);
	return normalize(vec3(x, y, z));
}

vec3 estimateNormalsTorus(vec3 p)
{
	float epsilon = 0.001;
	float x = torusSDF(vec3(p.x + epsilon, p.y, p.z), torus.R, torus.r) - torusSDF(vec3(p.x - epsilon, p.y, p.z), torus.R, torus.r);
	float y = torusSDF(vec3(p.x, p.y + epsilon, p.z), torus.R, torus.r) - torusSDF(vec3(p.x, p.y - epsilon, p.z), torus.R, torus.r);
	float z = torusSDF(vec3(p.x, p.y, p.z + epsilon), torus.R, torus.r) - torusSDF(vec3(p.x, p.y, p.z - epsilon), torus.R, torus.r);
	return normalize(vec3(x, y, z));
}

float smoothMinSDF(float d1, float d2, float k) {
	if (k == 0.0) k = 0.000001;
	float h = max(k - abs(d1 - d2), 0) / k;
	return min(d1, d2) - h * h * h * k * 1.0 / 6.0;
}

float smoothMaxSDF(float d1, float d2, float k) {
	if (k == 0.0) k = 0.000001;
	float h = min(k - abs(d1 - d2), 0) / k;
	return min(d1, d2) - h * h * h * k * 1.0 / 6.0;
}

vec3 estimateSmoothNormals(vec3 p, float smoothness) {
	float epsilon = 0.001;
	float d = smoothMinSDF(torusSDF(p, torus.R, torus.r), sphereSDF(p, sphere.center, sphere.r), smoothness);
	float nx = smoothMinSDF(torusSDF(vec3(p.x + epsilon, p.y, p.z), torus.R, torus.r), sphereSDF(vec3(p.x + epsilon, p.y, p.z), sphere.center, sphere.r), smoothness) - d;
	float ny = smoothMinSDF(torusSDF(vec3(p.x, p.y + epsilon, p.z), torus.R, torus.r), sphereSDF(vec3(p.x, p.y + epsilon, p.z), sphere.center, sphere.r), smoothness) - d;
	float nz = smoothMinSDF(torusSDF(vec3(p.x, p.y, p.z + epsilon), torus.R, torus.r), sphereSDF(vec3(p.x, p.y, p.z + epsilon), sphere.center, sphere.r), smoothness) - d;
	return normalize(vec3(nx, ny, nz));
}
vec3 estimateNormalsMax(vec3 p, float smoothness) {
	float epsilon = 0.001;
	float d = max(torusSDF(p, torus.R, torus.r), sphereSDF(p, sphere.center, sphere.r));
	float nx = max(torusSDF(vec3(p.x + epsilon, p.y, p.z), torus.R, torus.r), sphereSDF(vec3(p.x + epsilon, p.y, p.z), sphere.center, sphere.r)) - d;
	float ny = max(torusSDF(vec3(p.x, p.y + epsilon, p.z), torus.R, torus.r), sphereSDF(vec3(p.x, p.y + epsilon, p.z), sphere.center, sphere.r)) - d;
	float nz = max(torusSDF(vec3(p.x, p.y, p.z + epsilon), torus.R, torus.r), sphereSDF(vec3(p.x, p.y, p.z + epsilon), sphere.center, sphere.r)) - d;
	return normalize(vec3(nx, ny, nz));
}


void main(void)
{
	vec3 normals;
	vec3 color = la;
	vec4 ray_pos = vec4(cam_pos, 1.0);
	float ray_dist = 0.0;
	float max_dist = 100.0;
	float epsilon = 0.0001;
	int steps = 0;
	int max_steps = 1000;
	vec3 light_pos = vec3(0.0, 1.0, 0.0);
	int spec_exponent = 40;
	float k = smoothing;

	initTorus();
	initSphere();

	vec2 ndc_pos = 2.0 * vec2(gl_FragCoord.x / window_width, gl_FragCoord.y / window_height) - 1.0;
	vec4 cam_dir = inverse(P) * vec4(ndc_pos, 1.0, 1.0);
	cam_dir /= cam_dir.w;
	cam_dir = vec4(cam_dir.xyz, 0.0);

	vec4 ray_dir = normalize(inverse(V) * cam_dir);

	while (ray_dist < max_dist && steps < max_steps)
	{
		steps++;
		float distTorus = torusSDF(ray_pos.xyz, torus.R, torus.r);
		float distSphere = sphereSDF(ray_pos.xyz, sphere.center, sphere.r);
		float minDist = smoothMinSDF(distTorus, distSphere, k);
		//float minDist = max(distTorus, distSphere);
		//bool isTorus = distTorus < distSphere;
		//float minDist = min(distTorus, distSphere);

		if (minDist <= epsilon)
		{
			vec4 ambient_color, diffuse_color;

			ambient_color = vec4(ka, 1.0);
			diffuse_color = vec4(kd, 1.0);

			vec4 ambient = vec4(la, 1.0) * ambient_color;

			normals = estimateSmoothNormals(ray_pos.xyz, k);

			//if (isTorus)
			//	normals = estimateNormalsTorus(ray_pos.xyz);
			//else
			//	normals = estimateNormalsSphere(ray_pos.xyz);

			vec3 nw = normalize(normals);
			vec3 lw = normalize(light_pos - ray_pos.xyz);
			float dist = length(vec4(ray_pos.xyz - light_pos, 1.0));
			vec4 diffuse = max(dot(nw, lw), 0) * diffuse_color * vec4(ld, 1.0);

			float spec;
			vec3 vw = normalize(cam_pos - ray_pos.xyz);

			vec3 halfwayDir = normalize(lw + vw);
			spec = pow(max(dot(halfwayDir, nw), 0), spec_exponent);

			vec4 specular = spec * vec4(ks * ls, 1.0);

			color = (ambient + (1.0 / (dist * dist) * (diffuse + specular))).xyz;
			break;
		}
		ray_pos += ray_dir * minDist;
		ray_dist += minDist;
	}

	FragColor = vec4(color, 1.0);
}