#include "ProxyHeaders.hpp"

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BurnLightsProcess::BurnLightsProcess():
	materialsAreWhite(true),
	lightsToBurn(NULL),
	matrixStack(30, 20, "Light Burning Stack")
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BurnLightsProcess::BurnLightsProcess(NotationFile *data_file):
	Process(data_file),
	lightsToBurn(NULL),
	matrixStack(30, 20, "Light Burning Stack")
{
	Check_Object(data_file);
	Page *page = data_file->FindPage("BurnLights");
	if (page)
		page->GetEntry("MaterialsAreWhite", &materialsAreWhite);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ChildProxy::FindLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);
	process->FindLightsCallback(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	GroupProxy::FindLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->FindLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//---------------------------------------------------
	// If the proxy is a group, look in it for lights
	//---------------------------------------------------
	//
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->FindLights(process);
		child->DetachReference();
		child = next;
		if (!process->continueProcess)
		{
			if (child)
				child->DetachReference();
			break;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	LightProxy::FindLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->FindLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//--------------------------------------------------
	// If the proxy is a light, add it to the light list
	//--------------------------------------------------
	//
	AttachReference();
	process->lightsToBurn.Add(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SceneProxy::FindLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->FindLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//-----------------------------------------------
	// If the proxy is a scene, look in it for lights
	//-----------------------------------------------
	//
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->FindLights(process);
		child->DetachReference();
		child = next;
		if (!process->continueProcess)
		{
			if (child)
				child->DetachReference();
			break;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	VertexProxy::BurnLights(
		BurnLightsProcess *process,
		DynamicArrayOf<TransformedLight> &lights
	)
{
	Check_Object(this);
	Check_Object(process);
	Check_Object(&lights);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->BurnLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//-------------------------------------------------------------
	// Make sure that this vertex can be lit (i.e. it has a normal)
	//-------------------------------------------------------------
	//
	Normal3D normal;
	if (!GetNormal(&normal))
		return;

	//
	//--------------------------------------------------------------------
	// Get the current color for its alpha, then initialize to the ambient
	// color
	//--------------------------------------------------------------------
	//
	unsigned light_count = lights.GetLength();
	RGBAColor total_color;
	GetColor(&total_color);
	total_color.red = 0.0f;
	total_color.green = 0.0f;
	total_color.blue = 0.0f;

	//
	//-----------------------------------
	// Test each light against the vertex
	//-----------------------------------
	//
	for (unsigned i=0; i<light_count; ++i)
	{
		LightProxy *light = lights[i].lightProxy;
		Check_Object(light);

		//
		//----------------------------------------------------------------
		// Get the light color and if it is an ambient light, just add the
		// light color
		//----------------------------------------------------------------
		//
		RGBColor light_color;
		light->GetColor(&light_color);
		if (light->IsAmbient())
		{
			total_color.red += light_color.red;
			total_color.green += light_color.green;
			total_color.blue += light_color.blue;
			continue;
		}

		//
		//---------------------------------------------------------------------
		// Get the falloff distances.  If there are no falloff distances, it is
		// an infinite light, so set the light normal directly from the matrix
		//---------------------------------------------------------------------
		//
		Scalar n,f;
		UnitVector3D light_z;
		if (!light->GetFalloffDistance(&n, &f))
			lights[i].lightToLocal.GetLocalForwardInWorld(&light_z);

		//
		//---------------------------------------------------------------
		// Otherwise, it will be a point or spot light, in which case the
		// translation component of the lightToLocal matrix contains the
		// vertex to light vector
		//---------------------------------------------------------------
		//
		else
		{
			Point3D vertex_to_light;
			vertex_to_light = lights[i].lightToLocal;
			Point3D position;
			GetPosition(&position);
			vertex_to_light -= position;

			//
			//--------------------------------------------------------------
			// If the distance to the vertex is zero, the light will not
			// contribute to the vertex coloration.  Otherwise, decrease the
			// light level as appropriate to the distance
			//--------------------------------------------------------------
			//
			Scalar length = vertex_to_light.GetLength();
			if (Small_Enough(length) || length>=f)
				continue;
			else if (length > n)
			{
				Verify(f - n > SMALL);
				Scalar falloff = (length - n) / (f - n);
				light_color.red *= falloff;
				light_color.green *= falloff;
				light_color.blue *= falloff;
			}

			//
			//--------------------------------------------------------------
			// If this is a point light, set the light vector to the negated
			// normal of vertex to light
			//--------------------------------------------------------------
			//
			Radian spread_angle;
			if (!light->GetSpreadAngle(&spread_angle))
			{
				length = -1.0f / length;
				light_z.Vector3D::Multiply(vertex_to_light, length);
			}

			//
			//---------------------------------------------------------------
			// Otherwise, this is a spotlight, so we need to reduce the light
			// level further based upon the spread angle of the light
			//---------------------------------------------------------------
			//
			else
			{
				lights[i].lightToLocal.GetLocalForwardInWorld(&light_z);
				length = -1.0f / length;
				vertex_to_light *= length;
				Scalar spread = Cos(spread_angle);
				Scalar t = vertex_to_light * light_z;
				if (t <= spread)
				{
					continue;
				}
				Verify(!Close_Enough(spread, 1.0f));
				spread = 1.0f - ((1.0f - t) / (1.0f - spread));
				light_color.red *= spread;
				light_color.green *= spread;
				light_color.blue *= spread;
				light_z.x = vertex_to_light.x;
				light_z.y = vertex_to_light.y;
				light_z.z = vertex_to_light.z;
			}
		}

		//
		//-------------------------------------------------------------------
		// Now we reduce the light level falling on the vertex based upon the
		// cosine of the angle between light and normal
		//-------------------------------------------------------------------
		//
		Scalar cosine = -(light_z * normal);
		if (cosine > SMALL)
		{
			light_color.red *= cosine;
			light_color.green *= cosine;
			light_color.blue *= cosine;
			total_color.red += light_color.red;
			total_color.green += light_color.green;
			total_color.blue += light_color.blue;
		}
	}

	//
	//-----------------------------------------------------------------
	// We now have the total color on the vertex established, so set it
	//-----------------------------------------------------------------
	//
	Clamp(total_color.red, 0.0f, 1.0f);
	Clamp(total_color.green, 0.0f, 1.0f);
	Clamp(total_color.blue, 0.0f, 1.0f);
	SetColor(total_color);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ChildProxy::BurnLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);
	process->BurnLightsCallback(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	PolygonMeshProxy::BurnLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->BurnLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//-----------------------------------------------------------------
	// Apply the local to parent matrix to the stack, then invert it in
	// preparation for the polygon meshes
	//-----------------------------------------------------------------
	//
	LinearMatrix4D local_to_parent;
	GetLocalToParent(&local_to_parent);
	process->matrixStack.Concatenate(local_to_parent);
	LinearMatrix4D world_to_local;
	world_to_local.Invert(process->matrixStack);

	//
	//-----------------
	// Count the lights
	//-----------------
	//
	ChainIteratorOf<LightProxy*> light_itr(&process->lightsToBurn);
	LightProxy* light;
	int light_count = 0;
	while ((light = light_itr.ReadAndNext()) != NULL)
	{
		Check_Object(light);
		++light_count;
	}

	//
	//------------------------------------------
	// transform all the lights into local space
	//------------------------------------------
	//
	DynamicArrayOf<TransformedLight> lights(light_count);
	light_itr.First();
	unsigned i;
	for (i=0; i<light_count; ++i)
	{
		light = light_itr.ReadAndNext();
		lights[i].lightProxy = light;
		LinearMatrix4D light_to_world;
		light->GetLocalToWorld(&light_to_world);
		lights[i].lightToLocal.Multiply(light_to_world, world_to_local);
	}

	//
	//------------------------------------------
	// Recurse the children, and stop if told to
	//------------------------------------------
	//
	DynamicArrayOf<VertexProxy*> vertices;
	unsigned vertex_count = UseVertexArray(&vertices);
	Verify(vertex_count == vertices.GetLength());
	for (i=0; i<vertex_count; ++i)
	{
		VertexProxy *vertex = vertices[i];
		Check_Object(vertex);
		vertex->BurnLights(process, lights);
		if (!process->continueProcess)
			break;
	}
	DetachArrayReferences(&vertices);
	process->matrixStack.Pop();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	GroupProxy::BurnLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->BurnLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//----------------------------------------------
	// Apply the local to parent matrix to the stack
	//----------------------------------------------
	//
	LinearMatrix4D local_to_parent;
	GetLocalToParent(&local_to_parent);
	process->matrixStack.Concatenate(local_to_parent);

	//
	//------------------------------------------
	// Recurse the children, and stop if told to
	//------------------------------------------
	//
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->BurnLights(process);
		child->DetachReference();
		child = next;
		if (!process->continueProcess)
		{
			if (child)
				child->DetachReference();
			break;
		}
	}
	process->matrixStack.Pop();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	SceneProxy::BurnLights(BurnLightsProcess *process)
{
	Check_Object(this);
	Check_Object(process);

	//
	//--------------------------
	// Call the control callback
	//--------------------------
	//
	process->BurnLightsCallback(this);
	if (!process->continueProcess)
		return;

	//
	//-------------------------------------------------------------
	// Get the ambient color and put a identity matrix on the stack
	//-------------------------------------------------------------
	//
	process->matrixStack.Push(LinearMatrix4D::Identity);

	//
	//---------------------------------------------
	// Burn the lights into each child of the scene
	//---------------------------------------------
	//
	ChildProxy *child = UseFirstChildProxy();
	while (child)
	{
		Check_Object(child);
		ChildProxy *next = child->UseNextSiblingProxy();
		child->BurnLights(process);
		child->DetachReference();
		child = next;
		if (!process->continueProcess)
		{
			if (child)
				child->DetachReference();
			break;
		}
	}

	//
	//-----------------------
	// Remove the last matrix
	//-----------------------
	//
	process->matrixStack.Pop();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	BurnLightsProcess::DiscardLights()
{
	Check_Object(this);

	ChainIteratorOf<LightProxy*> lights(&lightsToBurn);
	LightProxy* light;
	while ((light = lights.ReadAndNext()) != NULL)
	{
		Check_Object(light);
		light->DetachReference();
	}
}
