/******************************************************************************
|
|
@File PVRTBoneBatch.cpp
|
|
@Title PVRTBoneBatch
|
|
@Version
|
|
@Copyright Copyright (c) Imagination Technologies Limited.
|
|
@Platform ANSI compatible
|
|
@Description Utility functions which process vertices.
|
|
******************************************************************************/
|
|
/****************************************************************************
|
** Includes
|
****************************************************************************/
|
#include "PVRTGlobal.h"
|
#include "PVRTContext.h"
|
|
#include <vector>
|
#include <list>
|
|
#include "PVRTMatrix.h"
|
#include "PVRTVertex.h"
|
#include "PVRTBoneBatch.h"
|
|
/****************************************************************************
|
** Defines
|
****************************************************************************/
|
|
/****************************************************************************
|
** Macros
|
****************************************************************************/
|
|
/****************************************************************************
|
** Structures
|
****************************************************************************/
|
/*!***************************************************************************
|
@Class CBatch
|
@Brief Class to contain and manage batch information.
|
*****************************************************************************/
|
class CBatch
|
{
|
protected:
|
int m_nCapacity, // Maximum size of the batch
|
m_nCnt, // Number of elements currently contained in the batch
|
*m_pnPalette; // Array of palette indices
|
|
public:
|
/*!***************************************************************************
|
@Function CBatch
|
@Description The default constructor
|
*****************************************************************************/
|
CBatch() : m_nCapacity(0),
|
m_nCnt(0),
|
m_pnPalette(0)
|
{
|
}
|
|
/*!***************************************************************************
|
@Function CBatch
|
@Input src CBatch to copy
|
@Description Copy constructor
|
*****************************************************************************/
|
CBatch(const CBatch &src) : m_pnPalette(0)
|
{
|
SetSize(src.m_nCapacity);
|
*this = src;
|
}
|
|
/*!***************************************************************************
|
@Function ~CBatch
|
@Description Destructor
|
*****************************************************************************/
|
~CBatch()
|
{
|
FREE(m_pnPalette);
|
}
|
|
/*!***************************************************************************
|
@Function operator=
|
@Description Operator overload for the '=' operand
|
*****************************************************************************/
|
CBatch& operator= (const CBatch &src)
|
{
|
_ASSERT(m_nCapacity == src.m_nCapacity);
|
m_nCnt = src.m_nCnt;
|
memcpy(m_pnPalette, src.m_pnPalette, m_nCnt * sizeof(*m_pnPalette));
|
return *this;
|
}
|
|
/*!***************************************************************************
|
@Function SetSize
|
@Input nSize The new size of the batch
|
@Description Delete all current information and resizes the batch
|
to the value that has been passed in.
|
*****************************************************************************/
|
void SetSize(const int nSize)
|
{
|
FREE(m_pnPalette);
|
|
m_nCapacity = nSize;
|
m_nCnt = 0;
|
m_pnPalette = (int*)malloc(m_nCapacity * sizeof(*m_pnPalette));
|
}
|
|
/*!***************************************************************************
|
@Function Clear
|
@Description Resets the count
|
*****************************************************************************/
|
void Clear()
|
{
|
m_nCnt = 0;
|
}
|
|
/*!***************************************************************************
|
@Function Clear
|
@Input n The index of the new item
|
Return bool Returns true if the item already exists or has been added.
|
@Description Adds a new item to the batch, providing it has not already
|
been added to the batch and the count doesn't exceed the
|
maximum number of bones the batch can hold.
|
*****************************************************************************/
|
bool Add(const int n)
|
{
|
int i;
|
|
if(n < 0)
|
return false;
|
|
// If we already have this item, do nothing
|
for(i = 0; i < m_nCnt; ++i)
|
{
|
if(m_pnPalette[i] == n)
|
return true;
|
}
|
|
// Add the new item
|
if(m_nCnt < m_nCapacity)
|
{
|
m_pnPalette[m_nCnt] = n;
|
++m_nCnt;
|
return true;
|
}
|
else
|
{
|
return false;
|
}
|
}
|
|
/*!***************************************************************************
|
@Function Merge
|
@Input src The batch to merge with
|
@Description Merges the input batch with the current batch.
|
*****************************************************************************/
|
void Merge(const CBatch &src)
|
{
|
int i;
|
|
for(i = 0; i < src.m_nCnt; ++i)
|
Add(src.m_pnPalette[i]);
|
}
|
|
/*!***************************************************************************
|
@Function TestMerge
|
@Input src The batch to merge with
|
@Return int The number of items that are not already
|
present in the batch. -1 if the merge will
|
exceed the capacity of the batch
|
@Description Tests how many of the items of the input batch are not
|
already contained in the batch. This returns the number of
|
items that would need to be added, or -1 if the number
|
of additional items would exceed the capacity of the batch.
|
*****************************************************************************/
|
int TestMerge(const CBatch &src)
|
{
|
int i, nCnt;
|
|
nCnt = 0;
|
for(i = 0; i < src.m_nCnt; ++i)
|
if(!Contains(src.m_pnPalette[i]))
|
++nCnt;
|
|
return m_nCnt+nCnt > m_nCapacity ? -1 : nCnt;
|
}
|
|
/*!***************************************************************************
|
@Function Contains
|
@Input src The batch to compare
|
@Return bool Returns true if the batch and the input batch
|
have at least one item in common
|
@Description Returns true if the batch's have at least one item in common
|
*****************************************************************************/
|
bool Contains(const CBatch &batch) const
|
{
|
int i;
|
|
for(i = 0; i < batch.m_nCnt; ++i)
|
if(!Contains(batch.m_pnPalette[i]))
|
return false;
|
|
return true;
|
}
|
|
/*!***************************************************************************
|
@Function Contains
|
@Input n The index of the new item
|
@Return bool Returns true if the batch contains the item
|
@Description Returns true if the batch contains the item.
|
*****************************************************************************/
|
bool Contains(const int n) const
|
{
|
int i;
|
|
for(i = 0; i < m_nCnt; ++i)
|
if(m_pnPalette[i] == n)
|
return true;
|
|
return false;
|
}
|
|
/*!***************************************************************************
|
@Function Write
|
@Output pn The array of items to overwrite
|
@Output pnCnt The number of items in the array
|
@Description Writes the array of items and the number of items to the output
|
parameters.
|
*****************************************************************************/
|
void Write(
|
int * const pn,
|
int * const pnCnt) const
|
{
|
memcpy(pn, m_pnPalette, m_nCnt * sizeof(*pn));
|
*pnCnt = m_nCnt;
|
}
|
|
/*!***************************************************************************
|
@Function GetVertexBoneIndices
|
@Modified pfI Returned index
|
@Input pfW Weight?
|
@Input n Length of index array
|
@Description For each element of the input array, the index value is compared
|
with the palette's index value. If the values are equal, the
|
value of the current input array element is replaced with the
|
palette index, otherwise the value is set to zero.
|
*****************************************************************************/
|
void GetVertexBoneIndices(
|
float * const pfI,
|
const float * const pfW,
|
const int n)
|
{
|
int i, j;
|
|
for(i = 0; i < n; ++i)
|
{
|
if(pfW[i] != 0)
|
{
|
for(j = 0; j < m_nCnt; ++j)
|
{
|
if(pfI[i] != m_pnPalette[j])
|
continue;
|
|
pfI[i] = (float)j;
|
break;
|
}
|
|
// This batch *must* contain this vertex
|
_ASSERT(j != m_nCnt);
|
}
|
else
|
{
|
pfI[i] = 0;
|
}
|
}
|
}
|
};
|
|
/*!***************************************************************************
|
@Class CGrowableArray
|
@Brief Class that provides an array structure that can change its size dynamically.
|
*****************************************************************************/
|
class CGrowableArray
|
{
|
protected:
|
char *m_p;
|
int m_nSize;
|
int m_nCnt;
|
|
public:
|
/*!***************************************************************************
|
@Function CGrowableArray
|
@Input nSize The size of the data (in bytes) that the array will contain
|
@Description Initialises the size of the data the array will contain to the
|
value that has been passed in and initialises the remaining
|
data members with default values.
|
*****************************************************************************/
|
CGrowableArray(const int nSize)
|
{
|
m_p = NULL;
|
m_nSize = nSize;
|
m_nCnt = 0;
|
}
|
|
/*!***************************************************************************
|
@Function ~CGrowableArray
|
@Description The destructor
|
*****************************************************************************/
|
~CGrowableArray()
|
{
|
FREE(m_p);
|
}
|
|
/*!***************************************************************************
|
@Function Append
|
@Input pData The data to append
|
@Input nCnt The amount of data elements to append
|
@Description Resizes the array and appends the new data that has been passed in.
|
*****************************************************************************/
|
void Append(const void * const pData, const int nCnt)
|
{
|
m_p = (char*)realloc(m_p, (m_nCnt + nCnt) * m_nSize);
|
_ASSERT(m_p);
|
|
memcpy(&m_p[m_nCnt * m_nSize], pData, nCnt * m_nSize);
|
m_nCnt += nCnt;
|
}
|
|
/*!***************************************************************************
|
@Function last
|
@Return char* The last element of the array
|
@Description Returns a pointer to the last element of the array.
|
*****************************************************************************/
|
char *last()
|
{
|
return at(m_nCnt-1);
|
}
|
|
/*!***************************************************************************
|
@Function at
|
@Input nIdx The index of the requested element
|
@Return char* The element at the specified index of the array
|
@Description Returns a pointer to the data at the specified index of the array.
|
*****************************************************************************/
|
char *at(const int nIdx)
|
{
|
return &m_p[nIdx * m_nSize];
|
}
|
|
/*!***************************************************************************
|
@Function size
|
@Return int The number of elements contained in the array
|
@Description Returns the number of elements contained in the array.
|
*****************************************************************************/
|
int size() const
|
{
|
return m_nCnt;
|
}
|
|
/*!***************************************************************************
|
@Function Surrender
|
@Output pData The pointer to surrender the data to
|
@Description Assigns the memory address of the data to the pointer that has
|
been passed in. Sets the class's number of elements and
|
data pointer back to their default values.
|
*****************************************************************************/
|
int Surrender(
|
char ** const pData)
|
{
|
int nCnt;
|
|
*pData = m_p;
|
nCnt = m_nCnt;
|
|
m_p = NULL;
|
m_nCnt = 0;
|
|
return nCnt;
|
}
|
};
|
|
/****************************************************************************
|
** Constants
|
****************************************************************************/
|
|
/****************************************************************************
|
** Local function definitions
|
****************************************************************************/
|
static bool FillBatch(
|
CBatch &batch,
|
const unsigned int * const pui32Idx, // input AND output; index array for triangle list
|
const char * const pVtx, // Input vertices
|
const int nStride, // Size of a vertex (in bytes)
|
const int nOffsetWeight, // Offset in bytes to the vertex bone-weights
|
EPVRTDataType eTypeWeight, // Data type of the vertex bone-weights
|
const int nOffsetIdx, // Offset in bytes to the vertex bone-indices
|
EPVRTDataType eTypeIdx, // Data type of the vertex bone-indices
|
const int nVertexBones); // Number of bones affecting each vertex
|
|
static bool BonesMatch(
|
const float * const pfIdx0,
|
const float * const pfIdx1);
|
|
/*****************************************************************************
|
** Functions
|
*****************************************************************************/
|
|
/*!***************************************************************************
|
@Function Create
|
@Output pnVtxNumOut vertex count
|
@Output pVtxOut Output vertices (program must free() this)
|
@Modified pui32Idx index array for triangle list
|
@Input nVtxNum vertex count
|
@Input pVtx vertices
|
@Input nStride Size of a vertex (in bytes)
|
@Input nOffsetWeight Offset in bytes to the vertex bone-weights
|
@Input eTypeWeight Data type of the vertex bone-weights
|
@Input nOffsetIdx Offset in bytes to the vertex bone-indices
|
@Input eTypeIdx Data type of the vertex bone-indices
|
@Input nTriNum Number of triangles
|
@Input nBatchBoneMax Number of bones a batch can reference
|
@Input nVertexBones Number of bones affecting each vertex
|
@Returns PVR_SUCCESS if successful
|
@Description Fills the bone batch structure
|
*****************************************************************************/
|
EPVRTError CPVRTBoneBatches::Create(
|
int * const pnVtxNumOut,
|
char ** const pVtxOut,
|
unsigned int * const pui32Idx,
|
const int nVtxNum,
|
const char * const pVtx,
|
const int nStride,
|
const int nOffsetWeight,
|
const EPVRTDataType eTypeWeight,
|
const int nOffsetIdx,
|
const EPVRTDataType eTypeIdx,
|
const int nTriNum,
|
const int nBatchBoneMax,
|
const int nVertexBones)
|
{
|
int i, j, k, nTriCnt;
|
CBatch batch;
|
std::list<CBatch> lBatch;
|
std::list<CBatch>::iterator iBatch, iBatch2;
|
CBatch **ppBatch;
|
unsigned int *pui32IdxNew;
|
const char *pV, *pV2;
|
PVRTVECTOR4 vWeight, vIdx;
|
PVRTVECTOR4 vWeight2, vIdx2;
|
std::vector<int> *pvDup;
|
CGrowableArray *pVtxBuf;
|
unsigned int ui32SrcIdx;
|
|
memset(this, 0, sizeof(*this));
|
|
if(nVertexBones <= 0 || nVertexBones > 4)
|
{
|
_RPT0(_CRT_WARN, "CPVRTBoneBatching() will only handle 1..4 bones per vertex.\n");
|
return PVR_FAIL;
|
}
|
|
memset(&vWeight, 0, sizeof(vWeight));
|
memset(&vWeight2, 0, sizeof(vWeight2));
|
memset(&vIdx, 0, sizeof(vIdx));
|
memset(&vIdx2, 0, sizeof(vIdx2));
|
|
batch.SetSize(nBatchBoneMax);
|
|
// Allocate some working space
|
ppBatch = (CBatch**)malloc(nTriNum * sizeof(*ppBatch));
|
pui32IdxNew = (unsigned int*)malloc(nTriNum * 3 * sizeof(*pui32IdxNew));
|
pvDup = new std::vector<int>[nVtxNum];
|
pVtxBuf = new CGrowableArray(nStride);
|
|
// Check what batches are necessary
|
for(i = 0; i < nTriNum; ++i)
|
{
|
// Build the batch
|
if(!FillBatch(batch, &pui32Idx[i * 3], pVtx, nStride, nOffsetWeight, eTypeWeight, nOffsetIdx, eTypeIdx, nVertexBones))
|
{
|
free(pui32IdxNew);
|
return PVR_FAIL;
|
}
|
|
// Update the batch list
|
for(iBatch = lBatch.begin(); iBatch != lBatch.end(); ++iBatch)
|
{
|
// Do nothing if an existing batch is a superset of this new batch
|
if(iBatch->Contains(batch))
|
{
|
break;
|
}
|
|
// If this new batch is a superset of an existing batch, replace the old with the new
|
if(batch.Contains(*iBatch))
|
{
|
*iBatch = batch;
|
break;
|
}
|
}
|
|
// If no suitable batch exists, create a new one
|
if(iBatch == lBatch.end())
|
{
|
lBatch.push_back(batch);
|
}
|
}
|
|
// Group batches into fewer batches. This simple greedy algorithm could be improved.
|
int nCurrent, nShortest;
|
std::list<CBatch>::iterator iShortest;
|
|
for(iBatch = lBatch.begin(); iBatch != lBatch.end(); ++iBatch)
|
{
|
for(;;)
|
{
|
nShortest = nBatchBoneMax;
|
iBatch2 = iBatch;
|
++iBatch2;
|
for(; iBatch2 != lBatch.end(); ++iBatch2)
|
{
|
nCurrent = iBatch->TestMerge(*iBatch2);
|
|
if(nCurrent >= 0 && nCurrent < nShortest)
|
{
|
nShortest = nCurrent;
|
iShortest = iBatch2;
|
}
|
}
|
|
if(nShortest < nBatchBoneMax)
|
{
|
iBatch->Merge(*iShortest);
|
lBatch.erase(iShortest);
|
}
|
else
|
{
|
break;
|
}
|
}
|
}
|
|
// Place each triangle in a batch.
|
for(i = 0; i < nTriNum; ++i)
|
{
|
if(!FillBatch(batch, &pui32Idx[i * 3], pVtx, nStride, nOffsetWeight, eTypeWeight, nOffsetIdx, eTypeIdx, nVertexBones))
|
{
|
free(pui32IdxNew);
|
return PVR_FAIL;
|
}
|
|
for(iBatch = lBatch.begin(); iBatch != lBatch.end(); ++iBatch)
|
{
|
if(iBatch->Contains(batch))
|
{
|
ppBatch[i] = &*iBatch;
|
break;
|
}
|
}
|
|
_ASSERT(iBatch != lBatch.end());
|
}
|
|
// Now that we know how many batches there are, we can allocate the output arrays
|
CPVRTBoneBatches::nBatchBoneMax = nBatchBoneMax;
|
pnBatches = (int*) calloc(lBatch.size() * nBatchBoneMax, sizeof(*pnBatches));
|
pnBatchBoneCnt = (int*) calloc(lBatch.size(), sizeof(*pnBatchBoneCnt));
|
pnBatchOffset = (int*) calloc(lBatch.size(), sizeof(*pnBatchOffset));
|
|
// Create the new triangle index list, the new vertex list, and the batch information.
|
nTriCnt = 0;
|
nBatchCnt = 0;
|
|
for(iBatch = lBatch.begin(); iBatch != lBatch.end(); ++iBatch)
|
{
|
// Write pnBatches, pnBatchBoneCnt and pnBatchOffset for this batch.
|
iBatch->Write(&pnBatches[nBatchCnt * nBatchBoneMax], &pnBatchBoneCnt[nBatchCnt]);
|
pnBatchOffset[nBatchCnt] = nTriCnt;
|
++nBatchCnt;
|
|
// Copy any triangle indices for this batch
|
for(i = 0; i < nTriNum; ++i)
|
{
|
if(ppBatch[i] != &*iBatch)
|
continue;
|
|
for(j = 0; j < 3; ++j)
|
{
|
ui32SrcIdx = pui32Idx[3 * i + j];
|
|
// Get desired bone indices for this vertex/tri
|
pV = &pVtx[ui32SrcIdx * nStride];
|
|
PVRTVertexRead(&vWeight, &pV[nOffsetWeight], eTypeWeight, nVertexBones);
|
PVRTVertexRead(&vIdx, &pV[nOffsetIdx], eTypeIdx, nVertexBones);
|
|
iBatch->GetVertexBoneIndices(&vIdx.x, &vWeight.x, nVertexBones);
|
_ASSERT(vIdx.x == 0 || vIdx.x != vIdx.y);
|
|
// Check the list of copies of this vertex for one with suitable bone indices
|
for(k = 0; k < (int)pvDup[ui32SrcIdx].size(); ++k)
|
{
|
pV2 = pVtxBuf->at(pvDup[ui32SrcIdx][k]);
|
|
PVRTVertexRead(&vWeight2, &pV2[nOffsetWeight], eTypeWeight, nVertexBones);
|
PVRTVertexRead(&vIdx2, &pV2[nOffsetIdx], eTypeIdx, nVertexBones);
|
|
if(BonesMatch(&vIdx2.x, &vIdx.x))
|
{
|
pui32IdxNew[3 * nTriCnt + j] = pvDup[ui32SrcIdx][k];
|
break;
|
}
|
}
|
|
if(k != (int)pvDup[ui32SrcIdx].size())
|
continue;
|
|
// Did not find a suitable duplicate of the vertex, so create one
|
pVtxBuf->Append(pV, 1);
|
pvDup[ui32SrcIdx].push_back(pVtxBuf->size() - 1);
|
|
PVRTVertexWrite(&pVtxBuf->last()[nOffsetIdx], eTypeIdx, nVertexBones, &vIdx);
|
|
pui32IdxNew[3 * nTriCnt + j] = pVtxBuf->size() - 1;
|
}
|
++nTriCnt;
|
}
|
}
|
_ASSERTE(nTriCnt == nTriNum);
|
_ASSERTE(nBatchCnt == (int)lBatch.size());
|
|
// Copy indices to output
|
memcpy(pui32Idx, pui32IdxNew, nTriNum * 3 * sizeof(*pui32IdxNew));
|
|
// Move vertices to output
|
*pnVtxNumOut = pVtxBuf->Surrender(pVtxOut);
|
|
// Free working memory
|
delete [] pvDup;
|
delete pVtxBuf;
|
FREE(ppBatch);
|
FREE(pui32IdxNew);
|
|
return PVR_SUCCESS;
|
}
|
|
/****************************************************************************
|
** Local functions
|
****************************************************************************/
|
|
/*!***********************************************************************
|
@Function FillBatch
|
@Modified batch The batch to fill
|
@Input pui32Idx Input index array for triangle list
|
@Input pVtx Input vertices
|
@Input nStride Size of a vertex (in bytes)
|
@Input nOffsetWeight Offset in bytes to the vertex bone-weights
|
@Input eTypeWeight Data type of the vertex bone-weights
|
@Input nOffsetIdx Offset in bytes to the vertex bone-indices
|
@Input eTypeIdx Data type of the vertex bone-indices
|
@Input nVertexBones Number of bones affecting each vertex
|
@Returns True if successful
|
@Description Creates a bone batch from a triangle.
|
*************************************************************************/
|
static bool FillBatch(
|
CBatch &batch,
|
const unsigned int * const pui32Idx,
|
const char * const pVtx,
|
const int nStride,
|
const int nOffsetWeight,
|
EPVRTDataType eTypeWeight,
|
const int nOffsetIdx,
|
EPVRTDataType eTypeIdx,
|
const int nVertexBones)
|
{
|
PVRTVECTOR4 vWeight, vIdx;
|
const char *pV;
|
int i;
|
bool bOk;
|
|
bOk = true;
|
batch.Clear();
|
for(i = 0; i < 3; ++i)
|
{
|
pV = &pVtx[pui32Idx[i] * nStride];
|
|
memset(&vWeight, 0, sizeof(vWeight));
|
PVRTVertexRead(&vWeight, &pV[nOffsetWeight], eTypeWeight, nVertexBones);
|
PVRTVertexRead(&vIdx, &pV[nOffsetIdx], eTypeIdx, nVertexBones);
|
|
if(nVertexBones >= 1 && vWeight.x != 0) bOk &= batch.Add((int)vIdx.x);
|
if(nVertexBones >= 2 && vWeight.y != 0) bOk &= batch.Add((int)vIdx.y);
|
if(nVertexBones >= 3 && vWeight.z != 0) bOk &= batch.Add((int)vIdx.z);
|
if(nVertexBones >= 4 && vWeight.w != 0) bOk &= batch.Add((int)vIdx.w);
|
}
|
return bOk;
|
}
|
|
/*!***********************************************************************
|
@Function BonesMatch
|
@Input pfIdx0 A float 4 array
|
@Input pfIdx1 A float 4 array
|
@Returns True if the two float4 arraus are identical
|
@Description Checks if the two float4 arrays are identical.
|
*************************************************************************/
|
static bool BonesMatch(
|
const float * const pfIdx0,
|
const float * const pfIdx1)
|
{
|
int i;
|
|
for(i = 0; i < 4; ++i)
|
{
|
if(pfIdx0[i] != pfIdx1[i])
|
return false;
|
}
|
|
return true;
|
}
|
|
/*****************************************************************************
|
End of file (PVRTBoneBatch.cpp)
|
*****************************************************************************/
|