diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/MixedGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/MixedGradientComponent.h index 9c9867cf1e..658118fca4 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/MixedGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/MixedGradientComponent.h @@ -99,6 +99,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; bool IsEntityInHierarchy(const AZ::EntityId& entityId) const override; protected: @@ -110,6 +111,34 @@ namespace GradientSignal MixedGradientLayer* GetLayer(int layerIndex) override; private: + static float PerformMixingOperation(MixedGradientLayer::MixingOperation operation, float prevValue, float currentUnpremultiplied) + { + switch (operation) + { + case MixedGradientLayer::MixingOperation::Initialize: + return currentUnpremultiplied; + case MixedGradientLayer::MixingOperation::Multiply: + return prevValue * currentUnpremultiplied; + case MixedGradientLayer::MixingOperation::Add: + return prevValue + currentUnpremultiplied; + case MixedGradientLayer::MixingOperation::Subtract: + return prevValue - currentUnpremultiplied; + case MixedGradientLayer::MixingOperation::Min: + return AZStd::min(prevValue, currentUnpremultiplied); + case MixedGradientLayer::MixingOperation::Max: + return AZStd::max(prevValue, currentUnpremultiplied); + case MixedGradientLayer::MixingOperation::Average: + return (prevValue + currentUnpremultiplied) / 2.0f; + case MixedGradientLayer::MixingOperation::Normal: + return currentUnpremultiplied; + case MixedGradientLayer::MixingOperation::Overlay: + return (prevValue >= 0.5f) ? (1.0f - (2.0f * (1.0f - prevValue) * (1.0f - currentUnpremultiplied))) + : (2.0f * prevValue * currentUnpremultiplied); + default: + return currentUnpremultiplied; + } + } + MixedGradientConfig m_configuration; LmbrCentral::DependencyMonitor m_dependencyMonitor; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/PosterizeGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/PosterizeGradientComponent.h index 9b0714b449..bff49ac619 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/PosterizeGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/PosterizeGradientComponent.h @@ -73,6 +73,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; bool IsEntityInHierarchy(const AZ::EntityId& entityId) const override; protected: @@ -86,6 +87,39 @@ namespace GradientSignal GradientSampler& GetGradientSampler() override; private: + + static float PosterizeValue(float input, float bands, PosterizeGradientConfig::ModeType mode) + { + const float clampedInput = AZ::GetClamp(input, 0.0f, 1.0f); + float output = 0.0f; + + // "quantize" the input down to a number that goes from 0 to (bands-1) + const float band = AZ::GetMin(floorf(clampedInput * bands), bands - 1.0f); + + // Given our quantized band, produce the right output for that band range. + switch (mode) + { + default: + case PosterizeGradientConfig::ModeType::Floor: + // Floor: the output range should be the lowest value of each band, or (0 to bands-1) / bands + output = (band + 0.0f) / bands; + break; + case PosterizeGradientConfig::ModeType::Round: + // Round: the output range should be the midpoint of each band, or (0.5 to bands-0.5) / bands + output = (band + 0.5f) / bands; + break; + case PosterizeGradientConfig::ModeType::Ceiling: + // Ceiling: the output range should be the highest value of each band, or (1 to bands) / bands + output = (band + 1.0f) / bands; + break; + case PosterizeGradientConfig::ModeType::Ps: + // Ps: the output range should be equally distributed from 0-1, or (0 to bands-1) / (bands-1) + output = band / (bands - 1.0f); + break; + } + return AZ::GetMin(output, 1.0f); + } + PosterizeGradientConfig m_configuration; LmbrCentral::DependencyMonitor m_dependencyMonitor; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ReferenceGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ReferenceGradientComponent.h index bf16b484fc..17c40865b3 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ReferenceGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ReferenceGradientComponent.h @@ -64,6 +64,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; bool IsEntityInHierarchy(const AZ::EntityId& entityId) const override; protected: diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SmoothStepGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SmoothStepGradientComponent.h index 85947175af..03f629ab1e 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SmoothStepGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SmoothStepGradientComponent.h @@ -71,6 +71,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; bool IsEntityInHierarchy(const AZ::EntityId& entityId) const override; protected: diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h index 6ed841dafb..eb76fac292 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace LmbrCentral { @@ -89,6 +90,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; protected: ////////////////////////////////////////////////////////////////////////// @@ -105,7 +107,22 @@ namespace GradientSignal void AddTag(AZStd::string tag) override; private: - mutable AZStd::recursive_mutex m_cacheMutex; + static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax) + { + if (points.empty()) + { + return 0.0f; + } + + // GetSurfacePoints (which was used to populate the points list) always returns points in decreasing height order, so the + // first point in the list contains the highest altitude. + const float highestAltitude = points.front().m_position.GetZ(); + + // Turn the absolute altitude value into a 0-1 value by returning the % of the given altitude range that it falls at. + return GetRatio(altitudeMin, altitudeMax, highestAltitude); + } + + mutable AZStd::shared_mutex m_cacheMutex; SurfaceAltitudeGradientConfig m_configuration; LmbrCentral::DependencyMonitor m_dependencyMonitor; AZStd::atomic_bool m_dirty{ false }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h index f5400719d0..3eafb8c115 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h @@ -70,6 +70,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; protected: ////////////////////////////////////////////////////////////////////////// @@ -80,6 +81,21 @@ namespace GradientSignal void AddTag(AZStd::string tag) override; private: + static float GetMaxSurfaceWeight(const SurfaceData::SurfacePointList& points) + { + float result = 0.0f; + + for (const auto& point : points) + { + for (const auto& [maskId, weight] : point.m_masks) + { + result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result); + } + } + + return result; + } + SurfaceMaskGradientConfig m_configuration; LmbrCentral::DependencyMonitor m_dependencyMonitor; }; diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h index b6805202f1..b464b6fb0f 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h @@ -14,6 +14,7 @@ #include #include #include +#include namespace LmbrCentral { @@ -91,6 +92,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; protected: ////////////////////////////////////////////////////////////////////////// @@ -121,6 +123,37 @@ namespace GradientSignal void SetFallOffMidpoint(float midpoint) override; private: + float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const + { + if (points.empty()) + { + return 0.0f; + } + + // Assuming our surface normal vector is actually normalized, we can get the slope + // by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()). + AZ_Assert( + points.front().m_normal.GetNormalized().IsClose(points.front().m_normal), + "Surface normals are expected to be normalized"); + const float slope = points.front().m_normal.GetZ(); + // Convert slope back to an angle so that we can lerp in "angular space", not "slope value space". + // (We want our 0-1 range to be linear across the range of angles) + const float slopeAngle = acosf(slope); + + switch (m_configuration.m_rampType) + { + case SurfaceSlopeGradientConfig::RampType::SMOOTH_STEP: + return m_configuration.m_smoothStep.GetSmoothedValue(GetRatio(angleMin, angleMax, slopeAngle)); + case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_UP: + // For ramp up, linearly interpolate from min to max. + return GetRatio(angleMin, angleMax, slopeAngle); + case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_DOWN: + default: + // For ramp down, linearly interpolate from max to min. + return GetRatio(angleMax, angleMin, slopeAngle); + } + } + SurfaceSlopeGradientConfig m_configuration; }; } diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ThresholdGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ThresholdGradientComponent.h index dbf211551c..96bc235ea8 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/ThresholdGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/ThresholdGradientComponent.h @@ -65,6 +65,7 @@ namespace GradientSignal ////////////////////////////////////////////////////////////////////////// // GradientRequestBus float GetValue(const GradientSampleParams& sampleParams) const override; + void GetValues(AZStd::span positions, AZStd::span outValues) const override; bool IsEntityInHierarchy(const AZ::EntityId& entityId) const override; protected: diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h b/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h index c9aa7f680c..bf5c8d1ea0 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/GradientSampler.h @@ -152,7 +152,7 @@ namespace GradientSignal auto ClearOutputValues = [](AZStd::span outValues) { // If we don't have a valid gradient (or it is fully transparent), clear out all the output values. - memset(outValues.data(), 0, outValues.size() * sizeof(float)); + AZStd::fill(outValues.begin(), outValues.end(), 0.0f); }; if (m_opacity <= 0.0f || !m_gradientId.IsValid()) diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/SmoothStep.h b/Gems/GradientSignal/Code/Include/GradientSignal/SmoothStep.h index 0489fa4893..c8fc77ac60 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/SmoothStep.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/SmoothStep.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -26,29 +27,46 @@ namespace GradientSignal static void Reflect(AZ::ReflectContext* context); inline float GetSmoothedValue(float inputValue) const; + inline void GetSmoothedValues(AZStd::span inOutValues) const; float m_falloffMidpoint = 0.5f; float m_falloffRange = 0.5f; float m_falloffStrength = 0.25f; + + private: + inline float CalculateSmoothedValue(float min, float max, float valueFalloffStrength, float inputValue) const; }; - inline float SmoothStep::GetSmoothedValue(float inputValue) const + inline float SmoothStep::CalculateSmoothedValue(float min, float max, float valueFalloffStrength, float inputValue) const { - float output = 0.0f; - const float value = AZ::GetClamp(inputValue, 0.0f, 1.0f); - const float valueFalloffStrength = AZ::GetClamp(m_falloffStrength, 0.0f, 1.0f); - - float min = m_falloffMidpoint - m_falloffRange / 2.0f; - float max = m_falloffMidpoint + m_falloffRange / 2.0f; float result1 = GetRatio(min, min + valueFalloffStrength, value); result1 = GetSmoothStep(result1); float result2 = GetRatio(max - valueFalloffStrength, max, value); result2 = GetSmoothStep(result2); - output = result1 * (1.0f - result2); + return result1 * (1.0f - result2); + } + + inline float SmoothStep::GetSmoothedValue(float inputValue) const + { + const float min = m_falloffMidpoint - m_falloffRange / 2.0f; + const float max = m_falloffMidpoint + m_falloffRange / 2.0f; + const float valueFalloffStrength = AZ::GetClamp(m_falloffStrength, 0.0f, 1.0f); + + return CalculateSmoothedValue(min, max, valueFalloffStrength, inputValue); + } + + inline void SmoothStep::GetSmoothedValues(AZStd::span inOutValues) const + { + const float min = m_falloffMidpoint - m_falloffRange / 2.0f; + const float max = m_falloffMidpoint + m_falloffRange / 2.0f; + const float valueFalloffStrength = AZ::GetClamp(m_falloffStrength, 0.0f, 1.0f); - return output; + for (auto& inOutValue : inOutValues) + { + inOutValue = CalculateSmoothedValue(min, max, valueFalloffStrength, inOutValue); + } } -} +} // namespace GradientSignal diff --git a/Gems/GradientSignal/Code/Source/Components/MixedGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/MixedGradientComponent.cpp index e042188f8a..23bdd379fe 100644 --- a/Gems/GradientSignal/Code/Source/Components/MixedGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/MixedGradientComponent.cpp @@ -257,62 +257,80 @@ namespace GradientSignal float MixedGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { - AZ_PROFILE_FUNCTION(Entity); - //accumulate the mixed/combined result of all layers and operations float result = 0.0f; - float operationResult = 0.0f; for (const auto& layer : m_configuration.m_layers) { // added check to prevent opacity of 0.0, which will bust when we unpremultiply the alpha out if (layer.m_enabled && layer.m_gradientSampler.m_opacity != 0.0f) { + // Precalculate the inverse opacity that we'll use for blending the current accumulated value with. + // In the one case of "Initialize" blending, force this value to 0 so that we erase any accumulated values. + const float inverseOpacity = (layer.m_operation == MixedGradientLayer::MixingOperation::Initialize) + ? 0.0f + : (1.0f - layer.m_gradientSampler.m_opacity); + // this includes leveling and opacity result, we need unpremultiplied opacity to combine properly float current = layer.m_gradientSampler.GetValue(sampleParams); // unpremultiplied alpha (we clamp the end result) - float currentUnpremultiplied = current / layer.m_gradientSampler.m_opacity; - switch (layer.m_operation) - { - default: - case MixedGradientLayer::MixingOperation::Initialize: - //reset the result of the mixed/combined layers to the current value - result = 0.0f; - operationResult = currentUnpremultiplied; - break; - case MixedGradientLayer::MixingOperation::Multiply: - operationResult = result * currentUnpremultiplied; - break; - case MixedGradientLayer::MixingOperation::Add: - operationResult = result + currentUnpremultiplied; - break; - case MixedGradientLayer::MixingOperation::Subtract: - operationResult = result - currentUnpremultiplied; - break; - case MixedGradientLayer::MixingOperation::Min: - operationResult = AZStd::min(currentUnpremultiplied, result); - break; - case MixedGradientLayer::MixingOperation::Max: - operationResult = AZStd::max(currentUnpremultiplied, result); - break; - case MixedGradientLayer::MixingOperation::Average: - operationResult = (result + currentUnpremultiplied) / 2.0f; - break; - case MixedGradientLayer::MixingOperation::Normal: - operationResult = currentUnpremultiplied; - break; - case MixedGradientLayer::MixingOperation::Overlay: - operationResult = (result >= 0.5f) ? (1.0f - (2.0f * (1.0f - result) * (1.0f - currentUnpremultiplied))) : (2.0f * result * currentUnpremultiplied); - break; - } + const float currentUnpremultiplied = current / layer.m_gradientSampler.m_opacity; + const float operationResult = PerformMixingOperation(layer.m_operation, result, currentUnpremultiplied); // blend layers (re-applying opacity, which is why we needed to use unpremultiplied) - result = (result * (1.0f - layer.m_gradientSampler.m_opacity)) + (operationResult * layer.m_gradientSampler.m_opacity); + result = (result * inverseOpacity) + (operationResult * layer.m_gradientSampler.m_opacity); } } return AZ::GetClamp(result, 0.0f, 1.0f); } + void MixedGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } + + // Initialize all of our output data to 0.0f. Layer blends will combine with this, so we need it to have an initial value. + AZStd::fill(outValues.begin(), outValues.end(), 0.0f); + + AZStd::vector layerValues(positions.size()); + + // accumulate the mixed/combined result of all layers and operations + for (const auto& layer : m_configuration.m_layers) + { + // added check to prevent opacity of 0.0, which will bust when we unpremultiply the alpha out + if (layer.m_enabled && layer.m_gradientSampler.m_opacity != 0.0f) + { + // Precalculate the inverse opacity that we'll use for blending the current accumulated value with. + // In the one case of "Initialize" blending, force this value to 0 so that we erase any accumulated values. + const float inverseOpacity = (layer.m_operation == MixedGradientLayer::MixingOperation::Initialize) + ? 0.0f + : (1.0f - layer.m_gradientSampler.m_opacity); + + // this includes leveling and opacity result, we need unpremultiplied opacity to combine properly + layer.m_gradientSampler.GetValues(positions, layerValues); + + for (size_t index = 0; index < outValues.size(); index++) + { + // unpremultiplied alpha (we clamp the end result) + const float currentUnpremultiplied = layerValues[index] / layer.m_gradientSampler.m_opacity; + const float operationResult = PerformMixingOperation(layer.m_operation, outValues[index], currentUnpremultiplied); + // blend layers (re-applying opacity, which is why we needed to use unpremultiplied) + outValues[index] = (outValues[index] * inverseOpacity) + (operationResult * layer.m_gradientSampler.m_opacity); + } + } + } + + for (auto& outValue : outValues) + { + outValue = AZ::GetClamp(outValue, 0.0f, 1.0f); + } + } + + + bool MixedGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const { for (const auto& layer : m_configuration.m_layers) diff --git a/Gems/GradientSignal/Code/Source/Components/PosterizeGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/PosterizeGradientComponent.cpp index 21d321df13..4616e080f1 100644 --- a/Gems/GradientSignal/Code/Source/Components/PosterizeGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/PosterizeGradientComponent.cpp @@ -151,34 +151,28 @@ namespace GradientSignal float PosterizeGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { const float bands = AZ::GetMax(static_cast(m_configuration.m_bands), 2.0f); - const float input = AZ::GetClamp(m_configuration.m_gradientSampler.GetValue(sampleParams), 0.0f, 1.0f); - float output = 0.0f; + const float input = m_configuration.m_gradientSampler.GetValue(sampleParams); + return PosterizeValue(input, bands, m_configuration.m_mode); + } + + void PosterizeGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } + + const float bands = AZ::GetMax(static_cast(m_configuration.m_bands), 2.0f); - // "quantize" the input down to a number that goes from 0 to (bands-1) - const float band = AZ::GetClamp(floorf(input * bands), 0.0f, bands - 1.0f); + // Fill in the outValues with all of the generated inupt gradient values. + m_configuration.m_gradientSampler.GetValues(positions, outValues); - // Given our quantized band, produce the right output for that band range. - switch (m_configuration.m_mode) + // Run through all the input values and posterize them. + for (auto& outValue : outValues) { - default: - case PosterizeGradientConfig::ModeType::Floor: - // Floor: the output range should be the lowest value of each band, or (0 to bands-1) / bands - output = (band + 0.0f) / bands; - break; - case PosterizeGradientConfig::ModeType::Round: - // Round: the output range should be the midpoint of each band, or (0.5 to bands-0.5) / bands - output = (band + 0.5f) / bands; - break; - case PosterizeGradientConfig::ModeType::Ceiling: - // Ceiling: the output range should be the highest value of each band, or (1 to bands) / bands - output = (band + 1.0f) / bands; - break; - case PosterizeGradientConfig::ModeType::Ps: - // Ps: the output range should be equally distributed from 0-1, or (0 to bands-1) / (bands-1) - output = band / (bands - 1.0f); - break; + outValue = PosterizeValue(outValue, bands, m_configuration.m_mode); } - return AZ::GetClamp(output, 0.0f, 1.0f); } bool PosterizeGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const diff --git a/Gems/GradientSignal/Code/Source/Components/ReferenceGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ReferenceGradientComponent.cpp index ea304a7eed..e135401ff5 100644 --- a/Gems/GradientSignal/Code/Source/Components/ReferenceGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ReferenceGradientComponent.cpp @@ -131,13 +131,18 @@ namespace GradientSignal float ReferenceGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { - AZ_PROFILE_FUNCTION(Entity); - - float output = 0.0f; + return m_configuration.m_gradientSampler.GetValue(sampleParams); + } - output = m_configuration.m_gradientSampler.GetValue(sampleParams); + void ReferenceGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } - return output; + m_configuration.m_gradientSampler.GetValues(positions, outValues); } bool ReferenceGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const diff --git a/Gems/GradientSignal/Code/Source/Components/SmoothStepGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SmoothStepGradientComponent.cpp index 510ed41510..683f0a37fa 100644 --- a/Gems/GradientSignal/Code/Source/Components/SmoothStepGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SmoothStepGradientComponent.cpp @@ -168,12 +168,20 @@ namespace GradientSignal float SmoothStepGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { - float output = 0.0f; + const float value = m_configuration.m_gradientSampler.GetValue(sampleParams); + return m_configuration.m_smoothStep.GetSmoothedValue(value); + } - const float value = AZ::GetClamp(m_configuration.m_gradientSampler.GetValue(sampleParams), 0.0f, 1.0f); - output = m_configuration.m_smoothStep.GetSmoothedValue(value); + void SmoothStepGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } - return output; + m_configuration.m_gradientSampler.GetValues(positions, outValues); + m_configuration.m_smoothStep.GetSmoothedValues(outValues); } bool SmoothStepGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp index 8b36182750..ee6c272e40 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp @@ -202,19 +202,49 @@ namespace GradientSignal float SurfaceAltitudeGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { - AZStd::lock_guard lock(m_cacheMutex); + AZStd::shared_lock lock(m_cacheMutex); SurfaceData::SurfacePointList points; SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, sampleParams.m_position, m_configuration.m_surfaceTagsToSample, points); - if (points.empty()) + return CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax); + } + + void SurfaceAltitudeGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) { - return 0.0f; + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; } - const AZ::Vector3& position = points.front().m_position; - return GetRatio(m_configuration.m_altitudeMin, m_configuration.m_altitudeMax, position.GetZ()); + AZStd::shared_lock lock(m_cacheMutex); + bool valuesFound = false; + + // Rather than calling GetSurfacePoints on the EBus repeatedly in a loop, we instead pass a lambda into the EBus that contains + // the loop within it so that we can avoid the repeated EBus-calling overhead. + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + [this, positions, &outValues, &valuesFound](SurfaceData::SurfaceDataSystemRequestBus::Events* surfaceDataRequests) + { + // It's possible that there's nothing connected to the EBus, so keep track of the fact that we have valid results. + valuesFound = true; + SurfaceData::SurfacePointList points; + + // For each position, call GetSurfacePoints() and turn the height into a 0-1 value based on our min/max altitudes. + for (size_t index = 0; index < positions.size(); index++) + { + points.clear(); + surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); + outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax); + } + }); + + if (!valuesFound) + { + // No surface data, so no output values. + AZStd::fill(outValues.begin(), outValues.end(), 0.0f); + } } void SurfaceAltitudeGradientComponent::OnCompositionChanged() @@ -246,7 +276,7 @@ namespace GradientSignal { AZ_PROFILE_FUNCTION(Entity); - AZStd::lock_guard lock(m_cacheMutex); + AZStd::unique_lock lock(m_cacheMutex); if (m_configuration.m_shapeEntityId.IsValid()) { diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp index df7a3f5787..f697050f56 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp @@ -161,8 +161,6 @@ namespace GradientSignal float SurfaceMaskGradientComponent::GetValue(const GradientSampleParams& params) const { - AZ_PROFILE_FUNCTION(Entity); - float result = 0.0f; if (!m_configuration.m_surfaceTagList.empty()) @@ -171,18 +169,50 @@ namespace GradientSignal SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, params.m_position, m_configuration.m_surfaceTagList, points); - for (const auto& point : points) - { - for (const auto& maskPair : point.m_masks) - { - result = AZ::GetMax(AZ::GetClamp(maskPair.second, 0.0f, 1.0f), result); - } - } + result = GetMaxSurfaceWeight(points); } return result; } + void SurfaceMaskGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } + + bool valuesFound = false; + + if (!m_configuration.m_surfaceTagList.empty()) + { + // Rather than calling GetSurfacePoints on the EBus repeatedly in a loop, we instead pass a lambda into the EBus that contains + // the loop within it so that we can avoid the repeated EBus-calling overhead. + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + [this, positions, &outValues, &valuesFound](SurfaceData::SurfaceDataSystemRequestBus::Events* surfaceDataRequests) + { + // It's possible that there's nothing connected to the EBus, so keep track of the fact that we have valid results. + valuesFound = true; + SurfaceData::SurfacePointList points; + + for (size_t index = 0; index < positions.size(); index++) + { + points.clear(); + surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points); + outValues[index] = GetMaxSurfaceWeight(points); + } + }); + } + + if (!valuesFound) + { + // No surface tags, so no output values. + AZStd::fill(outValues.begin(), outValues.end(), 0.0f); + } + + } + size_t SurfaceMaskGradientComponent::GetNumTags() const { return m_configuration.GetNumTags(); diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp index 84e0b61a62..50105a862f 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp @@ -209,36 +209,50 @@ namespace GradientSignal SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, sampleParams.m_position, m_configuration.m_surfaceTagsToSample, points); - if (points.empty()) + const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f)); + const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f)); + + return GetSlopeRatio(points, angleMin, angleMax); + } + + void SurfaceSlopeGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) { - return 0.0f; + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; } - // Assuming our surface normal vector is actually normalized, we can get the slope - // by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()). - AZ_Assert(points.front().m_normal.GetNormalized().IsClose(points.front().m_normal), "Surface normals are expected to be normalized"); - const float slope = points.front().m_normal.GetZ(); - // Convert slope back to an angle so that we can lerp in "angular space", not "slope value space". - // (We want our 0-1 range to be linear across the range of angles) - const float slopeAngle = acosf(slope); + bool valuesFound = false; - const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f)); - const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f)); - - switch (m_configuration.m_rampType) + // Rather than calling GetSurfacePoints on the EBus repeatedly in a loop, we instead pass a lambda into the EBus that contains + // the loop within it so that we can avoid the repeated EBus-calling overhead. + SurfaceData::SurfaceDataSystemRequestBus::Broadcast( + [this, positions, &outValues, &valuesFound](SurfaceData::SurfaceDataSystemRequestBus::Events* surfaceDataRequests) + { + // It's possible that there's nothing connected to the EBus, so keep track of the fact that we have valid results. + valuesFound = true; + SurfaceData::SurfacePointList points; + + const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f)); + const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f)); + + for (size_t index = 0; index < positions.size(); index++) + { + points.clear(); + surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); + outValues[index] = GetSlopeRatio(points, angleMin, angleMax); + } + }); + + if (!valuesFound) { - case SurfaceSlopeGradientConfig::RampType::SMOOTH_STEP: - return m_configuration.m_smoothStep.GetSmoothedValue(GetRatio(angleMin, angleMax, slopeAngle)); - case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_UP: - // For ramp up, linearly interpolate from min to max. - return GetRatio(angleMin, angleMax, slopeAngle); - case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_DOWN: - default: - // For ramp down, linearly interpolate from max to min. - return GetRatio(angleMax, angleMin, slopeAngle); + // No surface tags, so no output values. + AZStd::fill(outValues.begin(), outValues.end(), 0.0f); } } + float SurfaceSlopeGradientComponent::GetSlopeMin() const { return m_configuration.m_slopeMin; diff --git a/Gems/GradientSignal/Code/Source/Components/ThresholdGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/ThresholdGradientComponent.cpp index a47ebdebe6..5df579576d 100644 --- a/Gems/GradientSignal/Code/Source/Components/ThresholdGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/ThresholdGradientComponent.cpp @@ -138,11 +138,22 @@ namespace GradientSignal float ThresholdGradientComponent::GetValue(const GradientSampleParams& sampleParams) const { - float output = 0.0f; + return (m_configuration.m_gradientSampler.GetValue(sampleParams) <= m_configuration.m_threshold) ? 0.0f : 1.0f; + } - output = m_configuration.m_gradientSampler.GetValue(sampleParams) <= m_configuration.m_threshold ? 0.0f : 1.0f; + void ThresholdGradientComponent::GetValues(AZStd::span positions, AZStd::span outValues) const + { + if (positions.size() != outValues.size()) + { + AZ_Assert(false, "input and output lists are different sizes (%zu vs %zu).", positions.size(), outValues.size()); + return; + } - return output; + m_configuration.m_gradientSampler.GetValues(positions, outValues); + for (auto& outValue : outValues) + { + outValue = (outValue <= m_configuration.m_threshold) ? 0.0f : 1.0f; + } } bool ThresholdGradientComponent::IsEntityInHierarchy(const AZ::EntityId& entityId) const