From 463573c771288e935403af3ff465a622bc56a087 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 9 Dec 2019 09:56:01 +0000 Subject: [PATCH 1/6] Apply clangformat --- code/PostProcessing/OptimizeGraph.cpp | 419 +++++++++++++------------- 1 file changed, 208 insertions(+), 211 deletions(-) diff --git a/code/PostProcessing/OptimizeGraph.cpp b/code/PostProcessing/OptimizeGraph.cpp index 5db51f58b..709f465e2 100644 --- a/code/PostProcessing/OptimizeGraph.cpp +++ b/code/PostProcessing/OptimizeGraph.cpp @@ -43,13 +43,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of the aiProcess_OptimizGraph step */ - #ifndef ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS #include "OptimizeGraph.h" #include "ProcessHelper.h" -#include #include +#include #include using namespace Assimp; @@ -60,292 +59,290 @@ using namespace Assimp; * The unhashed variant should be faster, except for *very* large data sets */ #ifdef AI_OG_USE_HASHING - // Use our standard hashing function to compute the hash -# define AI_OG_GETKEY(str) SuperFastHash(str.data,str.length) +// Use our standard hashing function to compute the hash +#define AI_OG_GETKEY(str) SuperFastHash(str.data, str.length) #else - // Otherwise hope that std::string will utilize a static buffer - // for shorter node names. This would avoid endless heap copying. -# define AI_OG_GETKEY(str) std::string(str.data) +// Otherwise hope that std::string will utilize a static buffer +// for shorter node names. This would avoid endless heap copying. +#define AI_OG_GETKEY(str) std::string(str.data) #endif // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -OptimizeGraphProcess::OptimizeGraphProcess() -: mScene() -, nodes_in() -, nodes_out() -, count_merged() { - // empty +OptimizeGraphProcess::OptimizeGraphProcess() : + mScene(), + nodes_in(), + nodes_out(), + count_merged() { + // empty } // ------------------------------------------------------------------------------------------------ // Destructor, private as well OptimizeGraphProcess::~OptimizeGraphProcess() { - // empty + // empty } // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool OptimizeGraphProcess::IsActive( unsigned int pFlags) const { - return (0 != (pFlags & aiProcess_OptimizeGraph)); +bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const { + return (0 != (pFlags & aiProcess_OptimizeGraph)); } // ------------------------------------------------------------------------------------------------ // Setup properties for the post-processing step -void OptimizeGraphProcess::SetupProperties(const Importer* pImp) { - // Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST - std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST,""); - AddLockedNodeList(tmp); +void OptimizeGraphProcess::SetupProperties(const Importer *pImp) { + // Get value of AI_CONFIG_PP_OG_EXCLUDE_LIST + std::string tmp = pImp->GetPropertyString(AI_CONFIG_PP_OG_EXCLUDE_LIST, ""); + AddLockedNodeList(tmp); } // ------------------------------------------------------------------------------------------------ // Collect new children -void OptimizeGraphProcess::CollectNewChildren(aiNode* nd, std::list& nodes) { - nodes_in += nd->mNumChildren; +void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &nodes) { + nodes_in += nd->mNumChildren; - // Process children - std::list child_nodes; - for (unsigned int i = 0; i < nd->mNumChildren; ++i) { - CollectNewChildren(nd->mChildren[i],child_nodes); - nd->mChildren[i] = nullptr; - } + // Process children + std::list child_nodes; + for (unsigned int i = 0; i < nd->mNumChildren; ++i) { + CollectNewChildren(nd->mChildren[i], child_nodes); + nd->mChildren[i] = nullptr; + } - // Check whether we need this node; if not we can replace it by our own children (warn, danger of incest). - if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end() ) { - for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end();) { + // Check whether we need this node; if not we can replace it by our own children (warn, danger of incest). + if (locked.find(AI_OG_GETKEY(nd->mName)) == locked.end()) { + for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end();) { - if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) { - (*it)->mTransformation = nd->mTransformation * (*it)->mTransformation; - nodes.push_back(*it); + if (locked.find(AI_OG_GETKEY((*it)->mName)) == locked.end()) { + (*it)->mTransformation = nd->mTransformation * (*it)->mTransformation; + nodes.push_back(*it); - it = child_nodes.erase(it); - continue; - } - ++it; - } + it = child_nodes.erase(it); + continue; + } + ++it; + } - if (nd->mNumMeshes || !child_nodes.empty()) { - nodes.push_back(nd); - } else { - delete nd; /* bye, node */ - return; - } - } else { + if (nd->mNumMeshes || !child_nodes.empty()) { + nodes.push_back(nd); + } else { + delete nd; /* bye, node */ + return; + } + } else { - // Retain our current position in the hierarchy - nodes.push_back(nd); + // Retain our current position in the hierarchy + nodes.push_back(nd); - // Now check for possible optimizations in our list of child nodes. join as many as possible - aiNode* join_master = NULL; - aiMatrix4x4 inv; + // Now check for possible optimizations in our list of child nodes. join as many as possible + aiNode *join_master = NULL; + aiMatrix4x4 inv; - const LockedSetType::const_iterator end = locked.end(); + const LockedSetType::const_iterator end = locked.end(); - std::list join; - for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end();) { - aiNode* child = *it; - if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) { + std::list join; + for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end();) { + aiNode *child = *it; + if (child->mNumChildren == 0 && locked.find(AI_OG_GETKEY(child->mName)) == end) { - // There may be no instanced meshes - unsigned int n = 0; - for (; n < child->mNumMeshes;++n) { - if (meshes[child->mMeshes[n]] > 1) { - break; - } - } - if (n == child->mNumMeshes) { - if (!join_master) { - join_master = child; - inv = join_master->mTransformation; - inv.Inverse(); - } else { - child->mTransformation = inv * child->mTransformation ; + // There may be no instanced meshes + unsigned int n = 0; + for (; n < child->mNumMeshes; ++n) { + if (meshes[child->mMeshes[n]] > 1) { + break; + } + } + if (n == child->mNumMeshes) { + if (!join_master) { + join_master = child; + inv = join_master->mTransformation; + inv.Inverse(); + } else { + child->mTransformation = inv * child->mTransformation; - join.push_back(child); - it = child_nodes.erase(it); - continue; - } - } - } - ++it; - } - if (join_master && !join.empty()) { - join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i",count_merged++); + join.push_back(child); + it = child_nodes.erase(it); + continue; + } + } + } + ++it; + } + if (join_master && !join.empty()) { + join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i", count_merged++); - unsigned int out_meshes = 0; - for (std::list::iterator it = join.begin(); it != join.end(); ++it) { - out_meshes += (*it)->mNumMeshes; - } + unsigned int out_meshes = 0; + for (std::list::iterator it = join.begin(); it != join.end(); ++it) { + out_meshes += (*it)->mNumMeshes; + } - // copy all mesh references in one array - if (out_meshes) { - unsigned int* meshes = new unsigned int[out_meshes+join_master->mNumMeshes], *tmp = meshes; - for (unsigned int n = 0; n < join_master->mNumMeshes;++n) { - *tmp++ = join_master->mMeshes[n]; - } + // copy all mesh references in one array + if (out_meshes) { + unsigned int *meshes = new unsigned int[out_meshes + join_master->mNumMeshes], *tmp = meshes; + for (unsigned int n = 0; n < join_master->mNumMeshes; ++n) { + *tmp++ = join_master->mMeshes[n]; + } - for (std::list::iterator it = join.begin(); it != join.end(); ++it) { - for (unsigned int n = 0; n < (*it)->mNumMeshes; ++n) { + for (std::list::iterator it = join.begin(); it != join.end(); ++it) { + for (unsigned int n = 0; n < (*it)->mNumMeshes; ++n) { - *tmp = (*it)->mMeshes[n]; - aiMesh* mesh = mScene->mMeshes[*tmp++]; + *tmp = (*it)->mMeshes[n]; + aiMesh *mesh = mScene->mMeshes[*tmp++]; - // manually move the mesh into the right coordinate system - const aiMatrix3x3 IT = aiMatrix3x3( (*it)->mTransformation ).Inverse().Transpose(); - for (unsigned int a = 0; a < mesh->mNumVertices; ++a) { + // manually move the mesh into the right coordinate system + const aiMatrix3x3 IT = aiMatrix3x3((*it)->mTransformation).Inverse().Transpose(); + for (unsigned int a = 0; a < mesh->mNumVertices; ++a) { - mesh->mVertices[a] *= (*it)->mTransformation; + mesh->mVertices[a] *= (*it)->mTransformation; - if (mesh->HasNormals()) - mesh->mNormals[a] *= IT; + if (mesh->HasNormals()) + mesh->mNormals[a] *= IT; - if (mesh->HasTangentsAndBitangents()) { - mesh->mTangents[a] *= IT; - mesh->mBitangents[a] *= IT; - } - } - } - delete *it; // bye, node - } - delete[] join_master->mMeshes; - join_master->mMeshes = meshes; - join_master->mNumMeshes += out_meshes; - } - } - } - // reassign children if something changed - if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) { + if (mesh->HasTangentsAndBitangents()) { + mesh->mTangents[a] *= IT; + mesh->mBitangents[a] *= IT; + } + } + } + delete *it; // bye, node + } + delete[] join_master->mMeshes; + join_master->mMeshes = meshes; + join_master->mNumMeshes += out_meshes; + } + } + } + // reassign children if something changed + if (child_nodes.empty() || child_nodes.size() > nd->mNumChildren) { - delete[] nd->mChildren; + delete[] nd->mChildren; - if (!child_nodes.empty()) { - nd->mChildren = new aiNode*[child_nodes.size()]; - } - else nd->mChildren = nullptr; - } + if (!child_nodes.empty()) { + nd->mChildren = new aiNode *[child_nodes.size()]; + } else + nd->mChildren = nullptr; + } - nd->mNumChildren = static_cast(child_nodes.size()); + nd->mNumChildren = static_cast(child_nodes.size()); - if (nd->mChildren) { - aiNode** tmp = nd->mChildren; - for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) { - aiNode* node = *tmp++ = *it; - node->mParent = nd; - } - } + if (nd->mChildren) { + aiNode **tmp = nd->mChildren; + for (std::list::iterator it = child_nodes.begin(); it != child_nodes.end(); ++it) { + aiNode *node = *tmp++ = *it; + node->mParent = nd; + } + } - nodes_out += static_cast(child_nodes.size()); + nodes_out += static_cast(child_nodes.size()); } // ------------------------------------------------------------------------------------------------ // Execute the post-processing step on the given scene -void OptimizeGraphProcess::Execute( aiScene* pScene) { - ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin"); - nodes_in = nodes_out = count_merged = 0; - mScene = pScene; +void OptimizeGraphProcess::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("OptimizeGraphProcess begin"); + nodes_in = nodes_out = count_merged = 0; + mScene = pScene; - meshes.resize(pScene->mNumMeshes,0); - FindInstancedMeshes(pScene->mRootNode); + meshes.resize(pScene->mNumMeshes, 0); + FindInstancedMeshes(pScene->mRootNode); - // build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it - locked.clear(); - for (std::list::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) { + // build a blacklist of identifiers. If the name of a node matches one of these, we won't touch it + locked.clear(); + for (std::list::const_iterator it = locked_nodes.begin(); it != locked_nodes.end(); ++it) { #ifdef AI_OG_USE_HASHING - locked.insert(SuperFastHash((*it).c_str())); + locked.insert(SuperFastHash((*it).c_str())); #else - locked.insert(*it); + locked.insert(*it); #endif - } + } - for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) { - for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) { - aiNodeAnim* anim = pScene->mAnimations[i]->mChannels[a]; - locked.insert(AI_OG_GETKEY(anim->mNodeName)); - } - } + for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) { + for (unsigned int a = 0; a < pScene->mAnimations[i]->mNumChannels; ++a) { + aiNodeAnim *anim = pScene->mAnimations[i]->mChannels[a]; + locked.insert(AI_OG_GETKEY(anim->mNodeName)); + } + } - for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { - for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) { + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + for (unsigned int a = 0; a < pScene->mMeshes[i]->mNumBones; ++a) { - aiBone* bone = pScene->mMeshes[i]->mBones[a]; - locked.insert(AI_OG_GETKEY(bone->mName)); + aiBone *bone = pScene->mMeshes[i]->mBones[a]; + locked.insert(AI_OG_GETKEY(bone->mName)); - // HACK: Meshes referencing bones may not be transformed; we need to look them. - // The easiest way to do this is to increase their reference counters ... - meshes[i] += 2; - } - } + // HACK: Meshes referencing bones may not be transformed; we need to look them. + // The easiest way to do this is to increase their reference counters ... + meshes[i] += 2; + } + } - for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { - aiCamera* cam = pScene->mCameras[i]; - locked.insert(AI_OG_GETKEY(cam->mName)); - } + for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { + aiCamera *cam = pScene->mCameras[i]; + locked.insert(AI_OG_GETKEY(cam->mName)); + } - for (unsigned int i = 0; i < pScene->mNumLights; ++i) { - aiLight* lgh = pScene->mLights[i]; - locked.insert(AI_OG_GETKEY(lgh->mName)); - } + for (unsigned int i = 0; i < pScene->mNumLights; ++i) { + aiLight *lgh = pScene->mLights[i]; + locked.insert(AI_OG_GETKEY(lgh->mName)); + } - // Insert a dummy master node and make it read-only - aiNode* dummy_root = new aiNode(AI_RESERVED_NODE_NAME); - locked.insert(AI_OG_GETKEY(dummy_root->mName)); + // Insert a dummy master node and make it read-only + aiNode *dummy_root = new aiNode(AI_RESERVED_NODE_NAME); + locked.insert(AI_OG_GETKEY(dummy_root->mName)); - const aiString prev = pScene->mRootNode->mName; - pScene->mRootNode->mParent = dummy_root; + const aiString prev = pScene->mRootNode->mName; + pScene->mRootNode->mParent = dummy_root; - dummy_root->mChildren = new aiNode*[dummy_root->mNumChildren = 1]; - dummy_root->mChildren[0] = pScene->mRootNode; + dummy_root->mChildren = new aiNode *[dummy_root->mNumChildren = 1]; + dummy_root->mChildren[0] = pScene->mRootNode; - // Do our recursive processing of scenegraph nodes. For each node collect - // a fully new list of children and allow their children to place themselves - // on the same hierarchy layer as their parents. - std::list nodes; - CollectNewChildren (dummy_root,nodes); + // Do our recursive processing of scenegraph nodes. For each node collect + // a fully new list of children and allow their children to place themselves + // on the same hierarchy layer as their parents. + std::list nodes; + CollectNewChildren(dummy_root, nodes); - ai_assert(nodes.size() == 1); + ai_assert(nodes.size() == 1); - if (dummy_root->mNumChildren == 0) { - pScene->mRootNode = NULL; - throw DeadlyImportError("After optimizing the scene graph, no data remains"); - } + if (dummy_root->mNumChildren == 0) { + pScene->mRootNode = NULL; + throw DeadlyImportError("After optimizing the scene graph, no data remains"); + } - if (dummy_root->mNumChildren > 1) { - pScene->mRootNode = dummy_root; + if (dummy_root->mNumChildren > 1) { + pScene->mRootNode = dummy_root; - // Keep the dummy node but assign the name of the old root node to it - pScene->mRootNode->mName = prev; - } - else { + // Keep the dummy node but assign the name of the old root node to it + pScene->mRootNode->mName = prev; + } else { - // Remove the dummy root node again. - pScene->mRootNode = dummy_root->mChildren[0]; + // Remove the dummy root node again. + pScene->mRootNode = dummy_root->mChildren[0]; - dummy_root->mChildren[0] = NULL; - delete dummy_root; - } + dummy_root->mChildren[0] = NULL; + delete dummy_root; + } - pScene->mRootNode->mParent = NULL; - if (!DefaultLogger::isNullLogger()) { - if ( nodes_in != nodes_out) { - ASSIMP_LOG_INFO_F("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out); - } else { - ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished"); - } - } - meshes.clear(); - locked.clear(); + pScene->mRootNode->mParent = NULL; + if (!DefaultLogger::isNullLogger()) { + if (nodes_in != nodes_out) { + ASSIMP_LOG_INFO_F("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out); + } else { + ASSIMP_LOG_DEBUG("OptimizeGraphProcess finished"); + } + } + meshes.clear(); + locked.clear(); } // ------------------------------------------------------------------------------------------------ // Build a LUT of all instanced meshes -void OptimizeGraphProcess::FindInstancedMeshes (aiNode* pNode) -{ - for (unsigned int i = 0; i < pNode->mNumMeshes;++i) { - ++meshes[pNode->mMeshes[i]]; - } +void OptimizeGraphProcess::FindInstancedMeshes(aiNode *pNode) { + for (unsigned int i = 0; i < pNode->mNumMeshes; ++i) { + ++meshes[pNode->mMeshes[i]]; + } - for (unsigned int i = 0; i < pNode->mNumChildren; ++i) - FindInstancedMeshes(pNode->mChildren[i]); + for (unsigned int i = 0; i < pNode->mNumChildren; ++i) + FindInstancedMeshes(pNode->mChildren[i]); } #endif // !! ASSIMP_BUILD_NO_OPTIMIZEGRAPH_PROCESS From 193b02cdac6cf38ad87d57c20e8e19da33157e0f Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 9 Dec 2019 10:42:50 +0000 Subject: [PATCH 2/6] Odd negative scale: OptimizeGraph OptimizeGraph postprocessing now reverses face order when node scale is mirroring. Fixes flip to backfacing in models that mirrored some nodes. (Odd count of negative scale components, negative determinant) --- code/PostProcessing/ConvertToLHProcess.h | 5 ++-- code/PostProcessing/OptimizeGraph.cpp | 32 ++++++++++++++++-------- code/PostProcessing/OptimizeGraph.h | 6 ++--- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/code/PostProcessing/ConvertToLHProcess.h b/code/PostProcessing/ConvertToLHProcess.h index f32b91fc3..0c4a3a091 100644 --- a/code/PostProcessing/ConvertToLHProcess.h +++ b/code/PostProcessing/ConvertToLHProcess.h @@ -137,8 +137,9 @@ public: // ------------------------------------------------------------------- void Execute( aiScene* pScene); -protected: - void ProcessMesh( aiMesh* pMesh); +public: + /** Some other types of post-processing require winding order flips */ + static void ProcessMesh( aiMesh* pMesh); }; // --------------------------------------------------------------------------- diff --git a/code/PostProcessing/OptimizeGraph.cpp b/code/PostProcessing/OptimizeGraph.cpp index 709f465e2..43bd7a3ee 100644 --- a/code/PostProcessing/OptimizeGraph.cpp +++ b/code/PostProcessing/OptimizeGraph.cpp @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "OptimizeGraph.h" #include "ProcessHelper.h" +#include "ConvertToLHProcess.h" #include #include #include @@ -135,7 +136,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n nodes.push_back(nd); // Now check for possible optimizations in our list of child nodes. join as many as possible - aiNode *join_master = NULL; + aiNode *join_master = nullptr; aiMatrix4x4 inv; const LockedSetType::const_iterator end = locked.end(); @@ -172,7 +173,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n join_master->mName.length = ::ai_snprintf(join_master->mName.data, MAXLEN, "$MergedNode_%i", count_merged++); unsigned int out_meshes = 0; - for (std::list::iterator it = join.begin(); it != join.end(); ++it) { + for (std::list::const_iterator it = join.cbegin(); it != join.cend(); ++it) { out_meshes += (*it)->mNumMeshes; } @@ -183,17 +184,26 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n *tmp++ = join_master->mMeshes[n]; } - for (std::list::iterator it = join.begin(); it != join.end(); ++it) { - for (unsigned int n = 0; n < (*it)->mNumMeshes; ++n) { + for (const aiNode *join_node : join) { + for (unsigned int n = 0; n < join_node->mNumMeshes; ++n) { - *tmp = (*it)->mMeshes[n]; + *tmp = join_node->mMeshes[n]; aiMesh *mesh = mScene->mMeshes[*tmp++]; + // Assume the transformation is affine // manually move the mesh into the right coordinate system - const aiMatrix3x3 IT = aiMatrix3x3((*it)->mTransformation).Inverse().Transpose(); + + // Check for odd negative scale (mirror) + if (join_node->mTransformation.Determinant() < 0) { + // Reverse the mesh face winding order + FlipWindingOrderProcess::ProcessMesh(mesh); + } + + // Update positions, normals and tangents + const aiMatrix3x3 IT = aiMatrix3x3(join_node->mTransformation).Inverse().Transpose(); for (unsigned int a = 0; a < mesh->mNumVertices; ++a) { - mesh->mVertices[a] *= (*it)->mTransformation; + mesh->mVertices[a] *= join_node->mTransformation; if (mesh->HasNormals()) mesh->mNormals[a] *= IT; @@ -204,7 +214,7 @@ void OptimizeGraphProcess::CollectNewChildren(aiNode *nd, std::list &n } } } - delete *it; // bye, node + delete join_node; // bye, node } delete[] join_master->mMeshes; join_master->mMeshes = meshes; @@ -304,7 +314,7 @@ void OptimizeGraphProcess::Execute(aiScene *pScene) { ai_assert(nodes.size() == 1); if (dummy_root->mNumChildren == 0) { - pScene->mRootNode = NULL; + pScene->mRootNode = nullptr; throw DeadlyImportError("After optimizing the scene graph, no data remains"); } @@ -318,11 +328,11 @@ void OptimizeGraphProcess::Execute(aiScene *pScene) { // Remove the dummy root node again. pScene->mRootNode = dummy_root->mChildren[0]; - dummy_root->mChildren[0] = NULL; + dummy_root->mChildren[0] = nullptr; delete dummy_root; } - pScene->mRootNode->mParent = NULL; + pScene->mRootNode->mParent = nullptr; if (!DefaultLogger::isNullLogger()) { if (nodes_in != nodes_out) { ASSIMP_LOG_INFO_F("OptimizeGraphProcess finished; Input nodes: ", nodes_in, ", Output nodes: ", nodes_out); diff --git a/code/PostProcessing/OptimizeGraph.h b/code/PostProcessing/OptimizeGraph.h index 82cc5db3f..d2a6de9a2 100644 --- a/code/PostProcessing/OptimizeGraph.h +++ b/code/PostProcessing/OptimizeGraph.h @@ -75,13 +75,13 @@ public: ~OptimizeGraphProcess(); // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** @brief Add a list of node names to be locked and not modified. From 9cabeddf4f85c12df950673c29115e3a1a299099 Mon Sep 17 00:00:00 2001 From: RichardTea <31507749+RichardTea@users.noreply.github.com> Date: Mon, 9 Dec 2019 11:07:13 +0000 Subject: [PATCH 3/6] Odd Negative Scale: PretransformVertices PretransformVertices postprocessing now reverses face order when transform is mirroring. Fixes flip to backfacing in models that mirrored some nodes. (Odd count of negative scale components, negative determinant) --- code/PostProcessing/PretransformVertices.cpp | 1038 +++++++++--------- code/PostProcessing/PretransformVertices.h | 142 +-- 2 files changed, 570 insertions(+), 610 deletions(-) diff --git a/code/PostProcessing/PretransformVertices.cpp b/code/PostProcessing/PretransformVertices.cpp index 52001a057..fb6b458d8 100644 --- a/code/PostProcessing/PretransformVertices.cpp +++ b/code/PostProcessing/PretransformVertices.cpp @@ -45,11 +45,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * @brief Implementation of the "PretransformVertices" post processing step */ - #include "PretransformVertices.h" +#include "ConvertToLHProcess.h" #include "ProcessHelper.h" -#include #include +#include using namespace Assimp; @@ -59,670 +59,630 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -PretransformVertices::PretransformVertices() -: configKeepHierarchy (false) -, configNormalize(false) -, configTransform(false) -, configTransformation() -, mConfigPointCloud( false ) { - // empty +PretransformVertices::PretransformVertices() : + configKeepHierarchy(false), + configNormalize(false), + configTransform(false), + configTransformation(), + mConfigPointCloud(false) { + // empty } // ------------------------------------------------------------------------------------------------ // Destructor, private as well PretransformVertices::~PretransformVertices() { - // nothing to do here + // nothing to do here } // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool PretransformVertices::IsActive( unsigned int pFlags) const -{ - return (pFlags & aiProcess_PreTransformVertices) != 0; +bool PretransformVertices::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PreTransformVertices) != 0; } // ------------------------------------------------------------------------------------------------ // Setup import configuration -void PretransformVertices::SetupProperties(const Importer* pImp) -{ - // Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE, - // AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION - configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY,0)); - configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE,0)); - configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION,0)); +void PretransformVertices::SetupProperties(const Importer *pImp) { + // Get the current value of AI_CONFIG_PP_PTV_KEEP_HIERARCHY, AI_CONFIG_PP_PTV_NORMALIZE, + // AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION and AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION + configKeepHierarchy = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, 0)); + configNormalize = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_NORMALIZE, 0)); + configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION, 0)); - configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4()); + configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4()); - mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); + mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS); } // ------------------------------------------------------------------------------------------------ // Count the number of nodes -unsigned int PretransformVertices::CountNodes( aiNode* pcNode ) -{ - unsigned int iRet = 1; - for (unsigned int i = 0;i < pcNode->mNumChildren;++i) - { - iRet += CountNodes(pcNode->mChildren[i]); - } - return iRet; +unsigned int PretransformVertices::CountNodes(const aiNode *pcNode) const { + unsigned int iRet = 1; + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + iRet += CountNodes(pcNode->mChildren[i]); + } + return iRet; } // ------------------------------------------------------------------------------------------------ // Get a bitwise combination identifying the vertex format of a mesh -unsigned int PretransformVertices::GetMeshVFormat( aiMesh* pcMesh ) -{ - // the vertex format is stored in aiMesh::mBones for later retrieval. - // there isn't a good reason to compute it a few hundred times - // from scratch. The pointer is unused as animations are lost - // during PretransformVertices. - if (pcMesh->mBones) - return (unsigned int)(uint64_t)pcMesh->mBones; +unsigned int PretransformVertices::GetMeshVFormat(aiMesh *pcMesh) const { + // the vertex format is stored in aiMesh::mBones for later retrieval. + // there isn't a good reason to compute it a few hundred times + // from scratch. The pointer is unused as animations are lost + // during PretransformVertices. + if (pcMesh->mBones) + return (unsigned int)(uint64_t)pcMesh->mBones; + const unsigned int iRet = GetMeshVFormatUnique(pcMesh); - const unsigned int iRet = GetMeshVFormatUnique(pcMesh); - - // store the value for later use - pcMesh->mBones = (aiBone**)(uint64_t)iRet; - return iRet; + // store the value for later use + pcMesh->mBones = (aiBone **)(uint64_t)iRet; + return iRet; } // ------------------------------------------------------------------------------------------------ // Count the number of vertices in the whole scene and a given // material index -void PretransformVertices::CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, unsigned int iMat, - unsigned int iVFormat, unsigned int* piFaces, unsigned int* piVertices) -{ - for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) - { - aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ]; - if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) - { - *piVertices += pcMesh->mNumVertices; - *piFaces += pcMesh->mNumFaces; - } - } - for (unsigned int i = 0;i < pcNode->mNumChildren;++i) - { - CountVerticesAndFaces(pcScene,pcNode->mChildren[i],iMat, - iVFormat,piFaces,piVertices); - } +void PretransformVertices::CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat, + unsigned int iVFormat, unsigned int *piFaces, unsigned int *piVertices) const { + for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]]; + if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) { + *piVertices += pcMesh->mNumVertices; + *piFaces += pcMesh->mNumFaces; + } + } + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + CountVerticesAndFaces(pcScene, pcNode->mChildren[i], iMat, + iVFormat, piFaces, piVertices); + } } // ------------------------------------------------------------------------------------------------ // Collect vertex/face data -void PretransformVertices::CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat, - unsigned int iVFormat, aiMesh* pcMeshOut, - unsigned int aiCurrent[2], unsigned int* num_refs) -{ - // No need to multiply if there's no transformation - const bool identity = pcNode->mTransformation.IsIdentity(); - for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) - { - aiMesh* pcMesh = pcScene->mMeshes[ pcNode->mMeshes[i] ]; - if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) - { - // Decrement mesh reference counter - unsigned int& num_ref = num_refs[pcNode->mMeshes[i]]; - ai_assert(0 != num_ref); - --num_ref; - // Save the name of the last mesh - if (num_ref==0) - { - pcMeshOut->mName = pcMesh->mName; - } +void PretransformVertices::CollectData(const aiScene *pcScene, const aiNode *pcNode, unsigned int iMat, + unsigned int iVFormat, aiMesh *pcMeshOut, + unsigned int aiCurrent[2], unsigned int *num_refs) const { + // No need to multiply if there's no transformation + const bool identity = pcNode->mTransformation.IsIdentity(); + for (unsigned int i = 0; i < pcNode->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[pcNode->mMeshes[i]]; + if (iMat == pcMesh->mMaterialIndex && iVFormat == GetMeshVFormat(pcMesh)) { + // Decrement mesh reference counter + unsigned int &num_ref = num_refs[pcNode->mMeshes[i]]; + ai_assert(0 != num_ref); + --num_ref; + // Save the name of the last mesh + if (num_ref == 0) { + pcMeshOut->mName = pcMesh->mName; + } - if (identity) { - // copy positions without modifying them - ::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mVertices, - pcMesh->mNumVertices * sizeof(aiVector3D)); + if (identity) { + // copy positions without modifying them + ::memcpy(pcMeshOut->mVertices + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mVertices, + pcMesh->mNumVertices * sizeof(aiVector3D)); - if (iVFormat & 0x2) { - // copy normals without modifying them - ::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mNormals, - pcMesh->mNumVertices * sizeof(aiVector3D)); - } - if (iVFormat & 0x4) - { - // copy tangents without modifying them - ::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mTangents, - pcMesh->mNumVertices * sizeof(aiVector3D)); - // copy bitangents without modifying them - ::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mBitangents, - pcMesh->mNumVertices * sizeof(aiVector3D)); - } - } - else - { - // copy positions, transform them to worldspace - for (unsigned int n = 0; n < pcMesh->mNumVertices;++n) { - pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX]+n] = pcNode->mTransformation * pcMesh->mVertices[n]; - } - aiMatrix4x4 mWorldIT = pcNode->mTransformation; - mWorldIT.Inverse().Transpose(); + if (iVFormat & 0x2) { + // copy normals without modifying them + ::memcpy(pcMeshOut->mNormals + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mNormals, + pcMesh->mNumVertices * sizeof(aiVector3D)); + } + if (iVFormat & 0x4) { + // copy tangents without modifying them + ::memcpy(pcMeshOut->mTangents + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mTangents, + pcMesh->mNumVertices * sizeof(aiVector3D)); + // copy bitangents without modifying them + ::memcpy(pcMeshOut->mBitangents + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mBitangents, + pcMesh->mNumVertices * sizeof(aiVector3D)); + } + } else { + // copy positions, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mVertices[aiCurrent[AI_PTVS_VERTEX] + n] = pcNode->mTransformation * pcMesh->mVertices[n]; + } + aiMatrix4x4 mWorldIT = pcNode->mTransformation; + mWorldIT.Inverse().Transpose(); - // TODO: implement Inverse() for aiMatrix3x3 - aiMatrix3x3 m = aiMatrix3x3(mWorldIT); + // TODO: implement Inverse() for aiMatrix3x3 + aiMatrix3x3 m = aiMatrix3x3(mWorldIT); - if (iVFormat & 0x2) - { - // copy normals, transform them to worldspace - for (unsigned int n = 0; n < pcMesh->mNumVertices;++n) { - pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX]+n] = - (m * pcMesh->mNormals[n]).Normalize(); - } - } - if (iVFormat & 0x4) - { - // copy tangents and bitangents, transform them to worldspace - for (unsigned int n = 0; n < pcMesh->mNumVertices;++n) { - pcMeshOut->mTangents [aiCurrent[AI_PTVS_VERTEX]+n] = (m * pcMesh->mTangents[n]).Normalize(); - pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX]+n] = (m * pcMesh->mBitangents[n]).Normalize(); - } - } - } - unsigned int p = 0; - while (iVFormat & (0x100 << p)) - { - // copy texture coordinates - memcpy(pcMeshOut->mTextureCoords[p] + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mTextureCoords[p], - pcMesh->mNumVertices * sizeof(aiVector3D)); - ++p; - } - p = 0; - while (iVFormat & (0x1000000 << p)) - { - // copy vertex colors - memcpy(pcMeshOut->mColors[p] + aiCurrent[AI_PTVS_VERTEX], - pcMesh->mColors[p], - pcMesh->mNumVertices * sizeof(aiColor4D)); - ++p; - } - // now we need to copy all faces. since we will delete the source mesh afterwards, - // we don't need to reallocate the array of indices except if this mesh is - // referenced multiple times. - for (unsigned int planck = 0;planck < pcMesh->mNumFaces;++planck) - { - aiFace& f_src = pcMesh->mFaces[planck]; - aiFace& f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE]+planck]; + if (iVFormat & 0x2) { + // copy normals, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mNormals[aiCurrent[AI_PTVS_VERTEX] + n] = + (m * pcMesh->mNormals[n]).Normalize(); + } + } + if (iVFormat & 0x4) { + // copy tangents and bitangents, transform them to worldspace + for (unsigned int n = 0; n < pcMesh->mNumVertices; ++n) { + pcMeshOut->mTangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mTangents[n]).Normalize(); + pcMeshOut->mBitangents[aiCurrent[AI_PTVS_VERTEX] + n] = (m * pcMesh->mBitangents[n]).Normalize(); + } + } + } + unsigned int p = 0; + while (iVFormat & (0x100 << p)) { + // copy texture coordinates + memcpy(pcMeshOut->mTextureCoords[p] + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mTextureCoords[p], + pcMesh->mNumVertices * sizeof(aiVector3D)); + ++p; + } + p = 0; + while (iVFormat & (0x1000000 << p)) { + // copy vertex colors + memcpy(pcMeshOut->mColors[p] + aiCurrent[AI_PTVS_VERTEX], + pcMesh->mColors[p], + pcMesh->mNumVertices * sizeof(aiColor4D)); + ++p; + } + // now we need to copy all faces. since we will delete the source mesh afterwards, + // we don't need to reallocate the array of indices except if this mesh is + // referenced multiple times. + for (unsigned int planck = 0; planck < pcMesh->mNumFaces; ++planck) { + aiFace &f_src = pcMesh->mFaces[planck]; + aiFace &f_dst = pcMeshOut->mFaces[aiCurrent[AI_PTVS_FACE] + planck]; - const unsigned int num_idx = f_src.mNumIndices; + const unsigned int num_idx = f_src.mNumIndices; - f_dst.mNumIndices = num_idx; + f_dst.mNumIndices = num_idx; - unsigned int* pi; - if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */ - pi = f_dst.mIndices = f_src.mIndices; + unsigned int *pi; + if (!num_ref) { /* if last time the mesh is referenced -> no reallocation */ + pi = f_dst.mIndices = f_src.mIndices; - // offset all vertex indices - for (unsigned int hahn = 0; hahn < num_idx;++hahn){ - pi[hahn] += aiCurrent[AI_PTVS_VERTEX]; - } - } - else { - pi = f_dst.mIndices = new unsigned int[num_idx]; + // offset all vertex indices + for (unsigned int hahn = 0; hahn < num_idx; ++hahn) { + pi[hahn] += aiCurrent[AI_PTVS_VERTEX]; + } + } else { + pi = f_dst.mIndices = new unsigned int[num_idx]; - // copy and offset all vertex indices - for (unsigned int hahn = 0; hahn < num_idx;++hahn){ - pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX]; - } - } + // copy and offset all vertex indices + for (unsigned int hahn = 0; hahn < num_idx; ++hahn) { + pi[hahn] = f_src.mIndices[hahn] + aiCurrent[AI_PTVS_VERTEX]; + } + } - // Update the mPrimitiveTypes member of the mesh - switch (pcMesh->mFaces[planck].mNumIndices) - { - case 0x1: - pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; - case 0x2: - pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; - case 0x3: - pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; - default: - pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POLYGON; - break; - }; - } - aiCurrent[AI_PTVS_VERTEX] += pcMesh->mNumVertices; - aiCurrent[AI_PTVS_FACE] += pcMesh->mNumFaces; - } - } + // Update the mPrimitiveTypes member of the mesh + switch (pcMesh->mFaces[planck].mNumIndices) { + case 0x1: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; + case 0x2: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; + case 0x3: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + default: + pcMeshOut->mPrimitiveTypes |= aiPrimitiveType_POLYGON; + break; + }; + } + aiCurrent[AI_PTVS_VERTEX] += pcMesh->mNumVertices; + aiCurrent[AI_PTVS_FACE] += pcMesh->mNumFaces; + } + } - // append all children of us - for (unsigned int i = 0;i < pcNode->mNumChildren;++i) { - CollectData(pcScene,pcNode->mChildren[i],iMat, - iVFormat,pcMeshOut,aiCurrent,num_refs); - } + // append all children of us + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + CollectData(pcScene, pcNode->mChildren[i], iMat, + iVFormat, pcMeshOut, aiCurrent, num_refs); + } } // ------------------------------------------------------------------------------------------------ // Get a list of all vertex formats that occur for a given material index // The output list contains duplicate elements -void PretransformVertices::GetVFormatList( aiScene* pcScene, unsigned int iMat, - std::list& aiOut) -{ - for (unsigned int i = 0; i < pcScene->mNumMeshes;++i) - { - aiMesh* pcMesh = pcScene->mMeshes[ i ]; - if (iMat == pcMesh->mMaterialIndex) { - aiOut.push_back(GetMeshVFormat(pcMesh)); - } - } +void PretransformVertices::GetVFormatList(const aiScene *pcScene, unsigned int iMat, + std::list &aiOut) const { + for (unsigned int i = 0; i < pcScene->mNumMeshes; ++i) { + aiMesh *pcMesh = pcScene->mMeshes[i]; + if (iMat == pcMesh->mMaterialIndex) { + aiOut.push_back(GetMeshVFormat(pcMesh)); + } + } } // ------------------------------------------------------------------------------------------------ // Compute the absolute transformation matrices of each node -void PretransformVertices::ComputeAbsoluteTransform( aiNode* pcNode ) -{ - if (pcNode->mParent) { - pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation; - } +void PretransformVertices::ComputeAbsoluteTransform(aiNode *pcNode) { + if (pcNode->mParent) { + pcNode->mTransformation = pcNode->mParent->mTransformation * pcNode->mTransformation; + } - for (unsigned int i = 0;i < pcNode->mNumChildren;++i) { - ComputeAbsoluteTransform(pcNode->mChildren[i]); - } + for (unsigned int i = 0; i < pcNode->mNumChildren; ++i) { + ComputeAbsoluteTransform(pcNode->mChildren[i]); + } } // ------------------------------------------------------------------------------------------------ // Apply the node transformation to a mesh -void PretransformVertices::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat) -{ - // Check whether we need to transform the coordinates at all - if (!mat.IsIdentity()) { +void PretransformVertices::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(); + // Check for odd negative scale (mirror) + if (mesh->HasFaces() && mat.Determinant() < 0) { + // Reverse the mesh face winding order + FlipWindingOrderProcess::ProcessMesh(mesh); + } - // TODO: implement Inverse() for aiMatrix3x3 - aiMatrix3x3 m = aiMatrix3x3(mWorldIT); + // Update positions + if (mesh->HasPositions()) { + for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { + mesh->mVertices[i] = mat * mesh->mVertices[i]; + } + } - 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(); - } - } - } - } + // Update normals and tangents + if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) { + const aiMatrix3x3 m = aiMatrix3x3(mat).Inverse().Transpose(); + + 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(); + } + } + } + } } // ------------------------------------------------------------------------------------------------ // Simple routine to build meshes in worldspace, no further optimization -void PretransformVertices::BuildWCSMeshes(std::vector& out, aiMesh** in, - unsigned int numIn, aiNode* node) -{ - // NOTE: - // aiMesh::mNumBones store original source mesh, or UINT_MAX if not a copy - // aiMesh::mBones store reference to abs. transform we multiplied with +void PretransformVertices::BuildWCSMeshes(std::vector &out, aiMesh **in, + unsigned int numIn, aiNode *node) const { + // NOTE: + // aiMesh::mNumBones store original source mesh, or UINT_MAX if not a copy + // aiMesh::mBones store reference to abs. transform we multiplied with - // process meshes - for (unsigned int i = 0; i < node->mNumMeshes;++i) { - aiMesh* mesh = in[node->mMeshes[i]]; + // process meshes + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + aiMesh *mesh = in[node->mMeshes[i]]; - // check whether we can operate on this mesh - if (!mesh->mBones || *reinterpret_cast(mesh->mBones) == node->mTransformation) { - // yes, we can. - mesh->mBones = reinterpret_cast (&node->mTransformation); - mesh->mNumBones = UINT_MAX; - } - else { + // check whether we can operate on this mesh + if (!mesh->mBones || *reinterpret_cast(mesh->mBones) == node->mTransformation) { + // yes, we can. + mesh->mBones = reinterpret_cast(&node->mTransformation); + mesh->mNumBones = UINT_MAX; + } else { - // try to find us in the list of newly created meshes - for (unsigned int n = 0; n < out.size(); ++n) { - aiMesh* ctz = out[n]; - if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast(ctz->mBones) == node->mTransformation) { + // try to find us in the list of newly created meshes + for (unsigned int n = 0; n < out.size(); ++n) { + aiMesh *ctz = out[n]; + if (ctz->mNumBones == node->mMeshes[i] && *reinterpret_cast(ctz->mBones) == node->mTransformation) { - // ok, use this one. Update node mesh index - node->mMeshes[i] = numIn + n; - } - } - if (node->mMeshes[i] < numIn) { - // Worst case. Need to operate on a full copy of the mesh - ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms"); - aiMesh* ntz; + // ok, use this one. Update node mesh index + node->mMeshes[i] = numIn + n; + } + } + if (node->mMeshes[i] < numIn) { + // Worst case. Need to operate on a full copy of the mesh + ASSIMP_LOG_INFO("PretransformVertices: Copying mesh due to mismatching transforms"); + aiMesh *ntz; - const unsigned int tmp = mesh->mNumBones; // - mesh->mNumBones = 0; - SceneCombiner::Copy(&ntz,mesh); - mesh->mNumBones = tmp; + const unsigned int tmp = mesh->mNumBones; // + mesh->mNumBones = 0; + SceneCombiner::Copy(&ntz, mesh); + mesh->mNumBones = tmp; - ntz->mNumBones = node->mMeshes[i]; - ntz->mBones = reinterpret_cast (&node->mTransformation); + ntz->mNumBones = node->mMeshes[i]; + ntz->mBones = reinterpret_cast(&node->mTransformation); - out.push_back(ntz); + out.push_back(ntz); - node->mMeshes[i] = static_cast(numIn + out.size() - 1); - } - } - } + node->mMeshes[i] = static_cast(numIn + out.size() - 1); + } + } + } - // call children - for (unsigned int i = 0; i < node->mNumChildren;++i) - BuildWCSMeshes(out,in,numIn,node->mChildren[i]); + // call children + for (unsigned int i = 0; i < node->mNumChildren; ++i) + BuildWCSMeshes(out, in, numIn, node->mChildren[i]); } // ------------------------------------------------------------------------------------------------ // Reset transformation matrices to identity -void PretransformVertices::MakeIdentityTransform(aiNode* nd) -{ - nd->mTransformation = aiMatrix4x4(); +void PretransformVertices::MakeIdentityTransform(aiNode *nd) const { + nd->mTransformation = aiMatrix4x4(); - // call children - for (unsigned int i = 0; i < nd->mNumChildren;++i) - MakeIdentityTransform(nd->mChildren[i]); + // call children + for (unsigned int i = 0; i < nd->mNumChildren; ++i) + MakeIdentityTransform(nd->mChildren[i]); } // ------------------------------------------------------------------------------------------------ // Build reference counters for all meshes -void PretransformVertices::BuildMeshRefCountArray(aiNode* nd, unsigned int * refs) -{ - for (unsigned int i = 0; i< nd->mNumMeshes;++i) - refs[nd->mMeshes[i]]++; +void PretransformVertices::BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const { + for (unsigned int i = 0; i < nd->mNumMeshes; ++i) + refs[nd->mMeshes[i]]++; - // call children - for (unsigned int i = 0; i < nd->mNumChildren;++i) - BuildMeshRefCountArray(nd->mChildren[i],refs); + // call children + for (unsigned int i = 0; i < nd->mNumChildren; ++i) + BuildMeshRefCountArray(nd->mChildren[i], refs); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void PretransformVertices::Execute( aiScene* pScene) -{ - ASSIMP_LOG_DEBUG("PretransformVerticesProcess begin"); +void PretransformVertices::Execute(aiScene *pScene) { + ASSIMP_LOG_DEBUG("PretransformVerticesProcess begin"); - // Return immediately if we have no meshes - if (!pScene->mNumMeshes) - return; + // Return immediately if we have no meshes + if (!pScene->mNumMeshes) + return; - const unsigned int iOldMeshes = pScene->mNumMeshes; - const unsigned int iOldAnimationChannels = pScene->mNumAnimations; - const unsigned int iOldNodes = CountNodes(pScene->mRootNode); + const unsigned int iOldMeshes = pScene->mNumMeshes; + const unsigned int iOldAnimationChannels = pScene->mNumAnimations; + const unsigned int iOldNodes = CountNodes(pScene->mRootNode); - if(configTransform) { - pScene->mRootNode->mTransformation = configTransformation; - } + if (configTransform) { + pScene->mRootNode->mTransformation = configTransformation; + } - // first compute absolute transformation matrices for all nodes - ComputeAbsoluteTransform(pScene->mRootNode); + // first compute absolute transformation matrices for all nodes + ComputeAbsoluteTransform(pScene->mRootNode); - // Delete aiMesh::mBones for all meshes. The bones are - // removed during this step and we need the pointer as - // temporary storage - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { - aiMesh* mesh = pScene->mMeshes[i]; + // Delete aiMesh::mBones for all meshes. The bones are + // removed during this step and we need the pointer as + // temporary storage + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *mesh = pScene->mMeshes[i]; - for (unsigned int a = 0; a < mesh->mNumBones;++a) - delete mesh->mBones[a]; + for (unsigned int a = 0; a < mesh->mNumBones; ++a) + delete mesh->mBones[a]; - delete[] mesh->mBones; - mesh->mBones = NULL; - } + delete[] mesh->mBones; + mesh->mBones = NULL; + } - // now build a list of output meshes - std::vector apcOutMeshes; + // now build a list of output meshes + std::vector apcOutMeshes; - // Keep scene hierarchy? It's an easy job in this case ... - // we go on and transform all meshes, if one is referenced by nodes - // with different absolute transformations a depth copy of the mesh - // is required. - if( configKeepHierarchy ) { + // Keep scene hierarchy? It's an easy job in this case ... + // we go on and transform all meshes, if one is referenced by nodes + // with different absolute transformations a depth copy of the mesh + // is required. + if (configKeepHierarchy) { - // Hack: store the matrix we're transforming a mesh with in aiMesh::mBones - BuildWCSMeshes(apcOutMeshes,pScene->mMeshes,pScene->mNumMeshes, pScene->mRootNode); + // Hack: store the matrix we're transforming a mesh with in aiMesh::mBones + BuildWCSMeshes(apcOutMeshes, pScene->mMeshes, pScene->mNumMeshes, pScene->mRootNode); - // ... if new meshes have been generated, append them to the end of the scene - if (apcOutMeshes.size() > 0) { - aiMesh** npp = new aiMesh*[pScene->mNumMeshes + apcOutMeshes.size()]; + // ... if new meshes have been generated, append them to the end of the scene + if (apcOutMeshes.size() > 0) { + aiMesh **npp = new aiMesh *[pScene->mNumMeshes + apcOutMeshes.size()]; - memcpy(npp,pScene->mMeshes,sizeof(aiMesh*)*pScene->mNumMeshes); - memcpy(npp+pScene->mNumMeshes,&apcOutMeshes[0],sizeof(aiMesh*)*apcOutMeshes.size()); + memcpy(npp, pScene->mMeshes, sizeof(aiMesh *) * pScene->mNumMeshes); + memcpy(npp + pScene->mNumMeshes, &apcOutMeshes[0], sizeof(aiMesh *) * apcOutMeshes.size()); - pScene->mNumMeshes += static_cast(apcOutMeshes.size()); - delete[] pScene->mMeshes; pScene->mMeshes = npp; - } + pScene->mNumMeshes += static_cast(apcOutMeshes.size()); + delete[] pScene->mMeshes; + pScene->mMeshes = npp; + } - // now iterate through all meshes and transform them to worldspace - for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { - ApplyTransform(pScene->mMeshes[i],*reinterpret_cast( pScene->mMeshes[i]->mBones )); + // now iterate through all meshes and transform them to worldspace + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + ApplyTransform(pScene->mMeshes[i], *reinterpret_cast(pScene->mMeshes[i]->mBones)); - // prevent improper destruction - pScene->mMeshes[i]->mBones = NULL; - pScene->mMeshes[i]->mNumBones = 0; - } - } else { - apcOutMeshes.reserve(pScene->mNumMaterials<<1u); - std::list aiVFormats; + // prevent improper destruction + pScene->mMeshes[i]->mBones = NULL; + pScene->mMeshes[i]->mNumBones = 0; + } + } else { + apcOutMeshes.reserve(pScene->mNumMaterials << 1u); + std::list aiVFormats; - std::vector s(pScene->mNumMeshes,0); - BuildMeshRefCountArray(pScene->mRootNode,&s[0]); + std::vector s(pScene->mNumMeshes, 0); + BuildMeshRefCountArray(pScene->mRootNode, &s[0]); - for (unsigned int i = 0; i < pScene->mNumMaterials;++i) { - // get the list of all vertex formats for this material - aiVFormats.clear(); - GetVFormatList(pScene,i,aiVFormats); - aiVFormats.sort(); - aiVFormats.unique(); - for (std::list::const_iterator j = aiVFormats.begin();j != aiVFormats.end();++j) { - unsigned int iVertices = 0; - unsigned int iFaces = 0; - CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices); - if (0 != iFaces && 0 != iVertices) - { - apcOutMeshes.push_back(new aiMesh()); - aiMesh* pcMesh = apcOutMeshes.back(); - pcMesh->mNumFaces = iFaces; - pcMesh->mNumVertices = iVertices; - pcMesh->mFaces = new aiFace[iFaces]; - pcMesh->mVertices = new aiVector3D[iVertices]; - pcMesh->mMaterialIndex = i; - if ((*j) & 0x2)pcMesh->mNormals = new aiVector3D[iVertices]; - if ((*j) & 0x4) - { - pcMesh->mTangents = new aiVector3D[iVertices]; - pcMesh->mBitangents = new aiVector3D[iVertices]; - } - iFaces = 0; - while ((*j) & (0x100 << iFaces)) - { - pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices]; - if ((*j) & (0x10000 << iFaces))pcMesh->mNumUVComponents[iFaces] = 3; - else pcMesh->mNumUVComponents[iFaces] = 2; - iFaces++; - } - iFaces = 0; - while ((*j) & (0x1000000 << iFaces)) - pcMesh->mColors[iFaces++] = new aiColor4D[iVertices]; + for (unsigned int i = 0; i < pScene->mNumMaterials; ++i) { + // get the list of all vertex formats for this material + aiVFormats.clear(); + GetVFormatList(pScene, i, aiVFormats); + aiVFormats.sort(); + aiVFormats.unique(); + for (std::list::const_iterator j = aiVFormats.begin(); j != aiVFormats.end(); ++j) { + unsigned int iVertices = 0; + unsigned int iFaces = 0; + CountVerticesAndFaces(pScene, pScene->mRootNode, i, *j, &iFaces, &iVertices); + if (0 != iFaces && 0 != iVertices) { + apcOutMeshes.push_back(new aiMesh()); + aiMesh *pcMesh = apcOutMeshes.back(); + pcMesh->mNumFaces = iFaces; + pcMesh->mNumVertices = iVertices; + pcMesh->mFaces = new aiFace[iFaces]; + pcMesh->mVertices = new aiVector3D[iVertices]; + pcMesh->mMaterialIndex = i; + if ((*j) & 0x2) pcMesh->mNormals = new aiVector3D[iVertices]; + if ((*j) & 0x4) { + pcMesh->mTangents = new aiVector3D[iVertices]; + pcMesh->mBitangents = new aiVector3D[iVertices]; + } + iFaces = 0; + while ((*j) & (0x100 << iFaces)) { + pcMesh->mTextureCoords[iFaces] = new aiVector3D[iVertices]; + if ((*j) & (0x10000 << iFaces)) + pcMesh->mNumUVComponents[iFaces] = 3; + else + pcMesh->mNumUVComponents[iFaces] = 2; + iFaces++; + } + iFaces = 0; + while ((*j) & (0x1000000 << iFaces)) + pcMesh->mColors[iFaces++] = new aiColor4D[iVertices]; - // fill the mesh ... - unsigned int aiTemp[2] = {0,0}; - CollectData(pScene,pScene->mRootNode,i,*j,pcMesh,aiTemp,&s[0]); - } - } - } + // fill the mesh ... + unsigned int aiTemp[2] = { 0, 0 }; + CollectData(pScene, pScene->mRootNode, i, *j, pcMesh, aiTemp, &s[0]); + } + } + } - // If no meshes are referenced in the node graph it is possible that we get no output meshes. - if (apcOutMeshes.empty()) { - - throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes"); - } - else - { - // now delete all meshes in the scene and build a new mesh list - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) - { - aiMesh* mesh = pScene->mMeshes[i]; - mesh->mNumBones = 0; - mesh->mBones = NULL; + // If no meshes are referenced in the node graph it is possible that we get no output meshes. + if (apcOutMeshes.empty()) { - // we're reusing the face index arrays. avoid destruction - for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { - mesh->mFaces[a].mNumIndices = 0; - mesh->mFaces[a].mIndices = NULL; - } + throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes"); + } else { + // now delete all meshes in the scene and build a new mesh list + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + aiMesh *mesh = pScene->mMeshes[i]; + mesh->mNumBones = 0; + mesh->mBones = NULL; - delete mesh; + // we're reusing the face index arrays. avoid destruction + for (unsigned int a = 0; a < mesh->mNumFaces; ++a) { + mesh->mFaces[a].mNumIndices = 0; + mesh->mFaces[a].mIndices = NULL; + } - // Invalidate the contents of the old mesh array. We will most - // likely have less output meshes now, so the last entries of - // the mesh array are not overridden. We set them to NULL to - // make sure the developer gets notified when his application - // attempts to access these fields ... - mesh = NULL; - } + delete mesh; - // It is impossible that we have more output meshes than - // input meshes, so we can easily reuse the old mesh array - pScene->mNumMeshes = (unsigned int)apcOutMeshes.size(); - for (unsigned int i = 0; i < pScene->mNumMeshes;++i) { - pScene->mMeshes[i] = apcOutMeshes[i]; - } - } - } + // Invalidate the contents of the old mesh array. We will most + // likely have less output meshes now, so the last entries of + // the mesh array are not overridden. We set them to NULL to + // make sure the developer gets notified when his application + // attempts to access these fields ... + mesh = NULL; + } - // remove all animations from the scene - for (unsigned int i = 0; i < pScene->mNumAnimations;++i) - delete pScene->mAnimations[i]; - delete[] pScene->mAnimations; + // It is impossible that we have more output meshes than + // input meshes, so we can easily reuse the old mesh array + pScene->mNumMeshes = (unsigned int)apcOutMeshes.size(); + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { + pScene->mMeshes[i] = apcOutMeshes[i]; + } + } + } - pScene->mAnimations = NULL; - pScene->mNumAnimations = 0; + // remove all animations from the scene + for (unsigned int i = 0; i < pScene->mNumAnimations; ++i) + delete pScene->mAnimations[i]; + delete[] pScene->mAnimations; - // --- we need to keep all cameras and lights - for (unsigned int i = 0; i < pScene->mNumCameras;++i) - { - aiCamera* cam = pScene->mCameras[i]; - const aiNode* nd = pScene->mRootNode->FindNode(cam->mName); - ai_assert(NULL != nd); + pScene->mAnimations = NULL; + pScene->mNumAnimations = 0; - // multiply all properties of the camera with the absolute - // transformation of the corresponding node - cam->mPosition = nd->mTransformation * cam->mPosition; - cam->mLookAt = aiMatrix3x3( nd->mTransformation ) * cam->mLookAt; - cam->mUp = aiMatrix3x3( nd->mTransformation ) * cam->mUp; - } + // --- we need to keep all cameras and lights + for (unsigned int i = 0; i < pScene->mNumCameras; ++i) { + aiCamera *cam = pScene->mCameras[i]; + const aiNode *nd = pScene->mRootNode->FindNode(cam->mName); + ai_assert(NULL != nd); - for (unsigned int i = 0; i < pScene->mNumLights;++i) - { - aiLight* l = pScene->mLights[i]; - const aiNode* nd = pScene->mRootNode->FindNode(l->mName); - ai_assert(NULL != nd); + // multiply all properties of the camera with the absolute + // transformation of the corresponding node + cam->mPosition = nd->mTransformation * cam->mPosition; + cam->mLookAt = aiMatrix3x3(nd->mTransformation) * cam->mLookAt; + cam->mUp = aiMatrix3x3(nd->mTransformation) * cam->mUp; + } - // multiply all properties of the camera with the absolute - // transformation of the corresponding node - l->mPosition = nd->mTransformation * l->mPosition; - l->mDirection = aiMatrix3x3( nd->mTransformation ) * l->mDirection; - l->mUp = aiMatrix3x3( nd->mTransformation ) * l->mUp; - } + for (unsigned int i = 0; i < pScene->mNumLights; ++i) { + aiLight *l = pScene->mLights[i]; + const aiNode *nd = pScene->mRootNode->FindNode(l->mName); + ai_assert(NULL != nd); - if( !configKeepHierarchy ) { + // multiply all properties of the camera with the absolute + // transformation of the corresponding node + l->mPosition = nd->mTransformation * l->mPosition; + l->mDirection = aiMatrix3x3(nd->mTransformation) * l->mDirection; + l->mUp = aiMatrix3x3(nd->mTransformation) * l->mUp; + } - // now delete all nodes in the scene and build a new - // flat node graph with a root node and some level 1 children - aiNode* newRoot = new aiNode(); - newRoot->mName = pScene->mRootNode->mName; - delete pScene->mRootNode; - pScene->mRootNode = newRoot; + if (!configKeepHierarchy) { - if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras) - { - pScene->mRootNode->mNumMeshes = 1; - pScene->mRootNode->mMeshes = new unsigned int[1]; - pScene->mRootNode->mMeshes[0] = 0; - } - else - { - pScene->mRootNode->mNumChildren = pScene->mNumMeshes+pScene->mNumLights+pScene->mNumCameras; - aiNode** nodes = pScene->mRootNode->mChildren = new aiNode*[pScene->mRootNode->mNumChildren]; + // now delete all nodes in the scene and build a new + // flat node graph with a root node and some level 1 children + aiNode *newRoot = new aiNode(); + newRoot->mName = pScene->mRootNode->mName; + delete pScene->mRootNode; + pScene->mRootNode = newRoot; - // generate mesh nodes - for (unsigned int i = 0; i < pScene->mNumMeshes;++i,++nodes) - { - aiNode* pcNode = new aiNode(); - *nodes = pcNode; - pcNode->mParent = pScene->mRootNode; - pcNode->mName = pScene->mMeshes[i]->mName; + if (1 == pScene->mNumMeshes && !pScene->mNumLights && !pScene->mNumCameras) { + pScene->mRootNode->mNumMeshes = 1; + pScene->mRootNode->mMeshes = new unsigned int[1]; + pScene->mRootNode->mMeshes[0] = 0; + } else { + pScene->mRootNode->mNumChildren = pScene->mNumMeshes + pScene->mNumLights + pScene->mNumCameras; + aiNode **nodes = pScene->mRootNode->mChildren = new aiNode *[pScene->mRootNode->mNumChildren]; - // setup mesh indices - pcNode->mNumMeshes = 1; - pcNode->mMeshes = new unsigned int[1]; - pcNode->mMeshes[0] = i; - } - // generate light nodes - for (unsigned int i = 0; i < pScene->mNumLights;++i,++nodes) - { - aiNode* pcNode = new aiNode(); - *nodes = pcNode; - pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u",i); - pScene->mLights[i]->mName = pcNode->mName; - } - // generate camera nodes - for (unsigned int i = 0; i < pScene->mNumCameras;++i,++nodes) - { - aiNode* pcNode = new aiNode(); - *nodes = pcNode; - pcNode->mParent = pScene->mRootNode; - pcNode->mName.length = ::ai_snprintf(pcNode->mName.data,MAXLEN,"cam_%u",i); - pScene->mCameras[i]->mName = pcNode->mName; - } - } - } - else { - // ... and finally set the transformation matrix of all nodes to identity - MakeIdentityTransform(pScene->mRootNode); - } + // generate mesh nodes + for (unsigned int i = 0; i < pScene->mNumMeshes; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName = pScene->mMeshes[i]->mName; - if (configNormalize) { - // compute the boundary of all meshes - aiVector3D min,max; - MinMaxChooser ()(min,max); + // setup mesh indices + pcNode->mNumMeshes = 1; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + } + // generate light nodes + for (unsigned int i = 0; i < pScene->mNumLights; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "light_%u", i); + pScene->mLights[i]->mName = pcNode->mName; + } + // generate camera nodes + for (unsigned int i = 0; i < pScene->mNumCameras; ++i, ++nodes) { + aiNode *pcNode = new aiNode(); + *nodes = pcNode; + pcNode->mParent = pScene->mRootNode; + pcNode->mName.length = ::ai_snprintf(pcNode->mName.data, MAXLEN, "cam_%u", i); + pScene->mCameras[i]->mName = pcNode->mName; + } + } + } else { + // ... and finally set the transformation matrix of all nodes to identity + MakeIdentityTransform(pScene->mRootNode); + } - for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { - aiMesh* m = pScene->mMeshes[a]; - for (unsigned int i = 0; i < m->mNumVertices;++i) { - min = std::min(m->mVertices[i],min); - max = std::max(m->mVertices[i],max); - } - } + if (configNormalize) { + // compute the boundary of all meshes + aiVector3D min, max; + MinMaxChooser()(min, max); - // find the dominant axis - aiVector3D d = max-min; - const ai_real div = std::max(d.x,std::max(d.y,d.z))*ai_real( 0.5); + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *m = pScene->mMeshes[a]; + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + min = std::min(m->mVertices[i], min); + max = std::max(m->mVertices[i], max); + } + } - d = min + d * (ai_real)0.5; - for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { - aiMesh* m = pScene->mMeshes[a]; - for (unsigned int i = 0; i < m->mNumVertices;++i) { - m->mVertices[i] = (m->mVertices[i]-d)/div; - } - } - } + // find the dominant axis + aiVector3D d = max - min; + const ai_real div = std::max(d.x, std::max(d.y, d.z)) * ai_real(0.5); - // print statistics - if (!DefaultLogger::isNullLogger()) { - ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished"); + d = min + d * (ai_real)0.5; + for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) { + aiMesh *m = pScene->mMeshes[a]; + for (unsigned int i = 0; i < m->mNumVertices; ++i) { + m->mVertices[i] = (m->mVertices[i] - d) / div; + } + } + } - ASSIMP_LOG_INFO_F("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (", - CountNodes(pScene->mRootNode) ," output nodes)" ); - ASSIMP_LOG_INFO_F("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras." ); - ASSIMP_LOG_INFO_F("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")"); - } + // print statistics + if (!DefaultLogger::isNullLogger()) { + ASSIMP_LOG_DEBUG("PretransformVerticesProcess finished"); + + ASSIMP_LOG_INFO_F("Removed ", iOldNodes, " nodes and ", iOldAnimationChannels, " animation channels (", + CountNodes(pScene->mRootNode), " output nodes)"); + ASSIMP_LOG_INFO_F("Kept ", pScene->mNumLights, " lights and ", pScene->mNumCameras, " cameras."); + ASSIMP_LOG_INFO_F("Moved ", iOldMeshes, " meshes to WCS (number of output meshes: ", pScene->mNumMeshes, ")"); + } } diff --git a/code/PostProcessing/PretransformVertices.h b/code/PostProcessing/PretransformVertices.h index b2982951e..7898f6ae3 100644 --- a/code/PostProcessing/PretransformVertices.h +++ b/code/PostProcessing/PretransformVertices.h @@ -59,7 +59,7 @@ struct aiNode; class PretransformVerticesTest; -namespace Assimp { +namespace Assimp { // --------------------------------------------------------------------------- /** The PretransformVertices pre-transforms all vertices in the node tree @@ -68,97 +68,97 @@ namespace Assimp { */ class ASSIMP_API PretransformVertices : public BaseProcess { public: - PretransformVertices (); - ~PretransformVertices (); + PretransformVertices(); + ~PretransformVertices(); - // ------------------------------------------------------------------- - // Check whether step is active - bool IsActive( unsigned int pFlags) const; + // ------------------------------------------------------------------- + // Check whether step is active + bool IsActive(unsigned int pFlags) const override; - // ------------------------------------------------------------------- - // Execute step on a given scene - void Execute( aiScene* pScene); + // ------------------------------------------------------------------- + // Execute step on a given scene + void Execute(aiScene *pScene) override; - // ------------------------------------------------------------------- - // Setup import settings - void SetupProperties(const Importer* pImp); + // ------------------------------------------------------------------- + // Setup import settings + void SetupProperties(const Importer *pImp) override; - // ------------------------------------------------------------------- - /** @brief Toggle the 'keep hierarchy' option + // ------------------------------------------------------------------- + /** @brief Toggle the 'keep hierarchy' option * @param keep true for keep configuration. */ - void KeepHierarchy(bool keep) { - configKeepHierarchy = keep; - } + void KeepHierarchy(bool keep) { + configKeepHierarchy = keep; + } - // ------------------------------------------------------------------- - /** @brief Check whether 'keep hierarchy' is currently enabled. + // ------------------------------------------------------------------- + /** @brief Check whether 'keep hierarchy' is currently enabled. * @return ... */ - bool IsHierarchyKept() const { - return configKeepHierarchy; - } + bool IsHierarchyKept() const { + return configKeepHierarchy; + } private: - // ------------------------------------------------------------------- - // Count the number of nodes - unsigned int CountNodes( aiNode* pcNode ); + // ------------------------------------------------------------------- + // Count the number of nodes + unsigned int CountNodes(const aiNode *pcNode) const; - // ------------------------------------------------------------------- - // Get a bitwise combination identifying the vertex format of a mesh - unsigned int GetMeshVFormat(aiMesh* pcMesh); + // ------------------------------------------------------------------- + // Get a bitwise combination identifying the vertex format of a mesh + unsigned int GetMeshVFormat(aiMesh *pcMesh) const; - // ------------------------------------------------------------------- - // Count the number of vertices in the whole scene and a given - // material index - void CountVerticesAndFaces( aiScene* pcScene, aiNode* pcNode, - unsigned int iMat, - unsigned int iVFormat, - unsigned int* piFaces, - unsigned int* piVertices); + // ------------------------------------------------------------------- + // Count the number of vertices in the whole scene and a given + // material index + void CountVerticesAndFaces(const aiScene *pcScene, const aiNode *pcNode, + unsigned int iMat, + unsigned int iVFormat, + unsigned int *piFaces, + unsigned int *piVertices) const; - // ------------------------------------------------------------------- - // Collect vertex/face data - void CollectData( aiScene* pcScene, aiNode* pcNode, - unsigned int iMat, - unsigned int iVFormat, - aiMesh* pcMeshOut, - unsigned int aiCurrent[2], - unsigned int* num_refs); + // ------------------------------------------------------------------- + // Collect vertex/face data + void CollectData(const aiScene *pcScene, const aiNode *pcNode, + unsigned int iMat, + unsigned int iVFormat, + aiMesh *pcMeshOut, + unsigned int aiCurrent[2], + unsigned int *num_refs) const; - // ------------------------------------------------------------------- - // Get a list of all vertex formats that occur for a given material - // The output list contains duplicate elements - void GetVFormatList( aiScene* pcScene, unsigned int iMat, - std::list& aiOut); + // ------------------------------------------------------------------- + // Get a list of all vertex formats that occur for a given material + // The output list contains duplicate elements + void GetVFormatList(const aiScene *pcScene, unsigned int iMat, + std::list &aiOut) const; - // ------------------------------------------------------------------- - // Compute the absolute transformation matrices of each node - void ComputeAbsoluteTransform( aiNode* pcNode ); + // ------------------------------------------------------------------- + // Compute the absolute transformation matrices of each node + void ComputeAbsoluteTransform(aiNode *pcNode); - // ------------------------------------------------------------------- - // Simple routine to build meshes in worldspace, no further optimization - void BuildWCSMeshes(std::vector& out, aiMesh** in, - unsigned int numIn, aiNode* node); + // ------------------------------------------------------------------- + // Simple routine to build meshes in worldspace, no further optimization + void BuildWCSMeshes(std::vector &out, aiMesh **in, + unsigned int numIn, aiNode *node) const; - // ------------------------------------------------------------------- - // Apply the node transformation to a mesh - void ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat); + // ------------------------------------------------------------------- + // Apply the node transformation to a mesh + void ApplyTransform(aiMesh *mesh, const aiMatrix4x4 &mat) const; - // ------------------------------------------------------------------- - // Reset transformation matrices to identity - void MakeIdentityTransform(aiNode* nd); + // ------------------------------------------------------------------- + // Reset transformation matrices to identity + void MakeIdentityTransform(aiNode *nd) const; - // ------------------------------------------------------------------- - // Build reference counters for all meshes - void BuildMeshRefCountArray(aiNode* nd, unsigned int * refs); + // ------------------------------------------------------------------- + // Build reference counters for all meshes + void BuildMeshRefCountArray(const aiNode *nd, unsigned int *refs) const; - //! Configuration option: keep scene hierarchy as long as possible - bool configKeepHierarchy; - bool configNormalize; - bool configTransform; - aiMatrix4x4 configTransformation; - bool mConfigPointCloud; + //! Configuration option: keep scene hierarchy as long as possible + bool configKeepHierarchy; + bool configNormalize; + bool configTransform; + aiMatrix4x4 configTransformation; + bool mConfigPointCloud; }; } // end of namespace Assimp From 08c5fa37bfba822505abeb9b204f8621287cc5ef Mon Sep 17 00:00:00 2001 From: Mike Samsonov Date: Tue, 10 Dec 2019 12:04:35 +0000 Subject: [PATCH 4/6] Add a support for 3DSMax Physically Based Materials for FBX format --- code/FBX/FBXConverter.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 26a3c4b99..5b34868ba 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -2088,7 +2088,14 @@ namespace Assimp { TrySetTextureProperties(out_mat, textures, "Maya|TEX_emissive_map|file", aiTextureType_EMISSION_COLOR, mesh); TrySetTextureProperties(out_mat, textures, "Maya|TEX_metallic_map|file", aiTextureType_METALNESS, mesh); TrySetTextureProperties(out_mat, textures, "Maya|TEX_roughness_map|file", aiTextureType_DIFFUSE_ROUGHNESS, mesh); - TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh); + TrySetTextureProperties(out_mat, textures, "Maya|TEX_ao_map|file", aiTextureType_AMBIENT_OCCLUSION, mesh); + + // 3DSMax PBR + TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|base_color_map", aiTextureType_BASE_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|bump_map", aiTextureType_NORMAL_CAMERA, mesh); + TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|emission_map", aiTextureType_EMISSION_COLOR, mesh); + TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|metalness_map", aiTextureType_METALNESS, mesh); + TrySetTextureProperties(out_mat, textures, "3dsMax|Parameters|roughness_map", aiTextureType_DIFFUSE_ROUGHNESS, mesh); } void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh) From b70c05b4e0bd38e034af81a7ec40cfd7d38d292d Mon Sep 17 00:00:00 2001 From: Malcolm Tyrrell Date: Thu, 12 Dec 2019 09:38:50 +0000 Subject: [PATCH 5/6] Fix texcoords. --- code/glTF2/glTF2Importer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/code/glTF2/glTF2Importer.cpp b/code/glTF2/glTF2Importer.cpp index dd80aeba9..9b416850f 100644 --- a/code/glTF2/glTF2Importer.cpp +++ b/code/glTF2/glTF2Importer.cpp @@ -201,6 +201,7 @@ inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); + mat->AddProperty(&prop.texCoord, 1, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(texType, texSlot)); if (prop.textureTransformSupported) { aiUVTransform transform; From 2124da83406e6cbb5bcc3b5030702daa7173eec1 Mon Sep 17 00:00:00 2001 From: Malcolm Tyrrell Date: Thu, 12 Dec 2019 12:06:36 +0000 Subject: [PATCH 6/6] Add a texcoord unit test. --- .../glTF2/BoxTexcoords-glTF/boxTexcoords.bin | Bin 0 -> 1032 bytes .../glTF2/BoxTexcoords-glTF/boxTexcoords.gltf | 172 ++++++++++++++++++ .../glTF2/BoxTexcoords-glTF/texture.png | Bin 0 -> 41302 bytes test/unit/utglTF2ImportExport.cpp | 29 +++ 4 files changed, 201 insertions(+) create mode 100644 test/models/glTF2/BoxTexcoords-glTF/boxTexcoords.bin create mode 100644 test/models/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf create mode 100644 test/models/glTF2/BoxTexcoords-glTF/texture.png diff --git a/test/models/glTF2/BoxTexcoords-glTF/boxTexcoords.bin b/test/models/glTF2/BoxTexcoords-glTF/boxTexcoords.bin new file mode 100644 index 0000000000000000000000000000000000000000..ae6a3f0d12d2a157502364f55961b4da73a3d7bb GIT binary patch literal 1032 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 N=jH?5${Y4k{|Du~q|yKY literal 0 HcmV?d00001 diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index ef1316fb7..006471436 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -47,6 +47,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + using namespace Assimp; class utglTF2ImportExport : public AbstractImportExportBase { @@ -436,3 +438,30 @@ TEST_F(utglTF2ImportExport, error_string_preserved) { } #endif // ASSIMP_BUILD_NO_EXPORT + +TEST_F(utglTF2ImportExport, texcoords) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTexcoords-glTF/boxTexcoords.gltf", + aiProcess_ValidateDataStructure); + ASSERT_NE(scene, nullptr); + + ASSERT_TRUE(scene->HasMaterials()); + const aiMaterial *material = scene->mMaterials[0]; + + aiString path; + aiTextureMapMode modes[2]; + EXPECT_EQ(aiReturn_SUCCESS, material->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, + nullptr, nullptr, modes)); + EXPECT_STREQ(path.C_Str(), "texture.png"); + + int uvIndex = -1; + EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_DIFFUSE, 0), &uvIndex), aiReturn_SUCCESS); + EXPECT_EQ(uvIndex, 0); + + // Using manual macro expansion of AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE here. + // The following works with some but not all compilers: + // #define APPLY(X, Y) X(Y) + // ..., APPLY(AI_MATKEY_GLTF_TEXTURE_TEXCOORD, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE), ... + EXPECT_EQ(aiGetMaterialInteger(material, AI_MATKEY_GLTF_TEXTURE_TEXCOORD(aiTextureType_UNKNOWN, 0), &uvIndex), aiReturn_SUCCESS); + EXPECT_EQ(uvIndex, 1); +}