| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| /// @file DeboneProcess.cpp |
| /** Implementation of the DeboneProcess post processing step */ |
| |
| |
| |
| // internal headers of the post-processing framework |
| #include "ProcessHelper.h" |
| #include "DeboneProcess.h" |
| #include <stdio.h> |
| |
| |
| using namespace Assimp; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Constructor to be privately used by Importer |
| DeboneProcess::DeboneProcess() |
| { |
| mNumBones = 0; |
| mNumBonesCanDoWithout = 0; |
| |
| mThreshold = AI_DEBONE_THRESHOLD; |
| mAllOrNone = false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor, private as well |
| DeboneProcess::~DeboneProcess() |
| { |
| // nothing to do here |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Returns whether the processing step is present in the given flag field. |
| bool DeboneProcess::IsActive( unsigned int pFlags) const |
| { |
| return (pFlags & aiProcess_Debone) != 0; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Executes the post processing step on the given imported data. |
| void DeboneProcess::SetupProperties(const Importer* pImp) |
| { |
| // get the current value of the property |
| mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false; |
| mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Executes the post processing step on the given imported data. |
| void DeboneProcess::Execute( aiScene* pScene) |
| { |
| DefaultLogger::get()->debug("DeboneProcess begin"); |
| |
| if(!pScene->mNumMeshes) { |
| return; |
| } |
| |
| std::vector<bool> splitList(pScene->mNumMeshes); |
| for( unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
| splitList[a] = ConsiderMesh( pScene->mMeshes[a] ); |
| } |
| |
| int numSplits = 0; |
| |
| if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones)) { |
| for(unsigned int a = 0; a < pScene->mNumMeshes; a++) { |
| if(splitList[a]) { |
| numSplits++; |
| } |
| } |
| } |
| |
| if(numSplits) { |
| // we need to do something. Let's go. |
| //mSubMeshIndices.clear(); // really needed? |
| mSubMeshIndices.resize(pScene->mNumMeshes); // because we're doing it here anyway |
| |
| // build a new array of meshes for the scene |
| std::vector<aiMesh*> meshes; |
| |
| for(unsigned int a=0;a<pScene->mNumMeshes;a++) |
| { |
| aiMesh* srcMesh = pScene->mMeshes[a]; |
| |
| std::vector<std::pair<aiMesh*,const aiBone*> > newMeshes; |
| |
| if(splitList[a]) { |
| SplitMesh(srcMesh,newMeshes); |
| } |
| |
| // mesh was split |
| if(!newMeshes.empty()) { |
| unsigned int out = 0, in = srcMesh->mNumBones; |
| |
| // store new meshes and indices of the new meshes |
| for(unsigned int b=0;b<newMeshes.size();b++) { |
| const aiString *find = newMeshes[b].second?&newMeshes[b].second->mName:0; |
| |
| aiNode *theNode = find?pScene->mRootNode->FindNode(*find):0; |
| std::pair<unsigned int,aiNode*> push_pair(static_cast<unsigned int>(meshes.size()),theNode); |
| |
| mSubMeshIndices[a].push_back(push_pair); |
| meshes.push_back(newMeshes[b].first); |
| |
| out+=newMeshes[b].first->mNumBones; |
| } |
| |
| if(!DefaultLogger::isNullLogger()) { |
| char buffer[1024]; |
| ::ai_snprintf(buffer,1024,"Removed %u bones. Input bones: %u. Output bones: %u",in-out,in,out); |
| DefaultLogger::get()->info(buffer); |
| } |
| |
| // and destroy the source mesh. It should be completely contained inside the new submeshes |
| delete srcMesh; |
| } |
| else { |
| // Mesh is kept unchanged - store it's new place in the mesh array |
| mSubMeshIndices[a].push_back(std::pair<unsigned int,aiNode*>(static_cast<unsigned int>(meshes.size()),(aiNode*)0)); |
| meshes.push_back(srcMesh); |
| } |
| } |
| |
| // rebuild the scene's mesh array |
| pScene->mNumMeshes = static_cast<unsigned int>(meshes.size()); |
| delete [] pScene->mMeshes; |
| pScene->mMeshes = new aiMesh*[pScene->mNumMeshes]; |
| std::copy( meshes.begin(), meshes.end(), pScene->mMeshes); |
| |
| // recurse through all nodes and translate the node's mesh indices to fit the new mesh array |
| UpdateNode( pScene->mRootNode); |
| } |
| |
| DefaultLogger::get()->debug("DeboneProcess end"); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Counts bones total/removable in a given mesh. |
| bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) |
| { |
| if(!pMesh->HasBones()) { |
| return false; |
| } |
| |
| bool split = false; |
| |
| //interstitial faces not permitted |
| bool isInterstitialRequired = false; |
| |
| std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); |
| std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); |
| |
| const unsigned int cUnowned = UINT_MAX; |
| const unsigned int cCoowned = UINT_MAX-1; |
| |
| for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
| for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { |
| float w = pMesh->mBones[i]->mWeights[j].mWeight; |
| |
| if(w==0.0f) { |
| continue; |
| } |
| |
| unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; |
| if(w>=mThreshold) { |
| |
| if(vertexBones[vid]!=cUnowned) { |
| if(vertexBones[vid]==i) //double entry |
| { |
| DefaultLogger::get()->warn("Encountered double entry in bone weights"); |
| } |
| else //TODO: track attraction in order to break tie |
| { |
| vertexBones[vid] = cCoowned; |
| } |
| } |
| else vertexBones[vid] = i; |
| } |
| |
| if(!isBoneNecessary[i]) { |
| isBoneNecessary[i] = w<mThreshold; |
| } |
| } |
| |
| if(!isBoneNecessary[i]) { |
| isInterstitialRequired = true; |
| } |
| } |
| |
| if(isInterstitialRequired) { |
| for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
| unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; |
| |
| for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { |
| unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; |
| |
| if(v!=w) { |
| if(v<pMesh->mNumBones) isBoneNecessary[v] = true; |
| if(w<pMesh->mNumBones) isBoneNecessary[w] = true; |
| } |
| } |
| } |
| } |
| |
| for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
| if(!isBoneNecessary[i]) { |
| mNumBonesCanDoWithout++; |
| split = true; |
| } |
| |
| mNumBones++; |
| } |
| return split; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Splits the given mesh by bone count. |
| void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const |
| { |
| // same deal here as ConsiderMesh basically |
| |
| std::vector<bool> isBoneNecessary(pMesh->mNumBones,false); |
| std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX); |
| |
| const unsigned int cUnowned = UINT_MAX; |
| const unsigned int cCoowned = UINT_MAX-1; |
| |
| for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
| for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++) { |
| float w = pMesh->mBones[i]->mWeights[j].mWeight; |
| |
| if(w==0.0f) { |
| continue; |
| } |
| |
| unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; |
| |
| if(w>=mThreshold) { |
| if(vertexBones[vid]!=cUnowned) { |
| if(vertexBones[vid]==i) //double entry |
| { |
| //DefaultLogger::get()->warn("Encountered double entry in bone weights"); |
| } |
| else //TODO: track attraction in order to break tie |
| { |
| vertexBones[vid] = cCoowned; |
| } |
| } |
| else vertexBones[vid] = i; |
| } |
| |
| if(!isBoneNecessary[i]) { |
| isBoneNecessary[i] = w<mThreshold; |
| } |
| } |
| } |
| |
| unsigned int nFacesUnowned = 0; |
| |
| std::vector<unsigned int> faceBones(pMesh->mNumFaces,UINT_MAX); |
| std::vector<unsigned int> facesPerBone(pMesh->mNumBones,0); |
| |
| for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
| unsigned int nInterstitial = 1; |
| |
| unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; |
| |
| for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) { |
| unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; |
| |
| if(v!=w) { |
| if(v<pMesh->mNumBones) isBoneNecessary[v] = true; |
| if(w<pMesh->mNumBones) isBoneNecessary[w] = true; |
| } |
| else nInterstitial++; |
| } |
| |
| if(v<pMesh->mNumBones &&nInterstitial==pMesh->mFaces[i].mNumIndices) { |
| faceBones[i] = v; //primitive belongs to bone #v |
| facesPerBone[v]++; |
| } |
| else nFacesUnowned++; |
| } |
| |
| // invalidate any "cojoined" faces |
| for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
| if(faceBones[i]<pMesh->mNumBones&&isBoneNecessary[faceBones[i]]) |
| { |
| ai_assert(facesPerBone[faceBones[i]]>0); |
| facesPerBone[faceBones[i]]--; |
| |
| nFacesUnowned++; |
| faceBones[i] = cUnowned; |
| } |
| } |
| |
| if(nFacesUnowned) { |
| std::vector<unsigned int> subFaces; |
| |
| for(unsigned int i=0;i<pMesh->mNumFaces;i++) { |
| if(faceBones[i]==cUnowned) { |
| subFaces.push_back(i); |
| } |
| } |
| |
| aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0); |
| std::pair<aiMesh*,const aiBone*> push_pair(baseMesh,(const aiBone*)0); |
| |
| poNewMeshes.push_back(push_pair); |
| } |
| |
| for(unsigned int i=0;i<pMesh->mNumBones;i++) { |
| |
| if(!isBoneNecessary[i]&&facesPerBone[i]>0) { |
| std::vector<unsigned int> subFaces; |
| |
| for(unsigned int j=0;j<pMesh->mNumFaces;j++) { |
| if(faceBones[j]==i) { |
| subFaces.push_back(j); |
| } |
| } |
| |
| unsigned int f = AI_SUBMESH_FLAGS_SANS_BONES; |
| aiMesh *subMesh =MakeSubmesh(pMesh,subFaces,f); |
| |
| //Lifted from PretransformVertices.cpp |
| ApplyTransform(subMesh,pMesh->mBones[i]->mOffsetMatrix); |
| std::pair<aiMesh*,const aiBone*> push_pair(subMesh,pMesh->mBones[i]); |
| |
| poNewMeshes.push_back(push_pair); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Recursively updates the node's mesh list to account for the changed mesh list |
| void DeboneProcess::UpdateNode(aiNode* pNode) const |
| { |
| // rebuild the node's mesh index list |
| |
| std::vector<unsigned int> newMeshList; |
| |
| // this will require two passes |
| |
| unsigned int m = static_cast<unsigned int>(pNode->mNumMeshes), n = static_cast<unsigned int>(mSubMeshIndices.size()); |
| |
| // first pass, look for meshes which have not moved |
| |
| for(unsigned int a=0;a<m;a++) { |
| |
| unsigned int srcIndex = pNode->mMeshes[a]; |
| const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex]; |
| unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); |
| |
| for(unsigned int b=0;b<nSubmeshes;b++) { |
| if(!subMeshes[b].second) { |
| newMeshList.push_back(subMeshes[b].first); |
| } |
| } |
| } |
| |
| // second pass, collect deboned meshes |
| |
| for(unsigned int a=0;a<n;a++) |
| { |
| const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[a]; |
| unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size()); |
| |
| for(unsigned int b=0;b<nSubmeshes;b++) { |
| if(subMeshes[b].second == pNode) { |
| newMeshList.push_back(subMeshes[b].first); |
| } |
| } |
| } |
| |
| if( pNode->mNumMeshes > 0 ) { |
| delete [] pNode->mMeshes; pNode->mMeshes = NULL; |
| } |
| |
| pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size()); |
| |
| if(pNode->mNumMeshes) { |
| pNode->mMeshes = new unsigned int[pNode->mNumMeshes]; |
| std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes); |
| } |
| |
| // do that also recursively for all children |
| for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) { |
| UpdateNode( pNode->mChildren[a]); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Apply the node transformation to a mesh |
| void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const |
| { |
| // Check whether we need to transform the coordinates at all |
| if (!mat.IsIdentity()) { |
| |
| if (mesh->HasPositions()) { |
| for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
| mesh->mVertices[i] = mat * mesh->mVertices[i]; |
| } |
| } |
| if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { |
| aiMatrix4x4 mWorldIT = mat; |
| mWorldIT.Inverse().Transpose(); |
| |
| // TODO: implement Inverse() for aiMatrix3x3 |
| aiMatrix3x3 m = aiMatrix3x3(mWorldIT); |
| |
| if (mesh->HasNormals()) { |
| for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
| mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize(); |
| } |
| } |
| if (mesh->HasTangentsAndBitangents()) { |
| for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { |
| mesh->mTangents[i] = (m * mesh->mTangents[i]).Normalize(); |
| mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize(); |
| } |
| } |
| } |
| } |
| } |