Browse Source

[Terrain] Auto-scaling brush size ranges (#13042)

* Adjustable brush size ranges.
We can use this to clamp the min/max sizes based on painting target size so that we don't crush performance or allow brushes that are so tiny they slip "between" the pixels.

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

* Don't expand empty dirty regions.

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

Signed-off-by: Mike Balfour <[email protected]>
Mike Balfour 2 years ago
parent
commit
c2f22fd94a

+ 35 - 6
Code/Framework/AzToolsFramework/AzToolsFramework/PaintBrushSettings/PaintBrushSettings.cpp

@@ -88,11 +88,9 @@ namespace AzToolsFramework
                     ->DataElement(
                     ->DataElement(
                         AZ::Edit::UIHandlers::Slider, &PaintBrushSettings::m_size, "Size",
                         AZ::Edit::UIHandlers::Slider, &PaintBrushSettings::m_size, "Size",
                         "Size/diameter of the brush stamp in meters.")
                         "Size/diameter of the brush stamp in meters.")
-                        ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
-                        ->Attribute(AZ::Edit::Attributes::SoftMin, 1.0f)
-                        ->Attribute(AZ::Edit::Attributes::Max, 1024.0f)
-                        ->Attribute(AZ::Edit::Attributes::SoftMax, 100.0f)
-                        ->Attribute(AZ::Edit::Attributes::Step, 0.25f)
+                        ->Attribute(AZ::Edit::Attributes::Min, &PaintBrushSettings::GetSizeMin)
+                        ->Attribute(AZ::Edit::Attributes::Max, &PaintBrushSettings::GetSizeMax)
+                        ->Attribute(AZ::Edit::Attributes::Step, &PaintBrushSettings::GetSizeStep)
                         ->Attribute(AZ::Edit::Attributes::DisplayDecimals, 2)
                         ->Attribute(AZ::Edit::Attributes::DisplayDecimals, 2)
                         ->Attribute(AZ::Edit::Attributes::Suffix, " m")
                         ->Attribute(AZ::Edit::Attributes::Suffix, " m")
                         ->Attribute(AZ::Edit::Attributes::Visibility, &PaintBrushSettings::GetSizeVisibility)
                         ->Attribute(AZ::Edit::Attributes::Visibility, &PaintBrushSettings::GetSizeVisibility)
@@ -258,6 +256,37 @@ namespace AzToolsFramework
         return true;
         return true;
     }
     }
 
 
+    // Make the brush size ranges configurable so that it can be appropriate for whatever type of data is being painted.
+
+    float PaintBrushSettings::GetSizeMin() const
+    {
+        return m_sizeMin;
+    }
+
+    float PaintBrushSettings::GetSizeMax() const
+    {
+        return m_sizeMax;
+    }
+
+    float PaintBrushSettings::GetSizeStep() const
+    {
+        // Set the step size to give us 100 values across the range.
+        // This is an arbitrary choice, but it seems like a good number of step sizes for a slider control.
+        return (GetSizeMax() - GetSizeMin()) / 100.0f;
+    }
+
+    void PaintBrushSettings::SetSizeRange(float minSize, float maxSize)
+    {
+        // Make sure the min and max sizes are valid ranges
+        m_sizeMax = AZStd::max(maxSize, 0.0f);
+        m_sizeMin = AZStd::clamp(minSize, 0.0f, m_sizeMax);
+
+        // Clamp our current paintbrush size to fall within the new min/max ranges.
+        m_size = AZStd::clamp(m_size, m_sizeMin, m_sizeMax);
+
+        PaintBrushSettingsNotificationBus::Broadcast(&PaintBrushSettingsNotificationBus::Events::OnVisiblePropertiesChanged);
+        OnSettingsChanged();
+    }
 
 
     void PaintBrushSettings::SetBrushMode(PaintBrushMode brushMode)
     void PaintBrushSettings::SetBrushMode(PaintBrushMode brushMode)
     {
     {
@@ -298,7 +327,7 @@ namespace AzToolsFramework
 
 
     void PaintBrushSettings::SetSize(float size)
     void PaintBrushSettings::SetSize(float size)
     {
     {
-        m_size = AZStd::max(size, 0.0f);
+        m_size = AZStd::clamp(size, m_sizeMin, m_sizeMax);
         OnSettingsChanged();
         OnSettingsChanged();
     }
     }
 
 

+ 13 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/PaintBrushSettings/PaintBrushSettings.h

@@ -143,6 +143,11 @@ namespace AzToolsFramework
             return m_size;
             return m_size;
         }
         }
 
 
+        AZStd::pair<float, float> GetSizeRange() const
+        {
+            return { m_sizeMin, m_sizeMax };
+        }
+
         float GetHardnessPercent() const
         float GetHardnessPercent() const
         {
         {
             return m_hardnessPercent;
             return m_hardnessPercent;
@@ -157,6 +162,7 @@ namespace AzToolsFramework
         }
         }
 
 
         void SetSize(float size);
         void SetSize(float size);
+        void SetSizeRange(float minSize, float maxSize);
         void SetHardnessPercent(float hardnessPercent);
         void SetHardnessPercent(float hardnessPercent);
         void SetFlowPercent(float flowPercent);
         void SetFlowPercent(float flowPercent);
         void SetDistancePercent(float distancePercent);
         void SetDistancePercent(float distancePercent);
@@ -182,6 +188,10 @@ namespace AzToolsFramework
         bool GetBlendModeVisibility() const;
         bool GetBlendModeVisibility() const;
         bool GetSmoothModeVisibility() const;
         bool GetSmoothModeVisibility() const;
 
 
+        float GetSizeMin() const;
+        float GetSizeMax() const;
+        float GetSizeStep() const;
+
         //! Brush settings brush mode
         //! Brush settings brush mode
         PaintBrushMode m_brushMode = PaintBrushMode::Paintbrush;
         PaintBrushMode m_brushMode = PaintBrushMode::Paintbrush;
 
 
@@ -197,6 +207,9 @@ namespace AzToolsFramework
 
 
         //! Brush stamp diameter in meters
         //! Brush stamp diameter in meters
         float m_size = 10.0f;
         float m_size = 10.0f;
+        float m_sizeMin = 0.0f;
+        float m_sizeMax = 1024.0f;
+
         //! Brush stamp hardness percent (0=soft falloff, 100=hard edge)
         //! Brush stamp hardness percent (0=soft falloff, 100=hard edge)
         float m_hardnessPercent = 100.0f;
         float m_hardnessPercent = 100.0f;
         //! Brush stamp flow percent (0=transparent stamps, 100=opaque stamps)
         //! Brush stamp flow percent (0=transparent stamps, 100=opaque stamps)

+ 13 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/PaintBrushSettings/PaintBrushSettingsRequestBus.h

@@ -73,6 +73,12 @@ namespace AzToolsFramework
         //! @return The size of the paintbrush in meters
         //! @return The size of the paintbrush in meters
         virtual float GetSize() const = 0;
         virtual float GetSize() const = 0;
 
 
+        //! Returns the brush stamp min/max size range.
+        //! The range is used to ensure that our brush size is appropriately sized relative to the world size of the data we're painting.
+        //! If we let it get too big, we can run into serious performance issues.
+        //! @return A pair containing the min size and max size that constrain the range of sizes in meters for the paintbrush.
+        virtual AZStd::pair<float, float> GetSizeRange() const = 0;
+
         //! Returns the brush stamp hardness (0=soft falloff, 100=hard edge).
         //! Returns the brush stamp hardness (0=soft falloff, 100=hard edge).
         virtual float GetHardnessPercent() const = 0;
         virtual float GetHardnessPercent() const = 0;
 
 
@@ -86,6 +92,13 @@ namespace AzToolsFramework
         //! @param size The new size, in meters.
         //! @param size The new size, in meters.
         virtual void SetSize(float size) = 0;
         virtual void SetSize(float size) = 0;
 
 
+        //! Sets the brush stamp min/max size range.
+        //! The range is used to ensure that our brush size is appropriately sized relative to the world size of the data we're painting.
+        //! If we let it get too big, we can run into serious performance issues.
+        //! @param minSize The minimum size of the paint brush in meters.
+        //! @param maxSize The maximum size of the paint brush in meters.
+        virtual void SetSizeRange(float minSize, float maxSize) = 0;
+
         //! Sets the brush stamp hardness.
         //! Sets the brush stamp hardness.
         //! @param hardness The new hardness, in 0-100 range.
         //! @param hardness The new hardness, in 0-100 range.
         virtual void SetHardnessPercent(float hardnessPercent) = 0;
         virtual void SetHardnessPercent(float hardnessPercent) = 0;

+ 10 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/PaintBrushSettings/PaintBrushSettingsSystemComponent.cpp

@@ -71,6 +71,11 @@ namespace AzToolsFramework
         return m_settings.GetSize();
         return m_settings.GetSize();
     }
     }
 
 
+    AZStd::pair<float, float> PaintBrushSettingsSystemComponent::GetSizeRange() const
+    {
+        return m_settings.GetSizeRange();
+    }
+
     AZ::Color PaintBrushSettingsSystemComponent::GetColor() const
     AZ::Color PaintBrushSettingsSystemComponent::GetColor() const
     {
     {
         return m_settings.GetColor();
         return m_settings.GetColor();
@@ -106,6 +111,11 @@ namespace AzToolsFramework
         m_settings.SetSize(size);
         m_settings.SetSize(size);
     }
     }
 
 
+    void PaintBrushSettingsSystemComponent::SetSizeRange(float minSize, float maxSize)
+    {
+        m_settings.SetSizeRange(minSize, maxSize);
+    }
+
     void PaintBrushSettingsSystemComponent::SetColor(const AZ::Color& color)
     void PaintBrushSettingsSystemComponent::SetColor(const AZ::Color& color)
     {
     {
         m_settings.SetColor(color);
         m_settings.SetColor(color);

+ 2 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/PaintBrushSettings/PaintBrushSettingsSystemComponent.h

@@ -40,6 +40,7 @@ namespace AzToolsFramework
         PaintBrushColorMode GetBrushColorMode() const override;
         PaintBrushColorMode GetBrushColorMode() const override;
         void SetBrushColorMode(PaintBrushColorMode colorMode) override;
         void SetBrushColorMode(PaintBrushColorMode colorMode) override;
         float GetSize() const override;
         float GetSize() const override;
+        AZStd::pair<float, float> GetSizeRange() const override;
         AZ::Color GetColor() const override;
         AZ::Color GetColor() const override;
         float GetHardnessPercent() const override;
         float GetHardnessPercent() const override;
         float GetFlowPercent() const override;
         float GetFlowPercent() const override;
@@ -47,6 +48,7 @@ namespace AzToolsFramework
         PaintBrushBlendMode GetBlendMode() const override;
         PaintBrushBlendMode GetBlendMode() const override;
         PaintBrushSmoothMode GetSmoothMode() const override;
         PaintBrushSmoothMode GetSmoothMode() const override;
         void SetSize(float size) override;
         void SetSize(float size) override;
+        void SetSizeRange(float minSize, float maxSize) override;
         void SetColor(const AZ::Color& color) override;
         void SetColor(const AZ::Color& color) override;
         void SetHardnessPercent(float hardnessPercent) override;
         void SetHardnessPercent(float hardnessPercent) override;
         void SetFlowPercent(float flowPercent) override;
         void SetFlowPercent(float flowPercent) override;

+ 40 - 10
Gems/GradientSignal/Code/Source/Editor/EditorImageGradientComponentMode.cpp

@@ -302,6 +302,25 @@ namespace GradientSignal
 
 
         AzToolsFramework::PaintBrushNotificationBus::Handler::BusConnect(entityComponentIdPair);
         AzToolsFramework::PaintBrushNotificationBus::Handler::BusConnect(entityComponentIdPair);
 
 
+        // Set our paint brush min/max world size range. The minimum size should be large enough to paint at least one pixel, and
+        // the max size is clamped so that we can't paint more than 256 x 256 pixels per brush stamp.
+        // 256 is an arbitrary number, but if we start getting much larger, performance can drop precipitously.
+        // Note: To truly control performance, additional clamping is still needed, because large mouse movements in world space with
+        // a tiny brush can still cause extremely large numbers of brush points to get calculated and checked.
+
+        constexpr float MaxBrushPixelSize = 256.0f;
+        AZ::Vector2 imagePixelsPerMeter(0.0f);
+        ImageGradientRequestBus::EventResult(imagePixelsPerMeter, GetEntityId(), &ImageGradientRequestBus::Events::GetImagePixelsPerMeter);
+
+        float minBrushSize = AZStd::min(imagePixelsPerMeter.GetX(), imagePixelsPerMeter.GetY());
+        float maxBrushSize = AZStd::max(imagePixelsPerMeter.GetX(), imagePixelsPerMeter.GetY());
+
+        minBrushSize = (minBrushSize <= 0.0f) ? 0.0f : (1.0f / minBrushSize);
+        maxBrushSize = (maxBrushSize <= 0.0f) ? 0.0f : (MaxBrushPixelSize / maxBrushSize);
+
+        AzToolsFramework::PaintBrushSettingsRequestBus::Broadcast(
+            &AzToolsFramework::PaintBrushSettingsRequestBus::Events::SetSizeRange, minBrushSize, maxBrushSize);
+
         AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
         AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
         AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformInterface::GetWorldTM);
         AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformInterface::GetWorldTM);
 
 
@@ -320,6 +339,7 @@ namespace GradientSignal
 
 
         AzToolsFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
         AzToolsFramework::PaintBrushNotificationBus::Handler::BusDisconnect();
         m_brushManipulator->Unregister();
         m_brushManipulator->Unregister();
+        m_brushManipulator.reset();
 
 
         EndUndoBatch();
         EndUndoBatch();
 
 
@@ -410,16 +430,20 @@ namespace GradientSignal
     {
     {
         AZ_Assert(m_paintBrushUndoBuffer != nullptr, "Undo batch is expected to exist while painting");
         AZ_Assert(m_paintBrushUndoBuffer != nullptr, "Undo batch is expected to exist while painting");
 
 
-        // Expand the dirty region for this brush stroke by one pixel in each direction
-        // to account for any data affected by bilinear filtering as well.
-        m_paintStrokeData.m_dirtyRegion.Expand(AZ::Vector3(m_paintStrokeData.m_metersPerPixelX, m_paintStrokeData.m_metersPerPixelY, 0.0f));
-
-        // Expand the dirty region to encompass the full Z range since image gradients are 2D.
-        auto dirtyRegionMin = m_paintStrokeData.m_dirtyRegion.GetMin();
-        auto dirtyRegionMax = m_paintStrokeData.m_dirtyRegion.GetMax();
-        m_paintStrokeData.m_dirtyRegion.Set(
-            AZ::Vector3(dirtyRegionMin.GetX(), dirtyRegionMin.GetY(), AZStd::numeric_limits<float>::lowest()),
-            AZ::Vector3(dirtyRegionMax.GetX(), dirtyRegionMax.GetY(), AZStd::numeric_limits<float>::max()));
+        if (m_paintStrokeData.m_dirtyRegion.IsValid())
+        {
+            // Expand the dirty region for this brush stroke by one pixel in each direction
+            // to account for any data affected by bilinear filtering as well.
+            m_paintStrokeData.m_dirtyRegion.Expand(
+                AZ::Vector3(m_paintStrokeData.m_metersPerPixelX, m_paintStrokeData.m_metersPerPixelY, 0.0f));
+
+            // Expand the dirty region to encompass the full Z range since image gradients are 2D.
+            auto dirtyRegionMin = m_paintStrokeData.m_dirtyRegion.GetMin();
+            auto dirtyRegionMax = m_paintStrokeData.m_dirtyRegion.GetMax();
+            m_paintStrokeData.m_dirtyRegion.Set(
+                AZ::Vector3(dirtyRegionMin.GetX(), dirtyRegionMin.GetY(), AZStd::numeric_limits<float>::lowest()),
+                AZ::Vector3(dirtyRegionMax.GetX(), dirtyRegionMax.GetY(), AZStd::numeric_limits<float>::max()));
+        }
 
 
         // Hand over ownership of the paint stroke buffer to the undo/redo buffer.
         // Hand over ownership of the paint stroke buffer to the undo/redo buffer.
         m_paintBrushUndoBuffer->SetUndoBufferAndDirtyArea(
         m_paintBrushUndoBuffer->SetUndoBufferAndDirtyArea(
@@ -457,6 +481,12 @@ namespace GradientSignal
         const int32_t xPoints = aznumeric_cast<int32_t>((maxDistances.GetX() - minDistances.GetX()) / m_paintStrokeData.m_metersPerPixelX);
         const int32_t xPoints = aznumeric_cast<int32_t>((maxDistances.GetX() - minDistances.GetX()) / m_paintStrokeData.m_metersPerPixelX);
         const int32_t yPoints = aznumeric_cast<int32_t>((maxDistances.GetY() - minDistances.GetY()) / m_paintStrokeData.m_metersPerPixelY);
         const int32_t yPoints = aznumeric_cast<int32_t>((maxDistances.GetY() - minDistances.GetY()) / m_paintStrokeData.m_metersPerPixelY);
 
 
+        // Early out if the dirty area is smaller than our point size.
+        if ((xPoints <= 0) || (yPoints <= 0))
+        {
+            return;
+        }
+
         // Calculate the minimum set of world space points that map to those pixels.
         // Calculate the minimum set of world space points that map to those pixels.
         AZStd::vector<AZ::Vector3> points;
         AZStd::vector<AZ::Vector3> points;
         points.reserve(xPoints * yPoints);
         points.reserve(xPoints * yPoints);