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

#include "Ray.h"
#include "Sphere.h"
#include "MaterialData.h"

class cSceneCuller;
class cLightSceneNode;

/// 장면 노드 타입
enum eSceneNodeType
{
	SCENENODE_NULL		= 0,
	SCENENODE_RENDERING	= (1 <<  0),

	SCENENODE_AREA		= (1 <<  1),
	SCENENODE_LIGHT		= (1 <<  2),
	SCENENODE_SOUND		= (1 <<  3),
	SCENENODE_STATIC	= (1 <<  4) | SCENENODE_RENDERING,
	SCENENODE_DYNAMIC	= (1 <<  5) | SCENENODE_RENDERING,
	SCENENODE_EFFECT	= (1 <<  6) | SCENENODE_RENDERING,

	SCENENODE_ITEM		= (1 <<  7) | SCENENODE_STATIC,
	SCENENODE_PORTAL	= (1 <<  8) | SCENENODE_STATIC,
	SCENENODE_GATHERING	= (1 <<  9) | SCENENODE_STATIC,

	SCENENODE_PLAYER	= (1 << 10) | SCENENODE_DYNAMIC,
	SCENENODE_NPC		= (1 << 11) | SCENENODE_DYNAMIC,
	SCENENODE_MONSTER	= (1 << 12) | SCENENODE_DYNAMIC,

	SCENENODE_HERO		= (1 << 13) | SCENENODE_PLAYER,
};

enum eSceneNodeTypeVer8
{
	SCENENODE_NULL_VER8	= 0,
	SCENENODE_AREA_VER8,
	SCENENODE_LIGHT_VER8,
	SCENENODE_SOUND_VER8,
	SCENENODE_STATIC_VER8,
	SCENENODE_DYNAMIC_VER8,
};

/// 알파 데이타
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;

	/// 위치
	NiPoint3 mTranslate;

	/// 회전
	NiMatrix3 mRotate;

	/// 배율
	float mScale;

	/// 엔진 노드
	mutable NiAVObject* mNiObject;

	/// 픽킹
	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 )
, mNiObject( 0 )
, mPickType( NiPick::FIND_ALL )
, mPickSort( NiPick::SORT )
, mPickIntersect( NiPick::TRIANGLE_INTERSECT )
, mPickCoordinate( NiPick::WORLD_COORDINATES )
, mPickFrontOnly( true )
, mPickReturnTexture( false )
, mPickReturnNormal( false )
, mPickReturnColor( false )
{
}

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

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

	/// 갱신
	void Update();
	void UpdateAlpha();
	void UpdateTransform();

	/// 픽킹
	virtual bool Pick( const cRay& ray );

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

	/// 조명
	void RemoveLight( cLightSceneNode* node );

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

	bool IsEnableFrustumFlag() { return mCheckFrustum; }

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

	/// 이동
	void Translate( const NiPoint3& move );
	virtual void SetTranslate( const NiPoint3& trans );
	const NiPoint3& GetTranslate() const;
	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;

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

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

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

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

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

	/// 파일 이름
	const cString& GetFileName() const;

	/// 사용자 데이타
	void SetUserData0( unsigned int data );
	void SetUserData1( unsigned int data );
	void SetUserData2( unsigned int data );
	void SetUserData3( unsigned int data );
	unsigned int GetUserData0() const;
	unsigned int GetUserData1() const;
	unsigned int GetUserData2() const;
	unsigned int GetUserData3() const;

	/// 알파 블렌딩
	bool IsAlphaBlended() const;

	/// 갱신 필요 여부
	void SetNeedUpdateTransform( bool update );
	void SetNeedUpdateEffects( bool update );

	/// 기하 오브젝트 수
	unsigned int GetNumGeoms() const;

#ifdef REGEN_TOOL
	/// 컬링
	void SetCulled( bool culled );
#endif

protected:
	cSceneNode( eSceneNodeType = SCENENODE_NULL );
	virtual ~cSceneNode();

	/// 저장
	virtual bool Save( cFileSaver& saver );

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

	/// 기하 오브젝트
	void CollectGeoms( NiAVObject* obj );
	void CollectBillboards( NiAVObject* obj );
	NiGeometry* GetGeom( NiNode* node );

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

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

protected:
	/// 타입
	eSceneNodeType mType;

	/// 이름
	cString mFilePath;
	cString mFileName;

	/// 사용자 데이타
	unsigned int mUserData0;
	unsigned int mUserData1;
	unsigned int mUserData2;
	unsigned int mUserData3;

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

	/// 재질 데이타 리스트
	typedef tList<cMaterialData> cMaterialDataList;
	cMaterialDataList mMatDataList;

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

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

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

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

	/// 갱신 필요 여부
	bool mNeedUpdateProperties;
	bool mNeedUpdateTransform;
	bool mNeedUpdateEffects;
	bool mNeedUpdateAlpha;

	/// 경계 구
	cSphere mBoundSphere;

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

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

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

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

	/// 컨테이너에 의해 설정된 내부 반복자
	tList<cSceneNode*>::cIterator mIteratorByContainer;

	bool mCheckFrustum;

#ifdef REGEN_TOOL
	bool mCulled;
#endif
};

inline
void cSceneNode::SetNeedUpdateTransform( bool update )
{
	mNeedUpdateTransform = update;
}

inline
void cSceneNode::SetNeedUpdateEffects( bool update )
{
	mNeedUpdateEffects = update;
}

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

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

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

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
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 cString& cSceneNode::GetFileName() const
{
	return mFileName;
}

inline
void cSceneNode::SetUserData0( unsigned int data )
{
	mUserData0 = data;
}

inline
void cSceneNode::SetUserData1( unsigned int data )
{
	mUserData1 = data;
}

inline
void cSceneNode::SetUserData2( unsigned int data )
{
	mUserData2 = data;
}

inline 
void cSceneNode::SetUserData3( unsigned int data )
{
	mUserData3 = data;
}

inline
unsigned int cSceneNode::GetUserData0() const
{
	return mUserData0;
}

inline
unsigned int cSceneNode::GetUserData1() const
{
	return mUserData1;
}

inline
unsigned int cSceneNode::GetUserData2() const
{
	return mUserData2;
}

inline
unsigned int cSceneNode::GetUserData3() const
{
	return mUserData3;
}


inline
unsigned int cSceneNode::GetNumGeoms() const
{
	return mGeomList.GetSize();
}

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

#ifdef REGEN_TOOL
inline
void cSceneNode::SetCulled( bool culled )
{
	mCulled = culled;
}
#endif

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