Browse Source

Merge remote-tracking branch 'upstream/stabilization/2305' into Prism/remote-gem-versions

Signed-off-by: Alex Peterson <[email protected]>

# Conflicts:
#	engine.json
Alex Peterson 2 years ago
parent
commit
15ab95c73c
25 changed files with 844 additions and 68 deletions
  1. 5 0
      Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexUVData.cpp
  2. 2 0
      Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexUVData.h
  3. 5 0
      Code/Tools/SceneAPI/SceneData/ManifestMetaInfoHandler.cpp
  4. 2 0
      Code/Tools/SceneAPI/SceneData/ReflectionRegistrar.cpp
  5. 129 0
      Code/Tools/SceneAPI/SceneData/Rules/UVsRule.cpp
  6. 73 0
      Code/Tools/SceneAPI/SceneData/Rules/UVsRule.h
  7. 2 0
      Code/Tools/SceneAPI/SceneData/SceneData_files.cmake
  8. 2 1
      Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore
  9. 0 10
      Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/imgui.ini
  10. 0 10
      Gems/AtomLyIntegration/TechnicalArt/imgui.ini
  11. 6 0
      Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentPreExportComponent.cpp
  12. 5 0
      Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentPreExportComponent.h
  13. 304 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerateComponent.cpp
  14. 54 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerateComponent.h
  15. 54 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.cpp
  16. 22 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.h
  17. 63 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsPreExportComponent.cpp
  18. 23 0
      Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsPreExportComponent.h
  19. 4 0
      Gems/SceneProcessing/Code/Source/SceneProcessingModule.cpp
  20. 6 0
      Gems/SceneProcessing/Code/sceneprocessing_editor_static_files.cmake
  21. 46 38
      Registry/sceneassetimporter.setreg
  22. 17 1
      cmake/Platform/Common/Install_common.cmake
  23. 11 1
      cmake/Subdirectories.cmake
  24. 1 0
      cmake/install/engine.json.in
  25. 8 7
      engine.json

+ 5 - 0
Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexUVData.cpp

@@ -75,6 +75,11 @@ namespace AZ
                 m_uvs.reserve(size);
             }
 
+            void MeshVertexUVData::Clear()
+            {
+                m_uvs.clear();
+            }
+
             void MeshVertexUVData::AppendUV(const AZ::Vector2& uv)
             {
                 m_uvs.push_back(uv);

+ 2 - 0
Code/Tools/SceneAPI/SceneData/GraphData/MeshVertexUVData.h

@@ -43,6 +43,8 @@ namespace AZ
                 SCENE_DATA_API void ReserveContainerSpace(size_t size);
                 SCENE_DATA_API void AppendUV(const AZ::Vector2& uv);
 
+                SCENE_DATA_API void Clear();
+
                 SCENE_DATA_API void GetDebugOutput(AZ::SceneAPI::Utilities::DebugOutput& output) const override;
             protected:
                 AZStd::vector<AZ::Vector2> m_uvs;

+ 5 - 0
Code/Tools/SceneAPI/SceneData/ManifestMetaInfoHandler.cpp

@@ -28,6 +28,7 @@
 #include <SceneAPI/SceneData/Rules/SkinMeshAdvancedRule.h>
 #include <SceneAPI/SceneData/Rules/SkinRule.h>
 #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
+#include <SceneAPI/SceneData/Rules/UVsRule.h>
 
 namespace AZ
 {
@@ -85,6 +86,10 @@ namespace AZ
                     {
                         modifiers.push_back(azrtti_typeid<CoordinateSystemRule>());
                     }
+                    if (existingRules.find(SceneData::UVsRule::TYPEINFO_Uuid()) == existingRules.end())
+                    {
+                        modifiers.push_back(SceneData::UVsRule::TYPEINFO_Uuid());
+                    }
                     if (existingRules.find(SceneData::TangentsRule::TYPEINFO_Uuid()) == existingRules.end())
                     {
                         modifiers.push_back(SceneData::TangentsRule::TYPEINFO_Uuid());

+ 2 - 0
Code/Tools/SceneAPI/SceneData/ReflectionRegistrar.cpp

@@ -21,6 +21,7 @@
 #include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
 #include <SceneAPI/SceneData/Rules/SkeletonProxyRule.h>
 #include <SceneAPI/SceneData/Rules/TangentsRule.h>
+#include <SceneAPI/SceneData/Rules/UVsRule.h>
 #include <SceneAPI/SceneData/Rules/CoordinateSystemRule.h>
 
 #include <SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h>
@@ -74,6 +75,7 @@ namespace AZ
             SceneData::SkeletonProxyRule::Reflect(context);
             SceneData::SkinMeshAdvancedRule::Reflect(context);
             SceneData::TangentsRule::Reflect(context);
+            SceneData::UVsRule::Reflect(context);
             SceneData::CoordinateSystemRule::Reflect(context);
 
             // Utility

+ 129 - 0
Code/Tools/SceneAPI/SceneData/Rules/UVsRule.cpp

@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <SceneAPI/SceneData/Rules/UVsRule.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <SceneAPI/SceneCore/Utilities/Reporting.h>
+#include <AzCore/Settings/SettingsRegistry.h>
+
+namespace AZ
+{
+    namespace SceneAPI
+    {
+        namespace SceneData
+        {
+            static constexpr AZStd::string_view DefaultUVsGenerationMethodKeyIfNoRulePresent{
+                "/O3DE/SceneAPI/UVsGenerateComponent/DefaultGenerationMethodIfNoRulePresent"
+            };
+
+            static constexpr AZStd::string_view DefaultUVsGenerationMethodKeyWhenAddingNewRules{
+                "/O3DE/SceneAPI/UVsGenerateComponent/DefaultGenerationMethodWhenRuleIsPresent"
+            };
+
+
+            UVsRule::UVsRule()
+                : DataTypes::IRule()
+            {
+                m_generationMethod = GetDefaultGenerationMethodWhenAddingNewRule();
+            }
+
+            AZ::SceneAPI::DataTypes::UVsGenerationMethod GetGenerationMethodFromRegistry(
+                AZStd::string_view regKey, AZ::SceneAPI::DataTypes::UVsGenerationMethod defaultValue)
+            {
+                if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
+                {
+                    AZStd::string stringFromRegistry;
+                    if (settingsRegistry->Get(stringFromRegistry, regKey))
+                    {
+                        const bool isCaseSensitive = false;
+                        if (AZ::StringFunc::Equal(stringFromRegistry, "LeaveSceneDataAsIs", isCaseSensitive))
+                        {
+                            return AZ::SceneAPI::DataTypes::UVsGenerationMethod::LeaveSceneDataAsIs;
+                        }
+                        else if (AZ::StringFunc::Equal(stringFromRegistry, "SphericalProjection", isCaseSensitive))
+                        {
+                            return AZ::SceneAPI::DataTypes::UVsGenerationMethod::SphericalProjection;
+                        }
+                        else
+                        {
+                            AZ_Warning(
+                                AZ::SceneAPI::Utilities::WarningWindow,
+                                false,
+                                "'%s' is not a valid default UV generation method. Check the value of " AZ_STRING_FORMAT " in your "
+                                "settings registry, and change "
+                                "it to 'LeaveSceneDataAsIs' or 'SphericalProjection'",
+                                stringFromRegistry.c_str(),
+                                AZ_STRING_ARG(regKey));
+                        }
+                    }
+                }
+                return defaultValue;
+            }
+
+            //! return the default method for when a new rule is explicitly created by script or user
+            AZ::SceneAPI::DataTypes::UVsGenerationMethod UVsRule::GetDefaultGenerationMethodWhenAddingNewRule()
+            {
+                // When someone goes to the effort of actually adding a new rule, make the default actually do something
+                return GetGenerationMethodFromRegistry(DefaultUVsGenerationMethodKeyWhenAddingNewRules,
+                    AZ::SceneAPI::DataTypes::UVsGenerationMethod::SphericalProjection);
+            }
+
+            AZ::SceneAPI::DataTypes::UVsGenerationMethod UVsRule::GetDefaultGenerationMethodWithNoRule()
+            {
+                // when there is no rule on the mesh, do nothing by default
+                return GetGenerationMethodFromRegistry(DefaultUVsGenerationMethodKeyIfNoRulePresent,
+                    AZ::SceneAPI::DataTypes::UVsGenerationMethod::LeaveSceneDataAsIs);
+            }
+
+            AZ::SceneAPI::DataTypes::UVsGenerationMethod UVsRule::GetGenerationMethod() const
+            {
+                return m_generationMethod;
+            }
+
+            bool UVsRule::GetReplaceExisting() const
+            {
+                return m_replaceExisting;
+            }
+
+            void UVsRule::Reflect(AZ::ReflectContext* context)
+            {
+                AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+                if (!serializeContext)
+                {
+                    return;
+                }
+
+                serializeContext->Class<UVsRule, DataTypes::IRule>()
+                    ->Version(1)
+                    ->Field("generationMethod", &UVsRule::m_generationMethod)
+                    ->Field("replaceExisting", &UVsRule::m_replaceExisting);
+
+                AZ::EditContext* editContext = serializeContext->GetEditContext();
+                if (editContext)
+                {
+                    editContext->Class<UVsRule>("UVs", "Specify how UVs are imported or generated.")
+                        ->ClassElement(Edit::ClassElements::EditorData, "")
+                        ->Attribute("AutoExpand", true)
+                        ->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
+                        ->DataElement(
+                            AZ::Edit::UIHandlers::ComboBox,
+                            &AZ::SceneAPI::SceneData::UVsRule::m_generationMethod,
+                            "Generation Method",
+                            "Specify the UVs generation method when UVs are generated.")
+                        ->EnumAttribute(AZ::SceneAPI::DataTypes::UVsGenerationMethod::LeaveSceneDataAsIs, "Do not generate UVs")
+                        ->EnumAttribute(AZ::SceneAPI::DataTypes::UVsGenerationMethod::SphericalProjection, "Spherical Projection")
+                        ->DataElement(0, &AZ::SceneAPI::SceneData::UVsRule::m_replaceExisting,
+                            "Replace existing UVs",
+                            "If true, will replace UVs in the source scene even if present in the incoming data.")
+                    ;
+                }
+            }
+        } // SceneData
+    } // SceneAPI
+} // AZ

+ 73 - 0
Code/Tools/SceneAPI/SceneData/Rules/UVsRule.h

@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <SceneAPI/SceneCore/DataTypes/Rules/IRule.h>
+#include <SceneAPI/SceneData/SceneDataConfiguration.h>
+
+namespace AZ
+{
+    class ReflectContext;
+
+    namespace SceneAPI
+    {
+        namespace Containers
+        {
+            class Scene;
+        }
+
+        namespace DataTypes
+        {
+            class IMeshVertexUVData;
+            enum class UVsGenerationMethod
+            {
+                LeaveSceneDataAsIs = 0, //! don't do anything to the scene
+                SphericalProjection = 1 //! generate UVs using simple spherical positional projection
+            };
+        }
+
+        namespace SceneData
+        {
+            //! The UVsRule class contains the settings for one particular instance of the "Generate UVs" modifier
+            //! on one particular mesh group in the scene.
+            class SCENE_DATA_CLASS UVsRule
+                : public DataTypes::IRule
+            {
+            public:
+                AZ_RTTI(UVsRule, "{79FB186C-E9B2-4569-9172-84B85DF81DB9}", DataTypes::IRule);
+                AZ_CLASS_ALLOCATOR(UVsRule, AZ::SystemAllocator)
+
+                SCENE_DATA_API UVsRule();
+                SCENE_DATA_API ~UVsRule() override = default;
+
+                SCENE_DATA_API AZ::SceneAPI::DataTypes::UVsGenerationMethod GetGenerationMethod() const;
+                SCENE_DATA_API bool GetReplaceExisting() const;
+
+                static void Reflect(ReflectContext* context);
+
+                // it can be useful to have a different default for when there is no rule ("do nothing" for example)
+                // versus if the user actually clicks a button or something to cause a rule to exist now, ie, actually do something
+                // useful.
+
+                //! Return the default method for UV Generation when a Generate UVs rule is attached as a modifier to a mesh group.
+                SCENE_DATA_API static AZ::SceneAPI::DataTypes::UVsGenerationMethod GetDefaultGenerationMethodWhenAddingNewRule();
+
+                //! Return the default method for when there is no Generate UVs rule attached to the mesh group.
+                //! this should probably be left as "do nothing" unless you want to auto-generate UVs for everything without UVs
+                SCENE_DATA_API static AZ::SceneAPI::DataTypes::UVsGenerationMethod GetDefaultGenerationMethodWithNoRule();
+
+            protected:
+                AZ::SceneAPI::DataTypes::UVsGenerationMethod m_generationMethod = AZ::SceneAPI::DataTypes::UVsGenerationMethod::LeaveSceneDataAsIs;
+                bool m_replaceExisting = false;
+
+            };
+        } // SceneData
+    } // SceneAPI
+} // AZ

+ 2 - 0
Code/Tools/SceneAPI/SceneData/SceneData_files.cmake

@@ -67,6 +67,8 @@ set(FILES
     Rules/SkinRule.cpp
     Rules/TangentsRule.h
     Rules/TangentsRule.cpp
+    Rules/UVsRule.h
+    Rules/UVsRule.cpp
     Rules/UnmodifiableRule.h
     Rules/UnmodifiableRule.cpp
     GraphData/CustomPropertyData.h

+ 2 - 1
Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore

@@ -12,4 +12,5 @@ settings.local.json
 azpy/_sample_package_/*
 .env
 settings_export.json.tmp
-Env_Dev.bat
+Env_Dev.bat
+imgui.ini

+ 0 - 10
Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/imgui.ini

@@ -1,10 +0,0 @@
-[Window][Debug##Default]
-Pos=60,60
-Size=400,400
-Collapsed=0
-
-[Window][Debug Console]
-Pos=60,60
-Size=640,480
-Collapsed=0
-

+ 0 - 10
Gems/AtomLyIntegration/TechnicalArt/imgui.ini

@@ -1,10 +0,0 @@
-[Window][Debug##Default]
-Pos=60,60
-Size=400,400
-Collapsed=0
-
-[Window][Debug Console]
-Pos=60,60
-Size=640,480
-Collapsed=0
-

+ 6 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentPreExportComponent.cpp

@@ -21,6 +21,12 @@ namespace AZ::SceneGenerationComponents
         BindToCall(&TangentPreExportComponent::Register);
     }
 
+    uint8_t TangentPreExportComponent::GetPriority() const
+    {
+        return AZ::SceneAPI::Events::CallProcessor::ProcessingPriority::LateProcessing;
+    }
+
+
     void TangentPreExportComponent::Reflect(AZ::ReflectContext* context)
     {
         AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);

+ 5 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/TangentGenerator/TangentPreExportComponent.h

@@ -28,6 +28,11 @@ namespace AZ::SceneGenerationComponents
 
         static void Reflect(AZ::ReflectContext* context);
 
+        // bumps Tangent export to later on in the generation phase, so that it can generate tangents after other rules have
+        // generated things like normals and UVs.
+
+        uint8_t GetPriority() const override;
+
         AZ::SceneAPI::Events::ProcessingResult Register(AZ::SceneAPI::Events::GenerateAdditionEventContext& context);
     };
 } // namespace AZ::SceneGenerationComponents

+ 304 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerateComponent.cpp

@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Generation/Components/UVsGenerator/UVsGenerateComponent.h>
+#include <Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.h>
+
+#include <AzCore/std/smart_ptr/make_shared.h>
+
+#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
+#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
+#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
+#include <SceneAPI/SceneCore/Containers/Views/SceneGraphChildIterator.h>
+#include <SceneAPI/SceneCore/DataTypes/DataTypeUtilities.h>
+#include <SceneAPI/SceneCore/DataTypes/Groups/IGroup.h>
+#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
+#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshVertexUVData.h>
+#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
+#include <SceneAPI/SceneData/Rules/UVsRule.h>
+#include <SceneAPI/SceneCore/Utilities/Reporting.h>
+
+namespace AZ::SceneGenerationComponents
+{
+    //! Check whether UVs are to be generated, and if so, generate them.
+    class UVsGenerateComponent : public AZ::SceneAPI::SceneCore::GenerationComponent
+    {
+    public:
+        AZ_COMPONENT(UVsGenerateComponent, s_UVsGenerateComponentTypeId, SceneAPI::SceneCore::GenerationComponent);
+
+        UVsGenerateComponent();
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        // Invoked by the CallProcessorBinder flow.  This is essentially the entry point for this operation.
+        AZ::SceneAPI::Events::ProcessingResult GenerateUVsData(UVsGenerateContext& context);
+
+    private:
+        bool GenerateUVsForMesh(
+            AZ::SceneAPI::Containers::Scene& scene,
+            const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex,
+            AZ::SceneAPI::DataTypes::IMeshData* meshData,
+            const AZ::SceneAPI::DataTypes::UVsGenerationMethod generationMethod,
+            const bool replaceExisting);
+
+        //! How many UV Sets already exist on the mesh?
+        size_t CalcUvSetCount(
+            AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex) const;
+
+        //! find the Nth UV Set on the mesh and return it.
+        AZ::SceneAPI::DataTypes::IMeshVertexUVData* FindUvData(
+            AZ::SceneAPI::Containers::SceneGraph& graph,
+            const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex,
+            AZ::u64 uvSet) const;
+
+        //! Return the UV Rule (the modifier on the mesh group) or nullptr if no such modifier is applied.
+        const AZ::SceneAPI::SceneData::UVsRule* GetUVsRule(const AZ::SceneAPI::Containers::Scene& scene) const;
+
+        //! Create a new UV set and hook it into the scene graph
+        bool CreateUVsLayer(
+            AZ::SceneAPI::Containers::SceneManifest& manifest,
+            const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex,
+            AZ::SceneAPI::Containers::SceneGraph& graph,
+            SceneData::GraphData::MeshVertexUVData** outUVsData);
+    };
+
+    AZ::ComponentDescriptor* CreateUVsGenerateComponentDescriptor()
+    {
+        return UVsGenerateComponent::CreateDescriptor();
+    }
+
+    UVsGenerateComponent::UVsGenerateComponent()
+    {
+        BindToCall(&UVsGenerateComponent::GenerateUVsData);
+    }
+
+    void UVsGenerateComponent::Reflect(AZ::ReflectContext* context)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serializeContext)
+        {
+            serializeContext->Class<UVsGenerateComponent, AZ::SceneAPI::SceneCore::GenerationComponent>()->Version(1);
+        }
+    }
+
+    const AZ::SceneAPI::SceneData::UVsRule* UVsGenerateComponent::GetUVsRule(const AZ::SceneAPI::Containers::Scene& scene) const
+    {
+        for (const auto& object : scene.GetManifest().GetValueStorage())
+        {
+            if (object->RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IGroup::TYPEINFO_Uuid()))
+            {
+                const AZ::SceneAPI::DataTypes::IGroup* group = azrtti_cast<const AZ::SceneAPI::DataTypes::IGroup*>(object.get());
+                const AZ::SceneAPI::SceneData::UVsRule* rule = group->GetRuleContainerConst().FindFirstByType<AZ::SceneAPI::SceneData::UVsRule>().get();
+                if (rule)
+                {
+                    return rule;
+                }
+            }
+        }
+
+        return nullptr;
+    }
+
+    AZ::SceneAPI::Events::ProcessingResult UVsGenerateComponent::GenerateUVsData(UVsGenerateContext& context)
+    {
+        // this component runs regardless of what modifiers are present on the mesh.
+        // Set some defaults to use if no settings are present at all:
+        AZ::SceneAPI::DataTypes::UVsGenerationMethod defaultGenerationMethod =
+            AZ::SceneAPI::SceneData::UVsRule::GetDefaultGenerationMethodWithNoRule();
+        bool defaultReplaceExisting = false;
+
+        const AZ::SceneAPI::SceneData::UVsRule* uvsRule = GetUVsRule(context.GetScene());
+        const AZ::SceneAPI::DataTypes::UVsGenerationMethod generationMethod = uvsRule ? uvsRule->GetGenerationMethod() : defaultGenerationMethod;
+        const bool replaceExisting = uvsRule ? uvsRule->GetReplaceExisting() : defaultReplaceExisting;
+
+        if (generationMethod == SceneAPI::DataTypes::UVsGenerationMethod::LeaveSceneDataAsIs)
+        {
+            // no point in going any further if the rule basically says to leave scene data as is
+            return AZ::SceneAPI::Events::ProcessingResult::Success;
+        }
+
+        // Iterate over all graph content and filter out all meshes.
+        AZ::SceneAPI::Containers::SceneGraph& graph = context.GetScene().GetGraph();
+        AZ::SceneAPI::Containers::SceneGraph::ContentStorageData graphContent = graph.GetContentStorage();
+
+        // Build a list of mesh data nodes.
+        AZStd::vector<AZStd::pair<AZ::SceneAPI::DataTypes::IMeshData*, AZ::SceneAPI::Containers::SceneGraph::NodeIndex> > meshes;
+        for (auto item = graphContent.begin(); item != graphContent.end(); ++item)
+        {
+            // Skip anything that isn't a mesh.
+            if (!(*item) || !(*item)->RTTI_IsTypeOf(AZ::SceneAPI::DataTypes::IMeshData::TYPEINFO_Uuid()))
+            {
+                continue;
+            }
+
+            // Get the mesh data and node index and store them in the vector as a pair, so we can iterate over them later.
+            auto* mesh = static_cast<AZ::SceneAPI::DataTypes::IMeshData*>(item->get());
+            AZ::SceneAPI::Containers::SceneGraph::NodeIndex nodeIndex = graph.ConvertToNodeIndex(item);
+            meshes.emplace_back(mesh, nodeIndex);
+        }
+
+        // Iterate over them. We had to build the array before as this method can insert new nodes, so using the iterator directly would fail.
+        for (auto& [mesh, nodeIndex] : meshes)
+        {
+            // Generate UVs for the mesh
+            if (!GenerateUVsForMesh(context.GetScene(), nodeIndex, mesh, generationMethod, replaceExisting))
+            {
+                return AZ::SceneAPI::Events::ProcessingResult::Failure;
+            }
+        }
+
+        return AZ::SceneAPI::Events::ProcessingResult::Success;
+    }
+
+    bool UVsGenerateComponent::GenerateUVsForMesh(
+        AZ::SceneAPI::Containers::Scene& scene,
+        const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex,
+        AZ::SceneAPI::DataTypes::IMeshData* meshData,
+        const AZ::SceneAPI::DataTypes::UVsGenerationMethod generationMethod,
+        const bool replaceExisting)
+    {
+        AZ::SceneAPI::Containers::SceneGraph& graph = scene.GetGraph();
+        size_t uvSetCount = CalcUvSetCount(graph, nodeIndex);
+
+        // there might already be existing data there - see if there is.
+        AZ::SceneData::GraphData::MeshVertexUVData* dataToFill = nullptr;
+        if (uvSetCount > 0)
+        {
+            // This modifier always works on UV Set #0.
+            dataToFill = static_cast<AZ::SceneData::GraphData::MeshVertexUVData*>(FindUvData(graph, nodeIndex, 0));
+        }
+        AZStd::string currentNodeName = graph.GetNodeName(nodeIndex).GetPath();
+        if ((dataToFill) && (!replaceExisting))
+        {
+            // if there's already data, and we are not set to replace existing, do not generate data
+            AZ_Info(
+                AZ::SceneAPI::Utilities::LogWindow,
+                "Asked to generate UVs for mesh " AZ_STRING_FORMAT " but it already has UVs and 'replace existing' is not set.  Not replacing existing data.\n",
+                AZ_STRING_ARG(currentNodeName));
+            return true; // this is not an error!
+        }
+
+        if (!dataToFill)
+        {
+            if (!CreateUVsLayer(scene.GetManifest(), nodeIndex, graph, &dataToFill))
+            {
+                // the above function will emit an error if it fails.
+                return false;
+            }
+        }
+
+        bool allSuccess = true;
+        AZ_Info(
+            AZ::SceneAPI::Utilities::LogWindow,
+            "Generating UVs for " AZ_STRING_FORMAT ".\n",
+            AZ_STRING_ARG(currentNodeName));
+
+        switch (generationMethod)
+        {
+        case AZ::SceneAPI::DataTypes::UVsGenerationMethod::SphericalProjection:
+            {
+                allSuccess &= AZ::UVsGeneration::Mesh::SphericalMapping::GenerateUVsSphericalMapping(meshData, dataToFill);
+                break;
+            }
+            // for future expansion - add new methods here if you want to support additional methods of UV auto generation.
+        default:
+            {
+                AZ_Assert(false, "Unknown UVs generation method selected (%u) cannot generate UVs.\n", static_cast<AZ::u32>(generationMethod));
+                allSuccess = false;
+            }
+        }
+
+        return allSuccess;
+    }
+
+    size_t UVsGenerateComponent::CalcUvSetCount(
+        AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex) const
+    {
+        const auto nameContentView = AZ::SceneAPI::Containers::Views::MakePairView(graph.GetNameStorage(), graph.GetContentStorage());
+
+        size_t result = 0;
+        auto meshChildView = AZ::SceneAPI::Containers::Views::MakeSceneGraphChildView<AZ::SceneAPI::Containers::Views::AcceptEndPointsOnly>(
+            graph, nodeIndex, nameContentView.begin(), true);
+        for (auto child = meshChildView.begin(); child != meshChildView.end(); ++child)
+        {
+            AZ::SceneAPI::DataTypes::IMeshVertexUVData* data =
+                azrtti_cast<AZ::SceneAPI::DataTypes::IMeshVertexUVData*>(child->second.get());
+            if (data)
+            {
+                result++;
+            }
+        }
+
+        return result;
+    }
+
+    AZ::SceneAPI::DataTypes::IMeshVertexUVData* UVsGenerateComponent::FindUvData(
+        AZ::SceneAPI::Containers::SceneGraph& graph, const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex, AZ::u64 uvSet) const
+    {
+        const auto nameContentView = AZ::SceneAPI::Containers::Views::MakePairView(graph.GetNameStorage(), graph.GetContentStorage());
+
+        AZ::u64 uvSetIndex = 0;
+        auto meshChildView = AZ::SceneAPI::Containers::Views::MakeSceneGraphChildView<AZ::SceneAPI::Containers::Views::AcceptEndPointsOnly>(
+            graph, nodeIndex, nameContentView.begin(), true);
+        for (auto child = meshChildView.begin(); child != meshChildView.end(); ++child)
+        {
+            AZ::SceneAPI::DataTypes::IMeshVertexUVData* data =
+                azrtti_cast<AZ::SceneAPI::DataTypes::IMeshVertexUVData*>(child->second.get());
+            if (data)
+            {
+                if (uvSetIndex == uvSet)
+                {
+                    return data;
+                }
+                uvSetIndex++;
+            }
+        }
+
+        return nullptr;
+    }
+
+    bool UVsGenerateComponent::CreateUVsLayer(
+        AZ::SceneAPI::Containers::SceneManifest& manifest,
+        const AZ::SceneAPI::Containers::SceneGraph::NodeIndex& nodeIndex,
+        AZ::SceneAPI::Containers::SceneGraph& graph,
+        SceneData::GraphData::MeshVertexUVData** outUVsData)
+    {
+        *outUVsData = nullptr;
+
+        AZStd::shared_ptr<SceneData::GraphData::MeshVertexUVData> uvData =
+            AZStd::make_shared<AZ::SceneData::GraphData::MeshVertexUVData>();
+
+        if (!uvData)
+        {
+            // it is unlikely you will even see this message since you are out of memory and the below
+            AZ_Error(AZ::SceneAPI::Utilities::ErrorWindow, false, "OUT OF MEMORY - Failed to allocate UV data.\n"
+            "You could try reducing the size of the files, splitting into multiple geometries, or reducing the number "
+            "or concurrent Asset Processor Jobs allowed to run.");
+            return false;
+        }
+
+       
+        const AZStd::string uvSetName =
+            AZ::SceneAPI::DataTypes::Utilities::CreateUniqueName<SceneData::GraphData::MeshVertexUVData>("UV0", manifest);
+        uvData->SetCustomName(uvSetName.c_str());
+
+        AZ::SceneAPI::Containers::SceneGraph::NodeIndex newIndex = graph.AddChild(nodeIndex, uvSetName.c_str(), uvData);
+        // if this assert triggers theres some terrible bug deep in the scene graph system, and the artist that sees it
+        // is not going to be able to fix it without code intervention (so assert).
+        AZ_Assert(newIndex.IsValid(), "Failed to create SceneGraph node for UVs attribute.");
+        if (!newIndex.IsValid())
+        {
+            return false;
+        }
+        graph.MakeEndPoint(newIndex);
+
+        *outUVsData = uvData.get();
+        return true;
+    }
+
+} // namespace AZ::SceneGenerationComponents

+ 54 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerateComponent.h

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/RTTI/RTTI.h>
+#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
+#include <SceneAPI/SceneCore/Containers/Scene.h>
+
+namespace AZ
+{
+    namespace SceneAPI::DataTypes
+    {
+        class IMeshData;
+        class IMeshVertexUVData;
+    }
+
+    namespace SceneData::GraphData
+    {
+        class MeshVertexUVData;
+    }
+
+    class ComponentDescriptor;
+} // namespace AZ
+
+namespace AZ::SceneGenerationComponents
+{
+    struct UVsGenerateContext
+        : public AZ::SceneAPI::Events::ICallContext
+    {
+        AZ_RTTI(UVsGenerateContext, "{CC7301AB-A7EC-41FB-8BEE-DCC8C8C32BF4}", AZ::SceneAPI::Events::ICallContext)
+
+        UVsGenerateContext(AZ::SceneAPI::Containers::Scene& scene)
+            : m_scene(scene) {}
+        UVsGenerateContext& operator=(const UVsGenerateContext& other) = delete;
+
+        AZ::SceneAPI::Containers::Scene& GetScene() { return m_scene; }
+        const AZ::SceneAPI::Containers::Scene& GetScene() const { return m_scene; }
+
+    private:
+        AZ::SceneAPI::Containers::Scene& m_scene;
+    };
+
+    inline constexpr const char* s_UVsGenerateComponentTypeId = "{49121BDD-C7E5-4D39-89BC-28789C90057F}";
+
+    //! This function will be called by the module class to get the descriptor.  Doing it this way saves
+    //! it from having to actually see the entire component declaration here, it can all be in the implementation file.
+    AZ::ComponentDescriptor* CreateUVsGenerateComponentDescriptor();
+} // namespace AZ::SceneGenerationComponents

+ 54 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.cpp

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.h>
+
+#include <SceneAPI/SceneCore/DataTypes/GraphData/IMeshData.h>
+#include <SceneAPI/SceneCore/Utilities/Reporting.h>
+#include <SceneAPI/SceneData/GraphData/MeshVertexUVData.h>
+#include <SceneAPI/SceneData/Rules/UVsRule.h>
+
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/Math/Vector2.h>
+#include <AzCore/Math/Aabb.h>
+
+namespace AZ::UVsGeneration::Mesh::SphericalMapping
+{
+    bool GenerateUVsSphericalMapping(const AZ::SceneAPI::DataTypes::IMeshData* meshData, AZ::SceneData::GraphData::MeshVertexUVData* uvData)
+    {
+        uvData->Clear();
+        unsigned int vertexCount = meshData->GetVertexCount();
+        if (vertexCount == 0)
+        {
+            AZ_Trace(AZ::SceneAPI::Utilities::LogWindow, "Mesh has 0 vertex count, skipping UV generation.")
+            return true;
+        }
+
+        uvData->ReserveContainerSpace(meshData->GetVertexCount());
+
+        // calculate mesh center by bounding box center.
+        AZ::Aabb meshAabb = AZ::Aabb::CreateNull();
+        for (unsigned int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
+        {
+            meshAabb.AddPoint(meshData->GetPosition(vertexIndex));
+        }
+
+        AZ::Vector3 centerPoint = meshAabb.GetCenter();
+        for (unsigned int vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex)
+        {
+            // project to sphere.
+            AZ::Vector3 projection = meshData->GetPosition(vertexIndex) - centerPoint;
+            projection.Normalize();
+            AZ::Vector2 uvCoords(asinf(projection.GetX()) / AZ::Constants::Pi + 0.5f,
+                                 asinf(projection.GetY()) / AZ::Constants::Pi + 0.5f);
+            uvData->AppendUV(uvCoords);
+        }
+
+        return true;
+    }
+} 

+ 22 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+namespace AZ::SceneAPI::DataTypes { class IMeshData; }
+namespace AZ::SceneData::GraphData { class MeshVertexUVData; } 
+
+namespace AZ::UVsGeneration::Mesh::SphericalMapping
+{
+    //! A simple positional sphere mapping UV generator.
+    //! The vertices from the mesh are projected onto a unit sphere and then that is used
+    //! to generate UV coordinates.
+    bool GenerateUVsSphericalMapping(
+        const AZ::SceneAPI::DataTypes::IMeshData* meshData, // in
+        AZ::SceneData::GraphData::MeshVertexUVData* uvData); // out
+}

+ 63 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsPreExportComponent.cpp

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <Generation/Components/UVsGenerator/UVsPreExportComponent.h>
+
+#include <AzCore/RTTI/RTTI.h>
+
+#include <Generation/Components/UVsGenerator/UVsGenerateComponent.h> // for the context
+#include <SceneAPI/SceneCore/Components/GenerationComponent.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <SceneAPI/SceneCore/Events/GenerateEventContext.h>
+#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
+#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
+
+namespace AZ::SceneGenerationComponents
+{
+    //! This is the component responsible for actually hooking into the scene API's processing flow
+    //! during the generation step.
+    class UVsPreExportComponent : public AZ::SceneAPI::SceneCore::GenerationComponent
+    {
+    public:
+        AZ_COMPONENT(UVsPreExportComponent, s_UVsPreExportComponentTypeId, AZ::SceneAPI::SceneCore::GenerationComponent);
+        UVsPreExportComponent();
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        AZ::SceneAPI::Events::ProcessingResult Register(AZ::SceneAPI::Events::GenerateAdditionEventContext& context);
+    };
+
+    AZ::ComponentDescriptor* CreateUVsPreExportComponentDescriptor()
+    {
+        return UVsPreExportComponent::CreateDescriptor();
+    }
+
+    namespace SceneEvents = AZ::SceneAPI::Events;
+
+    UVsPreExportComponent::UVsPreExportComponent()
+    {
+        BindToCall(&UVsPreExportComponent::Register);
+    }
+
+    void UVsPreExportComponent::Reflect(AZ::ReflectContext* context)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serializeContext)
+        {
+            serializeContext->Class<UVsPreExportComponent, AZ::SceneAPI::SceneCore::GenerationComponent>()->Version(1);
+        }
+    }
+
+    AZ::SceneAPI::Events::ProcessingResult UVsPreExportComponent::Register(AZ::SceneAPI::Events::GenerateAdditionEventContext& context)
+    {
+        SceneEvents::ProcessingResultCombiner result;
+        UVsGenerateContext uvsGenerateContext(context.GetScene());
+        result += SceneEvents::Process<UVsGenerateContext>(uvsGenerateContext);
+        return result.GetResult();
+    }
+} // namespace AZ::SceneGenerationComponents

+ 23 - 0
Gems/SceneProcessing/Code/Source/Generation/Components/UVsGenerator/UVsPreExportComponent.h

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+namespace AZ
+{
+    class ComponentDescriptor;
+}
+
+namespace AZ::SceneGenerationComponents
+{
+    inline constexpr const char* s_UVsPreExportComponentTypeId = "{64F79C1E-CED6-42A9-8229-6607F788C731}";
+
+    //! This function will be called by the module class to get the descriptor.  Doing it this way saves
+    //! it from having to actually see the entire component declaration here, it can all be in the implementation file.
+    AZ::ComponentDescriptor* CreateUVsPreExportComponentDescriptor();
+} // namespace AZ::SceneGenerationComponents

+ 4 - 0
Gems/SceneProcessing/Code/Source/SceneProcessingModule.cpp

@@ -16,6 +16,8 @@
 #include <Config/Widgets/GraphTypeSelector.h>
 #include <Generation/Components/TangentGenerator/TangentGenerateComponent.h>
 #include <Generation/Components/TangentGenerator/TangentPreExportComponent.h>
+#include <Generation/Components/UVsGenerator/UVsGenerateComponent.h>
+#include <Generation/Components/UVsGenerator/UVsPreExportComponent.h>
 #include <Generation/Components/MeshOptimizer/MeshOptimizerComponent.h>
 #include <Source/SceneProcessingModule.h>
 
@@ -44,6 +46,8 @@ namespace AZ
                     SceneBuilder::SceneSerializationHandler::CreateDescriptor(),
                     AZ::SceneGenerationComponents::TangentPreExportComponent::CreateDescriptor(),
                     AZ::SceneGenerationComponents::TangentGenerateComponent::CreateDescriptor(),
+                    AZ::SceneGenerationComponents::CreateUVsGenerateComponentDescriptor(),
+                    AZ::SceneGenerationComponents::CreateUVsPreExportComponentDescriptor(),
                     AZ::SceneGenerationComponents::MeshOptimizerComponent::CreateDescriptor(),
                 });
 

+ 6 - 0
Gems/SceneProcessing/Code/sceneprocessing_editor_static_files.cmake

@@ -20,6 +20,12 @@ set(FILES
     Source/Generation/Components/TangentGenerator/TangentGenerators/MikkTGenerator.cpp
     Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.h
     Source/Generation/Components/TangentGenerator/TangentGenerators/BlendShapeMikkTGenerator.cpp
+    Source/Generation/Components/UVsGenerator/UVsGenerateComponent.h
+    Source/Generation/Components/UVsGenerator/UVsGenerateComponent.cpp
+    Source/Generation/Components/UVsGenerator/UVsPreExportComponent.h
+    Source/Generation/Components/UVsGenerator/UVsPreExportComponent.cpp
+    Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.h
+    Source/Generation/Components/UVsGenerator/UVsGenerators/SphereMappingUVsGenerator.cpp
     Source/Generation/Components/MeshOptimizer/MeshBuilder.cpp
     Source/Generation/Components/MeshOptimizer/MeshBuilder.h
     Source/Generation/Components/MeshOptimizer/MeshBuilderInvalidIndex.h

+ 46 - 38
Registry/sceneassetimporter.setreg

@@ -1,39 +1,47 @@
 {
-	"O3DE":
-	{
-		"SceneAPI":
-		{
-			"AssetImporter":
-			{
-				"SupportedFileTypeExtensions":
-				[
-					".fbx",
-					".stl",
-					".gltf",
-					".glb"
-				]
-			},
-			"MaterialConverter": 
-			{
-				"Enable": true,
-				"DefaultMaterial": "Materials/Presets/PBR/default_grid.material"
-			},
-			"TangentGenerateComponent":
-			{
-				"DefaultGenerationMethod": "FromSourceScene",
-				"DebugBitangentFlip": false
-			},
-			"SkinRule":
-			{
-				"DefaultMaxSkinInfluencesPerVertex": 8,
-				"DefaultWeightThreshold": 0.001
-			},
-			"ModelBuilder": 
-			{
-				// When false, scenes with multiple meshes assigned to the same material but with different vertex layouts will successfully process
-				// When true, stricter error checking will cause the asset to fail to process with an error message indicating why
-				"MismatchedVertexLayoutsAreErrors": false 
-			}
-		}
-	}
-}
+    "O3DE":
+    {
+        "SceneAPI":
+        {
+            "AssetImporter":
+            {
+                "SupportedFileTypeExtensions":
+                [
+                    ".fbx",
+                    ".stl",
+                    ".gltf",
+                    ".glb"
+                ]
+            },
+            "MaterialConverter": 
+            {
+                "Enable": true,
+                "DefaultMaterial": "Materials/Presets/PBR/default_grid.material"
+            },
+            "TangentGenerateComponent":
+            {
+                "DefaultGenerationMethod": "FromSourceScene",
+                "DebugBitangentFlip": false
+            },
+            "UVsGenerateComponent":
+            {
+                // you may have to reprocess assets if you change these defaults.
+                // When the user has not added any rules regarding UV generation, what should we do? Choose from "SphericalProjection" or "LeaveSceneDataAsIs"
+                "DefaultGenerationMethodIfNoRulePresent" : "LeaveSceneDataAsIs",
+                // when the user actually clicks to add a new Rule or a script adds a new rule to generate uvs, what method should it use by default?
+                "DefaultGenerationMethodWhenRuleIsPresent" : "SphericalProjection"
+            },
+            "SkinRule":
+            {
+                "DefaultMaxSkinInfluencesPerVertex": 8,
+                "DefaultWeightThreshold": 0.001
+            },
+            "ModelBuilder": 
+            {
+                // When false, scenes with multiple meshes assigned to the same material but with different vertex layouts will successfully process
+                // When true, stricter error checking will cause the asset to fail to process with an error message indicating why
+                "MismatchedVertexLayoutsAreErrors": false 
+            }
+        }
+    }
+}

+ 17 - 1
cmake/Platform/Common/Install_common.cmake

@@ -481,14 +481,30 @@ function(ly_setup_cmake_install)
             list(APPEND relative_external_subdirs "\"${engine_rel_external_subdir}\"")
         endif()
     endforeach()
+    # Sort the external subdirectories before joining them with commas
+    list(SORT relative_external_subdirs CASE INSENSITIVE)
     list(JOIN relative_external_subdirs ",\n${indent}" O3DE_INSTALL_EXTERNAL_SUBDIRS)
 
+    # Use the cache list of "gem_names" from the engine.json to populate
+    # the generated engine.json file
+    # The O3DE_INSTALL_ENGINE_GEMS is the configure placeholder that needs to be set
+    # at the end
+    get_property(active_engine_gems GLOBAL PROPERTY "O3DE_EXPLICIT_ACTIVE_GEMS_ENGINE")
+    if (active_engine_gems)
+        foreach(active_engine_gem IN LISTS active_engine_gems)
+            list(APPEND quoted_active_engine_gems "\"${active_engine_gem}\"")
+        endforeach()
+        list(SORT quoted_active_engine_gems CASE INSENSITIVE)
+        list(JOIN quoted_active_engine_gems ",\n${indent}" O3DE_INSTALL_ENGINE_GEMS)
+    endif()
+
     # Read the "templates" key from the source engine.json
     o3de_read_json_array(engine_templates ${LY_ROOT_FOLDER}/engine.json "templates")
     if(engine_templates)
-        foreach(template_path ${engine_templates})
+        foreach(template_path IN LISTS engine_templates)
             list(APPEND relative_templates "\"${template_path}\"")
         endforeach()
+        list(SORT relative_templates CASE INSENSITIVE)
         list(JOIN relative_templates ",\n${indent}" O3DE_INSTALL_TEMPLATES)
     endif()
 

+ 11 - 1
cmake/Subdirectories.cmake

@@ -337,8 +337,18 @@ function(get_all_external_subdirectories_for_o3de_object output_subdirs object_t
         resolve_gem_dependencies(${object_type} "${object_path}")
     endif()
 
-    foreach(gem_name_with_version_specifier IN LISTS initial_gem_names)
+    # Cache the "gem_names" field entries as read from the <o3de_object>.json file
+    # This will be used in the Install code to generate an "engine.json" with the same
+    # set of active gems into its "gem_names" field
+    get_property(explicit_active_gems GLOBAL PROPERTY "O3DE_EXPLICIT_ACTIVE_GEMS_${object_type}")
+    # Append to any existing active gems mapped using the ${object_type} key
+    list(APPEND explicit_active_gems ${initial_gem_names})
+    # Make the list of active gems unique
+    list(REMOVE_DUPLICATES explicit_active_gems)
+    # Update the ${object_type} -> active gem GLOBAL property
+    set_property(GLOBAL PROPERTY "O3DE_EXPLICIT_ACTIVE_GEMS_${object_type}" "${explicit_active_gems}")
 
+    foreach(gem_name_with_version_specifier IN LISTS initial_gem_names)
         # Use the ERROR_VARIABLE to catch the common case when it's a simple string and not a json type.
         string(JSON json_type ERROR_VARIABLE json_error TYPE ${gem_name_with_version_specifier})
         set(gem_optional FALSE)

+ 1 - 0
cmake/install/engine.json.in

@@ -8,6 +8,7 @@
     "copyright_year": @O3DE_COPYRIGHT_YEAR@,
     "build": @O3DE_INSTALL_BUILD_VERSION@,
     "external_subdirectories": [@O3DE_INSTALL_EXTERNAL_SUBDIRS@],
+    "gem_names": [@O3DE_INSTALL_ENGINE_GEMS@],
     "projects": [@O3DE_INSTALL_PROJECTS@],
     "repos": [@O3DE_INSTALL_REPOS@],
     "templates": [@O3DE_INSTALL_TEMPLATES@]

+ 8 - 7
engine.json

@@ -68,7 +68,6 @@
         "Gems/Prefab/PrefabBuilder",
         "Gems/Presence",
         "Gems/PrimitiveAssets",
-        "Gems/Streamer",
         "Gems/Profiler",
         "Gems/PythonAssetBuilder",
         "Gems/QtForPython",
@@ -89,6 +88,7 @@
         "Gems/StartingPointCamera",
         "Gems/StartingPointInput",
         "Gems/StartingPointMovement",
+        "Gems/Streamer",
         "Gems/SurfaceData",
         "Gems/Terrain",
         "Gems/TestAssetBuilder",
@@ -102,10 +102,10 @@
         "Gems/WhiteBox"
     ],
     "gem_names": [
-        "AtomShader",
         "Atom_Bootstrap",
-        "CommonFeaturesAtom",
+        "AtomShader",
         "Camera",
+        "CommonFeaturesAtom",
         "Maestro",
         "PrefabBuilder",
         "SceneProcessing"
@@ -114,16 +114,17 @@
         "AutomatedTesting"
     ],
     "templates": [
-        "Templates/PrebuiltGem",
-        "Templates/GemRepo",
         "Templates/AssetGem",
+        "Templates/CppToolGem",
         "Templates/DefaultComponent",
         "Templates/DefaultGem",
         "Templates/DefaultProject",
-        "Templates/CppToolGem",
+        "Templates/GemRepo",
         "Templates/MinimalProject",
+        "Templates/PrebuiltGem",
+        "Templates/PythonToolGem",
         "Templates/ScriptCanvasNode",
-        "Templates/PythonToolGem"
+        "Templates/UnifiedMultiplayerGem"
     ],
     "repos": [
         "https://canonical.o3de.org"