#include "MLRHeaders.hpp"

//#############################################################################
//#########################    MLRProjectLight    ################################
//#############################################################################

MLRProjectLight::ClassData*
	MLRProjectLight::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::InitializeClass()
{
	Verify(!DefaultData);
	Verify(gos_GetCurrentHeap() == StaticHeap);
	DefaultData =
		new ClassData(
			MLRProjectLightClassID,
			"MidLevelRenderer::MLRProjectLight",
			MLRInfiniteLightWithFalloff::DefaultData
		);
	Register_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRProjectLight::MLRProjectLight() :
	MLRInfiniteLightWithFalloff(DefaultData)
{
	Verify(gos_GetCurrentHeap() == LightsHeap);
	lightMap = NULL;

	inverseLightToShape = Matrix4D::Identity;

	xMin = -0.005f;
	xMax = 0.005f;
	yMin = -0.005f;
	yMax = 0.005f;
	nnear = 0.1f;
	ffar = 1.9f;
	distance = -1.0f;

	CreateProjectMatrix();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRProjectLight::MLRProjectLight(
	Stuff::MemoryStream *stream,
	int version
) :
	MLRInfiniteLightWithFalloff(DefaultData, stream, version)
{
	Check_Object(stream);
	Verify(gos_GetCurrentHeap() == LightsHeap);
	lightMap = NULL;
	if (version > 7)
	{
		MString name;
		*stream >> name;
		if (name.GetLength() > 0)
		{
			Check_Object(MLRTexturePool::Instance);
			MLRTexture *texture = (*MLRTexturePool::Instance)(name, 0);
			if (!texture)
				texture = MLRTexturePool::Instance->Add(name, 0);
			Check_Object(texture);
			lightMap = new MLRLightMap(texture);
			Register_Object(lightMap);
			lightMap->AttachReference();
			lightMask |= MLRState::LightMapLightingMode;
		}
	}
	Radian angle;
	*stream >> angle;
	SetSpreadAngle(angle);

	inverseLightToShape = Matrix4D::Identity;

	xMin = -0.05f;
	xMax = 0.05f;
	yMin = -0.05f;
	yMax = 0.05f;
	nnear = 0.1f;
	ffar = 1.9f;
	distance = -1.0f;

	CreateProjectMatrix();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRProjectLight::MLRProjectLight(Stuff::Page *page):
	MLRInfiniteLightWithFalloff(DefaultData, page)
{
	Check_Object(page);
	Verify(gos_GetCurrentHeap() == LightsHeap);
	lightMap = NULL;
	const char* lightmap;
	if (page->GetEntry("LightMap", &lightmap))
	{
		Check_Pointer(lightmap);
		Check_Object(MLRTexturePool::Instance);
		MLRTexture *texture = (*MLRTexturePool::Instance)(lightmap, 0);
		if (!texture)
			texture = MLRTexturePool::Instance->Add(lightmap, 0);
		Check_Object(texture);
		lightMap = new MLRLightMap(texture);
		Register_Object(lightMap);
		lightMap->AttachReference();
		lightMask |= MLRState::LightMapLightingMode;
	}
	Degree angle=45.0;
	page->GetEntry("Spread", &angle.angle);
	SetSpreadAngle(angle);

	inverseLightToShape = Matrix4D::Identity;

	xMin = -0.05f;
	xMax = 0.05f;
	yMin = -0.05f;
	yMax = 0.05f;
	nnear = 0.1f;
	ffar = 1.9f;
	distance = -1.0f;

	CreateProjectMatrix();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRProjectLight::~MLRProjectLight()
{
	if(lightMap!=NULL)
	{
		lightMap->DetachReference();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);

	MLRInfiniteLightWithFalloff::Save(stream);
	if (lightMap)
	{
		Check_Object(lightMap);
		unsigned handle = lightMap->GetState().GetTextureHandle();
		MLRTexture *texture = (*MLRTexturePool::Instance)[handle];
		Check_Object(texture);
		MString name = texture->GetTextureName();
		*stream << name;
	}
	else
		*stream << *MString::s_Empty;
	*stream << spreadAngle;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::Write(Stuff::Page *page)
{
	Check_Object(this);
	Check_Object(page);

	MLRInfiniteLightWithFalloff::Write(page);
	if (lightMap)
	{
		Check_Object(lightMap);
		unsigned handle = lightMap->GetState().GetTextureHandle();
		MLRTexture *texture = (*MLRTexturePool::Instance)[handle];
		Check_Object(texture);
		page->SetEntry("LightMap", texture->GetTextureName());
	}
	page->SetEntry("Spread", spreadAngle * Degrees_Per_Radian);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::CreateProjectMatrix()
{
	Matrix4D m4_1, m4_2;
	m4_1 = Matrix4D::Identity;
//	m4_1.BuildTranslation(Point3D(0.5f, 0.5f, 0.0f));

//	m4_1(0,0) = 0.5f;
//	m4_1(1,1) = 0.5f;
//	m4_1(2,2) = 1.0f; 
	
	m4_2 = Matrix4D::Identity;
	m4_2.SetPerspective(nnear, ffar, xMax, xMin, yMax, yMin);

	projMatrix.Multiply(m4_2, m4_1);

	LinearMatrix4D moveBack;
	moveBack = LinearMatrix4D::Identity;
	moveBack.BuildTranslation(Point3D(0.0f, 0.0f, distance));

	m4_1.Multiply(moveBack, projMatrix);

	projMatrix = m4_1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::InitLineTextureProjection(
		Stuff::Point3D *lineTestPoints,
		Stuff::RGBAColor *lineTestColors
	)
{
	Point3D p[8];

	p[0] = Point3D(xMin, yMin, (nnear + distance));
	p[1] = Point3D(xMax, yMin, (nnear + distance));
	p[2] = Point3D(xMax, yMax, (nnear + distance));
	p[3] = Point3D(xMin, yMax, (nnear + distance));

	float t = ffar / nnear;
	p[4] = Point3D(xMin * t, yMin * t, (ffar + distance));
	p[5] = Point3D(xMax * t, yMin * t, (ffar + distance));
	p[6] = Point3D(xMax * t, yMax * t, (ffar + distance));
	p[7] = Point3D(xMin * t, yMax * t, (ffar + distance));

	lineTestPoints[0] = p[0]; lineTestPoints[1] = p[1];
	lineTestPoints[2] = p[1]; lineTestPoints[3] = p[2];
	lineTestPoints[4] = p[2]; lineTestPoints[5] = p[3];
	lineTestPoints[6] = p[3]; lineTestPoints[7] = p[0];

	lineTestPoints[8] = p[0]; lineTestPoints[9] = p[4];
	lineTestPoints[10] = p[1]; lineTestPoints[11] = p[5];
	lineTestPoints[12] = p[2]; lineTestPoints[13] = p[6];
	lineTestPoints[14] = p[3]; lineTestPoints[15] = p[7];

	lineTestPoints[16] = p[4]; lineTestPoints[17] = p[5];
	lineTestPoints[18] = p[5]; lineTestPoints[19] = p[6];
	lineTestPoints[20] = p[6]; lineTestPoints[21] = p[7];
	lineTestPoints[22] = p[7]; lineTestPoints[23] = p[4];


	for(int i=0;i<24;i++)
	{
		lineTestColors[i] = RGBAColor (
			Random::GetFraction(),
			Random::GetFraction(),
			Random::GetFraction(),
			1.0f
		);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::SetLightToShapeMatrix(const LinearMatrix4D& worldToShape)
{
	Check_Object(this);

	lightToShape.Multiply(lightToWorld, worldToShape);
	inverseLightToShape.Invert(lightToShape);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::SetSpreadAngle(const Radian &radian)
{
	Check_Object(this);

	spreadAngle = radian;

	tanSpreadAngle = (Scalar)tan(spreadAngle);
	cosSpreadAngle = (Scalar)cos(spreadAngle);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::SetSpreadAngle(const Degree &degree)
{
	Check_Object(this);

	spreadAngle = degree;

	tanSpreadAngle = (Scalar)tan(spreadAngle);
	cosSpreadAngle = (Scalar)cos(spreadAngle);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	MLRProjectLight::GetSpreadAngle(Radian *angle)
{
	Check_Object(this);

	*angle = spreadAngle;

	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::LightVertex(const MLRVertexData& vertexData)
{
	UnitVector3D light_z;
	RGBColor light_color(color);
	Point3D vertex_to_light;

	Verify(GetFalloffDistance(vertex_to_light.x, vertex_to_light.y));

	GetInShapePosition(vertex_to_light);

	vertex_to_light -= *vertexData.point;

	//
	//--------------------------------------------------------------
	// 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.GetApproximateLength();

	Scalar falloff = 1.0f;
	if(GetFalloff(length, falloff))
	{
		light_color.red *= falloff;
		light_color.green *= falloff;
		light_color.blue *= falloff;
	}
	else
	{
		return;
	}

	Scalar cos_spread_angle = GetCosSpreadAngle();

	GetInShapeDirection(light_z);

	length = -1.0f / length;
	vertex_to_light *= length;
	
	Scalar t = vertex_to_light * light_z;
	
	if (t <= cos_spread_angle)
	{
		return;
	}

/*	Verify(!Close_Enough(cos_spread_angle, 1.0f));
	
	spread = 1.0f - ((1.0f - t) / (1.0f - cos_spread_angle));
	
	light_color.red *= cos_spread_angle;
	light_color.green *= cos_spread_angle;
	light_color.blue *= cos_spread_angle;
*/
	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 = 0.7071f;

	if(vertexData.normal!=NULL)
	{
		cosine = -(light_z * (*vertexData.normal));	// * intensity;
	}

	if (cosine > SMALL)
	{
		light_color.red *= cosine;
		light_color.green *= cosine;
		light_color.blue *= cosine;
		Stuff::RGBAColor *t = const_cast<Stuff::RGBAColor*>(vertexData.color);
		Check_Pointer(t);
		t->red += light_color.red;
		t->green += light_color.green;
		t->blue += light_color.blue;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::LightCenter(RGBAColor& rcol)
{
	UnitVector3D light_z;
	RGBColor light_color(color);
	Point3D vertex_to_light;

	Verify(GetFalloffDistance(vertex_to_light.x, vertex_to_light.y));

	GetInShapePosition(vertex_to_light);

	//
	//--------------------------------------------------------------
	// 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.GetApproximateLength();

	Scalar falloff = 1.0f;
	if(GetFalloff(length, falloff))
	{
		light_color.red *= falloff;
		light_color.green *= falloff;
		light_color.blue *= falloff;
	}
	else
	{
		return;
	}

	Scalar cos_spread_angle = GetCosSpreadAngle();

	GetInShapeDirection(light_z);

	length = -1.0f / length;
	vertex_to_light *= length;
	
	Scalar t = vertex_to_light * light_z;
	
	if (t <= cos_spread_angle)
	{
		return;
	}

/*	Verify(!Close_Enough(cos_spread_angle, 1.0f));
	
	spread = 1.0f - ((1.0f - t) / (1.0f - cos_spread_angle));
	
	light_color.red *= cos_spread_angle;
	light_color.green *= cos_spread_angle;
	light_color.blue *= cos_spread_angle;
*/
	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 = 0.7071f;

	if (cosine > SMALL)
	{
		light_color.red *= cosine;
		light_color.green *= cosine;
		light_color.blue *= cosine;

		rcol.red += light_color.red;
		rcol.green += light_color.green;
		rcol.blue += light_color.blue;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRProjectLight::SetLightMap(MLRLightMap *light_map)
{
	Check_Object(this);
	if (lightMap)
	{
		lightMap->DetachReference();
	}
	lightMap = light_map;

	if (lightMap == NULL)
	{
		lightMask &= ~MLRState::LightMapLightingMode;
	}
	else
	{
		lightMap->AttachReference();
		lightMask |= MLRState::LightMapLightingMode;
	}
}
