Helios - LYN-3250 - Fixed morph targets for meshes that had multiple … (#696)

* Helios - LYN-3250 - Fixed morph targets for meshes that had multiple materials (#374)

Fixed morph targets for meshes that had multiple materials and were split by AssImp: Recombined them into one mesh in the O3DE scene graph, so the behavior would match FBX SDK.
main
AMZN-stankowi 5 years ago committed by GitHub
parent 25e811ff6c
commit f779821ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -260,7 +260,7 @@ namespace AZ
{
AZ_TraceContext("Importer", "Animation");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
// Add check for animation layers at the scene level.
@ -387,11 +387,10 @@ namespace AZ
}
Events::ProcessingResultCombiner combinedAnimationResult;
for (AZ::u32 meshIndex = 0; meshIndex < currentNode->mNumMeshes; ++meshIndex)
if (context.m_sourceNode.ContainsMesh())
{
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[meshIndex]];
if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(mesh->mName.C_Str());
const aiMesh* firstMesh = scene->mMeshes[currentNode->mMeshes[0]];
if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(firstMesh->mName.C_Str());
channelsForMeshName != meshMorphAnimations.end())
{
const auto [nodeIterName, channels] = *channelsForMeshName;
@ -399,7 +398,7 @@ namespace AZ
{
const auto& [animation, morphAnimation] = animAndMorphAnim;
combinedAnimationResult += ImportBlendShapeAnimation(
context, animation, morphAnimation, mesh);
context, animation, morphAnimation, firstMesh);
}
}
}
@ -413,32 +412,39 @@ namespace AZ
if (boneAnimations.empty() && !meshMorphAnimations.empty())
{
const aiAnimation* animation = scene->mAnimations[0];
// Morph animations need a regular animation on the node, as well.
// If there is no bone animation on the current node, then generate one here.
AZStd::shared_ptr<SceneData::GraphData::AnimationData> createdAnimationData =
AZStd::make_shared<SceneData::GraphData::AnimationData>();
const size_t numKeyframes = animation->mDuration + 1; // +1 because we start at 0 and the last keyframe is at mDuration instead of mDuration-1
createdAnimationData->ReserveKeyFrames(numKeyframes);
const double timeStepBetweenFrames = 1.0 / animation->mTicksPerSecond;
createdAnimationData->SetTimeStepBetweenFrames(timeStepBetweenFrames);
// Set every frame of the animation to the start location of the node.
aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode);
DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
for (AZ::u32 time = 0; time <= animation->mDuration; ++time)
for (AZ::u32 channelIndex = 0; channelIndex < animation->mNumMorphMeshChannels; ++channelIndex)
{
createdAnimationData->AddKeyFrame(localTransform);
}
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
context.m_currentGraphPosition, nodeName.c_str(), AZStd::move(createdAnimationData));
context.m_scene.GetGraph().MakeEndPoint(addNode);
const aiMeshMorphAnim* nodeAnim = animation->mMorphMeshChannels[channelIndex];
// Morph animations need a regular animation on the node, as well.
// If there is no bone animation on the current node, then generate one here.
AZStd::shared_ptr<SceneData::GraphData::AnimationData> createdAnimationData =
AZStd::make_shared<SceneData::GraphData::AnimationData>();
const size_t numKeyframes = GetNumKeyFrames(
nodeAnim->mNumKeys,
animation->mDuration,
animation->mTicksPerSecond);
createdAnimationData->ReserveKeyFrames(numKeyframes);
const double timeStepBetweenFrames = 1.0 / animation->mTicksPerSecond;
createdAnimationData->SetTimeStepBetweenFrames(timeStepBetweenFrames);
// Set every frame of the animation to the start location of the node.
aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode);
DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
for (AZ::u32 time = 0; time <= numKeyframes; ++time)
{
createdAnimationData->AddKeyFrame(localTransform);
}
const AZStd::string stubBoneAnimForMorphName(AZStd::string::format("%s%s", nodeName.c_str(), nodeAnim->mName.C_Str()));
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
context.m_currentGraphPosition, stubBoneAnimForMorphName.c_str(), AZStd::move(createdAnimationData));
context.m_scene.GetGraph().MakeEndPoint(addNode);
}
return combinedAnimationResult.GetResult();
}
decltype(boneAnimations) parentFillerAnimations;
@ -446,8 +452,8 @@ namespace AZ
// Go through all the animations and make sure we create animations for bones who's parents don't have an animation
for (auto&& anim : boneAnimations)
{
aiNode* node = scene->mRootNode->FindNode(anim.first.c_str());
aiNode* parent = node->mParent;
const aiNode* node = scene->mRootNode->FindNode(anim.first.c_str());
const aiNode* parent = node->mParent;
while (parent && parent != scene->mRootNode)
{
@ -598,7 +604,8 @@ namespace AZ
// Keyframes generated for every single frame of the animation.
typedef AZStd::map<int, AZStd::vector<KeyData>> ValueToKeyDataMap;
ValueToKeyDataMap valueToKeyDataMap;
// Key time can be less than zero, normalize to have zero be the lowest time.
double keyOffset = 0;
for (int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++)
{
aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx];
@ -609,6 +616,10 @@ namespace AZ
valueToKeyDataMap[currentValue].insert(
AZStd::upper_bound(valueToKeyDataMap[currentValue].begin(), valueToKeyDataMap[currentValue].end(),thisKey),
thisKey);
if (key.mTime < keyOffset)
{
keyOffset = key.mTime;
}
}
}
@ -631,7 +642,7 @@ namespace AZ
const double time = GetTimeForFrame(frame, animation->mTicksPerSecond);
float weight = 0;
if (!SampleKeyFrame(weight, keys, keys.size(), time, keyIdx))
if (!SampleKeyFrame(weight, keys, keys.size(), time + keyOffset, keyIdx))
{
return Events::ProcessingResult::Failure;
}

@ -25,7 +25,6 @@
#include <assimp/scene.h>
#include <assimp/mesh.h>
namespace AZ
{
namespace SceneAPI
@ -44,7 +43,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpBitangentStreamImporter, SceneCore::LoadingComponent>()->Version(2); // LYN-2576
serializeContext->Class<AssImpBitangentStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
@ -55,62 +54,79 @@ namespace AZ
{
return Events::ProcessingResult::Ignored;
}
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
};
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes)
// If there are no bitangents on any meshes, there's nothing to import in this function.
const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{
AZ_Error(Utilities::ErrorWindow, false,
"Tried to construct bitangent stream attribute for invalid or non-mesh parent data, mesh index is invalid");
return Events::ProcessingResult::Failure;
return Events::ProcessingResult::Ignored;
}
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
if (!mesh->HasTangentsAndBitangents())
// AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material.
// This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes,
// and the engine has code to do this later.
const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!allMeshesHaveTangentsAndBitangents)
{
return Events::ProcessingResult::Ignored;
const char* mixedBitangentsError =
"Node with name %s has meshes with and without bitangents. "
"Placeholder incorrect bitangents will be generated to allow the data to process, "
"but the source art needs to be fixed to correct this. Either apply bitangents to all meshes on this node, "
"or remove all bitangents from all meshes on this node.";
AZ_Error(
Utilities::ErrorWindow, false, mixedBitangentsError, currentNode->mName.C_Str());
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
AZStd::shared_ptr<SceneData::GraphData::MeshVertexBitangentData> bitangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexBitangentData>();
// AssImp only has one bitangentStream per mesh.
bitangentStream->SetBitangentSetIndex(0);
bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
bitangentStream->ReserveContainerSpace(vertexCount);
for (int v = 0; v < mesh->mNumVertices; ++v)
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
const Vector3 bitangent(
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v]));
bitangentStream->AppendBitangent(bitangent);
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (!mesh->HasTangentsAndBitangents())
{
// This node has mixed meshes with and without bitangents.
// An error was already thrown above. Output stub bitangents so
// the mesh can still be output in some form, even if the data isn't correct.
// The bitangent count needs to match the vertex count on the associated mesh node.
bitangentStream->AppendBitangent(Vector3::CreateAxisY());
}
else
{
const Vector3 bitangent(
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v]));
bitangentStream->AppendBitangent(bitangent);
}
}
}
AZStd::string nodeName(AZStd::string::format("%s",m_defaultNodeName));
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName);
Events::ProcessingResult bitangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, nodeName.c_str());
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, m_defaultNodeName);
bitangentResults = Events::Process(dataPopulated);
if (bitangentResults != Events::ProcessingResult::Failure)
{
bitangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
return bitangentResults;
}

@ -74,37 +74,51 @@ namespace AZ
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
int parentMeshIndex = parentMeshData->GetSdkMeshIndex();
Events::ProcessingResultCombiner combinedBlendShapeResult;
// 1. Loop through meshes & anims
// Create storage: Anim to meshes
// 2. Loop through anims & meshes
// Create an anim mesh for each anim, with meshes re-combined.
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
AZStd::map<AZStd::string_view, AZStd::vector<AZStd::pair<int, int>>> animToMeshToAnimMeshIndices;
for (int nodeMeshIdx = 0; nodeMeshIdx < numMesh; nodeMeshIdx++)
{
int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx];
const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
// Each mesh gets its own node in the scene graph, so only generate
// morph targets for the current mesh.
if (parentMeshIndex != nodeMeshIdx || !aiMesh->mNumAnimMeshes)
for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
{
continue;
aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx];
animToMeshToAnimMeshIndices[aiAnimMesh->mName.C_Str()].emplace_back(nodeMeshIdx, animIdx);
}
}
for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
for (const auto& animToMeshIndex : animToMeshToAnimMeshIndices)
{
AZStd::shared_ptr<SceneData::GraphData::BlendShapeData> blendShapeData =
AZStd::make_shared<SceneData::GraphData::BlendShapeData>();
// Some DCC tools, like Maya, include a full path separated by '.' in the node names.
// For example, "cone_skin_blendShapeNode.cone_squash"
// Downstream processing doesn't want anything but the last part of that node name,
// so find the last '.' and remove anything before it.
AZStd::string nodeName(animToMeshIndex.first);
size_t dotIndex = nodeName.rfind('.');
if (dotIndex != AZStd::string::npos)
{
AZStd::shared_ptr<SceneData::GraphData::BlendShapeData> blendShapeData =
AZStd::make_shared<SceneData::GraphData::BlendShapeData>();
aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx];
AZStd::string nodeName(aiAnimMesh->mName.C_Str());
size_t dotIndex = nodeName.rfind('.');
if (dotIndex != AZStd::string::npos)
{
nodeName.erase(0, dotIndex + 1);
}
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
AZ_TraceContext("Blend shape name", nodeName);
nodeName.erase(0, dotIndex + 1);
}
int vertexOffset = 0;
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
AZ_TraceContext("Blend shape name", nodeName);
for (const auto& meshIndex : animToMeshIndex.second)
{
int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[meshIndex.first];
const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
const aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[meshIndex.second];
AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumUVSets> uvSetUsedFlags;
for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex)
@ -128,7 +142,7 @@ namespace AZ
context.m_sourceSceneSystem.ConvertUnit(vertex);
blendShapeData->AddPosition(vertex);
blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx);
blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx + vertexOffset, vertIdx + vertexOffset);
// Add normals
if (aiAnimMesh->HasNormals())
@ -191,33 +205,36 @@ namespace AZ
}
for (int idx = 0; idx < face.mNumIndices; ++idx)
{
blendFace.vertexIndex[idx] = face.mIndices[idx];
blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset;
}
blendShapeData->AddFace(blendFace);
}
vertexOffset += aiMesh->mNumVertices;
// Report problem if no vertex or face converted to MeshData
if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0)
{
AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str());
return Events::ProcessingResult::Failure;
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
}
Events::ProcessingResult blendShapeResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName);
blendShapeResult = Events::Process(dataPopulated);
if (blendShapeResult != Events::ProcessingResult::Failure)
{
blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedBlendShapeResult += blendShapeResult;
// Report problem if no vertex or face converted to MeshData
if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0)
{
AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str());
return Events::ProcessingResult::Failure;
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
Events::ProcessingResult blendShapeResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, blendShapeData, newIndex, nodeName);
blendShapeResult = Events::Process(dataPopulated);
if (blendShapeResult != Events::ProcessingResult::Failure)
{
blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedBlendShapeResult += blendShapeResult;
}
return combinedBlendShapeResult.GetResult();

@ -46,8 +46,8 @@ namespace AZ
}
void EnumBonesInNode(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, aiBone*>& boneLookup)
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
/* From AssImp Documentation
a) Create a map or a similar container to store which nodes are necessary for the skeleton. Pre-initialise it for all nodes with a "no".
@ -62,14 +62,14 @@ namespace AZ
for (unsigned meshIndex = 0; meshIndex < node->mNumMeshes; ++meshIndex)
{
aiMesh* mesh = scene->mMeshes[node->mMeshes[meshIndex]];
const aiMesh* mesh = scene->mMeshes[node->mMeshes[meshIndex]];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
aiBone* bone = mesh->mBones[boneIndex];
const aiBone* bone = mesh->mBones[boneIndex];
aiNode* boneNode = scene->mRootNode->FindNode(bone->mName);
aiNode* boneParent = boneNode->mParent;
const aiNode* boneNode = scene->mRootNode->FindNode(bone->mName);
const aiNode* boneParent = boneNode->mParent;
mainBoneList[bone->mName.C_Str()] = boneNode;
boneLookup[bone->mName.C_Str()] = bone;
@ -85,8 +85,8 @@ namespace AZ
}
void EnumChildren(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, aiBone*>& boneLookup)
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
EnumBonesInNode(scene, node, mainBoneList, boneLookup);
@ -102,7 +102,7 @@ namespace AZ
{
AZ_TraceContext("Importer", "Bone");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (IsPivotNode(currentNode->mName))
@ -118,8 +118,8 @@ namespace AZ
}
else
{
AZStd::unordered_map<AZStd::string, aiNode*> mainBoneList;
AZStd::unordered_map<AZStd::string, aiBone*> boneLookup;
AZStd::unordered_map<AZStd::string, const aiNode*> mainBoneList;
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
EnumChildren(scene, scene->mRootNode, mainBoneList, boneLookup);
if (mainBoneList.find(currentNode->mName.C_Str()) != mainBoneList.end())
@ -172,7 +172,7 @@ namespace AZ
}
aiMatrix4x4 transform = currentNode->mTransformation;
aiNode* parent = currentNode->mParent;
const aiNode* parent = currentNode->mParent;
while (parent)
{

@ -11,6 +11,7 @@
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.h>
@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpColorStreamImporter, SceneCore::LoadingComponent>()->Version(2); // LYN-2576
serializeContext->Class<AssImpColorStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
@ -55,43 +56,64 @@ namespace AZ
{
return Events::ProcessingResult::Ignored;
}
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
// This node has at least one mesh, verify that the color channel counts are the same for all meshes.
const int expectedColorChannels = scene->mMeshes[currentNode->mMeshes[0]]->GetNumColorChannels();
const bool allMeshesHaveSameNumberOfColorChannels =
AZStd::all_of(currentNode->mMeshes + 1, currentNode->mMeshes + currentNode->mNumMeshes, [scene, expectedColorChannels](const unsigned int meshIndex)
{
return scene->mMeshes[meshIndex]->GetNumColorChannels() == expectedColorChannels;
});
AZ_Error(
Utilities::ErrorWindow,
allMeshesHaveSameNumberOfColorChannels,
"Color channel counts for node %s has meshes with different color channel counts. "
"The color channel count for the first mesh will be used, and placeholder incorrect color values "
"will be generated to allow the data to process, but the source art needs to be fixed to correct this. "
"All meshes on this node should have the same number of color channels.",
currentNode->mName.C_Str());
if (expectedColorChannels == 0)
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
if (sdkMeshIndex < 0)
{
AZ_Error(Utilities::ErrorWindow, false,
"Tried to construct color stream attribute for invalid or non-mesh parent data, mesh index is missing");
return Events::ProcessingResult::Failure;
return Events::ProcessingResult::Ignored;
}
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
Events::ProcessingResultCombiner combinedVertexColorResults;
for (int colorSetIndex = 0; colorSetIndex < mesh->GetNumColorChannels(); ++colorSetIndex)
for (int colorSetIndex = 0; colorSetIndex < expectedColorChannels; ++colorSetIndex)
{
AZStd::shared_ptr<SceneData::GraphData::MeshVertexColorData> vertexColors =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexColorData>();
vertexColors->ReserveContainerSpace(vertexCount);
for (int v = 0; v < mesh->mNumVertices; ++v)
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
AZ::SceneAPI::DataTypes::Color vertexColor(
AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v]));
vertexColors->AppendColor(vertexColor);
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (colorSetIndex < mesh->GetNumColorChannels())
{
AZ::SceneAPI::DataTypes::Color vertexColor(
AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v]));
vertexColors->AppendColor(vertexColor);
}
else
{
// An error was already emitted if this mesh has less color channels
// than other meshes on the parent node. Append an arbitrary color value, fully opaque black,
// so the mesh can still be processed.
// It's better to let the engine load a partially valid mesh than to completely fail.
vertexColors->AppendColor(AZ::SceneAPI::DataTypes::Color(0.0f,0.0f,0.0f,1.0f));
}
}
}
AZStd::string nodeName(AZStd::string::format("%s%d",m_defaultNodeName,colorSetIndex));
AZStd::string nodeName(AZStd::string::format("%s%d", m_defaultNodeName, colorSetIndex));
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
@ -106,9 +128,7 @@ namespace AZ
combinedVertexColorResults += colorMapResults;
}
return combinedVertexColorResults.GetResult();
}
} // namespace FbxSceneBuilder

@ -69,7 +69,7 @@ namespace AZ
aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode)
{
aiNode* parent = currentNode->mParent;
const aiNode* parent = currentNode->mParent;
aiMatrix4x4 combinedTransform = currentNode->mTransformation;
while (parent)

@ -62,7 +62,7 @@ namespace AZ
for (int idx = 0; idx < context.m_sourceNode.m_assImpNode->mNumMeshes; ++idx)
{
int meshIndex = context.m_sourceNode.m_assImpNode->mMeshes[idx];
aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex];
const aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex];
AZ_Assert(assImpMesh, "Asset Importer Mesh should not be null.");
int materialIndex = assImpMesh->mMaterialIndex;
AZ_TraceContext("Material Index", materialIndex);

@ -45,7 +45,7 @@ namespace AZ
{
AZ_TraceContext("Importer", "Mesh");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (!context.m_sourceNode.ContainsMesh() || IsSkinnedMesh(*currentNode, *scene))

@ -45,7 +45,7 @@ namespace AZ
{
AZ_TraceContext("Importer", "Skin");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (!context.m_sourceNode.ContainsMesh() || !IsSkinnedMesh(*currentNode, *scene))

@ -51,7 +51,7 @@ namespace AZ
{
AZ_TraceContext("Importer", "Skin Weights");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if(currentNode->mNumMeshes <= 0)
@ -59,35 +59,21 @@ namespace AZ
return Events::ProcessingResult::Ignored;
}
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
Events::ProcessingResultCombiner combinedSkinWeightsResult;
int parentMeshIndex = parentMeshData->GetSdkMeshIndex();
// Don't create this until a bone with weights is encountered
Containers::SceneGraph::NodeIndex weightsIndexForMesh;
AZStd::string skinWeightName;
AZStd::shared_ptr<SceneData::GraphData::SkinWeightData> skinWeightData;
Events::ProcessingResultCombiner combinedSkinWeightsResult;
const uint64_t totalVertices = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
int vertexCount = 0;
for(unsigned nodeMeshIndex = 0; nodeMeshIndex < currentNode->mNumMeshes; ++nodeMeshIndex)
{
if (nodeMeshIndex != parentMeshIndex)
{
// Only generate skinning data for the parent mesh.
// Each AssImp mesh is assigned to a unique node,
// so the skinning data should be generated as a child node
// for the associated parent mesh.
continue;
}
int sceneMeshIndex = currentNode->mMeshes[nodeMeshIndex];
const aiMesh* mesh = scene->mMeshes[sceneMeshIndex];
// Don't create this until a bone with weights is encountered
Containers::SceneGraph::NodeIndex weightsIndexForMesh;
AZStd::string skinWeightName;
AZStd::shared_ptr<SceneData::GraphData::SkinWeightData> skinWeightData;
for(unsigned b = 0; b < mesh->mNumBones; ++b)
{
const aiBone* bone = mesh->mBones[b];
@ -100,7 +86,6 @@ namespace AZ
if (!weightsIndexForMesh.IsValid())
{
skinWeightName = s_skinWeightName;
skinWeightName += AZStd::to_string(nodeMeshIndex);
RenamedNodesMap::SanitizeNodeName(skinWeightName, context.m_scene.GetGraph(), context.m_currentGraphPosition);
weightsIndexForMesh =
@ -116,23 +101,25 @@ namespace AZ
}
Pending pending;
pending.m_bone = bone;
pending.m_numVertices = mesh->mNumVertices;
pending.m_numVertices = totalVertices;
pending.m_skinWeightData = skinWeightData;
pending.m_vertOffset = vertexCount;
m_pendingSkinWeights.push_back(pending);
}
vertexCount += mesh->mNumVertices;
}
Events::ProcessingResult skinWeightsResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
skinWeightsResult = Events::Process(dataPopulated);
if (skinWeightsResult != Events::ProcessingResult::Failure)
{
skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
Events::ProcessingResult skinWeightsResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
skinWeightsResult = Events::Process(dataPopulated);
combinedSkinWeightsResult += skinWeightsResult;
if (skinWeightsResult != Events::ProcessingResult::Failure)
{
skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedSkinWeightsResult += skinWeightsResult;
return combinedSkinWeightsResult.GetResult();
}
@ -153,7 +140,7 @@ namespace AZ
link.boneId = boneId;
link.weight = it.m_bone->mWeights[weight].mWeight;
it.m_skinWeightData->AddAndSortLink(it.m_bone->mWeights[weight].mVertexId, link);
it.m_skinWeightData->AddAndSortLink(it.m_bone->mWeights[weight].mVertexId + it.m_vertOffset, link);
}
}
const auto result = m_pendingSkinWeights.empty() ? Events::ProcessingResult::Ignored : Events::ProcessingResult::Success;

@ -61,6 +61,7 @@ namespace AZ
{
const aiBone* m_bone = nullptr;
unsigned m_numVertices = 0;
unsigned m_vertOffset = 0;
AZStd::shared_ptr<SceneData::GraphData::SkinWeightData> m_skinWeightData;
};

@ -11,6 +11,7 @@
*/
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.h>
@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpTangentStreamImporter, SceneCore::LoadingComponent>()->Version(2); // LYN-2576
serializeContext->Class<AssImpTangentStreamImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-3250
}
}
@ -55,62 +56,79 @@ namespace AZ
{
return Events::ProcessingResult::Ignored;
}
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
};
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes)
// If there are no tangents on any meshes, there's nothing to import in this function.
const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{
AZ_Error(Utilities::ErrorWindow, false,
"Tried to construct tangent stream attribute for invalid or non-mesh parent data, mesh index is invalid");
return Events::ProcessingResult::Failure;
return Events::ProcessingResult::Ignored;
}
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
if (!mesh->HasTangentsAndBitangents())
// AssImp nodes with multiple meshes on them occur when AssImp split a mesh on material.
// This logic recombines those meshes to minimize the changes needed to replace FBX SDK with AssImp, FBX SDK did not separate meshes,
// and the engine has code to do this later.
const bool allMeshesHaveTangentsAndBitangents = AZStd::all_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!allMeshesHaveTangentsAndBitangents)
{
return Events::ProcessingResult::Ignored;
const char* mixedTangentsError =
"Node with name %s has meshes with and without tangents. "
"Placeholder incorrect tangents will be generated to allow the data to process, "
"but the source art needs to be fixed to correct this. Either apply tangents to all meshes on this node, "
"or remove all tangents from all meshes on this node.";
AZ_Error(
Utilities::ErrorWindow, false, mixedTangentsError, currentNode->mName.C_Str());
}
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
AZStd::shared_ptr<SceneData::GraphData::MeshVertexTangentData> tangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexTangentData>();
// AssImp only has one tangentStream per mesh.
tangentStream->SetTangentSetIndex(0);
tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
tangentStream->ReserveContainerSpace(vertexCount);
for (int v = 0; v < mesh->mNumVertices; ++v)
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
// Vector4's constructor that takes in a vector3 sets w to 1.0f automatically.
const Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
tangentStream->AppendTangent(tangent);
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (!mesh->HasTangentsAndBitangents())
{
// This node has mixed meshes with and without tangents.
// An error was already thrown above. Output stub tangents so
// the mesh can still be output in some form, even if the data isn't correct.
// The tangent count needs to match the vertex count on the associated mesh node.
tangentStream->AppendTangent(Vector4(0.f, 1.f, 0.f, 1.f));
}
else
{
const Vector4 tangent(
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
tangentStream->AppendTangent(tangent);
}
}
}
AZStd::string nodeName(AZStd::string::format("%s", m_defaultNodeName));
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName);
Events::ProcessingResult tangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, nodeName.c_str());
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, m_defaultNodeName);
tangentResults = Events::Process(dataPopulated);
if (tangentResults != Events::ProcessingResult::Failure)
{
tangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
return tangentResults;
}

@ -50,7 +50,7 @@ namespace AZ
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "transform");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName))

@ -12,17 +12,19 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.h>
#include <SceneAPI/FbxSceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
#include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
@ -45,7 +47,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AssImpUvMapImporter, SceneCore::LoadingComponent>()->Version(3); // LYN-2506
serializeContext->Class<AssImpUvMapImporter, SceneCore::LoadingComponent>()->Version(4); // LYN-3250
}
}
@ -56,28 +58,53 @@ namespace AZ
{
return Events::ProcessingResult::Ignored;
}
aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
if (!meshDataResult.IsSuccess())
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
bool foundTextureCoordinates = false;
AZStd::array<int, AI_MAX_NUMBER_OF_TEXTURECOORDS> meshesPerTextureCoordinateIndex = {};
for (int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex)
{
return meshDataResult.GetError();
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[localMeshIndex]];
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
if (!mesh->mTextureCoords[texCoordIndex])
{
continue;
}
++meshesPerTextureCoordinateIndex[texCoordIndex];
foundTextureCoordinates = true;
}
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
if (!foundTextureCoordinates)
{
return Events::ProcessingResult::Ignored;
}
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
AZ_Assert(sdkMeshIndex >= 0,
"Tried to construct uv stream attribute for invalid or non-mesh parent data, mesh index is missing");
const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
int meshesWithIndex = meshesPerTextureCoordinateIndex[texCoordIndex];
AZ_Error(
Utilities::ErrorWindow,
meshesWithIndex == 0 || meshesWithIndex == currentNode->mNumMeshes,
"Texture coordinate index %d for node %s is not on all meshes on this node. "
"Placeholder arbitrary texture values will be generated to allow the data to process, but the source art "
"needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.",
texCoordIndex,
currentNode->mName.C_Str());
}
Events::ProcessingResultCombiner combinedUvMapResults;
for (int texCoordIndex = 0; texCoordIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++texCoordIndex)
for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
if (!mesh->mTextureCoords[texCoordIndex])
// No meshes have this texture coordinate index, skip it.
if (meshesPerTextureCoordinateIndex[texCoordIndex] == 0)
{
continue;
}
@ -85,24 +112,55 @@ namespace AZ
AZStd::shared_ptr<SceneData::GraphData::MeshVertexUVData> uvMap =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexUVData>();
uvMap->ReserveContainerSpace(vertexCount);
bool customNameFound = false;
AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex));
if (mesh->mTextureCoordsNames[texCoordIndex].length)
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
name = mesh->mTextureCoordsNames[texCoordIndex].C_Str();
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
if(mesh->mTextureCoords[texCoordIndex])
{
if (mesh->mTextureCoordsNames[texCoordIndex].length > 0)
{
if (!customNameFound)
{
name = mesh->mTextureCoordsNames[texCoordIndex].C_Str();
customNameFound = true;
}
else
{
AZ_Warning(Utilities::WarningWindow,
strcmp(name.c_str(), mesh->mTextureCoordsNames[texCoordIndex].C_Str()) == 0,
"Node %s has conflicting mesh coordinate names at index %d, %s and %s. Using %s.",
currentNode->mName.C_Str(),
texCoordIndex,
name.c_str(),
mesh->mTextureCoordsNames[texCoordIndex].C_Str(),
name.c_str());
}
}
}
for (int v = 0; v < mesh->mNumVertices; ++v)
{
if (mesh->mTextureCoords[texCoordIndex])
{
AZ::Vector2 vertexUV(
mesh->mTextureCoords[texCoordIndex][v].x,
// The engine's V coordinate is reverse of how it's stored in the FBX file.
1.0f - mesh->mTextureCoords[texCoordIndex][v].y);
uvMap->AppendUV(vertexUV);
}
else
{
// An error was already emitted if the UV channels for all meshes on this node do not match.
// Append an arbitrary UV value so that the mesh can still be processed.
// It's better to let the engine load a partially valid mesh than to completely fail.
uvMap->AppendUV(AZ::Vector2::CreateZero());
}
}
}
uvMap->SetCustomName(name.c_str());
for (int v = 0; v < mesh->mNumVertices; ++v)
{
AZ::Vector2 vertexUV(
mesh->mTextureCoords[texCoordIndex][v].x,
// The engine's V coordinate is reverse of how it's stored in the FBX file.
1.0f - mesh->mTextureCoords[texCoordIndex][v].y);
uvMap->AppendUV(vertexUV);
}
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str());
@ -116,6 +174,7 @@ namespace AZ
}
combinedUvMapResults += uvMapResults;
}
return combinedUvMapResults.GetResult();

@ -13,6 +13,7 @@
#include <assimp/mesh.h>
#include <assimp/scene.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <SceneAPI/FbxSceneBuilder/FbxSceneSystem.h>
#include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h>
@ -24,7 +25,7 @@
namespace AZ::SceneAPI::FbxSceneBuilder
{
bool BuildSceneMeshFromAssImpMesh(aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc)
{
AZStd::unordered_map<int, int> assImpMatIndexToLYIndex;
@ -34,17 +35,18 @@ namespace AZ::SceneAPI::FbxSceneBuilder
{
return false;
}
auto newMesh = makeMeshFunc();
newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters());
newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters());
// AssImp separates meshes that have multiple materials.
// This code re-combines them to match previous FBX SDK behavior,
// so they can be separated by engine code instead.
int vertOffset = 0;
for (int m = 0; m < currentNode->mNumMeshes; ++m)
{
auto newMesh = makeMeshFunc();
newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters());
newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters());
newMesh->SetSdkMeshIndex(m);
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]];
const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]];
// Lumberyard materials are created in order based on mesh references in the scene
if (assImpMatIndexToLYIndex.find(mesh->mMaterialIndex) == assImpMatIndexToLYIndex.end())
@ -59,7 +61,7 @@ namespace AZ::SceneAPI::FbxSceneBuilder
sceneSystem.SwapVec3ForUpAxis(vertex);
sceneSystem.ConvertUnit(vertex);
newMesh->AddPosition(vertex);
newMesh->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx);
newMesh->SetVertexIndexToControlPointIndexMap(vertIdx + vertOffset, vertIdx + vertOffset);
if (mesh->HasNormals())
{
@ -86,14 +88,15 @@ namespace AZ::SceneAPI::FbxSceneBuilder
}
for (int idx = 0; idx < face.mNumIndices; ++idx)
{
meshFace.vertexIndex[idx] = face.mIndices[idx];
meshFace.vertexIndex[idx] = face.mIndices[idx] + vertOffset;
}
newMesh->AddFace(meshFace, assImpMatIndexToLYIndex[mesh->mMaterialIndex]);
}
vertOffset += mesh->mNumVertices;
meshes.push_back(newMesh);
}
meshes.push_back(newMesh);
return true;
}
@ -127,4 +130,13 @@ namespace AZ::SceneAPI::FbxSceneBuilder
azrtti_cast<const SceneData::GraphData::MeshData* const>(parentData);
return AZ::Success(parentMeshData);
}
uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene)
{
return AZStd::accumulate(node.mMeshes, node.mMeshes + node.mNumMeshes, uint64_t{ 0u },
[&scene](auto runningTotal, unsigned int meshIndex)
{
return runningTotal + scene.mMeshes[meshIndex]->mNumVertices;
});
}
}

@ -44,11 +44,16 @@ namespace AZ
namespace FbxSceneBuilder
{
bool BuildSceneMeshFromAssImpMesh(aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector<AZStd::shared_ptr<DataTypes::IGraphObject>>& meshes,
const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc);
typedef AZ::Outcome<const SceneData::GraphData::MeshData* const, Events::ProcessingResult> GetMeshDataFromParentResult;
GetMeshDataFromParentResult GetMeshDataFromParent(AssImpSceneNodeAppendedContext& context);
// If a node in the original scene file has a mesh with multiple materials on it, the associated AssImp
// node will have multiple meshes on it, broken apart per material. This returns the total number
// of vertices on all meshes on the given node.
uint64_t GetVertexCountForAllMeshesOnNode(const aiNode& node, const aiScene& scene);
}
}
}

@ -27,6 +27,7 @@ namespace AZ
Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{
AZ_TraceContext("Node name", name);
const AZStd::string originalNodeName(name);
bool isNameUpdated = false;
// Nodes can't have an empty name, except of the root, otherwise nodes can't be referenced.
@ -56,7 +57,7 @@ namespace AZ
// can't reference the same parent in that case. This is to make sure the node can be quickly found as
// the full path will be unique. To fix any issues, an index is appended.
size_t index = 1;
size_t offset = name.length();
const size_t offset = name.length();
while (graph.Find(parentNode, name).IsValid())
{
// Remove the previously tried extension.
@ -71,7 +72,8 @@ namespace AZ
if (isNameUpdated)
{
AZ_TraceContext("New node name", name);
AZ_TracePrintf(Utilities::WarningWindow, "The name of the node was invalid or conflicting and was updated.");
AZ_TracePrintf(Utilities::WarningWindow, "The name of the node '%s' was invalid or conflicting and was updated to '%s'.",
originalNodeName.c_str(), name.c_str());
}
return isNameUpdated;

@ -38,12 +38,14 @@ namespace AZ
{
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "AssImpSceneWrapper::LoadSceneFromFile %s", fileName);
AZ_TraceContext("Filename", fileName);
// aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this,
// this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release.
// There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph.
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false);
m_sceneFileName = fileName;
m_assImpScene = m_importer.ReadFile(fileName,
aiProcess_Triangulate //Triangulates all faces of all meshes
| aiProcess_JoinIdenticalVertices //Identifies and joins identical vertex data sets for the imported meshes
| aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value
//dropping the least important and re-normalizing
| aiProcess_GenNormals); //Generate normals for meshes

@ -19,6 +19,16 @@ namespace AZ::SceneAPI::Utilities
m_output += AZStd::string::format("\t%s: %s\n", name, data);
}
void DebugOutput::WriteArray(const char* name, const unsigned int* data, int size)
{
m_output += AZStd::string::format("\t%s: ", name);
for (int index = 0; index < size; ++index)
{
m_output += AZStd::string::format("%d, ", data[index]);
}
m_output += AZStd::string::format("\n");
}
void DebugOutput::Write(const char* name, const AZStd::string& data)
{
Write(name, data.c_str());

@ -29,6 +29,7 @@ namespace AZ::SceneAPI::Utilities
void Write(const char* name, const AZStd::vector<AZStd::vector<T>>& data);
SCENE_CORE_API void Write(const char* name, const char* data);
SCENE_CORE_API void WriteArray(const char* name, const unsigned int* data, int size);
SCENE_CORE_API void Write(const char* name, const AZStd::string& data);
SCENE_CORE_API void Write(const char* name, double data);
SCENE_CORE_API void Write(const char* name, uint64_t data);

@ -285,8 +285,26 @@ namespace AZ
void BlendShapeData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const
{
output.Write("Positions", m_positions);
int index = 0;
for (const auto& position : m_positions)
{
output.Write(AZStd::string::format("\t%d", index).c_str(), position);
++index;
}
index = 0;
output.Write("Normals", m_normals);
for (const auto& normal : m_normals)
{
output.Write(AZStd::string::format("\t%d", index).c_str(), normal);
++index;
}
index = 0;
output.Write("Faces", m_faces);
for (const auto& face : m_faces)
{
output.WriteArray(AZStd::string::format("\t%d", index).c_str(), face.vertexIndex, 3);
++index;
}
}
} // GraphData
} // SceneData

@ -45,7 +45,6 @@ namespace AZ
behaviorContext->Class<MeshData>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "scene")
->Method("GetSdkMeshIndex", &MeshData::GetSdkMeshIndex)
->Method("GetControlPointIndex", &MeshData::GetControlPointIndex)
->Method("GetUsedControlPointCount", &MeshData::GetUsedControlPointCount)
->Method("GetUsedPointIndexForControlPoint", &MeshData::GetUsedPointIndexForControlPoint)
@ -77,10 +76,6 @@ namespace AZ
void MeshData::CloneAttributesFrom(const IGraphObject* sourceObject)
{
IMeshData::CloneAttributesFrom(sourceObject);
if (const auto* typedSource = azrtti_cast<const MeshData*>(sourceObject))
{
SetSdkMeshIndex(typedSource->GetSdkMeshIndex());
}
}
void MeshData::AddPosition(const AZ::Vector3& position)
@ -111,15 +106,6 @@ namespace AZ
m_faceMaterialIds.push_back(faceMaterialId);
}
void MeshData::SetSdkMeshIndex(int sdkMeshIndex)
{
m_sdkMeshIndex = sdkMeshIndex;
}
int MeshData::GetSdkMeshIndex() const
{
return m_sdkMeshIndex;
}
void MeshData::SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex)
{
m_vertexIndexToControlPointIndexMap[vertexIndex] = controlPointIndex;
@ -206,8 +192,26 @@ namespace AZ
void MeshData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const
{
output.Write("Positions", m_positions);
int index = 0;
for (const auto& position : m_positions)
{
output.Write(AZStd::string::format("\t%d", index).c_str(), position);
++index;
}
index = 0;
output.Write("Normals", m_normals);
for (const auto& normal : m_normals)
{
output.Write(AZStd::string::format("\t%d", index).c_str(), normal);
++index;
}
index = 0;
output.Write("FaceList", m_faceList);
for (const auto& face : m_faceList)
{
output.WriteArray(AZStd::string::format("\t%d", index).c_str(), face.vertexIndex, 3);
++index;
}
output.Write("FaceMaterialIds", m_faceMaterialIds);
}
}

@ -49,9 +49,6 @@ namespace AZ
SCENE_DATA_API void AddFace(const AZ::SceneAPI::DataTypes::IMeshData::Face& face,
unsigned int faceMaterialId = AZ::SceneAPI::DataTypes::IMeshData::s_invalidMaterialId);
SCENE_DATA_API void SetSdkMeshIndex(int sdkMeshIndex);
SCENE_DATA_API int GetSdkMeshIndex() const;
SCENE_DATA_API void SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex);
SCENE_DATA_API size_t GetUsedControlPointCount() const override;
SCENE_DATA_API int GetControlPointIndex(int vertexIndex) const override;
@ -80,8 +77,6 @@ namespace AZ
AZStd::unordered_map<int, int> m_vertexIndexToControlPointIndexMap;
AZStd::unordered_map<int, int> m_controlPointToUsedVertexIndexMap;
int m_sdkMeshIndex = -1;
};
}
}

@ -57,7 +57,6 @@ namespace AZ
meshData->AddNormal(Vector3{0.1f, 0.2f, 0.3f});
meshData->AddNormal(Vector3{0.4f, 0.5f, 0.6f});
meshData->SetOriginalUnitSizeInMeters(10.0f);
meshData->SetSdkMeshIndex(1337);
meshData->SetUnitSizeInMeters(0.5f);
meshData->SetVertexIndexToControlPointIndexMap(0, 10);
meshData->SetVertexIndexToControlPointIndexMap(1, 11);
@ -252,7 +251,6 @@ namespace AZ
ExpectExecute("TestExpectFloatEquals(meshData:GetNormal(1).z, 0.6)");
ExpectExecute("TestExpectFloatEquals(meshData:GetOriginalUnitSizeInMeters(), 10.0)");
ExpectExecute("TestExpectFloatEquals(meshData:GetUnitSizeInMeters(), 0.5)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetSdkMeshIndex(), 1337)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetUsedControlPointCount(), 4)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(0), 10)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(1), 11)");

@ -135,7 +135,7 @@ namespace AZ::SceneGenerationComponents
{
for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex)
{
const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(aznumeric_caster(controlPointIndex));
const int usedPointIndex = meshData->GetUsedPointIndexForControlPoint(meshData->GetControlPointIndex(aznumeric_caster(controlPointIndex)));
const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex);
if (usedPointIndex < 0 || linkCount == 0)

Loading…
Cancel
Save