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"); AZ_TraceContext("Importer", "Animation");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
// Add check for animation layers at the scene level. // Add check for animation layers at the scene level.
@ -387,11 +387,10 @@ namespace AZ
} }
Events::ProcessingResultCombiner combinedAnimationResult; Events::ProcessingResultCombiner combinedAnimationResult;
for (AZ::u32 meshIndex = 0; meshIndex < currentNode->mNumMeshes; ++meshIndex) if (context.m_sourceNode.ContainsMesh())
{ {
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[meshIndex]]; const aiMesh* firstMesh = scene->mMeshes[currentNode->mMeshes[0]];
if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(firstMesh->mName.C_Str());
if (NodeToChannelToMorphAnim::iterator channelsForMeshName = meshMorphAnimations.find(mesh->mName.C_Str());
channelsForMeshName != meshMorphAnimations.end()) channelsForMeshName != meshMorphAnimations.end())
{ {
const auto [nodeIterName, channels] = *channelsForMeshName; const auto [nodeIterName, channels] = *channelsForMeshName;
@ -399,7 +398,7 @@ namespace AZ
{ {
const auto& [animation, morphAnimation] = animAndMorphAnim; const auto& [animation, morphAnimation] = animAndMorphAnim;
combinedAnimationResult += ImportBlendShapeAnimation( combinedAnimationResult += ImportBlendShapeAnimation(
context, animation, morphAnimation, mesh); context, animation, morphAnimation, firstMesh);
} }
} }
} }
@ -413,32 +412,39 @@ namespace AZ
if (boneAnimations.empty() && !meshMorphAnimations.empty()) if (boneAnimations.empty() && !meshMorphAnimations.empty())
{ {
const aiAnimation* animation = scene->mAnimations[0]; const aiAnimation* animation = scene->mAnimations[0];
for (AZ::u32 channelIndex = 0; channelIndex < animation->mNumMorphMeshChannels; ++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 = 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)
{ {
createdAnimationData->AddKeyFrame(localTransform); 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.
Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild( AZStd::shared_ptr<SceneData::GraphData::AnimationData> createdAnimationData =
context.m_currentGraphPosition, nodeName.c_str(), AZStd::move(createdAnimationData)); AZStd::make_shared<SceneData::GraphData::AnimationData>();
context.m_scene.GetGraph().MakeEndPoint(addNode);
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(); return combinedAnimationResult.GetResult();
} }
decltype(boneAnimations) parentFillerAnimations; 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 // 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) for (auto&& anim : boneAnimations)
{ {
aiNode* node = scene->mRootNode->FindNode(anim.first.c_str()); const aiNode* node = scene->mRootNode->FindNode(anim.first.c_str());
aiNode* parent = node->mParent; const aiNode* parent = node->mParent;
while (parent && parent != scene->mRootNode) while (parent && parent != scene->mRootNode)
{ {
@ -598,7 +604,8 @@ namespace AZ
// Keyframes generated for every single frame of the animation. // Keyframes generated for every single frame of the animation.
typedef AZStd::map<int, AZStd::vector<KeyData>> ValueToKeyDataMap; typedef AZStd::map<int, AZStd::vector<KeyData>> ValueToKeyDataMap;
ValueToKeyDataMap 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++) for (int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++)
{ {
aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx]; aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx];
@ -609,6 +616,10 @@ namespace AZ
valueToKeyDataMap[currentValue].insert( valueToKeyDataMap[currentValue].insert(
AZStd::upper_bound(valueToKeyDataMap[currentValue].begin(), valueToKeyDataMap[currentValue].end(),thisKey), AZStd::upper_bound(valueToKeyDataMap[currentValue].begin(), valueToKeyDataMap[currentValue].end(),thisKey),
thisKey); thisKey);
if (key.mTime < keyOffset)
{
keyOffset = key.mTime;
}
} }
} }
@ -631,7 +642,7 @@ namespace AZ
const double time = GetTimeForFrame(frame, animation->mTicksPerSecond); const double time = GetTimeForFrame(frame, animation->mTicksPerSecond);
float weight = 0; float weight = 0;
if (!SampleKeyFrame(weight, keys, keys.size(), time, keyIdx)) if (!SampleKeyFrame(weight, keys, keys.size(), time + keyOffset, keyIdx))
{ {
return Events::ProcessingResult::Failure; return Events::ProcessingResult::Failure;
} }

@ -25,7 +25,6 @@
#include <assimp/scene.h> #include <assimp/scene.h>
#include <assimp/mesh.h> #include <assimp/mesh.h>
namespace AZ namespace AZ
{ {
namespace SceneAPI namespace SceneAPI
@ -44,7 +43,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context); SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext) 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; return Events::ProcessingResult::Ignored;
} }
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
if (!meshDataResult.IsSuccess())
{ {
return meshDataResult.GetError(); return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
} };
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); // If there are no bitangents on any meshes, there's nothing to import in this function.
if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes) const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{ {
AZ_Error(Utilities::ErrorWindow, false, return Events::ProcessingResult::Ignored;
"Tried to construct bitangent stream attribute for invalid or non-mesh parent data, mesh index is invalid");
return Events::ProcessingResult::Failure;
} }
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; // 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,
if (!mesh->HasTangentsAndBitangents()) // 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::shared_ptr<SceneData::GraphData::MeshVertexBitangentData> bitangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexBitangentData>(); AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexBitangentData>();
// AssImp only has one bitangentStream per mesh. // AssImp only has one bitangentStream per mesh.
bitangentStream->SetBitangentSetIndex(0); bitangentStream->SetBitangentSetIndex(0);
bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx); bitangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
bitangentStream->ReserveContainerSpace(vertexCount); bitangentStream->ReserveContainerSpace(vertexCount);
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
for (int v = 0; v < mesh->mNumVertices; ++v)
{ {
const Vector3 bitangent( const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mBitangents[v]));
bitangentStream->AppendBitangent(bitangent); 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 = 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; Events::ProcessingResult bitangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, nodeName.c_str()); AssImpSceneAttributeDataPopulatedContext dataPopulated(context, bitangentStream, newIndex, m_defaultNodeName);
bitangentResults = Events::Process(dataPopulated); bitangentResults = Events::Process(dataPopulated);
if (bitangentResults != Events::ProcessingResult::Failure) if (bitangentResults != Events::ProcessingResult::Failure)
{ {
bitangentResults = AddAttributeDataNodeWithContexts(dataPopulated); bitangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
} }
return bitangentResults; return bitangentResults;
} }

@ -74,37 +74,51 @@ namespace AZ
{ {
return meshDataResult.GetError(); return meshDataResult.GetError();
} }
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
int parentMeshIndex = parentMeshData->GetSdkMeshIndex();
Events::ProcessingResultCombiner combinedBlendShapeResult; 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++) for (int nodeMeshIdx = 0; nodeMeshIdx < numMesh; nodeMeshIdx++)
{ {
int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx]; int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx];
const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx]; const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
// Each mesh gets its own node in the scene graph, so only generate
// morph targets for the current mesh.
if (parentMeshIndex != nodeMeshIdx || !aiMesh->mNumAnimMeshes)
{ {
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 = nodeName.erase(0, dotIndex + 1);
AZStd::make_shared<SceneData::GraphData::BlendShapeData>(); }
int vertexOffset = 0;
aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx]; RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
AZStd::string nodeName(aiAnimMesh->mName.C_Str()); AZ_TraceContext("Blend shape name", nodeName);
size_t dotIndex = nodeName.rfind('.'); for (const auto& meshIndex : animToMeshIndex.second)
if (dotIndex != AZStd::string::npos) {
{ int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[meshIndex.first];
nodeName.erase(0, dotIndex + 1); const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx];
} const aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[meshIndex.second];
RenamedNodesMap::SanitizeNodeName(nodeName, context.m_scene.GetGraph(), context.m_currentGraphPosition, "BlendShape");
AZ_TraceContext("Blend shape name", nodeName);
AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumUVSets> uvSetUsedFlags; AZStd::bitset<SceneData::GraphData::BlendShapeData::MaxNumUVSets> uvSetUsedFlags;
for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex) for (AZ::u8 uvSetIndex = 0; uvSetIndex < SceneData::GraphData::BlendShapeData::MaxNumUVSets; ++uvSetIndex)
@ -128,7 +142,7 @@ namespace AZ
context.m_sourceSceneSystem.ConvertUnit(vertex); context.m_sourceSceneSystem.ConvertUnit(vertex);
blendShapeData->AddPosition(vertex); blendShapeData->AddPosition(vertex);
blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx); blendShapeData->SetVertexIndexToControlPointIndexMap(vertIdx + vertexOffset, vertIdx + vertexOffset);
// Add normals // Add normals
if (aiAnimMesh->HasNormals()) if (aiAnimMesh->HasNormals())
@ -191,33 +205,36 @@ namespace AZ
} }
for (int idx = 0; idx < face.mNumIndices; ++idx) for (int idx = 0; idx < face.mNumIndices; ++idx)
{ {
blendFace.vertexIndex[idx] = face.mIndices[idx]; blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset;
} }
blendShapeData->AddFace(blendFace); 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) // Report problem if no vertex or face converted to MeshData
{ if (blendShapeData->GetVertexCount() <= 0 || blendShapeData->GetFaceCount() <= 0)
blendShapeResult = AddAttributeDataNodeWithContexts(dataPopulated); {
} AZ_Error(Utilities::ErrorWindow, false, "Missing geometry data in blendshape node %s.", nodeName.c_str());
combinedBlendShapeResult += blendShapeResult; 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(); return combinedBlendShapeResult.GetResult();

@ -46,8 +46,8 @@ namespace AZ
} }
void EnumBonesInNode( void EnumBonesInNode(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, aiNode*>& mainBoneList, const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, aiBone*>& boneLookup) AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{ {
/* From AssImp Documentation /* 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". 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) 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) 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); const aiNode* boneNode = scene->mRootNode->FindNode(bone->mName);
aiNode* boneParent = boneNode->mParent; const aiNode* boneParent = boneNode->mParent;
mainBoneList[bone->mName.C_Str()] = boneNode; mainBoneList[bone->mName.C_Str()] = boneNode;
boneLookup[bone->mName.C_Str()] = bone; boneLookup[bone->mName.C_Str()] = bone;
@ -85,8 +85,8 @@ namespace AZ
} }
void EnumChildren( void EnumChildren(
const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, aiNode*>& mainBoneList, const aiScene* scene, const aiNode* node, AZStd::unordered_map<AZStd::string, const aiNode*>& mainBoneList,
AZStd::unordered_map<AZStd::string, aiBone*>& boneLookup) AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{ {
EnumBonesInNode(scene, node, mainBoneList, boneLookup); EnumBonesInNode(scene, node, mainBoneList, boneLookup);
@ -102,7 +102,7 @@ namespace AZ
{ {
AZ_TraceContext("Importer", "Bone"); AZ_TraceContext("Importer", "Bone");
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (IsPivotNode(currentNode->mName)) if (IsPivotNode(currentNode->mName))
@ -118,8 +118,8 @@ namespace AZ
} }
else else
{ {
AZStd::unordered_map<AZStd::string, aiNode*> mainBoneList; AZStd::unordered_map<AZStd::string, const aiNode*> mainBoneList;
AZStd::unordered_map<AZStd::string, aiBone*> boneLookup; AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
EnumChildren(scene, scene->mRootNode, mainBoneList, boneLookup); EnumChildren(scene, scene->mRootNode, mainBoneList, boneLookup);
if (mainBoneList.find(currentNode->mName.C_Str()) != mainBoneList.end()) if (mainBoneList.find(currentNode->mName.C_Str()) != mainBoneList.end())
@ -172,7 +172,7 @@ namespace AZ
} }
aiMatrix4x4 transform = currentNode->mTransformation; aiMatrix4x4 transform = currentNode->mTransformation;
aiNode* parent = currentNode->mParent; const aiNode* parent = currentNode->mParent;
while (parent) while (parent)
{ {

@ -11,6 +11,7 @@
*/ */
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h> #include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.h> #include <SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.h>
@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context); SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext) 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; return Events::ProcessingResult::Ignored;
} }
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); // This node has at least one mesh, verify that the color channel counts are the same for all meshes.
if (!meshDataResult.IsSuccess()) 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(); return Events::ProcessingResult::Ignored;
}
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;
} }
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
Events::ProcessingResultCombiner combinedVertexColorResults; 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::shared_ptr<SceneData::GraphData::MeshVertexColorData> vertexColors =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexColorData>(); AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexColorData>();
vertexColors->ReserveContainerSpace(vertexCount); vertexColors->ReserveContainerSpace(vertexCount);
for (int v = 0; v < mesh->mNumVertices; ++v) for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{ {
AZ::SceneAPI::DataTypes::Color vertexColor( const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
AssImpSDKWrapper::AssImpTypeConverter::ToColor(mesh->mColors[colorSetIndex][v])); for (int v = 0; v < mesh->mNumVertices; ++v)
vertexColors->AppendColor(vertexColor); {
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 = Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str()); context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
@ -106,9 +128,7 @@ namespace AZ
combinedVertexColorResults += colorMapResults; combinedVertexColorResults += colorMapResults;
} }
return combinedVertexColorResults.GetResult(); return combinedVertexColorResults.GetResult();
} }
} // namespace FbxSceneBuilder } // namespace FbxSceneBuilder

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

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

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

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

@ -51,7 +51,7 @@ namespace AZ
{ {
AZ_TraceContext("Importer", "Skin Weights"); 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(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if(currentNode->mNumMeshes <= 0) if(currentNode->mNumMeshes <= 0)
@ -59,35 +59,21 @@ namespace AZ
return Events::ProcessingResult::Ignored; return Events::ProcessingResult::Ignored;
} }
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); Events::ProcessingResultCombiner combinedSkinWeightsResult;
if (!meshDataResult.IsSuccess())
{
return meshDataResult.GetError();
}
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
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) 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]; int sceneMeshIndex = currentNode->mMeshes[nodeMeshIndex];
const aiMesh* mesh = scene->mMeshes[sceneMeshIndex]; 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) for(unsigned b = 0; b < mesh->mNumBones; ++b)
{ {
const aiBone* bone = mesh->mBones[b]; const aiBone* bone = mesh->mBones[b];
@ -100,7 +86,6 @@ namespace AZ
if (!weightsIndexForMesh.IsValid()) if (!weightsIndexForMesh.IsValid())
{ {
skinWeightName = s_skinWeightName; skinWeightName = s_skinWeightName;
skinWeightName += AZStd::to_string(nodeMeshIndex);
RenamedNodesMap::SanitizeNodeName(skinWeightName, context.m_scene.GetGraph(), context.m_currentGraphPosition); RenamedNodesMap::SanitizeNodeName(skinWeightName, context.m_scene.GetGraph(), context.m_currentGraphPosition);
weightsIndexForMesh = weightsIndexForMesh =
@ -116,23 +101,25 @@ namespace AZ
} }
Pending pending; Pending pending;
pending.m_bone = bone; pending.m_bone = bone;
pending.m_numVertices = mesh->mNumVertices; pending.m_numVertices = totalVertices;
pending.m_skinWeightData = skinWeightData; pending.m_skinWeightData = skinWeightData;
pending.m_vertOffset = vertexCount;
m_pendingSkinWeights.push_back(pending); m_pendingSkinWeights.push_back(pending);
} }
vertexCount += mesh->mNumVertices;
}
Events::ProcessingResult skinWeightsResult; Events::ProcessingResult skinWeightsResult;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName); AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
skinWeightsResult = Events::Process(dataPopulated); skinWeightsResult = Events::Process(dataPopulated);
if (skinWeightsResult != Events::ProcessingResult::Failure)
{
skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
}
combinedSkinWeightsResult += skinWeightsResult; if (skinWeightsResult != Events::ProcessingResult::Failure)
{
skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
} }
combinedSkinWeightsResult += skinWeightsResult;
return combinedSkinWeightsResult.GetResult(); return combinedSkinWeightsResult.GetResult();
} }
@ -153,7 +140,7 @@ namespace AZ
link.boneId = boneId; link.boneId = boneId;
link.weight = it.m_bone->mWeights[weight].mWeight; 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; const auto result = m_pendingSkinWeights.empty() ? Events::ProcessingResult::Ignored : Events::ProcessingResult::Success;

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

@ -11,6 +11,7 @@
*/ */
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h> #include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.h> #include <SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.h>
@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context); SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext) 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; return Events::ProcessingResult::Ignored;
} }
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); const auto meshHasTangentsAndBitangents = [&scene](const unsigned int meshIndex)
if (!meshDataResult.IsSuccess())
{ {
return meshDataResult.GetError(); return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
} };
const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
size_t vertexCount = parentMeshData->GetVertexCount();
int sdkMeshIndex = parentMeshData->GetSdkMeshIndex(); // If there are no tangents on any meshes, there's nothing to import in this function.
if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes) const bool anyMeshHasTangentsAndBitangents = AZStd::any_of(currentNode->mMeshes, currentNode->mMeshes + currentNode->mNumMeshes, meshHasTangentsAndBitangents);
if (!anyMeshHasTangentsAndBitangents)
{ {
AZ_Error(Utilities::ErrorWindow, false, return Events::ProcessingResult::Ignored;
"Tried to construct tangent stream attribute for invalid or non-mesh parent data, mesh index is invalid");
return Events::ProcessingResult::Failure;
} }
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; // 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,
if (!mesh->HasTangentsAndBitangents()) // 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::shared_ptr<SceneData::GraphData::MeshVertexTangentData> tangentStream =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexTangentData>(); AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexTangentData>();
// AssImp only has one tangentStream per mesh. // AssImp only has one tangentStream per mesh.
tangentStream->SetTangentSetIndex(0); tangentStream->SetTangentSetIndex(0);
tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx); tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
tangentStream->ReserveContainerSpace(vertexCount); tangentStream->ReserveContainerSpace(vertexCount);
for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
for (int v = 0; v < mesh->mNumVertices; ++v)
{ {
// Vector4's constructor that takes in a vector3 sets w to 1.0f automatically. const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
const Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
tangentStream->AppendTangent(tangent); 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 = 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; Events::ProcessingResult tangentResults;
AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, nodeName.c_str()); AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, m_defaultNodeName);
tangentResults = Events::Process(dataPopulated); tangentResults = Events::Process(dataPopulated);
if (tangentResults != Events::ProcessingResult::Failure) if (tangentResults != Events::ProcessingResult::Failure)
{ {
tangentResults = AddAttributeDataNodeWithContexts(dataPopulated); tangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
} }
return tangentResults; return tangentResults;
} }

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

@ -12,17 +12,19 @@
#include <AzCore/Math/Vector2.h> #include <AzCore/Math/Vector2.h>
#include <AzCore/Serialization/SerializeContext.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 <AzCore/std/smart_ptr/make_shared.h>
#include <AzToolsFramework/Debug/TraceContext.h> #include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h> #include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h>
#include <SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.h> #include <SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.h>
#include <SceneAPI/FbxSceneBuilder/Importers/ImporterUtilities.h> #include <SceneAPI/FbxSceneBuilder/Importers/ImporterUtilities.h>
#include <SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h> #include <SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.h>
#include <SceneAPI/SDKWrapper/AssImpNodeWrapper.h> #include <SceneAPI/SceneCore/Utilities/Reporting.h>
#include <SceneAPI/SDKWrapper/AssImpSceneWrapper.h>
#include <SceneAPI/SceneData/GraphData/MeshData.h> #include <SceneAPI/SceneData/GraphData/MeshData.h>
#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.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/scene.h>
#include <assimp/mesh.h> #include <assimp/mesh.h>
@ -45,7 +47,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context); SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
if (serializeContext) 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; return Events::ProcessingResult::Ignored;
} }
aiNode* currentNode = context.m_sourceNode.GetAssImpNode(); const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene(); const aiScene* scene = context.m_sourceScene.GetAssImpScene();
GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context)); // AssImp separates meshes that have multiple materials.
if (!meshDataResult.IsSuccess()) // 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(); const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
AZ_Assert(sdkMeshIndex >= 0,
"Tried to construct uv stream attribute for invalid or non-mesh parent data, mesh index is missing");
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; 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; continue;
} }
@ -85,24 +112,55 @@ namespace AZ
AZStd::shared_ptr<SceneData::GraphData::MeshVertexUVData> uvMap = AZStd::shared_ptr<SceneData::GraphData::MeshVertexUVData> uvMap =
AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexUVData>(); AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexUVData>();
uvMap->ReserveContainerSpace(vertexCount); uvMap->ReserveContainerSpace(vertexCount);
bool customNameFound = false;
AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex)); 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()); 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 = Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str()); context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str());
@ -116,6 +174,7 @@ namespace AZ
} }
combinedUvMapResults += uvMapResults; combinedUvMapResults += uvMapResults;
} }
return combinedUvMapResults.GetResult(); return combinedUvMapResults.GetResult();

@ -13,6 +13,7 @@
#include <assimp/mesh.h> #include <assimp/mesh.h>
#include <assimp/scene.h> #include <assimp/scene.h>
#include <AzCore/Casting/numeric_cast.h> #include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/numeric.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <SceneAPI/FbxSceneBuilder/FbxSceneSystem.h> #include <SceneAPI/FbxSceneBuilder/FbxSceneSystem.h>
#include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h> #include <SceneAPI/FbxSceneBuilder/ImportContexts/AssImpImportContexts.h>
@ -24,7 +25,7 @@
namespace AZ::SceneAPI::FbxSceneBuilder 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) const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc)
{ {
AZStd::unordered_map<int, int> assImpMatIndexToLYIndex; AZStd::unordered_map<int, int> assImpMatIndexToLYIndex;
@ -34,17 +35,18 @@ namespace AZ::SceneAPI::FbxSceneBuilder
{ {
return false; 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) for (int m = 0; m < currentNode->mNumMeshes; ++m)
{ {
auto newMesh = makeMeshFunc(); const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]];
newMesh->SetUnitSizeInMeters(sceneSystem.GetUnitSizeInMeters());
newMesh->SetOriginalUnitSizeInMeters(sceneSystem.GetOriginalUnitSizeInMeters());
newMesh->SetSdkMeshIndex(m);
aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]];
// Lumberyard materials are created in order based on mesh references in the scene // Lumberyard materials are created in order based on mesh references in the scene
if (assImpMatIndexToLYIndex.find(mesh->mMaterialIndex) == assImpMatIndexToLYIndex.end()) if (assImpMatIndexToLYIndex.find(mesh->mMaterialIndex) == assImpMatIndexToLYIndex.end())
@ -59,7 +61,7 @@ namespace AZ::SceneAPI::FbxSceneBuilder
sceneSystem.SwapVec3ForUpAxis(vertex); sceneSystem.SwapVec3ForUpAxis(vertex);
sceneSystem.ConvertUnit(vertex); sceneSystem.ConvertUnit(vertex);
newMesh->AddPosition(vertex); newMesh->AddPosition(vertex);
newMesh->SetVertexIndexToControlPointIndexMap(vertIdx, vertIdx); newMesh->SetVertexIndexToControlPointIndexMap(vertIdx + vertOffset, vertIdx + vertOffset);
if (mesh->HasNormals()) if (mesh->HasNormals())
{ {
@ -86,14 +88,15 @@ namespace AZ::SceneAPI::FbxSceneBuilder
} }
for (int idx = 0; idx < face.mNumIndices; ++idx) 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]); newMesh->AddFace(meshFace, assImpMatIndexToLYIndex[mesh->mMaterialIndex]);
} }
vertOffset += mesh->mNumVertices;
meshes.push_back(newMesh);
} }
meshes.push_back(newMesh);
return true; return true;
} }
@ -127,4 +130,13 @@ namespace AZ::SceneAPI::FbxSceneBuilder
azrtti_cast<const SceneData::GraphData::MeshData* const>(parentData); azrtti_cast<const SceneData::GraphData::MeshData* const>(parentData);
return AZ::Success(parentMeshData); 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 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); const AZStd::function<AZStd::shared_ptr<SceneData::GraphData::MeshData>()>& makeMeshFunc);
typedef AZ::Outcome<const SceneData::GraphData::MeshData* const, Events::ProcessingResult> GetMeshDataFromParentResult; typedef AZ::Outcome<const SceneData::GraphData::MeshData* const, Events::ProcessingResult> GetMeshDataFromParentResult;
GetMeshDataFromParentResult GetMeshDataFromParent(AssImpSceneNodeAppendedContext& context); 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) Containers::SceneGraph::NodeIndex parentNode, const char* defaultName)
{ {
AZ_TraceContext("Node name", name); AZ_TraceContext("Node name", name);
const AZStd::string originalNodeName(name);
bool isNameUpdated = false; bool isNameUpdated = false;
// Nodes can't have an empty name, except of the root, otherwise nodes can't be referenced. // 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 // 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. // the full path will be unique. To fix any issues, an index is appended.
size_t index = 1; size_t index = 1;
size_t offset = name.length(); const size_t offset = name.length();
while (graph.Find(parentNode, name).IsValid()) while (graph.Find(parentNode, name).IsValid())
{ {
// Remove the previously tried extension. // Remove the previously tried extension.
@ -71,7 +72,8 @@ namespace AZ
if (isNameUpdated) if (isNameUpdated)
{ {
AZ_TraceContext("New node name", name); 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; return isNameUpdated;

@ -38,12 +38,14 @@ namespace AZ
{ {
AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "AssImpSceneWrapper::LoadSceneFromFile %s", fileName); AZ_TracePrintf(SceneAPI::Utilities::LogWindow, "AssImpSceneWrapper::LoadSceneFromFile %s", fileName);
AZ_TraceContext("Filename", 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_PRESERVE_PIVOTS, false);
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false); m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false);
m_sceneFileName = fileName; m_sceneFileName = fileName;
m_assImpScene = m_importer.ReadFile(fileName, m_assImpScene = m_importer.ReadFile(fileName,
aiProcess_Triangulate //Triangulates all faces of all meshes 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 | aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value
//dropping the least important and re-normalizing //dropping the least important and re-normalizing
| aiProcess_GenNormals); //Generate normals for meshes | 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); 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) void DebugOutput::Write(const char* name, const AZStd::string& data)
{ {
Write(name, data.c_str()); 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); 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 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, const AZStd::string& data);
SCENE_CORE_API void Write(const char* name, double data); SCENE_CORE_API void Write(const char* name, double data);
SCENE_CORE_API void Write(const char* name, uint64_t 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 void BlendShapeData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const
{ {
output.Write("Positions", m_positions); 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); 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); 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 } // GraphData
} // SceneData } // SceneData

@ -45,7 +45,6 @@ namespace AZ
behaviorContext->Class<MeshData>() behaviorContext->Class<MeshData>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "scene") ->Attribute(AZ::Script::Attributes::Module, "scene")
->Method("GetSdkMeshIndex", &MeshData::GetSdkMeshIndex)
->Method("GetControlPointIndex", &MeshData::GetControlPointIndex) ->Method("GetControlPointIndex", &MeshData::GetControlPointIndex)
->Method("GetUsedControlPointCount", &MeshData::GetUsedControlPointCount) ->Method("GetUsedControlPointCount", &MeshData::GetUsedControlPointCount)
->Method("GetUsedPointIndexForControlPoint", &MeshData::GetUsedPointIndexForControlPoint) ->Method("GetUsedPointIndexForControlPoint", &MeshData::GetUsedPointIndexForControlPoint)
@ -77,10 +76,6 @@ namespace AZ
void MeshData::CloneAttributesFrom(const IGraphObject* sourceObject) void MeshData::CloneAttributesFrom(const IGraphObject* sourceObject)
{ {
IMeshData::CloneAttributesFrom(sourceObject); IMeshData::CloneAttributesFrom(sourceObject);
if (const auto* typedSource = azrtti_cast<const MeshData*>(sourceObject))
{
SetSdkMeshIndex(typedSource->GetSdkMeshIndex());
}
} }
void MeshData::AddPosition(const AZ::Vector3& position) void MeshData::AddPosition(const AZ::Vector3& position)
@ -111,15 +106,6 @@ namespace AZ
m_faceMaterialIds.push_back(faceMaterialId); 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) void MeshData::SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex)
{ {
m_vertexIndexToControlPointIndexMap[vertexIndex] = controlPointIndex; m_vertexIndexToControlPointIndexMap[vertexIndex] = controlPointIndex;
@ -206,8 +192,26 @@ namespace AZ
void MeshData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const void MeshData::GetDebugOutput(SceneAPI::Utilities::DebugOutput& output) const
{ {
output.Write("Positions", m_positions); 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); 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); 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); output.Write("FaceMaterialIds", m_faceMaterialIds);
} }
} }

@ -49,9 +49,6 @@ namespace AZ
SCENE_DATA_API void AddFace(const AZ::SceneAPI::DataTypes::IMeshData::Face& face, SCENE_DATA_API void AddFace(const AZ::SceneAPI::DataTypes::IMeshData::Face& face,
unsigned int faceMaterialId = AZ::SceneAPI::DataTypes::IMeshData::s_invalidMaterialId); 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 void SetVertexIndexToControlPointIndexMap(int vertexIndex, int controlPointIndex);
SCENE_DATA_API size_t GetUsedControlPointCount() const override; SCENE_DATA_API size_t GetUsedControlPointCount() const override;
SCENE_DATA_API int GetControlPointIndex(int vertexIndex) 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_vertexIndexToControlPointIndexMap;
AZStd::unordered_map<int, int> m_controlPointToUsedVertexIndexMap; 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.1f, 0.2f, 0.3f});
meshData->AddNormal(Vector3{0.4f, 0.5f, 0.6f}); meshData->AddNormal(Vector3{0.4f, 0.5f, 0.6f});
meshData->SetOriginalUnitSizeInMeters(10.0f); meshData->SetOriginalUnitSizeInMeters(10.0f);
meshData->SetSdkMeshIndex(1337);
meshData->SetUnitSizeInMeters(0.5f); meshData->SetUnitSizeInMeters(0.5f);
meshData->SetVertexIndexToControlPointIndexMap(0, 10); meshData->SetVertexIndexToControlPointIndexMap(0, 10);
meshData->SetVertexIndexToControlPointIndexMap(1, 11); meshData->SetVertexIndexToControlPointIndexMap(1, 11);
@ -252,7 +251,6 @@ namespace AZ
ExpectExecute("TestExpectFloatEquals(meshData:GetNormal(1).z, 0.6)"); ExpectExecute("TestExpectFloatEquals(meshData:GetNormal(1).z, 0.6)");
ExpectExecute("TestExpectFloatEquals(meshData:GetOriginalUnitSizeInMeters(), 10.0)"); ExpectExecute("TestExpectFloatEquals(meshData:GetOriginalUnitSizeInMeters(), 10.0)");
ExpectExecute("TestExpectFloatEquals(meshData:GetUnitSizeInMeters(), 0.5)"); ExpectExecute("TestExpectFloatEquals(meshData:GetUnitSizeInMeters(), 0.5)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetSdkMeshIndex(), 1337)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetUsedControlPointCount(), 4)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetUsedControlPointCount(), 4)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(0), 10)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(0), 10)");
ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(1), 11)"); ExpectExecute("TestExpectIntegerEquals(meshData:GetControlPointIndex(1), 11)");

@ -135,7 +135,7 @@ namespace AZ::SceneGenerationComponents
{ {
for (size_t controlPointIndex = 0; controlPointIndex < skinData.get().GetVertexCount(); ++controlPointIndex) 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); const size_t linkCount = skinData.get().GetLinkCount(controlPointIndex);
if (usedPointIndex < 0 || linkCount == 0) if (usedPointIndex < 0 || linkCount == 0)

Loading…
Cancel
Save