/* shared vertex/fragment code */
uniform vec3 camera; // world coordinates of camera
varying vec4 myColor;
varying vec3 location; // world coordinates of our pixel

uniform sampler3D blobSampler; // 3D blob texture
uniform float time;

/* Raytracer framework */
const float invalid_t=1.0e9; // far away
const float close_t=1.0e-2; // too close (behind head, self-intersection, etc)

/* This struct describes how a surface looks */
struct surface_hit_t {
	float shiny; /* 0: totally matte surface; 1: big highlight */
	vec3 reflectance; /* diffuse color */
	float mirror; /* proportion of perfect mirror specular reflection */
	float refract; /* if not 1.0, the refractive index of the material */
	float density; /* if <1.0, object is volume rendered */
};

/* This struct describes everything we know about a ray-object hit. */
struct ray_hit_t {
	vec3 P; /* world coords location of hit */
	vec3 N; /* surface normal of the hit */
	float t; /* ray t value at hit (or invalid_t if a miss) */
	float enter_t; /* ray's t value at entrance to object (same as t) */
	surface_hit_t s;
};


/* Return the t value where the ray (C,D) hits 
    the disk with normal N and radius r. */
void disk_hit(inout ray_hit_t rh,vec3 C,vec3 D,   // ray parameters
		vec3 N,vec3 center,float r, // object parameters
		surface_hit_t surface,float plaidiness)  // shading parameters
{
	float a=dot(C-center,N);
	float b=dot(D,N);
	float t=-a/b;
	if (t<close_t || t>rh.t) return; // we don't see this object.
	
	vec3 P=C+t*D; // ray-object intersection point (world coordinates)
	float Pr=length(P-center)/r;
	if (Pr>1.0) return; // trim surface
	
	/* If we got here, we're the closest hit so far. */
	rh.t=rh.enter_t=t;
	rh.P=P;
	rh.N=N;
	rh.s=surface;
	
	if (rh.s.mirror>0.5) 
	{ // add 70's style mirror tiles
		rh.s.mirror*=abs(cos(10.0*P.x)*sin(13.0*P.y));
	}
}

/* Fog sphere:
    Return the t value where the ray (C,D) hits 
    the sphere with this center and radius r. */
void sphere_hit(inout ray_hit_t rh,vec3 C,vec3 D,   // ray parameters
		vec3 center,float r, // object parameters
		surface_hit_t surface)  // shading parameters
{
	float a=dot(D,D);
	float b=2.0*dot(C-center,D);
	float c=dot(C-center,C-center)-r*r;
	float det=b*b-4.0*a*c;
	if (det<0.0) return; /* miss: can't take square root of negative */
	float entr_t=(-b-sqrt(det))/(2.0*a); /* - intersection == entry point */
	float exit_t=(-b+sqrt(det))/(2.0*a); /* + intersection == exit point */
	float t=entr_t;
	if (exit_t<close_t || entr_t>rh.t) return; /* behind head or another object */
	
	vec3 P=C+t*D; // ray-object exit point (world coordinates)
	
	/* If we got here, we're the closest hit so far. */
	rh.s=surface;
	
	if (rh.s.density<1.0) 
	{ // we are volume rendered:
		rh.t=exit_t;
		rh.enter_t=entr_t;
		rh.P=C+exit_t*D;
		if (entr_t<close_t) entr_t=close_t;
		float span=exit_t-entr_t; /* fog happens between - and + intersections */
		float k=1.0; /* fog's exponential density constant */
		rh.s.density=1.0-exp(-k*span); // analytic uniform-density model
		rh.s.reflectance*=rh.s.density;
	}
	else { /* ordinary surface */
		rh.t=rh.enter_t=t; /* shade at entry point */
		rh.P=P;
		rh.N=normalize(P-center);
	}
}

/* Blob parameters */
	float blob_threshold=0.18+0.1*sin(time); /* slowly grows and shrinks */
	float tex_scale=1.0;
	float min_distance=0.5/128.0; /* minimum t step size (0.5 voxels) */  
	
	float blob_distance(vec3 P) {
		P=P*(1.0/tex_scale);
		
		// Rasterized texture version
		//float rawdist=float(texture3D(blobSampler,P));
	
		// Analytic version (faster, but only supports simple shape)
		vec3 c1=vec3(0.3,0.5,0.5);
		float r1=length(P-c1);
		
		vec3 c2=vec3(0.7,0.5,0.5);
		float r2=length(P-c2);
		
		float rawdist=min(r1,r2);
		
		rawdist+=0.03*sin(20.0*P.x+2.3*time); // add X ridges
		rawdist+=0.02*sin(30.0*P.y+1.5*time); // add Y ridges
		rawdist+=0.01*sin(40.0*P.z+1.3*time); // add Z ridges
		rawdist-=blob_threshold;
		rawdist*=tex_scale; // bigger texture -> longer distances
		return rawdist;
	}
	
/* The blob. */
void blob_hit(inout ray_hit_t rh,vec3 C,vec3 D,   // ray parameters
		surface_hit_t surface)  // shading parameters
{
/* Start with a sphere bounding volume */
	float r=tex_scale*0.866; /* == sqrt(3/4), distance from center to corners */
	vec3 center=tex_scale*vec3(0.5,0.5,0.5);
	float a=dot(D,D);
	float b=2.0*dot(C-center,D);
	float c=dot(C-center,C-center)-r*r;
	float det=b*b-4.0*a*c;
	if (det<0.0) return; /* miss: can't take square root of negative */
	float entr_t=(-b-sqrt(det))/(2.0*a); /* - intersection == entry point */
	float exit_t=(-b+sqrt(det))/(2.0*a); /* + intersection == exit point */
	
	if (exit_t<close_t || entr_t>rh.t) return; /* behind head or another object */
	if (entr_t<close_t) entr_t=close_t; /* start ray at your head */

	float t=entr_t;
	
	float sign=1.0; // -1.0 if we want to leave surface
	
	if (blob_distance(C+(t+0.001)*D)<0.0) { // ray started inside object
		sign=-sign;
	}
	
	float dt=0.0005;
	while (t<exit_t) {
		float dist=blob_distance(C+t*D);
		if (dist*sign<=0.0) { /* inside object! */
			// walk backwards to the surface
			while (blob_distance(C+t*D)*sign<=0.0 && t>0.0) t-=dt;
			rh.enter_t=rh.t=t;
			vec3 P=C+rh.t*D;
			rh.P=P;
			float h=0.02;
			rh.N=normalize(vec3(
				(blob_distance(P+vec3(h,0.0,0.0))-blob_distance(P))/h,
				(blob_distance(P+vec3(0.0,h,0.0))-blob_distance(P))/h,
				(blob_distance(P+vec3(0.0,0.0,h))-blob_distance(P))/h
			)); // gradient based normal
			
			rh.s=surface;
			return;
		}
		t+=dist*sign+dt;
	}
	
	/* If we get here, we missed.  Return and forget it. */
}

/* Return a ray_hit for this world ray.  Tests against all objects. */
ray_hit_t world_hit(vec3 C,vec3 D)
{
	ray_hit_t rh; rh.t=invalid_t;
	
// Intersect new ray with all the world's geometry:
	// Ground plane
	disk_hit(rh,C,D, normalize(vec3(0.0,0.0,1.0)),vec3(0.0,0.0,-1.0),1000.0,
		 surface_hit_t(0.0,vec3(0.4,0.5,0.2),0.0,1.0,1.01),0.0);

	// Mirror sphere
	sphere_hit(rh,C,D, vec3(0.0,4.0,0.0),0.5,
		 surface_hit_t(1.0,vec3(0.4),0.6,1.0,1.01));
	
	// The blob
	blob_hit(rh,C,D, 
		 surface_hit_t(0.3,vec3(0.0,0.0,0.0),0.0,1.3,1.01));
	
	return rh;
}


vec3 skyColor=vec3(0.4,0.6,1.0);

/* Add sky light to this color, which passed through this much air */
vec3 skyBlend(float airDistance,vec3 geomColor) {
	float k=0.01;
	float G=exp(-k*airDistance);
	
	return skyColor*(1.0-G)+G*geomColor;
}

vec3 calc_world_color(vec3 C,vec3 D) {
	vec3 color=vec3(0.0);
	float frac=1.0; /* fraction of object light that makes it to the camera */
	
	for (int bounce=0;bounce<5;bounce++) 
	{
		D=normalize(D);
	/* Intersect camera ray with world geometry */
		ray_hit_t rh=world_hit(C,D);

		if (rh.t>=invalid_t) {
			color+=frac*skyColor*(1.0-D.z);
			break; // return color; //<- crashes my ATI
		}

	/* Else do lighting */
		if (rh.s.density>1.0) { // solid surface 
			if (dot(rh.N,D)>0.1) { // back side of surface
				rh.N=-rh.N;
				rh.s.refract=1.0/rh.s.refract; // leaving the surface
			}

			vec3 L=normalize(vec3(0.8,-0.5,0.7));
			vec3 H=normalize(L+normalize(-D));
			float specular=rh.s.shiny*pow(clamp(dot(H,rh.N),0.0,1.0),500.0);
			float diffuse=clamp(dot(rh.N,L),0.0,1.0);

			// check shadow ray 
			ray_hit_t shadow=world_hit(rh.P,L);
			if (shadow.t<invalid_t) {
				diffuse*=(1.0-shadow.s.density); 
				specular*=(1.0-shadow.s.density); 
			}

			float ambient=0.1;

			vec3 curObject=(ambient+diffuse)*rh.s.reflectance+specular*vec3(1.0);
			color+=frac*skyBlend(rh.enter_t,curObject);
		
		} else { // volume rendered 
			color+=frac*skyBlend(rh.enter_t,rh.s.reflectance);
			frac*=1.0-rh.s.density; // fraction we see of background
		}
		
	/* Check for mirror bounce */
		if (rh.s.mirror>0.0) {
			frac*=rh.s.mirror;
			//color+=rh.s.mirror*calc_world_color(rh.P,reflect(D,rh.N));
			C=rh.P;
			D=reflect(D,rh.N);
		}
		else  if (rh.s.refract!=1.0) 
		{ /* refract! */
			vec3 I=normalize(D);
			vec3 N=normalize(rh.N);
			/* Mirror reflection first */
			float mirror=0.08+0.92*pow(1.0-dot(-I,N),5.0);
			color+=0.5*frac*skyColor*mirror;
			
			/* Now refraction */
			C=rh.P;  
			vec3 R;
			float eta=1.0/rh.s.refract;
			
			float k = 1.0 - eta * eta * (1.0 - dot(N, I) * dot(N, I));
			if (k < 0.0)
				R = reflect(I,rh.N); /* total internal reflection */
			else
				R = eta * I - (eta * dot(N, I) + sqrt(k)) * N;
			D=R;
		} 
		else if (rh.s.density<=1.0) {
			// continue ray beyond our volume-rendered object
			C=rh.P;
			D=D; /* direction unchanged */
		}
		else break;
		if (frac<0.005) return color;
	} 
	
	return color;
}

void main(void) {
	vec3 C=camera; // origin of ray (world coords)
	vec3 D=location-camera; // direction of ray (world coords)

	gl_FragColor.rgb=calc_world_color(C,D);
	gl_FragColor.a=1.0; // opaque
}
