#include "stdafx.h"
#include "VisibleArraySorter.h"

#include "VisibleArray.h"
#include "Camera.h"

#include "Renderer.h"

cVisibleArraySorter::cVisibleArraySorter( cVisibleArray* array )
: mArray( array )
{
	assert( array && "null visible array" );
}

cVisibleArraySorter::~cVisibleArraySorter()
{
}

cAlphaArraySorter::cAlphaArraySorter( cVisibleArray* array )
: cVisibleArraySorter( array )
, mNumGeoms( 0 )
, mMaxGeoms( 0 )
, mGeoms( 0 )
, mDepths( 0 )
{
}

cAlphaArraySorter::~cAlphaArraySorter()
{
	NiFree( mDepths );
}

void cAlphaArraySorter::Sort( const cCamera& cam )
{
	/// 기하 오브젝트 수를 검사
	mNumGeoms = mArray->GetCount();
	if( mNumGeoms == 0 )
		return;

	if( mNumGeoms > mMaxGeoms )
	{
		mMaxGeoms = mArray->GetAllocatedSize();

		NiFree( mDepths );
		mDepths = NiAlloc( float, mMaxGeoms );
		assert( mDepths );
	}

	/// 기하 배열을 설정
	mGeoms = mArray->GetGeomArray();

	/// 기하 오브젝트들의 깊이값을 계산
	NiPoint3 viewDir = cam.GetWorldDirection();

	for( int i = 0; i < mNumGeoms; ++i )
	{
		mDepths[i] = mGeoms[i]->GetWorldBound().GetCenter() * viewDir;
	}

	SortObjectsByDepth( 0, mNumGeoms - 1 );
}

void cAlphaArraySorter::Render()
{
	//	NiRenderer* renderer = NiRenderer::GetRenderer();
	cRenderer* renderer = (cRenderer*)NiRenderer::GetRenderer();
	if( renderer == 0 )
	{
		assert( 0 );
		return;
	}

	mNumGeoms = mArray->GetCount();
	if( mNumGeoms == 0 )
		return;

	for( int i = mNumGeoms - 1; i >= 0; --i )
	{
		NiAlphaProperty* alpha = (NiAlphaProperty*)mGeoms[i]->GetProperty( NiProperty::ALPHA );
		if( alpha )
		{
			renderer->mTest1 = alpha->GetSrcBlendMode();
			renderer->mTest2 = alpha->GetDestBlendMode();
		}
		else
		{
			renderer->mTest1 = NiAlphaProperty::ALPHA_SRCALPHA;
			renderer->mTest2 = NiAlphaProperty::ALPHA_INVSRCALPHA;
		}

		mGeoms[i]->RenderImmediate( renderer );
	}

}

void cAlphaArraySorter::SortObjectsByDepth( int l, int r )
{
	if( r > l)
	{
		int i, j;

		i = l - 1;
		j = r + 1;
		float fPivot = ChoosePivot(l, r);

		for( ;;)
		{
			do 
			{
				j--;
			} while (fPivot < mDepths[j]);

			do
			{
				i++;
			} while (mDepths[i] < fPivot);

			if( i < j)
			{
				NiGeometry* pkObjTemp = mGeoms[i];
				mGeoms[i] = mGeoms[j];
				mGeoms[j] = pkObjTemp;
				float fTemp = mDepths[i];
				mDepths[i] = mDepths[j];
				mDepths[j] = fTemp;
			}
			else
			{
				break;
			}
		}

		if( j == r)
		{
			SortObjectsByDepth(l, j - 1);
		}
		else
		{
			SortObjectsByDepth(l, j);
			SortObjectsByDepth(j + 1, r);
		}
	}
}

float cAlphaArraySorter::ChoosePivot( int l, int r ) const
{
	// Check the first, middle, and last element. Choose the one which falls
	// between the other two. This has a good chance of discouraging 
	// quadratic behavior from qsort.
	// In the case when all three are equal, this code chooses the middle
	// element, which will prevent quadratic behavior for a list with 
	// all elements equal.

	int m = (l + r) >> 1;

	if( mDepths[l] < mDepths[m])
	{
		if( mDepths[m] < mDepths[r])
		{
			return mDepths[m];
		}
		else
		{
			if( mDepths[l] < mDepths[r])
				return mDepths[r];
			else
				return mDepths[l];
		}
	}
	else
	{
		if( mDepths[l] < mDepths[r])
		{
			return mDepths[l];
		}
		else
		{
			if( mDepths[m] < mDepths[r])
				return mDepths[r];
			else
				return mDepths[m];
		}
	}
}

cSolidArraySorter::cSolidArraySorter( cVisibleArray* array )
: cVisibleArraySorter( array )
, mNumGeoms( 0 )
, mGeoms( 0 )
{
}

cSolidArraySorter::~cSolidArraySorter()
{
}

void cSolidArraySorter::Sort( const cCamera& /*cam*/ )
{
	/// 기하 오브젝트 수를 검사
	mNumGeoms = mArray->GetCount();
	if( mNumGeoms == 0 )
		return;

	/// 기하 배열을 설정
	mGeoms = mArray->GetGeomArray();

	//// 기하 배열을 정렬
	::Sort( mGeoms, mGeoms + mNumGeoms - 1, cSolidNodeCompare() );
}

void cSolidArraySorter::Render()
{
	NiRenderer* renderer = NiRenderer::GetRenderer();

	if( renderer == 0 )
	{
		assert( 0 && "null renderer" );
		return;
	}

	if( mNumGeoms == 0 )
		return;

	/// 배치 렌더링
	NiGeometry* geom = mGeoms[0];
	NiProperty* current = geom->GetProperty( NiProperty::TEXTURING );
	renderer->BeginBatch( geom->GetPropertyState(), geom->GetEffectState() );
	renderer->BatchRenderStrips( (NiTriStrips*)geom );

	for( unsigned int i = 1; i < mNumGeoms; ++i )
	{
		NiGeometry* geom = mGeoms[i];
		NiProperty* prop = geom->GetProperty( NiProperty::TEXTURING );

		if( current != prop )
		{
			current = prop;
			renderer->EndBatch();
			renderer->BeginBatch( geom->GetPropertyState(), geom->GetEffectState() );
		}

		renderer->BatchRenderStrips( (NiTriStrips*)geom );
	}
	renderer->EndBatch();
}
