//Diffuse BRDF
//Project: LightWhat - CPU Pathtracer
//https://github.com/Illation/LightWhat
//Author: Robert Lindner

//1. Texture data is extracted
//2a) if not max bounces
//		Get a random vector in a cosine weighted hemisphere
//		Recursively raycast with that random vector (pathtracer bounces)
//2b) if max bounces are reached
//		apply diffuse lambert shading from all lights
//		calculate light falloff from those lights
//3. Return the color


//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


//generate tangent and bitangent for hemishphere sampling
inline void tangentBitangent(vec3 N, vec3 &T, vec3 &B)
{
	vec3 NU;
	if (N.x || N.z)NU = vec3(0, 1, 0);//non parallel up vector
	else NU = vec3(0, 0, 1);
	T = N.Cross(NU).Norm();
	B = N.Cross(T).Norm();
}
//cosine weighted unit disc
inline void toUnitDisc(float &rx, float &ay)
{
	const float r = sqrtf(rx);//get a radius
	const float phi = ay * PI2;//get an angle
	rx = r * cosf(phi);
	ay = r * sinf(phi);
}
//generate a random vector in a hemisphere around N where it is more probably to return a vector close to n
inline vec3 cosineSampleHemisphere(vec3 N, vec3 T, vec3 B)
{

	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_real_distribution<> dis(0, 1);
	float u1 = (float)dis(gen);
	float u2 = (float)dis(gen);

	toUnitDisc(u1, u2);
	float costheta = sqrtf(max(1.0f - u1 * u1 - u2 * u2, 0.0f));
	return (T*u1 + N*costheta + B*u2);
}

//Diffuse BRDF
colRGB DiffuseBRDF::shade(DifferentialGeometry dg, LWScene *lScPtr, TraceUnit *lRenPtr)
{
	//Header
	colRGB ret = colRGB(0,0,0);
	colRGB dif = diffuse;
	if (hasTexture)//get diffuse texture
	{
		dif = lScPtr->textures[TexIdx].getRGB(dg.uv.x, dg.uv.y);
	}
	vec3 N = dg.n;
	//tangent space normal mapping
	if (hasNormTex && dg.hasTangentSpace)
	{
		colRGB texCol = lScPtr->textures[normTexIdx].getRGB(dg.uv.x, dg.uv.y);
		vec3 texN;
		texN.x = (texCol.red * 2) - 1;
		texN.y = (texCol.green * 2) - 1;
		texN.z = (texCol.blue * 2) - 1;
		vec3 tSpaceN = dg.t*texN.x + dg.b*texN.y + N*texN.z;
		N = tSpaceN.Norm();
	}
	if (N.Dot(dg.dir) > 0)
	{
		N = -N;
	}
	//Actual Shader
	if (dg.bounces > 0)
	{	
		//generate cosine weighted hemisphere vector to surface normal
		vec3 T, B;
		if (dg.hasTangentSpace){T = dg.t, B = dg.b;}//get tangent space from differential geometry
		else MonteCarlo::tangentBitangent(N, T, B);//if no tangentspace exists, generate it
		vec3 R = MonteCarlo::cosineSampleHemisphere(N, T, B);//get a random ray within the cosine weighted hemisphere of the noraml vector
		//Recursivly Bounce
		Ray ray = Ray(line(dg.i.p, R), dg.bounces - 1, false);
		ray.precalculate();
		float t;
		colRGB rcol = lRenPtr->raycast(ray, t);//get color from recursive raycast

		ret = rcol*dif*intensity;
	}
	else //calculate direct illumination
	{
		for (size_t i = 0; i < lScPtr->lights.size(); i++)//for all lights
		{
			//L inncoming light direction
			vec3 L = (lScPtr->lights[i]->getPosition() - dg.i.p);
			float distance = L.Length();
			L /= distance;
			float LightIntensity = lRenPtr->getLightIntensity(lScPtr->lights[i], dg.i.p);
			//calculate light falloff
			if (LightIntensity>0)
			{
				float r = 1.f;
				float d = max(distance - r, 0);
				float denom = d / r + 1;
				float attenuation = 1 / (denom*denom);
				//Calculate Lambert diffuse
				float intensity = N.Dot(L) * LightIntensity * attenuation;
				if (intensity > 0.f)
				{
					ret += dif*lScPtr->lights[i]->getColor()*intensity;
				}
			}
		}
	}
	return ret;
}