/******************************************************************************

 @File         PVRTGeometry.cpp

 @Title        PVRTGeometry

 @Version      

 @Copyright    Copyright (C)  Imagination Technologies Limited.

 @Platform     Independant

 @Description  Code to affect triangle mesh geometry.

******************************************************************************/

/*****************************************************************************
  For each vertex with only one free triangle
    Start collecting triangles from there
	  Add the triangle which gives the highest triangles/vertex number (extra tris usually come for free)
	  When full, test against current best
	    If markedly better tri/vtx, take new block
		If close-enough to prev tri/vtx, take block which closes the highest number of edges (and opens fewest)
	  If not quite full, goto 1 to continue filling block
  If no block has been found, start at any free triangle and use resulting block
  Copy block to output, empty it and goto 1.
*****************************************************************************/

/****************************************************************************
** Build options
****************************************************************************/
#undef PVRTRISORT_ENABLE_VERIFY_RESULTS

/****************************************************************************
** Includes
****************************************************************************/
#include <vector>
#include <math.h>

#include "PVRTGeometry.h"

#ifdef PVRTRISORT_ENABLE_VERIFY_RESULTS
#include "PvrVGPBlockTest.h"
#endif

#include "PVRTGlobal.h"
#include "PVRTContext.h"
/****************************************************************************
** Structures
****************************************************************************/

struct SVtx;

/****************************************************************************
@Function 		SEdg
@Description	Information about an "edge" - the shared boundary between two triangles
****************************************************************************/
struct SEdg {
	const SVtx	*psVtx[2];		// Identify the edge by the two vertices it joins
	int			nTriNumFree;	// Number of triangle using this edge
};

/****************************************************************************
@Function 		STri
@Description	Information about a triangle
****************************************************************************/
struct STri {
	const PVRTGEOMETRY_IDX	*pwIdx;			// Vertex indices forming this triangle
	SEdg					*psEdg[3];		// Pointer to the three triangle edges
	bool					bUsed;
};

/****************************************************************************
@Function 	SVtx
@Description	Information about a vertex
****************************************************************************/
struct SVtx {
	STri	**psTri;		// Allocated array of pointers to the triangles sharing this vertex
	int		nTriNumTot;		// Length of the above array
	int		nTriNumFree;	// Number of triangles unused in the above array
	SVtx	**ppMeshPos;	// Position in VtxByMesh list
};

/****************************************************************************
@Function 		SMesh
@Description	Information about a mesh
****************************************************************************/
struct SMesh {
	SVtx	**ppVtx;
	int		nVtxNum;
};

/****************************************************************************
@Function 		CObject
@Description	Information about an object (i.e. collection of mesh's to form
				a single entity)
****************************************************************************/
class CObject {
public:
	STri	*m_pTri;		// Array of all the triangles in the mesh
	SEdg	*m_pEdg;		// Array of all the edges in the mesh
	SVtx	*m_pVtx;		// Array of all the vertices in a mesh

	int		m_nTriNumFree;

	std::vector<SMesh> *m_pvMesh;
	std::vector<SMesh> m_vMeshLg;

protected:
	int		m_nVtxTot;		// Total vertices in the object
	int		m_nEdgTot;		// Total edges in the object
	int		m_nTriTot;		// Total triangles in the object

	int		m_nVtxLimit;	// Maximum number of vertices a block can contain
	int		m_nTriLimit;	// Maximum number of triangles a block can contain

	SVtx	**m_ppVtxByMesh;

public:
	CObject(
		const PVRTGEOMETRY_IDX	* const pwIdx,
		const int				nVtxTot,
		const int				nTriTot,
		const int				nVtxLimit,
		const int				nTriLimit);

	~CObject();

	int GetVertexCount() const;
	int GetTriangleCount() const;
		void SplitMesh(
		SMesh		* const pMesh,
		const int	nVtxNum,
		SVtx		** const ppVtx);

	void ResizeMesh(
		const int	nVtxNum,
		SVtx		** const ppVtx);

protected:
	SEdg *BuildEdgeList(
		const SVtx * const pVtx0,
		const SVtx * const pVtx1);

	void CreateMeshList();
};

/****************************************************************************
@Function 		CBlockOption
@Description	A possible group of polygons to use
****************************************************************************/
struct CBlockOption {
protected:
	struct SEdgeDelta {
		const SEdg	*pEdg;
		int			nRefCnt;
	};

public:
	int			nVtxNum;			// Number of vertices in the block
	int			nEdgNum;			// Number of edges in the block
	int			nTriNum;			// Number of triangles in the block

	SVtx		**psVtx;			// Pointers to vertices
protected:
	SEdgeDelta	*psEdgeDelta;
	STri		**psTri;			// Pointers to triangles

	int			m_nVtxLimit;		// Maximum number of vertices a block can contain
	int			m_nTriLimit;		// Maximum number of triangles a block can contain

public:
	~CBlockOption();

	void Init(
		const int	nVtxLimit,
		const int	nTriLimit);
	void Copy(const CBlockOption * const pSrc);

	void Clear();

	void Output(
		PVRTGEOMETRY_IDX	* const pwOut,
		int					* const pnVtxCnt,
		int					* const pnTriCnt,
		const CObject		* const pOb) const;

	bool UsingVertex(const SVtx * const pVtx) const;
	bool Contains(const STri * const pTri) const;

	bool IsEmpty() const;
	bool IsFull() const;

	void AddVertex(SVtx * const pVtx);
	void AddVertexCheckDup(SVtx * const pVtx);

	void AddTriangleCheckDup(STri * const pTri);

	void AddEdgeCheckDup(const SEdg * const pEdg);

	void AddTriangle(STri * const pTri);

	void AddOneTriangle(
		STri		* const pTri,
		const CObject	* const pOb);

	int GetClosedEdgeDelta() const;
	bool IsBetterThan(const CBlockOption * const pCmp) const;

	void Add(
		const CBlockOption	* const pSrc,
		const CObject		* const pOb);

	void Add(
		const SMesh		* const pMesh);
};

/****************************************************************************
@Function 		CBlock
@Description	A model of a HW block (triangles and vertices)
****************************************************************************/
class CBlock {
protected:
	CBlockOption	m_sOpt, m_sOptBest;

	int				m_nVtxLimit;		// Maximum number of vertices a block can contain
	int				m_nTriLimit;		// Maximum number of triangles a block can contain

	CBlockOption	m_sJob0, m_sJob1;	// Workspace: to find the best triangle to add

public:
	CBlock(
		const int	nBufferVtxLimit,
		const int	nBufferTriLimit);

	void Clear();

	bool FillFrom(
		SMesh	* const pMesh,
		SVtx	* const pVtx,
		CObject	* const pOb);

	int Fill(
		CObject	* const pOb);

	void Output(
		PVRTGEOMETRY_IDX	* const pwOut,
		int					* const pnVtxCnt,
		int					* const pnTriCnt,
		const CObject		* const pOb) const;

protected:
	bool AddBestTrianglesAppraise(
		CBlockOption	* const pJob,
		const CObject		* const pOb,
		const STri		* const pTriAppraise);

	void AddBestTriangles(CObject * const pOb);
};

/****************************************************************************
** Local function prototypes
****************************************************************************/

/****************************************************************************
@Function		CObject
@Input			pwIdx			Array of indices
@Input			nVrxTot			Total number of vertices
@Input			nTriTot			Total number of triangles
@Input			nVtxLimit		Max number of vertices a block can contain
@Input			nTriLimit		Max number of triangles a block can contain
@Description	The class's constructor.
****************************************************************************/
CObject::CObject(
	const PVRTGEOMETRY_IDX	* const pwIdx,
	const int				nVtxTot,
	const int				nTriTot,
	const int				nVtxLimit,
	const int				nTriLimit)
{
	int		i;
	SVtx	*pVtx0, *pVtx1, *pVtx2;

	m_nVtxLimit		= nVtxLimit;
	m_nTriLimit		= nTriLimit;

	m_pvMesh = new std::vector<SMesh>[nVtxLimit-2];
	_ASSERT(m_pvMesh);

	m_ppVtxByMesh = (SVtx**)calloc(nVtxTot, sizeof(*m_ppVtxByMesh));
	_ASSERT(m_ppVtxByMesh);

	m_nVtxTot = nVtxTot;
	m_nEdgTot = 0;
	m_nTriTot = nTriTot;

	m_nTriNumFree = m_nTriTot;

	m_pTri = (STri*)calloc(nTriTot, sizeof(*m_pTri));
	_ASSERT(m_pTri);

	m_pEdg = (SEdg*)calloc(nTriTot*3, sizeof(*m_pEdg));	// Allocate the maximum possible number of edges, though it should be far fewer than this
	_ASSERT(m_pEdg);

	m_pVtx = (SVtx*)calloc(nVtxTot, sizeof(*m_pVtx));
	_ASSERT(m_pVtx);

	// Run through triangles...
	for(i = 0; i < nTriTot; ++i) {
		pVtx0 = &m_pVtx[pwIdx[3*i+0]];
		pVtx1 = &m_pVtx[pwIdx[3*i+1]];
		pVtx2 = &m_pVtx[pwIdx[3*i+2]];

		// Mark each vertex for the number of times it's referenced
		++pVtx0->nTriNumFree;
		++pVtx1->nTriNumFree;
		++pVtx2->nTriNumFree;

		// Build the edge list
		m_pTri[i].psEdg[0] = BuildEdgeList(pVtx0, pVtx1);
		m_pTri[i].psEdg[1] = BuildEdgeList(pVtx1, pVtx2);
		m_pTri[i].psEdg[2] = BuildEdgeList(pVtx2, pVtx0);
	}

	// Run through vertices, creating enough space for pointers to each triangle using this vertex
	for(i = 0; i < nVtxTot; ++i)
		m_pVtx[i].psTri = (STri**)calloc(m_pVtx[i].nTriNumFree, sizeof(*m_pVtx[i].psTri));

	// Run through triangles, marking each vertex used with a pointer to this tri
	for(i = 0; i < nTriTot; ++i) {
		pVtx0 = &m_pVtx[pwIdx[3*i+0]];
		pVtx1 = &m_pVtx[pwIdx[3*i+1]];
		pVtx2 = &m_pVtx[pwIdx[3*i+2]];

		pVtx0->psTri[pVtx0->nTriNumTot++] = &m_pTri[i];
		pVtx1->psTri[pVtx1->nTriNumTot++] = &m_pTri[i];
		pVtx2->psTri[pVtx2->nTriNumTot++] = &m_pTri[i];

		// Give each triangle a pointer to its indices
		m_pTri[i].pwIdx = &pwIdx[3*i];
	}

#ifdef _DEBUG
	for(i = 0; i < nVtxTot; ++i) {
		_ASSERTE(m_pVtx[i].nTriNumFree == m_pVtx[i].nTriNumTot);
	}
#endif

	CreateMeshList();
}

/****************************************************************************
@Function 		~CObject
@Description	Destructor
****************************************************************************/
CObject::~CObject()
{
	_ASSERT(m_nTriNumFree == 0);

	while(m_nVtxTot) {
		--m_nVtxTot;
		FREE(m_pVtx[m_nVtxTot].psTri);
		_ASSERTE(m_pVtx[m_nVtxTot].nTriNumFree == 0);
		_ASSERTE(m_pVtx[m_nVtxTot].ppMeshPos);
	}

#ifdef _DEBUG
	while(m_nEdgTot) {
		--m_nEdgTot;
		_ASSERTE(m_pEdg[m_nEdgTot].nTriNumFree == 0);
	}
	while(m_nTriTot) {
		--m_nTriTot;
		_ASSERTE(m_pTri[m_nTriTot].bUsed);
	}
#endif

	FREE(m_pTri);
	FREE(m_pEdg);
	FREE(m_pVtx);

	delete [] m_pvMesh;
	FREE(m_ppVtxByMesh);
}

/****************************************************************************
@Function 		GetVertexCount
@Return			int
@Description	Return the vertex count
****************************************************************************/
int CObject::GetVertexCount() const
{
	return m_nVtxTot;
}

/****************************************************************************
@Function 		GetTriangleCount
@Return			int
@Description	Return the triangle count
****************************************************************************/
int CObject::GetTriangleCount() const
{
	return m_nTriTot;
}

/****************************************************************************
@Function 		BuildEdgeList
@Input			pVtx0			Edge 0
@Input			pVtx1			Edge 1
@Return			SEdg*		
@Description	If the vertices that have been passed in are already used by an edge,
				the number of triangles sharing the edge is increased by one and a
				pointer to the edge is returned. If the edge is not already in the
				list, the edge is added to the list.
****************************************************************************/
SEdg *CObject::BuildEdgeList(
	const SVtx * const pVtx0,
	const SVtx * const pVtx1)
{
	SEdg		*pEdg;
	const SVtx	*pVtxL, *pVtxH;
	int			i;

	pVtxL = pVtx0 < pVtx1 ? pVtx0 : pVtx1;
	pVtxH = pVtx0 > pVtx1 ? pVtx0 : pVtx1;

	// Do nothing if the edge already exists
	i = m_nEdgTot;
	while(i) {
		--i;

		pEdg = &m_pEdg[i];
		if(pEdg->psVtx[0] == pVtxL && pEdg->psVtx[1] == pVtxH)
		{
			++pEdg->nTriNumFree;
			return pEdg;
		}
	}

	// Add the new edge
	_ASSERT(m_nEdgTot < m_nTriTot*3);
	pEdg				= &m_pEdg[m_nEdgTot++];
	pEdg->psVtx[0]		= pVtxL;
	pEdg->psVtx[1]		= pVtxH;
	pEdg->nTriNumFree	= 1;

	return pEdg;
}

/****************************************************************************
@Function 		CreateMeshList
@Description	Creates the mesh list
****************************************************************************/
void CObject::CreateMeshList()
{
	SVtx	**ppR, **ppW, *pVtx;
	STri	*pTri;
	int		i, j, k;
	SMesh	sMesh;
	int		nMeshCnt;

	nMeshCnt = 0;

	ppR = m_ppVtxByMesh;
	ppW = m_ppVtxByMesh;

	for(i = 0; i < m_nVtxTot; ++i) {
		pVtx = &m_pVtx[i];

		if(pVtx->ppMeshPos) {
			_ASSERT(pVtx->ppMeshPos < ppW);
			continue;
		}

		++nMeshCnt;
		sMesh.ppVtx = ppW;

		*ppW			= pVtx;
		pVtx->ppMeshPos	= ppW;
		++ppW;

		do {
			// Add all the vertices of all the triangles of *ppR to the list - unless they're already in there
			for(j = 0; j < (*ppR)->nTriNumTot; ++j) {
				pTri = (*ppR)->psTri[j];

				for(k = 0; k < 3; ++k) {
					pVtx = &m_pVtx[pTri->pwIdx[k]];

					if(pVtx->ppMeshPos) {
						_ASSERT(pVtx->ppMeshPos < ppW);
						continue;
					}

					*ppW			= pVtx;
					pVtx->ppMeshPos	= ppW;
					++ppW;
				}
			}

			++ppR;
		} while(ppR != ppW);

		sMesh.nVtxNum = (int)(ppR - sMesh.ppVtx);
//		_RPT2(_CRT_WARN, "CreateMeshList() mesh %d %dvtx\n", nMeshCnt, sMesh.nVtxNum);
		if(sMesh.nVtxNum >= 3)
		{
			if(sMesh.nVtxNum >= m_nVtxLimit)
				m_vMeshLg.push_back(sMesh);
			else
				m_pvMesh[sMesh.nVtxNum-3].push_back(sMesh);
		}
		else
		{
			/*
				Vertex is not used by any triangles; this may be because we're
				optimising a subset of the mesh (e.g. for bone batching).
			*/
			_ASSERT(sMesh.nVtxNum == 1);
		}
	}

	_ASSERT(ppR == &m_ppVtxByMesh[m_nVtxTot]);
	_ASSERT(ppW == &m_ppVtxByMesh[m_nVtxTot]);
//	_RPT1(_CRT_WARN, "CreateMeshList() %d meshes\n", nMeshCnt);

#ifdef _DEBUG
/*	for(i = 0; i < m_nVtxLimit-2; ++i)
		if(m_pvMesh[i].size())
			_RPT2(_CRT_WARN, "%d:%d ", i+3, m_pvMesh[i].size());
	_RPT1(_CRT_WARN, "lg:%d\n", m_vMeshLg.size());*/
#endif
}

/****************************************************************************
@Function 		SplitMesh
@Input			pMesh			Pointer to mesh data
@Input			nVtxNum			Number of vertices in the mesh?
@Output			ppVtx			Array of vertices
@Description	Note: Ask Aaron
****************************************************************************/
void CObject::SplitMesh(
	SMesh		* const pMesh,
	const int	nVtxNum,
	SVtx		** const ppVtx)
{
	SVtx	*pTmp;
	int		i;
	SMesh	sNew;

	_ASSERT(nVtxNum);

	for(i = 0; i < nVtxNum; ++i) {
		pTmp					= pMesh->ppVtx[i];		// Keep a record of the old vtx that's already here

		pMesh->ppVtx[i]			= ppVtx[i];				// Move the new vtx into place
		*ppVtx[i]->ppMeshPos	= pTmp;					// Move the old vtx into place

		pTmp->ppMeshPos			= ppVtx[i]->ppMeshPos;	// Tell the old vtx where it is now
		ppVtx[i]->ppMeshPos		= &pMesh->ppVtx[i];		// Tell the new vtx where it is now

		_ASSERT(pMesh->ppVtx[i]->nTriNumFree);
	}

	sNew.nVtxNum	= nVtxNum;
	sNew.ppVtx		= pMesh->ppVtx;
	m_pvMesh[nVtxNum-3].push_back(sNew);

	pMesh->ppVtx	= &pMesh->ppVtx[nVtxNum];
	pMesh->nVtxNum	-= nVtxNum;
	if(pMesh->nVtxNum < m_nVtxLimit) {
		ResizeMesh(pMesh->nVtxNum, pMesh->ppVtx);
		m_vMeshLg.pop_back();
#ifdef _DEBUG
/*	} else {
		for(i = 0; i < m_nVtxLimit-2; ++i)
			if(m_pvMesh[i].size())
				_RPT2(_CRT_WARN, "%d:%d ", i+3, m_pvMesh[i].size());
		_RPT1(_CRT_WARN, "lg:%d\n", m_vMeshLg.size());*/
#endif
	}
}

/****************************************************************************
@Function 		ResizeMesh
@Input			nVtxNum			The size of the array of vertices being passed in
@Input			ppVtx			Array of vertices
@Description	Note: Ask Aaron
****************************************************************************/
void CObject::ResizeMesh(
	const int	nVtxNum,
	SVtx		** const ppVtx)
{
	SVtx	**ppR, **ppW;
	SMesh	sNew;
	int		i;

	ppR = ppVtx;
	ppW = ppVtx;

	// Make a list of vertices that have unused triangles in their array of triangles
	for(i = 0; i < nVtxNum; ++i) {
		if((*ppR)->nTriNumFree) {
			(*ppW) = (*ppR);
			++ppW;
		}
		++ppR;
	}

	sNew.nVtxNum = (int)(ppW - ppVtx);
	_ASSERT(sNew.nVtxNum <= nVtxNum);

	// If any mesh still exists, add it to the relevant list
	if(sNew.nVtxNum) {
		_ASSERT(sNew.nVtxNum >= 3);
		_ASSERT(sNew.nVtxNum < m_nVtxLimit);

		sNew.ppVtx = ppVtx;
		m_pvMesh[sNew.nVtxNum-3].push_back(sNew);
	}

#ifdef _DEBUG
/*	for(i = 0; i < m_nVtxLimit-2; ++i)
		if(m_pvMesh[i].size())
			_RPT2(_CRT_WARN, "%d:%d ", i+3, m_pvMesh[i].size());
	_RPT1(_CRT_WARN, "lg:%d\n", m_vMeshLg.size());*/
#endif
}

/****************************************************************************
@Function 		~CBlockOption
@Description	Default destructor
****************************************************************************/
CBlockOption::~CBlockOption()
{
	FREE(psVtx);
	FREE(psTri);
	FREE(psEdgeDelta);
}

/****************************************************************************
@Function 		Init
@Input			nVertexLimit		The maximum number of vertices a block can contain
@Input			nTriLimit			The maximum number of triangles a block can contain
@Description	Initialises the class
****************************************************************************/
void CBlockOption::Init(
	const int	nVtxLimit,
	const int	nTriLimit)
{
	m_nVtxLimit = nVtxLimit;
	m_nTriLimit = nTriLimit;

	psVtx		= (SVtx**)malloc(nVtxLimit * sizeof(*psVtx));
	psTri		= (STri**)malloc(nTriLimit * sizeof(*psTri));
	psEdgeDelta	= (SEdgeDelta*)malloc(3 * nTriLimit * sizeof(*psEdgeDelta));
}

/****************************************************************************
@Function 		Copy
@Input			pSrc				Pointer to the source data
@Description	Overwrites the data in the current instance with the data from
				the input CBlockOption.
****************************************************************************/
void CBlockOption::Copy(const CBlockOption * const pSrc)
{
	nVtxNum = pSrc->nVtxNum;
	nEdgNum = pSrc->nEdgNum;
	nTriNum = pSrc->nTriNum;

	memcpy(psVtx, pSrc->psVtx, nVtxNum * sizeof(*psVtx));
	memcpy(psEdgeDelta, pSrc->psEdgeDelta, nEdgNum * sizeof(*psEdgeDelta));
	memcpy(psTri, pSrc->psTri, nTriNum * sizeof(*psTri));
}

/****************************************************************************
@Function 		Clear
@Description	Sets the value of the number of vertices, edges and triangles
				to zero.
****************************************************************************/
void CBlockOption::Clear()
{
	nVtxNum = 0;
	nEdgNum = 0;
	nTriNum = 0;
}

/****************************************************************************
@Function 		Output
@Output			pwOut			Index output
@Output			pnVtxCnt		Vertex count
@Output			pnTriCnt		Triangle count
@Modified		pOb				Pointer to an object
@Description	Outputs key information about the instance of CBlockOption
****************************************************************************/
void CBlockOption::Output(
	PVRTGEOMETRY_IDX	* const pwOut,
	int					* const pnVtxCnt,
	int					* const pnTriCnt,
	const CObject		* const pOb) const
{
	STri	*pTri;
	int		i, j;

	for(i = 0; i < nTriNum; ++i) {
		pTri = psTri[i];

		_ASSERT(!pTri->bUsed);

		for(j = 0; j < 3; ++j) {
			_ASSERT(pOb->m_pVtx[pTri->pwIdx[j]].nTriNumFree > 0);
			_ASSERT(pTri->psEdg[j]->nTriNumFree > 0);

			--pOb->m_pVtx[pTri->pwIdx[j]].nTriNumFree;
			--pTri->psEdg[j]->nTriNumFree;

			_ASSERT(pOb->m_pVtx[pTri->pwIdx[j]].nTriNumFree >= 0);
			_ASSERT(pTri->psEdg[j]->nTriNumFree >= 0);
		}

		pTri->bUsed = true;

		// Copy indices into output
		memcpy(&pwOut[3*i], pTri->pwIdx, 3 * sizeof(*pTri->pwIdx));
	}

	*pnVtxCnt = nVtxNum;
	*pnTriCnt = nTriNum;
}

/****************************************************************************
@Function 		UsingVertex
@Input			pVtx			Vertex to compare
@Return			bool			True on success
@Description	Returns true if the supplied vertex is already being used
				in the block option.
****************************************************************************/
bool CBlockOption::UsingVertex(
	const SVtx		* const pVtx) const
{
	int i;

	i = nVtxNum;
	while(i) {
		--i;

		if(psVtx[i] == pVtx)
			return true;
	}

	return false;
}

/****************************************************************************
@Function 		Contains
@Input			pVtx			Triangle to compare
@Return			bool			True on success
@Description	Returns true if the supplied triangle is already being used
				in the block option.
****************************************************************************/
bool CBlockOption::Contains(const STri * const pTri) const
{
	int i;

	i = nTriNum;
	while(i) {
		--i;

		if(psTri[i] == pTri)
			return true;
	}

	return false;
}

/****************************************************************************
@Function 		IsEmpty
@Return			bool			True if the block option is empty
@Description	Returns true if the block option is empty.
****************************************************************************/
bool CBlockOption::IsEmpty() const
{
	return !(nVtxNum + nEdgNum + nTriNum);
}

/****************************************************************************
@Function 		IsFull
@Return			bool			True if the block option is full
@Description	Returns true if the block option is full.
****************************************************************************/
bool CBlockOption::IsFull() const
{
	return  (m_nVtxLimit - nVtxNum) < 3 || nTriNum == m_nTriLimit;
}

/****************************************************************************
@Function 		AddVertex
@Input			pVtx			Vertex to add
@Description	Providing the current number of vertices is less than the
				maximum, the input vertex is added to the end of the array.
****************************************************************************/
void CBlockOption::AddVertex(SVtx * const pVtx)
{
	_ASSERT(nVtxNum < m_nVtxLimit);
	psVtx[nVtxNum++] = pVtx;
}

/****************************************************************************
@Function 		AddVertexCheckDup
@Input			pVtx			Vertex to add
@Description	Checks that the input vertex is not already contained in the
				vertex array. If it is new, it is added to the array.
****************************************************************************/
void CBlockOption::AddVertexCheckDup(SVtx * const pVtx)
{
	int i;

	for(i = 0; i < nVtxNum; ++i)
		if(psVtx[i] == pVtx)
			return;

	AddVertex(pVtx);
}

/****************************************************************************
@Function 		AddTriangleCheckDup
@Input			pTri			Triangle to add
@Description	Checks that the input triangle is not already contained in the
				triangle array. If it is new, it is added to the array.
****************************************************************************/
void CBlockOption::AddTriangleCheckDup(STri * const pTri)
{
	int i;

	for(i = 0; i < nTriNum; ++i)
		if(psTri[i] == pTri)
			return;

	_ASSERT(nTriNum < m_nTriLimit);
	psTri[nTriNum++] = pTri;
}

/****************************************************************************
@Function 		AddEdgeCheckDup
@Input			pEdg			Edge to add
@Description	Checks that the input edge is not already contained in the
				edge array. If it is new, it is added to the array.
****************************************************************************/
void CBlockOption::AddEdgeCheckDup(const SEdg * const pEdg)
{
	int i;

	for(i = 0; i < nEdgNum; ++i) {
		if(psEdgeDelta[i].pEdg == pEdg) {
			++psEdgeDelta[i].nRefCnt;
			return;
		}
	}

	_ASSERT(nEdgNum < 3*m_nTriLimit);
	psEdgeDelta[nEdgNum].pEdg		= pEdg;
	psEdgeDelta[nEdgNum].nRefCnt	= 1;
	++nEdgNum;
}

/****************************************************************************
@Function 		AddTriangle
@Input			pTri			Triangle to add
@Description	Providing the current number of triangles is less than the
				maximum, the input triangle is added to the end of the array.
				Once this has been done, the array of edges is updated.
****************************************************************************/
// TODO: if this is only used to add fresh triangles, all edges must be added
void CBlockOption::AddTriangle(STri * const pTri)
{
	int i;

	_ASSERT(nTriNum < m_nTriLimit);
	psTri[nTriNum++] = pTri;

	// Keep a count of edges and the number of tris which share them
	for(i = 0; i < 3; ++i)
		AddEdgeCheckDup(pTri->psEdg[i]);
}

/****************************************************************************
@Function 		AddOneTriangle
@Input			pTri			Triangle to add
@Input			pOb				Object to copy vertices from
@Description	Calls the AddTriangle function.
				Once this has been done, the array of vertices is updated.
****************************************************************************/
// TODO: if this is only called to add a fresh start triangle, all vertices must be added
void CBlockOption::AddOneTriangle(
	STri			* const pTri,
	const CObject	* const pOb)
{
	int		i;

	// Add the triangle to the block
	AddTriangle(pTri);

	// Add the vertices to the block
	for(i = 0; i < 3; ++i)
		AddVertexCheckDup(&pOb->m_pVtx[pTri->pwIdx[i]]);
}

/****************************************************************************
@Function 		GetClosedEdgeDelta
@Return			int					The delta value of closed edges
@Description	This method returns a value that represents the average state of
				the edges. If the value is greater than zero, the majority of
				edges are closed. If the value is less than zero, the majority
				of edges are open.
****************************************************************************/
int CBlockOption::GetClosedEdgeDelta() const
{
	int i, nDelta;

	nDelta = 0;
	for(i = 0; i < nEdgNum; ++i) {
		_ASSERT(psEdgeDelta[i].pEdg->nTriNumFree >= psEdgeDelta[i].nRefCnt);

		// Check how many tris will use the edge if these are taken away
		switch(psEdgeDelta[i].pEdg->nTriNumFree - psEdgeDelta[i].nRefCnt) {
			case 0:
				// If the edge was open, and is now closed, that's good
				if(psEdgeDelta[i].pEdg->nTriNumFree == 1)
					++nDelta;
				break;
			case 1:
				// if the edge is now open, that's bad
				--nDelta;
				break;
		}
	}

	return nDelta;
}

/****************************************************************************
@Function 		IsBetterThan
@Input			pCmp				The block option to compare with
@Return			bool				True if the current block option is best
@Description	Returns true if the current block option is better than the
				block option that has been passed in. Otherwise, it returns false.
****************************************************************************/
bool CBlockOption::IsBetterThan(const CBlockOption * const pCmp) const
{
	float	fWorth0, fWorth1;
	int		nClosed0, nClosed1;

	// Check "worth" - TrisAdded/VtxAdded
	fWorth0 = (float)nTriNum / (float)nVtxNum;
	fWorth1 = (float)pCmp->nTriNum / (float)pCmp->nVtxNum;

	nClosed0 = GetClosedEdgeDelta();
	nClosed1 = pCmp->GetClosedEdgeDelta();

	if(fabsf(fWorth0 - fWorth1) > 0.1f) {
		return fWorth0 > fWorth1;
	} else if(nClosed0 != nClosed1) {
		return nClosed0 > nClosed1;
	} else {
		return nTriNum > pCmp->nTriNum;
	}
}

/****************************************************************************
@Function 		Add
@Input			pSrc			The block option to add
@Input			pOb				Object to use vertices from
@Description	Add's the input vertex and triangle data to the current block option
****************************************************************************/
void CBlockOption::Add(
	const CBlockOption	* const pSrc,
	const CObject		* const pOb)
{
	PVRT_UNREFERENCED_PARAMETER(pOb);

	int i;

	// Add vertices from job to block
	for(i = 0; i < pSrc->nVtxNum; ++i)
		AddVertexCheckDup(pSrc->psVtx[i]);

	// Add triangles from job to block
	for(i = 0; i < pSrc->nTriNum; ++i)
		AddTriangle(pSrc->psTri[i]);
}

/****************************************************************************
@Function 		Add
@Input			pMesh			The mesh to add
@Description	Add's the input mesh to the current block option
****************************************************************************/
void CBlockOption::Add(
	const SMesh		* const pMesh)
{
	int i, j;
	SVtx	*pVtx;

	for(i = 0; i < pMesh->nVtxNum; ++i) {
		pVtx = pMesh->ppVtx[i];

		AddVertexCheckDup(pVtx);

		for(j = 0; j < pVtx->nTriNumTot; ++j) {
			if(!pVtx->psTri[j]->bUsed)
				AddTriangleCheckDup(pVtx->psTri[j]);
		}
	}
}

/****************************************************************************
@Function 		CBlock
@Description	Default constructor
****************************************************************************/
CBlock::CBlock(
	const int	nBufferVtxLimit,
	const int	nBufferTriLimit)
{
	m_nVtxLimit = nBufferVtxLimit;
	m_nTriLimit = nBufferTriLimit;

	m_sOpt.Init(m_nVtxLimit, m_nTriLimit);
	m_sOptBest.Init(m_nVtxLimit, m_nTriLimit);

	// Intialise "job" blocks
	m_sJob0.Init(3, m_nTriLimit);
	m_sJob1.Init(3, m_nTriLimit);
}

/****************************************************************************
@Function 		Clear
@Description	Clears the current and best block options
****************************************************************************/
void CBlock::Clear()
{
	m_sOpt.Clear();
	m_sOptBest.Clear();
}

/****************************************************************************
@Function 		Output
@Output			pwOut			Index output
@Output			pnVtxCnt		Vertex count
@Output			pnTriCnt		Triangle count
@Modified		pOb				Pointer to an object
@Description	Outputs key information about the instance of CBlockOption
****************************************************************************/
void CBlock::Output(
	PVRTGEOMETRY_IDX	* const pwOut,
	int					* const pnVtxCnt,
	int					* const pnTriCnt,
	const CObject		* const pOb) const
{
	m_sOptBest.Output(pwOut, pnVtxCnt, pnTriCnt, pOb);
}

/****************************************************************************
@Function 		AddBestTrianglesAppraise
@Modified		pJob			The block object to alter
@Input			pOb				The object
@Input			pTriAppraise	The triangle to appraise
@Return			bool
@Description	Uses the input object and triangle to create a new block option.
****************************************************************************/
bool CBlock::AddBestTrianglesAppraise(
	CBlockOption	* const pJob,
	const CObject	* const pOb,
	const STri		* const pTriAppraise)
{
	SVtx	*pVtx;
	STri	*pTri;
	int		i, j;

	pJob->Clear();

	// Add vertices
	for(i = 0; i < 3; ++i) {
		pVtx = &pOb->m_pVtx[pTriAppraise->pwIdx[i]];
		if(!m_sOpt.UsingVertex(pVtx))
			pJob->AddVertex(pVtx);
	}

	if(pJob->nVtxNum > (m_nVtxLimit-m_sOpt.nVtxNum))
		return false;

	// Add triangles referenced by each vertex
	for(i = 0; i < 3; ++i) {
		pVtx = &pOb->m_pVtx[pTriAppraise->pwIdx[i]];

		_ASSERT(pVtx->nTriNumFree >= 1);
		_ASSERT(pVtx->nTriNumFree <= pVtx->nTriNumTot);

		for(j = 0; j < pVtx->nTriNumTot; ++j) {
			if(pJob->nTriNum >= (m_nTriLimit-m_sOpt.nTriNum))
				break;

			pTri = pVtx->psTri[j];

			// Don't count the same triangle twice!
			if(pTri->bUsed || m_sOpt.Contains(pTri) || pJob->Contains(pTri))
				continue;

			// If all the triangle's vertices are or will be in the block, then increase nTri
			if(
					(
						pTri->pwIdx[0] == pTriAppraise->pwIdx[0] ||
						pTri->pwIdx[0] == pTriAppraise->pwIdx[1] ||
						pTri->pwIdx[0] == pTriAppraise->pwIdx[2] ||
						m_sOpt.UsingVertex(&pOb->m_pVtx[pTri->pwIdx[0]])
					) && (
						pTri->pwIdx[1] == pTriAppraise->pwIdx[0] ||
						pTri->pwIdx[1] == pTriAppraise->pwIdx[1] ||
						pTri->pwIdx[1] == pTriAppraise->pwIdx[2] ||
						m_sOpt.UsingVertex(&pOb->m_pVtx[pTri->pwIdx[1]])
					) && (
						pTri->pwIdx[2] == pTriAppraise->pwIdx[0] ||
						pTri->pwIdx[2] == pTriAppraise->pwIdx[1] ||
						pTri->pwIdx[2] == pTriAppraise->pwIdx[2] ||
						m_sOpt.UsingVertex(&pOb->m_pVtx[pTri->pwIdx[2]])
					)
				)
			{
				pJob->AddTriangle(pTri);
			}
		}
	}

	_ASSERT(pJob->nTriNum);
	_ASSERT(pJob->nTriNum <= (m_nTriLimit-m_sOpt.nTriNum));

	return true;
}

/****************************************************************************
@Function 		AddBestTriangles
@Input			pOb				The object
@Description	Finds the best triangles and adds them to the current block option (m_sOpt)
****************************************************************************/
void CBlock::AddBestTriangles(CObject * const pOb)
{
	int				i, j;
	const SVtx		*pVtx;
	STri			*pTri;
	CBlockOption	*pJob, *pJobBest;

	pJob = &m_sJob0;

	do {
		pJobBest = 0;

		for(i = 0; i < m_sOpt.nVtxNum; ++i) {
			pVtx = m_sOpt.psVtx[i];

			if(!pVtx->nTriNumFree)
				continue;

			for(j = 0; j < pVtx->nTriNumTot; ++j) {
				pTri = pVtx->psTri[j];

				if(pTri->bUsed || m_sOpt.Contains(pTri))
					continue;

				// Find out how many triangles and vertices this tri adds
				if(!AddBestTrianglesAppraise(pJob, pOb, pTri))
					continue;

				if(!pJobBest || pJob->IsBetterThan(pJobBest)) {
					pJobBest	= pJob;
					pJob		= (pJob == &m_sJob0 ? &m_sJob1 : &m_sJob0);
				}
			}
		}

		if(pJobBest) {
			m_sOpt.Add(pJobBest, pOb);
		}
	} while(pJobBest && m_nTriLimit != m_sOpt.nTriNum);
}

/****************************************************************************
@Function 		FillFrom
@Input			pMesh			Mesh to fill with
@Input			pVtx			Vertex to fill with
@Input			pOb				Object to fill with
@Return			bool			Returns true if the current block option isn't full
@Description	Returns TRUE if Fill() needs to be called again - i.e. blockOption is already filled
****************************************************************************/
bool CBlock::FillFrom(
	SMesh		* const pMesh,
	SVtx		* const pVtx,
	CObject		* const pOb)
{
	// Let's try starting from this vertex then
	_ASSERT(pVtx->nTriNumFree);
	m_sOpt.Clear();
	m_sOpt.AddVertex(pVtx);
	AddBestTriangles(pOb);

	if(m_sOpt.IsFull()) {
		if(m_sOptBest.IsEmpty() || m_sOpt.IsBetterThan(&m_sOptBest))
			m_sOptBest.Copy(&m_sOpt);
		return false;
	}
	else
	{
		_ASSERT(!m_sOpt.IsEmpty());
		pOb->SplitMesh(pMesh, m_sOpt.nVtxNum, m_sOpt.psVtx);		// Split the sub-mesh into its own mesh
		return true;
	}
}

/****************************************************************************
@Function 		Fill
@Input			pOb				Object to fill with
@Return			int				-1 if the block if the best option is already full
@Description	Note: Ask Aaron
****************************************************************************/
int CBlock::Fill(
	CObject	* const pOb)
{
	SVtx	*pVtx;
	int		i;
	SMesh	*pMesh;

	/*
		Build blocks from the large meshes
	*/
	if(!pOb->m_vMeshLg.empty()) {
		pMesh = &pOb->m_vMeshLg.back();

//		_RPT1(_CRT_WARN, "Fill() using large with %d vtx\n", pMesh->nVtxNum);

		// Find the vertex with the fewest unused triangles
		for(i = 0; i < pMesh->nVtxNum; ++i) {
			pVtx = pMesh->ppVtx[i];

			if(pVtx->nTriNumFree == 1) {
				if(FillFrom(pMesh, pVtx, pOb))
					return Fill(pOb);
			}
		}

		if(m_sOptBest.IsEmpty()) {
			// Just start from any old vertex
			for(i = 0; i < pMesh->nVtxNum; ++i) {
				pVtx = pMesh->ppVtx[i];

				if(pVtx->nTriNumFree) {
					if(FillFrom(pMesh, pVtx, pOb))
						return Fill(pOb);
					break;
				}
			}

			if(m_sOptBest.IsEmpty()) {
				pOb->m_vMeshLg.pop_back();					// Delete the mesh from the list
				return Fill(pOb);
			}
		}

		if(m_sOptBest.IsFull())
			return -1;
	}

	/*
		Match together the small meshes into blocks
	*/
	_ASSERT(m_sOptBest.IsEmpty());
	i = m_nVtxLimit - m_sOptBest.nVtxNum - 3;

//	_RPT0(_CRT_WARN, "Fill() grouping small ");

	// Starting with the largest meshes, lump them into this block
	while(i >= 0 && (m_nVtxLimit - m_sOptBest.nVtxNum) >= 3) {
		if(pOb->m_pvMesh[i].empty()) {
			--i;
			continue;
		}

		pMesh = &pOb->m_pvMesh[i].back();
		m_sOptBest.Add(pMesh);
//		_RPT1(_CRT_WARN, "+%d", pMesh->nVtxNum);
		pOb->m_pvMesh[i].pop_back();
		i = PVRT_MIN(i, m_nVtxLimit - m_sOptBest.nVtxNum - 3);
	}

	// If there's any space left in this block (and clearly there are no blocks
	// just the right size to fit) then take SOME of the largest block available.
	if(!m_sOptBest.IsFull()) {
		m_sOpt.Copy(&m_sOptBest);

		// Note: This loop purposely does not check m_pvMesh[0] - any block
		// which is looking to grab more geometry would have already sucked
		// up those meshes
		for(i = (m_nVtxLimit-3); i; --i) {
			if(!pOb->m_pvMesh[i].empty()) {
				pMesh = &pOb->m_pvMesh[i].back();

				_ASSERT(pMesh->ppVtx[0]->nTriNumFree);
				_ASSERT(!m_sOpt.UsingVertex(pMesh->ppVtx[0]));

				m_sOpt.AddVertex(pMesh->ppVtx[0]);
//				_RPT1(_CRT_WARN, "(+%d)\n", pMesh->nVtxNum);
				AddBestTriangles(pOb);

				m_sOptBest.Copy(&m_sOpt);
				_ASSERT(m_sOptBest.IsFull());
				return i;
			}
		}
	}
//	_RPT0(_CRT_WARN, "\n");
	return -1;
}

/****************************************************************************
** Local functions
****************************************************************************/
/****************************************************************************
@Function 		Fill
@Input			pVtxData		Vertex data
@Input			pwIdx			Index array
@Input			nStride			Stride
@Input			nVertNum		Number of vertices
@Input			nIdxNum			Number of indices
@Description	Sorts the vertices.
****************************************************************************/
static void SortVertices(
	void				* const pVtxData,
	PVRTGEOMETRY_IDX	* const pwIdx,
	const int			nStride,
	const int			nVertNum,
	const int			nIdxNum)
{
	void				*pVtxNew;
	int					*pnVtxDest;
	int					i;
	PVRTGEOMETRY_IDX	wNext;

	pVtxNew		= malloc(nVertNum * nStride);
	_ASSERT(pVtxNew);

	pnVtxDest	= (int*)malloc(nVertNum * sizeof(*pnVtxDest));
	_ASSERT(pnVtxDest);

	wNext = 0;

	// Default all indices to an invalid number
	for(i = 0; i < nVertNum; ++i)
		pnVtxDest[i] = -1;

	// Let's get on with it then.
	for(i = 0; i < nIdxNum; ++i) {
		if(pnVtxDest[pwIdx[i]] == -1) {
			_ASSERT((int) wNext < nVertNum);
			memcpy((char*)pVtxNew+(wNext*nStride), (char*)pVtxData+(pwIdx[i]*nStride), nStride);
			pnVtxDest[pwIdx[i]] = wNext++;
		}

		pwIdx[i] = pnVtxDest[pwIdx[i]];
	}

	/*
		This assert will fail if sorting a sub-set of the triangles (e.g. if
		the mesh is bone-batched).

		In that situation vertex sorting should be performed only once after
		all the tri sorting is finished, not per tri-sort.
	*/
	_ASSERT((int) wNext == nVertNum);
	memcpy(pVtxData, pVtxNew, nVertNum * nStride);

	FREE(pnVtxDest);
	FREE(pVtxNew);
}

/****************************************************************************
** Functions
****************************************************************************/
/*!***************************************************************************
 @Function		PVRTGeometrySort
 @Modified		pVtxData		Pointer to array of vertices
 @Modified		pwIdx			Pointer to array of indices
 @Input			nStride			Size of a vertex (in bytes)
 @Input			nVertNum		Number of vertices. Length of pVtxData array
 @Input			nTriNum			Number of triangles. Length of pwIdx array is 3* this
 @Input			nBufferVtxLimit	Number of vertices that can be stored in a buffer
 @Input			nBufferTriLimit	Number of triangles that can be stored in a buffer
 @Input			dwFlags			PVRTGEOMETRY_SORT_* flags
 @Description	Triangle sorter
*****************************************************************************/
void PVRTGeometrySort(
	void				* const pVtxData,
	PVRTGEOMETRY_IDX	* const pwIdx,
	const int			nStride,
	const int			nVertNum,
	const int			nTriNum,
	const int			nBufferVtxLimit,
	const int			nBufferTriLimit,
	const unsigned int	dwFlags)
{
	CObject				sOb(pwIdx, nVertNum, nTriNum, nBufferVtxLimit, nBufferTriLimit);
	CBlock				sBlock(nBufferVtxLimit, nBufferTriLimit);
	PVRTGEOMETRY_IDX	*pwIdxOut;
	int					nTriCnt, nVtxCnt;
	int					nOutTriCnt, nOutVtxCnt, nOutBlockCnt;
	int					nMeshToResize;
#ifdef PVRTRISORT_ENABLE_VERIFY_RESULTS
	int					i;
	int					pnBlockTriCnt[PVRVGPBLOCKTEST_MAX_BLOCKS];
	SVGPModel			sVGPMdlBefore;
	SVGPModel			sVGPMdlAfter;
#endif

	if(dwFlags & PVRTGEOMETRY_SORT_VERTEXCACHE) {
#ifdef PVRTRISORT_ENABLE_VERIFY_RESULTS
		VGP590Test(&sVGPMdlBefore, pwIdx, nTriNum);
		_RPT4(_CRT_WARN, "OptimiseTriListPVR() Before: Tri: %d, Vtx: %d, vtx/tri=%f Blocks=%d\n", nTriNum, sVGPMdlBefore.nVtxCnt, (float)sVGPMdlBefore.nVtxCnt / (float)nTriNum, sVGPMdlBefore.nBlockCnt);
#endif

		pwIdxOut	= (PVRTGEOMETRY_IDX*)malloc(nTriNum * 3 * sizeof(*pwIdxOut));
		_ASSERT(pwIdxOut);

		// Sort geometry into blocks
		nOutTriCnt		= 0;
		nOutVtxCnt		= 0;
		nOutBlockCnt	= 0;
		do {
			// Clear & fill the block
			sBlock.Clear();
			nMeshToResize = sBlock.Fill(&sOb);

			// Copy indices into output
			sBlock.Output(&pwIdxOut[3*nOutTriCnt], &nVtxCnt, &nTriCnt, &sOb);
			sOb.m_nTriNumFree	-= nTriCnt;
			nOutTriCnt			+= nTriCnt;

			if(nMeshToResize >= 0) {
				SMesh	*pMesh;
				_ASSERT(nMeshToResize <= (nBufferVtxLimit-3));
				pMesh = &sOb.m_pvMesh[nMeshToResize].back();
				sOb.ResizeMesh(pMesh->nVtxNum, pMesh->ppVtx);
				sOb.m_pvMesh[nMeshToResize].pop_back();
			}

			_ASSERT(nVtxCnt <= nBufferVtxLimit);
			_ASSERT(nTriCnt <= nBufferTriLimit);

#ifdef PVRTRISORT_ENABLE_VERIFY_RESULTS
			_ASSERT(nOutBlockCnt < PVRVGPBLOCKTEST_MAX_BLOCKS);
			pnBlockTriCnt[nOutBlockCnt] = nTriCnt;
#endif
			nOutVtxCnt += nVtxCnt;
			nOutBlockCnt++;

//			_RPT4(_CRT_WARN, "%d/%d tris (+%d), %d blocks\n", nOutTriCnt, nTriNum, nTriCnt, nOutBlockCnt);

			_ASSERT(nTriCnt == nBufferTriLimit || (nBufferVtxLimit - nVtxCnt) < 3 || nOutTriCnt == nTriNum);
		} while(nOutTriCnt < nTriNum);

		_ASSERT(nOutTriCnt == nTriNum);
		// The following will fail if optimising a subset of the mesh (e.g. a bone batching)
		//_ASSERT(nOutVtxCnt >= nVertNum);

		// Done!
		memcpy(pwIdx, pwIdxOut, nTriNum * 3 * sizeof(*pwIdx));
		FREE(pwIdxOut);

		_RPT3(_CRT_WARN, "OptimiseTriListPVR() In: Tri: %d, Vtx: %d, vtx/tri=%f\n", nTriNum, nVertNum, (float)nVertNum / (float)nTriNum);
		_RPT4(_CRT_WARN, "OptimiseTriListPVR() HW: Tri: %d, Vtx: %d, vtx/tri=%f Blocks=%d\n", nOutTriCnt, nOutVtxCnt, (float)nOutVtxCnt / (float)nOutTriCnt, nOutBlockCnt);

#ifdef PVRTRISORT_ENABLE_VERIFY_RESULTS
		VGP590Test(&sVGPMdlAfter, pwIdx, nTriNum);
		_RPT4(_CRT_WARN, "OptimiseTriListPVR() After : Tri: %d, Vtx: %d, vtx/tri=%f Blocks=%d\n", nTriNum, sVGPMdlAfter.nVtxCnt, (float)sVGPMdlAfter.nVtxCnt / (float)nTriNum, sVGPMdlAfter.nBlockCnt);
		_ASSERTE(sVGPMdlAfter.nVtxCnt <= sVGPMdlBefore.nVtxCnt);
		_ASSERTE(sVGPMdlAfter.nBlockCnt <= sVGPMdlBefore.nBlockCnt);

		for(i = 0; i < nOutBlockCnt; ++i) {
			_ASSERT(pnBlockTriCnt[i] == sVGPMdlAfter.pnBlockTriCnt[i]);
		}
#endif
	}

	if(!(dwFlags & PVRTGEOMETRY_SORT_IGNOREVERTS)) {
		// Re-order the vertices so maybe they're accessed in a more linear
		// manner. Should cut page-breaks on the initial memory read of
		// vertices. Affects both the order of vertices, and the values
		// of indices, but the triangle order is unchanged.
		SortVertices(pVtxData, pwIdx, nStride, nVertNum, nTriNum*3);
	}
}

/*****************************************************************************
 End of file (PVRTGeometry.cpp)
*****************************************************************************/

