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 <jillich@amazon.com>
This commit is contained in:
Benjamin Jillich
2021-07-25 23:39:02 -07:00
committed by GitHub
parent f3a5e9d948
commit 8f35def25b
4 changed files with 147 additions and 155 deletions
@@ -41,45 +41,6 @@ namespace AZ
}
}
void MakeBoneMap(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> 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_multimap<AZStd::string, const aiBone*> boneByNameMap;
FindAllBones(scene, boneByNameMap);
bool isBone = FindFirstBoneByNodeName(currentNode, boneByNameMap);
if (!isBone)
{
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
MakeBoneMap(scene, boneLookup);
isBone = boneLookup.contains(currentNode->mName.C_Str());
// If we have an animation, the bones will be listed in there
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)
{
aiAnimation* animation = scene->mAnimations[animIndex];
aiNodeAnim* nodeAnim = animation->mChannels[channelIndex];
for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex)
{
aiNodeAnim* nodeAnim = animation->mChannels[channelIndex];
if (nodeAnim->mNodeName == currentNode->mName)
{
isBone = true;
break;
}
}
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;
}
}
@@ -6,12 +6,13 @@
*
*/
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <assimp/scene.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
namespace AZ
{
@@ -85,6 +86,107 @@ namespace AZ
return combinedTransform;
}
void FindAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& outBoneByNameMap)
{
outBoneByNameMap.clear();
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> 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<AZStd::string, const aiBone*> 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<AZStd::string, const aiBone*>& 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<AZStd::string, const aiBone*>& 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
@@ -9,13 +9,15 @@
#pragma once
#include <assimp/matrix4x4.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/DataTypes/MatrixType.h>
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<AZStd::string, const aiBone*>& outBoneByNameMap);
// Find the first bone with the name of the given node.
const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap<AZStd::string, const aiBone*>& 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<AZStd::string, const aiBone*>& boneByNameMap);
} // namespace AZ
@@ -42,45 +42,6 @@ namespace AZ
serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
void GetAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> 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<AZStd::string, const aiBone*> 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<DataTypes::MatrixType> 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())