+#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/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
index 5737a188de..3c221d6055 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
@@ -58,13 +58,6 @@ namespace O3DE::ProjectManager
hLayout->addWidget(m_gemListView);
hLayout->addWidget(m_gemInspector);
-
-
- // Select the first entry after everything got correctly sized
- QTimer::singleShot(100, [=]{
- QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
- m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
- });
}
QVector GemCatalogScreen::GenerateTestData()
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
index 729935fc8e..5b7127bdbe 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp
@@ -32,21 +32,36 @@ namespace O3DE::ProjectManager
{
switch (platform)
{
- case O3DE::ProjectManager::GemInfo::Android:
+ case Android:
return "Android";
- case O3DE::ProjectManager::GemInfo::iOS:
+ case iOS:
return "iOS";
- case O3DE::ProjectManager::GemInfo::Linux:
+ case Linux:
return "Linux";
- case O3DE::ProjectManager::GemInfo::macOS:
+ case macOS:
return "macOS";
- case O3DE::ProjectManager::GemInfo::Windows:
+ case Windows:
return "Windows";
default:
return "";
}
}
+ QString GemInfo::GetTypeString(Type type)
+ {
+ switch (type)
+ {
+ case Asset:
+ return "Asset";
+ case Code:
+ return "Code";
+ case Tool:
+ return "Tool";
+ default:
+ return "";
+ }
+ }
+
bool GemInfo::IsPlatformSupported(Platform platform) const
{
return (m_platforms & platform);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
index 7ee619702f..28b2fab451 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
@@ -36,6 +36,16 @@ namespace O3DE::ProjectManager
Q_DECLARE_FLAGS(Platforms, Platform)
static QString GetPlatformString(Platform platform);
+ enum Type
+ {
+ Asset = 1 << 0,
+ Code = 1 << 1,
+ Tool = 1 << 2,
+ NumTypes = 3
+ };
+ Q_DECLARE_FLAGS(Types, Type)
+ static QString GetTypeString(Type type);
+
GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const;
@@ -50,6 +60,7 @@ namespace O3DE::ProjectManager
bool m_isAdded = false; //! Is the gem currently added and enabled in the project?
QString m_summary;
Platforms m_platforms;
+ Types m_types; //! Asset and/or Code and/or Tool
QStringList m_features;
QString m_directoryLink;
QString m_documentationLink;
@@ -62,3 +73,4 @@ namespace O3DE::ProjectManager
} // namespace O3DE::ProjectManager
Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Platforms)
+Q_DECLARE_OPERATORS_FOR_FLAGS(O3DE::ProjectManager::GemInfo::Types)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
index 1112c656f3..addf59783d 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp
@@ -10,7 +10,8 @@
*
*/
-#include "GemModel.h"
+#include
+#include
namespace O3DE::ProjectManager
{
@@ -32,8 +33,11 @@ namespace O3DE::ProjectManager
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(gemInfo.m_name, RoleName);
+ const QString uuidString = gemInfo.m_uuid.ToString().c_str();
+ item->setData(uuidString, RoleUuid);
item->setData(gemInfo.m_creator, RoleCreator);
- item->setData(static_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms);
+ item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes);
item->setData(gemInfo.m_summary, RoleSummary);
item->setData(gemInfo.m_isAdded, RoleIsAdded);
@@ -48,6 +52,8 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_features, RoleFeatures);
appendRow(item);
+
+ m_uuidToNameMap[uuidString] = gemInfo.m_displayName;
}
void GemModel::Clear()
@@ -65,11 +71,21 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleCreator).toString();
}
+ QString GemModel::GetUuidString(const QModelIndex& modelIndex)
+ {
+ return modelIndex.data(RoleUuid).toString();
+ }
+
GemInfo::Platforms GemModel::GetPlatforms(const QModelIndex& modelIndex)
{
return static_cast(modelIndex.data(RolePlatforms).toInt());
}
+ GemInfo::Types GemModel::GetTypes(const QModelIndex& modelIndex)
+ {
+ return static_cast(modelIndex.data(RoleTypes).toInt());
+ }
+
QString GemModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
@@ -90,9 +106,35 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleDocLink).toString();
}
+ AZ::Outcome GemModel::FindGemNameByUuidString(const QString& uuidString) const
+ {
+ const auto iterator = m_uuidToNameMap.find(uuidString);
+ if (iterator != m_uuidToNameMap.end())
+ {
+ return AZ::Success(iterator.value());
+ }
+
+ return AZ::Failure();
+ }
+
QStringList GemModel::GetDependingGems(const QModelIndex& modelIndex)
{
- return modelIndex.data(RoleDependingGems).toStringList();
+ QStringList result = modelIndex.data(RoleDependingGems).toStringList();
+ if (result.isEmpty())
+ {
+ return {};
+ }
+
+ for (QString& dependingGemString : result)
+ {
+ AZ::Outcome gemNameOutcome = FindGemNameByUuidString(dependingGemString);
+ if (gemNameOutcome.IsSuccess())
+ {
+ dependingGemString = gemNameOutcome.GetValue();
+ }
+ }
+
+ return result;
}
QStringList GemModel::GetConflictingGems(const QModelIndex& modelIndex)
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
index fba65e7009..76211b1f22 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h
@@ -13,7 +13,8 @@
#pragma once
#if !defined(Q_MOC_RUN)
-#include "GemInfo.h"
+#include
+#include
#include
#include
#include
@@ -33,14 +34,18 @@ namespace O3DE::ProjectManager
void AddGem(const GemInfo& gemInfo);
void Clear();
+ AZ::Outcome FindGemNameByUuidString(const QString& uuidString) const;
+ QStringList GetDependingGems(const QModelIndex& modelIndex);
+
static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex);
+ static QString GetUuidString(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
+ static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static bool IsAdded(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex);
- static QStringList GetDependingGems(const QModelIndex& modelIndex);
static QStringList GetConflictingGems(const QModelIndex& modelIndex);
static QString GetVersion(const QModelIndex& modelIndex);
static QString GetLastUpdated(const QModelIndex& modelIndex);
@@ -51,6 +56,7 @@ namespace O3DE::ProjectManager
enum UserRole
{
RoleName = Qt::UserRole,
+ RoleUuid,
RoleCreator,
RolePlatforms,
RoleSummary,
@@ -63,8 +69,10 @@ namespace O3DE::ProjectManager
RoleLastUpdated,
RoleBinarySize,
RoleFeatures,
+ RoleTypes
};
+ QHash m_uuidToNameMap;
QItemSelectionModel* m_selectionModel = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.cpp b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
index fddc4cd8c9..a6308f6c62 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.cpp
@@ -27,7 +27,12 @@ namespace O3DE::ProjectManager
void LinkLabel::mousePressEvent([[maybe_unused]] QMouseEvent* event)
{
- QDesktopServices::openUrl(m_url);
+ if (m_url.isValid())
+ {
+ QDesktopServices::openUrl(m_url);
+ }
+
+ emit clicked();
}
void LinkLabel::enterEvent([[maybe_unused]] QEvent* event)
diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.h b/Code/Tools/ProjectManager/Source/LinkWidget.h
index 7055dce2af..b3a34cd63a 100644
--- a/Code/Tools/ProjectManager/Source/LinkWidget.h
+++ b/Code/Tools/ProjectManager/Source/LinkWidget.h
@@ -26,10 +26,16 @@ namespace O3DE::ProjectManager
class LinkLabel
: public QLabel
{
+ Q_OBJECT // AUTOMOC
+
public:
- LinkLabel(const QString& text, const QUrl& url = {}, QWidget* parent = nullptr);
+ LinkLabel(const QString& text = {}, const QUrl& url = {}, QWidget* parent = nullptr);
void SetUrl(const QUrl& url);
+
+ signals:
+ void clicked();
+
private:
void mousePressEvent(QMouseEvent* event) override;
void enterEvent(QEvent* event) override;
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/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index e925c81032..2c2c143845 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -426,7 +426,7 @@ namespace O3DE::ProjectManager
{
// required
gemInfo.m_name = Py_To_String(data["Name"]);
- gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
+ gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"]));
// optional
gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name);
@@ -437,7 +437,8 @@ namespace O3DE::ProjectManager
{
for (auto dependency : data["Dependencies"])
{
- gemInfo.m_dependingGemUuids.push_back(Py_To_String(dependency["Uuid"]));
+ const AZ::Uuid uuid = Py_To_String(dependency["Uuid"]);
+ gemInfo.m_dependingGemUuids.push_back(uuid.ToString().c_str());
}
}
if (data.contains("Tags"))
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..f36633142f 100644
--- a/Code/Tools/ProjectManager/project_manager.qrc
+++ b/Code/Tools/ProjectManager/project_manager.qrc
@@ -9,5 +9,8 @@
Resources/iOS.svg
Resources/Linux.svg
Resources/macOS.svg
+ Resources/ArrowDownLine.svg
+ Resources/ArrowUpLine.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 tangentStream =
AZStd::make_shared();
-
// AssImp only has one tangentStream per mesh.
tangentStream->SetTangentSetIndex(0);
tangentStream->SetTangentSpace(AZ::SceneAPI::DataTypes::TangentSpace::FromFbx);
tangentStream->ReserveContainerSpace(vertexCount);
-
- for (int v = 0; v < mesh->mNumVertices; ++v)
+ for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
- // Vector4's constructor that takes in a vector3 sets w to 1.0f automatically.
- const Vector4 tangent(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
- tangentStream->AppendTangent(tangent);
+ const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
+
+ for (int v = 0; v < mesh->mNumVertices; ++v)
+ {
+ if (!mesh->HasTangentsAndBitangents())
+ {
+ // This node has mixed meshes with and without tangents.
+ // An error was already thrown above. Output stub tangents so
+ // the mesh can still be output in some form, even if the data isn't correct.
+ // The tangent count needs to match the vertex count on the associated mesh node.
+ tangentStream->AppendTangent(Vector4(0.f, 1.f, 0.f, 1.f));
+ }
+ else
+ {
+ const Vector4 tangent(
+ AssImpSDKWrapper::AssImpTypeConverter::ToVector3(mesh->mTangents[v]));
+ tangentStream->AppendTangent(tangent);
+ }
+ }
}
- AZStd::string nodeName(AZStd::string::format("%s", m_defaultNodeName));
Containers::SceneGraph::NodeIndex newIndex =
- context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, nodeName.c_str());
+ context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, m_defaultNodeName);
Events::ProcessingResult tangentResults;
- AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, nodeName.c_str());
+ AssImpSceneAttributeDataPopulatedContext dataPopulated(context, tangentStream, newIndex, m_defaultNodeName);
tangentResults = Events::Process(dataPopulated);
if (tangentResults != Events::ProcessingResult::Failure)
{
tangentResults = AddAttributeDataNodeWithContexts(dataPopulated);
}
-
return tangentResults;
}
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
index 84c0e3e18c..5357c32fa9 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
@@ -50,7 +50,7 @@ namespace AZ
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "transform");
- aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
+ const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName))
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp
index f5f47b233d..e37a4f4285 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpUvMapImporter.cpp
@@ -12,17 +12,19 @@
#include
#include
+#include
+#include
#include
#include
#include
#include
#include
#include
-#include
-#include
+#include
#include
#include
-#include
+#include
+#include
#include
#include
@@ -45,7 +47,7 @@ namespace AZ
SerializeContext* serializeContext = azrtti_cast(context);
if (serializeContext)
{
- serializeContext->Class()->Version(3); // LYN-2506
+ serializeContext->Class()->Version(4); // LYN-3250
}
}
@@ -56,28 +58,53 @@ namespace AZ
{
return Events::ProcessingResult::Ignored;
}
- aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
+ const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
- GetMeshDataFromParentResult meshDataResult(GetMeshDataFromParent(context));
- if (!meshDataResult.IsSuccess())
+ // AssImp separates meshes that have multiple materials.
+ // This code re-combines them to match previous FBX SDK behavior,
+ // so they can be separated by engine code instead.
+ bool foundTextureCoordinates = false;
+ AZStd::array meshesPerTextureCoordinateIndex = {};
+ for (int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex)
{
- return meshDataResult.GetError();
+ aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[localMeshIndex]];
+ for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
+ {
+ if (!mesh->mTextureCoords[texCoordIndex])
+ {
+ continue;
+ }
+ ++meshesPerTextureCoordinateIndex[texCoordIndex];
+ foundTextureCoordinates = true;
+ }
}
- const SceneData::GraphData::MeshData* const parentMeshData(meshDataResult.GetValue());
- size_t vertexCount = parentMeshData->GetVertexCount();
+ if (!foundTextureCoordinates)
+ {
+ return Events::ProcessingResult::Ignored;
+ }
- int sdkMeshIndex = parentMeshData->GetSdkMeshIndex();
- AZ_Assert(sdkMeshIndex >= 0,
- "Tried to construct uv stream attribute for invalid or non-mesh parent data, mesh index is missing");
+ const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene);
- aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
+ for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
+ {
+ int meshesWithIndex = meshesPerTextureCoordinateIndex[texCoordIndex];
+ AZ_Error(
+ Utilities::ErrorWindow,
+ meshesWithIndex == 0 || meshesWithIndex == currentNode->mNumMeshes,
+ "Texture coordinate index %d for node %s is not on all meshes on this node. "
+ "Placeholder arbitrary texture values will be generated to allow the data to process, but the source art "
+ "needs to be fixed to correct this. All meshes on this node should have the same number of texture coordinate channels.",
+ texCoordIndex,
+ currentNode->mName.C_Str());
+ }
Events::ProcessingResultCombiner combinedUvMapResults;
- for (int texCoordIndex = 0; texCoordIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++texCoordIndex)
+ for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex)
{
- if (!mesh->mTextureCoords[texCoordIndex])
+ // No meshes have this texture coordinate index, skip it.
+ if (meshesPerTextureCoordinateIndex[texCoordIndex] == 0)
{
continue;
}
@@ -85,24 +112,55 @@ namespace AZ
AZStd::shared_ptr uvMap =
AZStd::make_shared();
uvMap->ReserveContainerSpace(vertexCount);
-
+ bool customNameFound = false;
AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex));
- if (mesh->mTextureCoordsNames[texCoordIndex].length)
+ for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex)
{
- name = mesh->mTextureCoordsNames[texCoordIndex].C_Str();
+ const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]];
+ if(mesh->mTextureCoords[texCoordIndex])
+ {
+ if (mesh->mTextureCoordsNames[texCoordIndex].length > 0)
+ {
+ if (!customNameFound)
+ {
+ name = mesh->mTextureCoordsNames[texCoordIndex].C_Str();
+ customNameFound = true;
+ }
+ else
+ {
+ AZ_Warning(Utilities::WarningWindow,
+ strcmp(name.c_str(), mesh->mTextureCoordsNames[texCoordIndex].C_Str()) == 0,
+ "Node %s has conflicting mesh coordinate names at index %d, %s and %s. Using %s.",
+ currentNode->mName.C_Str(),
+ texCoordIndex,
+ name.c_str(),
+ mesh->mTextureCoordsNames[texCoordIndex].C_Str(),
+ name.c_str());
+ }
+ }
+ }
+
+ for (int v = 0; v < mesh->mNumVertices; ++v)
+ {
+ if (mesh->mTextureCoords[texCoordIndex])
+ {
+ AZ::Vector2 vertexUV(
+ mesh->mTextureCoords[texCoordIndex][v].x,
+ // The engine's V coordinate is reverse of how it's stored in the FBX file.
+ 1.0f - mesh->mTextureCoords[texCoordIndex][v].y);
+ uvMap->AppendUV(vertexUV);
+ }
+ else
+ {
+ // An error was already emitted if the UV channels for all meshes on this node do not match.
+ // Append an arbitrary UV value so that the mesh can still be processed.
+ // It's better to let the engine load a partially valid mesh than to completely fail.
+ uvMap->AppendUV(AZ::Vector2::CreateZero());
+ }
+ }
}
uvMap->SetCustomName(name.c_str());
-
- for (int v = 0; v < mesh->mNumVertices; ++v)
- {
- AZ::Vector2 vertexUV(
- mesh->mTextureCoords[texCoordIndex][v].x,
- // The engine's V coordinate is reverse of how it's stored in the FBX file.
- 1.0f - mesh->mTextureCoords[texCoordIndex][v].y);
- uvMap->AppendUV(vertexUV);
- }
-
Containers::SceneGraph::NodeIndex newIndex =
context.m_scene.GetGraph().AddChild(context.m_currentGraphPosition, name.c_str());
@@ -116,6 +174,7 @@ namespace AZ
}
combinedUvMapResults += uvMapResults;
+
}
return combinedUvMapResults.GetResult();
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp
index 59821336e0..c90fe7d1f3 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp
@@ -13,6 +13,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -24,7 +25,7 @@
namespace AZ::SceneAPI::FbxSceneBuilder
{
- bool BuildSceneMeshFromAssImpMesh(aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes,
+ bool BuildSceneMeshFromAssImpMesh(const aiNode* currentNode, const aiScene* scene, const FbxSceneSystem& sceneSystem, AZStd::vector>& meshes,
const AZStd::function