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

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

		Author:	Lionel
		Date:	10/5/2004

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

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

#include "DataCompUtils.h"

#include "COMUtilities.h"
#include "COMSafeUtilities.h"
#include "IShUtilities.h"
#include "IShThreadUtilities.h"

#include "RenderTypes.h"
#include "PublicUtilities.h"
#include "IShComponent.h"
#include "IShRasterLayer.h"
#include "SimpleVolumetricEffectDef.h"

#include "I3DShScene.h"
#include "I3DShRenderFeature.h"
#include "I3DRenderingModule.h"

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

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

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

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

	if (classId == TLightConeVolumetric::sClassId)
		res = new TLightConeVolumetric;
	if (classId == TLightConePostRenderer::sClassId)
		res = new TLightConePostRenderer;
	if (classId == TLightConeControl::sClassId)
		res = new TLightConeControl;

	MCAssert(res != NULL);

	return res;
}

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

const MCGUID TLightConeVolumetric::sClassId		(R_CLSID_SimpleLightConeVol);
const MCGUID TLightConePostRenderer::sClassId	(R_CLSID_SimpleLightConePostRend);


// *********************************************************************
//						LightConeInfo
// *********************************************************************

LightConeInfo::LightConeInfo()
{
	fIntensity=1.0f;
	fRadius=230.0f;
	fFogColor.Set(1.0f,1.0f,1.0f,0);
	fFallOff=0;
}

// *********************************************************************
//				TLightCone::TLightCone
// *********************************************************************
TSimpleLightCone::TSimpleLightCone()
{
}


// *********************************************************************
//				LightCone::~LightCone
// *********************************************************************

TSimpleLightCone::~TSimpleLightCone( )
{
}

// ***************************************************************
//				TLightCone::Initialise
//
// ***************************************************************
void TSimpleLightCone::Initialise(I3DShScene* scene)
{
	// initialise the light array

	fLights.ArrayFree();
	fTransforms.ArrayFree();
	fZScale.ArrayFree();

	uint32 lightNbr=scene->GetLightsourcesCount();
	uint32 i=0;

	TMCCountedPtr<I3DShLightsource> light;

	for (i=0;i<lightNbr;i++)
	{
		scene->GetLightsourceByIndex(&light,i);			// we get the light

		TMCCountedPtr<IShParameterComponent> component;
		DataComponentUtils::GetLightDataComponent(light,SimpleLightConeControlID,&component);

		boolean enabled=false;
		component->GetParameter('ENBL',(void*)&enabled);

		if (enabled)
		{
			boolean hasLightCone=false;
			boolean hasLightSphere=false;
			real halfAngle;
			TTransform3D transform;

			if (light) light->GetLightInfo(hasLightCone,hasLightSphere,halfAngle,transform);

			if (hasLightCone) // this light supports the light cone effect
			{
				if (halfAngle>89.0f) halfAngle=89.0f;
				real a=tan(PI*halfAngle/180.0);
				TMatrix33 scale( 1,0,0, 0,1,0, 0,0,a);

				fLights.AddElem(light);
				fZScale.AddElem(a);

				transform=Inverse(transform);
				transform.fRotationAndScale=scale*transform.fRotationAndScale;
				transform.fTranslation=scale*transform.fTranslation;

				fTransforms.AddElem(transform);

				LightConeInfo data;
				component->GetParameter(IDTYPE('I','N','T','S'),(void*)&data.fIntensity);
				component->GetParameter(IDTYPE('R','A','D','I'),(void*)&data.fRadius);
				component->GetParameter(IDTYPE('C','O','L','O'),(void*)&data.fFogColor);
				component->GetParameter(IDTYPE('F','A','L','L'),(void*)&data.fFallOff);

				fLightData.AddElem(data);
			}
		}
	}

	// get light color
	uint32 lightConeNbr=fLights.GetElemCount();

	fLightColorBuffers.SetElemCount(lightConeNbr);
	fLightColor.SetElemCount(lightConeNbr);

	for (i=0;i<lightConeNbr;i++)
	{
		fLightColorBuffers[i]=GetLightColorBuffer(i,fLights[i],fTransforms[i],fLightColor[i]);
	}
}

// ***************************************************************
//				TLightCone::DirectionFilter
//
// ***************************************************************
void TSimpleLightCone::DirectionFilter(const TVector3& rayOrigin,const TVector3& rayDirection,
								real maxt,TMCColorRGBA& inOutFilter,boolean isShadowCasting)
{
	if (isShadowCasting) return; // no effect on shadow casting !

	const int32 lightCount = fLights.GetElemCount();

	real Rt=0,Gt=0,Bt=0,At=0;

	for (int32 lightIndex=0;lightIndex<lightCount;lightIndex++)
	{
		real Rl=0,Gl=0,Bl=0,Al=0;
		real intensity=0;

		// ray to light local coordinates
		TVector3 origin=TransformPoint(fTransforms[lightIndex],rayOrigin);
		TVector3 direction=TransformVector(fTransforms[lightIndex],rayDirection);

		// intersection with the cone : x^2+y^2-z^2=0
		real a=direction.x*direction.x+direction.y*direction.y-direction.z*direction.z;
		real b=direction.x*origin.x+direction.y*origin.y-direction.z*origin.z;
		real c=origin.x*origin.x+origin.y*origin.y-origin.z*origin.z;

		real delta = b*b-a*c;
		real t1,t2; // parameter of intersection with the cone

		if (delta>0.000001f && a!=0.0f)
		{
			delta=sqrt(delta);
			if (a>0.0f)
			{
				t1=(-b-delta)/a;
				t2=(-b+delta)/a;
			}
			else if (a<0.0f)
			{
				t1=(-b+delta)/a;
				t2=(-b-delta)/a;
			}

			real z1,z2; // intersection in local coordinates

			z1=t1*direction.z+origin.z;
			z2=t2*direction.z+origin.z;

			if (z1<0.0 || z2<0.0) // good side of the cone
			{
				if (z1>=0.0f)
				{
					t1=t2;z1=z2;
					t2=1e5;z2=origin.z+t2*direction.z;
				}
				if (z2>=0.0f)
				{
					t2=t1;z2=z1;
					t1=0.0f;z1=origin.z;
				}
				if (t1<maxt && t2>0.0f)	// in front of objects and camera
				{
					if (t1<0.0f) // part of the cone is behind the camera
					{
						t1=0.0f;z1=origin.z;
					}
					if (t2>maxt) // part of the cone is behind the objects
					{
						t2=maxt;z2=origin.z+t2*direction.z;
					}
					real x1=(direction.x*t1+origin.x);
					real y1=(direction.y*t1+origin.y);

					real x2=(direction.x*t2+origin.x);
					real y2=(direction.y*t2+origin.y);

					real dist=1.0f+sqrt(x2*x2+y2*y2+z2*z2);

					const real t = fLightData[lightIndex].fFallOff;

					intensity= (t2-t1) / ( (1.0f-t) * dist + t * dist*dist );

					Rl=fLightColor[lightIndex].R;
					Gl=fLightColor[lightIndex].G;
					Bl=fLightColor[lightIndex].B;

					Rl*=intensity;
					Gl*=intensity;
					Bl*=intensity;

					real maxIntensity = MC_Max(Rl,Gl,Bl);
					if (maxIntensity>1.0f) maxIntensity=1.0f;

					Al= maxIntensity;
				}
			}
			const real lightIntensity = fLightData[lightIndex].fIntensity;

			Rt+=Rl*lightIntensity * fLightData[lightIndex].fFogColor.R;
			Gt+=Gl*lightIntensity * fLightData[lightIndex].fFogColor.G;
 			Bt+=Bl*lightIntensity * fLightData[lightIndex].fFogColor.B;
			At+=Al*lightIntensity;
		}
	}
	inOutFilter.R += Rt;
	inOutFilter.G += Gt;
	inOutFilter.B += Bt;

	MCAssert(inOutFilter.A>=0 && inOutFilter.A<=1.0f);
	MCAssert(At>=0);

	if (At>1.0f) At = 1.0f;

	inOutFilter.A+= (1.0f - inOutFilter.A) * At;
	//inOutFilter.R += Rt;
}

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

// ***************************************************************
//				TLightConeVolumetric::TLightConeVolumetric
//
// ***************************************************************
TLightConeVolumetric::TLightConeVolumetric()
{
}

// ***************************************************************
//				TLightConeVolumetric::~TLightConeVolumetric
//
// ***************************************************************
TLightConeVolumetric::~TLightConeVolumetric()
{
}

// ***************************************************************
//				TLightConeVolumetric::Initialise
//
// ***************************************************************
void TLightConeVolumetric::Initialise(I3DShScene* scene)
{
	TSimpleLightCone::Initialise(scene);
}

// ***************************************************************
//				TLightConeVolumetric::DirectionFilter
//
// ***************************************************************
void TLightConeVolumetric::DirectionFilter(const TVector3& origin,const TVector3& direction,real maxt,TMCColorRGBA& inOutFilter,boolean isShadowCasting)
{
	TSimpleLightCone::DirectionFilter(origin,direction,maxt,inOutFilter,isShadowCasting);
}

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

// ***************************************************************
//				TLightConePostRenderer::TLightConePostRenderer
//
// ***************************************************************
TLightConePostRenderer::TLightConePostRenderer()
{
}

// ***************************************************************
//				TLightConePostRenderer::~TLightConePostRenderer
//
// ***************************************************************
TLightConePostRenderer::~TLightConePostRenderer()
{
}

// ***************************************************************
//				TLightConePostRenderer::GetBufferNeeds
//
// ***************************************************************
void TLightConePostRenderer::GetBufferNeeds(RenderFilterNeeds& needs, void* renderer)
{
	needs.Set(k32Distance); // needs ZBuffer
}


// ***************************************************************
//				TLightConePostRenderer::PrepareDraw
//
// called for each frame before beginning the postrenderering
// by tile
//
// input   : is a rasterLayer with 3 channels (R,G,B) in 16 bits
// bounds  : is the rectangle of the full image
// buffers : is the array of pointer to the GBuffers
// scene   : is a pointer to the current scene being renderered
// uvBox   : is the coordinates of the screen in screen coordinates
//
// ***************************************************************

MCCOMErr TLightConePostRenderer::PrepareDraw(IShRasterLayer* input,
								const TMCRect& bounds,
								IShChannel* buffers[],
								I3DShScene* scene,
								I3DShCamera* renderingCamera,
								const TBBox2D&		uvBox,
								const TBBox2D& productionFrame)
{

	TSimpleLightCone::Initialise(scene);

	// get the rendering camera and screen area

	fCamera=renderingCamera;

	fuvBox=productionFrame;
	fDeltaU=(uvBox.fMax[0]-uvBox.fMin[0])/bounds.GetWidth();
	fDeltaV=-(uvBox.fMax[1]-uvBox.fMin[1])/bounds.GetHeight();

	// keep references to input buffers

	fColorOffscreen=input;
	fZBuffer=buffers[k32Distance];

	return MC_S_OK;
}

// ***************************************************************
//				TLightConePostRenderer::DrawRect
//
// ***************************************************************
MCCOMErr TLightConePostRenderer::DrawRect(const TMCRect& outputRect,
							 const TChannelDataBucket* output[],
							 const TBBox2D &uvBox)
{
	int32 channelCount = fColorOffscreen->GetChannelCount();

	MCAssert(channelCount==3 || channelCount==4); // RGB or RGBA

	TMCPtrArray<const TChannelDataBucket> color(4,true);
	uint16 index[4]={0,1,2,3};
	fColorOffscreen->GetTile(outputRect,color.ArrayData(),index,channelCount,eTileRead);

	const TChannelDataBucket *distancePixelBucket=NULL;
	fZBuffer->GetTile(outputRect,&distancePixelBucket,eTileRead);
	
	TVector2 uv;
	uv[1]=uvBox.fMax[1];

	int32 x,y;
	int32 width=outputRect.GetWidth();
	int32 height=outputRect.GetHeight();

	Ray3D ray;
	TVector3 origin,direction;
	real *distance;

	for (y=0;y<height;y++)
	{
		uv[0]=uvBox.fMin[0];

		uint8* red	  = output[0]->RowPtr(y);
		uint8* green  = output[1]->RowPtr(y);
		uint8* blue   = output[2]->RowPtr(y);
		uint8 *alpha  = NULL;
		
		if (output[3])
		{
			alpha = output[3]->RowPtr(y);
		}

		uint8* oldRed   = color[0]->RowPtr(y);
		uint8* oldGreen = color[1]->RowPtr(y);
		uint8* oldBlue  = color[2]->RowPtr(y);
		uint8 *oldAlpha = NULL;

		if (color[3])
		{
			oldAlpha = color[3]->RowPtr(y);
		}

		distance=(real*)distancePixelBucket->RowPtr32(y);

		for (x=0;x<width;x++)
		{
			gShellThreadUtilities->YieldProcesses(15);

			fCamera->CreateRay(&uv,&ray.fOrigin,&ray.fDirection);

			real maxt=distance[x];

			TMCColorRGBA inOutColor(real(oldRed[x])/255,
									real(oldGreen[x])/255,
									real(oldBlue[x])/255,
									1.0f);

			if (oldAlpha)
			{
				inOutColor.A = real(oldAlpha[x])/255;
			}

			DirectionFilter(ray.fOrigin,ray.fDirection,maxt,inOutColor,false);


			int32 r=(inOutColor.R)*255;
			if (r>255) r=255;
			red[x]=r;

			int32 g=(inOutColor.G)*255;
			if (g>255) g=255;
			green[x]=g;

			int32 b=(inOutColor.B)*255;
			if (b>255) b=255;
			blue[x]=b;

			if (alpha)
			{
				int32 a=(inOutColor.A)*255;
				if (a>255) a=255;
				alpha[x]=a;
			}

			uv[0]+=fDeltaU;
		}
		uv[1]+=fDeltaV;
	}
	fColorOffscreen->UnGetTile(color.ArrayData(),index,channelCount,false);
	fZBuffer->UnGetTile(&distancePixelBucket,false);

	return MC_S_OK;
}

// ***************************************************************
//				TLightConePostRenderer::FinishDraw
//
// ***************************************************************
MCCOMErr TLightConePostRenderer::FinishDraw()
{
	fLights.ArrayFree();
	fTransforms.ArrayFree();
	
	uint32 i;
	for (i=0;i<fLightColorBuffers.GetElemCount();i++)
	{
		delete [] fLightColorBuffers[i];
	}
	fLightColorBuffers.ArrayFree();

	fCamera=NULL;
	fColorOffscreen=NULL;
	fZBuffer=NULL;

	fLightData.ArrayFree();

	return MC_S_OK;
}

// ***************************************************************
//				TLightConePostRenderer::GetLightColorBuffer
//
// ***************************************************************
uint32*	TSimpleLightCone::GetLightColorBuffer(uint32 lightIndex,I3DShLightsource *light,TTransform3D &transform,TMCColorRGB &resultColor)
{
	uint32 width=8; // default size

	uint32 *buffer=new uint32[width*width];
	ThrowIfNoMem(buffer);

	uint32 x,y;
	
	TTransform3D Light2Global=Inverse(transform);

	TVector3 i=TransformVector(Light2Global,TVector3(1,0,0));
	TVector3 j=TransformVector(Light2Global,TVector3(0,1,0));
	TVector3 k=TransformVector(Light2Global,TVector3(0,0,1));
	TVector3 center=TransformPoint(Light2Global,TVector3(0,0,0));

	TVector3 di=i/(0.5f*real(width));
	TVector3 dj=j/(0.5f*real(width));

	TVector3 linePos=center-i-j-k;
	TVector3 pos,lightDir;
	real lightDist;
	TMCColorRGB lightColor;
	real shadowEffect;
	real red=0,green=0,blue=0;
	real nPix=0;

	for (y=0;y<width;y++)
	{
		pos=linePos;
		for (x=0;x<width;x++)
		{
			light->GetDirection(pos,lightDir,lightDist);
			if (light->GetColor(pos,lightDir,lightDist,lightColor,shadowEffect))
			{
				if (lightColor.R>1.0f) lightColor.R=1.0f;
				if (lightColor.G>1.0f) lightColor.G=1.0f;
				if (lightColor.B>1.0f) lightColor.B=1.0f;

				uint32 color=uint8(lightColor.R*255);
				color=(color<<8)+uint8(lightColor.G*255);
				color=(color<<8)+uint8(lightColor.B*255);

				buffer[x+width*y]=color;

				red+=lightColor.R;
				green+=lightColor.G;
				blue+=lightColor.B;
				nPix++;
			}
			else
			{
				buffer[x+width*y]=0;
			}
			pos+=di;
		}
		linePos+=dj;
	}

	if (nPix>0)
	{
		resultColor.R = red / nPix;
		resultColor.G = green / nPix;
		resultColor.B = blue / nPix;
	}
	return buffer;
}
