/****************************************************************************** @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) *****************************************************************************/