#include "Mix/Private/Scene/Common/InternalParticle.h"
#include "Mix/Scene/ICamera.h"
#include "Mix/Scene/IMaterial.h"

namespace Mix{ namespace Scene{ namespace Common{

const UInt32 InternalParticle::ES_POS_COL		= ( 1 << Mix::Graphics::VLS_POSITION ) | ( 1 << Mix::Graphics::VLS_COLOR );
const UInt32 InternalParticle::ES_TANGENTSPACE	= ( 1 << Mix::Graphics::VLS_TANGENT ) | ( 1 << Mix::Graphics::VLS_BINORMAL );
const UInt32 InternalParticle::ES_NORMAL =	( 1 << Mix::Graphics::VLS_NORMAL );

//サポートしているセマンティクスのマスク
const UInt32 InternalParticle::ES_MASK0 =	( 1 << Mix::Graphics::VLS_POSITION ) |
											( 1 << Mix::Graphics::VLS_NORMAL ) |
											( 1 << Mix::Graphics::VLS_TEXTURE ) |
											( 1 << Mix::Graphics::VLS_COLOR );

//サポートしていないセマンティクスのマスク
const UInt32 InternalParticle::ES_MASK1 =	( 1 << Mix::Graphics::VLS_BLENDWEIGHT ) |
											( 1 << Mix::Graphics::VLS_BLENDINDICES ) |
											( 1 << Mix::Graphics::VLS_PSIZE ) |
											( 1 << Mix::Graphics::VLS_TANGENT ) |
											( 1 << Mix::Graphics::VLS_BINORMAL );

const wchar_t* InternalParticle::STR_ONLY_DEFMATERIAL = L"デフォルトマテリアルのみサポートしています";
const wchar_t* InternalParticle::STR_ILLEGAL_IS = L"不正な入力シグネチャのマテリアルを設定しようとしました";
const wchar_t* InternalParticle::STR_NS_BUMP = L"バンプマッピングはサポートしていません(接線空間)";

Boolean InternalParticle::CheckMaterial( Mix::Scene::IMaterial* pMaterial, const wchar_t* pLabel, const wchar_t* pDebugName )
{
	MIX_ASSERT( pMaterial != NULL );

	UInt32 vis;
	UInt32 visMask;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// タイプのチェック
	////////////////////////////////////////////////////////////////////////////////////////////////////

	if( pMaterial->GetType() != Mix::Scene::IMaterial::DEFAULT )
	{
		if( MIX_STR_LENGTH( pLabel ) > 0 )
		{
			MIX_LOG_ERROR( L"%s : %s : DebugName[%s]", pLabel, InternalParticle::STR_ONLY_DEFMATERIAL, MIX_SAFE_NAME( pDebugName ) );
		}
		else
		{
			MIX_LOG_ERROR( L"%s : DebugName[%s]", InternalParticle::STR_ONLY_DEFMATERIAL, MIX_SAFE_NAME( pDebugName ) );
		}

		return False;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 入力シグネチャのチェック
	////////////////////////////////////////////////////////////////////////////////////////////////////

	vis = pMaterial->GetVertexInputSigunature( Mix::Scene::IMaterial::TR_SIMPLE );

	visMask = vis & InternalParticle::ES_MASK0;
	if( MIX_TESTBIT( visMask, InternalParticle::ES_POS_COL ) != InternalParticle::ES_POS_COL )
	{
		if( MIX_STR_LENGTH( pLabel ) > 0 )
		{
			MIX_LOG_ERROR( L"%s : %s(1) : DebugName[%s]", pLabel, InternalParticle::STR_ILLEGAL_IS, MIX_SAFE_NAME( pDebugName ) );
		}
		else
		{
			MIX_LOG_ERROR( L"%s(1) : DebugName[%s]", InternalParticle::STR_ILLEGAL_IS, MIX_SAFE_NAME( pDebugName ) );
		}

		return False;
	}

	visMask = vis & InternalParticle::ES_MASK1;
	if( visMask != 0 )
	{
		if( MIX_TESTBIT( visMask, InternalParticle::ES_TANGENTSPACE ) != 0 )
		{
			if( MIX_STR_LENGTH( pLabel ) > 0 )
			{
				MIX_LOG_ERROR( L"%s : %s : DebugName[%s]", pLabel, InternalParticle::STR_NS_BUMP, MIX_SAFE_NAME( pDebugName ) );
			}
			else
			{
				MIX_LOG_ERROR( L"%s : DebugName[%s]", InternalParticle::STR_NS_BUMP, MIX_SAFE_NAME( pDebugName ) );
			}
		}
		else
		{
			if( MIX_STR_LENGTH( pLabel ) > 0 )
			{
				MIX_LOG_ERROR( L"%s : %s(2) : DebugName[%s]", pLabel, InternalParticle::STR_ILLEGAL_IS, MIX_SAFE_NAME( pDebugName ) );
			}
			else
			{
				MIX_LOG_ERROR( L"%s(2) : DebugName[%s]", InternalParticle::STR_ILLEGAL_IS, MIX_SAFE_NAME( pDebugName ) );
			}
		}

		return False;
	}

	return True;
}

InternalParticle::InternalParticle( const wchar_t* pDebugName ) :
m_pMaterial( NULL ),
m_VertexDataSize( 0 ),
m_VertexTWOffset( 0 )
{
	MIX_ASSERT( pDebugName != NULL );

#ifdef _DEBUG
	m_DebugName = MIX_SAFE_NAME( pDebugName );
	m_DebVertexStride = 0;
#endif //_DEBUG
}

InternalParticle::~InternalParticle( void )
{
	MIX_RELEASE( m_pMaterial );
}

void InternalParticle::GetMaterial( Mix::Scene::IMaterial** ppMaterial )
{
	MIX_ASSERT( ppMaterial != NULL );

	MIX_ADD_REF( m_pMaterial );
	( *ppMaterial ) = m_pMaterial;
}

Boolean InternalParticle::SetMaterial( Mix::Scene::IMaterial* pMaterial )
{
	MIX_ASSERT( pMaterial != NULL );

	UInt32 vis;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// マテリアルを再設定
	////////////////////////////////////////////////////////////////////////////////////////////////////

	MIX_RELEASE( m_pMaterial );

	MIX_ADD_REF( pMaterial );
	m_pMaterial = pMaterial;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// 必要なものをあらかじめ展開しておく
	////////////////////////////////////////////////////////////////////////////////////////////////////

	vis = m_pMaterial->GetVertexInputSigunature( IMaterial::TR_SIMPLE );

	m_VertexDataSize = WIDGET_COLOR_SIZE;
	if( MIX_TESTBIT( vis, ES_NORMAL ) == ES_NORMAL ) { m_VertexDataSize += WIDGET_NORMAL_SIZE; }

	m_VertexTWOffset = WIDGET_POS_SIZE + m_VertexDataSize;

	// クワッドにも設定
	FlashQuads();

#ifdef _DEBUG
	m_DebVertexStride = m_pMaterial->GetVertexStride( IMaterial::TR_SIMPLE );
#endif //_DEBUG

	////////////////////////////////////////////////////////////////////////////////////////////////////

	return True;
}

void InternalParticle::FlashQuads( void )
{
	if( m_QuadPool.IsEmpty() == False )
	{
		Mix::Scene::Common::WIDGET_QUAD** ppQuad = m_QuadPool.GetBeginPtr();
		Mix::Scene::Common::WIDGET_QUAD** ppQuadEnd = m_QuadPool.GetEndPtr();

		while( ppQuad != ppQuadEnd )
		{
			Mix::Scene::Common::WIDGET_QUAD* pQuad = *ppQuad;

			pQuad->dataSize = m_VertexDataSize;
			pQuad->texWriteOffset = m_VertexTWOffset;

			ppQuad++;
		}
	}
}

void InternalParticle::ExtendQuadsPool( UInt32 addCount )
{
	MIX_ASSERT( addCount > 0 );
	MIX_ASSERT( m_pMaterial != NULL );

	for( UInt32 i = 0; i < addCount; i++ )
	{
		WIDGET_QUAD* pQuad = m_QuadPool.Add();

		pQuad->dataSize = m_VertexDataSize;
		pQuad->texWriteOffset = m_VertexTWOffset;
	}
}

void InternalParticle::FixedBillboardSubset( const Mix::Matrix4x4& mat, Mix::Scene::Common::WIDGET_QUAD** quads, UInt32 quadNum )
{
	Float32 m00 = mat.m00;
	Float32 m01 = mat.m01;
	Float32 m02 = mat.m02;
	Float32 m10 = mat.m10;
	Float32 m11 = mat.m11;
	Float32 m12 = mat.m12;
	Float32 m20 = mat.m20;
	Float32 m21 = mat.m21;
	Float32 m22 = mat.m22;

	Mix::Scene::Common::WIDGET_QUAD** ppQuad = &( quads[0] );
	Mix::Scene::Common::WIDGET_QUAD** ppQuadEnd = ppQuad + quadNum;
	Mix::Scene::Common::WIDGET_QUAD* pQuad;

	const Mix::Vector3* pSrcPoint;
	const Mix::Vector3* pSrcPointEnd;
	Mix::Vector3* pDstPoint;

	const Mix::Vector3* pos;

	Float32 sx;
	Float32 sy;
	Float32 sz;

	while( ppQuad != ppQuadEnd )
	{
		pQuad = *ppQuad;

		pSrcPoint = &( pQuad->localPoints[0] );
		pSrcPointEnd = pSrcPoint + 4;
		pDstPoint = &( pQuad->points[0] );

		pos = &( pQuad->pos );

		while( pSrcPoint != pSrcPointEnd )
		{
			sx = pSrcPoint->x;
			sy = pSrcPoint->y;
			sz = pSrcPoint->z;

			pDstPoint->x = ( sx * m00 + sy * m10 + sz * m20 ) + pos->x;
			pDstPoint->y = ( sx * m01 + sy * m11 + sz * m21 ) + pos->y;
			pDstPoint->z = ( sx * m02 + sy * m12 + sz * m22 ) + pos->z;

//			*pDstPoint = mat.TransformSR( *pSrcPoint );
//			*pDstPoint += center;

			pSrcPoint++;
			pDstPoint++;
		}

		ppQuad++;
	}
}

void InternalParticle::RefreshDefaultSubset(	Boolean bIndividual,
												const Mix::Matrix4x4& worldMat,
												const Mix::Scene::IParticleProcessor::FACE* faces,
												UInt32 faceNum,
												UInt32 quadOffset,
												Mix::Scene::Common::WIDGET_AABB_SUBSET& subset )
{
	MIX_ASSERT( faces != NULL );
	MIX_ASSERT( faceNum > 0 );
	MIX_ASSERT( m_QuadPool.GetCount() >= ( quadOffset + faceNum ) );

	const IParticleProcessor::FACE* pFace = &( faces[0] );
	const IParticleProcessor::FACE* pFaceEnd = pFace + faceNum;

	WIDGET_QUAD** ppQuadBegin = m_QuadPool.GetBeginPtr() + quadOffset;
	WIDGET_QUAD** ppQuad = ppQuadBegin;

	Mix::Vector3* bmin = &( subset.bounds.min );
	Mix::Vector3* bmax = &( subset.bounds.max );

	Float32 m00 = worldMat.m00;
	Float32 m01 = worldMat.m01;
	Float32 m02 = worldMat.m02;
	Float32 m10 = worldMat.m10;
	Float32 m11 = worldMat.m11;
	Float32 m12 = worldMat.m12;
	Float32 m20 = worldMat.m20;
	Float32 m21 = worldMat.m21;
	Float32 m22 = worldMat.m22;

	const Mix::Vector3* localPoints;
	Mix::Vector3* quadPoints;
	const Mix::Vector3* lp;
	Mix::Vector3* qp;

	Mix::Vector3 qmin;
	Mix::Vector3 qmax;

	UInt32 i;

	while( pFace != pFaceEnd )
	{
		WIDGET_QUAD* pQuad = *ppQuad;
		const Mix::Vector3& qpos = pQuad->pos;

		localPoints = pFace->localPoints;
		quadPoints = pQuad->points;

		/*
			全般
		*/

		if( bIndividual == True )
		{
			pQuad->pos = pFace->pos;
			pQuad->normal = pFace->normal;
		}
		else
		{
			pQuad->pos = worldMat * pFace->pos;
			pQuad->normal = worldMat.TransformSR( pFace->normal );
		}

		pQuad->localPoints = NULL;
		pQuad->texCoords = pFace->texCoords;
		pQuad->color = pFace->color;

		/*
			ポイントを求めつつ、クワッドの AABB を求める
		*/

		qmin.Set( +MIX_FLOAT_MAX, +MIX_FLOAT_MAX, +MIX_FLOAT_MAX );
		qmax.Set( -MIX_FLOAT_MAX, -MIX_FLOAT_MAX, -MIX_FLOAT_MAX );

		for( i = 0; i < 4; i++ )
		{
			lp = &( localPoints[i] );
			qp = &( quadPoints[i] );

			qp->x = ( lp->x * m00 + lp->y * m10 + lp->z * m20 ) + qpos.x;
			qp->y = ( lp->x * m01 + lp->y * m11 + lp->z * m21 ) + qpos.y;
			qp->z = ( lp->x * m02 + lp->y * m12 + lp->z * m22 ) + qpos.z;

			if( qmin.x > qp->x ) qmin.x = qp->x;
			if( qmin.y > qp->y ) qmin.y = qp->y;
			if( qmin.z > qp->z ) qmin.z = qp->z;

			if( qmax.x < qp->x ) qmax.x = qp->x;
			if( qmax.y < qp->y ) qmax.y = qp->y;
			if( qmax.z < qp->z ) qmax.z = qp->z;
		}

		/*
			境界を結合
		*/

		if( bmin->x > qmin.x ) { bmin->x = qmin.x; }
		if( bmin->y > qmin.y ) { bmin->y = qmin.y; }
		if( bmin->z > qmin.z ) { bmin->z = qmin.z; }

		if( bmax->x < qmax.x ) { bmax->x = qmax.x; }
		if( bmax->y < qmax.y ) { bmax->y = qmax.y; }
		if( bmax->z < qmax.z ) { bmax->z = qmax.z; }

		/*
			次のフェイスへ
		*/

		ppQuad++;
		pFace++;
	}

	subset.quadCount = faceNum;
	subset.quads = ppQuadBegin;
}

void InternalParticle::RefreshBillboardSubset(	Boolean bIndividual,
												const Mix::Matrix4x4& worldMat,
												const Mix::Scene::IParticleProcessor::FACE* faces,
												UInt32 faceNum,
												UInt32 quadOffset,
												Mix::Scene::Common::WIDGET_AABB_SUBSET& subset )
{
	MIX_ASSERT( faces != NULL );
	MIX_ASSERT( faceNum > 0 );
	MIX_ASSERT( m_QuadPool.GetCount() >= ( quadOffset + faceNum ) );

	const IParticleProcessor::FACE* pFace = &( faces[0] );
	const IParticleProcessor::FACE* pFaceEnd = pFace + faceNum;

	WIDGET_QUAD** ppQuadBegin = m_QuadPool.GetBeginPtr() + quadOffset;
	WIDGET_QUAD** ppQuad = ppQuadBegin;

	Mix::Vector3* bmin = &( subset.bounds.min );
	Mix::Vector3* bmax = &( subset.bounds.max );

	const Mix::Vector3* localPoints;

	Float32 halfSizeY;

	Mix::Vector3 wp;

	Mix::Vector3 qmin;
	Mix::Vector3 qmax;
	Float32 qminZ;
	Float32 qmaxZ;

	UInt32 i;

	while( pFace != pFaceEnd )
	{
		WIDGET_QUAD* pQuad = *ppQuad;
		const Mix::Vector3& qpos = pQuad->pos;

		/*
			コピー
		*/

		pQuad->pos = ( bIndividual == True )? pFace->pos : worldMat * pFace->pos;
		pQuad->localPoints = pFace->localPoints;
		pQuad->texCoords = pFace->texCoords;
		pQuad->color = pFace->color;

		/*
			クワッドの AABB を求める
		*/

		localPoints = pQuad->localPoints;

		qmin.Set( +MIX_FLOAT_MAX, +MIX_FLOAT_MAX, +MIX_FLOAT_MAX );
		qmax.Set( -MIX_FLOAT_MAX, -MIX_FLOAT_MAX, -MIX_FLOAT_MAX );

		for( i = 0; i < 4; i++ )
		{
			wp = qpos + localPoints[i];

			if( qmin.x > wp.x ) qmin.x = wp.x;
			if( qmin.y > wp.y ) qmin.y = wp.y;

			if( qmax.x < wp.x ) qmax.x = wp.x;
			if( qmax.y < wp.y ) qmax.y = wp.y;
		}

		/*
			境界を結合
		*/

		halfSizeY = ( qmax.y - qmin.y ) * 0.5f;
		qminZ = qpos.z - halfSizeY;
		qmaxZ = qpos.z + halfSizeY;

		if( bmin->x > qmin.x ) { bmin->x = qmin.x; }
		if( bmin->y > qmin.y ) { bmin->y = qmin.y; }
		if( bmin->z > qminZ ) { bmin->z = qminZ; }

		if( bmax->x < qmax.x ) { bmax->x = qmax.x; }
		if( bmax->y < qmax.y ) { bmax->y = qmax.y; }
		if( bmax->z < qmaxZ ) { bmax->z = qmaxZ; }

		/*
			次のフェイスへ
		*/

		ppQuad++;
		pFace++;
	}

	subset.quadCount = faceNum;
	subset.quads = ppQuadBegin;
}

void InternalParticle::RefreshDefaultSubset(	const Mix::Matrix4x4& worldMat,
												const Mix::Scene::IParticleProcessor::FACE* faces,
												UInt32 faceNum,
												UInt32 quadOffset,
												Mix::Scene::Common::WIDGET_SPHERE_SUBSET& subset )
{
	MIX_ASSERT( faces != NULL );
	MIX_ASSERT( faceNum > 0 );
	MIX_ASSERT( m_QuadPool.GetCount() >= ( quadOffset + faceNum ) );

	const IParticleProcessor::FACE* pFace = &( faces[0] );
	const IParticleProcessor::FACE* pFaceEnd = pFace + faceNum;

	WIDGET_QUAD** ppQuadBegin = m_QuadPool.GetBeginPtr() + quadOffset;
	WIDGET_QUAD** ppQuad = ppQuadBegin;

	Mix::Vector3 bmin( +MIX_FLOAT_MAX, +MIX_FLOAT_MAX, +MIX_FLOAT_MAX );
	Mix::Vector3 bmax( -MIX_FLOAT_MAX, -MIX_FLOAT_MAX, -MIX_FLOAT_MAX );

	Float32 m00 = worldMat.m00;
	Float32 m01 = worldMat.m01;
	Float32 m02 = worldMat.m02;
	Float32 m10 = worldMat.m10;
	Float32 m11 = worldMat.m11;
	Float32 m12 = worldMat.m12;
	Float32 m20 = worldMat.m20;
	Float32 m21 = worldMat.m21;
	Float32 m22 = worldMat.m22;

	const Mix::Vector3* localPoints;
	Mix::Vector3* quadPoints;
	const Mix::Vector3* lp;
	Mix::Vector3* qp;

	UInt32 i;

	while( pFace != pFaceEnd )
	{
		WIDGET_QUAD* pQuad = *ppQuad;
		const Mix::Vector3& qpos = pQuad->pos;

		localPoints = pFace->localPoints;
		quadPoints = pQuad->points;

		/*
			全般
		*/

		pQuad->pos = pFace->pos;
		pQuad->normal = pFace->normal;
		pQuad->localPoints = NULL;
		pQuad->texCoords = pFace->texCoords;
		pQuad->color = pFace->color;

		/*
			ポイントを求めつつ、クワッドの AABB を求める
		*/

		for( i = 0; i < 4; i++ )
		{
			lp = &( localPoints[i] );
			qp = &( quadPoints[i] );

			qp->x = ( lp->x * m00 + lp->y * m10 + lp->z * m20 ) + qpos.x;
			qp->y = ( lp->x * m01 + lp->y * m11 + lp->z * m21 ) + qpos.y;
			qp->z = ( lp->x * m02 + lp->y * m12 + lp->z * m22 ) + qpos.z;

			if( bmin.x > qp->x ) bmin.x = qp->x;
			if( bmin.y > qp->y ) bmin.y = qp->y;
			if( bmin.z > qp->z ) bmin.z = qp->z;

			if( bmax.x < qp->x ) bmax.x = qp->x;
			if( bmax.y < qp->y ) bmax.y = qp->y;
			if( bmax.z < qp->z ) bmax.z = qp->z;
		}

		/*
			次のフェイスへ
		*/

		ppQuad++;
		pFace++;
	}

	subset.quadCount = faceNum;
	subset.quads = ppQuadBegin;
	subset.bounds.center = ( bmin + bmax ) * 0.5f;
	subset.bounds.radius = ( bmax - bmin ).GetLengthF() * 0.5f;
}

void InternalParticle::RefreshBillboardSubset(	const Mix::Scene::IParticleProcessor::FACE* faces,
												UInt32 faceNum,
												UInt32 quadOffset,
												Mix::Scene::Common::WIDGET_SPHERE_SUBSET& subset )
{
	MIX_ASSERT( faces != NULL );
	MIX_ASSERT( faceNum > 0 );
	MIX_ASSERT( m_QuadPool.GetCount() >= ( quadOffset + faceNum ) );

	const IParticleProcessor::FACE* pFace = &( faces[0] );
	const IParticleProcessor::FACE* pFaceEnd = pFace + faceNum;

	WIDGET_QUAD** ppQuadBegin = m_QuadPool.GetBeginPtr() + quadOffset;
	WIDGET_QUAD** ppQuad = ppQuadBegin;

	Mix::Vector3 bmin( +MIX_FLOAT_MAX, +MIX_FLOAT_MAX, +MIX_FLOAT_MAX );
	Mix::Vector3 bmax( -MIX_FLOAT_MAX, -MIX_FLOAT_MAX, -MIX_FLOAT_MAX );

	const Mix::Vector3* lp;
	const Mix::Vector3* lpEnd;
	Mix::Vector3 wp;

	while( pFace != pFaceEnd )
	{
		WIDGET_QUAD* pQuad = *ppQuad;
		const Mix::Vector3& qpos = pQuad->pos;

		/*
			コピー
		*/

		pQuad->pos = pFace->pos;
		pQuad->localPoints = pFace->localPoints;
		pQuad->texCoords = pFace->texCoords;
		pQuad->color = pFace->color;

		/*
			クワッドの AABB を求める
		*/

		lp = &( pQuad->localPoints[0] );
		lpEnd = lp + 4;

		while( lp != lpEnd )
		{
			wp = qpos + *lp;

			if( bmin.x > wp.x ) bmin.x = wp.x;
			if( bmin.y > wp.y ) bmin.y = wp.y;
			if( bmin.z > wp.z ) bmin.z = wp.z;

			if( bmax.x < wp.x ) bmax.x = wp.x;
			if( bmax.y < wp.y ) bmax.y = wp.y;
			if( bmax.z < wp.z ) bmax.z = wp.z;

			lp++;
		}

		/*
			次のフェイスへ
		*/

		ppQuad++;
		pFace++;
	}

	subset.quadCount = faceNum;
	subset.quads = ppQuadBegin;
	subset.bounds.center = ( bmin + bmax ) * 0.5f;
	subset.bounds.radius = ( bmax - bmin ).GetLengthF() * 0.5f;
}

}}}
