//Ray Mesh Intersection
//Project: LightWhat - CPU Pathtracer
//https://github.com/Illation/LightWhat
//Author: Robert Lindner

//Intersect a ray with a mesh, and calculate the shading data at intersection point if there is a hit detected
//looping over the triangles and materials is done externaly
//that is why subshape indexes are passed to the intersection function
//the first one determines the material group, the second one the triangle index

//Triangle data is precalculated before the raytracer begins to do it more efficiently

//I wrote a lot of this code before knowing a lot about c++ coding standards or the standard library
//A lot of this could be more efficient and better written



//precalculate triangle data for plane intersection and point in triangle test
void tri::precalculate(vec3 a, vec3 b, vec3 c)
{
	triPlane = plane(a, b, c);			//normal form of plane
	v0 = c - a;
	v1 = b - a;
	dot00 = v0.Dot(v0);
	dot01 = v0.Dot(v1);
	dot11 = v1.Dot(v1);
	invDenom = (dot00 * dot11 - dot01 * dot01);
}

//Plane intersection test with the normal form of planes, taking backface culling into consideration
//returns a ray distance to intersection point
inline intersection rayIts(line ln, bool bfc)	//inlining doesnt really help a lot
{
	intersection ret(false, point3(0, 0, 0), 0);	//create an empty intersection
	float nd = n.Dot(ln.dir);						//dot product of plane normal and ray direction
	if (nd<0)										//if plane and ray are not parralel
	{												//the next calculation determines how far the ray needs to travel to hit the plane
		ret.t = (d - n.Dot(ln.orig)) / nd;			//intersection hit scalar = (plane scalar - (plane normal dot ray origin)) / nd
		ret.hit = true;
		ret.backfacing = false;
	}
	else if (nd>0 && bfc == false)
	{
		ret.t = (d - n.Dot(ln.orig)) / nd;
		ret.hit = true;
		ret.backfacing = true;
	}
	return ret;
}

//Ray mesh intersection, finalzies ray plane intersection and does point in triangle test
//returns a shading geometry containing data for shaders if a hit is detected
void Mesh::getIntersection(size_t subShapeIdx, size_t subShapeIdx2, Ray ray, DifferentialGeometry &closest, float minT, bool bfc)
{
	intersection its;
	tri thisTri = m_TriLists[subShapeIdx].triangles[subShapeIdx2];			//Get The triangle to be tested
	point3 fVertA = m_VertexList[thisTri.vertA];							//Reference corner in triangle, used for point in triangle test
	its = thisTri.triPlane.rayIts(ray.ln, bfc);								//test plane intersection with triangle plane
	if (its.hit == true)
	{
		if ((its.t < closest.i.t || closest.i.hit == false) && its.t>minT) 	//check if its is closest intersection
		{
			//calculate baryacentric coordinates
			its.p = ray.ln.orig + ray.ln.dir*its.t;							//intersection point = ray origin + ray direction * intersection hit scalar
			
			//****************
			//Point in triangle test using barycentric coordinates
			//****************
			
			
			vec3 v2 = its.p - fVertA;										//vector between intersection point and reference corner
			float dot02 = thisTri.v0.Dot(v2);								
			float dot12 = thisTri.v1.Dot(v2);

			float u = (thisTri.dot11 * dot02 - thisTri.dot01 * dot12) / thisTri.invDenom; //calculate u coordinate for intersection point in relation to triangle
			if (u >= 0)
			{
				float v = (thisTri.dot00 * dot12 - thisTri.dot01 * dot02) / thisTri.invDenom; //calculate v coordinate for intersection point in relation to triangle
				if (v < 0) its.hit = false;                                 //exclude cases in which there is no hit
				if (u + v > 1) its.hit = false;								//exclude cases in which there is no hit
				if (its.hit)												//intersection point is in the triangle
				{
					
					//***********************
					//Calculate shading data for differential geometry
					//***********************
					closest.i = its;//new closest intersection point
					closest.mat = m_TriLists[subShapeIdx].matIndex;//set material

					//smooth normals
					vec3 n1 = m_NormalList[thisTri.normA];	//get the vertex normals of the triangle
					vec3 n2 = m_NormalList[thisTri.normB];
					vec3 n3 = m_NormalList[thisTri.normC];
					vec3 N = n1 + (n3 - n1)*u + (n2 - n1)*v; //interpolate between normals using barycentric coordenates of hit point
					closest.n = N.Norm(0.00001f);			//normalize that normal
					
					
					//tangent space for normal maps
					if (hasTangentSpace)					//determine tangent and bitangent the same way for normal mapping
					{
						vec3 t1 = m_TangentList[thisTri.normA];		//get all vertex tangents
						vec3 t2 = m_TangentList[thisTri.normB];
						vec3 t3 = m_TangentList[thisTri.normC];
						vec3 T = t1 + (t3 - t1)*u + (t2 - t1)*v;	//interpolate between tangents
						closest.t = T.Norm(0.00001f);				//normalize and store tangent

						vec3 b1 = m_BiTangentList[thisTri.normA];	//get all vertex bitangents
						vec3 b2 = m_BiTangentList[thisTri.normB];
						vec3 b3 = m_BiTangentList[thisTri.normC];
						vec3 B = b1 + (b3 - b1)*u + (b2 - b1)*v;	//interpolate between bitangents
						closest.b = B.Norm(0.00001f);				//store normalized bitangent

						closest.hasTangentSpace = true;
					}
					else closest.hasTangentSpace = false;

					
					//uv map
					closest.uv = point2(u, v);				//use barycentric coordinates by default for non mapped meshes
					if (m_TriLists[subShapeIdx].hasUV)		
					{
						vec2 uv1 = m_UVs[0].coords[thisTri.uvA];	//get all uv coordinates
						vec2 uv2 = m_UVs[0].coords[thisTri.uvB];
						vec2 uv3 = m_UVs[0].coords[thisTri.uvC];
						closest.uv = uv1 + (uv3 - uv1)*u + (uv2 - uv1)*v;	//interpolate between texcoords using barycentric coordinates
						closest.uv = closest.uv.correctUV();	//clamp uv coordiantes between 0 and 1
						closest.uv.y = 1 - closest.uv.y;		//invert v coordinate
					}
				}
			}
			else its.hit = false;
		}
		else its.hit = false;
	}
}