varying vec2 pos;
varying vec3 RayOrg;
varying vec3 RayDir;
varying vec3 RayDirInv;
varying vec3 a;
varying vec3 b;
varying vec3 c;
varying vec3 camera_position;

uniform float time;
uniform int ScreenWidth;
uniform int ScreenHeight;
uniform samplerBuffer tboSampler;
uniform sampler2D tex[10];
uniform int numrefl;

int RayBoxIntersection(vec3 BBMin, vec3 BBMax, float out tmin, float out tmax, vec3 out normal)
{
	float l1   = (BBMin.x - RayOrg.x) * RayDirInv.x;
	float l2   = (BBMax.x - RayOrg.x) * RayDirInv.x;
	tmin = min(l1,l2);
	tmax = max(l1,l2);

	l1   = (BBMin.y - RayOrg.y) * RayDirInv.y;
	l2   = (BBMax.y - RayOrg.y) * RayDirInv.y;
	tmin = max(min(l1,l2), tmin);
	tmax = min(max(l1,l2), tmax);

	l1   = (BBMin.z - RayOrg.z) * RayDirInv.z;
	l2   = (BBMax.z - RayOrg.z) * RayDirInv.z;
	tmin = max(min(l1,l2), tmin);
	tmax = min(max(l1,l2), tmax);

	int ret = ((tmax >= tmin) && (tmax >= 0.0f));
	if (ret > 0) {
	vec3 hit = RayOrg + vec3(1,1,1)/RayDirInv*tmin;

	float t=1000;
	if (t > distance(BBMin.x, hit.x)) {
		normal = vec3(-1,0,0);
		t = distance(BBMin.x, hit.x);
	}
	if (t > distance(BBMax.x, hit.x)) {
		normal = vec3(1,0,0);
		t = distance(BBMax.x, hit.x);
	}
	if (t > distance(BBMin.y, hit.y)) {
		normal = vec3(0,-1,0);
		t = distance(BBMin.y, hit.y);
	}
	if (t > distance(BBMax.y, hit.y)) {
		normal = vec3(0,1,0);
		t = distance(BBMax.y, hit.y);
	}
	if (t > distance(BBMin.z, hit.z)) {
		normal = vec3(0,0,-1);
		t = distance(BBMin.z, hit.z);
	}
	if (t > distance(BBMax.z, hit.z)) {
		normal = vec3(0,0,1);
		t = distance(BBMax.z, hit.z);
	}
	}
	return ret;
}

int BBox(vec3 BBMin, vec3 BBMax, float out tmin, float out tmax)
{
	float l1   = (BBMin.x - RayOrg.x) * RayDirInv.x;
	float l2   = (BBMax.x - RayOrg.x) * RayDirInv.x;
	tmin = min(l1,l2);
	tmax = max(l1,l2);

	l1   = (BBMin.y - RayOrg.y) * RayDirInv.y;
	l2   = (BBMax.y - RayOrg.y) * RayDirInv.y;
	tmin = max(min(l1,l2), tmin);
	tmax = min(max(l1,l2), tmax);

	l1   = (BBMin.z - RayOrg.z) * RayDirInv.z;
	l2   = (BBMax.z - RayOrg.z) * RayDirInv.z;
	tmin = max(min(l1,l2), tmin);
	tmax = min(max(l1,l2), tmax);

	return ((tmax >= tmin) && (tmax >= 0.0f));
}


float RayTriangleIntersection(vec3 v0, vec3 edge1, vec3 edge2, out vec2 uv)
{
	vec3 tvec = RayOrg - v0;  
	vec3 pvec = cross(RayDir, edge2);  
	float  det  = dot(edge1, pvec);  

	float u = dot(tvec, pvec) * (1.0/det);

	if (u < 0.0f || u > 1.0)  
		return -1.0f;  

	vec3 qvec = cross(tvec, edge1);  

	float v = dot(RayDir, qvec) * (1.0/det);  

	if (v < 0.0f || (u + v) > 1.0)  
		return -1.0f; 

	uv = vec2(u,v);

	return dot(edge2, qvec) * (1.0/det);  

}

float RaySphere(vec3 sphere, float radius) {
	float b, c, d;
	float t;
	vec3 sr = RayOrg - sphere;
	b =  dot(sr,RayDir);
	c = dot(sr,sr) - (radius*radius);
	d = b*b - c;
	if (d > 0) 
	{
		float e = sqrt(d);
		float t0 = -b-e;
		if(t0 < 0)
			t = -b+e;
		else
			t = min(-b-e,-b+e);
		return t;
	}
	return 0;
}

float BSphere(vec3 sphere, float radius) {
	float b, c, d;
	float t;
	vec3 sr = RayOrg - sphere;
	b =  dot(sr,RayDir);
	c = dot(sr,sr) - (radius*radius);
	d = b*b - c;
	if (d > 0) 
	{
		return 1;
	}
	return 0;
}

float rand(vec2 co){
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

int ShadowTest(float light_dist) {
	
	float tbmin, tbmax;
	vec2 tuv;
	vec4 meta =  texelFetchBuffer(tboSampler, 0);
		for (int b=0;b<meta.y;b++) {
				vec4 bbmin =  texelFetchBuffer(tboSampler, 524288+b*2);
				vec4 bbmax =  texelFetchBuffer(tboSampler, 524288+b*2+1);

				int tb = BBox(bbmin.xyz, bbmax.xyz, tbmin, tbmax);
				if (tb > 0) {
					for (int i=bbmin.w;i<bbmax.w;i+=8) {
						vec4 v0 =  texelFetchBuffer(tboSampler, i);
						vec4 e1 =  texelFetchBuffer(tboSampler, i+1);
						vec4 e2 =  texelFetchBuffer(tboSampler, i+2);
						if (e1.w > 0) {
							float tt = RayTriangleIntersection(v0.xyz, e1.xyz, e2.xyz, tuv);
							if (tt > 0 && tt < light_dist) {
								return 1;
							}
						}
					}
				}
		}
	return 0;
}

vec4 TraceRay(bool out trace, float out refl) {
	trace=false;
	vec3 col = vec3(0.0,0.0,0.0);

	float smin=1000000;
	vec3 normal;
	int hito = -1;
	int material=0;

	float bmin=10000, bmax=100000;
	float tbmin, tbmax;
	vec3 box_normal;
	vec3 tbox_normal;

	int bi=0;
	int t2 = 0;
	int ishit=0;

	vec4 n1;
	vec4 n2;
	vec4 n3;
	float dist=0;
	int rcvshadow=1;

	vec3 bbcol=vec3(0,0,0);
	vec2 uv, tuv;
	float t3 = 10000;
	vec3 he1,he2;
	int ab=0;
	vec3 tnormal=vec3(0,0,0);
	vec3 snormal=vec3(0,0,0);
	vec2 texcoord = vec2(0,0);
	int type=0;
	int tri=0;
	vec4 meta =  texelFetchBuffer(tboSampler, 0);
		for (int b=0;b<meta.y;b++) {
				vec4 bbmin =  texelFetchBuffer(tboSampler, 524288+b*2);
				vec4 bbmax =  texelFetchBuffer(tboSampler, 524288+b*2+1);
				
				if (bbmin.w == -2) {
					ab = RayBoxIntersection(bbmin.xyz, bbmax.xyz, tbmin, tbmax, tnormal);
					if (ab > 0 && tbmin < t3) {
						//bbcol+=vec3(0.0,0.2,0);
						//vec3 hit2 = RayOrg+RayDir*tbmin;
						//texcoord.x = 0;//mod((hit2.y-bbmin.y)/(bbmax.y-bbmin.y)*4, 1.0);
						//texcoord.y = 0;//mod((hit2.x-bbmin.x)/(bbmax.x-bbmin.x)*1, 1.0);
						t3 = tbmin;
						type=1;
						material = bbmax.w;
					}
				}
				else if (bbmin.w == -3) {
					float st = RaySphere(bbmin, bbmax.x);
					if (st > 0 && st < t3) {
						vec3 hit2 = RayOrg+RayDir*st;
						snormal = normalize(hit2-bbmin);
						material = bbmax.w;
						t3 = st;
						type=3;
					}
				} 
				else {
				
					int tb = BBox(bbmin.xyz, bbmax.xyz, tbmin, tbmax);
					if (tb >0 && tbmin < t3) {
						for (int i=bbmin.w;i<bbmax.w;i+=8) {
							vec4 v0 =  texelFetchBuffer(tboSampler, i);
							vec4 e1 =  texelFetchBuffer(tboSampler, i+1);
							vec4 e2 =  texelFetchBuffer(tboSampler, i+2);

							float tt = RayTriangleIntersection(v0.xyz, e1.xyz, e2.xyz, tuv);
							if (tt > 0 && tt < t3 && tt < bmin) {
								//tri = i;
								t3 = tt;
								if (e1.w == 0) {
									rcvshadow = 0;
								}
								n1 =  texelFetchBuffer(tboSampler, i+3);
								n2 =  texelFetchBuffer(tboSampler, i+4);
								n3 =  texelFetchBuffer(tboSampler, i+5);
								vec4 t1 = texelFetchBuffer(tboSampler, i+6);
								vec4 t2 = texelFetchBuffer(tboSampler, i+7);
								texcoord.x = t1.z + tuv.x * (t1.x - t1.z) + tuv.y * (t2.x - t1.z);
								texcoord.y = t1.w + tuv.x * (t1.y - t1.w) + tuv.y * (t2.y - t1.w);
								uv=tuv;
								type=2;
								material = v0.w;
							}
						}
				}
			}
		}

	if (t3 > 0 && t3 < 10000) {
		
		hito=4;
		bmin=t3;
		box_normal=tnormal;
		trace=false;
		if (type==1) {
			normal = tnormal;
		}
		else if (type==2) {


			normal = n2.xyz + uv.x * (n1.xyz - n2.xyz) + uv.y * (n3.xyz - n2.xyz);


		}
		else if (type==3) {
			normal = snormal;
		}
		ishit=1;
	}
	

	refl=0;

if (hito == 4) {
		vec4 m1 =  texelFetchBuffer(tboSampler, 786432+material*3);
		vec4 m2 =  texelFetchBuffer(tboSampler, 786432+material*3+1);
		vec4 m3 =  texelFetchBuffer(tboSampler, 786432+material*3+2);

		if (m3.x > 0) {
			trace = true;
			refl = m3.x;
		}
		int texno = -1;
		
		if (m3.w == 0) {
			col = texture2D(tex[0],texcoord).xyz;
		}
		else if (m3.w == 1) {
			col = texture2D(tex[1],texcoord).xyz;
		}
		else if (m3.w == 2) {
			col = texture2D(tex[2],texcoord).xyz;
		}
		else if (m3.w == 3) {
			col = texture2D(tex[3],texcoord).xyz;
		}
		else if (m3.w == 4) {
			col = texture2D(tex[4],texcoord).xyz;
		}
		else if (m3.w == 5) {
			col = texture2D(tex[5],texcoord).xyz;
		}
		else if (m3.w == 6) {
			col = texture2D(tex[6],texcoord).xyz;
		}
		else {
			col = m1.xyz;
		}

		vec3 hit = RayOrg+RayDir*bmin;

		dist = distance(RayOrg, hit);
		vec3 tcol = vec3(0,0,0);
		vec3 oro = RayOrg;
		RayOrg=RayOrg+RayDir*(bmin-0.1);
		vec3 trd = RayDir;

		for (int i=0;i<meta.z;i++) {
			vec4 l1 = texelFetchBuffer(tboSampler, 800000+i*2);
			if (!l1.w) {
				vec4 l2 = texelFetchBuffer(tboSampler, 800000+i*2+1);
				float light_dist = distance(l1.xyz, hit);
				if (light_dist < 40000.0) {
					float intens = l2.w;

					if (light_dist > 39000) {
						intens *= abs(40000-light_dist)/1000.0;
					}
					intens -= light_dist/10000.0;
					if (intens < 0) {
						intens = 0;
					}

					vec3 L = normalize(l1.xyz - hit);

					RayDir = L;
					RayDirInv = normalize(vec3(1.0/RayDir.x, 1.0/RayDir.y, 1.0/RayDir.z));
					int shadow = 0;
					if (rcvshadow != 0) {
						shadow = ShadowTest(light_dist);
					}

					vec3 N = normalize(normal);
	
					float lambertTerm = dot(N,L);
	
					if(lambertTerm > 0.0)
					{
						float s = (shadow > 0 ? 0.3 : 1.0);
						tcol += l2.xyz * col * lambertTerm * s * intens;	
		
						vec3 E = normalize(oro-hit);
						vec3 R = reflect(-L, N);
						float specular = pow( max(dot(R, E), 0.0), 
										 m2.w*1000 );
						if (shadow == 0) {
							tcol += l2.xyz * m2.xyz * specular * m1.w * intens;	
						}
					}
				}
			}
		}
		col = tcol*0.9+col*0.1;
		RayDir = trd;
		RayDirInv = normalize(vec3(1.0/RayDir.x, 1.0/RayDir.y, 1.0/RayDir.z));


		if (trace) {
			RayDir = normalize(reflect(RayDir, -normal));
		}
	}

	return vec4(col+bbcol,dist);
}

void main()
{
	float xf = pos.x;
	float yf = pos.y;

	vec3 t1 = c+(a*(xf));
	vec3 t2 = b*(yf);
	vec3 image_pos = t1 + t2;
	RayOrg = image_pos;
	RayDir = image_pos-camera_position;
	RayDir = normalize(RayDir);
	RayDirInv = normalize(vec3(1.0/RayDir.x, 1.0/RayDir.y, 1.0/RayDir.z));

	vec3 col=vec3(0,0,0);

	vec4 meta =  texelFetchBuffer(tboSampler, 0);

	vec4 res[4];
	int depth=0;
	bool trace=true;
	float refl[4];
	float dist=10000;
	while (trace && depth < numrefl) {
		res[depth] = TraceRay(trace, refl[depth]);
		depth++;
	}
	for (int c=depth-1;c>=0;c--) {
		if (refl[c] == 0) {
			col = res[c];
		}
		else {
			col = (col*(refl[c])+res[c].xyz*(1.0-refl[c]));
		}
		if (res[c].w < 10000) {
			dist = res[c].w;
		}
	}

	gl_FragColor = vec4(col,dist/100.0);
}

