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:
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user