#include "stdafx.h"
#include "SceneNode.h"

#include "Camera.h"
#include "SceneManager.h"
//#include "LightSceneNode.h"
//#include "LightAgent.h"

cSceneNode::cSceneNode( eType type )
: mType( type )
, mNeedUpdate( false )
, mAccumTime( 0 )
, mAlphaForced( false )
, mAlphaBlended( false )
, mTargetAlpha( 1.0f )
, mAlpha( 1.0f )
, mAlphaSpeed( 0.8f )
, mIndexByManager( UINT_MAX )
, mContainer( 0 )
, mIteratorValid( false )
, mIteratorByContainer( 0 )
, mAttachSelectLight( false )
, mUpdateSelectLIght( false )
, mViewNode( true )
{
	mPickPos = NiPoint3::ZERO;
	mPickDistance = 0.0f;

	//switch( mType )
	//{
	//case eNULL:
	//case eAREA:
	//case eLIGHT:
	//case eSOUND:
	//case eEFFECT:
	//case eMAPPORTAL:
	//	break;
	//case eSTATIC:
	//case eDYNAMIC:
	//case ePLAYER:
	//case eNPC:
	//case eMONSTER:
	//case eHERO:
	//case eDROPITEM:
	//	mLightArray.Reserve( 8 );
	//	break;
	//default:
	//	assert( 0 );
	//}
}

cSceneNode::~cSceneNode()
{
	cAlphaDataList::cIterator i = mAlphaDataList.Begin();
	cAlphaDataList::cIterator iend = mAlphaDataList.End();

	for( ; i != iend; ++i )
	{
		delete (cAlphaData*)(*i);
	}
	mAlphaDataList.Clear();

	mPick.RemoveTarget();
}

void cSceneNode::OnProcess( float /*deltaTime*/, float accumTime )
{
	mAccumTime = accumTime;

	if( mNeedUpdate )
	{
		mNeedUpdate = false;
		mNiObject->Update( accumTime );

		/// 경계구를 설정
		mBoundSphere.Set( GetCenter(), GetRadius() );

		/// 조명을 처리
		switch( mType )
		{
		case eNULL:
		case eAREA:
		case eLIGHT:
		case eSOUND:
		case eEFFECT:
		case eMAPPORTAL:
			break;
		case eSTATIC:
		case eDYNAMIC:
		case ePLAYER:
		case eNPC:
		case eMONSTER:
		case eHERO:
		case eDROPITEM:
			{
/*
				bool updateEffects = false;

				for( unsigned int i = 0, iend = mLightArray.GetSize(); i < iend; ++i )
				{
					cLightSceneNode* n = (cLightSceneNode*)mLightArray[i];

					if( mBoundSphere.IntersectSphere( n->GetBoundSphere() ) == false )
					{
						updateEffects = true;
						n->Detach( this );
					}
				}

				mLightArray.Clear();
				mLightArray.Reserve( 8 );

				if( SCENEMAN->PickLights( &mLightArray, mBoundSphere ) )
				{
					updateEffects = true;

					for( unsigned int i = 0, iend = mLightArray.GetSize(); i < iend; ++i )
					{
						cLightSceneNode* n = (cLightSceneNode*)mLightArray[i];
						n->Attach( this );
					}
				}

				///
				if( mUpdateSelectLIght )
				{
					NiNode* n = NiDynamicCast(NiNode, mNiObject);

					if( mAttachSelectLight )
						n->AttachEffect( SCENEMAN->GetAmbientLight() );
//						n->AttachEffect( LIGHTAGENT->GetSelectAmbientLight() );
					else
						n->DetachEffect( SCENEMAN->GetAmbientLight() );
//						n->DetachEffect( LIGHTAGENT->GetSelectAmbientLight() );

					updateEffects = true;

					mUpdateSelectLIght = false;
				}

				///
				if( updateEffects )
				{
					mNiObject->UpdateEffects();
				}
*/
				break;
			}
		}

		/// 장면 트리를 갱신
		if( mContainer )
			mContainer->Update( this );
	}
}

void cSceneNode::OnProcessAlpha( float deltaTime )
{
	if( mAlpha == mTargetAlpha )
		return;

	float dt = deltaTime;
	float da = dt * mAlphaSpeed;

	if( mAlpha < mTargetAlpha )
	{
		/// 페이드 인
		mAlpha += da;

		if( mAlpha > mTargetAlpha )
			mAlpha = mTargetAlpha;
	}
	else
	{
		/// 페이드 아웃
		mAlpha -= da;

		if( mAlpha < mTargetAlpha )
			mAlpha = mTargetAlpha;
	}

	if( mAlpha < 1.0f )
		mAlphaBlended = true;
	else
		mAlphaBlended = false;

	// 알파를 갱신
	UpdateAlpha();
}

bool cSceneNode::OnVisible( cSceneCuller& culler )
{
	if( mAlpha < 0.00001f )
		return false;

	if( mBillboardList.IsEmpty() == false )
	{
		cBillboardList::cConstIterator i = mBillboardList.Begin();
		cBillboardList::cConstIterator iend = mBillboardList.End();

		for( ; i != iend; ++i )
		{
			NiBillboardNode* node = (NiBillboardNode*)(*i);

			switch( node->GetMode() )
			{
			case NiBillboardNode::ALWAYS_FACE_CAMERA:
				{
					NiCamera* camera = (NiCamera*)*culler.mCamera;
					NiTransform worldTM = node->GetWorldTransform();
					NiMatrix3 faceMat = NiMatrix3::IDENTITY;
					NiPoint3 camD, camU, camR;

					{
						// billboard coordinates for world axes of camera
						camD = -camera->GetWorldDirection(); 
						camU = camera->GetWorldUpVector();
						camR = camera->GetWorldRightVector();
					}
					camD = camD * worldTM.m_Rotate;
					camU = camU * worldTM.m_Rotate;
					camR = camR * worldTM.m_Rotate;

					// Rotated model up vector is that vector in the plane
					// orthogonal to the camera direction which minimizes the angle
					// between it and the original model up (0,1,0).
					float root = NiSqrt( camR.y*camR.y + camU.y*camU.y );
					if( root > 1e-06f )
					{
						float invRoot = 1.0f / root;
						float cosi = camU.y * invRoot;
						float sine = -camR.y * invRoot;
						faceMat.SetCol( 0,  cosi*camR.x + sine*camU.x,  cosi*camR.y + sine*camU.y,  cosi*camR.z + sine*camU.z );
						faceMat.SetCol( 1, -sine*camR.x + cosi*camU.x, -sine*camR.y + cosi*camU.y, -sine*camR.z + cosi*camU.z );
						faceMat.SetCol( 2, camD);
					}
					else
					{
						faceMat.SetCol( 0, -camR );
						faceMat.SetCol( 1, -camU );
						faceMat.SetCol( 2,  camD );
					}
					worldTM.m_Rotate = worldTM.m_Rotate * faceMat;

					///
					for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
					{
						NiAVObject* child = node->GetAt( i );
						if( child )
						{
							NiTransform tm = worldTM * child->GetLocalTransform();

							child->SetWorldScale( tm.m_fScale );
							child->SetWorldRotate( tm.m_Rotate );
							child->SetWorldTranslate( tm.m_Translate );
						}
					}
					break;
				}
			case NiBillboardNode::ROTATE_ABOUT_UP:
				{
					NiTransform worldTM = node->GetWorldTransform();
					NiPoint3 dir = ((culler.mCamera->GetWorldTranslate() - worldTM.m_Translate) * worldTM.m_Rotate)/worldTM.m_fScale;
					dir.y = 0.0f;
					dir.Unitize();

					NiMatrix3 faceMat = NiMatrix3::IDENTITY;
					faceMat.SetCol( 0, dir.z, 0.0f, -dir.x );
					faceMat.SetCol( 1,  0.0f, 1.0f, 0.0f );
					faceMat.SetCol( 2, dir.x, 0.0f, dir.z );

					worldTM.m_Rotate = worldTM.m_Rotate * faceMat;

					for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
					{
						NiAVObject* child = node->GetAt( i );
						if( child )
						{
							NiTransform tm = worldTM * child->GetLocalTransform();

							child->SetWorldScale( tm.m_fScale );
							child->SetWorldRotate( tm.m_Rotate );
							child->SetWorldTranslate( tm.m_Translate );
						}
					}
					break;
				}
			case NiBillboardNode::RIGID_FACE_CAMERA:
				{
					NiCamera* camera = (NiCamera*)*culler.mCamera;
					NiTransform worldTM = node->GetWorldTransform();

					NiMatrix3 faceMat = NiMatrix3::IDENTITY;
					NiPoint3 camD, camU, camR;

					{
						// billboard coordinates for world axes of camera
						camD = -camera->GetWorldDirection(); 
						camU = camera->GetWorldUpVector();
						camR = camera->GetWorldRightVector();
					}
					camD = camD * worldTM.m_Rotate;
					camU = camU * worldTM.m_Rotate;
					camR = camR * worldTM.m_Rotate;

					faceMat.SetCol( 0, camR );
					faceMat.SetCol( 1, camU );
					faceMat.SetCol( 2, camD );

					worldTM.m_Rotate = worldTM.m_Rotate * faceMat;

					///
					for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
					{
						NiAVObject* child = node->GetAt( i );
						if( child )
						{
							NiTransform tm = worldTM * child->GetLocalTransform();

							child->SetWorldScale( tm.m_fScale );
							child->SetWorldRotate( tm.m_Rotate );
							child->SetWorldTranslate( tm.m_Translate );
						}
					}
				}
				break;
			default:
				assert( 0 && "invalid billboard mode" );
				break;
			}
		}
	}
	return true;
}

bool cSceneNode::Init( const cSceneNodeParam& param )
{
	/// 이름을 설정
	mNiObject->SetName( param.mPathName.Cstr() );

	/// 변환 행렬을 설정
	mNiObject->SetTranslate( param.mTranslate );
	mNiObject->SetRotate( param.mRotate );
	mNiObject->SetScale( param.mScale );

	NiShadeProperty* shadeProp = (NiShadeProperty*)mNiObject->GetProperty( NiProperty::SHADE );
	if( shadeProp )
	{
		shadeProp->SetSmooth( true );
	}
	else
	{
		shadeProp = NiNew NiShadeProperty;
		shadeProp->SetSmooth( true );
		mNiObject->AttachProperty( shadeProp );
	}

	NiDitherProperty* ditherProp = (NiDitherProperty*)mNiObject->GetProperty( NiProperty::DITHER );
	if( ditherProp )
	{
		ditherProp->SetDithering( true );
	}
	else
	{
		ditherProp = NiNew NiDitherProperty;
		ditherProp->SetDithering( true );
		mNiObject->AttachProperty( ditherProp );
	}

	/// 기하 오브젝트들을 수집
	CollectGeoms( mNiObject );

	/// 빌보드 노드들을 수집
	CollectBillboards( mNiObject );

	/// 픽킹 방식을 설정
	mPick.SetPickType( param.mPickType );
	mPick.SetSortType( param.mPickSort );
	mPick.SetIntersectType( param.mPickIntersect );
	mPick.SetCoordinateType( param.mPickCoordinate );
	mPick.SetFrontOnly( param.mPickFrontOnly );
	mPick.SetReturnTexture( param.mPickReturnTexture );
	mPick.SetReturnNormal( param.mPickReturnNormal );
	mPick.SetReturnSmoothNormal( false );
	mPick.SetReturnColor( param.mPickReturnColor );
	mPick.SetTarget( mNiObject );

	/// 경계 구를 설정
	mNiObject->Update( 0.0f );
	mBoundSphere.Set( GetCenter(), GetRadius() );

	/// 갱신 여부를 설정
	mNeedUpdate = true;
	return true;
}

void cSceneNode::UpdateAlpha()
{
	{
		cMatPropList::cIterator i = mMatPropList.Begin();
		cMatPropList::cIterator iend = mMatPropList.End();

		for( ; i != iend; ++i )
		{
			((NiMaterialProperty*)(*i))->SetAlpha( mAlpha );
		}
	}
	{
		cAlphaDataList::cIterator i = mAlphaDataList.Begin();
		cAlphaDataList::cIterator iend = mAlphaDataList.End();

		for( ; i != iend; ++i )
		{
			cAlphaData* alphaData = (cAlphaData*)(*i);
			NiAlphaProperty* alphaProp = alphaData->mProp;
			assert( alphaProp );

			if( mAlpha < 1.0f )
			{
				alphaProp->SetAlphaBlending( true );

				if( alphaData->mTestEnabled )
				{
					unsigned char ref = (unsigned char)(alphaData->mTestRef * mAlpha);
					if( ref > alphaData->mTestRef ) 
						ref = alphaData->mTestRef;

					alphaProp->SetTestMode( NiAlphaProperty::TEST_GREATER );
					alphaProp->SetTestRef( ref );
				}
			}
			else
			{
				alphaProp->SetAlphaBlending( alphaData->mBlendEnabled );

				if( alphaData->mTestEnabled )
				{
					alphaProp->SetTestMode( alphaData->mTestFunc );
					alphaProp->SetTestRef( alphaData->mTestRef );
				}
			}
		}
	}
}

bool cSceneNode::Pick( const NiPoint3& origin, const NiPoint3& dir )
{
	if( mPick.PickObjects( origin, dir ) )
	{
		NiPick::Record* record = mPick.GetResults().GetAt(0);
		if( record )
		{
			mPickPos = record->GetIntersection();
			mPickDistance = record->GetDistance();
		}
		return true;
	}
	return false;
}

void cSceneNode::Translate( const NiPoint3& move )
{
	mNiObject->SetTranslate( mNiObject->GetTranslate() + move );
	mNeedUpdate = true;
}

void cSceneNode::SetTranslate( const NiPoint3& trans )
{
	mNiObject->SetTranslate( trans );
	mNeedUpdate = true;
}

void cSceneNode::SetWorldTranslate( const NiPoint3& trans )
{
	mNiObject->SetWorldTranslate( trans );
	mNeedUpdate;
}

void cSceneNode::SetRotate( const NiPoint3& axis, float angle )
{
	mNiObject->SetRotate( angle, axis.x, axis.y, axis.z );
	mNeedUpdate = true;
}

void cSceneNode::SetRotate( float xangle, float yangle, float zangle )
{
	NiMatrix3 r;
	r.FromEulerAnglesXYZ( xangle, yangle, zangle );
	mNiObject->SetRotate( r );
	mNeedUpdate = true;
}

void cSceneNode::SetRotate( const NiMatrix3& rot )
{
	mNiObject->SetRotate( rot );
	mNeedUpdate = true;
}

void cSceneNode::SetScale( float scale )
{
	mNiObject->SetScale( scale );
	mNeedUpdate = true;
}

const NiPoint3& cSceneNode::GetCenter() const
{
	return mNiObject->GetWorldBound().GetCenter();
}

float cSceneNode::GetRadius() const
{
	return mNiObject->GetWorldBound().GetRadius();
}

void cSceneNode::CollectProperties( NiAVObject* obj )
{
	NiMaterialProperty* matProp = 0;
	NiAlphaProperty* alphaProp = 0;
	//NiTexturingProperty* texProp = 0;

	//texProp = (NiTexturingProperty*)obj->GetProperty( NiProperty::TEXTURING );
	//if( texProp)
	//	texProp->SetBaseFilterMode( NiTexturingProperty::FILTER_BILERP_MIPNEAREST );

	if( NiIsKindOf( NiTriBasedGeom, obj ) )
	{
		matProp = (NiMaterialProperty*)obj->GetProperty( NiProperty::MATERIAL );
		alphaProp = (NiAlphaProperty*)obj->GetProperty( NiProperty::ALPHA );

		if( matProp )
		{
			mMatPropList.PushBack( matProp );
		}
		if( alphaProp )
		{
			bool blendEnabled = alphaProp->GetAlphaBlending();
			bool testEnabled = alphaProp->GetAlphaTesting();
			NiAlphaProperty::TestFunction testFunc = alphaProp->GetTestMode();
			unsigned char testRef = alphaProp->GetTestRef();

			mAlphaDataList.PushBack( new cAlphaData( blendEnabled, testEnabled, testFunc, testRef, alphaProp ) );
		}
	}
	else if( NiIsKindOf( NiNode, obj ) )
	{
		NiNode* node = (NiNode*)obj;

		for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
		{
			NiAVObject* child = node->GetAt( i );
			if( child )
				CollectProperties( child );
		}
	}
}

void cSceneNode::RecollectGeoms()
{
	mGeomList.Clear();
	CollectGeoms( mNiObject );
}

void cSceneNode::CollectGeoms( NiAVObject* obj )
{
	assert( obj );

	if( obj->GetName() == "EffectSceneNode" )
		return;
	if( obj->GetName() == "PickObj" )
		return;

	if( NiIsKindOf(NiTriBasedGeom, obj) || NiIsKindOf(NiParticleSystem, obj) )
	{
		NiGeometry* geom = (NiGeometry*)obj;

		if( geom->GetVertexCount() >= 3 )
		{
			NiAlphaProperty* alphaProp = (NiAlphaProperty*)obj->GetProperty( NiProperty::ALPHA );
			mGeomList.PushBack( cGeomPair((NiTriBasedGeom*)obj, alphaProp != 0) );
		}
	}
	else if( NiIsKindOf(NiNode, obj) )
	{
		NiNode* node = (NiNode*)obj;

		for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
		{
			NiAVObject* child = node->GetAt(i);
			if( child )
				CollectGeoms( child );
		}
	}
}

NiGeometry* cSceneNode::GetGeom( NiAVObject* obj )
{
	if( obj == 0 )
	{
		assert( 0 && "null node" );
		return 0;
	}

	if( NiIsKindOf(NiGeometry, obj) )
	{
		return (NiGeometry*)obj;
	}
	else if( NiIsKindOf(NiNode, obj) )
	{
		NiNode* node = (NiNode*)obj;
		NiAVObject* child = 0;
		NiGeometry* next = 0;

		for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
		{
			child = node->GetAt( i );
			if( child )
			{
				if( NiIsKindOf(NiGeometry, child) )
				{
					return (NiGeometry*)child;
				}
				else
				{
					next = GetGeom( child );
					if( next )
						return next;
				}
			}
		}
	}

	return 0;
}

void cSceneNode::SetAppCulled( NiAVObject* obj, bool cull )
{
	assert( obj );
	if( NiIsKindOf(NiTriBasedGeom, obj) || NiIsKindOf(NiParticleSystem, obj) )
	{
		NiGeometry* geom = (NiGeometry*)obj;
		geom->SetAppCulled( cull );
	}
	else if( NiIsKindOf(NiNode, obj) )
	{
		NiNode* node = (NiNode*)obj;

		for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
		{
			NiAVObject* child = node->GetAt(i);
			if( child )
				SetAppCulled( child, cull );
		}
	}
}


void cSceneNode::CollectBillboards( NiAVObject* obj )
{
	if( NiIsKindOf(NiBillboardNode, obj) )
	{
		mBillboardList.PushBack( (NiBillboardNode*)obj );
	}
	else if( NiIsKindOf(NiNode, obj) )
	{
		NiNode* node = (NiNode*)obj;

		for( unsigned int i = 0, iend = node->GetArrayCount(); i < iend; ++i )
		{
			NiAVObject* child = node->GetAt(i);
			if( child )
				CollectBillboards( child );
		}
	}
}

void cSceneNode::AddToVisibleArray( NiVisibleArray* solidArray, NiVisibleArray* skinedArray, NiVisibleArray* alphaArray )
{
	/// 기하 오브젝트들을 가시 배열에 추가
	cGeomList::cConstIterator i = mGeomList.Begin();
	cGeomList::cConstIterator end = mGeomList.End();

	if( IsAlphaBlended() )
	{
		for( ; i != end; ++i )
		{
			NiGeometry* geom = i->mFirst;
			if( geom->GetAppCulled() == true )
				continue;

			alphaArray->Add( *geom );
		}
	}
	else
	{
		for( ; i != end; ++i )
		{
			if( i->mSecond )
			{
				alphaArray->Add( *(i->mFirst) );
			}
			else
			{
				NiGeometry* geom = i->mFirst;
				if( geom->GetAppCulled() == true )
					continue;

				if( geom->GetSkinInstance() )
					skinedArray->Add( *geom );
				else
					solidArray->Add( *geom );
			}
		}
	}
}

void cSceneNode::SetViewNode( bool view )
{
	mViewNode = view;
}