Browse Source

Misc scene-related bugfixes and optimizations (#16567)

* Fix bug where MeshLoader could be invalid and cause crashes at time of use in the queued lambda.

Signed-off-by: Mike Balfour <[email protected]>

* Optimize pattern matching by >=50%.
On each pattern match, 50% of the cost was in building the comparer. By building it once, we save that cost on every subsequent comparison.

Signed-off-by: Mike Balfour <[email protected]>

* Optimization - avoid redundant ScrollToBottom queueing.
This saves 1/3 of the total time in displaying FBX settings.

Signed-off-by: Mike Balfour <[email protected]>

* Bugfix - the sub ID list shouldn't be cleared between mesh groups, or else it won't accurately catch collisions.
This causes sub ID collisions to get significantly earlier in the export process.

Signed-off-by: Mike Balfour <[email protected]>

* Optimization - add all entries to the combo box widget at once
This has little to no savings for small lists of submeshes, but significant savings for large lists.

Signed-off-by: Mike Balfour <[email protected]>

* Simplified the last optimization a little

Signed-off-by: Mike Balfour <[email protected]>

* Optimization - change all existing soft name patterns to an array of postfix matches.
This cuts the overall processing time by > 50% from the previous regex patterns.

Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneCore/Tests/Utilities/PatternMatcherTests.cpp

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneCore/Utilities/PatternMatcher.cpp

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Update Code/Tools/SceneAPI/SceneCore/Tests/Utilities/PatternMatcherTests.cpp

Co-authored-by: lumberyard-employee-dm <[email protected]>
Signed-off-by: Mike Balfour <[email protected]>

* Compile fix for linux

Signed-off-by: Mike Balfour <[email protected]>

---------

Signed-off-by: Mike Balfour <[email protected]>
Co-authored-by: lumberyard-employee-dm <[email protected]>
Mike Balfour 2 years ago
parent
commit
d629db9b5b

+ 29 - 0
Code/Tools/SceneAPI/SceneCore/Tests/Utilities/PatternMatcherTests.cpp

@@ -27,6 +27,12 @@ namespace AZ
                 EXPECT_FALSE(matcher.MatchesPattern("string_with_something_else"));
             }
 
+            TEST(PatternMatcherTest, MatchesPattern_CaseInsensitiveMatchingNameWithPostFix_ReturnsTrue)
+            {
+                PatternMatcher matcher("_PoStFiX", PatternMatcher::MatchApproach::PostFix);
+                EXPECT_TRUE(matcher.MatchesPattern("string_with_postfix"));
+            }
+
             TEST(PatternMatcherTest, MatchesPattern_NonMatchingNameWithPostFixAndEarlyOutForSmallerTestThanPattern_ReturnsFalse)
             {
                 PatternMatcher matcher("_postfix", PatternMatcher::MatchApproach::PostFix);
@@ -45,6 +51,12 @@ namespace AZ
                 EXPECT_FALSE(matcher.MatchesPattern("string_with_something_else"));
             }
 
+            TEST(PatternMatcherTest, MatchesPattern_CaseInsensitiveMatchingNameWithPreFix_ReturnsTrue)
+            {
+                PatternMatcher matcher("PrEFiX_", PatternMatcher::MatchApproach::PreFix);
+                EXPECT_TRUE(matcher.MatchesPattern("prefix_for_string"));
+            }
+
             TEST(PatternMatcherTest, MatchesPattern_MatchingNameWithRegex_ReturnsTrue)
             {
                 PatternMatcher matcher("^.{4}$", PatternMatcher::MatchApproach::Regex);
@@ -56,6 +68,23 @@ namespace AZ
                 PatternMatcher matcher("^.{4}$", PatternMatcher::MatchApproach::Regex);
                 EXPECT_FALSE(matcher.MatchesPattern("string_to_long_for_regex"));
             }
+
+            TEST(PatternMatcherTest, MatchesPattern_MatchingPrefixInArrayOfPatterns_ReturnsTrue)
+            {
+                constexpr auto patterns = AZStd::to_array<AZStd::string_view>({ "postfix", "xxx", "prefix_" });
+
+                PatternMatcher matcher(patterns, PatternMatcher::MatchApproach::PreFix);
+                EXPECT_TRUE(matcher.MatchesPattern("prefix_for_string"));
+            }
+
+            TEST(PatternMatcherTest, MatchesPattern_NonMatchingPrefixInArrayOfPatterns_ReturnsFalse)
+            {
+                constexpr auto patterns = AZStd::to_array<AZStd::string_view>({ "postfix", "xxx" });
+
+                PatternMatcher matcher(patterns, PatternMatcher::MatchApproach::PreFix);
+                EXPECT_FALSE(matcher.MatchesPattern("prefix_for_string"));
+            }
+
         } // SceneCore
     } // SceneAPI
 } // AZ

+ 80 - 25
Code/Tools/SceneAPI/SceneCore/Utilities/PatternMatcher.cpp

@@ -20,33 +20,58 @@ namespace AZ
         namespace SceneCore
         {
             PatternMatcher::PatternMatcher(const char* pattern, MatchApproach matcher)
-                : m_pattern(pattern)
+                : m_patterns({ AZStd::string(pattern) })
                 , m_matcher(matcher)
             {
             }
 
             PatternMatcher::PatternMatcher(const AZStd::string& pattern, MatchApproach matcher)
-                : m_pattern(pattern)
+                : m_patterns({ pattern })
                 , m_matcher(matcher)
             {
             }
 
             PatternMatcher::PatternMatcher(AZStd::string&& pattern, MatchApproach matcher)
-                : m_pattern(AZStd::move(pattern))
+                : m_patterns({ AZStd::move(pattern) })
                 , m_matcher(matcher)
             {
             }
 
+            PatternMatcher::PatternMatcher(const AZStd::span<const AZStd::string_view> patterns, MatchApproach matcher)
+                : m_matcher(matcher)
+            {
+                m_patterns.reserve(patterns.size());
+                for (auto& pattern : patterns)
+                {
+                    m_patterns.emplace_back(pattern);
+                }
+            }
+
+            PatternMatcher::PatternMatcher(const PatternMatcher& rhs)
+                : m_patterns(rhs.m_patterns)
+                , m_matcher(rhs.m_matcher)
+            {
+            }
+
             PatternMatcher::PatternMatcher(PatternMatcher&& rhs)
-                : m_pattern(AZStd::move(rhs.m_pattern))
+                : m_patterns(AZStd::move(rhs.m_patterns))
                 , m_matcher(rhs.m_matcher)
             {
             }
 
+            PatternMatcher& PatternMatcher::operator=(const PatternMatcher& rhs)
+            {
+                m_patterns = rhs.m_patterns;
+                m_matcher = rhs.m_matcher;
+                m_regexMatchers.clear();
+                return *this;
+            }
+
             PatternMatcher& PatternMatcher::operator=(PatternMatcher&& rhs)
             {
-                m_pattern = AZStd::move(rhs.m_pattern);
+                m_patterns = AZStd::move(rhs.m_patterns);
                 m_matcher = rhs.m_matcher;
+                m_regexMatchers.clear();
                 return *this;
             }
 
@@ -97,31 +122,65 @@ namespace AZ
                     return false;
                 }
 
-                m_pattern = patternValue.GetString();
+                m_patterns = { patternValue.GetString() };
 
                 return true;
             }
 
             bool PatternMatcher::MatchesPattern(const char* name, size_t nameLength) const
             {
+                return MatchesPattern(AZStd::string_view(name, nameLength));
+            }
+
+            bool PatternMatcher::MatchesPattern(AZStd::string_view name) const
+            {
+                constexpr bool caseSensitive = false;
+
                 switch (m_matcher)
                 {
                 case MatchApproach::PreFix:
-                    return AzFramework::StringFunc::Equal(name, m_pattern.c_str(), false, m_pattern.size());
+                    for (const auto& pattern : m_patterns)
+                    {
+                        // Perform a case-insensitive prefix match.
+                        if (AZ::StringFunc::StartsWith(name, pattern, caseSensitive))
+                        {
+                            return true;
+                        }
+                    }
+                    return false;
                 case MatchApproach::PostFix:
-                {
-                    if (m_pattern.size() > nameLength)
+                    for (const auto& pattern : m_patterns)
                     {
-                        return false;
+                        // Perform a case-insensitive postfix match.
+                        if (AZ::StringFunc::EndsWith(name, pattern, caseSensitive))
+                        {
+                            return true;
+                        }
                     }
-                    size_t offset = nameLength - m_pattern.size();
-                    return AzFramework::StringFunc::Equal(name + offset, m_pattern.c_str());
-                }
+                    return false;
                 case MatchApproach::Regex:
                 {
-                    AZStd::regex comparer(m_pattern, AZStd::regex::extended);
-                    AZStd::smatch match;
-                    return AZStd::regex_match(name, match, comparer);
+                    for (size_t index = 0; index < m_patterns.size(); index++)
+                    {
+                        // Because PatternMatcher can get default constructed and serialized into directly, there's no good place
+                        // to initialize the regex matchers on construction, so we'll lazily initialize it here on first use.
+
+                        if (m_regexMatchers.empty())
+                        {
+                            m_regexMatchers.resize(m_patterns.size());
+                        }
+
+                        if (m_regexMatchers[index] == nullptr)
+                        {
+                            m_regexMatchers[index] = AZStd::make_unique<AZStd::regex>(m_patterns[index], AZStd::regex::extended);
+                        }
+                        AZStd::cmatch match;
+                        if (AZStd::regex_match(name.begin(), name.end(), match, *(m_regexMatchers[index])))
+                        {
+                            return true;
+                        }
+                    }
+                    return false;
                 }
                 default:
                     AZ_Assert(false, "Unknown option '%i' for pattern matcher.", m_matcher);
@@ -129,14 +188,10 @@ namespace AZ
                 }
             }
 
-            bool PatternMatcher::MatchesPattern(const AZStd::string& name) const
-            {
-                return MatchesPattern(name.c_str(), name.length());
-            }
-
             const AZStd::string& PatternMatcher::GetPattern() const
             {
-                return m_pattern;
+                const static AZStd::string emptyString{};
+                return !m_patterns.empty() ? m_patterns.front() : emptyString;
             }
 
             PatternMatcher::MatchApproach PatternMatcher::GetMatchApproach() const
@@ -150,8 +205,8 @@ namespace AZ
                 if (serializeContext)
                 {
                     serializeContext->Class<PatternMatcher>()
-                        ->Version(1)
-                        ->Field("pattern", &PatternMatcher::m_pattern)
+                        ->Version(2)
+                        ->Field("patterns", &PatternMatcher::m_patterns)
                         ->Field("matcher", &PatternMatcher::m_matcher);
 
                     EditContext* editContext = serializeContext->GetEditContext();
@@ -160,7 +215,7 @@ namespace AZ
                         editContext->Class<PatternMatcher>("Pattern matcher", "")
                             ->ClassElement(Edit::ClassElements::EditorData, "")
                             ->Attribute(Edit::Attributes::AutoExpand, true)
-                            ->DataElement(Edit::UIHandlers::Default, &PatternMatcher::m_pattern, "Pattern", "The pattern the matcher will check against.")
+                            ->DataElement(Edit::UIHandlers::Default, &PatternMatcher::m_patterns, "Patterns", "The patterns the matcher will check against.")
                             ->DataElement(Edit::UIHandlers::ComboBox, &PatternMatcher::m_matcher, "Matcher", "The used approach for matching.")
                                 ->EnumAttribute(MatchApproach::PreFix, "PreFix")
                                 ->EnumAttribute(MatchApproach::PostFix, "PostFix")

+ 7 - 4
Code/Tools/SceneAPI/SceneCore/Utilities/PatternMatcher.h

@@ -9,6 +9,7 @@
  */
 
 #include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/string/regex.h>
 #include <AzCore/std/string/string.h>
 #include <AzCore/JSON/document.h>
 #include <SceneAPI/SceneCore/SceneCoreConfiguration.h>
@@ -44,24 +45,26 @@ namespace AZ
                 SCENE_CORE_API PatternMatcher(const char* pattern, MatchApproach matcher);
                 SCENE_CORE_API PatternMatcher(const AZStd::string& pattern, MatchApproach matcher);
                 SCENE_CORE_API PatternMatcher(AZStd::string&& pattern, MatchApproach matcher);
-                SCENE_CORE_API PatternMatcher(const PatternMatcher& rhs) = default;
+                SCENE_CORE_API PatternMatcher(const AZStd::span<const AZStd::string_view> patterns, MatchApproach matcher);
+                SCENE_CORE_API PatternMatcher(const PatternMatcher& rhs);
                 SCENE_CORE_API PatternMatcher(PatternMatcher&& rhs);
 
-                SCENE_CORE_API PatternMatcher& operator=(const PatternMatcher& rhs) = default;
+                SCENE_CORE_API PatternMatcher& operator=(const PatternMatcher& rhs);
                 SCENE_CORE_API PatternMatcher& operator=(PatternMatcher&& rhs);
 
                 SCENE_CORE_API bool LoadFromJson(rapidjson::Document::ConstMemberIterator member);
 
                 SCENE_CORE_API bool MatchesPattern(const char* name, size_t nameLength) const;
-                SCENE_CORE_API bool MatchesPattern(const AZStd::string& name) const;
+                SCENE_CORE_API bool MatchesPattern(AZStd::string_view name) const;
 
                 SCENE_CORE_API const AZStd::string& GetPattern() const;
                 SCENE_CORE_API MatchApproach GetMatchApproach() const;
 
                 static void Reflect(AZ::ReflectContext* context);
             private:
-                AZStd::string m_pattern;
+                AZStd::vector<AZStd::string> m_patterns;
                 MatchApproach m_matcher = MatchApproach::PostFix;
+                mutable AZStd::vector<AZStd::unique_ptr<AZStd::regex>> m_regexMatchers;
             };
         } // SceneCore
     } // SceneAPI

+ 10 - 12
Code/Tools/SceneAPI/SceneUI/RowWidgets/NodeListSelectionWidget.cpp

@@ -136,7 +136,7 @@ namespace AZ
 
             void NodeListSelectionWidget::BuildList(const Containers::SceneGraph& graph)
             {
-                EntrySet entries;
+                QStringList entries;
 
                 auto view = Containers::Views::MakePairView(graph.GetNameStorage(), graph.GetContentStorage());
                 for (auto it = view.begin(); it != view.end(); ++it)
@@ -162,6 +162,12 @@ namespace AZ
 
                     AddEntry(entries, it->first);
                 }
+
+                if (!entries.empty())
+                {
+                    entries.removeDuplicates();
+                    addItems(entries);
+                }
             }
 
             bool NodeListSelectionWidget::IsCorrectType(const DataTypes::IGraphObject& object) const
@@ -180,25 +186,17 @@ namespace AZ
                 }
             }
 
-            void NodeListSelectionWidget::AddEntry(EntrySet& entries, const Containers::SceneGraph::Name& name)
+            void NodeListSelectionWidget::AddEntry(QStringList& comboListEntries, const Containers::SceneGraph::Name& name)
             {
                 if (m_useShortNames)
                 {
                     const char* shortName = name.GetName();
-                    if (entries.find(shortName) == entries.end())
-                    {
-                        entries.insert(shortName);
-                        addItem(shortName);
-                    }
+                    comboListEntries.append(QString(shortName));
                 }
                 else
                 {
                     const char* pathName = name.GetPath();
-                    if (entries.find(pathName) == entries.end())
-                    {
-                        entries.insert(pathName);
-                        addItem(pathName);
-                    }
+                    comboListEntries.append(QString(pathName));
                 }
             }
 

+ 1 - 3
Code/Tools/SceneAPI/SceneUI/RowWidgets/NodeListSelectionWidget.h

@@ -64,13 +64,11 @@ namespace AZ
                 void OnTextChange(const QString& text);
 
             protected:
-                using EntrySet = AZStd::unordered_set<AZStd::string>;
-
                 void showEvent(QShowEvent* event) override;
 
                 void BuildList(const Containers::SceneGraph& graph);
                 bool IsCorrectType(const DataTypes::IGraphObject& object) const;
-                void AddEntry(EntrySet& entries, const Containers::SceneGraph::Name& name);
+                void AddEntry(QStringList& comboListEntries, const Containers::SceneGraph::Name& name);
                 void SetSelection();
                 void AddDisabledOption();
 

+ 12 - 6
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.cpp

@@ -114,12 +114,16 @@ namespace AZ
 
                 UpdateAddButtonStatus();
 
-                QTimer::singleShot(0, this,
-                    [this]()
-                    {
-                        ScrollToBottom();
-                    }
-                );
+                // Guard against adding lots of ScrollToBottom calls in the case where we're performing bulk adds in a single frame.
+                if (!m_scrollToBottomQueued)
+                {
+                    m_scrollToBottomQueued = true;
+                    QTimer::singleShot(0, this,
+                        [this]()
+                        {
+                            ScrollToBottom();
+                        });
+                }
 
                 return true;
             }
@@ -251,6 +255,8 @@ namespace AZ
 
             void ManifestWidgetPage::ScrollToBottom()
             {
+                m_scrollToBottomQueued = false;
+
                 QScrollArea* propertyGridScrollArea = m_propertyEditor->findChild<QScrollArea*>();
                 if (propertyGridScrollArea)
                 {

+ 1 - 0
Code/Tools/SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.h

@@ -115,6 +115,7 @@ namespace AZ
                 size_t m_capSize;
                 QString m_helpUrl;
                 QMenu* m_editMenu;
+                bool m_scrollToBottomQueued = false;
             };
         } // namespace UI
     } // namespace SceneAPI

+ 3 - 1
Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Mesh/MeshFeatureProcessor.h

@@ -150,7 +150,9 @@ namespace AZ
             //! List of object SRGs used by meshes in this model 
             AZStd::vector<Data::Instance<RPI::ShaderResourceGroup>> m_objectSrgList;
             MeshFeatureProcessorInterface::ObjectSrgCreatedEvent m_objectSrgCreatedEvent;
-            AZStd::unique_ptr<MeshLoader> m_meshLoader;
+            // MeshLoader is a shared pointer because it can queue a reference to itself on the SystemTickBus. The reference
+            // needs to stay alive until the queued function is executed.
+            AZStd::shared_ptr<MeshLoader> m_meshLoader;
             RPI::Scene* m_scene = nullptr;
             RHI::DrawItemSortKey m_sortKey = 0;
 

+ 9 - 3
Gems/Atom/Feature/Common/Code/Source/Mesh/MeshFeatureProcessor.cpp

@@ -948,7 +948,7 @@ namespace AZ
             meshDataHandle->m_objectId = m_transformService->ReserveObjectId();
             meshDataHandle->m_rayTracingUuid = AZ::Uuid::CreateRandom();
             meshDataHandle->m_originalModelAsset = descriptor.m_modelAsset;
-            meshDataHandle->m_meshLoader = AZStd::make_unique<ModelDataInstance::MeshLoader>(descriptor.m_modelAsset, &*meshDataHandle);
+            meshDataHandle->m_meshLoader = AZStd::make_shared<ModelDataInstance::MeshLoader>(descriptor.m_modelAsset, &*meshDataHandle);
             meshDataHandle->m_flags.m_isAlwaysDynamic = descriptor.m_isAlwaysDynamic;
             meshDataHandle->m_flags.m_isDrawMotion = descriptor.m_isAlwaysDynamic;
 
@@ -1627,9 +1627,15 @@ namespace AZ
                 // If the asset was modified, reload it. This will also cause a model to change back to the default missing
                 // asset if it was removed, and it will replace the default missing asset with the real asset if it was added.
                 AZ::SystemTickBus::QueueFunction(
-                    [=]() mutable
+                    [=, meshLoader = m_parent->m_meshLoader]() mutable
                     {
-                        ModelReloaderSystemInterface::Get()->ReloadModel(modelAssetReference, m_modelReloadedEventHandler);
+                        // Only trigger the reload if the meshLoader is still being used by something other than the lambda.
+                        // If the lambda is the only owner, it will get destroyed after this queued call, so there's no point
+                        // in reloading the model.
+                        if (meshLoader.use_count() > 1)
+                        {
+                            ModelReloaderSystemInterface::Get()->ReloadModel(modelAssetReference, m_modelReloadedEventHandler);
+                        }
                     });
             }
         }

+ 0 - 2
Gems/Atom/RPI/Code/Source/RPI.Builders/Model/ModelAssetBuilderComponent.cpp

@@ -172,8 +172,6 @@ namespace AZ
                 m_systemInputAssemblyBufferPoolId = assetIdOutcome.GetValue();
             }
 
-            m_createdSubId.clear();
-
             m_modelName = context.m_group.GetName();
 
             const auto& scene = context.m_scene;

+ 60 - 60
Gems/SceneProcessing/Registry/SoftNameSettings.setreg

@@ -2,69 +2,69 @@
     "O3DE": {
         "AssetProcessor": {
             "SceneBuilder": {
-                "NodeSoftNameSettings": [
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Ll][Oo][Dd]_?1(_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "LODMesh1",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Ll][Oo][Dd]_?2(_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "LODMesh2",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Ll][Oo][Dd]_?3(_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "LODMesh3",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Ll][Oo][Dd]_?4(_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "LODMesh4",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Ll][Oo][Dd]_?5(_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "LODMesh5",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Pp][Hh][Yy][Ss](_optimized)?$",
-                            "matcher": 2
-                        },
-                        "virtualType": "PhysicsMesh",
-                        "includeChildren": true
-                    },
-                    {
-                        "pattern": {
-                            "pattern": "^.*_[Pp][Hh][Yy][Ss](_optimized)?$",
-                            "matcher": 1
-                        },
-                        "virtualType": "Ignore",
-                        "includeChildren": false
-                    }
-                ],
+              "NodeSoftNameSettings": [
+                {
+                  "pattern": {
+                    "patterns": [ "_lod_1", "_lod_1_optimized", "_lod1", "_lod1_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "LODMesh1",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "_lod_2", "_lod_2_optimized", "_lod2", "_lod2_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "LODMesh2",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "_lod_3", "_lod_3_optimized", "_lod3", "_lod3_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "LODMesh3",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "_lod_4", "_lod_4_optimized", "_lod4", "_lod4_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "LODMesh4",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "_lod_5", "_lod_5_optimized", "_lod5", "_lod5_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "LODMesh5",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "_phys", "_phys_optimized" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "PhysicsMesh",
+                  "includeChildren": true
+                },
+                {
+                  "pattern": {
+                    "patterns": [ "^.*_[Pp][Hh][Yy][Ss](_optimized)?$" ],
+                    "matcher": 1
+                  },
+                  "virtualType": "Ignore",
+                  "includeChildren": false
+                }
+              ],
                 "FileSoftNameSettings": [
                     {
                         "pattern": {
-                            "pattern": "_anim",
-                            "matcher": 1
+                          "patterns": [ "_anim" ],
+                          "matcher": 1
                         },
                         "virtualType": "Ignore",
                         "inclusiveList": false,