/******************************************************************************
@File PVRTTriStrip.cpp
@Title PVRTTriStrip
@Version @Version
@Copyright Copyright (c) Imagination Technologies Limited.
@Platform Independent
@Description Strips a triangle list.
******************************************************************************/
/****************************************************************************
** Includes
****************************************************************************/
#include <stdlib.h>
#include "PVRTGlobal.h"
#include "PVRTContext.h"
#include "PVRTTriStrip.h"
/****************************************************************************
** Defines
****************************************************************************/
#define RND_TRIS_ORDER
/****************************************************************************
** Structures
****************************************************************************/
/****************************************************************************
** Class: CTri
****************************************************************************/
class CTri;
/*!***************************************************************************
@Class CTriState
@Description Stores a pointer to the triangles either side of itself,
as well as it's winding.
*****************************************************************************/
class CTriState
{
public:
CTri *pRev, *pFwd;
bool bWindFwd;
CTriState()
{
bWindFwd = true; // Initial value irrelevent
pRev = NULL;
pFwd = NULL;
}
};
/*!***************************************************************************
@Class CTri
@Description Object used to store information about the triangle, such as
the vertex indices it is made from, which triangles are
adjacent to it, etc.
*****************************************************************************/
class CTri
{
public:
CTriState sNew, sOld;
CTri *pAdj[3];
bool bInStrip;
const unsigned int *pIdx; // three indices for the tri
bool bOutput;
public:
CTri();
int FindEdge(const unsigned int pw0, const unsigned int pw1) const;
void Cement();
void Undo();
int EdgeFromAdjTri(const CTri &tri) const; // Find the index of the adjacent tri
};
/*!***************************************************************************
@Class CStrip
@Description Object used to store the triangles that a given strip is
composed from.
*****************************************************************************/
class CStrip
{
protected:
unsigned int m_nTriCnt;
CTri *m_pTri;
unsigned int m_nStrips;
CTri **m_psStrip; // Working space for finding strips
public:
CStrip(
const unsigned int * const pui32TriList,
const unsigned int nTriCnt);
~CStrip();
protected:
bool StripGrow(
CTri &triFrom,
const unsigned int nEdgeFrom,
const int nMaxChange);
public:
void StripFromEdges();
void StripImprove();
void Output(
unsigned int **ppui32Strips,
unsigned int **ppnStripLen,
unsigned int *pnStripCnt);
};
/****************************************************************************
** Constants
****************************************************************************/
/****************************************************************************
** Code: Class: CTri
****************************************************************************/
CTri::CTri()
{
pAdj[0] = NULL;
pAdj[1] = NULL;
pAdj[2] = NULL;
bInStrip = false;
bOutput = false;
}
/*!***************************************************************************
@Function FindEdge
@Input pw0 The first index
@Input pw1 The second index
@Return The index of the edge
@Description Finds the index of the edge that the current object shares
with the two vertex index values that have been passed in
(or returns -1 if they dont share an edge).
*****************************************************************************/
int CTri::FindEdge(const unsigned int pw0, const unsigned int pw1) const
{
if((pIdx[0] == pw0 && pIdx[1] == pw1))
return 0;
if((pIdx[1] == pw0 && pIdx[2] == pw1))
return 1;
if((pIdx[2] == pw0 && pIdx[0] == pw1))
return 2;
return -1;
}
/*!***************************************************************************
@Function Cement
@Description Assigns the new state as the old state.
*****************************************************************************/
void CTri::Cement()
{
sOld = sNew;
}
/*!***************************************************************************
@Function Undo
@Description Reverts the new state to the old state.
*****************************************************************************/
void CTri::Undo()
{
sNew = sOld;
}
/*!***************************************************************************
@Function EdgeFromAdjTri
@Input tri The triangle to compare
@Return int Index of adjacent triangle (-1 if not adjacent)
@Description If the input triangle is adjacent to the current triangle,
it's index is returned.
*****************************************************************************/
int CTri::EdgeFromAdjTri(const CTri &tri) const
{
for(int i = 0; i < 3; ++i)
{
if(pAdj[i] == &tri)
{
return i;
}
}
_ASSERT(false);
return -1;
}
/****************************************************************************
** Local code
****************************************************************************/
/*!***************************************************************************
@Function OrphanTri
@Input tri The triangle test
@Return int Returns 1 if change was made
@Description If the input triangle is not wound forward and is not the last
triangle in the strip, the connection with the next triangle
in the strip is removed.
*****************************************************************************/
static int OrphanTri(
CTri * const pTri)
{
_ASSERT(!pTri->bInStrip);
if(pTri->sNew.bWindFwd || !pTri->sNew.pFwd)
return 0;
pTri->sNew.pFwd->sNew.pRev = NULL;
pTri->sNew.pFwd = NULL;
return 1;
}
/*!***************************************************************************
@Function TakeTri
@Input pTri The triangle to take
@Input pRevNew The triangle that is before pTri in the new strip
@Return int Returns 1 if a new strip has been created
@Description Removes the triangle from it's current strip
and places it in a new one (following pRevNew in the new strip).
*****************************************************************************/
static int TakeTri(
CTri * const pTri,
CTri * const pRevNew,
const bool bFwd)
{
int nRet;
_ASSERT(!pTri->bInStrip);
if(pTri->sNew.pFwd && pTri->sNew.pRev)
{
_ASSERT(pTri->sNew.pFwd->sNew.pRev == pTri);
pTri->sNew.pFwd->sNew.pRev = NULL;
_ASSERT(pTri->sNew.pRev->sNew.pFwd == pTri);
pTri->sNew.pRev->sNew.pFwd = NULL;
// If in the middle of a Strip, this will generate a new Strip
nRet = 1;
// The second tri in the strip may need to be orphaned, or it will have wrong winding order
nRet += OrphanTri(pTri->sNew.pFwd);
}
else if(pTri->sNew.pFwd)
{
_ASSERT(pTri->sNew.pFwd->sNew.pRev == pTri);
pTri->sNew.pFwd->sNew.pRev = NULL;
// If at the beginning of a Strip, no change
nRet = 0;
// The second tri in the strip may need to be orphaned, or it will have wrong winding order
nRet += OrphanTri(pTri->sNew.pFwd);
}
else if(pTri->sNew.pRev)
{
_ASSERT(pTri->sNew.pRev->sNew.pFwd == pTri);
pTri->sNew.pRev->sNew.pFwd = NULL;
// If at the end of a Strip, no change
nRet = 0;
}
else
{
// Otherwise it's a lonesome triangle; one Strip removed!
nRet = -1;
}
pTri->sNew.pFwd = NULL;
pTri->sNew.pRev = pRevNew;
pTri->bInStrip = true;
pTri->sNew.bWindFwd = bFwd;
if(pRevNew)
{
_ASSERT(!pRevNew->sNew.pFwd);
pRevNew->sNew.pFwd = pTri;
}
return nRet;
}
/*!***************************************************************************
@Function TryLinkEdge
@Input src The source triangle
@Input cmp The triangle to compare with
@Input nSrcEdge The edge of souce triangle to compare
@Input idx0 Vertex index 0 of the compare triangle
@Input idx1 Vertex index 1 of the compare triangle
@Description If the triangle to compare currently has no adjacent
triangle along the specified edge, link the source triangle
(along it's specified edge) with the compare triangle.
*****************************************************************************/
static bool TryLinkEdge(
CTri &src,
CTri &cmp,
const int nSrcEdge,
const unsigned int idx0,
const unsigned int idx1)
{
int nCmpEdge;
nCmpEdge = cmp.FindEdge(idx0, idx1);
if(nCmpEdge != -1 && !cmp.pAdj[nCmpEdge])
{
cmp.pAdj[nCmpEdge] = &src;
src.pAdj[nSrcEdge] = &cmp;
return true;
}
return false;
}
/****************************************************************************
** Code: Class: CStrip
****************************************************************************/
CStrip::CStrip(
const unsigned int * const pui32TriList,
const unsigned int nTriCnt)
{
unsigned int i, j;
bool b0, b1, b2;
m_nTriCnt = nTriCnt;
/*
Generate adjacency info
*/
m_pTri = new CTri[nTriCnt];
for(i = 0; i < nTriCnt; ++i)
{
// Set pointer to indices
m_pTri[i].pIdx = &pui32TriList[3 * i];
b0 = false;
b1 = false;
b2 = false;
for(j = 0; j < i && !(b0 & b1 & b2); ++j)
{
if(!b0)
b0 = TryLinkEdge(m_pTri[i], m_pTri[j], 0, m_pTri[i].pIdx[1], m_pTri[i].pIdx[0]);
if(!b1)
b1 = TryLinkEdge(m_pTri[i], m_pTri[j], 1, m_pTri[i].pIdx[2], m_pTri[i].pIdx[1]);
if(!b2)
b2 = TryLinkEdge(m_pTri[i], m_pTri[j], 2, m_pTri[i].pIdx[0], m_pTri[i].pIdx[2]);
}
}
// Initially, every triangle is a strip.
m_nStrips = m_nTriCnt;
// Allocate working space for the strippers
m_psStrip = new CTri*[m_nTriCnt];
}
CStrip::~CStrip()
{
delete [] m_pTri;
delete [] m_psStrip;
}
/*!***************************************************************************
@Function StripGrow
@Input triFrom The triangle to begin from
@Input nEdgeFrom The edge of the triangle to begin from
@Input maxChange The maximum number of changes to be made
@Description Takes triFrom as a starting point of triangles to add to
the list and adds triangles sequentially by finding the next
triangle that is adjacent to the current triangle.
This is repeated until the maximum number of changes
have been made.
*****************************************************************************/
bool CStrip::StripGrow(
CTri &triFrom,
const unsigned int nEdgeFrom,
const int nMaxChange)
{
unsigned int i;
bool bFwd;
int nDiff, nDiffTot, nEdge;
CTri *pTri, *pTriPrev, *pTmp;
unsigned int nStripLen;
// Start strip from this tri
pTri = &triFrom;
pTriPrev = NULL;
nDiffTot = 0;
nStripLen = 0;
// Start strip from this edge
nEdge = nEdgeFrom;
bFwd = true;
// Extend the strip until we run out, or we find an improvement
nDiff = 1;
while(nDiff > nMaxChange)
{
// Add pTri to the strip
_ASSERT(pTri);
nDiff += TakeTri(pTri, pTriPrev, bFwd);
_ASSERT(nStripLen < m_nTriCnt);
m_psStrip[nStripLen++] = pTri;
// Jump to next tri
pTriPrev = pTri;
pTri = pTri->pAdj[nEdge];
if(!pTri)
break; // No more tris, gotta stop
if(pTri->bInStrip)
break; // No more tris, gotta stop
// Find which edge we came over
nEdge = pTri->EdgeFromAdjTri(*pTriPrev);
// Find the edge to leave over
if(bFwd)
{
if(--nEdge < 0)
nEdge = 2;
}
else
{
if(++nEdge > 2)
nEdge = 0;
}
// Swap the winding order for the next tri
bFwd = !bFwd;
}
_ASSERT(!pTriPrev->sNew.pFwd);
/*
Accept or reject this strip.
Accepting changes which don't change the number of strips
adds variety, which can help better strips to develop.
*/
if(nDiff <= nMaxChange)
{
nDiffTot += nDiff;
// Great, take the Strip
for(i = 0; i < nStripLen; ++i)
{
pTri = m_psStrip[i];
_ASSERT(pTri->bInStrip);
// Cement affected tris
pTmp = pTri->sOld.pFwd;
if(pTmp && !pTmp->bInStrip)
{
if(pTmp->sOld.pFwd && !pTmp->sOld.pFwd->bInStrip)
pTmp->sOld.pFwd->Cement();
pTmp->Cement();
}
pTmp = pTri->sOld.pRev;
if(pTmp && !pTmp->bInStrip)
{
pTmp->Cement();
}
// Cement this tris
pTri->bInStrip = false;
pTri->Cement();
}
}
else
{
// Shame, undo the strip
for(i = 0; i < nStripLen; ++i)
{
pTri = m_psStrip[i];
_ASSERT(pTri->bInStrip);
// Undo affected tris
pTmp = pTri->sOld.pFwd;
if(pTmp && !pTmp->bInStrip)
{
if(pTmp->sOld.pFwd && !pTmp->sOld.pFwd->bInStrip)
pTmp->sOld.pFwd->Undo();
pTmp->Undo();
}
pTmp = pTri->sOld.pRev;
if(pTmp && !pTmp->bInStrip)
{
pTmp->Undo();
}
// Undo this tris
pTri->bInStrip = false;
pTri->Undo();
}
}
#ifdef _DEBUG
for(int nDbg = 0; nDbg < (int)m_nTriCnt; ++nDbg)
{
_ASSERT(m_pTri[nDbg].bInStrip == false);
_ASSERT(m_pTri[nDbg].bOutput == false);
_ASSERT(m_pTri[nDbg].sOld.pRev == m_pTri[nDbg].sNew.pRev);
_ASSERT(m_pTri[nDbg].sOld.pFwd == m_pTri[nDbg].sNew.pFwd);
if(m_pTri[nDbg].sNew.pRev)
{
_ASSERT(m_pTri[nDbg].sNew.pRev->sNew.pFwd == &m_pTri[nDbg]);
}
if(m_pTri[nDbg].sNew.pFwd)
{
_ASSERT(m_pTri[nDbg].sNew.pFwd->sNew.pRev == &m_pTri[nDbg]);
}
}
#endif
if(nDiffTot)
{
m_nStrips += nDiffTot;
return true;
}
return false;
}
/*!***************************************************************************
@Function StripFromEdges
@Description Creates a strip from the object's edge information.
*****************************************************************************/
void CStrip::StripFromEdges()
{
unsigned int i, j, nTest;
CTri *pTri, *pTriPrev;
int nEdge = 0;
/*
Attempt to create grid-oriented strips.
*/
for(i = 0; i < m_nTriCnt; ++i)
{
pTri = &m_pTri[i];
// Count the number of empty edges
nTest = 0;
for(j = 0; j < 3; ++j)
{
if(!pTri->pAdj[j])
{
++nTest;
}
else
{
nEdge = j;
}
}
if(nTest != 2)
continue;
for(;;)
{
// A tri with two empty edges is a corner (there are other corners too, but this works so...)
while(StripGrow(*pTri, nEdge, -1)) {};
pTriPrev = pTri;
pTri = pTri->pAdj[nEdge];
if(!pTri)
break;
// Find the edge we came over
nEdge = pTri->EdgeFromAdjTri(*pTriPrev);
// Step around to the next edge
if(++nEdge > 2)
nEdge = 0;
pTriPrev = pTri;
pTri = pTri->pAdj[nEdge];
if(!pTri)
break;
// Find the edge we came over
nEdge = pTri->EdgeFromAdjTri(*pTriPrev);
// Step around to the next edge
if(--nEdge < 0)
nEdge = 2;
#if 0
// If we're not tracking the edge, give up
nTest = nEdge - 1;
if(nTest < 0)
nTest = 2;
if(pTri->pAdj[nTest])
break;
else
continue;
#endif
}
}
}
#ifdef RND_TRIS_ORDER
struct pair
{
unsigned int i, o;
};
static int compare(const void *arg1, const void *arg2)
{
return ((pair*)arg1)->i - ((pair*)arg2)->i;
}
#endif
/*!***************************************************************************
@Function StripImprove
@Description Optimises the strip
*****************************************************************************/
void CStrip::StripImprove()
{
unsigned int i, j;
bool bChanged;
int nRepCnt, nChecks;
int nMaxChange;
#ifdef RND_TRIS_ORDER
pair *pnOrder;
/*
Create a random order to process the tris
*/
pnOrder = new pair[m_nTriCnt];
#endif
nRepCnt = 0;
nChecks = 2;
nMaxChange = 0;
/*
Reduce strip count by growing each of the three strips each tri can start.
*/
while(nChecks)
{
--nChecks;
bChanged = false;
#ifdef RND_TRIS_ORDER
/*
Create a random order to process the tris
*/
for(i = 0; i < m_nTriCnt; ++i)
{
pnOrder[i].i = rand() * rand();
pnOrder[i].o = i;
}
qsort(pnOrder, m_nTriCnt, sizeof(*pnOrder), compare);
#endif
/*
Process the tris
*/
for(i = 0; i < m_nTriCnt; ++i)
{
for(j = 0; j < 3; ++j)
{
#ifdef RND_TRIS_ORDER
bChanged |= StripGrow(m_pTri[pnOrder[i].o], j, nMaxChange);
#else
bChanged |= StripGrow(m_pTri[i], j, nMaxChange);
#endif
}
}
++nRepCnt;
// Check the results once or twice
if(bChanged)
nChecks = 2;
nMaxChange = (nMaxChange == 0 ? -1 : 0);
}
#ifdef RND_TRIS_ORDER
delete [] pnOrder;
#endif
_RPT1(_CRT_WARN, "Reps: %d\n", nRepCnt);
}
/*!***************************************************************************
@Function Output
@Output ppui32Strips
@Output ppnStripLen The length of the strip
@Output pnStripCnt
@Description Outputs key information about the strip to the output
parameters.
*****************************************************************************/
void CStrip::Output(
unsigned int **ppui32Strips,
unsigned int **ppnStripLen,
unsigned int *pnStripCnt)
{
unsigned int *pui32Strips;
unsigned int *pnStripLen;
unsigned int i, j, nIdx, nStrip;
CTri *pTri;
/*
Output Strips
*/
pnStripLen = (unsigned int*)malloc(m_nStrips * sizeof(*pnStripLen));
pui32Strips = (unsigned int*)malloc((m_nTriCnt + m_nStrips * 2) * sizeof(*pui32Strips));
nStrip = 0;
nIdx = 0;
for(i = 0; i < m_nTriCnt; ++i)
{
pTri = &m_pTri[i];
if(pTri->sNew.pRev)
continue;
_ASSERT(!pTri->sNew.pFwd || pTri->sNew.bWindFwd);
_ASSERT(pTri->bOutput == false);
if(!pTri->sNew.pFwd)
{
pui32Strips[nIdx++] = pTri->pIdx[0];
pui32Strips[nIdx++] = pTri->pIdx[1];
pui32Strips[nIdx++] = pTri->pIdx[2];
pnStripLen[nStrip] = 1;
pTri->bOutput = true;
}
else
{
if(pTri->sNew.pFwd == pTri->pAdj[0])
{
pui32Strips[nIdx++] = pTri->pIdx[2];
pui32Strips[nIdx++] = pTri->pIdx[0];
}
else if(pTri->sNew.pFwd == pTri->pAdj[1])
{
pui32Strips[nIdx++] = pTri->pIdx[0];
pui32Strips[nIdx++] = pTri->pIdx[1];
}
else
{
_ASSERT(pTri->sNew.pFwd == pTri->pAdj[2]);
pui32Strips[nIdx++] = pTri->pIdx[1];
pui32Strips[nIdx++] = pTri->pIdx[2];
}
pnStripLen[nStrip] = 0;
do
{
_ASSERT(pTri->bOutput == false);
// Increment tris-in-this-strip counter
++pnStripLen[nStrip];
// Output the new vertex index
for(j = 0; j < 3; ++j)
{
if(
(pui32Strips[nIdx-2] != pTri->pIdx[j]) &&
(pui32Strips[nIdx-1] != pTri->pIdx[j]))
{
break;
}
}
_ASSERT(j != 3);
pui32Strips[nIdx++] = pTri->pIdx[j];
// Double-check that the previous three indices are the indices of this tris (in some order)
_ASSERT(
((pui32Strips[nIdx-3] == pTri->pIdx[0]) && (pui32Strips[nIdx-2] == pTri->pIdx[1]) && (pui32Strips[nIdx-1] == pTri->pIdx[2])) ||
((pui32Strips[nIdx-3] == pTri->pIdx[1]) && (pui32Strips[nIdx-2] == pTri->pIdx[2]) && (pui32Strips[nIdx-1] == pTri->pIdx[0])) ||
((pui32Strips[nIdx-3] == pTri->pIdx[2]) && (pui32Strips[nIdx-2] == pTri->pIdx[0]) && (pui32Strips[nIdx-1] == pTri->pIdx[1])) ||
((pui32Strips[nIdx-3] == pTri->pIdx[2]) && (pui32Strips[nIdx-2] == pTri->pIdx[1]) && (pui32Strips[nIdx-1] == pTri->pIdx[0])) ||
((pui32Strips[nIdx-3] == pTri->pIdx[1]) && (pui32Strips[nIdx-2] == pTri->pIdx[0]) && (pui32Strips[nIdx-1] == pTri->pIdx[2])) ||
((pui32Strips[nIdx-3] == pTri->pIdx[0]) && (pui32Strips[nIdx-2] == pTri->pIdx[2]) && (pui32Strips[nIdx-1] == pTri->pIdx[1])));
// Check that the latest three indices are not degenerate
_ASSERT(pui32Strips[nIdx-1] != pui32Strips[nIdx-2]);
_ASSERT(pui32Strips[nIdx-1] != pui32Strips[nIdx-3]);
_ASSERT(pui32Strips[nIdx-2] != pui32Strips[nIdx-3]);
pTri->bOutput = true;
// Check that the next triangle is adjacent to this triangle
_ASSERT(
(pTri->sNew.pFwd == pTri->pAdj[0]) ||
(pTri->sNew.pFwd == pTri->pAdj[1]) ||
(pTri->sNew.pFwd == pTri->pAdj[2]) ||
(!pTri->sNew.pFwd));
// Check that this triangle is adjacent to the next triangle
_ASSERT(
(!pTri->sNew.pFwd) ||
(pTri == pTri->sNew.pFwd->pAdj[0]) ||
(pTri == pTri->sNew.pFwd->pAdj[1]) ||
(pTri == pTri->sNew.pFwd->pAdj[2]));
pTri = pTri->sNew.pFwd;
} while(pTri);
}
++nStrip;
}
_ASSERT(nIdx == m_nTriCnt + m_nStrips * 2);
_ASSERT(nStrip == m_nStrips);
// Check all triangles have been output
for(i = 0; i < m_nTriCnt; ++i)
{
_ASSERT(m_pTri[i].bOutput == true);
}
// Check all triangles are present
j = 0;
for(i = 0; i < m_nStrips; ++i)
{
j += pnStripLen[i];
}
_ASSERT(j == m_nTriCnt);
// Output data
*pnStripCnt = m_nStrips;
*ppui32Strips = pui32Strips;
*ppnStripLen = pnStripLen;
}
/****************************************************************************
** Code
****************************************************************************/
/*!***************************************************************************
@Function PVRTTriStrip
@Output ppui32Strips
@Output ppnStripLen
@Output pnStripCnt
@Input pui32TriList
@Input nTriCnt
@Description Reads a triangle list and generates an optimised triangle strip.
*****************************************************************************/
void PVRTTriStrip(
unsigned int **ppui32Strips,
unsigned int **ppnStripLen,
unsigned int *pnStripCnt,
const unsigned int * const pui32TriList,
const unsigned int nTriCnt)
{
unsigned int *pui32Strips;
unsigned int *pnStripLen;
unsigned int nStripCnt;
/*
If the order in which triangles are tested as strip roots is
randomised, then several attempts can be made. Use the best result.
*/
for(int i = 0; i <
#ifdef RND_TRIS_ORDER
5
#else
1
#endif
; ++i)
{
CStrip stripper(pui32TriList, nTriCnt);
#ifdef RND_TRIS_ORDER
srand(i);
#endif
stripper.StripFromEdges();
stripper.StripImprove();
stripper.Output(&pui32Strips, &pnStripLen, &nStripCnt);
if(!i || nStripCnt < *pnStripCnt)
{
if(i)
{
FREE(*ppui32Strips);
FREE(*ppnStripLen);
}
*ppui32Strips = pui32Strips;
*ppnStripLen = pnStripLen;
*pnStripCnt = nStripCnt;
}
else
{
FREE(pui32Strips);
FREE(pnStripLen);
}
}
}
/*!***************************************************************************
@Function PVRTTriStripList
@Modified pui32TriList
@Input nTriCnt
@Description Reads a triangle list and generates an optimised triangle strip.
Result is converted back to a triangle list.
*****************************************************************************/
void PVRTTriStripList(unsigned int * const pui32TriList, const unsigned int nTriCnt)
{
unsigned int *pui32Strips;
unsigned int *pnStripLength;
unsigned int nNumStrips;
unsigned int *pui32TriPtr, *pui32StripPtr;
/*
Strip the geometry
*/
PVRTTriStrip(&pui32Strips, &pnStripLength, &nNumStrips, pui32TriList, nTriCnt);
/*
Convert back to a triangle list
*/
pui32StripPtr = pui32Strips;
pui32TriPtr = pui32TriList;
for(unsigned int i = 0; i < nNumStrips; ++i)
{
*pui32TriPtr++ = *pui32StripPtr++;
*pui32TriPtr++ = *pui32StripPtr++;
*pui32TriPtr++ = *pui32StripPtr++;
for(unsigned int j = 1; j < pnStripLength[i]; ++j)
{
// Use two indices from previous triangle, flipping tri order alternately.
if(j & 0x01)
{
*pui32TriPtr++ = pui32StripPtr[-1];
*pui32TriPtr++ = pui32StripPtr[-2];
}
else
{
*pui32TriPtr++ = pui32StripPtr[-2];
*pui32TriPtr++ = pui32StripPtr[-1];
}
*pui32TriPtr++ = *pui32StripPtr++;
}
}
free(pui32Strips);
free(pnStripLength);
}
/*****************************************************************************
End of file (PVRTTriStrip.cpp)
*****************************************************************************/