/* ==========================================================================
* 작 성 자 : 이순규
* 작 성 일 : 2006.12.08
* 내    용 : 장면 노드
* 주의사항 : 
*===========================================================================*/
#pragma once

#include "Sphere.h"

class cSceneCuller;
class cLightSceneNode;

/// 알파 데이타
class cAlphaData
{
public:
	cAlphaData( bool blendEnabled, bool testEnabled, NiAlphaProperty::TestFunction testFunc, unsigned char testRef, NiAlphaProperty* prop );

public:
	/// 알파 블렌딩
	bool mBlendEnabled;

	/// 알파 테스팅
	bool mTestEnabled;
	NiAlphaProperty::TestFunction mTestFunc;
	unsigned char mTestRef;

	/// 알파 속성
	NiAlphaProperty* mProp;
};

inline
cAlphaData::cAlphaData( bool blendEnabled, bool testEnabled, NiAlphaProperty::TestFunction testFunc, unsigned char testRef, NiAlphaProperty* prop )
: mBlendEnabled( blendEnabled )
, mTestEnabled( testEnabled )
, mTestFunc( testFunc )
, mTestRef( testRef )
, mProp( prop )
{
	assert( prop );
}

/// 장면 노드 인자
class cSceneNodeParam
{
public:
	cSceneNodeParam();

public:
	/// 경로 이름
	mutable cString mPathName;

	/// 위치
	mutable NiPoint3 mTranslate;

	/// 회전
	mutable NiMatrix3 mRotate;

	/// 배율
	mutable float mScale;

	/// 픽킹
	mutable NiPick::PickType mPickType;
	mutable NiPick::SortType mPickSort;
	mutable NiPick::IntersectType mPickIntersect;
	mutable NiPick::CoordinateType mPickCoordinate;
	mutable bool mPickFrontOnly;
	mutable bool mPickReturnTexture;
	mutable bool mPickReturnNormal;
	mutable bool mPickReturnColor;
};

inline
cSceneNodeParam::cSceneNodeParam()
: mTranslate( NiPoint3::ZERO )
, mRotate( NiMatrix3::IDENTITY )
, mScale( 1.0f )
, mPickType( NiPick::FIND_ALL )
, mPickSort( NiPick::SORT )
, mPickIntersect( NiPick::TRIANGLE_INTERSECT )
, mPickCoordinate( NiPick::WORLD_COORDINATES )
, mPickFrontOnly( false )
, mPickReturnTexture( false )
, mPickReturnNormal( false )
, mPickReturnColor( false )
{
}

/// 장면 노드
class cSceneNode
{
	friend class cSceneManager;
	friend class cSceneTree;
	friend class cSceneTreeNode;

public:
	/// 장면 노드 타입
	enum eType
	{
		eNULL		= 0,
		eRENDERING	= (1 <<  0),

		eAREA		= (1 <<  1),
		eLIGHT		= (1 <<  2),
		eSOUND		= (1 <<  3),
		eSTATIC		= (1 <<  4) | eRENDERING,
		eDYNAMIC	= (1 <<  5) | eRENDERING,
		eEFFECT		= (1 <<  6) | eRENDERING,

		eDROPITEM	= (1 <<  7) | eSTATIC,
		eMAPPORTAL	= (1 <<  8) | eSTATIC,

		ePLAYER		= (1 <<  9) | eDYNAMIC,
		eNPC		= (1 << 10) | eDYNAMIC,
		eMONSTER	= (1 << 11) | eDYNAMIC,

		eHERO		= (1 << 12) | ePLAYER,
	};

	enum eTypeVer8
	{
		eNULL_VER8	= 0,
		eAREA_VER8,
		eLIGHT_VER8,
		eSOUND_VER8,
		eSTATIC_VER8,
		eDYNAMIC_VER8,
	};

public:
	/// 처리
	virtual void OnProcess( float deltaTime, float accumTime );
	virtual void OnProcessAlpha( float deltaTime );
	virtual bool OnVisible( cSceneCuller& culler );

	/// 갱신
	void Update();

	/// 픽킹
	virtual bool Pick( const NiPoint3& origin, const NiPoint3& dir );

	/// 픽킹 결과
	/// 주의: Pick()를 호출한 직후에만 사용해야 한다.
	const NiPick::Results& GetPickResults() const;
	const NiPoint3& GetPickPos() const;
	float GetPickDistance() const;

	/// 알파 블렌딩
	void SetAlphaBlended( bool blended, float targetAlpha );
	bool IsAlphaBlended() const;

	/// 타입
	eType GetType() const;
	bool IsKindof( eType type ) const;

	/// 엔진 오브젝트
	NiAVObject* GetNiObject() const;

	/// 이동
	void Translate( const NiPoint3& move );
	void SetTranslate( const NiPoint3& trans );
	const NiPoint3& GetTranslate() const;

	void SetWorldTranslate( const NiPoint3& trans );
	virtual const NiPoint3& GetWorldTranslate() const;

	/// 회전
	void SetRotate( const NiPoint3& axis, float angle );
	void SetRotate( float xangle, float yangle, float zangle );
	void SetRotate( const NiMatrix3& rot );
	const NiMatrix3& GetRotate() const;
	const NiMatrix3& GetWorldRotate() const;

	/// 크기
	virtual void SetScale( float scale );
	float GetScale() const;
	float GetWorldScale() const;

	/// 경계 구
	const cSphere& GetBoundSphere() const;

	/// 중심점
	virtual const NiPoint3& GetCenter() const;

	/// 반지름
	virtual float GetRadius() const;

	/// 평면의 어느쪽
	virtual unsigned int WhichSide( const NiPlane& plane ) const;

	/// 이름
	const char* GetName() const;

	static NiGeometry* GetGeom( NiAVObject* obj );
	static void SetAppCulled( NiAVObject* obj, bool cull );

	void SetSelectLightFlag( bool attach );

	virtual void Play( unsigned long ) {}
	virtual void Rew( unsigned long ) {}
	virtual void Stop() {}

	virtual void SetViewNode( bool view );
	inline bool IsViewNode() { return mViewNode; }

protected:
	cSceneNode( eType = eNULL );
	virtual ~cSceneNode();

	/// 초기화
	virtual bool Init( const cSceneNodeParam& param );

	/// 기하 오브젝트
	virtual void RecollectGeoms();
	virtual void CollectGeoms( NiAVObject* obj );
	void CollectBillboards( NiAVObject* obj );

	/// 기하 속성
	void CollectProperties( NiAVObject* obj );

	/// 기하 오브젝트들을 가시 배열에 추가
	virtual void AddToVisibleArray( NiVisibleArray* solidArray, NiVisibleArray* skinedArray, NiVisibleArray* alphaArray );

private:
	/// 알파를 갱신
	virtual void UpdateAlpha();

protected:
	/// 타입
	eType mType;

	/// 엔진 오브젝트
	NiAVObjectPtr mNiObject;

	/// 재질 속성 리스트
	typedef tList<void*> cMatPropList;
	cMatPropList mMatPropList;

	/// 알파 데이타 리스트
	typedef tList<void*> cAlphaDataList;
	cAlphaDataList mAlphaDataList;

	/// 기하 오브젝트 리스트
	typedef tPair<NiTriBasedGeom*, bool> cGeomPair;
	typedef tList<cGeomPair> cGeomList;
	cGeomList mGeomList;

	/// 빌보드 노드 리스트
	typedef tList<void*> cBillboardList;
	cBillboardList mBillboardList;

	/// 픽킹
	/// 장면 트리를 이용한 충돌 검사 과정에서 픽킹을 수행하고, 그 결과를 저장한다.
	NiPick mPick;
	NiPoint3 mPickPos;
	float mPickDistance;

	/// 
	bool mViewNode;

	/// 갱신 필요 여부
	bool mNeedUpdate;

	/// 누적 경과 시간
	float mAccumTime;

	/// 경계 구
	cSphere mBoundSphere;

	/// 알파
	bool mAlphaForced;
	bool mAlphaBlended;
	float mTargetAlpha;
	float mAlpha;
	float mAlphaSpeed;

	/// over & select node attach selectLight
	bool mAttachSelectLight;
	bool mUpdateSelectLIght;

	/// 조명 배열
	typedef tArray<void*> cLightArray;
	cLightArray mLightArray;

	/// 장면 관리자에 의해 설정된 내부 인덱스
	unsigned int mIndexByManager;

	/// 컨테이너
	/// 장면 노드를 포함하는 장면 트리 노드
	cSceneTreeNode* mContainer;

	/// 컨테이너에 의해 설정된 내부 반복자
	bool mIteratorValid;
	tPointerList<void*>::cIterator mIteratorByContainer;
};

inline
void cSceneNode::Update()
{
	mNiObject->Update(0.0f);
	mNeedUpdate = true;
}

inline
const NiPick::Results& cSceneNode::GetPickResults() const
{
	return mPick.GetResults();
}

inline
const NiPoint3& cSceneNode::GetPickPos() const
{
	return mPickPos;
}

inline
float cSceneNode::GetPickDistance() const
{
	return mPickDistance;
}

inline
void cSceneNode::SetAlphaBlended( bool blended, float targetAlpha )
{
	mAlphaBlended = blended;
	mTargetAlpha = targetAlpha;
}

inline
bool cSceneNode::IsAlphaBlended() const
{
	return mAlphaForced ? true : mAlphaBlended;
}

inline
cSceneNode::eType cSceneNode::GetType() const
{
	return mType;
}

inline
bool cSceneNode::IsKindof( eType type ) const
{
	return (mType & type) == type;
}

inline
NiAVObject* cSceneNode::GetNiObject() const
{
	return mNiObject;
}

inline
const NiPoint3& cSceneNode::GetTranslate() const
{
	return mNiObject->GetTranslate();
}

inline
const NiMatrix3& cSceneNode::GetRotate() const
{
	return mNiObject->GetRotate();
}

inline
float cSceneNode::GetScale() const
{
	return mNiObject->GetScale();
}

inline
const NiPoint3& cSceneNode::GetWorldTranslate() const
{
	return mNiObject->GetWorldTranslate();
}

inline
const NiMatrix3& cSceneNode::GetWorldRotate() const
{
	return mNiObject->GetWorldRotate();
}

inline
float cSceneNode::GetWorldScale() const
{
	return mNiObject->GetWorldScale();
}

inline
const cSphere& cSceneNode::GetBoundSphere() const
{
	return mBoundSphere;
}

inline
unsigned int cSceneNode::WhichSide( const NiPlane& plane ) const
{
	float dist = plane.Distance( GetCenter() );
	float radius = GetRadius();

	if( dist <= -radius )
		return NiPlane::NEGATIVE_SIDE;
	else if( dist >= radius )
		return NiPlane::POSITIVE_SIDE;
	else
		return NiPlane::NO_SIDE;
}

inline
const char* cSceneNode::GetName() const
{
	return (const char*)mNiObject->GetName();
}

inline
void cSceneNode::SetSelectLightFlag( bool attach )
{
	if( mAttachSelectLight != attach )
	{
		mAttachSelectLight = attach;
		mUpdateSelectLIght = true;

		mNeedUpdate = true;
	}
}

/// 장면 노드 비교 함수자
/// 픽킹 거리로 비교한다.
class cSceneNodeCompareByPickDistance
{
public:
	bool operator () ( const void* left, const void* right ) const
	{
		return ( ((cSceneNode*)left)->GetPickDistance() < ((cSceneNode*)right)->GetPickDistance() );
	};
};
