From 492aa8358b37369aebb8059b05ada1e600119319 Mon Sep 17 00:00:00 2001 From: ulfjorensen Date: Sat, 7 Jun 2008 23:21:36 +0000 Subject: [PATCH] - added a post processing step to limit the bone count per vertex git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@56 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- code/Importer.cpp | 2 + code/LimitBoneWeightsProcess.cpp | 109 ++++++++++++++++++++++++++++++ code/LimitBoneWeightsProcess.h | 71 +++++++++++++++++++ code/XFileParser.cpp | 2 +- include/aiPostProcess.h | 11 +++ workspaces/vc8/assimp.vcproj | 9 +++ workspaces/vc8/assimp_view.vcproj | 2 +- 7 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 code/LimitBoneWeightsProcess.cpp create mode 100644 code/LimitBoneWeightsProcess.h diff --git a/code/Importer.cpp b/code/Importer.cpp index 0d8dd4ab7..5fef34379 100644 --- a/code/Importer.cpp +++ b/code/Importer.cpp @@ -89,6 +89,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "KillNormalsProcess.h" #include "SplitLargeMeshes.h" #include "PretransformVertices.h" +#include "LimitBoneWeightsProcess.h" #include "../include/DefaultLogger.h" using namespace Assimp; @@ -143,6 +144,7 @@ Importer::Importer() : mPostProcessingSteps.push_back( new JoinVerticesProcess()); mPostProcessingSteps.push_back( new SplitLargeMeshesProcess_Vertex()); mPostProcessingSteps.push_back( new ConvertToLHProcess()); + mPostProcessingSteps.push_back( new LimitBoneWeightsProcess()); } // ------------------------------------------------------------------------------------------------ diff --git a/code/LimitBoneWeightsProcess.cpp b/code/LimitBoneWeightsProcess.cpp new file mode 100644 index 000000000..964203d63 --- /dev/null +++ b/code/LimitBoneWeightsProcess.cpp @@ -0,0 +1,109 @@ +/** Implementation of the LimitBoneWeightsProcess post processing step */ + +#include +#include +#include "LimitBoneWeightsProcess.h" +#include "../include/aiPostProcess.h" +#include "../include/aiMesh.h" +#include "../include/aiScene.h" + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LimitBoneWeightsProcess::LimitBoneWeightsProcess() +{ + // TODO: (thom) make this configurable from somewhere? + mMaxWeights = 4; +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LimitBoneWeightsProcess::~LimitBoneWeightsProcess() +{ + // nothing to do here +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the processing step is present in the given flag field. +bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const +{ + return (pFlags & aiProcess_LimitBoneWeights) != 0; +} + +// ------------------------------------------------------------------------------------------------ +// Executes the post processing step on the given imported data. +void LimitBoneWeightsProcess::Execute( aiScene* pScene) +{ + for( unsigned int a = 0; a < pScene->mNumMeshes; a++) + ProcessMesh( pScene->mMeshes[a]); +} + +// ------------------------------------------------------------------------------------------------ +// Unites identical vertices in the given mesh +void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh) +{ + if( !pMesh->HasBones()) + return; + + // collect all bone weights per vertex + typedef std::vector< std::vector< Weight > > WeightsPerVertex; + WeightsPerVertex vertexWeights( pMesh->mNumVertices); + + // collect all weights per vertex + for( unsigned int a = 0; a < pMesh->mNumBones; a++) + { + const aiBone* bone = pMesh->mBones[a]; + for( unsigned int b = 0; b < bone->mNumWeights; b++) + { + const aiVertexWeight& w = bone->mWeights[b]; + vertexWeights[w.mVertexId].push_back( Weight( a, w.mWeight)); + } + } + + // now cut the weight count if it exceeds the maximum + for( WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit) + { + if( vit->size() <= mMaxWeights) + continue; + + // more than the defined maximum -> first sort by weight in descending order. That's + // why we defined the < operator in such a weird way. + std::sort( vit->begin(), vit->end()); + + // now kill everything beyond the maximum count + vit->erase( vit->begin() + mMaxWeights, vit->end()); + + // and renormalize the weights + float sum = 0.0f; + for( std::vector::const_iterator it = vit->begin(); it != vit->end(); ++it) + sum += it->mWeight; + for( std::vector::iterator it = vit->begin(); it != vit->end(); ++it) + it->mWeight /= sum; + } + + // rebuild the vertex weight array for all bones + typedef std::vector< std::vector< aiVertexWeight > > WeightsPerBone; + WeightsPerBone boneWeights( pMesh->mNumBones); + for( unsigned int a = 0; a < vertexWeights.size(); a++) + { + const std::vector& vw = vertexWeights[a]; + for( std::vector::const_iterator it = vw.begin(); it != vw.end(); ++it) + boneWeights[it->mBone].push_back( aiVertexWeight( a, it->mWeight)); + } + + // and finally copy the vertex weight list over to the mesh's bones + for( unsigned int a = 0; a < pMesh->mNumBones; a++) + { + const std::vector& bw = boneWeights[a]; + aiBone* bone = pMesh->mBones[a]; + // ignore the bone if no vertex weights were removed there + if( bw.size() == bone->mNumWeights) + continue; + + // copy the weight list. should always be less weights than before, so we don't need a new allocation + assert( bw.size() < bone->mNumWeights); + bone->mNumWeights = (unsigned int) bw.size(); + memcpy( bone->mWeights, &bw[0], bw.size() * sizeof( aiVertexWeight)); + } +} diff --git a/code/LimitBoneWeightsProcess.h b/code/LimitBoneWeightsProcess.h new file mode 100644 index 000000000..d8b37ac32 --- /dev/null +++ b/code/LimitBoneWeightsProcess.h @@ -0,0 +1,71 @@ +/** Defines a post processing step to limit the number of bones affecting a single vertex. */ +#ifndef AI_LIMITBONEWEIGHTSPROCESS_H_INC +#define AI_LIMITBONEWEIGHTSPROCESS_H_INC + +#include "BaseProcess.h" + +struct aiMesh; + +namespace Assimp +{ + +// --------------------------------------------------------------------------- +/** This post processing step limits the number of bones affecting a vertex +* to a certain maximum value. If a vertex is affected by more than that number +* of bones, the bone weight with the least influence on this vertex are removed. +* The other weights on this bone are then renormalized to assure the sum weight +* to be 1. +*/ +class LimitBoneWeightsProcess : public BaseProcess +{ + friend class Importer; + +protected: + /** Constructor to be privately used by Importer */ + LimitBoneWeightsProcess(); + + /** Destructor, private as well */ + ~LimitBoneWeightsProcess(); + +public: + // ------------------------------------------------------------------- + /** Returns whether the processing step is present in the given flag field. + * @param pFlags The processing flags the importer was called with. A bitwise + * combination of #aiPostProcessSteps. + * @return true if the process is present in this flag fields, false if not. + */ + bool IsActive( unsigned int pFlags) const; + + // ------------------------------------------------------------------- + /** Executes the post processing step on the given imported data. + * At the moment a process is not supposed to fail. + * @param pScene The imported data to work at. + */ + void Execute( aiScene* pScene); + +protected: + // ------------------------------------------------------------------- + /** Limits the bone weight count for all vertices in the given mesh. + * @param pMesh The mesh to process. + */ + void ProcessMesh( aiMesh* pMesh); + +protected: + /** Describes a bone weight on a vertex */ + struct Weight + { + unsigned int mBone; ///< Index of the bone + float mWeight; ///< Weight of that bone on this vertex + Weight() { } + Weight( unsigned int pBone, float pWeight) { mBone = pBone; mWeight = pWeight; } + /** Comparision operator to sort bone weights by descending weight */ + bool operator < (const Weight& pWeight) const { return mWeight > pWeight.mWeight; } + }; + + /** Maximum number of bones influencing any single vertex. */ + unsigned int mMaxWeights; +}; + +} // end of namespace Assimp + +#endif // AI_LIMITBONEWEIGHTSPROCESS_H_INC diff --git a/code/XFileParser.cpp b/code/XFileParser.cpp index 037973d53..735923958 100644 --- a/code/XFileParser.cpp +++ b/code/XFileParser.cpp @@ -79,7 +79,7 @@ XFileParser::XFileParser( const std::vector& pBuffer) else if( strncmp( P + 8, "bin ", 4) == 0) mIsBinaryFormat = true; else - ThrowException( "Unsupported xfile format"); + ThrowException( boost::str( boost::format( "Unsupported xfile format '%c%c%c%c'") % P[8] % P[9] % P[10] % P[11])); // float size mBinaryFloatSize = (unsigned int)(P[12] - 48) * 1000 diff --git a/include/aiPostProcess.h b/include/aiPostProcess.h index d9f82e6d5..c19318a49 100644 --- a/include/aiPostProcess.h +++ b/include/aiPostProcess.h @@ -125,6 +125,17 @@ enum aiPostProcessSteps * the normal list will be zeroed. */ aiProcess_PreTransformVertices = 0x100, + + /** Limits the number of bones simultaneously affecting a single vertex + * to a maximum value. If any vertex is affected by more than that number + * of bones, the least important vertex weights are removed and the remaining + * vertex weights are renormalized so that the weights still sum up to 1. + * At the moment the maximum bone count is hardcoded to 4. + * + * If you intend to perform the skinning in hardware, this post processing step + * might be of interest for you. + */ + aiProcess_LimitBoneWeights = 0x200 }; // --------------------------------------------------------------------------- diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index e81a8e37a..528d1b034 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -48,6 +48,7 @@ RuntimeLibrary="1" EnableFunctionLevelLinking="true" WarningLevel="3" + Detect64BitPortabilityProblems="true" DebugInformationFormat="4" /> + + @@ -1300,6 +1305,10 @@ RelativePath="..\..\code\KillNormalsProcess.cpp" > + + diff --git a/workspaces/vc8/assimp_view.vcproj b/workspaces/vc8/assimp_view.vcproj index 2af09008c..8da1e1fd0 100644 --- a/workspaces/vc8/assimp_view.vcproj +++ b/workspaces/vc8/assimp_view.vcproj @@ -52,7 +52,7 @@ EnableFunctionLevelLinking="true" UsePrecompiledHeader="2" WarningLevel="3" - Detect64BitPortabilityProblems="false" + Detect64BitPortabilityProblems="true" DebugInformationFormat="4" />