/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"
#include "plLightInfo.h"
#include "plLightKonstants.h"
#include "hsBounds.h"
#include "hsStream.h"
#include "hsResMgr.h"
#include "pnMessage/plNodeRefMsg.h"
#include "plgDispatch.h"
#include "plIntersect/plVolumeIsect.h"
#include "plDrawable/plSpaceTree.h"
#include "plDrawable/plDrawableGenerator.h"
#include "plDrawable/plDrawableSpans.h"
#include "plPipeline/hsGDeviceRef.h"
#include "plPipeline/plRenderTarget.h"
#include "hsFastMath.h"
#include "pnSceneObject/plDrawInterface.h"
#include "plSurface/plLayerInterface.h"
#include "plSurface/plLayer.h"
#include "plSurface/hsGMaterial.h"
#include "plGImage/plMipmap.h"
#include "plMessage/plRenderMsg.h"
#include "plMessage/plRenderRequestMsg.h"
#include "plScene/plRenderRequest.h"
#include "plPipeline.h"
#include "plIntersect/plSoftVolume.h"
#include "plPipeline/plPipeDebugFlags.h"
#include "pnMessage/plPipeResMakeMsg.h"

#include "plScene/plVisRegion.h"
#include "plScene/plVisMgr.h"

// heinous
#include "plNetClient/plNetClientMgr.h"
#include "pnMessage/plEnableMsg.h"
static hsScalar kMaxYon = 1000.f;
static hsScalar kMinHither = 1.f;

#include "plLightProxy.h"

#include "plDrawable/plDrawableGenerator.h"

plLightInfo::plLightInfo()
:	fSceneNode(nil),
	fDeviceRef(nil),
	fVolFlags(0),
	fProjection(nil),
	fSoftVolume(nil)
{
	fLightToWorld.Reset();
	fWorldToLight.Reset();

	fLocalToWorld.Reset();
	fWorldToLocal.Reset();

	fLightToLocal.Reset();
	fLocalToLight.Reset();

	fWorldToProj.Reset();

	fNextDevPtr = nil;
	fPrevDevPtr = nil;

	fProxyGen = TRACKED_NEW plLightProxy;
	fProxyGen->Init(this);

	fRegisteredForRenderMsg = false;

	fVisSet.SetBit(plVisMgr::kNormal);
}

plLightInfo::~plLightInfo()
{
	if( fNextDevPtr != nil || fPrevDevPtr != nil )
		Unlink();

	hsRefCnt_SafeUnRef( fDeviceRef );

	if( fRegisteredForRenderMsg )
	{
		plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());
		plgDispatch::Dispatch()->UnRegisterForExactType(plPipeRTMakeMsg::Index(), GetKey());
		fRegisteredForRenderMsg = false;
	}

	delete fProxyGen;
}

void plLightInfo::SetDeviceRef( hsGDeviceRef *ref )
{
	hsRefCnt_SafeAssign( fDeviceRef, ref ); 
}

void plLightInfo::IRefresh()
{
	ICheckMaxStrength();
	if( fDeviceRef )
		fDeviceRef->SetDirty( true );
}

void plLightInfo::ICheckMaxStrength()
{
	hsScalar r = GetDiffuse().r >= 0 ? GetDiffuse().r : -GetDiffuse().r;
	hsScalar g = GetDiffuse().g >= 0 ? GetDiffuse().g : -GetDiffuse().g;
	hsScalar b = GetDiffuse().b >= 0 ? GetDiffuse().b : -GetDiffuse().b;
	fMaxStrength = 
		r > g 
		?	(
			r > b
				? r
				: b
			)
		:	(
			g > b
				? g
				: b
			);

	const hsScalar kMinMaxStrength = 1.e-2f;
	SetZero(fMaxStrength < kMinMaxStrength);
}

void plLightInfo::GetStrengthAndScale(const hsBounds3Ext& bnd, hsScalar& strength, hsScalar& scale) const
{
	if( IsIdle() )
	{
		strength = scale = 0.f;
		return;
	}

	strength = fMaxStrength;

	scale = 1.f;
	if( fSoftVolume )
	{
		scale = fSoftVolume->GetStrength(bnd.GetCenter());
		strength *= scale;

	}
	return;		
}

void plLightInfo::GetAffectedForced(const plSpaceTree* space, hsBitVector& list, hsBool charac)
{
	Refresh();

	if( IGetIsect() )
	{
		space->HarvestLeaves(IGetIsect(), list);
	}
	else
	{
		list.Set(space->GetNumLeaves());
	}
}

void plLightInfo::GetAffected(const plSpaceTree* space, hsBitVector& list, hsBool charac)
{
	Refresh();

	if( IsIdle() )
		return;

	if( !GetProperty(kLPHasIncludes) || (GetProperty(kLPIncludesChars) && charac) )
	{
		if( IGetIsect() )
		{
			space->HarvestLeaves(IGetIsect(), list);
		}
		else
		{
			list.Set(space->GetNumLeaves());
		}
	}
}

const hsTArray<Int16>& plLightInfo::GetAffected(plSpaceTree* space, const hsTArray<Int16>& visList, hsTArray<Int16>& litList, hsBool charac)
{
	Refresh();

	if( !IsIdle() )
	{
		if( !GetProperty(kLPHasIncludes) || (GetProperty(kLPIncludesChars) && charac) )
		{
			if( IGetIsect() )
			{
				static hsBitVector cache;
				cache.Clear();
				space->EnableLeaves(visList, cache);

				space->HarvestEnabledLeaves(IGetIsect(), cache, litList);

				return litList;
			}
			else
			{
				return visList;
			}
		}
	}

	litList.SetCount(0);
	return litList;
}

//// Set/GetProperty /////////////////////////////////////////////////////////
//	Sets/gets a property just like the normal Set/GetNativeProperty, but the 
//	flag taken in is from plDrawInterface, not our props flags. So we have to 
//	translate...

void plLightInfo::SetProperty( int prop, hsBool on )
{
	plObjInterface::SetProperty(prop, on);
	if( kDisable == prop )
		fProxyGen->SetDisable(on);
}

//// SetSpecular /////////////////////////////////////////////////////////////
//	A bit more complicated here--make sure we set/clear the kLPHasSpecular
//	flag so we can test more easily for such a condition.

void plLightInfo::SetSpecular( const hsColorRGBA& c ) 
{ 
	fSpecular = c; 

	if( fSpecular.r == 0.f && fSpecular.g == 0.f && fSpecular.b == 0.f )
		SetProperty( kLPHasSpecular, false );
	else
		SetProperty( kLPHasSpecular, true );

	SetDirty(); 
}

void plLightInfo::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l)
{
	fLocalToWorld = l2w;
	fWorldToLocal = w2l;

	fLightToWorld = l2w * fLightToLocal; 
	fWorldToLight = fLocalToLight * w2l; 

	if( IGetIsect() )
		IGetIsect()->SetTransform(fLightToWorld, fWorldToLight);

	if( fDeviceRef != nil )
		fDeviceRef->SetDirty( true );

	fProxyGen->SetTransform(fLightToWorld, fWorldToLight);

	SetDirty(true);

	if( GetProjection() )
	{
		Refresh();
		hsMatrix44 w2proj = IGetWorldToProj();
		plLayer* lay = plLayer::ConvertNoRef(GetProjection()->BottomOfStack());
		if( lay )
		{
			lay->SetTransform(w2proj);
		}
	}
}

void plLightInfo::SetLocalToLight(const hsMatrix44& l2lt, const hsMatrix44& lt2l)
{
	fLocalToLight = l2lt;
	fLightToLocal = lt2l;
}

const hsMatrix44& plLightInfo::GetLocalToWorld() const
{
	return fLocalToWorld;
}

const hsMatrix44& plLightInfo::GetWorldToLocal() const
{
	return fWorldToLocal;
}

const hsMatrix44& plLightInfo::GetLightToWorld() const
{
	return fLightToWorld;
}

const hsMatrix44& plLightInfo::GetWorldToLight() const
{
	return fWorldToLight;
}

#include "plProfile.h"
plProfile_CreateTimer("Light Info", "RenderSetup", LightInfo);

void plLightInfo::IAddVisRegion(plVisRegion* reg)
{
	if( reg )
	{
		int idx = fVisRegions.Find(reg);
		if( fVisRegions.kMissingIndex == idx )
		{
			fVisRegions.Append(reg);
			if( reg->GetProperty(plVisRegion::kIsNot) )
				fVisNot.SetBit(reg->GetIndex());
			else
			{
				fVisSet.SetBit(reg->GetIndex());
				if( reg->ReplaceNormal() )
					fVisSet.ClearBit(plVisMgr::kNormal);
			}
		}
	}
}

void plLightInfo::IRemoveVisRegion(plVisRegion* reg)
{
	if( reg )
	{
		int idx = fVisRegions.Find(reg);
		if( fVisRegions.kMissingIndex != idx )
		{
			fVisRegions.Remove(idx);
			if( reg->GetProperty(plVisRegion::kIsNot) )
				fVisNot.ClearBit(reg->GetIndex());
			else
				fVisSet.ClearBit(reg->GetIndex());
		}
	}
}


hsBool plLightInfo::MsgReceive(plMessage* msg)
{
	plRenderMsg* rendMsg = plRenderMsg::ConvertNoRef(msg);
	if( rendMsg )
	{
		plProfile_BeginLap(LightInfo, this->GetKey()->GetUoid().GetObjectName());

		if( !fDeviceRef && !GetProperty(kLPShadowOnly) )
		{
			rendMsg->Pipeline()->RegisterLight( this );
		}

		ICheckMaxStrength();

		plProfile_EndLap(LightInfo, this->GetKey()->GetUoid().GetObjectName());
		return true;
	}
	plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg);
	if( refMsg )
	{
		if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
		{
			switch( refMsg->fType )
			{
			case kProjection:
				fProjection = plLayerInterface::ConvertNoRef(refMsg->GetRef());
				{
					if( GetKey() && GetKey()->GetName() && !strncmp(GetKey()->GetName(), "RTPatternLight", strlen("RTPatternLight")) )
						SetProperty(kLPForceProj, true);
				}
				break;
			case kSoftVolume:
				fSoftVolume = plSoftVolume::ConvertNoRef(refMsg->GetRef());
				break;
			case kVisRegion:
				IAddVisRegion(plVisRegion::ConvertNoRef(refMsg->GetRef()));
				break;
			}
		}
		else if( refMsg->GetContext() & (plRefMsg::kOnRemove | plRefMsg::kOnDestroy) )
		{
			switch( refMsg->fType )
			{
			case kProjection:
				fProjection = nil;
				break;
			case kSoftVolume:
				fSoftVolume = nil;
				break;
			case kVisRegion:
				IRemoveVisRegion(plVisRegion::ConvertNoRef(refMsg->GetRef()));
				break;
			}
		}
		return true;
	}
	plPipeRTMakeMsg* rtMake = plPipeRTMakeMsg::ConvertNoRef(msg);
	if( rtMake )
	{
		// Make sure we're registered with the pipeline
		// If we're only here to cast shadows, just don't tell anyone
		// about us.
		if( !fDeviceRef && !GetProperty(kLPShadowOnly) )
		{
			rtMake->Pipeline()->RegisterLight( this );
		}
		return true;
	}
	plEnableMsg* enaMsg = plEnableMsg::ConvertNoRef(msg);
	if( enaMsg )
	{
		SetProperty(kDisable, enaMsg->Cmd(plEnableMsg::kDisable));
		return true;
	}
	


	return plObjInterface::MsgReceive(msg);
}


void plLightInfo::Read(hsStream* s, hsResMgr* mgr)
{
	hsRefCnt_SafeUnRef( fDeviceRef );
	fDeviceRef = nil;

	plObjInterface::Read(s, mgr);

	fAmbient.Read(s);
	fDiffuse.Read(s);
	fSpecular.Read(s);

	fLightToLocal.Read(s);
	fLocalToLight.Read(s);

	fLightToWorld.Read(s);
	fWorldToLight.Read(s);

	fLocalToWorld = fLightToWorld * fLocalToLight;
	fWorldToLocal = fLightToLocal * fWorldToLight;

	mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kProjection), plRefFlags::kActiveRef);

	mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kSoftVolume), plRefFlags::kActiveRef);

	// Let our sceneNode know we're here.
	plKey nodeKey = mgr->ReadKey(s);
	ISetSceneNode(nodeKey);

	int n = s->ReadSwap32();
	fVisRegions.SetCountAndZero(n);
	int i;
	for( i = 0; i < n; i++ )
		mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kVisRegion), plRefFlags::kActiveRef);

	SetDirty(true);
}

void plLightInfo::Write(hsStream* s, hsResMgr* mgr)
{
	plObjInterface::Write(s, mgr);

	fAmbient.Write(s);
	fDiffuse.Write(s);
	fSpecular.Write(s);

	fLightToLocal.Write(s);
	fLocalToLight.Write(s);

	fLightToWorld.Write(s);
	fWorldToLight.Write(s);

	mgr->WriteKey(s, GetProjection());

	mgr->WriteKey(s, fSoftVolume);

	mgr->WriteKey(s, fSceneNode);

	s->WriteSwap32(fVisRegions.GetCount());
	int i;
	for( i = 0; i < fVisRegions.GetCount(); i++ )
		mgr->WriteKey(s, fVisRegions[i]);
}

// These two should only be called by the SceneObject
void plLightInfo::ISetSceneNode(plKey node)
{
	if( node != fSceneNode )
	{
		if( node )
		{
			plNodeRefMsg* refMsg = TRACKED_NEW plNodeRefMsg(node, plRefMsg::kOnCreate, -1, plNodeRefMsg::kLight);
			hsgResMgr::ResMgr()->AddViaNotify(GetKey(), refMsg, plRefFlags::kPassiveRef);
		}
		if( fSceneNode )
		{
			fSceneNode->Release(GetKey());
		}
	}
	fSceneNode = node;

	if( fSceneNode != nil )
	{
		if( !fRegisteredForRenderMsg )
		{
			plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
			plgDispatch::Dispatch()->RegisterForExactType(plPipeRTMakeMsg::Index(), GetKey());
			fRegisteredForRenderMsg = true;
		}
	}
	else if( fRegisteredForRenderMsg )
	{
		plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());
		plgDispatch::Dispatch()->UnRegisterForExactType(plPipeRTMakeMsg::Index(), GetKey());
		fRegisteredForRenderMsg = false;
	}
}

plKey plLightInfo::GetSceneNode() const
{
	return fSceneNode;
}

//// Link & Unlink ///////////////////////////////////////////////////////

void	plLightInfo::Unlink( void )
{
	hsAssert( fPrevDevPtr, "Light info not in list" );
	if( fNextDevPtr )
		fNextDevPtr->fPrevDevPtr = fPrevDevPtr;
	*fPrevDevPtr = fNextDevPtr;

	fNextDevPtr = nil;
	fPrevDevPtr = nil;
}

void	plLightInfo::Link( plLightInfo **back )
{
	hsAssert( fNextDevPtr == nil && fPrevDevPtr == nil, "Trying to link a lightInfo that's already linked" );

	fNextDevPtr = *back;
	if( *back )
		(*back)->fPrevDevPtr = &fNextDevPtr;
	fPrevDevPtr = back;
	*back = this;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
///// Standard light types
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// Directional

plDirectionalLightInfo::plDirectionalLightInfo()
{
}

plDirectionalLightInfo::~plDirectionalLightInfo()
{
}

void plDirectionalLightInfo::GetStrengthAndScale(const hsBounds3Ext& bnd, hsScalar& strength, hsScalar& scale) const
{
	plLightInfo::GetStrengthAndScale(bnd, strength, scale);
}

void plDirectionalLightInfo::Read(hsStream* s, hsResMgr* mgr)
{
	plLightInfo::Read(s, mgr);
}

void plDirectionalLightInfo::Write(hsStream* s, hsResMgr* mgr)
{
	plLightInfo::Write(s, mgr);
}


hsVector3 plDirectionalLightInfo::GetWorldDirection() const
{ 
	return -fLightToWorld.GetAxis( hsMatrix44::kUp );
}

//////////////////////////////////////////////////////////////////////////
// Limited Directional
plLimitedDirLightInfo::plLimitedDirLightInfo()
:	fParPlanes(nil)
{
}

plLimitedDirLightInfo::~plLimitedDirLightInfo()
{
	delete fParPlanes;
}

void plLimitedDirLightInfo::IRefresh()
{
	plLightInfo::IRefresh();

	if( !IGetIsect() )
		IMakeIsect();

	if( GetProjection() )
	{
		hsMatrix44 l2ndc;
		l2ndc.Reset();

		hsScalar width = fWidth;
		hsScalar height = fHeight;

		l2ndc.fMap[0][0] = 1.f / width;
		l2ndc.fMap[0][3] = 0.5f;

		l2ndc.fMap[1][1] = -1.f / height;
		l2ndc.fMap[1][3] = 0.5f;

		// Map Screen Z to range 0 (at hither) to 1 (at yon)
		// No, map z dead on to 1.f
		l2ndc.fMap[2][2] = 1.f / fDepth;
		l2ndc.fMap[2][3] = 0;

		l2ndc.fMap[3][3] = 1.f;
		l2ndc.NotIdentity();

		fWorldToProj = l2ndc * fWorldToLight;
	}
}

void plLimitedDirLightInfo::GetStrengthAndScale(const hsBounds3Ext& bnd, hsScalar& strength, hsScalar& scale) const
{
	// If we haven't culled the object, return that we're full strength.
	plLightInfo::GetStrengthAndScale(bnd, strength, scale);
}

void plLimitedDirLightInfo::Read(hsStream* s, hsResMgr* mgr)
{
	plDirectionalLightInfo::Read(s, mgr);

	fWidth = s->ReadSwapScalar();
	fHeight = s->ReadSwapScalar();
	fDepth = s->ReadSwapScalar();
}

void plLimitedDirLightInfo::Write(hsStream* s, hsResMgr* mgr)
{
	plDirectionalLightInfo::Write(s, mgr);

	s->WriteSwapScalar(fWidth);
	s->WriteSwapScalar(fHeight);
	s->WriteSwapScalar(fDepth);
}

void plLimitedDirLightInfo::IMakeIsect()
{
	if( !fParPlanes )
		fParPlanes = TRACKED_NEW plParallelIsect;

	fParPlanes->SetNumPlanes(3);
	
	hsPoint3 p0, p1;

	hsScalar width = fWidth;
	hsScalar height = fHeight;
	p0.Set(-width*0.5f, 0, 0);
	p1.Set(width*0.5f, 0, 0);
	fParPlanes->SetPlane(0, p0, p1);

	p0.Set(0, -height * 0.5f, 0);
	p1.Set(0, height * 0.5f, 0);
	fParPlanes->SetPlane(1, p0, p1);

	p0.Set(0, 0, 0);
	p1.Set(0, 0, -fDepth);
	fParPlanes->SetPlane(2, p0, p1);

	fParPlanes->SetTransform(fLightToWorld, fWorldToLight);
}

//// ICreateProxy //////////////////////////////////////////////////////
//	Creates a new box drawable for showing the light's
//	influence.

plDrawableSpans* plLimitedDirLightInfo::CreateProxy(hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
{
	hsPoint3 corner;
	corner.Set(-fWidth*0.5f, -fHeight*0.5f, -fDepth);
	hsVector3 vecs[3];
	vecs[0].Set(fWidth, 0, 0);
	vecs[1].Set(0, fHeight, 0);
	vecs[2].Set(0, 0, fDepth);
		// Generate a rectangular drawable based on a corner and three vectors
	plDrawableSpans* draw = plDrawableGenerator::GenerateBoxDrawable( corner, vecs[0], vecs[1], vecs[2], 
															mat, 
															fLightToWorld, 
															true,
															nil,
															&idx, 
															addTo );

	return draw;
}



//////////////////////////////////////////////////////////////////////////
// Omni
plOmniLightInfo::plOmniLightInfo()
:	fAttenConst(0),
	fAttenLinear(1.f),
	fAttenQuadratic(0),
	fAttenCutoff(0),
	fSphere(nil)
{
}

plOmniLightInfo::~plOmniLightInfo()
{
	delete fSphere;
}

void plOmniLightInfo::IMakeIsect()
{
	fSphere = TRACKED_NEW plSphereIsect;
	fSphere->SetTransform(fLightToWorld, fWorldToLight);
}

void plOmniLightInfo::GetStrengthAndScale(const hsBounds3Ext& bnd, hsScalar& strength, hsScalar& scale) const
{
	plLightInfo::GetStrengthAndScale(bnd, strength, scale);

	// Volume - Want to base this on the closest point on the bounds, instead of just the center.
	const hsPoint3& pos = bnd.GetCenter();

	hsScalar dist = hsVector3(&pos, &GetWorldPosition()).MagnitudeSquared();
	dist = 1.f / hsFastMath::InvSqrtAppr(dist);
	if( fAttenQuadratic > 0 )
	{
		strength /= (fAttenConst + fAttenLinear * dist + fAttenQuadratic * dist * dist);
	}
	else if( fAttenLinear > 0 )
	{
		strength /= (fAttenConst + fAttenLinear * dist);
	}
	else if( fAttenConst > 0 )
	{
		strength /= fAttenConst;
	}
	else if( fAttenCutoff > 0 )
	{
		if( dist > fAttenCutoff )
			strength = 0;
	}
}

hsScalar plOmniLightInfo::GetRadius() const
{
	hsScalar radius = 0;
	if( fAttenQuadratic > 0 )
	{
		hsScalar mult = fDiffuse.a >= 0 ? fDiffuse.a : -fDiffuse.a;
		hsScalar det = fAttenLinear*fAttenLinear - 4.f * fAttenQuadratic * fAttenConst * (1.f - mult * plSillyLightKonstants::GetFarPowerKonst());
		if( det > 0 )
		{
			det = hsSquareRoot(det);

			radius = -fAttenLinear + det;
			radius /= fAttenQuadratic * 2.f;
			if( radius < 0 )
				radius = 0;
		}
	}
	else if( fAttenLinear > 0 )
	{
		hsScalar mult = fDiffuse.a >= 0 ? fDiffuse.a : -fDiffuse.a;
		radius = (mult * plSillyLightKonstants::GetFarPowerKonst() - 1.f ) * fAttenConst / fAttenLinear;
	}
	else if( fAttenCutoff > 0 )
	{
		radius = fAttenCutoff;
	}

	return radius;
}

void plOmniLightInfo::IRefresh()
{
	plLightInfo::IRefresh();

	if( IsAttenuated() )
	{
		if( !fSphere )
			IMakeIsect();
		fSphere->SetRadius(GetRadius());
	}
	else
	{
		delete fSphere;
		fSphere = nil;
	}
}

hsVector3 plOmniLightInfo::GetNegativeWorldDirection(const hsPoint3& pos) const
{
	return hsFastMath::NormalizeAppr(hsVector3(&GetWorldPosition(), &pos));
}

void plOmniLightInfo::Read(hsStream* s, hsResMgr* mgr)
{
	plLightInfo::Read(s, mgr);

	fAttenConst = s->ReadSwapScalar();
	fAttenLinear = s->ReadSwapScalar();
	fAttenQuadratic = s->ReadSwapScalar();
	fAttenCutoff = s->ReadSwapScalar();
}

void plOmniLightInfo::Write(hsStream* s, hsResMgr* mgr)
{
	plLightInfo::Write(s, mgr);

	s->WriteSwapScalar(fAttenConst);
	s->WriteSwapScalar(fAttenLinear);
	s->WriteSwapScalar(fAttenQuadratic);
	s->WriteSwapScalar( fAttenCutoff );
}


//// ICreateProxy //////////////////////////////////////////////////////
//	Creates a new sphere drawable for showing the omnilight's
//	sphere (haha) of influence.

plDrawableSpans* plOmniLightInfo::CreateProxy(hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
{
	float	rad = GetRadius();
	if( rad == 0 )
		rad = 50;

	plDrawableSpans* draw = plDrawableGenerator::GenerateSphericalDrawable(hsPoint3(0,0,0), 
														rad, 
														mat, 
														fLightToWorld, 
														true,
														nil,
														&idx,
														addTo);

	return draw;
}


//////////////////////////////////////////////////////////////////////////
// Spot
plSpotLightInfo::plSpotLightInfo()
:	fFalloff(1.f),
	fSpotInner(hsScalarPI * 0.125f),
	fSpotOuter(hsScalarPI * 0.25f),
	fCone(nil)
{
}

plSpotLightInfo::~plSpotLightInfo()
{
	delete fCone;
}

void plSpotLightInfo::GetStrengthAndScale(const hsBounds3Ext& bnd, hsScalar& strength, hsScalar& scale) const
{
	plOmniLightInfo::GetStrengthAndScale(bnd, strength, scale);

	// Volume - Want to base this on the closest point on the bounds, instead of just the center.
	const hsPoint3& pos = bnd.GetCenter();
	
	hsVector3 del;
	del.Set(&pos, &GetWorldPosition());
	hsScalar invDist = del.MagnitudeSquared();
	invDist = hsFastMath::InvSqrtAppr(invDist);

	hsScalar dot = del.InnerProduct(GetWorldDirection());
	dot *= invDist;

	hsScalar cosInner, cosOuter, t;
	hsFastMath::SinCosInRangeAppr(fSpotInner, t, cosInner);
	hsFastMath::SinCosInRangeAppr(fSpotOuter, t, cosOuter);
	if( dot < cosOuter )
		strength = 0;
	else if( dot < cosInner )
		strength *= (dot - cosOuter) / (cosInner - cosOuter);
}

void plSpotLightInfo::IMakeIsect()
{
	fCone = TRACKED_NEW plConeIsect;
	fCone->SetTransform(fLightToWorld, fWorldToLight);
}

void plSpotLightInfo::IRefresh()
{
	plLightInfo::IRefresh();

	if( !fCone )
		IMakeIsect();

	hsScalar effFOV = fSpotOuter;
	fCone->SetAngle(effFOV);
	

	if( IsAttenuated() )
	{
		fCone->SetLength(GetRadius());
		fCone->SetTransform(fLightToWorld, fWorldToLight);
	}

	if( GetProjection() )
	{
		hsScalar yon = GetRadius();
		if( yon < kMinHither )
			yon = kMaxYon;
		hsScalar hither = hsMinimum(kMinHither, yon * 0.5f);

		hsScalar sinFOV, cosFOV;
		hsFastMath::SinCos(effFOV, sinFOV, cosFOV);

		hsMatrix44 l2ndc;
		l2ndc.Reset();
		l2ndc.fMap[0][0] =   cosFOV / sinFOV * 0.5f;
		l2ndc.fMap[0][2] = -0.5f;
		l2ndc.fMap[1][1] =  -cosFOV / sinFOV * 0.5f;
		l2ndc.fMap[1][2] = -0.5f;
		l2ndc.fMap[2][2] =	-yon / (yon - hither);
		l2ndc.fMap[3][3] =	0;
		l2ndc.fMap[3][2] =	-1.f;

		l2ndc.NotIdentity();

		fWorldToProj = l2ndc * fWorldToLight;
	}
}

void plSpotLightInfo::Read(hsStream* s, hsResMgr* mgr)
{
	plOmniLightInfo::Read(s, mgr);

	fFalloff = s->ReadSwapScalar();
	fSpotInner = s->ReadSwapScalar();
	fSpotOuter = s->ReadSwapScalar();
}

void plSpotLightInfo::Write(hsStream* s, hsResMgr* mgr)
{
	plOmniLightInfo::Write(s, mgr);

	s->WriteSwapScalar(fFalloff);
	s->WriteSwapScalar(fSpotInner);
	s->WriteSwapScalar(fSpotOuter);
}


hsVector3 plSpotLightInfo::GetWorldDirection() const
{ 
	return -fLightToWorld.GetAxis( hsMatrix44::kUp );
}

//// ICreateProxy //////////////////////////////////////////////////////
//	Generates a new drawable for showing the spotlight's
//	sphere of influence.

plDrawableSpans* plSpotLightInfo::CreateProxy(hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
{
	float	rad = GetRadius();
	float	x, y;


	if( rad == 0 )
		rad = 80;

	hsFastMath::SinCosAppr( GetSpotOuter(), x, y );

	plDrawableSpans* draw = plDrawableGenerator::GenerateConicalDrawable(rad * x / y, -rad, 
														mat, 
														fLightToWorld, 
														true,
														nil,
														&idx,
														addTo);
	return draw;
}