///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT Class
//
//	(c) 2003 IDV, Inc.
//
//	*** INTERACTIVE DATA VISUALIZATION (IDV) PROPRIETARY INFORMATION ***
//
//	This software is supplied under the terms of a license agreement or
//	nondisclosure agreement with Interactive Data Visualization and may
//	not be copied or disclosed except in accordance with the terms of
//	that agreement.
//
//      Copyright (c) 2001-2003 IDV, Inc.
//      All Rights Reserved.
//
//		IDV, Inc.
//		1233 Washington St. Suite 610
//		Columbia, SC 29201
//		Voice: (803) 799-1699
//		Fax:   (803) 931-0320
//		Web:   http://www.idvinc.com


#include "StdAfx.h"
#include "BoundaryShapeManager.h"

#ifdef USE_SPEEDGRASS

inline float VecInterpolate(float fStart, float fEnd, float fPercent)
{
    return fStart + (fEnd - fStart) * fPercent;
}

#define VectorSinD(x)       sinf((x) / 57.29578f)
#define VectorCosD(x)       cosf((x) / 57.29578f)

using namespace std;

// macros
#ifndef max
#define max(a, b)  (((a) > (b)) ? (a) : (b)) 
#endif
#ifndef min
#define min(a, b)  (((a) < (b)) ? (a) : (b)) 
#endif

// static variables
float CSpeedGrassRT::m_fLodFarDistance = 100.0f;
float CSpeedGrassRT::m_fLodTransitionLength = 37.5f;
float CSpeedGrassRT::m_afUnitBillboard[12] = { 0.0f };
float CSpeedGrassRT::m_afWindDir[4] = { 1.0f, 0.3f, 0.0f, 0.0f };

// camera
float CSpeedGrassRT::m_afCameraOut[3] = { 0.0f, 1.0f, 0.0f };
float CSpeedGrassRT::m_afCameraUp[3] = { 0.0f, 0.0f, 1.0f };
float CSpeedGrassRT::m_afCameraRight[3] = { 1.0f, 0.0f, 0.0f };
float CSpeedGrassRT::m_afCameraPos[3] = { 0.0f, 0.0f, 0.0f };
float CSpeedGrassRT::m_fFieldOfView = D3DXToRadian(40.0f);
float CSpeedGrassRT::m_fAspectRatio = 4.0f / 3.0f;

// culling
float CSpeedGrassRT::m_afFrustumBox[6] = { 0.0f };
float CSpeedGrassRT::m_afFrustumMin[2] = { FLT_MAX, FLT_MAX };
float CSpeedGrassRT::m_afFrustumMax[2] = { -FLT_MAX, -FLT_MAX };
float CSpeedGrassRT::m_afFrustumPlanes[5][4] = { 0.0f };


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SBlade::SBlade

CSpeedGrassRT::SBlade::SBlade( ) :
	m_fSize(1.0f),
	m_fNoise(0.0f),
	m_fThrow(0.0f),
	m_ucWhichTexture(0)
{
	m_afBottomColor[0] = m_afBottomColor[1] = m_afBottomColor[2] = 1.0f;
	m_afTopColor[0] = m_afTopColor[1] = m_afTopColor[2] = 1.0f;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SRegion::SRegion

CSpeedGrassRT::SRegion::SRegion( ) :
	m_bCulled(false),
	m_fCullingRadius(1.0f)
{
	m_afCenter[0] = m_afCenter[1] = m_afCenter[2] = 0.5f;
	m_afMin[0] = m_afMin[1] = m_afMin[2] = 0.0f;
	m_afMax[0] = m_afMax[1] = m_afMax[2] = 1.0f;
	m_VertexBuffer.Destroy();
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::CSpeedGrassRT

CSpeedGrassRT::CSpeedGrassRT( ) :
	m_nNumRegions(0),
	m_nNumRegionCols(0),
	m_nNumRegionRows(0),
	m_pRegions(NULL),
	m_bAllRegionsCulled(false)
{
	m_afBoundingBox[0] = m_afBoundingBox[1] = m_afBoundingBox[2] = 0.0f;
	m_afBoundingBox[3] = m_afBoundingBox[4] = m_afBoundingBox[5] = 1.0f;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::~CSpeedGrassRT

CSpeedGrassRT::~CSpeedGrassRT( )
{
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::DeleteRegions

void CSpeedGrassRT::DeleteRegions(void)
{
	delete[] m_pRegions;
	m_pRegions = NULL;
	m_nNumRegions = 0;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::GetRegions

const CSpeedGrassRT::SRegion* CSpeedGrassRT::GetRegions(unsigned int& uiNumRegions)
{
	uiNumRegions = m_nNumRegions;

	return m_pRegions;
}

///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::ParseBsfFile

bool CSpeedGrassRT::ParseBsfFile(const char* pFilename, unsigned int nNumBlades, unsigned int uiRows, unsigned int uiCols, float fCollisionDistance)
{
	bool bSuccess = false;

	// copy region settings
	m_nNumRegionCols = int(uiCols);
	m_nNumRegionRows = int(uiRows);

	// initialize bounding box
	m_afBoundingBox[0] = m_afBoundingBox[1] = m_afBoundingBox[2] = FLT_MAX;
	m_afBoundingBox[3] = m_afBoundingBox[4] = m_afBoundingBox[5] = -FLT_MAX;

	CBoundaryShapeManager cManager;
	vector<SBlade> vSceneBlades;

	if (cManager.LoadBsfFile(pFilename))
	{
		for (unsigned int i = 0; i < nNumBlades; ++i)
		{
			SBlade sBlade;

			// try to place a blade
			if (cManager.RandomPoint(sBlade.m_afPos[0], sBlade.m_afPos[1]))
			{
				sBlade.m_afPos[2] = Height(sBlade.m_afPos[0], sBlade.m_afPos[1], sBlade.m_afNormal);

//				CVec3 cNormal(sBlade.m_afNormal[0], sBlade.m_afNormal[1], sBlade.m_afNormal[2]);
//				cNormal.Normalize( );
//				cNormal[2] = -cNormal[2];
//				memcpy(sBlade.m_afNormal, cNormal, 3 * sizeof(float));
				D3DXVECTOR3 v3Normal(sBlade.m_afNormal[0], sBlade.m_afNormal[1], sBlade.m_afNormal[2]);
				D3DXVec3Normalize(&v3Normal, &v3Normal);
				v3Normal.z = -v3Normal.z;
				sBlade.m_afNormal[0] = v3Normal.x;
				sBlade.m_afNormal[1] = v3Normal.y;
				sBlade.m_afNormal[2] = v3Normal.z;

				// check against overall scene bounding box
				for (int nAxis = 0; nAxis < 3; ++nAxis)
				{
					m_afBoundingBox[nAxis] = min(m_afBoundingBox[nAxis], sBlade.m_afPos[nAxis]);
					m_afBoundingBox[nAxis + 3] = max(m_afBoundingBox[nAxis + 3], sBlade.m_afPos[nAxis]);
				}

				// set bottom and top color
				float fHeightPercent = Color(sBlade.m_afPos[0], sBlade.m_afPos[1], sBlade.m_afNormal, sBlade.m_afTopColor, sBlade.m_afBottomColor);
				sBlade.m_fSize = VecInterpolate(c_fMinBladeSize, c_fMaxBladeSize, fHeightPercent);

				// assign which blade texture map
				sBlade.m_ucWhichTexture = GetRandom(0, c_nNumBladeMaps - 1);

				// compute wind effects
				sBlade.m_fNoise = GetRandom(c_fMinBladeNoise, c_fMaxBladeNoise);
				sBlade.m_fThrow = GetRandom(c_fMinBladeThrow, c_fMaxBladeThrow);

				// store all blades together
				vSceneBlades.push_back(sBlade);
			}
		}

		bSuccess = true;
	}
	else
		fprintf(stderr, "%s\n", cManager.GetCurrentError( ).c_str( ));

	if (bSuccess)
		CreateRegions(vSceneBlades, fCollisionDistance);

	return bSuccess;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::CustomPlacement
//
//	Use this function to perform custom grass blade placement.  Feel free
//	to add parameters as necessary but be sure to call CreateRegions( )
//	at the end of the function to set up the SpeedGrass region system.

bool CSpeedGrassRT::CustomPlacement(unsigned int uiRows, unsigned int uiCols)
{
	// copy region settings (do not remove)
	m_nNumRegionCols = int(uiCols);
	m_nNumRegionRows = int(uiRows);

	// initialize bounding box (do not remove)
	m_afBoundingBox[0] = m_afBoundingBox[1] = m_afBoundingBox[2] = FLT_MAX;
	m_afBoundingBox[3] = m_afBoundingBox[4] = m_afBoundingBox[5] = -FLT_MAX;

	// place one blade as an example
	vector<SBlade> vSceneBlades;

	SBlade sBlade;

	sBlade.m_afPos[0] = 0.0f;
	sBlade.m_afPos[1] = 0.0f;
	sBlade.m_afPos[2] = 0.0f;

	sBlade.m_afNormal[0] = 0.0f;
	sBlade.m_afNormal[1] = 0.0f;
	sBlade.m_afNormal[2] = 1.0f;

	// check against overall scene bounding box (always do this)
	for (int nAxis = 0; nAxis < 3; ++nAxis)
	{
		m_afBoundingBox[nAxis] = min(m_afBoundingBox[nAxis], sBlade.m_afPos[nAxis]);
		m_afBoundingBox[nAxis + 3] = max(m_afBoundingBox[nAxis + 3], sBlade.m_afPos[nAxis]);
	}

	// set bottom and top color
	memcpy(sBlade.m_afBottomColor, sBlade.m_afNormal, 12);
	memcpy(sBlade.m_afTopColor, sBlade.m_afNormal, 12);

	// assign which blade texture map
	sBlade.m_ucWhichTexture = GetRandom(0, c_nNumBladeMaps - 1);

	// compute wind effects
	sBlade.m_fNoise = GetRandom(c_fMinBladeNoise, c_fMaxBladeNoise);
	sBlade.m_fThrow = GetRandom(c_fMinBladeThrow, c_fMaxBladeThrow);

	// compute dimensions
	sBlade.m_fSize = GetRandom(c_fMinBladeSize, c_fMaxBladeSize);

	// store all blades together
	vSceneBlades.push_back(sBlade);

	// create regions based on blades (do not remove)
	CreateRegions(vSceneBlades);

	// true = success, false = error
	return true;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::GetLodParams
 
void CSpeedGrassRT::GetLodParams(float& fFarDistance, float& fTransitionLength)
{
	fFarDistance = m_fLodFarDistance;
	fTransitionLength = m_fLodTransitionLength;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SetLodParams

void CSpeedGrassRT::SetLodParams(float fFarDistance, float fTransitionLength)
{
	m_fLodFarDistance = fFarDistance;
	m_fLodTransitionLength = fTransitionLength;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::Cull
//
//	Using a two-dimensional projection, determine which regions
//	intersect with the view frustum (+Z is assumed to be up)

void CSpeedGrassRT::Cull(void)
{
	// convert raw frustum min and max values into min and max region cell indices
	int anFrustumCellsMin[2], anFrustumCellsMax[2];
	ConvertCoordsToCell(m_afFrustumMin, anFrustumCellsMin);
	ConvertCoordsToCell(m_afFrustumMax, anFrustumCellsMax);

	// set all regions to culled, modify later
	for (int i = 0; i < m_nNumRegions; ++i)
		m_pRegions[i].m_bCulled = true;
	
	int nRegionsDrawn = 0;

	// is the entire set of regions culled?
	if ((anFrustumCellsMin[0] < 0 && anFrustumCellsMax[0] < 0) ||
		(anFrustumCellsMin[0] >= m_nNumRegionCols && anFrustumCellsMax[0] >= m_nNumRegionCols) ||
		(anFrustumCellsMin[1] < 0 && anFrustumCellsMax[1] < 0) ||
		(anFrustumCellsMin[1] >= m_nNumRegionRows && anFrustumCellsMax[1] >= m_nNumRegionRows))
		m_bAllRegionsCulled = true;
	else
	{
		// clip cell values
		anFrustumCellsMin[0] = max(anFrustumCellsMin[0], 0);
		anFrustumCellsMin[1] = max(anFrustumCellsMin[1], 0);
		anFrustumCellsMax[0] = min(anFrustumCellsMax[0], m_nNumRegionCols - 1);
		anFrustumCellsMax[1] = min(anFrustumCellsMax[1], m_nNumRegionRows - 1);

		for (i = anFrustumCellsMin[0]; i <= anFrustumCellsMax[0]; ++i)
			for (int j = anFrustumCellsMin[1]; j <= anFrustumCellsMax[1]; ++j)
			{
				SRegion* pRegion = m_pRegions + GetRegionIndex(j, i);
				pRegion->m_bCulled = OutsideFrustum(pRegion);
			}

		m_bAllRegionsCulled = false;
	}
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SetWindDirection

void CSpeedGrassRT::SetWindDirection(const float* pWindDir)
{
	memcpy(m_afWindDir, pWindDir, 3 * sizeof(float));
	m_afWindDir[3] = 0.0f;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::GetWindDirection

const float* CSpeedGrassRT::GetWindDirection(void)
{
	return m_afWindDir;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::GetCameraPos

const float* CSpeedGrassRT::GetCameraPos(void)
{
	return m_afCameraPos;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SetCamera

void CSpeedGrassRT::SetCamera(const float* pPosition, const double* pModelviewMatrix)
{
	memcpy(m_afCameraPos, pPosition, 3 * sizeof(float));

	// "right" vector
	m_afCameraRight[0] = pModelviewMatrix[0];
	m_afCameraRight[1] = pModelviewMatrix[4];
	m_afCameraRight[2] = pModelviewMatrix[8];

	// "up" vector
	m_afCameraUp[0] = pModelviewMatrix[1];
	m_afCameraUp[1] = pModelviewMatrix[5];
	m_afCameraUp[2] = pModelviewMatrix[9];

	// "out of screen" vector
	m_afCameraOut[0] = pModelviewMatrix[2];
	m_afCameraOut[1] = pModelviewMatrix[6];
	m_afCameraOut[2] = pModelviewMatrix[10];

	// with direction changed, billboard turns
	ComputeUnitBillboard( );

	// compute new frustum box
	ComputeFrustum( );
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::SetPerspective

void CSpeedGrassRT::SetPerspective(float fAspectRatio, float fFieldOfView)
{
	m_fAspectRatio = fAspectRatio;
	m_fFieldOfView = D3DXToRadian(fAspectRatio * fFieldOfView);
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::CreateRegions

void CSpeedGrassRT::CreateRegions(const vector<SBlade>& vSceneBlades, float fCollisionDistance)
{
	// create regions based on overall extents
	DeleteRegions( );
	m_nNumRegions = int(m_nNumRegionRows * m_nNumRegionCols);
	m_pRegions = new SRegion[m_nNumRegions];

	// run through all regions, computing extents for each
	float fCellWidth = (m_afBoundingBox[3] - m_afBoundingBox[0]) / m_nNumRegionCols;
	float fCellHeight = (m_afBoundingBox[4] - m_afBoundingBox[1]) / m_nNumRegionRows;

	float fY = m_afBoundingBox[1];
	for (int nRow = 0; nRow < m_nNumRegionRows; ++nRow)
	{
		float fX = m_afBoundingBox[0];
		for (int nCol = 0; nCol < m_nNumRegionCols; ++nCol)
		{
			SRegion* pRegion = m_pRegions + GetRegionIndex(nRow, nCol);

			// compute extents
			pRegion->m_afMin[0] = fX;
			pRegion->m_afMax[0] = fX + fCellWidth;
			pRegion->m_afMin[1] = fY;
			pRegion->m_afMax[1] = fY + fCellHeight;
		
			// compute center
			pRegion->m_afCenter[0] = 0.5f * (pRegion->m_afMin[0] + pRegion->m_afMax[0]);
			pRegion->m_afCenter[1] = 0.5f * (pRegion->m_afMin[1] + pRegion->m_afMax[1]);

			// compute culling radius
			pRegion->m_fCullingRadius = 1.1f * sqrt(
				((pRegion->m_afMax[0] - pRegion->m_afCenter[0]) * (pRegion->m_afMax[0] - pRegion->m_afCenter[0])) +
				((pRegion->m_afMax[1] - pRegion->m_afCenter[1]) * (pRegion->m_afMax[1] - pRegion->m_afCenter[1]))
				);

			fX += fCellWidth;
		}

		fY += fCellHeight;
	}

	// assign each blade of grass to its particular region
	for (vector<SBlade>::const_iterator iBlade = vSceneBlades.begin( ); iBlade != vSceneBlades.end( ); ++iBlade)
	{
		// convert position to row/col index
		float fPercentAlongX = (iBlade->m_afPos[0] - m_afBoundingBox[0]) / (m_afBoundingBox[3] - m_afBoundingBox[0]);
		float fPercentAlongY = (iBlade->m_afPos[1] - m_afBoundingBox[1]) / (m_afBoundingBox[4] - m_afBoundingBox[1]);

		// clip values
		unsigned int uiCol = min(fPercentAlongX * m_nNumRegionCols, m_nNumRegionCols - 1);
		unsigned int uiRow = min(fPercentAlongY * m_nNumRegionRows, m_nNumRegionRows - 1);

		m_pRegions[GetRegionIndex(uiRow, uiCol)].m_vBlades.push_back(*iBlade);
	}

	// compute z extents (now that the blades are in)
	for (int i = 0; i < m_nNumRegions; ++i)
	{
		SRegion* pRegion = m_pRegions + i;

		pRegion->m_afMin[2] = FLT_MAX;
		pRegion->m_afMax[2] = -FLT_MAX;
		for (vector<SBlade>::iterator iBlade = pRegion->m_vBlades.begin( ); iBlade != pRegion->m_vBlades.end( ); ++iBlade)
		{
			pRegion->m_afMin[2] = min(pRegion->m_afMin[2], iBlade->m_afPos[2]);
			pRegion->m_afMax[2] = max(pRegion->m_afMax[2], iBlade->m_afPos[2] + iBlade->m_fSize);
		}

		pRegion->m_afCenter[0] = 0.5f * (pRegion->m_afMin[0] + pRegion->m_afMax[0]);
		pRegion->m_afCenter[1] = 0.5f * (pRegion->m_afMin[1] + pRegion->m_afMax[1]);
		pRegion->m_afCenter[2] = 0.5f * (pRegion->m_afMin[2] + pRegion->m_afMax[2]);

		// compute culling radius
		pRegion->m_fCullingRadius = 1.1f * sqrt(
			((pRegion->m_afMax[0] - pRegion->m_afCenter[0]) * (pRegion->m_afMax[0] - pRegion->m_afCenter[0])) +
			((pRegion->m_afMax[1] - pRegion->m_afCenter[1]) * (pRegion->m_afMax[1] - pRegion->m_afCenter[1])) +
			((pRegion->m_afMax[2] - pRegion->m_afCenter[2]) * (pRegion->m_afMax[2] - pRegion->m_afCenter[2])) 
			);
	}

	// collision detection
	if (fCollisionDistance > 0.0f)
	{
		fCollisionDistance *= fCollisionDistance;
		for (int nRow = 0; nRow < m_nNumRegionRows; ++nRow)
		{
			float fX = m_afBoundingBox[0];
			for (int nCol = 0; nCol < m_nNumRegionCols; ++nCol)
			{
				SRegion* pRegion = m_pRegions + GetRegionIndex(nRow, nCol);

				// check each blade against all other blades in the region
				for (DWORD i = 0; i < pRegion->m_vBlades.size( ); ++i)
				{
					float fX = pRegion->m_vBlades[i].m_afPos[0];
					float fY = pRegion->m_vBlades[i].m_afPos[1];
					bool bCollision = false;
					for (DWORD j = 0; j < pRegion->m_vBlades.size( ) && !bCollision; ++j)
					{
						if (i != j)
						{
							float fDistance = (fX - pRegion->m_vBlades[j].m_afPos[0]) * (fX - pRegion->m_vBlades[j].m_afPos[0]) + (fY - pRegion->m_vBlades[j].m_afPos[1]) * (fY - pRegion->m_vBlades[j].m_afPos[1]);
							if (fDistance < fCollisionDistance)
								bCollision = true;
						}
					}
					
					// delete the blade if necessary and adjust the main loop counter to compensate
					if (bCollision)
						pRegion->m_vBlades.erase(pRegion->m_vBlades.begin( ) + i--);
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
// CSpeedGrassRT::RotateAxisFromIdentity

__forceinline void CSpeedGrassRT::RotateAxisFromIdentity(D3DXMATRIX * pMat, const float & c_fAngle, const D3DXVECTOR3 & c_rv3Axis)
{
    float s = VectorSinD(c_fAngle);
    float c = VectorCosD(c_fAngle);
    float t = 1.0 - c;
	
    float x = c_rv3Axis.x;
    float y = c_rv3Axis.y;
    float z = c_rv3Axis.z;
	
    pMat->_11 = t * x * x + c;
    pMat->_12 = t * x * y + s * z;
    pMat->_13 = t * x * z - s * y;
    pMat->_21 = t * x * y - s * z;
    pMat->_22 = t * y * y + c;
    pMat->_23 = t * y * z + s * x;
    pMat->_31 = t * x * z + s * y;
    pMat->_32 = t * y * z - s * x;
    pMat->_33 = t * z * z + c;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::ComputeFrustum

void CSpeedGrassRT::ComputeFrustum(void)
{
	// setup useful vectors
//	CVec3 cCameraIn(-m_afCameraOut[0], -m_afCameraOut[1], -m_afCameraOut[2]);
//	CVec3 cCameraUp(m_afCameraUp[0], m_afCameraUp[1], m_afCameraUp[2]);
//	CVec3 cCameraRight(m_afCameraRight[0], m_afCameraRight[1], m_afCameraRight[2]);
//	CVec3 cCameraPos(m_afCameraPos[0], m_afCameraPos[1], m_afCameraPos[2]);
//	CVec3 cFarPoint = cCameraPos + cCameraIn * (m_fLodFarDistance + m_fLodTransitionLength);
	D3DXVECTOR3 cCameraIn(-m_afCameraOut[0], -m_afCameraOut[1], -m_afCameraOut[2]);
	D3DXVECTOR3 cCameraUp(m_afCameraUp[0], m_afCameraUp[1], m_afCameraUp[2]);
	D3DXVECTOR3 cCameraRight(m_afCameraRight[0], m_afCameraRight[1], m_afCameraRight[2]);
	D3DXVECTOR3 cCameraPos(m_afCameraPos[0], m_afCameraPos[1], m_afCameraPos[2]);
	D3DXVECTOR3 cFarPoint = cCameraPos + cCameraIn * (m_fLodFarDistance + m_fLodTransitionLength);

	// far plane
//	memcpy(m_afFrustumPlanes[0], cCameraIn, 3 * sizeof(float));
// 	m_afFrustumPlanes[0][3] = -(cCameraIn ^ cFarPoint); // operator^ is dot product
	m_afFrustumPlanes[0][0] = cCameraIn.x;
	m_afFrustumPlanes[0][1] = cCameraIn.y;
	m_afFrustumPlanes[0][2] = cCameraIn.z;
	m_afFrustumPlanes[0][3] = -D3DXVec3Dot(&cCameraIn, &cFarPoint); // operator^ is dot product

// 	CRotTransform cRotate(true);
	D3DXMATRIX cRotate;
	D3DXMatrixIdentity(&cRotate);
	D3DXVECTOR3 cNormal;

	// upper plane
//	cRotate.RotateAxisFromIdentity(VecRad2Deg(0.5f * m_fFieldOfView * m_fAspectRatio + c_fHalfPi) , cCameraRight);
//	CVec3 cNormal = cCameraIn * cRotate;
//	cNormal.Normalize( );
//	memcpy(m_afFrustumPlanes[1], cNormal, 3 * sizeof(float));
//	m_afFrustumPlanes[1][3] = -(cNormal ^ cCameraPos);

	// left plane
//	cRotate.RotateAxisFromIdentity(VecRad2Deg(0.5f * m_fFieldOfView + c_fHalfPi) , cCameraUp);
//	cNormal = cCameraIn * cRotate;
//	cNormal.Normalize( );
//	memcpy(m_afFrustumPlanes[2], cNormal, 3 * sizeof(float));
//	m_afFrustumPlanes[2][3] = -(cNormal ^ cCameraPos);

	// lower plane
//	cRotate.RotateAxisFromIdentity(-VecRad2Deg(0.5f * m_fFieldOfView * m_fAspectRatio + c_fHalfPi) , cCameraRight);
//	cNormal = cCameraIn * cRotate;
//	cNormal.Normalize( );
//	memcpy(m_afFrustumPlanes[3], cNormal, 3 * sizeof(float));
//	m_afFrustumPlanes[3][3] = -(cNormal ^ cCameraPos);

	// right plane
//	cRotate.RotateAxisFromIdentity(-VecRad2Deg(0.5f * m_fFieldOfView + c_fHalfPi) , cCameraUp);
//	cNormal = cCameraIn * cRotate;
//	cNormal.Normalize( );
//	memcpy(m_afFrustumPlanes[4], cNormal, 3 * sizeof(float));
//	m_afFrustumPlanes[4][3] = -(cNormal ^ cCameraPos);

	RotateAxisFromIdentity(&cRotate, D3DXToDegree(0.5f * m_fFieldOfView * m_fAspectRatio + c_fHalfPi), cCameraRight);
	D3DXVec3TransformCoord(&cNormal, &cCameraIn, &cRotate);
	D3DXVec3Normalize(&cNormal, &cNormal);
	m_afFrustumPlanes[1][0] = cNormal.x;
	m_afFrustumPlanes[1][1] = cNormal.y;
	m_afFrustumPlanes[1][2] = cNormal.z;
	m_afFrustumPlanes[1][3] = -D3DXVec3Dot(&cNormal, &cCameraPos); // operator^ is dot product

	RotateAxisFromIdentity(&cRotate, D3DXToDegree(0.5f * m_fFieldOfView + c_fHalfPi), cCameraUp);
	D3DXVec3TransformCoord(&cNormal, &cCameraIn, &cRotate);
	D3DXVec3Normalize(&cNormal, &cNormal);
	m_afFrustumPlanes[2][0] = cNormal.x;
	m_afFrustumPlanes[2][1] = cNormal.y;
	m_afFrustumPlanes[2][2] = cNormal.z;
	m_afFrustumPlanes[2][3] = -D3DXVec3Dot(&cNormal, &cCameraPos); // operator^ is dot product

	RotateAxisFromIdentity(&cRotate, -D3DXToDegree(0.5f * m_fFieldOfView * m_fAspectRatio + c_fHalfPi), cCameraRight);
	D3DXVec3TransformCoord(&cNormal, &cCameraIn, &cRotate);
	D3DXVec3Normalize(&cNormal, &cNormal);
	m_afFrustumPlanes[3][0] = cNormal.x;
	m_afFrustumPlanes[3][1] = cNormal.y;
	m_afFrustumPlanes[3][2] = cNormal.z;
	m_afFrustumPlanes[3][3] = -D3DXVec3Dot(&cNormal, &cCameraPos); // operator^ is dot product

	RotateAxisFromIdentity(&cRotate, -D3DXToDegree(0.5f * m_fFieldOfView + c_fHalfPi), cCameraUp);
	D3DXVec3TransformCoord(&cNormal, &cCameraIn, &cRotate);
	D3DXVec3Normalize(&cNormal, &cNormal);
	m_afFrustumPlanes[4][0] = cNormal.x;
	m_afFrustumPlanes[4][1] = cNormal.y;
	m_afFrustumPlanes[4][2] = cNormal.z;
	m_afFrustumPlanes[4][3] = -D3DXVec3Dot(&cNormal, &cCameraPos); // operator^ is dot product

	// frustum points
	float fFrustumHeight = (m_fLodFarDistance + m_fLodTransitionLength) * tanf(0.5f * m_fFieldOfView);
	float fFrustumWidth = (m_fLodFarDistance + m_fLodTransitionLength) * tanf(0.5f * m_fFieldOfView * m_fAspectRatio);

//	CVec3 acFrustum[5];
	D3DXVECTOR3 acFrustum[5];
	acFrustum[0] = cCameraPos;
	acFrustum[1] = cFarPoint + cCameraRight * fFrustumWidth + cCameraUp * fFrustumHeight;
	acFrustum[2] = cFarPoint - cCameraRight * fFrustumWidth + cCameraUp * fFrustumHeight;
	acFrustum[3] = cFarPoint - cCameraRight * fFrustumWidth - cCameraUp * fFrustumHeight;
	acFrustum[4] = cFarPoint + cCameraRight * fFrustumWidth - cCameraUp * fFrustumHeight;

	// find min/max (x,y) coordinates
	m_afFrustumMin[0] = m_afFrustumMin[1] = FLT_MAX;
	m_afFrustumMax[0] = m_afFrustumMax[1] = -FLT_MAX;
	for (int i = 0; i < 5; ++i)
	{
		m_afFrustumMin[0] = min(m_afFrustumMin[0], acFrustum[i][0]);
		m_afFrustumMax[0] = max(m_afFrustumMax[0], acFrustum[i][0]);
		m_afFrustumMin[1] = min(m_afFrustumMin[1], acFrustum[i][1]);
		m_afFrustumMax[1] = max(m_afFrustumMax[1], acFrustum[i][1]);
	}
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::ComputeUnitBillboard

void CSpeedGrassRT::ComputeUnitBillboard(void)
{
//	float fAzimuth = D3DXToDegree(atan2(-m_afCameraOut[1], -m_afCameraOut[0]));
	float fAzimuth = atan2(-m_afCameraOut[1], -m_afCameraOut[0]);

//    CRotTransform cTrans;
//    cTrans.RotateZ(fAzimuth);
//
//	static CVec3 afCorner1(0.0f, 0.5f, 1.0f);
//	static CVec3 afCorner2(0.0f, -0.5f, 1.0f);
//	static CVec3 afCorner3(0.0f, -0.5f, 0.0f);
//	static CVec3 afCorner4(0.0f, 0.5f, 0.0f);
//
//	CVec3 afNewCorner1 = afCorner1 * cTrans;
//	CVec3 afNewCorner2 = afCorner2 * cTrans;
//	CVec3 afNewCorner3 = afCorner3 * cTrans;
//	CVec3 afNewCorner4 = afCorner4 * cTrans;
//
//	memcpy(m_afUnitBillboard + 0, afNewCorner1.m_afData, 3 * sizeof(float));
//	memcpy(m_afUnitBillboard + 3, afNewCorner2.m_afData, 3 * sizeof(float));
//	memcpy(m_afUnitBillboard + 6, afNewCorner3.m_afData, 3 * sizeof(float));
//	memcpy(m_afUnitBillboard + 9, afNewCorner4.m_afData, 3 * sizeof(float));

	D3DXMATRIX cTrans;
	D3DXMatrixRotationZ(&cTrans, fAzimuth);

	static D3DXVECTOR3 afCorner1(0.0f, 0.5f, 1.0f);
	static D3DXVECTOR3 afCorner2(0.0f, -0.5f, 1.0f);
	static D3DXVECTOR3 afCorner3(0.0f, -0.5f, 0.0f);
	static D3DXVECTOR3 afCorner4(0.0f, 0.5f, 0.0f);

	D3DXVECTOR3 afNewCorner1;
	D3DXVECTOR3 afNewCorner2;
	D3DXVECTOR3 afNewCorner3;
	D3DXVECTOR3 afNewCorner4;

	D3DXVec3TransformCoord(&afNewCorner1, &afCorner1, &cTrans);
	D3DXVec3TransformCoord(&afNewCorner2, &afCorner2, &cTrans);
	D3DXVec3TransformCoord(&afNewCorner3, &afCorner3, &cTrans);
	D3DXVec3TransformCoord(&afNewCorner4, &afCorner4, &cTrans);

	m_afUnitBillboard[0] = afNewCorner1.x;
	m_afUnitBillboard[1] = afNewCorner1.y;
	m_afUnitBillboard[2] = afNewCorner1.z;
	m_afUnitBillboard[3] = afNewCorner2.x;
	m_afUnitBillboard[4] = afNewCorner2.y;
	m_afUnitBillboard[5] = afNewCorner2.z;
	m_afUnitBillboard[6] = afNewCorner3.x;
	m_afUnitBillboard[7] = afNewCorner3.y;
	m_afUnitBillboard[8] = afNewCorner3.z;
	m_afUnitBillboard[9] = afNewCorner4.x;
	m_afUnitBillboard[10] = afNewCorner4.y;
	m_afUnitBillboard[11] = afNewCorner4.z;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::ConvertCoordsToCell

void CSpeedGrassRT::ConvertCoordsToCell(const float* pCoords, int* pGridCoords) const
{
    float fPercentAlongX = (pCoords[0] - m_afBoundingBox[0]) / (m_afBoundingBox[3] - m_afBoundingBox[0]);
    float fPercentAlongY = (pCoords[1] - m_afBoundingBox[1]) / (m_afBoundingBox[4] - m_afBoundingBox[1]);

    if (fPercentAlongX < 0.0f)
		pGridCoords[0] = -1;
	else if (fPercentAlongX > 1.0f)
		pGridCoords[0] = m_nNumRegionCols;
	else
	    pGridCoords[0] = fPercentAlongX * m_nNumRegionCols;

	if (fPercentAlongY < 0.0f)
		pGridCoords[1] = -1;
	else if (fPercentAlongY > 1.0f)
		pGridCoords[1] = m_nNumRegionRows;
	else
	    pGridCoords[1] = fPercentAlongY * m_nNumRegionRows;
}


///////////////////////////////////////////////////////////////////////  
//	CSpeedGrassRT::OutsideFrustum

__forceinline bool CSpeedGrassRT::OutsideFrustum(CSpeedGrassRT::SRegion* pRegion)
{
	bool bOutside = false;

	for (int i = 0; i < 5 && !bOutside; ++i)
		if (m_afFrustumPlanes[i][0] * pRegion->m_afCenter[0] + 
			m_afFrustumPlanes[i][1] * pRegion->m_afCenter[1] + 
			m_afFrustumPlanes[i][2] * pRegion->m_afCenter[2] +
			m_afFrustumPlanes[i][3] > pRegion->m_fCullingRadius)
			bOutside = true;

	return bOutside;
}

#endif // USE_SPEEDGRASS