|
@@ -0,0 +1,909 @@
|
|
|
+/*
|
|
|
+Open Asset Import Library (ASSIMP)
|
|
|
+----------------------------------------------------------------------
|
|
|
+
|
|
|
+Copyright (c) 2006-2008, ASSIMP Development 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 Development 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.
|
|
|
+
|
|
|
+----------------------------------------------------------------------
|
|
|
+*/
|
|
|
+
|
|
|
+/** Implementation of the OptimizeGraphProcess post-processing step*/
|
|
|
+
|
|
|
+#include <vector>
|
|
|
+#include <list>
|
|
|
+
|
|
|
+#include "OptimizeGraphProcess.h"
|
|
|
+#include "Hash.h"
|
|
|
+
|
|
|
+#include "../include/aiPostProcess.h"
|
|
|
+#include "../include/aiScene.h"
|
|
|
+#include "../include/aiAssert.h"
|
|
|
+#include "../include/assimp.hpp"
|
|
|
+#include "../include/DefaultLogger.h"
|
|
|
+
|
|
|
+using namespace Assimp;
|
|
|
+
|
|
|
+// MSB for type unsigned int
|
|
|
+#define AI_OG_UINT_MSB (1u<<((sizeof(unsigned int)*8u)-1u))
|
|
|
+#define AI_OG_UINT_MSB_2 (AI_OG_UINT_MSB>>1)
|
|
|
+
|
|
|
+// check whether a node/a mesh is locked
|
|
|
+#define AI_OG_IS_NODE_LOCKED(nd) (nd->mNumChildren & AI_OG_UINT_MSB)
|
|
|
+#define AI_OG_IS_MESH_LOCKED(ms) (ms->mNumBones & AI_OG_UINT_MSB)
|
|
|
+
|
|
|
+// check whether a node has locked meshes in its list
|
|
|
+#define AI_OG_HAS_NODE_LOCKED_MESHES(nd) (nd->mNumChildren & AI_OG_UINT_MSB_2)
|
|
|
+
|
|
|
+// unmask the two upper bits of an unsigned int
|
|
|
+#define AI_OG_UNMASK(p) (p & (~(AI_OG_UINT_MSB|AI_OG_UINT_MSB_2)))
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+// Constructor to be privately used by Importer
|
|
|
+OptimizeGraphProcess::OptimizeGraphProcess()
|
|
|
+{
|
|
|
+ configRemoveAnimations = AI_OG_REMOVE_ANIMATIONS;
|
|
|
+ configMinNumFaces = AI_OG_MIN_NUM_FACES;
|
|
|
+ configJoinInequalTransforms = AI_OG_JOIN_INEQUAL_TRANSFORMS;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+// Destructor, private as well
|
|
|
+OptimizeGraphProcess::~OptimizeGraphProcess()
|
|
|
+{
|
|
|
+ // nothing to do here
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+// Returns whether the processing step is present in the given flag field.
|
|
|
+bool OptimizeGraphProcess::IsActive( unsigned int pFlags) const
|
|
|
+{
|
|
|
+ return (pFlags & aiProcess_OptimizeGraph) != 0;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+// Setup properties of the step
|
|
|
+void OptimizeGraphProcess::SetupProperties(const Importer* pImp)
|
|
|
+{
|
|
|
+ // remove animation nodes?
|
|
|
+ configRemoveAnimations = pImp->GetPropertyInteger(AI_CONFIG_PP_OG_REMOVE_ANIMATIONS,
|
|
|
+ AI_OG_REMOVE_ANIMATIONS) != 0 ? true : false;
|
|
|
+
|
|
|
+ // join nods with inequal transformations?
|
|
|
+ configRemoveAnimations = pImp->GetPropertyInteger(AI_CONFIG_PP_OG_JOIN_INEQUAL_TRANSFORMS,
|
|
|
+ AI_OG_JOIN_INEQUAL_TRANSFORMS) != 0 ? true : false;
|
|
|
+
|
|
|
+ // minimum face number per node
|
|
|
+ configMinNumFaces = pImp->GetPropertyInteger(AI_CONFIG_PP_OG_MIN_NUM_FACES,
|
|
|
+ AI_OG_MIN_NUM_FACES);
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+aiNode* OptimizeGraphProcess::RemoveAnimationNodes (aiNode* node)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+
|
|
|
+ std::vector<aiNode*> out;
|
|
|
+ RemoveAnimationNodes(node,out);
|
|
|
+ if (out.empty())
|
|
|
+ throw new ImportErrorException("OptimizeGraphProcess: no nodes are remaining.");
|
|
|
+ if (1 == out.size())
|
|
|
+ return out[0];
|
|
|
+ aiNode* p = new aiNode();
|
|
|
+ p->mName.Set("<dummy_root>");
|
|
|
+ p->mNumChildren = (unsigned int)out.size();
|
|
|
+ p->mChildren = new aiNode*[p->mNumChildren];
|
|
|
+ ::memcpy(p->mChildren,&out[0],p->mNumChildren*sizeof(void*));
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::RemoveAnimationNodes (aiNode* node,std::vector<aiNode*>& out)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+
|
|
|
+ // if this is an animation node: shift all children on this layer
|
|
|
+ if (!node->mNumMeshes)
|
|
|
+ {
|
|
|
+ unsigned int old = (unsigned int)out.size();
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ {
|
|
|
+ RemoveAnimationNodes(node->mChildren[i],out);
|
|
|
+ }
|
|
|
+ // update the transformations of all shifted childs
|
|
|
+ std::vector<aiNode*>::iterator it2 = out.end(),it = out.begin()+old;
|
|
|
+ for (; it != it2; ++it)
|
|
|
+ (*it)->mTransformation = node->mTransformation * (*it)->mTransformation;
|
|
|
+
|
|
|
+ delete[] node->mChildren;node->mChildren = NULL;
|
|
|
+ delete node;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // *this* node remains on this layer, and the children, too
|
|
|
+ out.push_back(node);
|
|
|
+
|
|
|
+ std::vector<aiNode*> outNew;
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ {
|
|
|
+ RemoveAnimationNodes(node->mChildren[i],outNew);
|
|
|
+ }
|
|
|
+ if (outNew.size() > node->mNumChildren)
|
|
|
+ {
|
|
|
+ delete[] node->mChildren;
|
|
|
+ node->mChildren = new aiNode*[outNew.size()];
|
|
|
+ }
|
|
|
+ node->mNumChildren = (unsigned int)outNew.size();
|
|
|
+ ::memcpy(node->mChildren,&outNew[0],node->mNumChildren*sizeof(void*));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::FindLockedNodes(aiNode* node)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+
|
|
|
+ for (unsigned int i = 0; i < pScene->mNumAnimations;++i)
|
|
|
+ {
|
|
|
+ aiAnimation* pani = pScene->mAnimations[i];
|
|
|
+ for (unsigned int a = 0; a < pani->mNumBones;++a)
|
|
|
+ {
|
|
|
+ aiBoneAnim* pba = pani->mBones[a];
|
|
|
+ if (pba->mBoneName == node->mName)
|
|
|
+ {
|
|
|
+ // this node is locked
|
|
|
+ node->mNumChildren |= AI_OG_UINT_MSB;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // call all children
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ FindLockedNodes(node->mChildren[i]);
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::FindLockedMeshes(aiNode* node, MeshRefCount* pRefCount)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node && NULL != pRefCount);
|
|
|
+ for (unsigned int i = 0;i < node->mNumMeshes;++i)
|
|
|
+ {
|
|
|
+ unsigned int m = node->mMeshes[i];
|
|
|
+ if (pRefCount[m].first)
|
|
|
+ {
|
|
|
+ // we have already one reference - lock the first node
|
|
|
+ // that had a referenced to this mesh too if it has only
|
|
|
+ // one mesh assigned. If there are multiple meshes,
|
|
|
+ // the others could still be used for optimizations.
|
|
|
+ if (pRefCount[m].second)
|
|
|
+ {
|
|
|
+ pRefCount[m].second->mNumChildren |= (pRefCount[m].second->mNumMeshes <= 1
|
|
|
+ ? AI_OG_UINT_MSB : AI_OG_UINT_MSB_2);
|
|
|
+
|
|
|
+ pRefCount[m].second = NULL;
|
|
|
+ }
|
|
|
+ pScene->mMeshes[m]->mNumBones |= AI_OG_UINT_MSB;
|
|
|
+
|
|
|
+ // lock this node
|
|
|
+ node->mNumChildren |= (node->mNumMeshes <= 1
|
|
|
+ ? AI_OG_UINT_MSB : AI_OG_UINT_MSB_2);
|
|
|
+ }
|
|
|
+ else pRefCount[m].second = node;
|
|
|
+ ++pRefCount[m].first;
|
|
|
+ }
|
|
|
+ // call all children
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ FindLockedMeshes(node->mChildren[i],pRefCount);
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::FindLockedMeshes(aiNode* node)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+ MeshRefCount* pRefCount = new MeshRefCount[pScene->mNumMeshes];
|
|
|
+ for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
|
|
|
+ pRefCount[i] = MeshRefCount();
|
|
|
+
|
|
|
+ // execute the algorithm
|
|
|
+ FindLockedMeshes(node,pRefCount);
|
|
|
+
|
|
|
+ delete[] pRefCount;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::UnlockNodes(aiNode* node)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+ node->mNumChildren &= ~(AI_OG_UINT_MSB|AI_OG_UINT_MSB_2);
|
|
|
+
|
|
|
+ // call all children
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ UnlockNodes(node->mChildren[i]);
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::UnlockMeshes()
|
|
|
+{
|
|
|
+ for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
|
|
|
+ pScene->mMeshes[i]->mNumBones &= ~AI_OG_UINT_MSB;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::ComputeMeshHashes()
|
|
|
+{
|
|
|
+ mMeshHashes.resize(pScene->mNumMeshes);
|
|
|
+ for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
|
|
|
+ {
|
|
|
+ unsigned int iRet = 0;
|
|
|
+ aiMesh* pcMesh = pScene->mMeshes[i];
|
|
|
+
|
|
|
+ // normals
|
|
|
+ if (pcMesh->HasNormals())iRet |= 0x1;
|
|
|
+ // tangents and bitangents
|
|
|
+ if (pcMesh->HasTangentsAndBitangents())iRet |= 0x2;
|
|
|
+
|
|
|
+ // texture coordinates
|
|
|
+ unsigned int p = 0;
|
|
|
+ ai_assert(4 >= AI_MAX_NUMBER_OF_TEXTURECOORDS);
|
|
|
+ while (pcMesh->HasTextureCoords(p))
|
|
|
+ {
|
|
|
+ iRet |= (0x100 << p++);
|
|
|
+
|
|
|
+ // NOTE: meshes with numUVComp != 3 && != 2 aren't handled correctly here
|
|
|
+ ai_assert(pcMesh->mNumUVComponents[p] == 3 || pcMesh->mNumUVComponents[p] == 2);
|
|
|
+ if (3 == pcMesh->mNumUVComponents[p])
|
|
|
+ iRet |= (0x1000 << p++);
|
|
|
+ }
|
|
|
+ // vertex colors
|
|
|
+ p = 0;
|
|
|
+ ai_assert(4 >= AI_MAX_NUMBER_OF_COLOR_SETS);
|
|
|
+ while (pcMesh->HasVertexColors(p))iRet |= (0x10000 << p++);
|
|
|
+ mMeshHashes[i] = iRet;
|
|
|
+
|
|
|
+ // material index -store it in the upper 1 1/2 bytes, so
|
|
|
+ // are able to encode 2^12 material indices.
|
|
|
+
|
|
|
+ iRet |= (pcMesh->mMaterialIndex << 20u);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+inline unsigned int OptimizeGraphProcess::BinarySearch(NodeIndexList& sortedArray,
|
|
|
+ unsigned int min, unsigned int& index, unsigned int iStart)
|
|
|
+{
|
|
|
+ unsigned int first = iStart,last = (unsigned int)sortedArray.size()-1;
|
|
|
+ while (first <= last)
|
|
|
+ {
|
|
|
+ unsigned int mid = (first + last) / 2;
|
|
|
+ unsigned int id = sortedArray[mid].second;
|
|
|
+
|
|
|
+ if (min > id)
|
|
|
+ first = mid + 1;
|
|
|
+ else if (min <= id)
|
|
|
+ {
|
|
|
+ last = mid - 1;
|
|
|
+ if (!mid || min > sortedArray[last].second)
|
|
|
+ {
|
|
|
+ index = sortedArray[last].first;
|
|
|
+ return mid;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return (unsigned int)sortedArray.size();
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::BuildUniqueBoneList(
|
|
|
+ std::vector<aiMesh*>::const_iterator it,
|
|
|
+ std::vector<aiMesh*>::const_iterator end,
|
|
|
+ std::list<BoneWithHash>& asBones)
|
|
|
+{
|
|
|
+
|
|
|
+ unsigned int iOffset = 0;
|
|
|
+ for (; it != end;++it)
|
|
|
+ {
|
|
|
+ for (unsigned int l = 0; l < (*it)->mNumBones;++l)
|
|
|
+ {
|
|
|
+ aiBone* p = (*it)->mBones[l];
|
|
|
+ uint32_t itml = SuperFastHash(p->mName.data,(unsigned int)p->mName.length);
|
|
|
+
|
|
|
+ std::list<BoneWithHash>::iterator it2 = asBones.begin();
|
|
|
+ std::list<BoneWithHash>::iterator end2 = asBones.end();
|
|
|
+
|
|
|
+ for (;it2 != end2;++it2)
|
|
|
+ {
|
|
|
+ if ((*it2).first == itml)
|
|
|
+ {
|
|
|
+ (*it2).pSrcBones.push_back(BoneSrcIndex(p,iOffset));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (end2 == it2)
|
|
|
+ {
|
|
|
+ // need to begin a new bone entry
|
|
|
+ asBones.push_back(BoneWithHash());
|
|
|
+ BoneWithHash& btz = asBones.back();
|
|
|
+
|
|
|
+ // setup members
|
|
|
+ btz.first = itml;
|
|
|
+ btz.second = &p->mName;
|
|
|
+ btz.pSrcBones.push_back(BoneSrcIndex(p,iOffset));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ iOffset += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::JoinBones(
|
|
|
+ std::vector<aiMesh*>::const_iterator it,
|
|
|
+ std::vector<aiMesh*>::const_iterator end,
|
|
|
+ aiMesh* out)
|
|
|
+{
|
|
|
+ ai_assert(NULL != out);
|
|
|
+
|
|
|
+ // find we need to build an unique list of all bones.
|
|
|
+ // we work with hashes to make the comparisons MUCH faster,
|
|
|
+ // at least if we have many bones.
|
|
|
+ std::list<BoneWithHash> asBones;
|
|
|
+ BuildUniqueBoneList(it,end,asBones);
|
|
|
+
|
|
|
+ // now create the output bones
|
|
|
+ out->mBones = new aiBone*[asBones.size()];
|
|
|
+
|
|
|
+ for (std::list<BoneWithHash>::const_iterator it = asBones.begin(),
|
|
|
+ end = asBones.end(); it != end;++it)
|
|
|
+ {
|
|
|
+ aiBone* pc = out->mBones[out->mNumBones++] = new aiBone();
|
|
|
+ pc->mName = aiString( *((*it).second ));
|
|
|
+
|
|
|
+ // get an itrator to the end of the list
|
|
|
+ std::vector< BoneSrcIndex >::const_iterator wend = (*it).pSrcBones.end();
|
|
|
+
|
|
|
+ // loop through all bones to be joined for this bone
|
|
|
+ for (std::vector< BoneSrcIndex >::const_iterator
|
|
|
+ wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit)
|
|
|
+ {
|
|
|
+ pc->mNumWeights += (*wmit).first->mNumWeights;
|
|
|
+
|
|
|
+ // NOTE: different offset matrices for bones with equal names
|
|
|
+ // are - at the moment - not handled correctly.
|
|
|
+ if (wmit != (*it).pSrcBones.begin() &&
|
|
|
+ pc->mOffsetMatrix != (*wmit).first->mOffsetMatrix)
|
|
|
+ {
|
|
|
+ DefaultLogger::get()->warn("Bones with equal names but different "
|
|
|
+ "offset matrices can't be joined at the moment. If this causes "
|
|
|
+ "problems, deactivate the OptimizeGraph-Step");
|
|
|
+
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ pc->mOffsetMatrix = (*wmit).first->mOffsetMatrix;
|
|
|
+ }
|
|
|
+ // allocate the vertex weight array
|
|
|
+ aiVertexWeight* avw = pc->mWeights = new aiVertexWeight[pc->mNumWeights];
|
|
|
+
|
|
|
+ // and copy the final weights - adjust the vertex IDs by the
|
|
|
+ // face index offset of the coresponding mesh.
|
|
|
+ for (std::vector< BoneSrcIndex >::const_iterator
|
|
|
+ wmit = (*it).pSrcBones.begin(); wmit != wend; ++wmit)
|
|
|
+ {
|
|
|
+ aiBone* pip = (*wmit).first;
|
|
|
+ for (unsigned int mp = 0; mp < pip->mNumWeights;++mp)
|
|
|
+ {
|
|
|
+ aiVertexWeight& vf = aiVertexWeight(pip->mWeights[mp]);
|
|
|
+ vf.mVertexId += (*wmit).second;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::JoinMeshes(std::vector<aiMesh*>& meshList,
|
|
|
+ aiMesh*& out, unsigned int max)
|
|
|
+{
|
|
|
+ ai_assert(NULL != out && 0 != max);
|
|
|
+
|
|
|
+ out->mMaterialIndex = meshList[0]->mMaterialIndex;
|
|
|
+
|
|
|
+ // allocate the output mesh
|
|
|
+ out = new aiMesh();
|
|
|
+ std::vector<aiMesh*>::const_iterator end = meshList.begin()+max;
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ out->mNumVertices += (*it)->mNumVertices;
|
|
|
+ out->mNumFaces += (*it)->mNumFaces;
|
|
|
+ out->mNumBones += AI_OG_UNMASK((*it)->mNumBones);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (out->mNumVertices) // just for safety
|
|
|
+ {
|
|
|
+ aiVector3D* pv2;
|
|
|
+
|
|
|
+ // copy vertex positions
|
|
|
+ if (meshList[0]->HasPositions())
|
|
|
+ {
|
|
|
+ pv2 = out->mVertices = new aiVector3D[out->mNumVertices];
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ ::memcpy(pv2,(*it)->mVertices,(*it)->mNumVertices*sizeof(aiVector3D));
|
|
|
+ pv2 += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // copy normals
|
|
|
+ if (meshList[0]->HasNormals())
|
|
|
+ {
|
|
|
+ pv2 = out->mNormals = new aiVector3D[out->mNumVertices];
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ ::memcpy(pv2,(*it)->mNormals,(*it)->mNumVertices*sizeof(aiVector3D));
|
|
|
+ pv2 += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // copy tangents and bitangents
|
|
|
+ if (meshList[0]->HasTangentsAndBitangents())
|
|
|
+ {
|
|
|
+ pv2 = out->mTangents = new aiVector3D[out->mNumVertices];
|
|
|
+ aiVector3D* pv2b = out->mBitangents = new aiVector3D[out->mNumVertices];
|
|
|
+
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ ::memcpy(pv2, (*it)->mTangents, (*it)->mNumVertices*sizeof(aiVector3D));
|
|
|
+ ::memcpy(pv2b,(*it)->mBitangents,(*it)->mNumVertices*sizeof(aiVector3D));
|
|
|
+ pv2 += (*it)->mNumVertices;
|
|
|
+ pv2b += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // copy texture coordinates
|
|
|
+ unsigned int n = 0;
|
|
|
+ while (meshList[0]->HasTextureCoords(n))
|
|
|
+ {
|
|
|
+ out->mNumUVComponents[n] = meshList[0]->mNumUVComponents[n];
|
|
|
+
|
|
|
+ pv2 = out->mTextureCoords[n] = new aiVector3D[out->mNumVertices];
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ ::memcpy(pv2,(*it)->mTextureCoords[n],(*it)->mNumVertices*sizeof(aiVector3D));
|
|
|
+ pv2 += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+ ++n;
|
|
|
+ }
|
|
|
+ // copy vertex colors
|
|
|
+ n = 0;
|
|
|
+ while (meshList[0]->HasVertexColors(n))
|
|
|
+ {
|
|
|
+ aiColor4D* pv2 = out->mColors[n] = new aiColor4D[out->mNumVertices];
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ ::memcpy(pv2,(*it)->mColors[n],(*it)->mNumVertices*sizeof(aiColor4D));
|
|
|
+ pv2 += (*it)->mNumVertices;
|
|
|
+ }
|
|
|
+ ++n;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (out->mNumFaces) // just for safety
|
|
|
+ {
|
|
|
+ // copy faces
|
|
|
+ out->mFaces = new aiFace[out->mNumFaces];
|
|
|
+ aiFace* pf2 = out->mFaces;
|
|
|
+
|
|
|
+ unsigned int ofs = 0;
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ {
|
|
|
+ for (unsigned int m = 0; m < (*it)->mNumFaces;++m,++pf2)
|
|
|
+ {
|
|
|
+ aiFace& face = (*it)->mFaces[m];
|
|
|
+ pf2->mNumIndices = face.mNumIndices;
|
|
|
+ pf2->mIndices = face.mIndices;
|
|
|
+
|
|
|
+ if (ofs)
|
|
|
+ {
|
|
|
+ // add the offset to the vertex
|
|
|
+ for (unsigned int q = 0; q < face.mNumIndices; ++q)
|
|
|
+ face.mIndices[q] += ofs;
|
|
|
+ }
|
|
|
+ ofs += (*it)->mNumVertices;
|
|
|
+ face.mIndices = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // bones - as this is quite lengthy, I moved the code to a separate function
|
|
|
+ if (out->mNumBones)JoinBones(meshList.begin(),end,out);
|
|
|
+
|
|
|
+ // delete all source meshes
|
|
|
+ for (std::vector<aiMesh*>::const_iterator it = meshList.begin(); it != end;++it)
|
|
|
+ delete *it;
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::ApplyNodeMeshesOptimization(aiNode* pNode)
|
|
|
+{
|
|
|
+ ai_assert(NULL != pNode);
|
|
|
+
|
|
|
+ // find all meshes which are compatible and could therefore be joined.
|
|
|
+ // we can't join meshes that are locked
|
|
|
+ std::vector<aiMesh*> apcMeshes(pNode->mNumMeshes);
|
|
|
+ unsigned int iNumMeshes;
|
|
|
+
|
|
|
+ for (unsigned int m = 0, ttt = 0; m < pNode->mNumMeshes;++m)
|
|
|
+ {
|
|
|
+ iNumMeshes = 0;
|
|
|
+
|
|
|
+ unsigned int nm = pNode->mMeshes[m];
|
|
|
+ if (0xffffffff == nm || AI_OG_IS_MESH_LOCKED(pScene->mMeshes[nm]))continue;
|
|
|
+
|
|
|
+ for (unsigned int q = m+1; q < pNode->mNumMeshes;++q)
|
|
|
+ {
|
|
|
+ register unsigned int nq = pNode->mMeshes[q];
|
|
|
+
|
|
|
+ // skip locked meshes
|
|
|
+ if (AI_OG_IS_MESH_LOCKED(pScene->mMeshes[nq]))continue;
|
|
|
+
|
|
|
+ // compare the mesh hashes
|
|
|
+ if (mMeshHashes[nm] == mMeshHashes[nq])
|
|
|
+ {
|
|
|
+ apcMeshes[iNumMeshes++] = pScene->mMeshes[nq];
|
|
|
+ pNode->mMeshes[q] = 0xffffffff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ aiMesh* out;
|
|
|
+ if (iNumMeshes > 0)
|
|
|
+ {
|
|
|
+ apcMeshes[iNumMeshes++] = pScene->mMeshes[nm];
|
|
|
+ JoinMeshes(apcMeshes,out,iNumMeshes);
|
|
|
+ }
|
|
|
+ else out = pScene->mMeshes[nm];
|
|
|
+
|
|
|
+ pNode->mMeshes[ttt++] = (unsigned int)mOutputMeshes.size();
|
|
|
+ mOutputMeshes.push_back(out);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::TransformMeshes(aiNode* quak,aiNode* pNode)
|
|
|
+{
|
|
|
+ for (unsigned int ä = 0; ä < quak->mNumMeshes;++ä)
|
|
|
+ {
|
|
|
+ aiMesh* mariusIsHot = pScene->mMeshes[quak->mMeshes[ä]];
|
|
|
+ aiMatrix4x4 mMatTransform = pNode->mTransformation;
|
|
|
+
|
|
|
+ // transformation: first back to the parent's local space,
|
|
|
+ // later into the local space of the destination child node
|
|
|
+ mMatTransform.Inverse();
|
|
|
+ mMatTransform = quak->mTransformation * mMatTransform;
|
|
|
+
|
|
|
+ // transform all vertices
|
|
|
+ for (unsigned int oo =0; oo < mariusIsHot->mNumVertices;++oo)
|
|
|
+ mariusIsHot->mVertices[oo] = mMatTransform * mariusIsHot->mVertices[oo];
|
|
|
+
|
|
|
+ // transform all normal vectors
|
|
|
+ if (mariusIsHot->HasNormals())
|
|
|
+ {
|
|
|
+ mMatTransform.Inverse().Transpose();
|
|
|
+ for (unsigned int oo =0; oo < mariusIsHot->mNumVertices;++oo)
|
|
|
+ mariusIsHot->mNormals[oo] = mMatTransform * mariusIsHot->mNormals[oo];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::ApplyOptimizations(aiNode* node)
|
|
|
+{
|
|
|
+ ai_assert(NULL != node);
|
|
|
+
|
|
|
+ unsigned int iJoinedIndex = 0;
|
|
|
+
|
|
|
+ // first: node index; second: number of faces in node
|
|
|
+ NodeIndexList aiBelowTreshold;
|
|
|
+ aiBelowTreshold.reserve(node->mNumChildren);
|
|
|
+
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ {
|
|
|
+ aiNode* pChild = node->mChildren[i];
|
|
|
+ if (AI_OG_IS_NODE_LOCKED(pChild) || !pChild->mNumMeshes)continue;
|
|
|
+
|
|
|
+ // find out how many faces this node is referencing
|
|
|
+ unsigned int iFaceCnt = 0;
|
|
|
+ for (unsigned int a = 0; a < pChild->mNumMeshes;++a)
|
|
|
+ iFaceCnt += pScene->mMeshes[pChild->mMeshes[a]]->mNumFaces;
|
|
|
+
|
|
|
+ // are we below the treshold?
|
|
|
+ if (iFaceCnt < configMinNumFaces)
|
|
|
+ {
|
|
|
+ aiBelowTreshold.push_back(NodeIndexEntry());
|
|
|
+ NodeIndexEntry& p = aiBelowTreshold.back();
|
|
|
+ p.first = i;
|
|
|
+ p.second = iFaceCnt;
|
|
|
+ p.pNode = pChild;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!aiBelowTreshold.empty())
|
|
|
+ {
|
|
|
+ // some typedefs for the data structures we'll need
|
|
|
+ typedef std::pair<unsigned int, unsigned int> JoinListEntry;
|
|
|
+ std::vector<JoinListEntry> aiJoinList(aiBelowTreshold.size());
|
|
|
+ std::vector<unsigned int> aiTempList(aiBelowTreshold.size());
|
|
|
+
|
|
|
+ unsigned int iNumJoins, iNumTemp;
|
|
|
+
|
|
|
+ // sort the list by size
|
|
|
+ std::sort(aiBelowTreshold.begin(),aiBelowTreshold.end());
|
|
|
+
|
|
|
+ unsigned int iStart = 0;
|
|
|
+ for (NodeIndexList::const_iterator it = aiBelowTreshold.begin(),end = aiBelowTreshold.end();
|
|
|
+ it != end; /*++it */++iStart)
|
|
|
+ {
|
|
|
+ aiNode* pNode = node->mChildren[(*it).first];
|
|
|
+
|
|
|
+ // get the hash of the mesh
|
|
|
+ const unsigned int iMeshVFormat = mMeshHashes[pNode->mMeshes[0]];
|
|
|
+
|
|
|
+ // we search for a node with more faces than this ... find
|
|
|
+ // the one that fits best and continue until we've reached
|
|
|
+ // treshold size.
|
|
|
+ int iDiff = configMinNumFaces-(*it).second;
|
|
|
+ for (;;)
|
|
|
+ {
|
|
|
+ // do a binary search and start the iteration there
|
|
|
+ unsigned int index;
|
|
|
+ unsigned int start = BinarySearch(aiBelowTreshold,iDiff,index,iStart);
|
|
|
+
|
|
|
+ if (index == (*it).first)start++;
|
|
|
+
|
|
|
+ if (start >= aiBelowTreshold.size())
|
|
|
+ {
|
|
|
+ // there is no node with enough faces. take the first
|
|
|
+ start = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // todo: implement algorithm to find the best possible combination ...
|
|
|
+ iNumTemp = 0;
|
|
|
+
|
|
|
+ while( start < aiBelowTreshold.size())
|
|
|
+ {
|
|
|
+ // check whether the node has akready been processed before
|
|
|
+ const NodeIndexEntry& entry = aiBelowTreshold[start];
|
|
|
+ if (!entry.pNode)continue;
|
|
|
+
|
|
|
+ const aiNode* pip = node->mChildren[entry.first];
|
|
|
+ if (configJoinInequalTransforms )
|
|
|
+ {
|
|
|
+ // we need to check whether this node has locked meshes
|
|
|
+ // in this case we can't add it here - the meshes will
|
|
|
+ // be transformed from one to another coordinate space
|
|
|
+
|
|
|
+ if (!AI_OG_HAS_NODE_LOCKED_MESHES(pip) || pip->mTransformation == pNode->mTransformation)
|
|
|
+ aiTempList[iNumTemp++] = start;
|
|
|
+ }
|
|
|
+ else if (node->mChildren[entry.first]->mTransformation == pNode->mTransformation)
|
|
|
+ {
|
|
|
+ aiTempList[iNumTemp++] = start;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ ++start;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (iNumTemp)
|
|
|
+ {
|
|
|
+ // search for a node which has a mesh with
|
|
|
+ // - the same material index
|
|
|
+ // - the same vertex layout
|
|
|
+ unsigned int d = iNumJoins = 0;
|
|
|
+ for (unsigned int m = 0; m < iNumTemp;++m)
|
|
|
+ {
|
|
|
+ register unsigned int mn = aiTempList[m];
|
|
|
+ aiNode* pip = aiBelowTreshold[mn].pNode;
|
|
|
+
|
|
|
+ for (unsigned int tt = 0; tt < pip->mNumMeshes;++tt)
|
|
|
+ {
|
|
|
+ register unsigned int mm = pip->mMeshes[tt];
|
|
|
+
|
|
|
+ if (mMeshHashes [ mm ] == iMeshVFormat)
|
|
|
+ {
|
|
|
+ d = mn;
|
|
|
+ goto break_out;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+break_out:
|
|
|
+ aiJoinList[iNumJoins++] = JoinListEntry( aiBelowTreshold[d].first, d );
|
|
|
+ iDiff -= aiBelowTreshold[d].second;
|
|
|
+ }
|
|
|
+ // did we reach the target treshold?
|
|
|
+ if (iDiff <= 0)break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // did we found any nodes to be joined with *this* one?
|
|
|
+ if (iNumJoins)
|
|
|
+ {
|
|
|
+ unsigned int iNumTotalChilds = pNode->mNumChildren;
|
|
|
+ unsigned int iNumTotalMeshes = pNode->mNumMeshes;
|
|
|
+ std::vector<JoinListEntry>::const_iterator wend = aiJoinList.begin()+iNumJoins;
|
|
|
+
|
|
|
+ // get output array bounds
|
|
|
+ for (std::vector<JoinListEntry>::const_iterator wit = aiJoinList.begin();
|
|
|
+ wit != wend;++wit )
|
|
|
+ {
|
|
|
+ aiNode*& quak = node->mChildren[(*wit).first];
|
|
|
+ iNumTotalChilds += AI_OG_UNMASK( quak->mNumChildren );
|
|
|
+ iNumTotalMeshes += quak->mNumMeshes;
|
|
|
+ }
|
|
|
+
|
|
|
+ // build the output child list
|
|
|
+ if (iNumTotalChilds != pNode->mNumChildren)
|
|
|
+ {
|
|
|
+ aiNode** ppc = pNode->mChildren;
|
|
|
+ delete[] pNode->mChildren;
|
|
|
+ pNode->mChildren = new aiNode*[iNumTotalChilds];
|
|
|
+ ::memcpy(pNode->mChildren,ppc, sizeof(void*)* AI_OG_UNMASK( pNode->mNumChildren ));
|
|
|
+
|
|
|
+ for (std::vector<JoinListEntry>::const_iterator wit = aiJoinList.begin();
|
|
|
+ wit != wend;++wit )
|
|
|
+ {
|
|
|
+ aiNode*& quak = node->mChildren[(*wit).first];
|
|
|
+ ::memcpy(pNode->mChildren+pNode->mNumChildren,
|
|
|
+ quak->mChildren, sizeof(void*)*quak->mNumChildren);
|
|
|
+
|
|
|
+ pNode->mNumChildren += AI_OG_UNMASK( quak->mNumChildren );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // build the output mesh list
|
|
|
+ unsigned int* ppc = pNode->mMeshes;
|
|
|
+ delete[] pNode->mMeshes;
|
|
|
+ pNode->mMeshes = new unsigned int[iNumTotalMeshes];
|
|
|
+ ::memcpy(pNode->mMeshes,ppc, sizeof(void*)*pNode->mNumMeshes);
|
|
|
+
|
|
|
+ for (std::vector<JoinListEntry>::const_iterator wit = aiJoinList.begin();
|
|
|
+ wit != wend;++wit )
|
|
|
+ {
|
|
|
+ aiNode*& quak = node->mChildren[(*wit).first];
|
|
|
+ ::memcpy(pNode->mMeshes+pNode->mNumMeshes,
|
|
|
+ quak->mMeshes, sizeof(unsigned int)*quak->mNumMeshes);
|
|
|
+
|
|
|
+ // if the node has a transformation matrix that is not equal to ours,
|
|
|
+ // we'll need to transform all vertices of the mesh into our
|
|
|
+ // local coordinate space.
|
|
|
+ if (configJoinInequalTransforms && quak->mTransformation != pNode->mTransformation)
|
|
|
+ TransformMeshes(quak,pNode);
|
|
|
+
|
|
|
+ pNode->mNumMeshes += quak->mNumMeshes;
|
|
|
+
|
|
|
+ // remove the joined nodes from all lists.
|
|
|
+ aiBelowTreshold[(*wit).second].pNode = NULL;
|
|
|
+ if ((*wit).second == iStart+1)++iStart;
|
|
|
+ }
|
|
|
+
|
|
|
+ // now generate an output name for the joined nodes
|
|
|
+ if (1 == iNumTotalChilds)
|
|
|
+ {
|
|
|
+ pNode->mName.length = ::sprintf( pNode->mName.data, "<Joined_%i_%i>",
|
|
|
+ iJoinedIndex++,iNumJoins+1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // now optimize the meshes in this node
|
|
|
+ ApplyNodeMeshesOptimization(pNode);
|
|
|
+
|
|
|
+ // note - this has been optimized away. The search in the binary
|
|
|
+ // list starts with iStart, which is incremented each iteration
|
|
|
+ ++it; // = aiBelowTreshold.erase(it);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // call all children recursively
|
|
|
+ for (unsigned int i = 0; i < node->mNumChildren;++i)
|
|
|
+ ApplyOptimizations(node->mChildren[i]);
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+void OptimizeGraphProcess::BuildOutputMeshList()
|
|
|
+{
|
|
|
+ // all meshes should have been deleted before if they are
|
|
|
+ // not contained in the new mesh list
|
|
|
+
|
|
|
+ if (pScene->mNumMeshes < mOutputMeshes.size())
|
|
|
+ {
|
|
|
+ delete[] pScene->mMeshes;
|
|
|
+ pScene->mMeshes = new aiMesh*[mOutputMeshes.size()];
|
|
|
+ }
|
|
|
+ pScene->mNumMeshes = (unsigned int)mOutputMeshes.size();
|
|
|
+ ::memcpy(pScene->mMeshes,&mOutputMeshes[0],pScene->mNumMeshes*sizeof(void*));
|
|
|
+}
|
|
|
+
|
|
|
+// ------------------------------------------------------------------------------------------------
|
|
|
+// Executes the post processing step on the given imported data.
|
|
|
+void OptimizeGraphProcess::Execute( aiScene* pScene)
|
|
|
+{
|
|
|
+ this->pScene = pScene;
|
|
|
+ /*
|
|
|
+
|
|
|
+ a) the term "mesh node" stands for a node with numMeshes > 0
|
|
|
+ b) the term "animation node" stands for a node with numMeshes == 0,
|
|
|
+ regardless whether the node is referenced by animation channels.
|
|
|
+
|
|
|
+ Algorithm:
|
|
|
+
|
|
|
+ 1. Compute hashes for all meshes that we're able to check whether
|
|
|
+ two meshes are compatible.
|
|
|
+ 2. Remove animation nodes if we have been configured to do so
|
|
|
+ 3. Find out which nodes may not be moved, so to speak are "locked" - a
|
|
|
+ locked node will never be joined with neighbors.
|
|
|
+ - A node lock is indicated by a set MSB in the aiNode::mNumChildren member
|
|
|
+ 4. Find out which meshes are locked - they are referenced by
|
|
|
+ more than one node. They will never be joined. Mark all
|
|
|
+ nodes referencing such a mesh as "locked", too.
|
|
|
+ - A mesh lock is indicated by a set MSB in the aiMesh::mNumBones member
|
|
|
+ 5. For each unlocked node count the face numbers of all assigned meshes
|
|
|
+ - if it is below the pre-defined treshold add the node to a list.
|
|
|
+ For each node in the list - try to find enough joinable nodes to
|
|
|
+ have enough faces all together.
|
|
|
+ Two nodes are joined if:
|
|
|
+ - none of them is locked
|
|
|
+ - (optional) their world matrices are identical
|
|
|
+ - nodes whose meshes share the same material indices are prefered
|
|
|
+ Two meshes in one node are joined if:
|
|
|
+ - their material indices are identical
|
|
|
+ - none of them is locked
|
|
|
+ - they share the same vertex format
|
|
|
+ 6. Build the final mesh list
|
|
|
+ 7. For all meshes and all nodes - remove locks.
|
|
|
+
|
|
|
+ */
|
|
|
+
|
|
|
+ throw new ImportErrorException("OG step is still undeer development and not yet finished");
|
|
|
+
|
|
|
+ // STEP 1
|
|
|
+ ComputeMeshHashes();
|
|
|
+
|
|
|
+ // STEP 2
|
|
|
+ if (configRemoveAnimations)
|
|
|
+ pScene->mRootNode = RemoveAnimationNodes(pScene->mRootNode);
|
|
|
+
|
|
|
+ // STEP 3
|
|
|
+ else FindLockedNodes(pScene->mRootNode);
|
|
|
+
|
|
|
+ // STEP 4
|
|
|
+ FindLockedMeshes(pScene->mRootNode);
|
|
|
+
|
|
|
+ // STEP 5
|
|
|
+ ApplyOptimizations(pScene->mRootNode);
|
|
|
+
|
|
|
+ // STEP 6
|
|
|
+ BuildOutputMeshList();
|
|
|
+
|
|
|
+ // STEP 7
|
|
|
+ UnlockNodes(pScene->mRootNode);
|
|
|
+ UnlockMeshes();
|
|
|
+}
|