I am including a chapter on loading .FBX files in my upcoming book. The samples will be free for download so I will upload the one for loading mesh data and generating vertex/index buffers later, but for now you can take a look at the mesh-loader’s header and source files. They are fully commented so it should be clear what they are doing.
#ifndef ___MESH_H___
#define ___MESH_H___
#include "../main.h"
#include <map>
#include <vector>
class CMesh {
public :
// == Various constructors.
CMesh();
~CMesh();
// == Enumerations.
enum {
FC_MAX_UVS = 8, /**< Maximum UV coordinates. */
FC_NORMALS = (1 << 0), /**< Normal data is included. */
FC_COLORS = (1 << 1), /**< Color data is included. */
};
// == Types.
/**
* A raw fully expanded vertex buffer.
*/
typedef struct FC_RAW_VERTEX {
FbxDouble3 vPosition; /**< Vertex position. */
FbxDouble3 vColor; /**< Vertex color. */
FbxDouble3 vNormal; /**< Vertex normal. */
FbxDouble2 vUvs[FC_MAX_UVS]; /**< Texture coordinates. */
// == Operators.
/** Less-than operator. */
bool operator < ( const FC_RAW_VERTEX &_vbOther ) const {
return std::memcmp( this, &_vbOther, sizeof( FC_RAW_VERTEX ) ) < 0;
}
/** Equality operator. */
bool operator == ( const FC_RAW_VERTEX &_vbOther ) const {
return std::memcmp( this, &_vbOther, sizeof( FC_RAW_VERTEX ) ) == 0;
}
} * LPFC_VERTEX_BUFFER;
/**
* A set of materials which acts as a key to an std::map.
*/
typedef struct FC_MATERIAL_KEY {
const char * pcMaterial[FC_MAX_UVS]; /**< A set of materials. */
// == Operators.
bool operator < ( const FC_MATERIAL_KEY &_fmkOther ) const {
// Inlined for speed.
for ( u32 I = 0UL; I < FC_MAX_UVS; ++I ) {
// If we are NULL and they are not, we are below them.
if ( !pcMaterial && _fmkOther.pcMaterial ) { return true; }
// If they are NULL and we are not, we are above them.
if ( pcMaterial && !_fmkOther.pcMaterial ) { return false; }
// If we are both non-NULL, use a string compare.
if ( pcMaterial && _fmkOther.pcMaterial ) {
int iCmp = ::strcmp( pcMaterial, _fmkOther.pcMaterial );
if ( iCmp < 0 ) { return true; }
if ( iCmp > 0 ) { return false; }
// Strings are the same. Continue.
}
// This case means we are both NULL or are the same string. Continue with the loop.
}
// We are the same all the way.
return false;
}
bool operator == ( const FC_MATERIAL_KEY &_fmkOther ) const {
// Inlined for speed.
for ( u32 I = 0UL; I < FC_MAX_UVS; ++I ) {
// Just check for anything being different.
if ( !pcMaterial && _fmkOther.pcMaterial ) { return false; }
if ( pcMaterial && !_fmkOther.pcMaterial ) { return false; }
if ( pcMaterial && _fmkOther.pcMaterial ) {
if ( ::strcmp( pcMaterial, _fmkOther.pcMaterial ) != 0 ) { return false; }
}
}
// Found no differences.
return true;
}
} * LPFC_MATERIAL_KEY;
// == Functions.
/**
* Loads a mesh.
*
* \param _pfmMesh
* \return Returns true if the mesh was loaded.
*/
bool LoadMesh( FbxMesh * _pfmMesh );
protected :
// == Types.
/**
* Our own intermediate representation of a face/polygon.
*/
typedef struct FC_FACE {
/** A nested structure representing one vertex on a face. */
typedef struct FC_FACE_VERTEX {
u32 u32VertexIndex; /**< Vertex index. */
u32 u32NormalIndex; /**< Normal index. */
u32 u32ColorIndex; /**< Color index. */
u32 u32UVIndex[FC_MAX_UVS]; /**< UV index. */
} * LPFC_FACE_VERTEX;
/** In order to save memory and time, a single large pool
of FC_FACE_VERTEX will be maintained. All faces will simply
reference parts of it, so all we need to store on this
structure is the starting index and the total vertices. */
u32 u32StartVertex;
u32 u32TotalVertices;
} * LPFC_FACE;
/**
* Pairing our intermediate faces with the vertex buffers they will
* later be used to fill.
*/
typedef struct FC_FACE_VERTEX_PAIR {
std::vector<FC_FACE> vFaces; /**< The faces in this bucket. */
std::vector<FC_RAW_VERTEX>
vVertices; /**< The vertices created by the faces. */
} * LPFC_FACE_VERTEX_PAIR;
/**
* Pairing a vertex buffer with an index buffer.
*/
typedef struct FC_INDEX_VERTEX_PAIR {
std::vector<u32> vIndices; /**< The indices. */
std::vector<FC_RAW_VERTEX>
vVertices; /**< The vertices. */
} * LPFC_INDEX_VERTEX_PAIR;
/**
* Index buffer ranges associated with materials.
*/
typedef struct FC_INDEX_BUFFER_RANGE {
u32 u32Start; /**< The start index. */
u32 u32Total; /**< Total indices. */
} * LPFC_INDEX_BUFFER_RANGE;
/**
* Data to pass to AddFace().
*/
typedef struct FC_ADD_VERTEX {
u32 u32IndexCount; /**< Index count (in/out). */
} * LPFC_ADD_VERTEX;
// == Members.
FbxMesh * m_pfmMesh; /**< The source mesh. */
u32 m_u32Flags; /**< Which data is included in the vertex buffer? */
u32 m_u32TexCoords; /**< How many texture coordinates are there? */
std::map<FC_MATERIAL_KEY, FC_INDEX_BUFFER_RANGE>
m_mMaterialMap; /**< Mapping of which indices go to which materials. */
FC_INDEX_VERTEX_PAIR m_ivpVertexIndexBuffer; /**< The final vertex and index buffers. */
// == Functions.
/**
* Adds a vertex to a vertex pool and adds an intermediate face to the proper bucket.
*
* \param _u32FaceIndex Index of the face to add.
* \param _mBuckets The buckets to which to add the face.
* \param _vVertices The pool to which to add the vertices.
* \param _fmkKey The key for _mBuffers to know to which vertex buffer to add the vertex.
* \param _favAddVert In/out parameters for adding the vertex.
* \return Returns true if the vertex was added.
*/
bool AddFace( u32 _u32FaceIndex,
std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> &_mBuckets,
std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices,
const FC_MATERIAL_KEY &_fmkKey,
FC_ADD_VERTEX &_favAddVert ) const;
/**
* Adds a (non-intermediate) vertex to a vertex buffer. These vertices are
* in fully expanded FC_RAW_VERTEX form.
*
* \param _u32FaceIndex Index of the vertex to add.
* \param _vDest The the std::vector to which to add the vertex.
* \param _vVertices The pool of intermediate vertices from which to read.
* \return Returns true if the vertex was added.
*/
bool AddVertex( u32 _u32VertIndex,
std::vector<FC_RAW_VERTEX> &_vDest,
const std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices ) const;
/**
* Takes a raw triangle-list vertex buffer as input and outputs the reduced vertex
* buffer and new index buffer.
*
* \param _vInputVerts Input vertex buffer.
* \param _vOutputVerts The output vertex buffer.
* \param _vIndices The output index buffer.
* \return Returns true if the index buffer was created successfully.
*/
static bool CreateIndexBuffer( const std::vector<FC_RAW_VERTEX> &_vInputVerts,
std::vector<FC_RAW_VERTEX> &_vOutputVerts,
std::vector<u32> &_vIndices );
};
#endif // #ifndef ___MESH_H___
#include "FCMesh.h"
// == Various constructors.
CMesh::CMesh() :
m_pfmMesh( NULL ),
m_u32Flags( 0UL ),
m_u32TexCoords( 0UL ) {
}
CMesh::~CMesh() {
}
// == Functions.
/**
* Loads a mesh.
*
* \param _pfmMesh
* \return Returns true if the mesh was loaded.
*/
bool CMesh::LoadMesh( FbxMesh * _pfmMesh ) {
m_pfmMesh = _pfmMesh;
u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
u32 u32TotalNormals = m_pfmMesh->GetLayer( 0 )->GetNormals() ? m_pfmMesh->GetLayer( 0 )->GetNormals()->GetDirectArray().GetCount() :
0UL;
u32 u32TotalColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors() ? m_pfmMesh->GetLayer( 0 )->GetVertexColors()->GetDirectArray().GetCount() :
0UL;
if ( u32TotalNormals ) { m_u32Flags |= FC_NORMALS; }
if ( u32TotalColors ) { m_u32Flags |= FC_COLORS; }
// Each layer may have 1 or 0 diffuse texture UV?. Count them layer-by-layer.
for ( u32 I = 0UL; I < u32TotalLayers; ++I ) {
const FbxLayerElementUV * pleuUvs = m_pfmMesh->GetLayer( I )->GetUVs( FbxLayerElement::eTextureDiffuse );
m_u32TexCoords += pleuUvs ? 1UL : 0UL;
}
m_u32TexCoords = std::min<u32>( m_u32TexCoords, FC_MAX_UVS );
// #include <map> added to the header.
std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> mBuckets;
std::vector<FC_FACE::FC_FACE_VERTEX> vVertexPool;
u32 ui32FaceCount = m_pfmMesh->GetPolygonCount();
FC_ADD_VERTEX favAddVertex = { 0UL };
for ( u32 I = 0; I < ui32FaceCount; ++I ) {
//u32 ui32PolyTotal = m_pfmMesh->GetPolygonSize( I );
//u32 ui32PolyStart = m_pfmMesh->GetPolygonVertexIndex( I );
FC_MATERIAL_KEY fmkThisFaceMaterials;
for ( u32 J = 0; J < FC_MAX_UVS; ++J ) {
// Be sure to fill a value into every pcMaterial[], even if there are not enough
// layers on the mesh.
if ( J >= u32TotalLayers ) {
fmkThisFaceMaterials.pcMaterial[J] = NULL;
continue;
}
// There is a layer here. Get its material name.
const FbxLayerElementMaterial * plemMaterials = m_pfmMesh->GetLayer( J )->GetMaterials();
if ( !plemMaterials || plemMaterials->GetMappingMode() == FbxGeometryElement::eAllSame ) {
// No material for this layer or all layers use material 0.
plemMaterials = m_pfmMesh->GetLayer( 0 )->GetMaterials();
}
// Get the index of the material for this face and layer.
u32 u32MaterialIndex = plemMaterials->GetIndexArray().GetAt( I );
// Index into the mesh's array of materials to get the name.
fmkThisFaceMaterials.pcMaterial[J] = reinterpret_cast<FbxSurfaceMaterial *>(m_pfmMesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>( u32MaterialIndex ))->GetName();
}
if ( !AddFace( I, mBuckets, vVertexPool, fmkThisFaceMaterials, favAddVertex ) ) {
return false;
}
}
// Now we can triangulate the faces using our intermediate faces.
// Note that it is possible to convert a whole face to our intermediate
// format and then to convert that face just afterwards, but we
// keep these processes separate for clarity here.
// Go over each bucket.
for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I ) {
// Go over each face in the bucket.
for ( std::vector<FC_FACE>::iterator J = I->second.vFaces.begin(); J != I->second.vFaces.end(); ++J ) {
// Go over each vertex on the face.
u32 u32PolyTotal = J->u32TotalVertices;
u32 u32PolyStart = J->u32StartVertex;
// Every triangle's first vertex is at index 0.
// Each next vertex is at J and the each final vertex is at J + 1.
// Hence the loop goes from 1 to ui32PolyTotal - 1.
u32 u32LoopEnd = u32PolyTotal - 1;
for ( u32 J = 1; J < u32LoopEnd; ++J ) {
// 0 added for clarity. Each index is offset by ui32PolyStart.
u32 u32A = 0 + u32PolyStart;
u32 u32B = J + u32PolyStart;
u32 u32C = J + 1 + u32PolyStart;
if ( !AddVertex( u32A, I->second.vVertices, vVertexPool ) ) { return false; }
if ( !AddVertex( u32B, I->second.vVertices, vVertexPool ) ) { return false; }
if ( !AddVertex( u32C, I->second.vVertices, vVertexPool ) ) { return false; }
}
}
// We no longer need this intermediate face list. Remove
// it to save memory.
I->second.vFaces.swap( std::vector<FC_FACE>() );
}
// No longer need the pool of vertices. Remove it to save memory.
vVertexPool.swap( std::vector<FC_FACE::FC_FACE_VERTEX>() );
// We will have a vector of index buffer/vertex buffer pairs
// such that each element in the vector is a one-to-one
// correlation to the materials in mBuckets.
std::vector<FC_INDEX_VERTEX_PAIR> vIndexVertexBuffers;
// The possible exception should be handled.
vIndexVertexBuffers.resize( mBuckets.size() );
// Go over each vertex buffer in mBuckets and convert
// to an index buffer/vertex pair.
size_t sNewIdx = 0;
for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I, ++sNewIdx ) {
if ( !CreateIndexBuffer( I->second.vVertices, vIndexVertexBuffers[sNewIdx].vVertices, vIndexVertexBuffers[sNewIdx].vIndices ) ) {
return false;
}
// We no longer need the expanded vertex buffer.
I->second.vVertices.swap( std::vector<FC_RAW_VERTEX>() );
}
// Finalize the mesh data by combinging all the vertices
// and indices into one vertex buffer and one index buffer,
// and keep a map of ranges into the index buffers where
// the vertices for a material are.
sNewIdx = 0;
for ( std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR>::iterator I = mBuckets.begin(); I != mBuckets.end(); ++I, ++sNewIdx ) {
// Store the range of indices for this material.
// Handling the possible exception is left to the reader.
FC_INDEX_BUFFER_RANGE ibrRange = {
static_cast<u32>(m_ivpVertexIndexBuffer.vIndices.size()),
static_cast<u32>(vIndexVertexBuffers[sNewIdx].vIndices.size()),
};
m_mMaterialMap[I->first] = ibrRange;
// The vertices can be added to the final vertex buffer
// via a straight copy, but the indices need to be
// adjusted to point to the new locations of the
// vertices to which they originally point.
// Add the indices one-by-one and apply that offset
// to each as they are added to the final index buffer.
// m_ivpVertexIndexBuffer.vVertices.size() is the offset
// amount.
for ( size_t J = 0; J < vIndexVertexBuffers[sNewIdx].vIndices.size(); ++J ) {
// Handling the possible exception is left to the reader.
m_ivpVertexIndexBuffer.vIndices.push_back( static_cast<u32>(vIndexVertexBuffers[sNewIdx].vIndices[J] + m_ivpVertexIndexBuffer.vVertices.size()) );
}
// Now add the vertices.
for ( size_t J = 0; J < vIndexVertexBuffers[sNewIdx].vVertices.size(); ++J ) {
// Handling the possible exception is left to the reader.
m_ivpVertexIndexBuffer.vVertices.push_back( vIndexVertexBuffers[sNewIdx].vVertices[J] );
}
}
std::cout << "Loaded mesh " << m_pfmMesh->GetName() << "." << std::endl;
std::cout << '\t' << m_ivpVertexIndexBuffer.vVertices.size() << " vertices, " << m_ivpVertexIndexBuffer.vIndices.size() << " indices." << std::endl;
return true;
}
/**
* Adds a vertex to a vertex pool and adds an intermediate face to the proper bucket.
*
* \param _u32FaceIndex Index of the face to add.
* \param _mBuckets The buckets to which to add the face.
* \param _vVertices The pool to which to add the vertices.
* \param _fmkKey The key for _mBuffers to know to which vertex buffer to add the vertex.
* \param _favAddVert In/out parameters for adding the vertex.
* \return Returns true if the vertex was added.
*/
bool CMesh::AddFace( u32 _u32FaceIndex,
std::map<FC_MATERIAL_KEY, FC_FACE_VERTEX_PAIR> &_mBuckets,
std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices,
const FC_MATERIAL_KEY &_fmkKey,
FC_ADD_VERTEX &_favAddVert ) const {
u32 ui32PolyTotal = m_pfmMesh->GetPolygonSize( _u32FaceIndex );
u32 ui32PolyStart = m_pfmMesh->GetPolygonVertexIndex( _u32FaceIndex );
int * piFacePool = m_pfmMesh->GetPolygonVertices();
// Create the intermediate face.
FC_FACE ffFace = {
static_cast<u32>(_vVertices.size()), // ffFace.u32StartVertex.
ui32PolyTotal, // ffFace.u32TotalVertices
};
// Add the face to the appropriate bucket.
// It is encouraged that you handle the possible exception here
// but it has been omitted here for brevity.
_mBuckets[_fmkKey].vFaces.push_back( ffFace );
// Go over each vertex in the face.
for ( u32 I = 0UL; I < ui32PolyTotal; ++I ) {
FC_FACE::FC_FACE_VERTEX ffvThisVert;
u32 u32VertIndex = piFacePool[ui32PolyStart+I];
ffvThisVert.u32VertexIndex = u32VertIndex;
if ( m_u32Flags & FC_NORMALS ) {
ffvThisVert.u32NormalIndex = 0UL;
const FbxLayerElementNormal * plenNormals = m_pfmMesh->GetLayer( 0 )->GetNormals();
switch ( plenNormals->GetMappingMode() ) {
case FbxGeometryElement::eByControlPoint : {
// The normal index is based on the vertex index.
if ( plenNormals->GetReferenceMode() == FbxLayerElement::eDirect ) {
// The normal index is the same as the vertex index.
ffvThisVert.u32NormalIndex = u32VertIndex;
}
else if ( plenNormals->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
// The normal index is found by plugging the vertex index into an array of indices.
ffvThisVert.u32NormalIndex = plenNormals->GetIndexArray().GetAt( u32VertIndex );
}
if ( ffvThisVert.u32NormalIndex >= static_cast<u32>(plenNormals->GetDirectArray().GetCount()) ) {
std::cout << "Invalid normal index." << std::endl;
}
break;
}
case FbxGeometryElement::eByPolygonVertex : {
// The normal index is based off _favAddVert.u32IndexCount, which is
// incremented once for each vertex over which we loop.
if ( plenNormals->GetReferenceMode() == FbxLayerElement::eDirect ) {
// It is just _favAddVert.u32IndexCount.
ffvThisVert.u32NormalIndex = _favAddVert.u32IndexCount;
}
else if ( plenNormals->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
// The normal index is found by plugging _favAddVert.u32IndexCount into an array of indices.
ffvThisVert.u32NormalIndex = plenNormals->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
}
break;
}
default : {
std::cout << "Unsupported normal mapping mode." << std::endl;
}
}
}
if ( m_u32Flags & FC_COLORS ) {
ffvThisVert.u32ColorIndex = 0UL;
const FbxLayerElementVertexColor * plevcColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors();
switch ( plevcColors->GetMappingMode() ) {
case FbxGeometryElement::eByControlPoint : {
if ( plevcColors->GetReferenceMode() == FbxLayerElement::eDirect ) {
ffvThisVert.u32ColorIndex = u32VertIndex;
}
else if ( plevcColors->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
ffvThisVert.u32ColorIndex = plevcColors->GetIndexArray().GetAt( u32VertIndex );
}
if ( ffvThisVert.u32ColorIndex >= static_cast<u32>(plevcColors->GetDirectArray().GetCount()) ) {
std::cout << "Invalid vertex color index." << std::endl;
}
break;
}
case FbxGeometryElement::eByPolygonVertex : {
if ( plevcColors->GetReferenceMode() == FbxLayerElement::eDirect ) {
ffvThisVert.u32ColorIndex = _favAddVert.u32IndexCount;
}
else if ( plevcColors->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
ffvThisVert.u32ColorIndex = plevcColors->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
}
break;
}
default : {
std::cout << "Unsupported vertex color mapping mode." << std::endl;
}
}
}
u32 u32TexIndex = 0UL;
u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
for ( u32 K = 0UL; K < u32TotalLayers && u32TexIndex < m_u32TexCoords; ++K ) {
const FbxLayerElementUV * pleuvUvElements = m_pfmMesh->GetLayer( K )->GetUVs( FbxLayerElement::eTextureDiffuse );
if ( !pleuvUvElements ) { continue; }
u32 u32Uv = 0;
switch ( pleuvUvElements->GetMappingMode() ) {
case FbxGeometryElement::eByControlPoint : {
if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eDirect ) {
u32Uv = u32VertIndex;
}
else if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
u32Uv = pleuvUvElements->GetIndexArray().GetAt( u32VertIndex );
}
break;
}
case FbxGeometryElement::eByPolygonVertex : {
if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eDirect ) {
u32Uv = _favAddVert.u32IndexCount;
}
else if ( pleuvUvElements->GetReferenceMode() == FbxLayerElement::eIndexToDirect ) {
u32Uv = pleuvUvElements->GetIndexArray().GetAt( _favAddVert.u32IndexCount );
}
break;
}
default : {
std::cout << "Unsupported UV mapping mode." << std::endl;
}
}
ffvThisVert.u32UVIndex[u32TexIndex++] = u32Uv;
}
// Add the vertex to the pool. Normally you should handle the exception
// this could throw, but it is removed for brevity here.
_vVertices.push_back( ffvThisVert );
++_favAddVert.u32IndexCount;
}
return true;
}
/**
* Adds a (non-intermediate) vertex to a vertex buffer. These vertices are
* in fully expanded FC_RAW_VERTEX form.
*
* \param _u32FaceIndex Index of the vertex to add.
* \param _vDest The the std::vector to which to add the vertex.
* \param _vVertices The pool of intermediate vertices from which to read.
* \return Returns true if the vertex was added.
*/
bool CMesh::AddVertex( u32 _u32VertIndex,
std::vector<FC_RAW_VERTEX> &_vDest,
const std::vector<FC_FACE::FC_FACE_VERTEX> &_vVertices ) const {
// For clarity.
const FC_FACE::FC_FACE_VERTEX & fvThisVert = _vVertices[_u32VertIndex];
// Initialize the vertex so that the position is filled and
// the rest are all 0's.
FC_RAW_VERTEX fvbBuffer = {
FbxDouble3( m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][0],
m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][1],
m_pfmMesh->GetControlPoints()[fvThisVert.u32VertexIndex][2] )
};
if ( m_u32Flags & FC_NORMALS ) {
const FbxLayerElementNormal * plenNormals = m_pfmMesh->GetLayer( 0 )->GetNormals();
fvbBuffer.vNormal = FbxDouble3( plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[0],
plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[1],
plenNormals->GetDirectArray().GetAt( fvThisVert.u32NormalIndex )[2] );
}
if ( m_u32Flags & FC_COLORS ) {
const FbxLayerElementVertexColor * plevcColors = m_pfmMesh->GetLayer( 0 )->GetVertexColors();
fvbBuffer.vNormal = FbxDouble3( plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[0],
plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[1],
plevcColors->GetDirectArray().GetAt( fvThisVert.u32ColorIndex )[2] );
}
u32 u32TexIndex = 0UL;
u32 u32TotalLayers = m_pfmMesh->GetLayerCount();
for ( u32 K = 0UL; K < u32TotalLayers && u32TexIndex < m_u32TexCoords; ++K ) {
const FbxLayerElementUV * pleuvUvElements = m_pfmMesh->GetLayer( K )->GetUVs( FbxLayerElement::eTextureDiffuse );
if ( !pleuvUvElements ) { continue; }
fvbBuffer.vUvs[u32TexIndex] = FbxDouble3( pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[0],
pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[1],
pleuvUvElements->GetDirectArray().GetAt( fvThisVert.u32UVIndex[u32TexIndex] )[2] );
++u32TexIndex;
}
// You should handle the possible exception here which
// has been omitted for brevity.
_vDest.push_back( fvbBuffer );
return true;
}
/**
* Takes a raw triangle-list vertex buffer as input and outputs the reduced vertex
* buffer and new index buffer.
*
* \param _vInputVerts Input vertex buffer.
* \param _vOutputVerts The output vertex buffer.
* \param _vIndices The output index buffer.
* \return Returns true if the index buffer was created successfully.
*/
bool CMesh::CreateIndexBuffer( const std::vector<FC_RAW_VERTEX> &_vInputVerts,
std::vector<FC_RAW_VERTEX> &_vOutputVerts,
std::vector<u32> &_vIndices ) {
// Keep track of which vertices have been added to the output vertex buffer and
// the index where they are in that buffer.
std::map<FC_RAW_VERTEX, u32> mUsedVerts;
// For each vertex.
for ( size_t I = 0; I < _vInputVerts.size(); ++I ) {
// Has it already been added?
u32 u32Index;
std::map<FC_RAW_VERTEX, u32>::const_iterator iExisting = mUsedVerts.find( _vInputVerts );
if ( iExisting == mUsedVerts.end() ) {
// Has not yet been added. Add it to the end of the
// list and store its index.
u32Index = static_cast<u32>(_vOutputVerts.size());
// You should handle the possible exceptions here,
// which have been unhandled here for brevity.
_vOutputVerts.push_back( _vInputVerts );
mUsedVerts[_vInputVerts] = u32Index;
}
else {
// It has been added. The index that goes
// into the index buffer is the index of
// the already-added vertex.
u32Index = iExisting->second;
}
// Add the index to the index buffer, handling the possible
// exception in your code.
_vIndices.push_back( u32Index );
}
return true;
}
This class loads a single mesh given an FbxMesh pointer.
It extracts the vertices, colors, normals, etc., and generates vertex buffers and index buffers (removing duplicate vertices), one for each material.
It then combines them into one vertex buffer with a table of ranges for each material, which is what you want for optimal rendering.
You should be able to follow the code with the comments there to help.
What is not yet shown here (will be in a later sub-chapter) is that this loading function should also return back to the caller a list of materials referenced by the mesh, which the higher-level functions can use to then determine which textures are referenced.
The meshes, materials, and textures would go into different parts of the file, and textures may optionally not be embedded for sharing purposes.
The book will be out later this year.
L. Spiro