#include "stdafx.h"
#include "Terrain.h"

#include "RenderSystem.h"
#include "Ray.h"
#include "Box.h"
#include "TerrainNode.h"
#include "ResourceManager.h"
#include "CameraManager.h"
#include "LightAgent.h"
#include "FogAgent.h"

#ifdef _DEVSYS
#include "DevSystem.h"
#endif

cTerrainTexture::cTerrainTexture( unsigned int index, LPDIRECT3DTEXTURE9 tex )
: mIndexByTerrain( index )
, mRefCount( 0 )
, mTexture( tex )
{
	assert( tex );

	///
	if( TERRAIN )
		TERRAIN->AddTexture( index, this );
}

cTerrainTexture::~cTerrainTexture()
{
	if( mTexture )
		mTexture->Release();

	if( TERRAIN )
		TERRAIN->RemoveTexture( mIndexByTerrain );

	assert( mRefCount == 0 && "bad reference counting!" );
}

void cTerrainTexture::Grab()
{
	++mRefCount;
}

void cTerrainTexture::Drop()
{
	if( --mRefCount == 0 )
	{
		delete this;
	}
}

cTerrainBuffer::cTerrainBuffer()
: mXIndex( 0 )
, mYIndex( 0 )
, mPosCoordBuffer( 0 )
, mNormalBuffer( 0 )
, mAlphaBuffer( 0 )
, mColorBuffer( 0 )
{
}

cTerrainBuffer::~cTerrainBuffer()
{
	/// 버퍼를 해제
	if( mPosCoordBuffer )
	{
		mPosCoordBuffer->Release();
		mPosCoordBuffer = 0;
	}
	if( mNormalBuffer )
	{
		mNormalBuffer->Release();
		mNormalBuffer = 0;
	}
	if( mAlphaBuffer )
	{
		mAlphaBuffer->Release();
		mAlphaBuffer = 0;
	}
	if( mColorBuffer )
	{
		mColorBuffer->Release();
		mColorBuffer = 0;
	}
}

bool cTerrainBuffer::Init( cTerrain* terrain, unsigned int xi, unsigned int yi )
{
	mXIndex = xi;
	mYIndex = yi;

	/// 버퍼를 생성
	cRenderer* renderer = RENDERSYS->GetRenderer();

	mPosCoordBuffer = renderer->CreateVertexBuffer( TERRAIN_BUFF_VERT_COUNT, sizeof(NiPoint3), 0 );
	if( mPosCoordBuffer == 0 )
		return false;

	mNormalBuffer = renderer->CreateVertexBuffer( TERRAIN_BUFF_VERT_COUNT, sizeof(NiPoint3), 0 );
	if( mNormalBuffer == 0 )
		return false;

	mColorBuffer = renderer->CreateVertexBuffer( TERRAIN_BUFF_VERT_COUNT, sizeof(NiColor), 0 );
	if( mColorBuffer == 0 )
		return false;

	mAlphaBuffer = renderer->CreateVertexBuffer( TERRAIN_BUFF_VERT_COUNT, sizeof(NiPoint3), 0 );
	if( mAlphaBuffer == 0 )
		return false;

	/// 버퍼를 초기화
	if( terrain )
	{
		UpdatePosCoord( terrain, mXIndex, mYIndex, TERRAIN_BUFF_LINE_COUNT );
		UpdateColor( terrain, mXIndex, mYIndex, TERRAIN_BUFF_LINE_COUNT );
		UpdateAlpha( terrain, mXIndex, mYIndex, TERRAIN_BUFF_LINE_COUNT );
	}
	return true;
}

void cTerrainBuffer::UpdatePosCoord( cTerrain* terrain, unsigned int xstart, unsigned int ystart, unsigned int count )
{
	assert( terrain );
	assert( mPosCoordBuffer );
	assert( mNormalBuffer );

	float unitsPerVertex = terrain->GetUnitsPerVertex();
	unsigned int length = TERRAIN_BUFF_VERT_COUNT * sizeof(NiPoint3);
	void* p = 0;
	HRESULT ret = mPosCoordBuffer->Lock( 0, length, &p, 0 );

	if( SUCCEEDED(ret) )
	{
		NiPoint3* posCoords = (NiPoint3*)p;
		unsigned int i = 0;

		for( unsigned int yi = ystart, yend = ystart + count; yi < yend; ++yi )
		{
			for( unsigned int xi = xstart, xend = xstart + count; xi < xend; ++xi )
			{
				i = (yi - mYIndex) * TERRAIN_BUFF_LINE_COUNT + (xi - mXIndex);
				posCoords[i].x = xi * unitsPerVertex;
				posCoords[i].y = yi * unitsPerVertex;
				posCoords[i].z = terrain->GetHeightFast( xi, yi );
			}
		}
	}
	mPosCoordBuffer->Unlock();

	///
	length = TERRAIN_BUFF_VERT_COUNT * sizeof(NiPoint3);
	p = 0;
	ret = mNormalBuffer->Lock( 0, length, &p, 0 );

	if( SUCCEEDED(ret) )
	{
		NiPoint3* normals = (NiPoint3*)p;
		unsigned int i = 0;

		for( unsigned int yi = ystart, yend = ystart + count; yi < yend; ++yi )
		{
			for( unsigned int xi = xstart, xend = xstart + count; xi < xend; ++xi )
			{
				i = (yi - mYIndex) * TERRAIN_BUFF_LINE_COUNT + (xi - mXIndex);
				normals[i] = terrain->GetNormalFast( xi, yi );
			}
		}
	}
	mNormalBuffer->Unlock();
}

void cTerrainBuffer::UpdateColor( cTerrain* terrain, unsigned int xstart, unsigned int ystart, unsigned int count )
{
	assert( terrain );
	assert( mColorBuffer );

	unsigned int length = TERRAIN_BUFF_VERT_COUNT * sizeof(NiColor);
	void* p = 0;
	HRESULT ret = mColorBuffer->Lock( 0, length, &p, 0 );

	if( SUCCEEDED(ret) )
	{
		NiColor* colors = (NiColor*)p;
		unsigned int i = 0;

		for( unsigned int yi = ystart, yend = ystart + count; yi < yend; ++yi )
		{
			for( unsigned int xi = xstart, xend = xstart + count; xi < xend; ++xi )
			{
				i = (yi - mYIndex) * TERRAIN_BUFF_LINE_COUNT + (xi - mXIndex);
				colors[i] = terrain->GetColorFast( xi, yi );
			}
		}
	}
	mColorBuffer->Unlock();
}

void cTerrainBuffer::UpdateAlpha( cTerrain* terrain, unsigned int xstart, unsigned int ystart, unsigned int count )
{
	assert( terrain );
	assert( mAlphaBuffer );

	unsigned int length = TERRAIN_BUFF_VERT_COUNT * sizeof(NiPoint3);
	void* p = 0;
	HRESULT ret = mAlphaBuffer->Lock( 0, length, &p, 0 );

	if( SUCCEEDED(ret) )
	{
		NiPoint3* alphas = (NiPoint3*)p;
		unsigned int i = 0;

		for( unsigned int yi = ystart, yend = ystart + count; yi < yend; ++yi )
		{
			for( unsigned int xi = xstart, xend = xstart + count; xi < xend; ++xi )
			{
				i = (yi - mYIndex) * TERRAIN_BUFF_LINE_COUNT + (xi - mXIndex);
				alphas[i] = terrain->GetAlphaFast( xi, yi );
			}
		}
	}
	mAlphaBuffer->Unlock();
}

cTerrain* cTerrain::mSingleton = 0;

cTerrain::cTerrain()
: mCellCount( 0 )
, mLineCount( 0 )
, mMetersPerVertex( 0.0f )
, mUnitsPerMeter( 0 )
, mUnitsPerVertex( 0.0f )
, mUnitsPerLeafNode( 0.0f )
, mSegmentLength( 0.0f )
, mRootNode( 0 )
, mHeights( 0 )
, mNormals( 0 )
, mColors( 0 )
, mAlphas( 0 )
, mBuffer( 0 )
, mVertexDeclaration( 0 )
, mIndexBuffer( 0 )
, mLoadArray( 0 )
, mLoadCount( 0 )
, mLoadIndex( 0 )
{
	assert( mSingleton == 0 && "bad singleton!" );
	mSingleton = this;

	for( unsigned int i = 0; i < TERRAIN_TEXTURE_COUNT; ++i )
	{
		mTextures[i] = 0;
	}

	/// Lod 거리 설정
/*
	float dist = 5000.0f;

	for( unsigned int i = 0, iend = TERRAIN_LOD_COUNT - 1; i < iend; ++i )
	{
		mSquaredDistanceLod[i] = dist * dist;
		dist *= 2.0f;
	}
	mSquaredDistanceLod[TERRAIN_LOD_COUNT-1] = NI_INFINITY;
*/
}

cTerrain::~cTerrain()
{
	Clear();

	mSingleton = 0;
}

void cTerrain::Clear()
{
	delete mRootNode;
	mRootNode = 0;
	NiDelete [] mAlphas;
	mAlphas = 0;
	NiDelete [] mColors;
	mColors = 0;
	NiDelete [] mNormals;
	mNormals = 0;
	delete [] mHeights;
	mHeights = 0;
	
	mNodeArray.Clear();
	mVisibleArray.Clear();

	/// 지형 텍스처들을 모두 제거
	for( unsigned int i = 0; i < TERRAIN_TEXTURE_COUNT; ++i )
	{
		delete mTextures[i];
		mTextures[i] = 0;
	}

	/// 정점 선언 및 인덱스 버퍼를 제거
	if( mVertexDeclaration )
	{
		mVertexDeclaration->Release();
		mVertexDeclaration = 0;
	}
	if( mIndexBuffer )
	{
		mIndexBuffer->Release();
		mIndexBuffer = 0;
	}

	/// 정점 버퍼를 제거
	if( mBuffer )
	{
		unsigned int buffCount = mCellCount / TERRAIN_BUFF_CELL_COUNT;

		for( unsigned int i = 0; i < buffCount; ++i )
		{
			delete [] mBuffer[i];
		}
		delete [] mBuffer;
		mBuffer = 0;
	}

	/// 로딩 배열을 제거
	delete [] mLoadArray;
	mLoadArray = 0;
	mLoadCount = 0;
	mLoadIndex = 0;

	mCellCount = 0;
	mLineCount = 0;
	mMetersPerVertex = 0.0f;
	mUnitsPerMeter = 0;
	mUnitsPerVertex = 0.0f;
	mUnitsPerLeafNode = 0.0f;
	mSegmentLength = 0.0f;
}

void cTerrain::Process()
{
	if( mRootNode == 0 )
		return;

	/// 컬링
	const cCamera* cam = CAMERAMAN->GetCurrent();
	if( cam == 0 )
	{
		assert( 0 );
		return;
	}

	NiFrustumPlanes frustum( *((NiCamera*)(*cam)) );
	mVisibleArray.Clear();
	frustum.EnableAllPlanes();
	cTerrainNode::mFrustum = &frustum;
	cTerrainNode::mVisibleArray = &mVisibleArray;
	mRootNode->Cull();

	/// Lod값 설정
/*
	NiPoint3 heroPos = cam->GetLookAt();

	for( unsigned int i = 0, iend = mVisibleArray.GetSize(); i < iend; ++i )
	{
		cTerrainLeafNode* n = (cTerrainLeafNode*)mVisibleArray[i];
		float sqrDist = (heroPos - n->GetCenter()).SqrLength();

		for( unsigned int j = 0; j < TERRAIN_LOD_COUNT; ++j )
		{
			if( sqrDist < mSquaredDistanceLod[j] )
			{
				n->mLod = j;
				break;
			}
		}
	}
*/
	/// 정렬
	if( mVisibleArray.GetSize() > 2 )
		::Sort( mVisibleArray.Begin(), mVisibleArray.End(), cTerrainNodeCompareForRendering() );

#ifdef _DEVSYS
	if( DEVSYSTEM )
		DEVSYSTEM->mTerrainRenderCount = mVisibleArray.GetSize();
#endif
}

void cTerrain::Render()
{
	if( mRootNode == 0 )
		return;

	///
	unsigned int numGeoms = mVisibleArray.GetSize();
	if( numGeoms == 0 )
		return;

	///
	cRenderer* renderer = RENDERSYS->GetRenderer();
	if( renderer == 0 )
	{
		assert( 0 );
		return;
	}

	/// 장치 설정을 보관
	LPDIRECT3DDEVICE9 device = renderer->GetD3DDevice();
	IDirect3DVertexDeclaration9* oldVertDecl = 0;
	device->GetVertexDeclaration( &oldVertDecl );

	/// 장치를 설정
	device->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
	device->SetVertexDeclaration( mVertexDeclaration );
	device->SetIndices( mIndexBuffer );

	/// 셰이더를 시작
	if( mShader.Begin( CAMERAMAN->GetCurrent(), renderer->GetD3DViewProj() ) == true )
	{
		mShader.SetAmbientLight( LIGHTAGENT->GetTerrainAmbientLight() );
		mShader.SetFog( FOGAGENT->GetFogProperty() );
		mShader.CommitChanges();

		///
		cTerrainLeafNode* node = 0;
		cTerrainBuffer* buff = 0;
		unsigned int tval = 0xFFFFFFFF;
		unsigned int baseVertIndex = 0;

		for( unsigned int i = 0, iend = mVisibleArray.GetSize(); i < iend; ++i )
		{
			node = (cTerrainLeafNode*)mVisibleArray[i];
			mShader.SetOrigin( node->mXIndex * mUnitsPerVertex, node->mYIndex * mUnitsPerVertex );
			mShader.SetPointLight( node->mLight );
//			mShader.CommitChanges();

			if( buff != node->mBuffer )
			{
				buff = node->mBuffer;

				device->SetStreamSource( 0, node->mBuffer->mPosCoordBuffer, 0, sizeof(NiPoint3) );
				device->SetStreamSource( 1, node->mBuffer->mNormalBuffer, 0, sizeof(NiPoint3) );
				device->SetStreamSource( 2, node->mBuffer->mColorBuffer, 0, sizeof(NiColor) );
				device->SetStreamSource( 3, node->mBuffer->mAlphaBuffer, 0, sizeof(NiPoint3) );
			}
			if( tval != node->mTextureValue1 )
			{
				tval = node->mTextureValue1;

				device->SetTexture( 0, node->mTexture0->mTexture );
				device->SetTexture( 1, node->mTexture1->mTexture );
				device->SetTexture( 2, node->mTexture2->mTexture );
			}

			baseVertIndex = (node->mYIndex - buff->mYIndex) * TERRAIN_BUFF_LINE_COUNT + (node->mXIndex - buff->mXIndex);

			device->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, baseVertIndex, 0, TERRAIN_BUFF_VERT_COUNT, 0, TERRAIN_RENDER_PRIME_COUNT );

/*
			for( unsigned int j = 0, jend = TERRAIN_LEAF_LINE_COUNT-1; j < jend; ++j )
			{
				device->DrawIndexedPrimitive( D3DPT_TRIANGLESTRIP, baseVertIndex, 0, TERRAIN_BUFF_VERT_COUNT, 0, TERRAIN_LEAF_LINE_COUNT_X2-2 );
				baseVertIndex += TERRAIN_BUFF_LINE_COUNT;
			}
*/
		}

		mShader.End();
	}

	/// 장치 설정을 복원
	device->SetVertexDeclaration( oldVertDecl );
}

void cTerrain::AddTexture( unsigned int i, cTerrainTexture* tex )
{
	assert( tex );

	/// 텍스처 배열에 추가
	mTextures[i] = tex;
}

void cTerrain::RemoveTexture( unsigned int i )
{
	assert( i < TERRAIN_TEXTURE_COUNT );

	/// 텍스처 배열에서 제거
	cTerrainTexture* tex = mTextures[i];

	if( tex )
	{
		mTextures[i] = 0;
	}
}

bool cTerrain::CollideSphere( tArray<void*>* pickedArray, const cSphere& sphere )
{
	assert( pickedArray );
	assert( mRootNode );

	cTerrainNode::mSphere = &sphere;
	cTerrainNode::mPickedArray = pickedArray;
	return mRootNode->CollideSphere();
}

bool cTerrain::CheckCellCount( unsigned int cellCount )
{
	if( cellCount % TERRAIN_DEFAULT_RESOLUTION != 0 )
	{
		assert( 0 );
		return false;
	}
/*
	cTerrainLeafNode::mCellCount[0] = TERRAIN_LEAF_CELL_COUNT;
	cTerrainLeafNode::mLineCount[0] = TERRAIN_LEAF_LINE_COUNT;

	for( unsigned int i = 1; i < TERRAIN_LOD_COUNT; ++i )
	{
		cTerrainLeafNode::mCellCount[i] = cTerrainLeafNode::mCellCount[i-1] / 2;
		cTerrainLeafNode::mLineCount[i] = cTerrainLeafNode::mCellCount[i] + 1;
	}
*/
	return true;
}

void cTerrain::SetLeafNode( unsigned int xi, unsigned int yi, cTerrainLeafNode* node )
{
	assert( node );

	unsigned int nodeCount = mCellCount / TERRAIN_LEAF_CELL_COUNT;
	assert( xi >= 0 && xi < nodeCount );
	assert( yi >= 0 && yi < nodeCount );

	mNodeArray[yi * nodeCount + xi] = node;
}

cTerrainLeafNode* cTerrain::GetLeafNode( float x, float y ) const
{
	assert( mRootNode );

	/// 해당 위치가 지형 안에 들어오는지 검사
	const cBox& box = mRootNode->GetBoundBox();

	if( x < box.GetMin().x || y < box.GetMin().y ||
		x > box.GetMax().x || y > box.GetMax().y )
	{
		return 0;
	}

	/// 범위 계산
	int xi = (int)(x / mUnitsPerLeafNode);
	int yi = (int)(y / mUnitsPerLeafNode);
	int nodeCount = (int)(mCellCount / TERRAIN_LEAF_CELL_COUNT);

	if( xi < 0 )
		xi = 0;
	if( yi < 0 )
		yi = 0;
	if( xi > nodeCount - 1 )
		xi = nodeCount - 1;
	if( yi > nodeCount - 1 )
		yi = nodeCount - 1;

	return (cTerrainLeafNode*)mNodeArray[yi * nodeCount + xi];
}

const cBox& cTerrain::GetBoundBox() const
{
	assert( mRootNode );

	return mRootNode->GetBoundBox();
}

bool cTerrain::GetHeight( float* height, unsigned int xi, unsigned int yi )
{
	if( xi < 0 || xi > mCellCount )
		return false;
	if( yi < 0 || yi > mCellCount )
		return false;

	*height = mHeights[yi * mLineCount + xi];
	return true;
}

void cTerrain::ComputeNormals( unsigned int xbegin, unsigned int ybegin, unsigned int xend, unsigned int yend )
{
	assert( mHeights );
	assert( mNormals );

	NiPoint3 sum, v0, v1, v2;
	unsigned int count;

	for( unsigned int yi = ybegin; yi < yend; ++yi )
	{
		for( unsigned int xi = xbegin; xi < xend; ++xi )
		{
			/// 정점의 법선을 계산
			sum = NiPoint3::ZERO;
			v0.x = xi * mUnitsPerVertex;
			v0.y = yi * mUnitsPerVertex;
			v0.z = mHeights[yi * mLineCount + xi];
			count = 0;

			/// 우상 삼각형
			if( GetHeight( &v1.z, xi + 1, yi ) &&
				GetHeight( &v2.z, xi, yi + 1 ) )
			{
				v1.x = (xi + 1) * mUnitsPerVertex;
				v1.y = (yi    ) * mUnitsPerVertex;
				v2.x = (xi    ) * mUnitsPerVertex;
				v2.y = (yi + 1) * mUnitsPerVertex;

				sum += (v1 - v0).UnitCross( v2 - v0 );
				++count;
			}

			/// 좌상 삼각형
			if( GetHeight( &v1.z, xi, yi + 1 ) &&
				GetHeight( &v2.z, xi - 1, yi ) )
			{
				v1.x = (xi    ) * mUnitsPerVertex;
				v1.y = (yi + 1) * mUnitsPerVertex;
				v2.x = (xi - 1) * mUnitsPerVertex;
				v2.y = (yi    ) * mUnitsPerVertex;

				sum += (v1 - v0).UnitCross( v2 - v0 );
				++count;
			}

			/// 좌하 삼각형
			if( GetHeight( &v1.z, xi - 1, yi ) &&
				GetHeight( &v2.z, xi, yi - 1 ) )
			{
				v1.x = (xi - 1) * mUnitsPerVertex;
				v1.y = (yi    ) * mUnitsPerVertex;
				v2.x = (xi    ) * mUnitsPerVertex;
				v2.y = (yi - 1) * mUnitsPerVertex;

				sum += (v1 - v0).UnitCross( v2 - v0 );
				++count;
			}

			/// 우하 삼각형
			if( GetHeight( &v1.z, xi, yi - 1 ) &&
				GetHeight( &v2.z, xi + 1, yi ) )
			{
				v1.x = (xi    ) * mUnitsPerVertex;
				v1.y = (yi - 1) * mUnitsPerVertex;
				v2.x = (xi + 1) * mUnitsPerVertex;
				v2.y = (yi    ) * mUnitsPerVertex;

				sum += (v1 - v0).UnitCross( v2 - v0 );
				++count;
			}

			assert( count > 0 );
			sum /= float(count);
			sum.Unitize();
			mNormals[yi * mLineCount + xi] = sum;
		}
	}
}
