diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Menu/helpers.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Menu/helpers.svg
new file mode 100644
index 0000000000..e782a7066a
--- /dev/null
+++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Menu/helpers.svg
@@ -0,0 +1,24 @@
+
+
\ No newline at end of file
diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
index bf110c1899..74610dca90 100644
--- a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
+++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc
@@ -29,5 +29,6 @@
Menu/trackview_editor.svg
Menu/ui_editor.svg
Menu/menu.svg
+ Menu/helpers.svg
diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.cpp b/Code/Sandbox/Editor/ViewportTitleDlg.cpp
index 491d31a2ff..5b13b1d308 100644
--- a/Code/Sandbox/Editor/ViewportTitleDlg.cpp
+++ b/Code/Sandbox/Editor/ViewportTitleDlg.cpp
@@ -130,6 +130,7 @@ CViewportTitleDlg::CViewportTitleDlg(QWidget* pParent)
SetupCameraDropdownMenu();
SetupResolutionDropdownMenu();
SetupViewportInformationMenu();
+ SetupHelpersButton();
SetupOverflowMenu();
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, gSettings.bMuteAudio ? m_oMuteAudioRequest : m_oUnmuteAudioRequest);
@@ -211,15 +212,16 @@ void CViewportTitleDlg::SetupViewportInformationMenu()
}
+void CViewportTitleDlg::SetupHelpersButton()
+{
+ connect(m_ui->m_helpers, &QToolButton::clicked, this, &CViewportTitleDlg::OnToggleHelpers);
+ m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
+}
+
void CViewportTitleDlg::SetupOverflowMenu()
{
// Setup the overflow menu
QMenu* overFlowMenu = new QMenu(this);
- m_debugHelpersAction = new QAction("Debug Helpers", overFlowMenu);
- m_debugHelpersAction->setCheckable(true);
- m_debugHelpersAction->setChecked(Helpers::IsHelpersShown());
- connect(m_debugHelpersAction, &QAction::triggered, this, &CViewportTitleDlg::OnToggleHelpers);
- overFlowMenu->addAction(m_debugHelpersAction);
m_audioMuteAction = new QAction("Mute Audio", overFlowMenu);
connect(m_audioMuteAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedMuteAudio);
@@ -333,7 +335,7 @@ void CViewportTitleDlg::OnMaximize()
void CViewportTitleDlg::OnToggleHelpers()
{
Helpers::ToggleHelpers();
- m_debugHelpersAction->setChecked(Helpers::IsHelpersShown());
+ m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
}
void CViewportTitleDlg::SetNoViewportInfo()
@@ -759,7 +761,7 @@ void CViewportTitleDlg::OnEditorNotifyEvent(EEditorNotifyEvent event)
switch (event)
{
case eNotify_OnDisplayRenderUpdate:
- m_debugHelpersAction->setChecked(Helpers::IsHelpersShown());
+ m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
break;
case eNotify_OnBeginGameMode:
case eNotify_OnEndGameMode:
diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.h b/Code/Sandbox/Editor/ViewportTitleDlg.h
index 255354dcbb..e4670e7873 100644
--- a/Code/Sandbox/Editor/ViewportTitleDlg.h
+++ b/Code/Sandbox/Editor/ViewportTitleDlg.h
@@ -102,6 +102,7 @@ protected:
void SetupResolutionDropdownMenu();
void SetupViewportInformationMenu();
void SetupOverflowMenu();
+ void SetupHelpersButton();
QString m_title;
@@ -172,7 +173,6 @@ protected:
QAction* m_normalInformationAction = nullptr;
QAction* m_fullInformationAction = nullptr;
QAction* m_compactInformationAction = nullptr;
- QAction* m_debugHelpersAction = nullptr;
QAction* m_audioMuteAction = nullptr;
QAction* m_enableVRAction = nullptr;
QAction* m_enableGridSnappingAction = nullptr;
diff --git a/Code/Sandbox/Editor/ViewportTitleDlg.ui b/Code/Sandbox/Editor/ViewportTitleDlg.ui
index 58b4ec70c6..7d8e9d50d4 100644
--- a/Code/Sandbox/Editor/ViewportTitleDlg.ui
+++ b/Code/Sandbox/Editor/ViewportTitleDlg.ui
@@ -81,6 +81,18 @@
+ -
+
+
+
+ :/Menu/helpers.svg:/Menu/helpers.svg
+
+
+
+ true
+
+
+
-
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
index 25b58ab544..b81d013b87 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpAnimationImporter.cpp
@@ -255,7 +255,6 @@ namespace AZ
{
return AZStd::make_pair(animation, anim);
}
-
Events::ProcessingResult AssImpAnimationImporter::ImportAnimation(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "Animation");
@@ -447,7 +446,22 @@ namespace AZ
return combinedAnimationResult.GetResult();
}
- decltype(boneAnimations) parentFillerAnimations;
+
+ AZStd::unordered_set boneList;
+
+ for (int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
+ {
+ aiMesh* mesh = scene->mMeshes[meshIndex];
+
+ for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
+ {
+ aiBone* bone = mesh->mBones[boneIndex];
+
+ boneList.insert(bone->mName.C_Str());
+ }
+ }
+
+ decltype(boneAnimations) fillerAnimations;
// 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)
@@ -459,8 +473,8 @@ namespace AZ
{
if (!IsPivotNode(parent->mName))
{
- if (boneAnimations.find(parent->mName.C_Str()) == boneAnimations.end() &&
- parentFillerAnimations.find(parent->mName.C_Str()) == parentFillerAnimations.end())
+ if (!boneAnimations.contains(parent->mName.C_Str()) &&
+ !fillerAnimations.contains(parent->mName.C_Str()))
{
// Create 1 key for each type that just copies the current transform
ConsolidatedNodeAnim emptyAnimation;
@@ -472,7 +486,7 @@ namespace AZ
globalTransform.Decompose(scale, rotation, position);
emptyAnimation.mNumRotationKeys = emptyAnimation.mNumPositionKeys = emptyAnimation.mNumScalingKeys = 1;
-
+
emptyAnimation.m_ownedPositionKeys.emplace_back(0, position);
emptyAnimation.mPositionKeys = emptyAnimation.m_ownedPositionKeys.data();
@@ -481,9 +495,9 @@ namespace AZ
emptyAnimation.m_ownedScalingKeys.emplace_back(0, scale);
emptyAnimation.mScalingKeys = emptyAnimation.m_ownedScalingKeys.data();
-
- parentFillerAnimations.insert(
- AZStd::make_pair(parent->mName.C_Str(), AZStd::make_pair(anim.second.first, AZStd::move(emptyAnimation))));
+
+ fillerAnimations.insert(AZStd::make_pair(
+ parent->mName.C_Str(), AZStd::make_pair(anim.second.first, AZStd::move(emptyAnimation))));
}
}
@@ -491,7 +505,7 @@ namespace AZ
}
}
- boneAnimations.insert(AZStd::make_move_iterator(parentFillerAnimations.begin()), AZStd::make_move_iterator(parentFillerAnimations.end()));
+ boneAnimations.insert(AZStd::make_move_iterator(fillerAnimations.begin()), AZStd::make_move_iterator(fillerAnimations.end()));
auto animItr = boneAnimations.equal_range(currentNode->mName.C_Str());
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
index 5b43941715..e726547c98 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.cpp
@@ -98,6 +98,20 @@ namespace AZ
}
}
+ aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode)
+ {
+ aiMatrix4x4 transform = {};
+ const aiNode* iteratingNode = currentNode;
+
+ while (iteratingNode)
+ {
+ transform = iteratingNode->mTransformation * transform;
+ iteratingNode = iteratingNode->mParent;
+ }
+
+ return transform;
+ }
+
Events::ProcessingResult AssImpBoneImporter::ImportBone(AssImpNodeEncounteredContext& context)
{
AZ_TraceContext("Importer", "Bone");
@@ -111,12 +125,7 @@ namespace AZ
}
bool isBone = false;
-
- if (NodeParentIsOfType(context.m_scene.GetGraph(), context.m_currentGraphPosition, DataTypes::IBoneData::TYPEINFO_Uuid()))
- {
- isBone = true;
- }
- else
+
{
AZStd::unordered_map mainBoneList;
AZStd::unordered_map boneLookup;
@@ -170,15 +179,8 @@ namespace AZ
{
createdBoneData = AZStd::make_shared();
}
-
- aiMatrix4x4 transform = currentNode->mTransformation;
- const aiNode* parent = currentNode->mParent;
- while (parent)
- {
- transform = parent->mTransformation * transform;
- parent = parent->mParent;
- }
+ aiMatrix4x4 transform = CalculateWorldTransform(currentNode);
SceneAPI::DataTypes::MatrixType globalTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(transform);
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.h b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.h
index bc4bdd474e..86069cdd9e 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.h
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpBoneImporter.h
@@ -31,7 +31,7 @@ namespace AZ
~AssImpBoneImporter() override = default;
static void Reflect(ReflectContext* context);
-
+
Events::ProcessingResult ImportBone(AssImpNodeEncounteredContext& context);
};
} // namespace FbxSceneBuilder
diff --git a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
index bcc007e3a7..f7a85a161b 100644
--- a/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
+++ b/Code/Tools/SceneAPI/FbxSceneBuilder/Importers/AssImpTransformImporter.cpp
@@ -46,8 +46,9 @@ namespace AZ
serializeContext->Class()->Version(1);
}
}
-
- void GetAllBones(const aiScene* scene, AZStd::unordered_map& boneLookup)
+
+ void GetAllBones(
+ const aiScene* scene, AZStd::unordered_multimap& boneLookup)
{
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
@@ -57,7 +58,7 @@ namespace AZ
{
const aiBone* bone = mesh->mBones[boneIndex];
- boneLookup[bone->mName.C_Str()] = bone;
+ boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
@@ -73,41 +74,53 @@ namespace AZ
return Events::ProcessingResult::Ignored;
}
- AZStd::unordered_map boneLookup;
+ AZStd::unordered_multimap boneLookup;
GetAllBones(scene, boneLookup);
auto boneIterator = boneLookup.find(currentNode->mName.C_Str());
const bool isBone = boneIterator != boneLookup.end();
-
- aiMatrix4x4 combinedTransform;
+
+ DataTypes::MatrixType localTransform;
if (isBone)
{
- auto parentNode = currentNode->mParent;
+ AZStd::vector offsets, inverseOffsets;
+ auto iteratingNode = currentNode;
- aiMatrix4x4 offsetMatrix = boneIterator->second->mOffsetMatrix;
- aiMatrix4x4 parentOffset {};
-
- auto parentBoneIterator = boneLookup.find(parentNode->mName.C_Str());
-
- if (parentNode && parentBoneIterator != boneLookup.end())
+ while (iteratingNode && boneLookup.count(iteratingNode->mName.C_Str()))
{
- const auto& parentBone = parentBoneIterator->second;
+ AZStd::string name = iteratingNode->mName.C_Str();
- parentOffset = parentBone->mOffsetMatrix;
+ auto range = boneLookup.equal_range(name);
+
+ if (range.first != range.second)
+ {
+ // There can be multiple offsetMatrices for a given bone, we're only interested in grabbing the first one
+ auto boneFirstOffsetMatrix = range.first->second->mOffsetMatrix;
+ auto azMat = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(boneFirstOffsetMatrix);
+ offsets.push_back(azMat);
+ inverseOffsets.push_back(azMat.GetInverseFull());
+ }
+
+ iteratingNode = iteratingNode->mParent;
}
-
- auto inverseOffset = offsetMatrix;
- inverseOffset.Inverse();
-
- combinedTransform = parentOffset * inverseOffset;
+
+ localTransform =
+ offsets.at(AZ::GetMin(offsets.size()-1, static_cast(1))) // parent bone offset, or if there is no parent, then current node offset
+ * inverseOffsets.at(inverseOffsets.size() - 1) // Inverse of root bone offset
+ * offsets.at(offsets.size() - 1) // Root bone offset
+ * inverseOffsets.at(0); // Inverse of current node offset
}
else
{
- combinedTransform = GetConcatenatedLocalTransform(currentNode);
+ localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(currentNode));
}
- DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
+ // Don't bother adding a node with the identity matrix
+ if (localTransform == DataTypes::MatrixType::Identity())
+ {
+ return Events::ProcessingResult::Ignored;
+ }
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp
index a895c04b95..551b561200 100644
--- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp
+++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp
@@ -173,7 +173,7 @@ namespace AZ
{
if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type"))
{
- // Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types
+ // Requiring explicit type info to differentiate between colors versus vectors and numeric types
const AZ::Uuid baseTypeId = azrtti_typeid();
AZ::Uuid typeId = AZ::Uuid::CreateNull();
result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId));
@@ -198,7 +198,7 @@ namespace AZ
{
outputPropertyValue.SetObject();
- // Storing explicit type info to differentiate be=tween colors versus vectors and numeric types
+ // Storing explicit type info to differentiate between colors versus vectors and numeric types
rapidjson::Value typeValue;
result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context));
outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator());
diff --git a/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h b/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h
index d08acd0b17..1937341cd2 100644
--- a/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h
+++ b/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h
@@ -14,6 +14,7 @@
// AZ
#include
+#include
#include
// Graph Model
diff --git a/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h b/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h
index 50c1287080..f3dd85575f 100644
--- a/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h
+++ b/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h
@@ -14,10 +14,10 @@
// AZ
#include
#include
-#include
#include
// Graph Model
+#include
#include
#include
@@ -136,9 +136,9 @@ namespace GraphModel
//! Set/gets a bundle of generic metadata that is provided by the node graph UI
//! system. This may include node positions, comment blocks, node groupings, and
//! bookmarks, for example.
- void SetUiMetadata(const AZStd::any& uiMetadata);
- const AZStd::any& GetUiMetadata() const;
- AZStd::any& GetUiMetadata();
+ void SetUiMetadata(const GraphModelIntegration::GraphCanvasMetadata& uiMetadata);
+ const GraphModelIntegration::GraphCanvasMetadata& GetUiMetadata() const;
+ GraphModelIntegration::GraphCanvasMetadata& GetUiMetadata();
AZStd::shared_ptr FindSlot(const Endpoint& endpoint);
@@ -157,7 +157,7 @@ namespace GraphModel
ConnectionList m_connections;
//! Used to store and serialize metadata from the graph UI, like node positions, comments, group boxes, etc.
- AZStd::any m_uiMetadata;
+ GraphModelIntegration::GraphCanvasMetadata m_uiMetadata;
//! Used to store all of our node <-> wrapper node mappings
NodeWrappingMap m_nodeWrappings;
diff --git a/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h b/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h
index 0ce02ffa9d..0b012fd2cd 100644
--- a/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h
+++ b/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h
@@ -12,6 +12,7 @@
#pragma once
// AZ
+#include
#include
#include
#include
@@ -165,6 +166,32 @@ namespace GraphModel
ExtendableSlotConfiguration m_extendableSlotConfiguration;
};
+ //! Custom JSON serializer for Slot because we use an AZStd::any for m_value
+ class JsonSlotSerializer
+ : public AZ::BaseJsonSerializer
+ {
+ public:
+ AZ_RTTI(JsonSlotSerializer, "{8AC96D70-7BCD-4D68-8813-269938982D51}", AZ::BaseJsonSerializer);
+ AZ_CLASS_ALLOCATOR(JsonSlotSerializer, AZ::SystemAllocator, 0);
+
+ AZ::JsonSerializationResult::Result Load(
+ void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
+ AZ::JsonDeserializerContext& context) override;
+
+ AZ::JsonSerializationResult::Result Store(
+ rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const AZ::Uuid& valueTypeId,
+ AZ::JsonSerializerContext& context) override;
+
+ private:
+ template
+ bool LoadAny(
+ AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
+ AZ::JsonSerializationResult::ResultCode& result);
+ template
+ bool StoreAny(
+ const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
+ AZ::JsonSerializationResult::ResultCode& result);
+ };
//!!! Start in Graph.h for high level GraphModel documentation !!!
@@ -180,6 +207,7 @@ namespace GraphModel
class Slot : public GraphElement, public AZStd::enable_shared_from_this
{
friend class Graph; // So the Graph can update the Slot's cache of Connection pointers
+ friend class JsonSlotSerializer; // So we can set the m_value and m_subId directly from the serializer
public:
AZ_CLASS_ALLOCATOR(Slot, AZ::SystemAllocator, 0);
diff --git a/Gems/GraphModel/Code/Source/Integration/GraphController.cpp b/Gems/GraphModel/Code/Source/Integration/GraphController.cpp
index f6ce6282f4..bb5291637e 100644
--- a/Gems/GraphModel/Code/Source/Integration/GraphController.cpp
+++ b/Gems/GraphModel/Code/Source/Integration/GraphController.cpp
@@ -1338,11 +1338,7 @@ namespace GraphModelIntegration
GraphCanvasMetadata* GraphController::GetGraphMetadata()
{
- if (!m_graph->GetUiMetadata().is())
- {
- m_graph->SetUiMetadata(AZStd::any(GraphCanvasMetadata()));
- }
- GraphCanvasMetadata* graphCanvasMetadata = AZStd::any_cast(&m_graph->GetUiMetadata());
+ GraphCanvasMetadata* graphCanvasMetadata = &m_graph->GetUiMetadata();
AZ_Assert(graphCanvasMetadata, "GraphCanvasMetadata not initialized");
return graphCanvasMetadata;
}
diff --git a/Gems/GraphModel/Code/Source/Model/Graph.cpp b/Gems/GraphModel/Code/Source/Model/Graph.cpp
index 809679e9f6..e82b5d8e15 100644
--- a/Gems/GraphModel/Code/Source/Model/Graph.cpp
+++ b/Gems/GraphModel/Code/Source/Model/Graph.cpp
@@ -37,7 +37,7 @@ namespace GraphModel
if (serializeContext)
{
serializeContext->Class()
- ->Version(1)
+ ->Version(2)
->Field("m_nodes", &Graph::m_nodes)
->Field("m_connections", &Graph::m_connections)
->Field("m_uiMetadata", &Graph::m_uiMetadata)
@@ -312,19 +312,19 @@ namespace GraphModel
}
- void Graph::SetUiMetadata(const AZStd::any& uiMetadata)
+ void Graph::SetUiMetadata(const GraphModelIntegration::GraphCanvasMetadata& uiMetadata)
{
m_uiMetadata = uiMetadata;
}
- const AZStd::any& Graph::GetUiMetadata() const
+ const GraphModelIntegration::GraphCanvasMetadata& Graph::GetUiMetadata() const
{
return m_uiMetadata;
}
- AZStd::any& Graph::GetUiMetadata()
+ GraphModelIntegration::GraphCanvasMetadata& Graph::GetUiMetadata()
{
return m_uiMetadata;
}
diff --git a/Gems/GraphModel/Code/Source/Model/Slot.cpp b/Gems/GraphModel/Code/Source/Model/Slot.cpp
index e5fb9f8cb8..6433129af8 100644
--- a/Gems/GraphModel/Code/Source/Model/Slot.cpp
+++ b/Gems/GraphModel/Code/Source/Model/Slot.cpp
@@ -11,9 +11,14 @@
*/
// AZ
+#include
+#include
+#include
+#include
#include
#include
#include
+#include
#include
// Graph Model
@@ -294,13 +299,164 @@ namespace GraphModel
/////////////////////////////////////////////////////////
// Slot
+ AZ::JsonSerializationResult::Result JsonSlotSerializer::Load(
+ void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
+ AZ::JsonDeserializerContext& context)
+ {
+ namespace JSR = AZ::JsonSerializationResult;
+
+ AZ_Assert(
+ azrtti_typeid() == outputValueTypeId,
+ "Unable to deserialize Slot from json because the provided type is %s.",
+ outputValueTypeId.ToString().c_str());
+
+ Slot* slot = reinterpret_cast(outputValue);
+ AZ_Assert(slot, "Output value for JsonSlotSerializer can't be null.");
+
+ JSR::ResultCode result(JSR::Tasks::ReadField);
+
+ auto serializedSlotValue = inputValue.FindMember("m_value");
+ if (serializedSlotValue != inputValue.MemberEnd())
+ {
+ AZStd::any slotValue;
+ if (LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result) ||
+ LoadAny(slotValue, serializedSlotValue->value, context, result))
+ {
+ slot->m_value = slotValue;
+ }
+ }
+
+ // Load m_subId normally because it's just an int
+ {
+ SlotSubId slotSubId = 0;
+ result.Combine(ContinueLoadingFromJsonObjectField(
+ &slotSubId, azrtti_typeid(), inputValue,
+ "m_subId", context));
+ slot->m_subId = slotSubId;
+ }
+
+ return context.Report(
+ result,
+ result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded Slot information."
+ : "Failed to load Slot information.");
+ }
+
+ AZ::JsonSerializationResult::Result JsonSlotSerializer::Store(
+ rapidjson::Value& outputValue, const void* inputValue, [[maybe_unused]] const void* defaultValue, const AZ::Uuid& valueTypeId,
+ AZ::JsonSerializerContext& context)
+ {
+ namespace JSR = AZ::JsonSerializationResult;
+
+ AZ_Assert(
+ azrtti_typeid() == valueTypeId,
+ "Unable to Serialize Slot because the provided type is %s.", valueTypeId.ToString().c_str());
+
+ const Slot* slot = reinterpret_cast(inputValue);
+ AZ_Assert(slot, "Input value for JsonSlotSerializer can't be null.");
+
+ outputValue.SetObject();
+
+ JSR::ResultCode result(JSR::Tasks::WriteValue);
+
+ {
+ AZ::ScopedContextPath subPathPropertyOverrides(context, "m_value");
+
+ if (!slot->m_value.empty())
+ {
+ rapidjson::Value outputPropertyValue;
+ if (StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result) ||
+ StoreAny(slot->m_value, outputPropertyValue, context, result))
+ {
+ outputValue.AddMember("m_value", outputPropertyValue, context.GetJsonAllocator());
+ }
+ }
+ }
+
+ {
+ AZ::ScopedContextPath subSlotId(context, "m_subId");
+ SlotSubId defaultSubId = 0;
+
+ result.Combine(ContinueStoringToJsonObjectField(
+ outputValue, "m_subId", &slot->m_subId, &defaultSubId,
+ azrtti_typeid(), context));
+ }
+
+ return context.Report(
+ result,
+ result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information."
+ : "Failed to store MaterialAssignment information.");
+ }
+
+ template
+ bool JsonSlotSerializer::LoadAny(
+ AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context,
+ AZ::JsonSerializationResult::ResultCode& result)
+ {
+ auto valueItr = inputPropertyValue.FindMember("Value");
+ auto typeItr = inputPropertyValue.FindMember("$type");
+ if ((valueItr != inputPropertyValue.MemberEnd()) && (typeItr != inputPropertyValue.MemberEnd()))
+ {
+ // Requiring explicit type info to differentiate between colors versus vectors and numeric types
+ const AZ::Uuid baseTypeId = azrtti_typeid();
+ AZ::Uuid typeId = AZ::Uuid::CreateNull();
+ result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId));
+
+ if (typeId == azrtti_typeid())
+ {
+ T value;
+ result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid(), inputPropertyValue, "Value", context));
+ propertyValue = value;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template
+ bool JsonSlotSerializer::StoreAny(
+ const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context,
+ AZ::JsonSerializationResult::ResultCode& result)
+ {
+ if (propertyValue.is())
+ {
+ outputPropertyValue.SetObject();
+
+ // Storing explicit type info to differentiate between colors versus vectors and numeric types
+ rapidjson::Value typeValue;
+ result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context));
+ outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator());
+
+ T value = AZStd::any_cast(propertyValue);
+ result.Combine(
+ ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid(), context));
+ return true;
+ }
+ return false;
+ }
+
void Slot::Reflect(AZ::ReflectContext* context)
{
- AZ::SerializeContext* serializeContext = azrtti_cast(context);
- if (serializeContext)
+ if (auto jsonContext = azrtti_cast(context))
+ {
+ jsonContext->Serializer()->HandlesType();
+ }
+
+ if (auto serializeContext = azrtti_cast(context))
{
serializeContext->Class()
- ->Version(0)
+ ->Version(1)
->Field("m_value", &Slot::m_value)
->Field("m_subId", &Slot::m_subId)
// m_slotDescription is not reflected because that data is populated procedurally by each node
diff --git a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp
index 3879cd2597..2f3a7d7ab4 100644
--- a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp
+++ b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp
@@ -460,10 +460,10 @@ namespace LandscapeCanvasEditor
GraphCanvas::StyleManagerRequestBus::Event(editorId, &GraphCanvas::StyleManagerRequests::RegisterDataPaletteStyle, LandscapeCanvas::AreaTypeId, "VegetationAreaDataColorPalette");
LandscapeCanvas::LandscapeCanvasRequestBus::Handler::BusConnect();
- AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorPickModeNotificationBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
AzToolsFramework::EntityCompositionNotificationBus::Handler::BusConnect();
AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusConnect();
+ AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
CrySystemEventBus::Handler::BusConnect();
AZ::EntitySystemBus::Handler::BusConnect();
@@ -480,6 +480,7 @@ namespace LandscapeCanvasEditor
{
AZ::EntitySystemBus::Handler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
+ AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorPickModeNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
@@ -883,6 +884,10 @@ namespace LandscapeCanvasEditor
if (landscapeCanvasComponent)
{
landscapeCanvasComponent->m_graph = *m_serializeContext->CloneObject(graph.get());
+
+ // Mark the Landscape Canvas entity as dirty so the changes to the graph will be picked up on the next save
+ AzToolsFramework::ScopedUndoBatch undo("Update Landscape Canvas Graph");
+ AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, rootEntityId);
}
}
}
@@ -1572,7 +1577,7 @@ namespace LandscapeCanvasEditor
void MainWindow::HandleEditorEntityCreated(const AZ::EntityId& entityId, GraphCanvas::GraphId graphId)
{
- if (m_ignoreGraphUpdates)
+ if (m_ignoreGraphUpdates || m_prefabPropagationInProgress)
{
return;
}
@@ -1622,6 +1627,11 @@ namespace LandscapeCanvasEditor
void MainWindow::OnEditorEntityDeleted(const AZ::EntityId& entityId)
{
+ if (m_prefabPropagationInProgress)
+ {
+ return;
+ }
+
m_queuedEntityDeletes.push_back(entityId);
QTimer::singleShot(0, [this, entityId]() {
@@ -2456,6 +2466,11 @@ namespace LandscapeCanvasEditor
void MainWindow::EntityParentChanged(AZ::EntityId entityId, AZ::EntityId newParentId, AZ::EntityId oldParentId)
{
+ if (m_prefabPropagationInProgress)
+ {
+ return;
+ }
+
GraphCanvas::GraphId oldGraphId = FindGraphContainingEntity(oldParentId);
GraphCanvas::GraphId newGraphId = FindGraphContainingEntity(newParentId);
@@ -2482,6 +2497,20 @@ namespace LandscapeCanvasEditor
}
}
+ void MainWindow::OnPrefabInstancePropagationBegin()
+ {
+ // Ignore graph updates during prefab propagation because the entities will be
+ // deleted and re-created, which would inadvertantly trigger our logic to close
+ // the graph when the corresponding entity is deleted.
+ m_prefabPropagationInProgress = true;
+ }
+
+ void MainWindow::OnPrefabInstancePropagationEnd()
+ {
+ // See comment above in OnPrefabInstancePropagationBegin
+ m_prefabPropagationInProgress = false;
+ }
+
void MainWindow::OnCryEditorEndCreate()
{
UpdateGraphEnabled();
@@ -2490,6 +2519,15 @@ namespace LandscapeCanvasEditor
void MainWindow::OnCryEditorEndLoad()
{
UpdateGraphEnabled();
+
+ AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
+ }
+
+ void MainWindow::OnCryEditorCloseScene()
+ {
+ UpdateGraphEnabled();
+
+ AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
}
void MainWindow::OnCryEditorSceneClosed()
diff --git a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h
index eddf998d8d..e6887eecfd 100644
--- a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h
+++ b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
#include
@@ -84,6 +85,7 @@ namespace LandscapeCanvasEditor
, private AzToolsFramework::EntityCompositionNotificationBus::Handler
, private AzToolsFramework::PropertyEditorEntityChangeNotificationBus::MultiHandler
, private AzToolsFramework::ToolsApplicationNotificationBus::Handler
+ , private AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
, private CrySystemEventBus::Handler
{
Q_OBJECT
@@ -183,10 +185,15 @@ namespace LandscapeCanvasEditor
void EntityParentChanged(AZ::EntityId entityId, AZ::EntityId newParentId, AZ::EntityId oldParentId) override;
////////////////////////////////////////////////////////////////////////
+ //! PrefabPublicNotificationBus overrides
+ void OnPrefabInstancePropagationBegin() override;
+ void OnPrefabInstancePropagationEnd() override;
+
////////////////////////////////////////////////////////////////////////
// CrySystemEventBus overrides
void OnCryEditorEndCreate() override;
void OnCryEditorEndLoad() override;
+ void OnCryEditorCloseScene() override;
void OnCryEditorSceneClosed() override;
////////////////////////////////////////////////////////////////////////
@@ -246,6 +253,7 @@ namespace LandscapeCanvasEditor
AZ::SerializeContext* m_serializeContext = nullptr;
bool m_ignoreGraphUpdates = false;
+ bool m_prefabPropagationInProgress = false;
bool m_inObjectPickMode = false;
using DeletedNodePositionsMap = AZStd::unordered_map;