Sfoglia il codice sorgente

[Terrain] Add "screen" mode to mixed gradient, dirty region to Shape Falloff (#12449)

* Add missing "screen" blend mode

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

* Add initializers to the Aabbs.

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

Signed-off-by: Mike Balfour <[email protected]>
Mike Balfour 2 anni fa
parent
commit
127a4aa689

+ 3 - 0
Gems/GradientSignal/Code/Include/GradientSignal/Components/MixedGradientComponent.h

@@ -41,6 +41,7 @@ namespace GradientSignal
             Average,
             Normal,
             Overlay,
+            Screen
         };
 
         bool m_enabled = true;
@@ -119,6 +120,8 @@ namespace GradientSignal
                 return currentUnpremultiplied;
             case MixedGradientLayer::MixingOperation::Multiply:
                 return prevValue * currentUnpremultiplied;
+            case MixedGradientLayer::MixingOperation::Screen:
+                return 1.0f - ((1.0f - prevValue) * (1.0f - currentUnpremultiplied));
             case MixedGradientLayer::MixingOperation::Add:
                 return prevValue + currentUnpremultiplied;
             case MixedGradientLayer::MixingOperation::Subtract:

+ 4 - 1
Gems/GradientSignal/Code/Include/GradientSignal/Components/ShapeAreaFalloffGradientComponent.h

@@ -100,10 +100,13 @@ namespace GradientSignal
         void Set3dFalloff(bool is3dFalloff) override;
 
         void CacheShapeBounds();
+
+        void NotifyRegionChanged(const AZ::Aabb& region);
+
     private:
         ShapeAreaFalloffGradientConfig m_configuration;
-        LmbrCentral::DependencyMonitor m_dependencyMonitor;
         mutable AZStd::shared_mutex m_queryMutex;
         AZ::Vector3 m_cachedShapeCenter;
+        AZ::Aabb m_cachedShapeBounds;
     };
 }

+ 1 - 0
Gems/GradientSignal/Code/Source/Components/MixedGradientComponent.cpp

@@ -37,6 +37,7 @@ namespace GradientSignal
                     ->DataElement(AZ::Edit::UIHandlers::ComboBox, &MixedGradientLayer::m_operation, "Operation", "Function used to mix the current gradient with the previous result.")
                     ->EnumAttribute(MixedGradientLayer::MixingOperation::Initialize, "Initialize")
                     ->EnumAttribute(MixedGradientLayer::MixingOperation::Multiply, "Multiply")
+                    ->EnumAttribute(MixedGradientLayer::MixingOperation::Screen, "Screen")
                     ->EnumAttribute(MixedGradientLayer::MixingOperation::Add, "Linear Dodge (Add)")
                     ->EnumAttribute(MixedGradientLayer::MixingOperation::Subtract, "Subtract")
                     ->EnumAttribute(MixedGradientLayer::MixingOperation::Min, "Darken (Min)")

+ 63 - 21
Gems/GradientSignal/Code/Source/Components/ShapeAreaFalloffGradientComponent.cpp

@@ -128,9 +128,6 @@ namespace GradientSignal
 
     void ShapeAreaFalloffGradientComponent::Activate()
     {
-        m_dependencyMonitor.Reset();
-        m_dependencyMonitor.ConnectOwner(GetEntityId());
-        m_dependencyMonitor.ConnectDependency(m_configuration.m_shapeEntityId);
         ShapeAreaFalloffGradientRequestBus::Handler::BusConnect(GetEntityId());
 
         // Make sure we're notified whenever the shape changes, so that we can re-cache its center point.
@@ -153,7 +150,6 @@ namespace GradientSignal
         GradientRequestBus::Handler::BusDisconnect();
 
         LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
-        m_dependencyMonitor.Reset();
         ShapeAreaFalloffGradientRequestBus::Handler::BusDisconnect();
         AZ::EntityBus::Handler::BusDisconnect();
     }
@@ -250,6 +246,20 @@ namespace GradientSignal
         }
     }
 
+    void ShapeAreaFalloffGradientComponent::NotifyRegionChanged(const AZ::Aabb& region)
+    {
+        if (region.IsValid())
+        {
+            LmbrCentral::DependencyNotificationBus::Event(
+                GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionRegionChanged, region);
+        }
+        else
+        {
+            LmbrCentral::DependencyNotificationBus::Event(
+                GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
+        }
+    }
+
     AZ::EntityId ShapeAreaFalloffGradientComponent::GetShapeEntityId() const
     {
         return m_configuration.m_shapeEntityId;
@@ -280,8 +290,6 @@ namespace GradientSignal
         }
 
         CacheShapeBounds();
-
-        LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
     }
 
     float ShapeAreaFalloffGradientComponent::GetFalloffWidth() const
@@ -291,14 +299,18 @@ namespace GradientSignal
 
     void ShapeAreaFalloffGradientComponent::SetFalloffWidth(float falloffWidth)
     {
+        AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
+
         // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
         // execute an arbitrary amount of logic, including calls back to this component.
         {
             AZStd::unique_lock lock(m_queryMutex);
+            // We only support outer falloff, so our dirty region is our shape expanded by the larger falloff width.
+            dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(AZ::GetMax(m_configuration.m_falloffWidth, falloffWidth)));
             m_configuration.m_falloffWidth = falloffWidth;
         }
 
-        LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
+        NotifyRegionChanged(dirtyRegion);
     }
 
     FalloffType ShapeAreaFalloffGradientComponent::GetFalloffType() const
@@ -308,14 +320,19 @@ namespace GradientSignal
 
     void ShapeAreaFalloffGradientComponent::SetFalloffType(FalloffType type)
     {
+        AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
+
         // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
         // execute an arbitrary amount of logic, including calls back to this component.
         {
             AZStd::unique_lock lock(m_queryMutex);
             m_configuration.m_falloffType = type;
+
+            // We only support outer falloff, so our dirty region is our shape expanded by the falloff width.
+            dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth));
         }
 
-        LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
+        NotifyRegionChanged(dirtyRegion);
     }
 
     bool ShapeAreaFalloffGradientComponent::Get3dFalloff() const
@@ -325,14 +342,19 @@ namespace GradientSignal
 
     void ShapeAreaFalloffGradientComponent::Set3dFalloff(bool is3dFalloff)
     {
+        AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
+
         // Only hold the lock while we're changing the data. Don't hold onto it during the OnCompositionChanged call, because that can
         // execute an arbitrary amount of logic, including calls back to this component.
         {
             AZStd::unique_lock lock(m_queryMutex);
             m_configuration.m_is3dFalloff = is3dFalloff;
+
+            // We only support outer falloff, so our dirty region is our shape expanded by the falloff width.
+            dirtyRegion = m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth));
         }
 
-        LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
+        NotifyRegionChanged(dirtyRegion);
     }
     
     void ShapeAreaFalloffGradientComponent::OnShapeChanged(
@@ -353,20 +375,40 @@ namespace GradientSignal
 
     void ShapeAreaFalloffGradientComponent::CacheShapeBounds()
     {
-        AZStd::unique_lock lock(m_queryMutex);
-        AZ::Aabb bounds = AZ::Aabb::CreateNull();
-
-        LmbrCentral::ShapeComponentRequestsBus::EventResult(
-            bounds, m_configuration.m_shapeEntityId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
+        AZ::Aabb dirtyRegion = AZ::Aabb::CreateNull();
 
-        // Grab the center of the shape so that we can calculate falloff distance in 2D.
-        if (bounds.IsValid())
         {
-            m_cachedShapeCenter = bounds.GetCenter();
-        }
-        else
-        {
-            m_cachedShapeCenter = AZ::Vector3::CreateZero();
+            AZStd::unique_lock lock(m_queryMutex);
+
+            AZ::Aabb previousShapeBounds = m_cachedShapeBounds;
+
+            m_cachedShapeBounds = AZ::Aabb::CreateNull();
+
+            LmbrCentral::ShapeComponentRequestsBus::EventResult(
+                m_cachedShapeBounds, m_configuration.m_shapeEntityId, &LmbrCentral::ShapeComponentRequestsBus::Events::GetEncompassingAabb);
+
+            // Grab the center of the shape so that we can calculate falloff distance in 2D.
+            if (m_cachedShapeBounds.IsValid())
+            {
+                m_cachedShapeCenter = m_cachedShapeBounds.GetCenter();
+            }
+            else
+            {
+                m_cachedShapeCenter = AZ::Vector3::CreateZero();
+            }
+
+            // Calculate the dirty region based on the previous and current shape bounds.
+            // If either the previous or current shape bounds is invalid, then leave the dirtyRegion invalid.
+            // This component returns 1.0 everywhere if there's no shape, because technically there's no falloff from max,
+            // so changing to or from a valid shape will cause potential value changes across the entire world space.
+            if (previousShapeBounds.IsValid() && m_cachedShapeBounds.IsValid())
+            {
+                dirtyRegion.AddAabb(previousShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth)));
+                dirtyRegion.AddAabb(m_cachedShapeBounds.GetExpanded(AZ::Vector3(m_configuration.m_falloffWidth)));
+            }
         }
+
+        // Any time we're caching the shape bounds, it's presumably because the shape changed, so notify about the change.
+        NotifyRegionChanged(dirtyRegion);
     }
 } // namespace GradientSignal

+ 28 - 0
Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp

@@ -303,6 +303,34 @@ namespace UnitTest
         TestMixedGradientComponent(dataSize, inputLayer1, inputLayer2, expectedOutput, GradientSignal::MixedGradientLayer::MixingOperation::Multiply, 1.0f);
     }
 
+    TEST_F(GradientSignalReferencesTestsFixture, MixedGradientComponent_OperationScreen)
+    {
+        // Mixed Gradient:  Create two layers and set the second one to blend with "Screen".
+        // Screen is defined as "1 - (1 - a) * (1 - b)"
+
+        constexpr int dataSize = 3;
+        AZStd::vector<float> inputLayer1 =
+        {
+            0.0f, 0.1f, 0.0f,
+            0.4f, 1.0f, 1.0f,
+            0.8f, 0.9f, 0.2f
+        };
+        AZStd::vector<float> inputLayer2 =
+        {
+            0.0f, 0.0f, 0.2f,
+            1.0f, 0.5f, 1.0f,
+            0.6f, 0.3f, 0.4f
+        };
+        AZStd::vector<float> expectedOutput =
+        {
+            0.0f, 0.1f, 0.2f,       // 1 - (1 - 0) * (1 - 0) = 0, 1 - (1 - a) * (1 - 0) = a, 1 - (1 - 0) * (1 - b) = b
+            1.0f, 1.0f, 1.0f,       // 1 - (1 - a) * (1 - 1) = 1, 1 - (1 - 1) * (1 - b) = 1, 1 - (1 - 1) * (1 - 1) = 1
+            0.92f, 0.93f, 0.52f     // 1 - (1 - a) * (1 - b) = c where c >= a and c >= b
+        };
+
+        TestMixedGradientComponent(dataSize, inputLayer1, inputLayer2, expectedOutput, GradientSignal::MixedGradientLayer::MixingOperation::Screen, 1.0f);
+    }
+
     TEST_F(GradientSignalReferencesTestsFixture, MixedGradientComponent_OperationAverage)
     {
         // Mixed Gradient:  Create two layers and set the second one to blend with "Average".