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

#include "Ray.h"
#include "Camera.h"
#include "SceneManager.h"
#include "LightSceneNode.h"

cSceneNode::cSceneNode( eSceneNodeType type )
: mType( type )
, mUserData0( 0 )
, mUserData1( 0 )
, mUserData2( 0 )
, mUserData3( 0 )
, mPickBound( false )
, mNeedUpdateProperties( false )
, mNeedUpdateTransform( false )
, mNeedUpdateEffects( false )
, mNeedUpdateAlpha( false )
, mAlphaForced( false )
, mAlphaBlended( false )
, mTargetAlpha( 1.0f )
, mAlpha( 1.0f )
, mIndexByManager( 0xFFFFFFFF )
, mContainer( 0 )
, mIteratorByContainer( 0 )
, mCheckFrustum(true)
#ifdef REGEN_TOOL
, mCulled( false )
#endif
{
	mPickPos = NiPoint3::ZERO;
	mPickDistance = 0.0f; 
}

cSceneNode::~cSceneNode()
{
	mPick.RemoveTarget();

	/// 컨테이너에서 제거
	if( mContainer )
	{
		mContainer->Remove( this );
	}
}

void cSceneNode::Process( float /*deltaTime*/, float accumTime )
{
	if( mNeedUpdateProperties )
	{
		mNiObject->UpdateProperties();
	}

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

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

		/// 조명을 처리
		if( mType != SCENENODE_LIGHT && mType != SCENENODE_SOUND && mType != SCENENODE_AREA )
		{
			for( unsigned int i = 0, iend = mLightArray.GetSize(); i < iend; ++i )
			{
				cLightSceneNode* n = (cLightSceneNode*)mLightArray[i];

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

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

			if( SCENEMAN->Pick( &mLightArray, mBoundSphere, SCENENODE_LIGHT ) )
			{
				mNeedUpdateEffects = true;

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

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

	if( mNeedUpdateEffects )
	{
		mNeedUpdateEffects = false;
		mNiObject->UpdateEffects();
	}
}

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

	mNeedUpdateAlpha = true;
	float da = deltaTime * 0.8f;

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

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

		if( mAlpha < mTargetAlpha )
			mAlpha = mTargetAlpha;
	}

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

bool cSceneNode::OnVisible( cSceneCuller& culler )
{
#ifdef REGEN_TOOL
	if( mCulled )
		return false;
#endif

//	if( mAlpha < 0.000001f )
//		return false;

	if( mAlpha < 0.0000001f )
	{
		mCheckFrustum = false;
		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::Save( cFileSaver& saver )
{
	/// 타입을 저장
	saver.WriteUnsignedInt( mType );

	/// 경로를 저장
	char cpath[64];
	::memset( cpath, 0, 64 );
	::strncpy( cpath, mFilePath.Cstr(), 63 );
	saver.Write( cpath, 64 );

	/// 이름을 저장
	char cname[64];
	::memset( cname, 0, 64 );
	::strncpy( cname, mFileName.Cstr(), 63 );
	saver.Write( cname, 64 );

	/// 위치를 저장
	saver.Write( &(GetWorldTranslate()), sizeof(NiPoint3) );

	/// 회전을 저장
	saver.Write( &(GetWorldRotate()), sizeof(NiMatrix3) );

	/// 배율을 저장
	saver.WriteFloat( GetWorldScale() );
	return true;
}

bool cSceneNode::Init( const cSceneNodeParam& param )
{
	mNiObject = param.mNiObject;

	if( mNiObject == 0 )
	{
		assert( 0 );
		return false;
	}

	/// 이름을 설정
	::GetFilePath( &mFilePath, param.mPathName );
	::GetFileName( &mFileName, param.mPathName );
	mNiObject->SetName( mFileName.Cstr() );

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

	/// 픽킹 방식을 설정
	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 );

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

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

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

	/// 갱신 여부를 설정
	mNeedUpdateTransform = true;
	mNeedUpdateAlpha = false;
	return true;
}

void cSceneNode::Update()
{
	Process( 0.0f, 0.0f );
}

void cSceneNode::UpdateAlpha()
{
	if( mNeedUpdateAlpha == false )
		return;
	else
	{
		if( mTargetAlpha == mAlpha )
			mNeedUpdateAlpha = false;
	}

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

	{
		cMaterialDataList::cIterator i = mMatDataList.Begin();
		cMaterialDataList::cIterator iend = mMatDataList.End();

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

		for( ; i != iend; ++i )
		{
			cAlphaData& alphaData = *i;
			NiAlphaProperty* alphaProp = alphaData.mProp;

			if( mAlpha < 0.999999f )
			{
				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 );
				}
			}
		}
	}
}

void cSceneNode::UpdateTransform()
{
	mNeedUpdateTransform = true;

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

bool cSceneNode::Pick( const cRay& ray )
{
	mPickDistance = NI_INFINITY;

	if( mBoundSphere.IntersectRay( &mPickPos, &mPickDistance, ray ) )
	{
		if( mPickBound )
			return true;

		if( mPick.PickObjects( ray.GetOrigin(), ray.GetDirection() ) )
		{
			NiPick::Record* record = mPick.GetResults().GetAt(0);
			if( record )
			{
				mPickPos = record->GetIntersection();
				mPickDistance = record->GetDistance();
			}
			return true;
		}
	}
	return false;
}

void cSceneNode::RemoveLight( cLightSceneNode* node )
{
	for( unsigned int i = 0, iend = mLightArray.GetSize(); i < iend; ++i )
	{
		if( node == mLightArray[i] )
		{
			mLightArray.PopAt( i );
			break;
		}
	}
}

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

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

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

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

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

void cSceneNode::SetScale( float scale )
{
	mNiObject->SetScale( scale );
	mNeedUpdateTransform = 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;

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

		if( matProp )
		{
			const NiColor& ambient = matProp->GetAmbientColor();
			const NiColor& diffuse = matProp->GetDiffuseColor();
			const NiColor& specular = matProp->GetSpecularColor();
			const NiColor& emissive = matProp->GetEmittance();
			float shininess = matProp->GetShineness();
			float alpha = matProp->GetAlpha();

			mMatDataList.PushBack( cMaterialData( ambient, diffuse, specular, emissive, shininess, alpha, matProp) );
		}
		if( alphaProp )
		{
			bool blendEnabled = alphaProp->GetAlphaBlending();
			bool testEnabled = alphaProp->GetAlphaTesting();
			NiAlphaProperty::TestFunction testFunc = alphaProp->GetTestMode();
			unsigned char testRef = alphaProp->GetTestRef();

			mAlphaDataList.PushBack( 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::CollectGeoms( NiAVObject* obj )
{
	assert( obj );

	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) );
		}

		///
		if( NiIsKindOf(NiParticleSystem, obj) )
		{
			mPickBound = true;
		}
	}
	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( NiNode* node )
{
	if( node == 0 )
	{
		assert( 0 );
		return 0;
	}

	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( NiDynamicCast(NiNode, child) );
				if( next )
					return next;
			}
		}
	}
	return 0;
}

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 ) const
{
	/// 기하 오브젝트들을 가시 배열에 추가
	cGeomList::cConstIterator i = mGeomList.Begin();
	cGeomList::cConstIterator iend = mGeomList.End();

	if( IsAlphaBlended() )
	{
		for( ; i != iend; ++i )
		{
			alphaArray->Add( *(i->mFirst) );
		}
	}
	else
	{
		for( ; i != iend; ++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 );
			}
		}
	}
}
