2019-04-03 07:54:58 +02:00
/*
Open Asset Import Library ( assimp )
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2020-03-09 10:42:18 +01:00
Copyright ( c ) 2006 - 2019 , assimp team
2019-04-03 07:54:58 +02:00
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 SplitByBoneCountProcess.cpp
/// Implementation of the SplitByBoneCount postprocessing step
// internal headers of the post-processing framework
# include "SplitByBoneCountProcess.h"
# include <assimp/postprocess.h>
# include <assimp/DefaultLogger.hpp>
# include <limits>
# include <assimp/TinyFormatter.h>
using namespace Assimp ;
using namespace Assimp : : Formatter ;
// ------------------------------------------------------------------------------------------------
// Constructor
SplitByBoneCountProcess : : SplitByBoneCountProcess ( )
{
// set default, might be overridden by importer config
mMaxBoneCount = AI_SBBC_DEFAULT_MAX_BONES ;
}
// ------------------------------------------------------------------------------------------------
// Destructor
SplitByBoneCountProcess : : ~ SplitByBoneCountProcess ( )
{
// nothing to do here
}
// ------------------------------------------------------------------------------------------------
// Returns whether the processing step is present in the given flag.
bool SplitByBoneCountProcess : : IsActive ( unsigned int pFlags ) const
{
return ! ! ( pFlags & aiProcess_SplitByBoneCount ) ;
}
// ------------------------------------------------------------------------------------------------
// Updates internal properties
void SplitByBoneCountProcess : : SetupProperties ( const Importer * pImp )
{
mMaxBoneCount = pImp - > GetPropertyInteger ( AI_CONFIG_PP_SBBC_MAX_BONES , AI_SBBC_DEFAULT_MAX_BONES ) ;
}
// ------------------------------------------------------------------------------------------------
// Executes the post processing step on the given imported data.
void SplitByBoneCountProcess : : Execute ( aiScene * pScene )
{
ASSIMP_LOG_DEBUG ( " SplitByBoneCountProcess begin " ) ;
// early out
bool isNecessary = false ;
for ( unsigned int a = 0 ; a < pScene - > mNumMeshes ; + + a )
if ( pScene - > mMeshes [ a ] - > mNumBones > mMaxBoneCount )
isNecessary = true ;
if ( ! isNecessary )
{
ASSIMP_LOG_DEBUG ( format ( ) < < " SplitByBoneCountProcess early-out: no meshes with more than " < < mMaxBoneCount < < " bones. " ) ;
return ;
}
// we need to do something. Let's go.
mSubMeshIndices . clear ( ) ;
mSubMeshIndices . resize ( pScene - > mNumMeshes ) ;
// 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 < aiMesh * > newMeshes ;
SplitMesh ( pScene - > mMeshes [ a ] , newMeshes ) ;
// mesh was split
if ( ! newMeshes . empty ( ) )
{
// store new meshes and indices of the new meshes
for ( unsigned int b = 0 ; b < newMeshes . size ( ) ; + + b )
{
mSubMeshIndices [ a ] . push_back ( static_cast < unsigned int > ( meshes . size ( ) ) ) ;
meshes . push_back ( newMeshes [ b ] ) ;
}
// 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 ( static_cast < unsigned int > ( meshes . size ( ) ) ) ;
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 ) ;
ASSIMP_LOG_DEBUG ( format ( ) < < " SplitByBoneCountProcess end: split " < < mSubMeshIndices . size ( ) < < " meshes into " < < meshes . size ( ) < < " submeshes. " ) ;
}
// ------------------------------------------------------------------------------------------------
// Splits the given mesh by bone count.
void SplitByBoneCountProcess : : SplitMesh ( const aiMesh * pMesh , std : : vector < aiMesh * > & poNewMeshes ) const
{
// skip if not necessary
if ( pMesh - > mNumBones < = mMaxBoneCount )
return ;
// necessary optimisation: build a list of all affecting bones for each vertex
// TODO: (thom) maybe add a custom allocator here to avoid allocating tens of thousands of small arrays
typedef std : : pair < unsigned int , float > BoneWeight ;
std : : vector < std : : vector < BoneWeight > > vertexBones ( pMesh - > mNumVertices ) ;
for ( unsigned int a = 0 ; a < pMesh - > mNumBones ; + + a )
{
const aiBone * bone = pMesh - > mBones [ a ] ;
for ( unsigned int b = 0 ; b < bone - > mNumWeights ; + + b )
vertexBones [ bone - > mWeights [ b ] . mVertexId ] . push_back ( BoneWeight ( a , bone - > mWeights [ b ] . mWeight ) ) ;
}
unsigned int numFacesHandled = 0 ;
std : : vector < bool > isFaceHandled ( pMesh - > mNumFaces , false ) ;
while ( numFacesHandled < pMesh - > mNumFaces )
{
// which bones are used in the current submesh
unsigned int numBones = 0 ;
std : : vector < bool > isBoneUsed ( pMesh - > mNumBones , false ) ;
// indices of the faces which are going to go into this submesh
std : : vector < unsigned int > subMeshFaces ;
subMeshFaces . reserve ( pMesh - > mNumFaces ) ;
// accumulated vertex count of all the faces in this submesh
unsigned int numSubMeshVertices = 0 ;
// a small local array of new bones for the current face. State of all used bones for that face
// can only be updated AFTER the face is completely analysed. Thanks to imre for the fix.
std : : vector < unsigned int > newBonesAtCurrentFace ;
// add faces to the new submesh as long as all bones affecting the faces' vertices fit in the limit
for ( unsigned int a = 0 ; a < pMesh - > mNumFaces ; + + a )
{
// skip if the face is already stored in a submesh
if ( isFaceHandled [ a ] )
continue ;
const aiFace & face = pMesh - > mFaces [ a ] ;
// check every vertex if its bones would still fit into the current submesh
for ( unsigned int b = 0 ; b < face . mNumIndices ; + + b )
{
const std : : vector < BoneWeight > & vb = vertexBones [ face . mIndices [ b ] ] ;
for ( unsigned int c = 0 ; c < vb . size ( ) ; + + c )
{
unsigned int boneIndex = vb [ c ] . first ;
// if the bone is already used in this submesh, it's ok
if ( isBoneUsed [ boneIndex ] )
continue ;
// if it's not used, yet, we would need to add it. Store its bone index
if ( std : : find ( newBonesAtCurrentFace . begin ( ) , newBonesAtCurrentFace . end ( ) , boneIndex ) = = newBonesAtCurrentFace . end ( ) )
newBonesAtCurrentFace . push_back ( boneIndex ) ;
}
}
// leave out the face if the new bones required for this face don't fit the bone count limit anymore
if ( numBones + newBonesAtCurrentFace . size ( ) > mMaxBoneCount )
continue ;
// mark all new bones as necessary
while ( ! newBonesAtCurrentFace . empty ( ) )
{
unsigned int newIndex = newBonesAtCurrentFace . back ( ) ;
newBonesAtCurrentFace . pop_back ( ) ; // this also avoids the deallocation which comes with a clear()
if ( isBoneUsed [ newIndex ] )
continue ;
isBoneUsed [ newIndex ] = true ;
numBones + + ;
}
// store the face index and the vertex count
subMeshFaces . push_back ( a ) ;
numSubMeshVertices + = face . mNumIndices ;
// remember that this face is handled
isFaceHandled [ a ] = true ;
numFacesHandled + + ;
}
// create a new mesh to hold this subset of the source mesh
aiMesh * newMesh = new aiMesh ;
if ( pMesh - > mName . length > 0 )
newMesh - > mName . Set ( format ( ) < < pMesh - > mName . data < < " _sub " < < poNewMeshes . size ( ) ) ;
newMesh - > mMaterialIndex = pMesh - > mMaterialIndex ;
newMesh - > mPrimitiveTypes = pMesh - > mPrimitiveTypes ;
poNewMeshes . push_back ( newMesh ) ;
// create all the arrays for this mesh if the old mesh contained them
newMesh - > mNumVertices = numSubMeshVertices ;
newMesh - > mNumFaces = static_cast < unsigned int > ( subMeshFaces . size ( ) ) ;
newMesh - > mVertices = new aiVector3D [ newMesh - > mNumVertices ] ;
if ( pMesh - > HasNormals ( ) )
newMesh - > mNormals = new aiVector3D [ newMesh - > mNumVertices ] ;
if ( pMesh - > HasTangentsAndBitangents ( ) )
{
newMesh - > mTangents = new aiVector3D [ newMesh - > mNumVertices ] ;
newMesh - > mBitangents = new aiVector3D [ newMesh - > mNumVertices ] ;
}
for ( unsigned int a = 0 ; a < AI_MAX_NUMBER_OF_TEXTURECOORDS ; + + a )
{
if ( pMesh - > HasTextureCoords ( a ) )
newMesh - > mTextureCoords [ a ] = new aiVector3D [ newMesh - > mNumVertices ] ;
newMesh - > mNumUVComponents [ a ] = pMesh - > mNumUVComponents [ a ] ;
}
for ( unsigned int a = 0 ; a < AI_MAX_NUMBER_OF_COLOR_SETS ; + + a )
{
if ( pMesh - > HasVertexColors ( a ) )
newMesh - > mColors [ a ] = new aiColor4D [ newMesh - > mNumVertices ] ;
}
// and copy over the data, generating faces with linear indices along the way
newMesh - > mFaces = new aiFace [ subMeshFaces . size ( ) ] ;
unsigned int nvi = 0 ; // next vertex index
std : : vector < unsigned int > previousVertexIndices ( numSubMeshVertices , std : : numeric_limits < unsigned int > : : max ( ) ) ; // per new vertex: its index in the source mesh
for ( unsigned int a = 0 ; a < subMeshFaces . size ( ) ; + + a )
{
const aiFace & srcFace = pMesh - > mFaces [ subMeshFaces [ a ] ] ;
aiFace & dstFace = newMesh - > mFaces [ a ] ;
dstFace . mNumIndices = srcFace . mNumIndices ;
dstFace . mIndices = new unsigned int [ dstFace . mNumIndices ] ;
// accumulate linearly all the vertices of the source face
for ( unsigned int b = 0 ; b < dstFace . mNumIndices ; + + b )
{
unsigned int srcIndex = srcFace . mIndices [ b ] ;
dstFace . mIndices [ b ] = nvi ;
previousVertexIndices [ nvi ] = srcIndex ;
newMesh - > mVertices [ nvi ] = pMesh - > mVertices [ srcIndex ] ;
if ( pMesh - > HasNormals ( ) )
newMesh - > mNormals [ nvi ] = pMesh - > mNormals [ srcIndex ] ;
if ( pMesh - > HasTangentsAndBitangents ( ) )
{
newMesh - > mTangents [ nvi ] = pMesh - > mTangents [ srcIndex ] ;
newMesh - > mBitangents [ nvi ] = pMesh - > mBitangents [ srcIndex ] ;
}
for ( unsigned int c = 0 ; c < AI_MAX_NUMBER_OF_TEXTURECOORDS ; + + c )
{
if ( pMesh - > HasTextureCoords ( c ) )
newMesh - > mTextureCoords [ c ] [ nvi ] = pMesh - > mTextureCoords [ c ] [ srcIndex ] ;
}
for ( unsigned int c = 0 ; c < AI_MAX_NUMBER_OF_COLOR_SETS ; + + c )
{
if ( pMesh - > HasVertexColors ( c ) )
newMesh - > mColors [ c ] [ nvi ] = pMesh - > mColors [ c ] [ srcIndex ] ;
}
nvi + + ;
}
}
ai_assert ( nvi = = numSubMeshVertices ) ;
// Create the bones for the new submesh: first create the bone array
newMesh - > mNumBones = 0 ;
newMesh - > mBones = new aiBone * [ numBones ] ;
std : : vector < unsigned int > mappedBoneIndex ( pMesh - > mNumBones , std : : numeric_limits < unsigned int > : : max ( ) ) ;
for ( unsigned int a = 0 ; a < pMesh - > mNumBones ; + + a )
{
if ( ! isBoneUsed [ a ] )
continue ;
// create the new bone
const aiBone * srcBone = pMesh - > mBones [ a ] ;
aiBone * dstBone = new aiBone ;
mappedBoneIndex [ a ] = newMesh - > mNumBones ;
newMesh - > mBones [ newMesh - > mNumBones + + ] = dstBone ;
dstBone - > mName = srcBone - > mName ;
dstBone - > mOffsetMatrix = srcBone - > mOffsetMatrix ;
dstBone - > mNumWeights = 0 ;
}
ai_assert ( newMesh - > mNumBones = = numBones ) ;
// iterate over all new vertices and count which bones affected its old vertex in the source mesh
for ( unsigned int a = 0 ; a < numSubMeshVertices ; + + a )
{
unsigned int oldIndex = previousVertexIndices [ a ] ;
const std : : vector < BoneWeight > & bonesOnThisVertex = vertexBones [ oldIndex ] ;
for ( unsigned int b = 0 ; b < bonesOnThisVertex . size ( ) ; + + b )
{
unsigned int newBoneIndex = mappedBoneIndex [ bonesOnThisVertex [ b ] . first ] ;
if ( newBoneIndex ! = std : : numeric_limits < unsigned int > : : max ( ) )
newMesh - > mBones [ newBoneIndex ] - > mNumWeights + + ;
}
}
// allocate all bone weight arrays accordingly
for ( unsigned int a = 0 ; a < newMesh - > mNumBones ; + + a )
{
aiBone * bone = newMesh - > mBones [ a ] ;
ai_assert ( bone - > mNumWeights > 0 ) ;
bone - > mWeights = new aiVertexWeight [ bone - > mNumWeights ] ;
bone - > mNumWeights = 0 ; // for counting up in the next step
}
// now copy all the bone vertex weights for all the vertices which made it into the new submesh
for ( unsigned int a = 0 ; a < numSubMeshVertices ; + + a )
{
// find the source vertex for it in the source mesh
unsigned int previousIndex = previousVertexIndices [ a ] ;
// these bones were affecting it
const std : : vector < BoneWeight > & bonesOnThisVertex = vertexBones [ previousIndex ] ;
// all of the bones affecting it should be present in the new submesh, or else
// the face it comprises shouldn't be present
for ( unsigned int b = 0 ; b < bonesOnThisVertex . size ( ) ; + + b )
{
unsigned int newBoneIndex = mappedBoneIndex [ bonesOnThisVertex [ b ] . first ] ;
ai_assert ( newBoneIndex ! = std : : numeric_limits < unsigned int > : : max ( ) ) ;
aiVertexWeight * dstWeight = newMesh - > mBones [ newBoneIndex ] - > mWeights + newMesh - > mBones [ newBoneIndex ] - > mNumWeights ;
newMesh - > mBones [ newBoneIndex ] - > mNumWeights + + ;
dstWeight - > mVertexId = a ;
dstWeight - > mWeight = bonesOnThisVertex [ b ] . second ;
}
}
// I have the strange feeling that this will break apart at some point in time...
}
}
// ------------------------------------------------------------------------------------------------
// Recursively updates the node's mesh list to account for the changed mesh list
void SplitByBoneCountProcess : : UpdateNode ( aiNode * pNode ) const
{
// rebuild the node's mesh index list
if ( pNode - > mNumMeshes > 0 )
{
std : : vector < unsigned int > newMeshList ;
for ( unsigned int a = 0 ; a < pNode - > mNumMeshes ; + + a )
{
unsigned int srcIndex = pNode - > mMeshes [ a ] ;
const std : : vector < unsigned int > & replaceMeshes = mSubMeshIndices [ srcIndex ] ;
newMeshList . insert ( newMeshList . end ( ) , replaceMeshes . begin ( ) , replaceMeshes . end ( ) ) ;
}
delete [ ] pNode - > mMeshes ;
pNode - > mNumMeshes = static_cast < unsigned int > ( newMeshList . size ( ) ) ;
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 ] ) ;
}
}