#include "stdafx.h"
#include "ShadowGeometry.h"

#include "RenderSystem.h"
#include "SceneManager.h"
#include "DynamicSceneNode.h"
#include "NaviMesh.h"
#include "Ray.h"

cShadowGeometry::cShadowGeometry()
: mMaxRadius( 0.0f )
, mRadius( 0.0f )
, mHeight( 0.0f )
, mLineCount( 0 )
, mMaxTris( 0 )
, mCenter( NiPoint3::ZERO )
, mOOWidth( 0.0f )
, mAlpha( 0.0f )
{
}

cShadowGeometry::~cShadowGeometry()
{
	Clear();
}

void cShadowGeometry::Clear()
{
	mMaxRadius = 0.0f;
	mRadius = 0.0f;
	mHeight = 0.0f;
	mLineCount = 0;
	mMaxTris = 0;
	mCenter = NiPoint3::ZERO;
	mOOWidth = 0.0f;
	mAlpha = 0.0f;
	if( mTris )
	{
		assert(mTris->GetRefCount()==1);
		mTris = 0;
	}
	if( mTexProp )
	{
		assert(mTexProp->GetRefCount()==1);
		mTexProp = 0;
	}
	mTex = 0;
}

bool cShadowGeometry::Init( NiTexture* tex, float maxRadius, float height )
{
	assert( tex );
	assert( maxRadius > 0.0f );

	Clear();

	///
	mRadius = mMaxRadius = maxRadius;
	mHeight = height;

	int i = int((mMaxRadius * 2.0f + 200.0f) / 100.0f);
	mMaxTris = i * i * 2;
	assert( mMaxTris );

	/// 엔진 기하를 생성
	unsigned int maxVerts = 3 * mMaxTris;

	NiPoint3* vert = NiNew NiPoint3[maxVerts];
	assert(vert);

	NiPoint3* normal = NiNew NiPoint3[maxVerts];
	assert(normal);
	for( unsigned int i = 0; i < maxVerts; ++i )
		normal[i] = NiPoint3::ZERO;

	unsigned short* triIndices = NiAlloc(unsigned short, mMaxTris * 3);
	assert(triIndices);

	NiTriShapeDynamicData* triData = NiNew NiTriShapeDynamicData(
		unsigned short(maxVerts), vert, normal, 0, 0, 1, 
		NiGeometryData::NBT_METHOD_NONE, unsigned short(mMaxTris), triIndices);
	assert(triData);

	mTris = NiNew NiTriShape(triData);
	assert(mTris);

	/// 셰이더를 설정
	mTris->SetShader( RENDERSYS->GetRenderer()->GetShadowShader() );

	/// 렌더링 속성을 적용
	mTex = tex;

	NiTexturingProperty::ShaderMap* shaderMap = NiNew NiTexturingProperty::ShaderMap( tex, 0, NiTexturingProperty::WRAP_S_WRAP_T, NiTexturingProperty::FILTER_BILERP );
	mTexProp = NiNew NiTexturingProperty;
	mTexProp->SetApplyMode( NiTexturingProperty::APPLY_REPLACE );
	mTexProp->SetShaderMap( 0, shaderMap );

	mTris->AttachProperty( mTexProp );
	mTris->SetActiveVertexCount(0);
	mTris->SetActiveTriangleCount(0);

	///
	mTris->UpdateProperties();
	mTris->Update( 0.0f );
	return true;
}

void cShadowGeometry::Process( bool needUpdate, const NiPoint3& center, float alpha )
{
	if( mTris == 0 )
		return;

	/// 그림자 삼각형을 갱신
	unsigned short flags = 0;

	if( needUpdate )
	{
		flags = NiGeometryData::VERTEX_MASK | NiGeometryData::NORMAL_MASK | NiTriBasedGeomData::TRIANGLE_INDEX_MASK | NiTriBasedGeomData::TRIANGLE_COUNT_MASK;

		mTris->SetActiveVertexCount(0);
		mTris->SetActiveTriangleCount(0);

		const float* heights = NAVIMESH->GetHeights();
		assert( heights );
		mLineCount = (int)NAVIMESH->GetCellCount() + 1;

		float r = mRadius;
		float x = mCenter.x = center.x;
		float y = mCenter.y = center.y;

		/// 그림자가 드리워지는 지형 범위를 계산
		unsigned int xbegin, ybegin, xend, yend;
		CalcRange( &xbegin, &ybegin, &xend, &yend, x, y, r );

		// Calculate an approximate width for the shadow geometry -- used
		// in texture coordinate generation
		mOOWidth = 1.0f / (r * 2.0f);

		///
		NiPoint3 v[3];
		float x0, y0;

		for( unsigned int yi = ybegin; yi < yend; ++yi )
		{
			for( unsigned int xi = xbegin; xi < xend; ++xi )
			{
				x0 = xi * 100.0f;
				y0 = yi * 100.0f;

				/// 왼쪽 아래 삼각형
				v[0].x = x0;
				v[0].y = y0;
				v[0].z = heights[mLineCount * yi + xi];
				v[1].x = x0 + 100.0f;
				v[1].y = y0;
				v[1].z = heights[mLineCount * yi + xi+1];
				v[2].x = x0;
				v[2].y = y0 + 100.0f;
				v[2].z = heights[mLineCount * (yi+1) + xi];

				AddShadowTriangle( v );

				/// 오른쪽 위 삼각형
				v[0].x = x0 + 100.0f;
				v[0].y = y0 + 100.0f;
				v[0].z = heights[mLineCount * (yi+1) + xi+1];
				v[1].x = x0;
				v[1].y = y0 + 100.0f;
				v[1].z = heights[mLineCount * (yi+1) + xi];
				v[2].x = x0 + 100.0f;
				v[2].y = y0;
				v[2].z = heights[mLineCount * yi + xi+1];

				AddShadowTriangle( v );
			}
		}
	}

	/// 알파값을 갱신
	if( needUpdate || mAlpha != alpha )
	{
		mAlpha = alpha;
		flags |= NiGeometryData::NORMAL_MASK;

		unsigned short numVerts = mTris->GetActiveVertexCount();
		NiPoint3* normal = mTris->GetNormals();

		for( unsigned int i = 0; i < numVerts; ++i )
		{
			normal[i].z = mAlpha;
		}
	}

	if( flags )
	{
		mTris->GetModelData()->MarkAsChanged( flags );
	}

	/// 장면 관리자의 그림자 가시 배열에 추가
	SCENEMAN->AddVisibleShadowGeom( this );
}

void cShadowGeometry::AddShadowTriangle( NiPoint3* v )
{
	unsigned short numTris = mTris->GetActiveTriangleCount();
	unsigned short numVerts = mTris->GetActiveVertexCount();

	if( numTris >= mMaxTris )
	{
		assert( 0 );
		return;
	}

	/// 텍스처 좌표를 갱신
	// For texture coordinates, generate s and t based on point Q's
	// distance along the RIGHT(R) and UP(U) relative to the 
	// projection of the camera's location(P) (assuming non-skewed
	// frustum) onto the ground geometry.
	//
	// s = (R * (Q - P)) / width - 0.5
	// t = (U * (Q - P)) / height - 0.5
	NiPoint3* normals = mTris->GetNormals(); 
	assert( normals );

	NiPoint3 diff;
	unsigned int i = numVerts;

	diff = v[0] - mCenter;
	normals[i  ].x = (NiPoint3::UNIT_X * diff) * mOOWidth + 0.5f;
	normals[i  ].y = (NiPoint3::UNIT_Y * diff) * mOOWidth + 0.5f;

	diff = v[1] - mCenter;
	normals[i+1].x = (NiPoint3::UNIT_X * diff) * mOOWidth + 0.5f;
	normals[i+1].y = (NiPoint3::UNIT_Y * diff) * mOOWidth + 0.5f;

	diff = v[2] - mCenter;
	normals[i+2].x = (NiPoint3::UNIT_X * diff) * mOOWidth + 0.5f;
	normals[i+2].y = (NiPoint3::UNIT_Y * diff) * mOOWidth + 0.5f;

	/// 정점 좌표를 갱신
	v[0].z += mHeight;
	v[1].z += mHeight;
	v[2].z += mHeight;

	NiPoint3* verts = mTris->GetVertices();
	assert( verts );

	verts[numVerts  ] = v[0];
	verts[numVerts+1] = v[1];
	verts[numVerts+2] = v[2];

	/// 인덱스를 갱신
	i = numTris * 3;
	unsigned short* triIndices = mTris->GetTriList();
	assert( triIndices );

	triIndices[i  ] = numVerts;
	triIndices[i+1] = numVerts + 1;
	triIndices[i+2] = numVerts + 2;

	mTris->SetActiveTriangleCount( numTris + 1 );
	mTris->SetActiveVertexCount( numVerts + 3 );
}

void cShadowGeometry::CalcRange( unsigned int* xbegin, unsigned int* ybegin, unsigned int* xend, unsigned int* yend, float x, float y, float radius )
{
	int xb = (int)((x - radius) / 100.0f);
	int yb = (int)((y - radius) / 100.0f);
	int xe = (int)((x + radius) / 100.0f) + 1;
	int ye = (int)((y + radius) / 100.0f) + 1;

	if( xb < 0 )
		xb = 0;
	if( yb < 0 )
		yb = 0;
	if( xe >= mLineCount )
		xe = mLineCount - 1;
	if( ye >= mLineCount )
		ye = mLineCount - 1;

	*xbegin = xb;
	*ybegin = yb;
	*xend = xe;
	*yend = ye;
}

void cShadowGeometry::SetTexture( NiTexture* tex )
{
	assert( tex );

	mTex = tex;

	NiTexturingProperty::ShaderMap* shaderMap = NiNew NiTexturingProperty::ShaderMap( tex, 0, NiTexturingProperty::CLAMP_S_CLAMP_T, NiTexturingProperty::FILTER_NEAREST );
	mTexProp->SetShaderMap( 0, shaderMap );
}
