/****************************************************************************************************

		SimpleLightingModel.cpp
		Copyright: (c) 2001 Eovia Corp. All rights reserved.

		Author:	Lionel
		Date:	10/5/2004

****************************************************************************************************/

#include "SimpleLightingModel.h"
#include "I3DShShader.h"
#include "IShUtilities.h"
#include "IMFPart.h"

const MCGUID TSimpleLightingModel::sClassId ( R_CLSID_SimpleLightingModel );


void Extension3DInit(IMCUnknown* utilities)
{
	// Perform your dll initialization here
}

void Extension3DCleanup()
{
	// Perform any nec clean-up here
}

TBasicUnknown* MakeCOMObject(const MCCLSID& classId)	// This method instanciates
{														// the object TSimpleTerrainFilter
	TBasicUnknown* res = NULL;

	if (classId == TSimpleLightingModel::sClassId) res = new TSimpleLightingModel;

	return res;
}

/*********************************************************************/

TSimpleLightingModel::TSimpleLightingModel()
{
	fShaderList = new TMCPtrArray<I3DShShader>(uint32(0), uint32(3), uint32(1), boolean(false));
	fShaderList->AddElem((I3DShShader*)nil);

	fPMap.fClusterValue = 'lati';
	fPMap.fUVAngle		= 0;
}

TSimpleLightingModel::~TSimpleLightingModel()
{
	delete fShaderList;
}


MCCOMErr TSimpleLightingModel::ExtensionDataChanged()
{
	uint32 nbShaders = fShaderList->GetElemCount();
	TMCCountedPtr<IShParameterComponent> * paramCompsRef = (TMCCountedPtr<IShParameterComponent>*)GetExtensionDataBuffer();
	
	TMCCountedPtr<IShParameterComponent>* paramComps = paramCompsRef;
	for (uint32 ii = 0; ii < nbShaders; ii++)
	{
		TMCCountedPtr<I3DShShader> shader;

		IShParameterComponent * paramComp = *paramComps++;				

		I3DShShader* theShader = nil;
		if (paramComp && (paramComp->QueryInterface(IID_I3DShShader, (void**)&shader) == MC_S_OK))
		{
			theShader = shader;
		}
		SetIndShader(ii, theShader, true);		
		
	}

	return MC_S_OK;
}


void TSimpleLightingModel::SetIndShader(uint32 index, I3DShShader*& theShader, boolean /*withAnim*/ )
{
	MCAssert(index < fShaderList->GetElemCount() );

	I3DShShader* oldShader = (*fShaderList)[index];
	if (oldShader != theShader)
	{
		fShaderList->SetElem(index, theShader);
	}
}


MCCOMErr TSimpleLightingModel::GetShadingFlags(ShadingFlags& theFlags)
{
	TMCPtrArray<I3DShShader>::iterator iter(*fShaderList);
	for(I3DShShader* theShader = iter.First() ; iter.More() ; theShader = iter.Next())
	{
		if (theShader)
		{
			ShadingFlags subFlags;
			theShader->GetShadingFlags(subFlags);			
			theFlags.CombineFlagsWith(subFlags);
		}
	}
	
	theFlags.fNeedsIsoUV = true;

	return MC_S_OK;
}


EShaderOutput TSimpleLightingModel::GetImplementedOutput()
{
	return EShaderOutput(kUsesDoShade+kUseCalculateDirectLighting);
}

MCCOMErr TSimpleLightingModel::DoShade(ShadingOut &result, ShadingIn& theShadingIn)
{
	I3DShShader* const shader = (*fShaderList)[0];

	if (shader)
	{
		shader->DoShade(result,theShadingIn);
	}
	return MC_S_OK;
}

/**********************************************************************************/

// This implementation of CalculateDirectLighting is based on Blanks anisotropic illumination model
// We need to fill the following fields of result: fAmbient, fDiffuseLight, fSpecularLight, fLightShadow and fGlow.

void TSimpleLightingModel::CalculateDirectLighting(LightingDetail&			result,
												   const LightingContext&	lightingContext)
{
	LightingFlags lightingFlags = lightingContext.fLightingFlags;

	if (lightingFlags.fComputeDiffuseDirectLighting)
	{
		const ShadingOut& shading = *result.fShadingOut;

		// first get the ambient light
		I3DExRaytracer* raytracer = lightingContext.fRaytracer;

		const TMCColorRGBA& ambientLight = lightingContext.GetAmbientLight();

		// this attenuates the ambient light for it to look like a distant light (useful if there are no other lights)
		real norm = (shading.fChangedNormal[2]+shading.fChangedNormal[0]) * 0.5f;

		if (norm<0.0f)	norm=-norm;
		norm= 0.5f*(norm-0.5f)+shading.fAmbient;

		if (norm<0.0f) norm=0.0f;
		if (norm>1.0f) norm=1.0f;
		
		// compute the ambient base color 
		result.fAmbient.R = shading.fColor.R * ambientLight.R * norm + shading.fGlow.R;
		result.fAmbient.G = shading.fColor.G * ambientLight.G * norm + shading.fGlow.G;
		result.fAmbient.B = shading.fColor.B * ambientLight.B * norm + shading.fGlow.B;

		// compute the glow color
		result.fGlow = shading.fGlow;

		// compute the lighting of each light
		const int32 lightCount = raytracer->GetLightCount();

		result.fDiffuseLight	= TMCColorRGB::kBlack;
		result.fSpecularLight	= TMCColorRGB::kBlack;

		DirectLighting directLighting;
		const TMCColorRGB&	lighting		= directLighting.fLightColor;
		const TMCColorRGB&	noShadowlighting = directLighting.fNoShadowLightColor;	// the no shadow ligtht color will be used when we need to compute separated shadow
		const TVector3&		lightDirection	= directLighting.fLightDirection;

		const TVector3&		reflectDir	= lightingContext.fReflectDir;
		const TVector3&		normalDir	= shading.fChangedNormal;

		TMCColorRGB			diffShadow = TMCColorRGB::kBlack;
		TMCColorRGB			specShadow = TMCColorRGB::kBlack;

		const IllumSettings& illumSettings = *lightingContext.fIllumSettings;

		for (int32 lightIndex = 0;lightIndex<lightCount; lightIndex++)
		{
			if (raytracer->GetLightIntensity(directLighting,lightIndex,lightingContext,lightingContext.fIllumSettings->fShadowsOn, true))
			{
				result.fIsInShadow	|= directLighting.fIsInShadow;

				// Tangent vector of Blanks Model
				TVector3	BlanksTang	= TVector3::kUnitX;

				switch (fPMap.fClusterValue)
				{
					case kLatitudeID:
					{
						if (!(normalDir.z > 0.9999f || normalDir.z < -0.9999f))
						{
							BlanksTang.SetValues(normalDir.y, -normalDir.x, 0.0f);
							BlanksTang.Normalize();
						}
						break;
					}
					case kMeridianLineID:
					{
						if (!(normalDir.z > 0.9999f || normalDir.z < -0.9999f))
						{
							BlanksTang.SetValues(normalDir.y, -normalDir.x, 0.0f);
							BlanksTang.Normalize();
							BlanksTang = normalDir ^ BlanksTang; // which is normalized
						}
						break;
					}
					case kCustomizedID:
					{
						const TVector3&	isoU = lightingContext.fHit->fIsoU;
						const TVector3&	isoV = lightingContext.fHit->fIsoV;

						BlanksTang = cos(fPMap.fUVAngle) * isoU + sin(fPMap.fUVAngle) * isoV;
						BlanksTang.Normalize();

						break;
					}
					default:
						MCNotify("Hey! We shouldn't get to this point!");
				}

				// ---- Normal vector of Blanks Model ----
				TVector3			BlanksNorm	= lightDirection - (lightDirection * BlanksTang) * BlanksTang;
				BlanksNorm.Normalize();

				const real			diffCoeff	= (BlanksNorm * lightDirection) * shading.fLambert;
				if (diffCoeff > 0)
				{
					const real		cos_light	= normalDir * lightDirection;

					if (illumSettings.fComputeSeparatedShadow)
					{
						// compute diffuse lighting separating the shadow factor, to do that properly
						// we now compute the lighting with and without shadow. we will use this
						// information to build fLightShadow.

						result.fDiffuseLight.R		+= cos_light * diffCoeff * noShadowlighting.R;
						result.fDiffuseLight.G		+= cos_light * diffCoeff * noShadowlighting.G;
						result.fDiffuseLight.B		+= cos_light * diffCoeff * noShadowlighting.B;

						diffShadow.R				+= cos_light * diffCoeff * lighting.R;
						diffShadow.G				+= cos_light * diffCoeff * lighting.G;
						diffShadow.B				+= cos_light * diffCoeff * lighting.B;
					}
					else
					{
						// compute diffuse lighting containing the shadow factor

						result.fDiffuseLight.R		+= cos_light * diffCoeff * lighting.R;
						result.fDiffuseLight.G		+= cos_light * diffCoeff * lighting.G;
						result.fDiffuseLight.B		+= cos_light * diffCoeff * lighting.B;
					}

					// ---- Reflection vector of Blanks Model ----
					const TVector3	eyeDir		= (2 * (reflectDir * normalDir) * normalDir) - reflectDir;
					const TVector3	BlanksRfDir	= (2 * (eyeDir * BlanksNorm) * BlanksNorm) - eyeDir;

					const real		specCos		= BlanksRfDir * lightDirection;					
					if (specCos > 0)
					{
						const real	roughness	= (0.001f+shading.fSpecularSize*shading.fSpecularSize)* kSpecularPowerCoef;
						const real	specCoeff	= 2 * RealPow(specCos, roughness);

						if (illumSettings.fComputeSeparatedShadow)
						{
							// compute specular lighting separating the shadow factor, to do that properly
							// we now compute the lighting with and without shadow. we will use this
							// information to build fLightShadow.

							result.fSpecularLight.R	+= cos_light * specCoeff * noShadowlighting.R;
							result.fSpecularLight.G	+= cos_light * specCoeff * noShadowlighting.G;
							result.fSpecularLight.B	+= cos_light * specCoeff * noShadowlighting.B;

							specShadow.R			+= cos_light * specCoeff * lighting.R;
							specShadow.G			+= cos_light * specCoeff * lighting.G;
							specShadow.B			+= cos_light * specCoeff * lighting.B;
						}
						else
						{
							// compute specular lighting containing the shadow factor

							result.fSpecularLight.R	+= cos_light * specCoeff * lighting.R;
							result.fSpecularLight.G	+= cos_light * specCoeff * lighting.G;
							result.fSpecularLight.B	+= cos_light * specCoeff * lighting.B;
						}
					}
				}
			}
			else
			{
				result.fIsInShadow	|= directLighting.fIsInShadow;
			}
		}

		if (illumSettings.fComputeSeparatedShadow)
		{
			// we can now compute the fLightShadow using the information from the lighting with and without shadow.

			TMCColorRGB shadowFactorDiffuse;
			for (int32 i=0; i<3; ++i)
			{
				if (result.fDiffuseLight[i] > 0) shadowFactorDiffuse[i] = diffShadow[i] / result.fDiffuseLight[i];
				else shadowFactorDiffuse[i] = (result.fIsInShadow ? 0 : 1);
			}

			// the result diffuse and specular light coorespond to the lighting without shadowing
			result.fDiffuseLight = result.fDiffuseLight % shading.fColor;
			result.fSpecularLight = result.fSpecularLight % shading.fSpecularColor;

			// this resColor is the diffuse and specular lighting with shadow. we use it to compute
			// fLightShadow
			TMCColorRGB resColor = (diffShadow % shading.fColor) + (specShadow % shading.fSpecularColor);

			const TMCColorRGB noShadowDiffuseSpecular = result.fDiffuseLight + result.fSpecularLight;
			for (int32 i=0; i<3; ++i)
			{
				if (noShadowDiffuseSpecular[i] > 1e-3f)
					result.fLightShadow[i] = resColor[i] / noShadowDiffuseSpecular[i];
				else
					result.fLightShadow[i] = shadowFactorDiffuse[i];
			}
		}
		else
		{
			// only the shading color is missing to have the correct result

			result.fDiffuseLight = result.fDiffuseLight % shading.fColor;
			result.fSpecularLight = result.fSpecularLight % shading.fSpecularColor;
			result.fLightShadow.Set(1, 1, 1);
		}
	}
}

/*********************************************************************************************/
