// GLSL fragment shader: GPU raytracer
//   Quadric object geometry is pulled from textures.
//   Supports weak version of recursion, for reflective objects.
// Dr. Orion Lawlor, olawlor@acm.org, 2010-02-11 (Public Domain)

// Evidently I need this extension in order to use texture rectangles.
#extension GL_ARB_texture_rectangle : enable

uniform vec3 C; // camera position, world coordinates
varying vec3 G; // proxy geometry location, world coordinates

/* Properties of an object at one point */
struct object_surface {
	vec3 reflectance; // diffuse color
	float mirror; // 0=matte; 1=totally specular reflective
	float specular; // surface has specular highlight?
	float checkerboard; // 0=uniform color; 1=black and white checkers
	float slice; // slices per meter
	float opacity; // 0=transparent; 1=opaque
};

const float miss_t=100.0; // t value for a miss

/* Everything about a ray-object hit point */
struct ray_intersection {
	float t; // t along the ray, or miss_t if we missed the object
	vec3 P; // intersection point, in world coordinates
	vec3 N; // surface normal at intersection point
	object_surface s; // surface properties
};

// A quadric object
struct quadric_object {
	mat4 A; // quadric surface matrix: 0 = x^T A x
	mat4 MVinv; // moves from world coordinates to "trim space"
	object_surface s; // surface properties
};

// Utility function: return true if this is a valid hit value.
bool valid_hit(float t,vec3 P,quadric_object q) {
	if (t<0.001) return false; // behind observer
	vec4 qP=q.MVinv*vec4(P,1.0); // move P into object space
	if (fract(qP.y*q.s.slice)>0.7) return false; // striped geometry
	if (length(vec3(qP))>10.0) return false; // outside trim
	return true;
}

const float num_objects=4.0;
uniform sampler2DRect quadriclist; // texture unit 0

// Load up a quadric object from the texture
quadric_object load_quadric(float objectNo) {
	quadric_object q;
	q.A[0]=texture2DRect(quadriclist,vec2(0.0,objectNo));
	q.A[1]=texture2DRect(quadriclist,vec2(1.0,objectNo));
	q.A[2]=texture2DRect(quadriclist,vec2(2.0,objectNo));
	q.A[3]=texture2DRect(quadriclist,vec2(3.0,objectNo));
	
	vec4 tmp=texture2DRect(quadriclist,vec2(4.0,objectNo));
	q.s.reflectance=vec3(tmp);
	q.s.mirror=tmp.w;
	
	q.MVinv[0]=texture2DRect(quadriclist,vec2(5.0,objectNo));
	q.MVinv[1]=texture2DRect(quadriclist,vec2(6.0,objectNo));
	q.MVinv[2]=texture2DRect(quadriclist,vec2(7.0,objectNo));
	q.MVinv[3]=texture2DRect(quadriclist,vec2(8.0,objectNo));
	
	tmp=texture2DRect(quadriclist,vec2(9.0,objectNo));
	q.s.opacity=1.0;
	q.s.checkerboard=tmp.x;
	q.s.slice=tmp.y;
	q.s.specular=tmp.z;
	
	return q;
}


// Ray-object intersection for quadric surface.
//   The quadric object's matrix is A: 0 = x^T A x
//   Ray is C+t*D, return value is t, or miss_t if miss.
//   C and D are in world coordinates.
ray_intersection quadric_intersection(vec3 C,vec3 D,quadric_object q)
{
	ray_intersection i;

	// tack on w component
	vec4 C4=vec4(C,1.0); // w==1.0 for a position vector
	vec4 D4=vec4(D,0.0); // w==0.0 for a direction vector

	// matrix-vector multiply
	vec4 AD=q.A*D4; 
	vec4 AC=q.A*C4; 

	// Quadratic equation terms:
	float a=dot(D4,AD); // t^2 term
	float b=dot(C4,AD) + dot(D4,AC);  // t term
	float c=dot(C4,AC); // constant term

	if (abs(a)<0.0001) 
	{ // NOT actually quadratic--linear only
		i.t= -c/b;  // 0 = c + t * b, so t = -c/b 
		if (!valid_hit(i.t,C+i.t*D,q)) { i.t=miss_t; return i; }
	}
	else 
	{ // Solve the quadratic equation
		float det=b*b-4.0*a*c;
		if (det<0.0) {i.t=miss_t; return i; }

		// Find the closest one of both roots
		i.t=miss_t; // assume far away.
		for (float sign=-1.0;sign<=1.0;sign+=2.0)
		{
			float t=(-b+sign*sqrt(det))/(2.0*a);
			if (valid_hit(t,C+t*D,q)) 
			{ /* a potential new closest hit point */
				i.t=min(t,i.t);
			}
		}
	}
	i.P=C+i.t*D;
	i.s=q.s;
	
	// Compute normal vector, via gradient
	vec4 P4=vec4(i.P,1.0);
	vec3 gradient=vec3(
		dot(q.A[0]+vec4(q.A[0][0],q.A[1][0],q.A[2][0],q.A[3][0]),P4),
		dot(q.A[1]+vec4(q.A[0][1],q.A[1][1],q.A[2][1],q.A[3][1]),P4),
		dot(q.A[2]+vec4(q.A[0][2],q.A[1][2],q.A[2][2],q.A[3][2]),P4)
	);
	i.N=normalize(gradient);
	return i;
}

uniform sampler2D checkertex;

// Get the diffuse color of this object here
vec3 get_color(ray_intersection i) {
	if (i.s.checkerboard>0.5)
	{
		//Checkerboard via texture
		return vec3(texture2D(checkertex,vec2(i.P)));
		
		//Checkerboard via arithmetic (suffers from aliasing)
		//vec3 f=step(0.5,fract(i.P))-0.5;
		//if (f.x*f.y > 0.0) return vec3(0.0); /* pitch black between squares */
	}
	return i.s.reflectance;
}

// Compute the local color of this ray-object intersection.  
//   D points toward the intersection from the viewer.
vec3 lighting(ray_intersection i,vec3 D) {
	// Compute surface normal
	if (dot(i.N,D)>0.0)
		i.N=-i.N; /* normal was facing wrong way... */

	// Compute lighting
	vec3 L=normalize(vec3(0.4,0,1.3));
	float diffuse=clamp(dot(i.N,L),0.0,1.0); // diffuse

	vec3 H=normalize(0.5*(L-D)); // Blinn's halfway vector
	float spec=clamp(dot(H,i.N),0.0,1.0);
	// spec=step(0.999,dot(H,i.N)); // "cartoon highlight"
	spec=pow(spec,500.0)*i.s.specular; // classic phong exponent

	// Check if shadow ray hits any of our geometry
	float lightFrac=1.0; /* fraction of light from light source that hits us */
	for (float objectNo=0.0;objectNo<num_objects;objectNo++)
	{
		ray_intersection i=quadric_intersection(i.P,L,load_quadric(objectNo));
		if (i.t<miss_t) { /* this object shadows us */
			lightFrac*=1.0-i.s.opacity;
		}
	}
	diffuse*=0.2+0.8*lightFrac; /* leave a little definition even in shadow */
	spec*=lightFrac; /* totally cut off specular in shadow */

	diffuse = diffuse*0.8 + 0.2; /* ambient */
	vec3 rgb=diffuse*get_color(i)*i.s.opacity+spec;
	return rgb;
}

// Given a ray, return the closest intersection with any geometry
ray_intersection ray_intersect(vec3 C,vec3 D) {
	ray_intersection i; i.t=miss_t; // closest intersection
	for (float objectNo=0.0;objectNo<num_objects;objectNo++)
	{
		ray_intersection qi=quadric_intersection(C,D,load_quadric(objectNo));
		if (qi.t<i.t) { // new closest hit
			i=qi;
		}
	}
	return i;
}


void main(void) {
	// Start with a camera ray
	vec3 rayDir=normalize(G-C); // points from camera to proxy
	vec3 rayStart=C;
	
	// Sum up reflections over multiple "recursive" ray bounces.
	//  Recursion is transformed to iteration here.
	vec3 finalColor = vec3(0.0);
	float contribution = 1.0; // fraction of our color to add to final color
	for (int raycount=0;raycount<3;raycount++) { // loop over reflected rays
	
		ray_intersection i=ray_intersect(rayStart,rayDir);
		if (i.t>=miss_t) 
		{ // sky object was hit: fabricate a color
			vec3 skyColor = vec3(0.4,0.5,0.7); // basic color
			skyColor *= (1.0-0.9*rayDir.z); // gentle color gradient */
			finalColor += skyColor*contribution;
			if (raycount==0) {gl_FragDepth=0.999;} // sky needs depth
			break; // exit loop-- sky is not reflective
		}
		if (raycount==0) 
		{ // camera ray--Z buffer should store only the first hit
			// Compute Z-buffer depth there
			vec4 P4=vec4(i.P,1.0); // world coords
			vec4 proj=gl_ProjectionMatrix*P4; // screen
			float depth=proj.z/proj.w; // persp. divide
			gl_FragDepth = clamp(0.5+0.5*depth,0.0,1.0); // glDepthRange
		}
		
		// Local illumination contribution
		vec3 local = lighting(i,rayDir);
		finalColor += local*(1.0-i.s.mirror)*contribution; 
		// Remote illumination contribution: "recurse" via loop
		contribution *= i.s.mirror; 
		rayDir = reflect(rayDir,i.N);
		rayStart = i.P;
		
		if (contribution<0.01) break; // give up--not worth another ray!
	}
	
	// Output final summed-up color
	gl_FragColor = vec4(finalColor,1.0);
}
