From 8f35def25bd3cf977e6eb06761df9ba6b67a7152 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich <43751992+amzn-jillich@users.noreply.github.com> Date: Sun, 25 Jul 2021 23:39:02 -0700 Subject: [PATCH] AssImp skeleton import improvements (#2348) * Disabled to skip exporting a node in case there are child bones underneath, this broke motion extraction as we skipped the motion extraction node. * Added several helper methods for getting the local space bind pose transform, finding all bones, getting the first bone for a given node name and a recursive has child bones. * Unified the cloned get all bone methods. Signed-off-by: Benjamin Jillich --- .../Importers/AssImpBoneImporter.cpp | 87 ++++---------- .../Importers/AssImpImporterUtilities.cpp | 110 +++++++++++++++++- .../Importers/AssImpImporterUtilities.h | 15 ++- .../Importers/AssImpTransformImporter.cpp | 88 +------------- 4 files changed, 146 insertions(+), 154 deletions(-) diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp index 92f84ad8fc..eeddf4a0b3 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBoneImporter.cpp @@ -41,45 +41,6 @@ namespace AZ } } - void MakeBoneMap(const aiScene* scene, AZStd::unordered_map& boneLookup) - { - AZStd::queue queue; - AZStd::unordered_set nodesWithNoMesh; - - queue.push(scene->mRootNode); - - while (!queue.empty()) - { - const aiNode* currentNode = queue.front(); - queue.pop(); - - if (currentNode->mNumMeshes == 0) - { - nodesWithNoMesh.emplace(currentNode->mName.C_Str()); - } - - for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) - { - queue.push(currentNode->mChildren[childIndex]); - } - } - - for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) - { - const aiMesh* mesh = scene->mMeshes[meshIndex]; - - for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) - { - const aiBone* bone = mesh->mBones[boneIndex]; - - if (nodesWithNoMesh.contains(bone->mName.C_Str())) - { - boneLookup.emplace(bone->mName.C_Str(), bone); - } - } - } - } - aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode) { aiMatrix4x4 transform = {}; @@ -106,37 +67,39 @@ namespace AZ return Events::ProcessingResult::Ignored; } - bool isBone = false; - - { - AZStd::unordered_map boneLookup; - MakeBoneMap(scene, boneLookup); + AZStd::unordered_multimap boneByNameMap; + FindAllBones(scene, boneByNameMap); - isBone = boneLookup.contains(currentNode->mName.C_Str()); - - // If we have an animation, the bones will be listed in there - if (!isBone) + bool isBone = FindFirstBoneByNodeName(currentNode, boneByNameMap); + if (!isBone) + { + for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex) { - for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex) - { - aiAnimation* animation = scene->mAnimations[animIndex]; - - for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex) - { - aiNodeAnim* nodeAnim = animation->mChannels[channelIndex]; + aiAnimation* animation = scene->mAnimations[animIndex]; - if (nodeAnim->mNodeName == currentNode->mName) - { - isBone = true; - break; - } - } + for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex) + { + aiNodeAnim* nodeAnim = animation->mChannels[channelIndex]; - if (isBone) + if (nodeAnim->mNodeName == currentNode->mName) { + isBone = true; break; } } + + if (isBone) + { + break; + } + } + + // In case any of the children, or children of children is a bone, make sure to not skip this node. + // Don't do this for the scene root itself, else wise all mesh nodes will be exported as bones and pollute the skeleton. + if (currentNode != scene->mRootNode && + RecursiveHasChildBone(currentNode, boneByNameMap)) + { + isBone = true; } } diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp index 15bb65399c..81feff7d69 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp @@ -6,12 +6,13 @@ * */ -#include - +#include #include +#include +#include #include - -#include +#include +#include namespace AZ { @@ -85,6 +86,107 @@ namespace AZ return combinedTransform; } + + void FindAllBones(const aiScene* scene, AZStd::unordered_multimap& outBoneByNameMap) + { + outBoneByNameMap.clear(); + AZStd::queue queue; + AZStd::unordered_set nodesWithNoMesh; + + queue.push(scene->mRootNode); + + while (!queue.empty()) + { + const aiNode* currentNode = queue.front(); + queue.pop(); + + if (currentNode->mNumMeshes == 0) + { + nodesWithNoMesh.emplace(currentNode->mName.C_Str()); + } + + for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) + { + queue.push(currentNode->mChildren[childIndex]); + } + } + + for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) + { + const aiMesh* mesh = scene->mMeshes[meshIndex]; + + for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) + { + const aiBone* bone = mesh->mBones[boneIndex]; + + if (nodesWithNoMesh.contains(bone->mName.C_Str())) + { + outBoneByNameMap.emplace(bone->mName.C_Str(), bone); + } + } + } + } + + DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node) + { + AZStd::unordered_multimap boneByNameMap; + FindAllBones(scene, boneByNameMap); + + const aiBone* bone = FindFirstBoneByNodeName(node, boneByNameMap); + if (bone) + { + const DataTypes::MatrixType inverseOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(bone->mOffsetMatrix).GetInverseFull(); + + const aiBone* parentBone = FindFirstBoneByNodeName(node->mParent, boneByNameMap); + if (parentBone) + { + const DataTypes::MatrixType parentBoneOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(parentBone->mOffsetMatrix); + return parentBoneOffsetMatrix * inverseOffsetMatrix; + } + else + { + return inverseOffsetMatrix; + } + } + + return AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(node)); + } + + const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap& boneByNameMap) + { + if (!node) + { + return nullptr; + } + + auto boneIterator = boneByNameMap.find(node->mName.C_Str()); + if (boneIterator != boneByNameMap.end()) + { + return boneIterator->second; + } + + return nullptr; + } + + bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap& boneByNameMap) + { + const bool isBone = boneByNameMap.contains(node->mName.C_Str()); + if (isBone) + { + return true; + } + + for (int childIndex = 0; childIndex < node->mNumChildren; ++childIndex) + { + const aiNode* childNode = node->mChildren[childIndex]; + if (RecursiveHasChildBone(childNode, boneByNameMap)) + { + return true; + } + } + + return false; + } } // namespace SceneBuilder } // namespace SceneAPI } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h index 5a943f339c..a629fe52d8 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h @@ -9,13 +9,15 @@ #pragma once #include +#include #include +#include +struct aiBone; struct aiNode; struct aiScene; struct aiString; - namespace AZ::SceneAPI::SceneBuilder { inline constexpr char PivotNodeMarker[] = "_$AssimpFbx$_"; @@ -30,5 +32,16 @@ namespace AZ::SceneAPI::SceneBuilder // Gets the entire, combined local transform for a node taking pivot nodes into account. When pivot nodes are not used, this just returns the node's transform aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode); + + DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node); + + // Gather all bones from the scene. (Bone in AssImp corresponds to nodes that influence any of the vertices). + void FindAllBones(const aiScene* scene, AZStd::unordered_multimap& outBoneByNameMap); + + // Find the first bone with the name of the given node. + const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap& boneByNameMap); + + // Check if the given node or any of its children, or children of children, is a bone by checking if the node name is part of the given map. + bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap& boneByNameMap); } // namespace AZ diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp index eba1063a1e..134c408bae 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTransformImporter.cpp @@ -42,45 +42,6 @@ namespace AZ serializeContext->Class()->Version(1); } } - - void GetAllBones(const aiScene* scene, AZStd::unordered_multimap& boneLookup) - { - AZStd::queue queue; - AZStd::unordered_set nodesWithNoMesh; - - queue.push(scene->mRootNode); - - while (!queue.empty()) - { - const aiNode* currentNode = queue.front(); - queue.pop(); - - if (currentNode->mNumMeshes == 0) - { - nodesWithNoMesh.emplace(currentNode->mName.C_Str()); - } - - for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) - { - queue.push(currentNode->mChildren[childIndex]); - } - } - - for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) - { - const aiMesh* mesh = scene->mMeshes[meshIndex]; - - for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) - { - const aiBone* bone = mesh->mBones[boneIndex]; - - if (nodesWithNoMesh.contains(bone->mName.C_Str())) - { - boneLookup.emplace(bone->mName.C_Str(), bone); - } - } - } - } Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context) { @@ -93,54 +54,7 @@ namespace AZ return Events::ProcessingResult::Ignored; } - AZStd::unordered_multimap boneLookup; - GetAllBones(scene, boneLookup); - - auto boneIterator = boneLookup.find(currentNode->mName.C_Str()); - const bool isBone = boneIterator != boneLookup.end(); - - DataTypes::MatrixType localTransform; - - if (isBone) - { - AZStd::vector offsets, inverseOffsets; - auto iteratingNode = currentNode; - - while (iteratingNode && boneLookup.count(iteratingNode->mName.C_Str())) - { - AZStd::string name = iteratingNode->mName.C_Str(); - - auto range = boneLookup.equal_range(name); - - if (range.first != range.second) - { - // There can be multiple offsetMatrices for a given bone, we're only interested in grabbing the first one - auto boneFirstOffsetMatrix = range.first->second->mOffsetMatrix; - auto azMat = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(boneFirstOffsetMatrix); - offsets.push_back(azMat); - inverseOffsets.push_back(azMat.GetInverseFull()); - } - - iteratingNode = iteratingNode->mParent; - } - - if (inverseOffsets.size() == 1) - { - // If this is the root bone, just use the inverseOffset, otherwise the equation below just results in the identity matrix - localTransform = inverseOffsets[0]; - } - else - { - localTransform = offsets.at(1) // parent bone offset - * inverseOffsets.at(inverseOffsets.size() - 1) // Inverse of root bone offset - * offsets.at(offsets.size() - 1) // Root bone offset - * inverseOffsets.at(0); // Inverse of current node offset - } - } - else - { - localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(currentNode)); - } + DataTypes::MatrixType localTransform = GetLocalSpaceBindPoseTransform(scene, currentNode); // Don't bother adding a node with the identity matrix if (localTransform == DataTypes::MatrixType::Identity())