/*
*	Phong.cpp
*	Author: Matt Bozarth
*
*	Description: The Phong class is responsible for applying the Phong shader. This entire class is static.
*
*	Notes:
*
*/
#include "Phong.h"
#include "ToneReproduction.h"

/*
 * After a ray has intersected with an object, we want to illuminate that object. 
 * This function takes in the object, the point of intersection, lighting information, 
 * camera information, and a description of the rest of the scene.
 * It uses that information to calculate the three color components of 
 * Phong shading (http://en.wikipedia.org/wiki/Phong_shading):
 * - ambient
 * - diffuse
 * - specular
 * 
 * This function also tests to make sure the point is actually hit by light. 
 * If it is blocked by other objects, then it is in shadow and no Phong will be calculated.
 * If the point is lit, then the diffuse and specular functions are called and added to the 
 * ambient value to produce the final color as well as factoring in any texture.
 *
 * This function also deals with soft shadows. 
 * If there are soft shadows it takes care of looping over the area light.
 *
 * Parameters:
 * object - the object to be illuminated
 * intersectPoint - where the current ray intersected with the object
 * lights - a list of the lights with their positions and colors
 * ambient - the ambient light in the scene
 * camera - the camera
 * sceneObjects - a list of all objects in the scene, used for determining shadows
 */
Color Phong::Illuminate(SceneObject *object, D3DXVECTOR3 *intersectPoint, std::vector<PointLight*> lights, Color *ambient, Camera *camera, std::vector<SceneObject*> sceneObjects)
{
	float THRESHOLD_DIST = 0.001f;

	Color ambientShader = Color();
	Color diffuseShader = Color();
	Color specularShader = Color();
	Color texturedColor = Color();
	Color tempColor = Color();

	//calculate shadow ray
	//if shadow return?
	//set the initial ray point (the intersection point)
	Ray shadowRay = Ray();
	shadowRay.P0.x = intersectPoint->x;
	shadowRay.P0.y = intersectPoint->y;
	shadowRay.P0.z = intersectPoint->z;

	float lightDist = 0.0f;
	D3DXVECTOR3 intersection = D3DXVECTOR3();
	D3DXVECTOR3 lightDistRay = D3DXVECTOR3();
	bool shadow = false;

	bool softShadows = true;

	//(posx, posy, posz, colorr, colorg, colorb, diameter)
	SquareLight squareLight = SquareLight(-2, 5, 7, 1, 1, 1, 1); //defaults to 0,10,6
	

	//are we rendering soft shadows or not?
	if (!softShadows)
	{
		//for each light see if we are lit and if so run diffuse and specular
		for (unsigned int i = 0; i < lights.size(); i++)
		{
			lightDist = D3DXVec3Length((&lightDistRay, *lights[i]->GetPosition(), intersectPoint));
			
			//set the end of the ray at the light
			shadowRay.P1.x = lights[i]->GetPosition()->x - shadowRay.P0.x;
			shadowRay.P1.y = lights[i]->GetPosition()->y - shadowRay.P0.y;
			shadowRay.P1.z = lights[i]->GetPosition()->z - shadowRay.P0.z;
			shadowRay.Normalize();

			//find closest intersection
			int closestDist = 1000000000;
			float currentDist = float(-1);
			D3DXVECTOR3 intersection = D3DXVECTOR3(0,0,0);
			//for every object in the scene see if it blocks
			for (int k = 0; k < (int)sceneObjects.size(); k++) {
				//see if it intersects
				if ((*sceneObjects[k]).intersect(&shadowRay, &intersection, &currentDist)) {
					//see if it is closer than the light
					if (currentDist < lightDist && currentDist > THRESHOLD_DIST) {
						//something blocked this light
						shadow = true;
					}
				}
			}
			if (!shadow)
			{
				//for this light do diffuse and specular and texture
				diffuseShader = diffuseShader.addColor(&CalcDiffuse(object, intersectPoint, lights[i]), Disabled);
				specularShader = specularShader.addColor(&CalcSpecular(object, intersectPoint, lights[i], camera), Disabled);

				//calculate texture
				if (object->textured)
				{
					Color temp = Color();
					Checkerboard::Texturize(&temp, object, intersectPoint);
					texturedColor = texturedColor.addColor(&temp, Disabled);
				}
			}
			shadow = false;
		}
	}
	else //do soft shadows
	{
		int softLightResolution = 4;
		
		float delta = (squareLight.maxX() - squareLight.minX())/softLightResolution;
		int lightHits = 0;
		float lightFraction = 0;
		int shots = 0;
		bool hitLight = true;

		//loop over the light
		for (float i = squareLight.minX(); i < squareLight.maxX(); i += delta)
		{
			for (float j = squareLight.minZ(); j < squareLight.maxZ(); j += delta)
			{
				lightDist = D3DXVec3Length((&lightDistRay, *squareLight.GetPosition(), intersectPoint));
				shadowRay.P1.x = i - shadowRay.P0.x;
				shadowRay.P1.y = squareLight.GetPosition()->y - shadowRay.P0.y;
				shadowRay.P1.z = j - shadowRay.P0.z;

				shadowRay.Normalize();
				shots++;

				//find closest intersection
				int closestDist = 1000000000;
				float currentDist = float(-1);
				D3DXVECTOR3 intersection = D3DXVECTOR3(0,0,0);
				//for every object in the scene see if it blocks
				for (int k = 0; k < (int)sceneObjects.size(); k++) {
					//see if it intersects
					if ((*sceneObjects[k]).intersect(&shadowRay, &intersection, &currentDist)) {
						//see if it is farther than the light
						if (currentDist < lightDist && currentDist > THRESHOLD_DIST)
						{
							hitLight = false;
						}
					}
				}

				if (hitLight)
				{
					lightHits++;
					hitLight = true;
				}
				hitLight = true;
			}
		}

		if (lightHits > 0)
		{
			diffuseShader = diffuseShader.addColor(&CalcDiffuse(object, intersectPoint, &squareLight), Enabled);
			specularShader = specularShader.addColor(&CalcSpecular(object, intersectPoint, &squareLight, camera), Enabled);
			
			lightFraction = (float)lightHits / (float)(softLightResolution * softLightResolution); //scale by the number of lights it sees

			diffuseShader.r *= lightFraction;
			diffuseShader.g *= lightFraction;
			diffuseShader.b *= lightFraction;

			specularShader.r *= lightFraction;
			specularShader.g *= lightFraction;
			specularShader.b *= lightFraction;
		}


		//calculate texture
		if (object->textured)
		{
			Color temp = Color();
			Checkerboard::Texturize(&temp, object, intersectPoint);
			texturedColor = texturedColor.addColor(&temp, Disabled);
		}
	}

	//do ambient regardless of shadowed
	ambientShader = CalcAmbient(object, ambient);

	float r = ambientShader.r + diffuseShader.r + specularShader.r + texturedColor.r;
	float g = ambientShader.g + diffuseShader.g + specularShader.g + texturedColor.g;
	float b = ambientShader.b + diffuseShader.b + specularShader.b + texturedColor.b;

	return Color(r, g, b);
}

/*
 * Calculate the ambient component and return it.
 *
 * Parameters:
 * object - the object we are lighting, used for it's ambient component
 * ambient - the scene's ambient value
 *
 * Return: the color value for the ambient component.
 */
Color Phong::CalcAmbient(SceneObject *object, Color *ambient)
{
	Color newColor = Color();

	newColor.r = object->GetPhong()->GetAmbient().r * ambient->r;
	newColor.g = object->GetPhong()->GetAmbient().g * ambient->g;
	newColor.b = object->GetPhong()->GetAmbient().b * ambient->b;

	return newColor;
}

/*
 * Calculate the diffuse component and return it.
 *
 * ----
 * diffuse equation:
 * 	I = Ip * Ik cos(theta)
 * Ip is the point light's intensiy and Ik is the diffuse property
 * but U.V = |u|||v|cos(theta)
 * ----
 *
 * Parameters:
 * object: the object being lit, used for its diffuse component
 * intersectPoint - the place where the ray hit the object, used in the angle calculations
 * light - the light illuminating the point
 *
 * Return: the color value for the diffuse component.
 */
Color Phong::CalcDiffuse(SceneObject *object, D3DXVECTOR3 *intersectPoint, PointLight* light)
{
	//I = light ** diffuse * (lightRay DOT normal)
	Color newColor = Color(0,0,0);
	D3DXVECTOR3 normal = object->Normal(intersectPoint);
	D3DXVECTOR3 lightRay = D3DXVECTOR3();
	D3DXVECTOR3 lightRayNormal = D3DXVECTOR3();

	//sum values for all lights
	D3DXVECTOR3 lightDiffuse = D3DXVECTOR3();
	D3DXVECTOR3 scaledProduct = D3DXVECTOR3();


	//dot light ray with the normal and then normalize
	D3DXVec3Subtract(&lightRay, light->GetPosition(), intersectPoint); //get current light ray
	D3DXVec3Normalize(&lightRayNormal, &lightRay);
	float lightRayDotNormal = D3DXVec3Dot(&lightRayNormal, &normal);				//get the light ray dotted with the normal (cos(theta))

	lightDiffuse = D3DXVECTOR3(light->GetColor()->r * object->GetPhong()->GetDiffuse().r,
								light->GetColor()->g * object->GetPhong()->GetDiffuse().g,
								light->GetColor()->b * object->GetPhong()->GetDiffuse().b);  //multiply each component

	//cos(theta), can't be less than 0 because theta can't be more than 90 degrees
	if (lightRayDotNormal < 0)
		return newColor;
	
	scaledProduct = D3DXVECTOR3();
	D3DXVec3Scale(&scaledProduct, &lightDiffuse, lightRayDotNormal); //scale the multiplication of components by the dot product

	newColor.r += scaledProduct.x;
	newColor.g += scaledProduct.y;
	newColor.b += scaledProduct.z;

	return newColor;
}

/*
 * Calculate the specular component and return it.
 *
 * ----
 * Reflection Equation:
 * r = S + 2a = S-2((S dot N)/(|n|^2)) * n
 * S = lightRay
 * n = normal
 * a = perpendicular to n and third side of S,n,a triangle
 * S^2 = a^2 + n^2
 * ----
 *
 * Parameters:
 * object - the object being lit, used for its specular component
 * intersectPoint - the place where the ray hit the object, used in the angle calculations
 * light - the light illuminating the point
 * camera - the camera, used for its position for the angle calculations
 *
 * Return: the color value for the specular component.
 */
Color Phong::CalcSpecular(SceneObject *object, D3DXVECTOR3 *intersectPoint, PointLight* light, Camera *camera)
{
	//declare the return variable
	Color newColor = Color();

	//declare variables that are used in this function
	D3DXVECTOR3 normal = object->Normal(intersectPoint);
	D3DXVECTOR3 lightRay = D3DXVECTOR3();
	D3DXVECTOR3 lightRayNormal = D3DXVECTOR3();
	D3DXVECTOR3 alpha = D3DXVECTOR3();
	D3DXVECTOR3 r = D3DXVECTOR3();
	D3DXVECTOR3 twoNNL = D3DXVECTOR3();
	D3DXVECTOR3 twoNNLL = D3DXVECTOR3();
	D3DXVECTOR3 v = D3DXVECTOR3();
	D3DXVECTOR3 normV = D3DXVECTOR3();


	D3DXVec3Subtract(&v, camera->getPosition(), camera->getLookAt());
	D3DXVec3Normalize(&normV, &v);

	float rDotV = 0.0f;

	//dot light ray with the normal and then normalize
	D3DXVec3Subtract(&lightRay, light->GetPosition(), intersectPoint);	//get current light ray
	D3DXVec3Normalize(&lightRayNormal, &lightRay);
	float lightRayDotNormal = D3DXVec3Dot(&lightRayNormal, &normal);	//get the light ray dotted with the normal (cose(theta))

	//cos(theta), can't be less than 0 because theta can't be more than 90 degrees
	if (lightRayDotNormal < 0)
		return newColor;

	//r = 2N (N.L) - L
	D3DXVec3Scale(&twoNNL, &normal, 2*lightRayDotNormal); //2N(N.L)
	D3DXVec3Subtract(&twoNNLL, &twoNNL, &lightRayNormal);	// (2N(N.L) - L)

	rDotV = D3DXVec3Dot(&twoNNLL, &normV);

	float k = object->GetPhong()->GetK();
	float n = object->GetPhong()->GetN();

	rDotV = (k * (pow(rDotV,n)));

	newColor.r += object->GetPhong()->GetSpecular().r * rDotV;
	newColor.g += object->GetPhong()->GetSpecular().g * rDotV;
	newColor.b += object->GetPhong()->GetSpecular().b * rDotV;

	return newColor;
}

