+#include
namespace O3DE::ProjectManager
{
+ inline constexpr static int s_contentMargins = 80;
+ inline constexpr static int s_buttonSpacing = 30;
+ inline constexpr static int s_iconSize = 24;
+ inline constexpr static int s_spacerSize = 20;
+ inline constexpr static int s_boxButtonWidth = 210;
+ inline constexpr static int s_boxButtonHeight = 280;
+
FirstTimeUseScreen::FirstTimeUseScreen(QWidget* parent)
: ScreenWidget(parent)
- , m_ui(new Ui::FirstTimeUseClass())
{
- m_ui->setupUi(this);
+ QVBoxLayout* vLayout = new QVBoxLayout();
+ setLayout(vLayout);
+ vLayout->setContentsMargins(s_contentMargins, s_contentMargins, s_contentMargins, s_contentMargins);
- connect(m_ui->createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton);
- connect(m_ui->openProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleOpenProjectButton);
+ QLabel* titleLabel = new QLabel(this);
+ titleLabel->setText(tr("Ready. Set. Create!"));
+ titleLabel->setStyleSheet("font-size: 60px");
+ vLayout->addWidget(titleLabel);
+
+ QLabel* introLabel = new QLabel(this);
+ introLabel->setTextFormat(Qt::AutoText);
+ introLabel->setText(tr("Welcome to O3DE! Start something new by creating a project. Not sure what to create?
Explore what\342\200\231s available by downloading our sample project.
"));
+ introLabel->setStyleSheet("font-size: 14px");
+ vLayout->addWidget(introLabel);
+
+ QHBoxLayout* buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(s_buttonSpacing);
+
+ m_createProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Add.svg"), tr("Create Project"), this);
+ m_createProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
+ buttonLayout->addWidget(m_createProjectButton);
+
+ m_addProjectButton = CreateLargeBoxButton(QIcon(":/Resources/Select_Folder.svg"), tr("Add a Project"), this);
+ m_addProjectButton->setIconSize(QSize(s_iconSize, s_iconSize));
+ buttonLayout->addWidget(m_addProjectButton);
+
+ QSpacerItem* buttonSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ buttonLayout->addItem(buttonSpacer);
+
+ vLayout->addItem(buttonLayout);
+
+ QSpacerItem* verticalSpacer = new QSpacerItem(s_spacerSize, s_spacerSize, QSizePolicy::Minimum, QSizePolicy::Expanding);
+ vLayout->addItem(verticalSpacer);
+
+ // Using border-image allows for scaling options background-image does not support
+ setStyleSheet("O3DE--ProjectManager--ScreenWidget { border-image: url(:/Resources/Backgrounds/FirstTimeBackgroundImage.jpg) repeat repeat; }");
+
+ connect(m_createProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleNewProjectButton);
+ connect(m_addProjectButton, &QPushButton::pressed, this, &FirstTimeUseScreen::HandleAddProjectButton);
}
ProjectManagerScreen FirstTimeUseScreen::GetScreenEnum()
@@ -36,9 +82,21 @@ namespace O3DE::ProjectManager
emit ResetScreenRequest(ProjectManagerScreen::NewProjectSettingsCore);
emit ChangeScreenRequest(ProjectManagerScreen::NewProjectSettingsCore);
}
- void FirstTimeUseScreen::HandleOpenProjectButton()
+ void FirstTimeUseScreen::HandleAddProjectButton()
{
emit ChangeScreenRequest(ProjectManagerScreen::ProjectsHome);
}
+ QPushButton* FirstTimeUseScreen::CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent)
+ {
+ QPushButton* largeBoxButton = new QPushButton(icon, text, parent);
+
+ largeBoxButton->setFixedSize(s_boxButtonWidth, s_boxButtonHeight);
+ largeBoxButton->setFlat(true);
+ largeBoxButton->setFocusPolicy(Qt::FocusPolicy::NoFocus);
+ largeBoxButton->setStyleSheet("QPushButton { font-size: 14px; background-color: rgba(0, 0, 0, 191); }");
+
+ return largeBoxButton;
+ }
+
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h
index 4b4a99f16a..b6b57dc16b 100644
--- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h
+++ b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.h
@@ -15,10 +15,8 @@
#include
#endif
-namespace Ui
-{
- class FirstTimeUseClass;
-}
+QT_FORWARD_DECLARE_CLASS(QIcon)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
namespace O3DE::ProjectManager
{
@@ -32,10 +30,13 @@ namespace O3DE::ProjectManager
protected slots:
void HandleNewProjectButton();
- void HandleOpenProjectButton();
+ void HandleAddProjectButton();
private:
- QScopedPointer m_ui;
+ QPushButton* CreateLargeBoxButton(const QIcon& icon, const QString& text, QWidget* parent = nullptr);
+
+ QPushButton* m_createProjectButton;
+ QPushButton* m_addProjectButton;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui b/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui
deleted file mode 100644
index fdc195731f..0000000000
--- a/Code/Tools/ProjectManager/Source/FirstTimeUseScreen.ui
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
- FirstTimeUseClass
-
-
-
- 0
- 0
- 881
- 555
-
-
-
- Form
-
-
- -
-
-
-
-
-
-
- 30
-
-
-
- READY. SET. CREATE!
-
-
-
- -
-
-
- <html><head/><body><p>Welcome to O3DE! Start something new by creating a project. Not sure what to create? </p><p>Explore what’s available by downloading our sample project.</p></body></html>
-
-
- Qt::AutoText
-
-
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
- Create Project
-
-
-
- :/Resources/Add.svg:/Resources/Add.svg
-
-
-
- 16
- 16
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Open a Project
-
-
-
- :/Resources/Select_Folder.svg:/Resources/Select_Folder.svg
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
index 977667071f..6b9d268564 100644
--- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
+++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
@@ -27,6 +27,12 @@ namespace O3DE::ProjectManager
, m_ui(new Ui::ProjectManagerWindowClass())
{
m_ui->setupUi(this);
+ QLayout* layout = m_ui->centralWidget->layout();
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ layout->setContentsMargins(0, 0, 0, 0);
+
+ setFixedSize(this->geometry().width(), this->geometry().height());
m_pythonBindings = AZStd::make_unique(engineRootPath);
diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
index 789dd1b656..a71ed3aabf 100644
--- a/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
+++ b/Code/Tools/ProjectManager/Source/ProjectManagerWindow.ui
@@ -6,10 +6,16 @@
0
0
- 800
- 600
+ 1200
+ 800
+
+
+ 0
+ 0
+
+
O3DE Project Manager
@@ -21,7 +27,7 @@
0
0
- 800
+ 1200
36
diff --git a/Code/Tools/ProjectManager/Source/ScreenWidget.h b/Code/Tools/ProjectManager/Source/ScreenWidget.h
index ae235daf2b..483066e031 100644
--- a/Code/Tools/ProjectManager/Source/ScreenWidget.h
+++ b/Code/Tools/ProjectManager/Source/ScreenWidget.h
@@ -15,18 +15,20 @@
#include
#include
+#include
+#include
#endif
namespace O3DE::ProjectManager
{
class ScreenWidget
- : public QWidget
+ : public QFrame
{
Q_OBJECT
public:
explicit ScreenWidget(QWidget* parent = nullptr)
- : QWidget(parent)
+ : QFrame(parent)
{
}
~ScreenWidget() = default;
diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp
index 69af09f496..b8a38ed155 100644
--- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp
+++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp
@@ -22,6 +22,9 @@ namespace O3DE::ProjectManager
: QWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout();
+ vLayout->setMargin(0);
+ vLayout->setSpacing(0);
+ vLayout->setContentsMargins(0, 0, 0, 0);
setLayout(vLayout);
m_screenStack = new QStackedWidget();
diff --git a/Code/Tools/ProjectManager/project_manager.qrc b/Code/Tools/ProjectManager/project_manager.qrc
index 6509a9f940..3c23bc24ff 100644
--- a/Code/Tools/ProjectManager/project_manager.qrc
+++ b/Code/Tools/ProjectManager/project_manager.qrc
@@ -9,5 +9,6 @@
Resources/iOS.svg
Resources/Linux.svg
Resources/macOS.svg
+ Resources/Backgrounds/FirstTimeBackgroundImage.jpg
diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake
index 9ffdb6029d..3594d1e079 100644
--- a/Code/Tools/ProjectManager/project_manager_files.cmake
+++ b/Code/Tools/ProjectManager/project_manager_files.cmake
@@ -22,7 +22,6 @@ set(FILES
Source/EngineInfo.cpp
Source/FirstTimeUseScreen.h
Source/FirstTimeUseScreen.cpp
- Source/FirstTimeUseScreen.ui
Source/ProjectManagerWindow.h
Source/ProjectManagerWindow.cpp
Source/ProjectTemplateInfo.h
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
index 3dc14814de..d2818f3653 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/DllMain.cpp
@@ -41,18 +41,6 @@ namespace AZ
static AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler* g_fbxImporter = nullptr;
static AZStd::vector g_componentDescriptors;
- void Initialize()
- {
- // Currently it's still needed to explicitly create an instance of this instead of letting
- // it be a normal component. This is because ResourceCompilerScene needs to return
- // the list of available extensions before it can start the application.
- if (!g_fbxImporter)
- {
- g_fbxImporter = aznew AZ::SceneAPI::FbxSceneImporter::FbxImportRequestHandler();
- g_fbxImporter->Activate();
- }
- }
-
void Reflect(AZ::SerializeContext* /*context*/)
{
// Descriptor registration is done in Reflect instead of Initialize because the ResourceCompilerScene initializes the libraries before
@@ -64,6 +52,7 @@ namespace AZ
{
// Global importer and behavior
g_componentDescriptors.push_back(FbxSceneBuilder::FbxImporter::CreateDescriptor());
+ g_componentDescriptors.push_back(FbxSceneImporter::FbxImportRequestHandler::CreateDescriptor());
// Node and attribute importers
g_componentDescriptors.push_back(AssImpBitangentStreamImporter::CreateDescriptor());
@@ -125,7 +114,6 @@ namespace AZ
extern "C" AZ_DLL_EXPORT void InitializeDynamicModule(void* env)
{
AZ::Environment::Attach(static_cast(env));
- AZ::SceneAPI::FbxSceneBuilder::Initialize();
}
extern "C" AZ_DLL_EXPORT void Reflect(AZ::SerializeContext* context)
{
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
index 155209f1b5..a43f1e16b8 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.cpp
@@ -10,12 +10,16 @@
*
*/
+#include
+#include
#include
-#include
+#include
+#include
+#include
+#include
#include
#include
#include
-#include
namespace AZ
{
@@ -23,10 +27,25 @@ namespace AZ
{
namespace FbxSceneImporter
{
- const char* FbxImportRequestHandler::s_extension = ".fbx";
+ void SceneImporterSettings::Reflect(AZ::ReflectContext* context)
+ {
+ if (auto serializeContext = azrtti_cast(context); serializeContext)
+ {
+ serializeContext->Class()
+ ->Version(1)
+ ->Field("SupportedFileTypeExtensions", &SceneImporterSettings::m_supportedFileTypeExtensions);
+ }
+ }
void FbxImportRequestHandler::Activate()
{
+ auto settingsRegistry = AZ::SettingsRegistry::Get();
+
+ if (settingsRegistry)
+ {
+ settingsRegistry->GetObject(m_settings, "/O3DE/SceneAPI/AssetImporter");
+ }
+
BusConnect();
}
@@ -37,21 +56,29 @@ namespace AZ
void FbxImportRequestHandler::Reflect(ReflectContext* context)
{
+ SceneImporterSettings::Reflect(context);
+
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(1);
+ serializeContext->Class()->Version(1)->Attribute(
+ AZ::Edit::Attributes::SystemComponentTags,
+ AZStd::vector({AssetBuilderSDK::ComponentTags::AssetBuilder}));
+
}
}
void FbxImportRequestHandler::GetSupportedFileExtensions(AZStd::unordered_set& extensions)
{
- extensions.insert(s_extension);
+ extensions.insert(m_settings.m_supportedFileTypeExtensions.begin(), m_settings.m_supportedFileTypeExtensions.end());
}
Events::LoadingResult FbxImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid, [[maybe_unused]] RequestingApplication requester)
{
- if (!AzFramework::StringFunc::Path::IsExtension(path.c_str(), s_extension))
+ AZStd::string extension;
+ StringFunc::Path::GetExtension(path.c_str(), extension);
+
+ if (!m_settings.m_supportedFileTypeExtensions.contains(extension))
{
return Events::LoadingResult::Ignored;
}
@@ -73,6 +100,11 @@ namespace AZ
return Events::LoadingResult::AssetFailure;
}
}
+
+ void FbxImportRequestHandler::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
+ {
+ provided.emplace_back(AZ_CRC_CE("AssetImportRequestHandler"));
+ }
} // namespace Import
} // namespace SceneAPI
} // namespace AZ
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
index 8b33051f1e..12c7c6f877 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/FbxImportRequestHandler.h
@@ -21,12 +21,21 @@ namespace AZ
{
namespace FbxSceneImporter
{
+ struct SceneImporterSettings
+ {
+ AZ_TYPE_INFO(SceneImporterSettings, "{8BB6C7AD-BF99-44DC-9DA1-E7AD3F03DC10}");
+
+ static void Reflect(AZ::ReflectContext* context);
+
+ AZStd::unordered_set m_supportedFileTypeExtensions;
+ };
+
class FbxImportRequestHandler
- : public SceneCore::BehaviorComponent
+ : public AZ::Component
, public Events::AssetImportRequestBus::Handler
{
public:
- AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}", SceneCore::BehaviorComponent);
+ AZ_COMPONENT(FbxImportRequestHandler, "{9F4B189C-0A96-4F44-A5F0-E087FF1561F8}");
~FbxImportRequestHandler() override = default;
@@ -38,8 +47,13 @@ namespace AZ
Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid,
RequestingApplication requester) override;
+ static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
+
private:
- static const char* s_extension;
+
+ SceneImporterSettings m_settings;
+
+ static constexpr const char* SettingsFilename = "AssetImporterSettings.json";
};
} // namespace FbxSceneImporter
} // namespace SceneAPI
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
index 0ee25195bc..38f7de89c6 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
@@ -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 createdAnimationData =
- AZStd::make_shared();
-
- 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);
+ 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 createdAnimationData =
+ AZStd::make_shared();
+
+ 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);
}
-
- Containers::SceneGraph::NodeIndex addNode = context.m_scene.GetGraph().AddChild(
- context.m_currentGraphPosition, nodeName.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> 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;
}
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp
index c2b1f20035..2ce9bc14f4 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBitangentStreamImporter.cpp
@@ -25,7 +25,6 @@
#include
#include
-
namespace AZ
{
namespace SceneAPI
@@ -44,7 +43,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(2); // LYN-2576
+ serializeContext->Class()->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());
+ return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
+ };
- size_t vertexCount = parentMeshData->GetVertexCount();
-
- int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
- if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes)
- {
- 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;
- }
-
- aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
-
- if (!mesh->HasTangentsAndBitangents())
+ // 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)
{
return Events::ProcessingResult::Ignored;
}
+ // 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)
+ {
+ 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 bitangentStream =
AZStd::make_shared();
-
// 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;
}
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp
index c0399329d3..34266a3e29 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBlendShapeImporter.cpp
@@ -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>> 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)
- {
- continue;
- }
-
for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++)
{
- AZStd::shared_ptr blendShapeData =
- AZStd::make_shared();
-
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);
+ animToMeshToAnimMeshIndices[aiAnimMesh->mName.C_Str()].emplace_back(nodeMeshIdx, animIdx);
+ }
+ }
+
+ for (const auto& animToMeshIndex : animToMeshToAnimMeshIndices)
+ {
+ AZStd::shared_ptr blendShapeData =
+ AZStd::make_shared();
+
+ // 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)
+ {
+ 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 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();
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
index 4467d6933b..5b43941715 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
@@ -46,8 +46,8 @@ namespace AZ
}
void EnumBonesInNode(
- const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList,
- AZStd::unordered_map& boneLookup)
+ const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList,
+ AZStd::unordered_map& 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& mainBoneList,
- AZStd::unordered_map& boneLookup)
+ const aiScene* scene, const aiNode* node, AZStd::unordered_map& mainBoneList,
+ AZStd::unordered_map& 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 mainBoneList;
- AZStd::unordered_map boneLookup;
+ AZStd::unordered_map mainBoneList;
+ AZStd::unordered_map 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)
{
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp
index 7ebdb55363..75fa39105f 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpColorStreamImporter.cpp
@@ -11,6 +11,7 @@
*/
#include
+#include
#include
#include
#include
@@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(2); // LYN-2576
+ serializeContext->Class()->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())
- {
- return meshDataResult.GetError();
- }
- const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
+ // 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;
+ });
- size_t vertexCount = parentMeshData->GetVertexCount();
+ 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());
- int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
- if (sdkMeshIndex < 0)
+ if (expectedColorChannels == 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 vertexColors =
AZStd::make_shared();
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
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp
index 80ac01ebdf..e79eaa09aa 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpImporterUtilities.cpp
@@ -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)
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp
index e314804ea1..a912b90e34 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMaterialImporter.cpp
@@ -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);
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp
index cafb96934d..193a1f9fd5 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpMeshImporter.cpp
@@ -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))
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp
index 145dc9a457..f4a5fd0f93 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinImporter.cpp
@@ -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))
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp
index abcbf10b4a..d8503857cc 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.cpp
@@ -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());
-
- int parentMeshIndex = parentMeshData->GetSdkMeshIndex();
-
Events::ProcessingResultCombiner combinedSkinWeightsResult;
+ // Don't create this until a bone with weights is encountered
+ Containers::SceneGraph::NodeIndex weightsIndexForMesh;
+ AZStd::string skinWeightName;
+ AZStd::shared_ptr skinWeightData;
+
+ 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 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);
}
-
- Events::ProcessingResult skinWeightsResult;
- AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
- skinWeightsResult = Events::Process(dataPopulated);
-
- if (skinWeightsResult != Events::ProcessingResult::Failure)
- {
- skinWeightsResult = AddAttributeDataNodeWithContexts(dataPopulated);
- }
-
- combinedSkinWeightsResult += skinWeightsResult;
+ vertexCount += mesh->mNumVertices;
}
+ Events::ProcessingResult skinWeightsResult;
+ AssImpSceneAttributeDataPopulatedContext dataPopulated(context, skinWeightData, weightsIndexForMesh, skinWeightName);
+ skinWeightsResult = Events::Process(dataPopulated);
+
+ 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;
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h
index f048fe0af2..655c838701 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpSkinWeightsImporter.h
@@ -61,6 +61,7 @@ namespace AZ
{
const aiBone* m_bone = nullptr;
unsigned m_numVertices = 0;
+ unsigned m_vertOffset = 0;
AZStd::shared_ptr m_skinWeightData;
};
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp
index 992a6a6ab1..47b7e410b4 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTangentStreamImporter.cpp
@@ -11,6 +11,7 @@
*/
#include
+#include
#include
#include
#include
@@ -44,7 +45,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(2); // LYN-2576
+ serializeContext->Class()->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());
+ return scene->mMeshes[meshIndex]->HasTangentsAndBitangents();
+ };
- size_t vertexCount = parentMeshData->GetVertexCount();
-
- int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
- if (sdkMeshIndex < 0 || sdkMeshIndex >= currentNode->mNumMeshes)
- {
- 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;
- }
-
- aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
-
- if (!mesh->HasTangentsAndBitangents())
+ // 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)
{
return Events::ProcessingResult::Ignored;
}
+ // 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)
+ {
+ 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