From d9ba0af6454e80ba39e5d2d3da5443c49761de22 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:27:59 -0600 Subject: [PATCH] SurfacePoint data structure encapsulations (#7413) * First pass at encapsulating SurfacePointList. The biggest challenge in optimizing SurfacePointList(s) usage is the overall memory management associated with it. There are M surface points with N surface mask entries created for every input point, which leads to a lot of container reallocation and memory shuffling when processing multiple input points. By encapsulating the list, it should become easier to preallocate the entries, as well as keep "helper data" around for managing the bookkeeping to associate the input points with the output points. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Small fixes and TODO reminders. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Encapsulate surface point creation and separate EnumeratePoints out from modifications. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Start removing SurfacePoint from the exposed API. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Changed SurfacePointList to split out the surface point storage to allow for span<> usage over time. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed entity id Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Removed SurfacePoint from SurfaceData, changed all remaining uses to AzFramework::SurfaceData::SurfacePoint. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Encapsulated SurfaceTagWeightMap and renamed to SurfaceTagWeights. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed make file. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Better commenting and parameter naming. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Renamed methods to be more descriptive. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> --- .../SurfaceData/SurfaceDataMeshComponent.cpp | 9 +- .../SurfaceData/SurfaceDataMeshComponent.h | 2 +- .../SurfaceAltitudeGradientComponent.h | 4 +- .../Components/SurfaceMaskGradientComponent.h | 17 +- .../SurfaceSlopeGradientComponent.h | 6 +- .../GradientSurfaceDataComponent.cpp | 27 +- .../SurfaceAltitudeGradientComponent.cpp | 2 +- .../SurfaceMaskGradientComponent.cpp | 2 +- .../SurfaceSlopeGradientComponent.cpp | 2 +- .../EditorGradientSurfaceDataComponent.cpp | 19 +- .../Tests/GradientSignalReferencesTests.cpp | 31 +- .../Code/Tests/GradientSignalSurfaceTests.cpp | 129 ++++--- .../Code/Tests/GradientSignalTestFixtures.cpp | 5 +- Gems/SurfaceData/Code/CMakeLists.txt | 2 +- .../Include/SurfaceData/SurfaceDataTypes.h | 202 ++++++++++- .../SurfaceData/Utility/SurfaceDataUtility.h | 107 +----- .../SurfaceDataColliderComponent.cpp | 33 +- .../Components/SurfaceDataColliderComponent.h | 2 +- .../Components/SurfaceDataShapeComponent.cpp | 27 +- .../Components/SurfaceDataShapeComponent.h | 2 +- .../Source/SurfaceDataSystemComponent.cpp | 104 +----- .../Code/Source/SurfaceDataSystemComponent.h | 3 - .../Code/Source/SurfaceDataTypes.cpp | 319 ++++++++++++++++++ .../Code/Tests/SurfaceDataBenchmarks.cpp | 2 +- .../SurfaceDataColliderComponentTest.cpp | 117 ++++--- .../Code/Tests/SurfaceDataTest.cpp | 157 +++++---- Gems/SurfaceData/Code/surfacedata_files.cmake | 1 + .../TerrainSurfaceDataSystemComponent.cpp | 18 +- .../Vegetation/Ebuses/AreaRequestBus.h | 4 +- .../Code/Include/Vegetation/InstanceData.h | 2 +- .../Code/Source/AreaSystemComponent.cpp | 25 +- .../Components/PositionModifierComponent.cpp | 41 ++- .../Source/Components/SpawnerComponent.cpp | 4 +- .../SurfaceMaskDepthFilterComponent.cpp | 34 +- .../Components/SurfaceMaskFilterComponent.cpp | 10 +- .../Code/Source/Debugger/DebugComponent.cpp | 2 +- .../Tests/VegetationComponentFilterTests.cpp | 5 +- .../VegetationComponentModifierTests.cpp | 2 +- Gems/Vegetation/Code/Tests/VegetationMocks.h | 8 +- 39 files changed, 947 insertions(+), 541 deletions(-) create mode 100644 Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp index 71e880fdef..2686ca8a16 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.cpp @@ -95,7 +95,7 @@ namespace SurfaceData m_refresh = false; // Update the cached mesh data and bounds, then register the surface data provider - AssignSurfaceTagWeights(m_configuration.m_tags, 1.0f, m_newPointWeights); + m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_tags, 1.0f); UpdateMeshData(); } @@ -178,12 +178,7 @@ namespace SurfaceData AZ::Vector3 hitNormal; if (DoRayTrace(inPosition, hitPosition, hitNormal)) { - SurfacePoint point; - point.m_entityId = GetEntityId(); - point.m_position = hitPosition; - point.m_normal = hitNormal; - point.m_masks = m_newPointWeights; - surfacePointList.push_back(AZStd::move(point)); + surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights); } } diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h index 758bb1ee99..4a8d3cf427 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/SurfaceData/SurfaceDataMeshComponent.h @@ -103,6 +103,6 @@ namespace SurfaceData AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity(); AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne(); AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull(); - SurfaceTagWeightMap m_newPointWeights; + SurfaceTagWeights m_newPointWeights; }; } diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h index 6843be8a0e..d8e902a0d5 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceAltitudeGradientComponent.h @@ -109,14 +109,14 @@ namespace GradientSignal private: static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax) { - if (points.empty()) + if (points.IsEmpty()) { 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(); + const float highestAltitude = points.GetHighestSurfacePoint().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); diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h index 2580da10a0..eb009e6b92 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceMaskGradientComponent.h @@ -85,13 +85,18 @@ namespace GradientSignal { float result = 0.0f; - for (const auto& point : points) + points.EnumeratePoints([&result]( + [[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) -> bool { - for (const auto& [maskId, weight] : point.m_masks) - { - result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result); - } - } + masks.EnumerateWeights( + [&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool + { + result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result); + return true; + }); + return true; + }); return result; } diff --git a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h index bdfbcd2b7f..e7a55a2bbc 100644 --- a/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h +++ b/Gems/GradientSignal/Code/Include/GradientSignal/Components/SurfaceSlopeGradientComponent.h @@ -125,7 +125,7 @@ namespace GradientSignal private: float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const { - if (points.empty()) + if (points.IsEmpty()) { return 0.0f; } @@ -133,9 +133,9 @@ namespace GradientSignal // 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), + points.GetHighestSurfacePoint().m_normal.GetNormalized().IsClose(points.GetHighestSurfacePoint().m_normal), "Surface normals are expected to be normalized"); - const float slope = points.front().m_normal.GetZ(); + const float slope = points.GetHighestSurfacePoint().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); diff --git a/Gems/GradientSignal/Code/Source/Components/GradientSurfaceDataComponent.cpp b/Gems/GradientSignal/Code/Source/Components/GradientSurfaceDataComponent.cpp index 5a0555fe33..cd87e3044c 100644 --- a/Gems/GradientSignal/Code/Source/Components/GradientSurfaceDataComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/GradientSurfaceDataComponent.cpp @@ -224,10 +224,9 @@ namespace GradientSignal validShapeBounds = m_cachedShapeConstraintBounds.IsValid(); } - const AZ::EntityId entityId = GetEntityId(); - for (auto& point : surfacePointList) - { - if (point.m_entityId != entityId) + surfacePointList.ModifySurfaceWeights( + GetEntityId(), + [this, validShapeBounds, shapeConstraintBounds](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights) { bool inBounds = true; @@ -236,28 +235,26 @@ namespace GradientSignal if (validShapeBounds) { inBounds = false; - if (shapeConstraintBounds.Contains(point.m_position)) + if (shapeConstraintBounds.Contains(position)) { - LmbrCentral::ShapeComponentRequestsBus::EventResult(inBounds, m_configuration.m_shapeConstraintEntityId, - &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position); + LmbrCentral::ShapeComponentRequestsBus::EventResult( + inBounds, m_configuration.m_shapeConstraintEntityId, + &LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, position); } } // If the point is within our allowed shape bounds, verify that it meets the gradient thresholds. - // If so, then add the value to the surface tags. + // If so, then return the value to add to the surface tags. if (inBounds) { - const GradientSampleParams sampleParams = { point.m_position }; + const GradientSampleParams sampleParams = { position }; const float value = m_gradientSampler.GetValue(sampleParams); - if (value >= m_configuration.m_thresholdMin && - value <= m_configuration.m_thresholdMax) + if (value >= m_configuration.m_thresholdMin && value <= m_configuration.m_thresholdMax) { - SurfaceData::AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, value); + weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, value); } } - - } - } + }); } } diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp index 1670483131..b79606818b 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceAltitudeGradientComponent.cpp @@ -234,7 +234,7 @@ namespace GradientSignal // 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(); + points.Clear(); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax); } diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp index ea72971818..56bb846ca4 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceMaskGradientComponent.cpp @@ -198,7 +198,7 @@ namespace GradientSignal for (size_t index = 0; index < positions.size(); index++) { - points.clear(); + points.Clear(); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points); outValues[index] = GetMaxSurfaceWeight(points); } diff --git a/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp b/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp index e557785509..fdfa9dd5f8 100644 --- a/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Components/SurfaceSlopeGradientComponent.cpp @@ -239,7 +239,7 @@ namespace GradientSignal for (size_t index = 0; index < positions.size(); index++) { - points.clear(); + points.Clear(); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); outValues[index] = GetSlopeRatio(points, angleMin, angleMax); } diff --git a/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp b/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp index b715df1f92..e318e0fdf6 100644 --- a/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp +++ b/Gems/GradientSignal/Code/Source/Editor/EditorGradientSurfaceDataComponent.cpp @@ -98,10 +98,9 @@ namespace GradientSignal return [this]([[maybe_unused]] float sampleValue, const GradientSampleParams& params) { // Create a fake surface point with the position we're sampling. - SurfaceData::SurfacePoint point; + AzFramework::SurfaceData::SurfacePoint point; point.m_position = params.m_position; - SurfaceData::SurfacePointList pointList; - pointList.emplace_back(point); + SurfaceData::SurfacePointList pointList = { { point } }; // Send it into the component, see what emerges m_component.ModifySurfacePoints(pointList); @@ -110,10 +109,18 @@ namespace GradientSignal // Technically, they should all have the same value, but we'll grab the max from all of them in case // the underlying logic ever changes to allow separate ranges per tag. float result = 0.0f; - for (auto& mask : pointList[0].m_masks) + pointList.EnumeratePoints([&result]( + [[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) -> bool { - result = AZ::GetMax(result, mask.second); - } + masks.EnumerateWeights( + [&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool + { + result = AZ::GetMax(result, weight); + return true; + }); + return true; + }); return result; }; } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp index 72bdfa202e..6f90c5022f 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalReferencesTests.cpp @@ -66,7 +66,7 @@ namespace UnitTest float falloffMidpoint, float falloffRange, float falloffStrength) { MockSurfaceDataSystem mockSurfaceDataSystem; - SurfaceData::SurfacePoint point; + AzFramework::SurfaceData::SurfacePoint point; // Fill our mock surface with the correct normal value for each point based on our test angle set. for (int y = 0; y < dataSize; y++) @@ -539,10 +539,10 @@ namespace UnitTest // Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range. MockSurfaceDataSystem mockSurfaceDataSystem; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; // We set the min/max to values other than 0-10 to help validate that they aren't used in the case of the pinned shape. GradientSignal::SurfaceAltitudeGradientConfig config; @@ -573,10 +573,10 @@ namespace UnitTest // Set a different altitude for each point we're going to test. We'll use 0, 2, 5, 10 to test various points along the range. MockSurfaceDataSystem mockSurfaceDataSystem; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() } }; // We set the min/max to 0-10, but don't set a shape. GradientSignal::SurfaceAltitudeGradientConfig config; @@ -634,13 +634,13 @@ namespace UnitTest MockSurfaceDataSystem mockSurfaceDataSystem; // Altitude value below min - should result in 0.0f. - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() } }; // Altitude value at exactly min - should result in 0.0f. - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 0.0f)] = { { AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() } }; // Altitude value at exactly max - should result in 1.0f. - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(0.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() } }; // Altitude value above max - should result in 1.0f. - mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } }; + mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(1.0f, 1.0f)] = { { AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() } }; // We set the min/max to -5 - 15. By using a range without 0 at either end, and not having 0 as the midpoint, // it should be easier to verify that we're successfully clamping to 0 and 1. @@ -668,14 +668,15 @@ namespace UnitTest }; MockSurfaceDataSystem mockSurfaceDataSystem; - SurfaceData::SurfacePoint point; + AzFramework::SurfaceData::SurfacePoint point; // Fill our mock surface with the test_mask set and the expected gradient value at each point. for (int y = 0; y < dataSize; y++) { for (int x = 0; x < dataSize; x++) { - point.m_masks[AZ_CRC("test_mask", 0x7a16e9ff)] = expectedOutput[(y * dataSize) + x]; + point.m_surfaceTags.clear(); + point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), expectedOutput[(y * dataSize) + x]); mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast(x), static_cast(y))] = { { point } }; } } diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp index a716fc79b8..9f4346e09e 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalSurfaceTests.cpp @@ -19,27 +19,53 @@ namespace UnitTest struct GradientSignalSurfaceTestsFixture : public GradientSignalTest { - void SetSurfacePoint(SurfaceData::SurfacePoint& point, AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector> tags) + void SetSurfacePoint(AzFramework::SurfaceData::SurfacePoint& point, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector> tags) { - point.m_entityId = id; point.m_position = position; point.m_normal = normal; for (auto& tag : tags) { - point.m_masks[SurfaceData::SurfaceTag(tag.first)] = tag.second; + point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second); } } - bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs) + bool SurfacePointsAreEqual(const AzFramework::SurfaceData::SurfacePoint& lhs, const AzFramework::SurfaceData::SurfacePoint& rhs) { - return (lhs.m_entityId == rhs.m_entityId) - && (lhs.m_position == rhs.m_position) - && (lhs.m_normal == rhs.m_normal) - && (lhs.m_masks == rhs.m_masks); + if ((lhs.m_position != rhs.m_position) || (lhs.m_normal != rhs.m_normal) + || (lhs.m_surfaceTags.size() != rhs.m_surfaceTags.size())) + { + return false; + } + + for (auto& mask : lhs.m_surfaceTags) + { + auto maskEntry = AZStd::find_if( + rhs.m_surfaceTags.begin(), rhs.m_surfaceTags.end(), + [mask](const AzFramework::SurfaceData::SurfaceTagWeight& weight) -> bool + { + return (mask.m_surfaceType == weight.m_surfaceType) && (mask.m_weight == weight.m_weight); + }); + if (maskEntry == rhs.m_surfaceTags.end()) + { + return false; + } + } + + return true; + } + + bool SurfacePointsAreEqual( + const AZ::Vector3& lhsPosition, const AZ::Vector3& lhsNormal, const SurfaceData::SurfaceTagWeights& lhsWeights, + const AzFramework::SurfaceData::SurfacePoint& rhs) + { + return ((lhsPosition == rhs.m_position) + && (lhsNormal == rhs.m_normal) + && (lhsWeights.SurfaceWeightsAreEqual(rhs.m_surfaceTags))); } void TestGradientSurfaceDataComponent(float gradientValue, float thresholdMin, float thresholdMax, AZStd::vector tags, bool usesShape, - const SurfaceData::SurfacePoint& input, const SurfaceData::SurfacePoint& expectedOutput) + const AzFramework::SurfaceData::SurfacePoint& input, + const AzFramework::SurfaceData::SurfacePoint& expectedOutput) { // This lets our component register with surfaceData successfully. MockSurfaceDataSystem mockSurfaceDataSystem; @@ -83,10 +109,17 @@ namespace UnitTest EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); // Call ModifySurfacePoints and verify the results - SurfaceData::SurfacePointList pointList; - pointList.emplace_back(input); + SurfaceData::SurfacePointList pointList = { { input } }; SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); - EXPECT_TRUE(SurfacePointsAreEqual(pointList[0],expectedOutput)); + ASSERT_EQ(pointList.GetSize(), 1); + pointList.EnumeratePoints( + [this, expectedOutput]( + const AZ::Vector3& position, const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) + { + EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput)); + return true; + }); } @@ -97,17 +130,17 @@ namespace UnitTest // Verify that for a gradient value within the threshold, the output point contains the // correct tag and gradient value. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below float gradientValue = 0.5f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input, but with an added tag / value - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, { AZStd::make_pair(tag, gradientValue) }); + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag, gradientValue) }); TestGradientSurfaceDataComponent( gradientValue, // constant gradient value @@ -123,17 +156,17 @@ namespace UnitTest { // Verify that for a gradient value outside the threshold, the output point contains no tags / values. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Choose a value outside the threshold range float gradientValue = 0.05f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input - no extra tags / values should be added. - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, {}); + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {}); TestGradientSurfaceDataComponent( gradientValue, // constant gradient value @@ -149,8 +182,8 @@ namespace UnitTest { // Verify that if the component has multiple tags, all of them get put on the output with the same gradient value. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag1 = "test_mask1"; const char* tag2 = "test_mask2"; @@ -158,9 +191,9 @@ namespace UnitTest float gradientValue = 0.5f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input, but with two added tags - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag1, gradientValue), AZStd::make_pair(tag2, gradientValue) }); TestGradientSurfaceDataComponent( @@ -178,8 +211,8 @@ namespace UnitTest // Verify that the output contains input tags that are NOT on the modification list and adds any // new tags that weren't in the input - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* preservedTag = "preserved_tag"; const char* modifierTag = "modifier_tag"; @@ -187,9 +220,9 @@ namespace UnitTest float gradientValue = 0.5f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(preservedTag, 1.0f) }); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(preservedTag, 1.0f) }); // Output should match the input, but with two added tags - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(preservedTag, 1.0f), AZStd::make_pair(modifierTag, gradientValue) }); TestGradientSurfaceDataComponent( @@ -206,8 +239,8 @@ namespace UnitTest { // Verify that if the input has a higher value on the tag than the modifier, it keeps the higher value. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below @@ -216,9 +249,9 @@ namespace UnitTest float inputValue = 0.75f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(tag, inputValue) }); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(tag, inputValue) }); // Output should match the input - the higher input value on the tag is preserved - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag, inputValue) }); TestGradientSurfaceDataComponent( @@ -235,8 +268,8 @@ namespace UnitTest { // Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below @@ -245,9 +278,9 @@ namespace UnitTest float inputValue = 0.25f; // Set arbitrary input data - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(tag, inputValue) }); + SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(tag, inputValue) }); // Output should match the input, except that the value on the tag gets the higher modifier value - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag, gradientValue) }); TestGradientSurfaceDataComponent( @@ -264,17 +297,17 @@ namespace UnitTest { // Verify that if no shape has been added, the component modifies points in unbounded space - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below float gradientValue = 0.5f; // Set arbitrary input data, but with a point that's extremely far away in space - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(-100000000.0f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(-100000000.0f), AZ::Vector3(0.0f), {}); // Output should match the input but with the tag added, even though the point was far away. - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag, gradientValue) }); TestGradientSurfaceDataComponent( @@ -292,17 +325,17 @@ namespace UnitTest // Verify that if a shape constraint is added, points within the shape are still modified. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below float gradientValue = 0.5f; // Set arbitrary input data, but with a point that's within the mock shape cube (0.25 vs -0.5 to 0.5) - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(0.25f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(0.25f), AZ::Vector3(0.0f), {}); // Output should match the input but with the tag added, since the point is within the shape constraint. - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair(tag, gradientValue) }); TestGradientSurfaceDataComponent( @@ -320,17 +353,17 @@ namespace UnitTest // Verify that if a shape constraint is added, points outside the shape are not modified. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space. - SurfaceData::SurfacePoint input; - SurfaceData::SurfacePoint expectedOutput; + AzFramework::SurfaceData::SurfacePoint input; + AzFramework::SurfaceData::SurfacePoint expectedOutput; const char* tag = "test_mask"; // Select a gradient value within the threshold range below float gradientValue = 0.5f; // Set arbitrary input data, but with a point that's outside the mock shape cube (10.0 vs -0.5 to 0.5) - SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(10.0f), AZ::Vector3(0.0f), {}); + SetSurfacePoint(input, AZ::Vector3(10.0f), AZ::Vector3(0.0f), {}); // Output should match the input with no tag added, since the point is outside the shape constraint - SetSurfacePoint(expectedOutput, input.m_entityId, input.m_position, input.m_normal, {}); + SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, {}); TestGradientSurfaceDataComponent( gradientValue, // constant gradient value diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp index d81370f6a8..402438ee88 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalTestFixtures.cpp @@ -84,7 +84,7 @@ namespace UnitTest AZStd::unique_ptr GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox) { - SurfaceData::SurfacePoint point; + AzFramework::SurfaceData::SurfacePoint point; AZStd::unique_ptr mockSurfaceDataSystem = AZStd::make_unique(); // Give the mock surface data a bunch of fake point values to return. @@ -101,7 +101,8 @@ namespace UnitTest // Create an arbitrary normal value. point.m_normal = point.m_position.GetNormalized(); // Create an arbitrary surface value. - point.m_masks[AZ_CRC_CE("test_mask")] = arbitraryPercentage; + point.m_surfaceTags.clear(); + point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), arbitraryPercentage); mockSurfaceDataSystem->m_GetSurfacePoints[AZStd::make_pair(x, y)] = { { point } }; } diff --git a/Gems/SurfaceData/Code/CMakeLists.txt b/Gems/SurfaceData/Code/CMakeLists.txt index 6cef8b6e25..cb75be2de4 100644 --- a/Gems/SurfaceData/Code/CMakeLists.txt +++ b/Gems/SurfaceData/Code/CMakeLists.txt @@ -37,7 +37,7 @@ ly_add_target( PUBLIC Include BUILD_DEPENDENCIES - PRIVATE + PUBLIC Gem::SurfaceData.Static Gem::LmbrCentral RUNTIME_DEPENDENCIES diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h index 6d39efaa9f..c892bc8c09 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/SurfaceDataTypes.h @@ -9,31 +9,215 @@ #pragma once #include +#include #include #include #include #include +#include #include namespace SurfaceData { //map of id or crc to contribution factor - using SurfaceTagWeightMap = AZStd::unordered_map; using SurfaceTagNameSet = AZStd::unordered_set; using SurfaceTagVector = AZStd::vector; - struct SurfacePoint final + //! SurfaceTagWeights stores a collection of surface tags and weights. + class SurfaceTagWeights { - AZ_CLASS_ALLOCATOR(SurfacePoint, AZ::SystemAllocator, 0); - AZ_TYPE_INFO(SurfacePoint, "{0DC7E720-68D6-47D4-BB6D-B89EF23C5A5C}"); + public: + SurfaceTagWeights() = default; - AZ::EntityId m_entityId; - AZ::Vector3 m_position; - AZ::Vector3 m_normal; - SurfaceTagWeightMap m_masks; + //! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList. + //! @param weights - The list of weights to assign to the new instance. + SurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights) + { + AssignSurfaceTagWeights(weights); + } + + //! Replace the existing surface tag weights with the given set. + //! @param weights - The list of weights to assign to this instance. + void AssignSurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights); + + //! Replace the existing surface tag weights with the given set. + //! @param tags - The list of tags to assign to this instance. + //! @param weight - The weight to assign to each tag. + void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight); + + //! Add a surface tag weight to this collection. + //! @param tag - The surface tag. + //! @param weight - The surface tag weight. + void AddSurfaceTagWeight(const AZ::Crc32 tag, const float weight); + + //! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found. + //! (This method is intentionally inlined for its performance impact) + //! @param tag - The surface tag. + //! @param weight - The surface tag weight. + void AddSurfaceWeightIfGreater(const AZ::Crc32 tag, const float weight) + { + const auto maskItr = m_weights.find(tag); + const float previousValue = maskItr != m_weights.end() ? maskItr->second : 0.0f; + m_weights[tag] = AZ::GetMax(weight, previousValue); + } + + //! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found. + //! (This method is intentionally inlined for its performance impact) + //! @param tags - The surface tags to replace/add. + //! @param weight - The surface tag weight to use for each tag. + void AddSurfaceWeightsIfGreater(const SurfaceTagVector& tags, const float weight) + { + for (const auto& tag : tags) + { + AddSurfaceWeightIfGreater(tag, weight); + } + } + + //! Replace the surface tag weight with the new one if it's higher, or add it if the tag isn't found. + //! (This method is intentionally inlined for its performance impact) + //! @param weights - The surface tags and weights to replace/add. + void AddSurfaceWeightsIfGreater(const SurfaceTagWeights& weights) + { + for (const auto& [tag, weight] : weights.m_weights) + { + AddSurfaceWeightIfGreater(tag, weight); + } + } + + //! Equality comparison operator for SurfaceTagWeights. + bool operator==(const SurfaceTagWeights& rhs) const; + + //! Inequality comparison operator for SurfaceTagWeights. + bool operator!=(const SurfaceTagWeights& rhs) const + { + return !(*this == rhs); + } + + //! Compares a SurfaceTagWeightList with a SurfaceTagWeights instance to look for equality. + //! They will be equal if they have the exact same set of tags and weights. + //! @param compareWeights - the set of weights to compare against. + bool SurfaceWeightsAreEqual(const AzFramework::SurfaceData::SurfaceTagWeightList& compareWeights) const; + + //! Clear the surface tag weight collection. + void Clear(); + + //! Get the size of the surface tag weight collection. + //! @return The size of the collection. + size_t GetSize() const; + + //! Get the collection of surface tag weights as a SurfaceTagWeightList. + //! @return SurfaceTagWeightList containing the same tags and weights as this collection. + AzFramework::SurfaceData::SurfaceTagWeightList GetSurfaceTagWeightList() const; + + //! Enumerate every tag and weight and call a callback for each one found. + //! Callback params: + //! AZ::Crc32 - The surface tag. + //! float - The surface tag weight. + //! return - true to keep enumerating, false to stop. + //! @param weightCallback - the callback to use for each surface tag / weight found. + void EnumerateWeights(AZStd::function weightCallback) const; + + //! Check to see if the collection has any valid tags stored within it. + //! A tag of "Unassigned" is considered an invalid tag. + //! @return True if there is at least one valid tag, false if there isn't. + bool HasValidTags() const; + + //! Check to see if the collection contains the given tag. + //! @param sampleTag - The tag to look for. + //! @return True if the tag is found, false if it isn't. + bool HasMatchingTag(const AZ::Crc32& sampleTag) const; + + //! Check to see if the collection contains the given tag with the given weight range. + //! The range check is inclusive on both sides of the range: [weightMin, weightMax] + //! @param sampleTag - The tag to look for. + //! @param weightMin - The minimum weight for this tag. + //! @param weightMax - The maximum weight for this tag. + //! @return True if the tag is found, false if it isn't. + bool HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const; + + //! Check to see if the collection contains any of the given tags. + //! @param sampleTags - The tags to look for. + //! @return True if any of the tags is found, false if none are found. + bool HasAnyMatchingTags(const SurfaceTagVector& sampleTags) const; + + //! Check to see if the collection contains the given tag with the given weight range. + //! The range check is inclusive on both sides of the range: [weightMin, weightMax] + //! @param sampleTags - The tags to look for. + //! @param weightMin - The minimum weight for this tag. + //! @param weightMax - The maximum weight for this tag. + //! @return True if any of the tags is found, false if none are found. + bool HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const; + + private: + AZStd::unordered_map m_weights; + }; + + //! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights. + class SurfacePointList + { + public: + AZ_CLASS_ALLOCATOR(SurfacePointList, AZ::SystemAllocator, 0); + AZ_TYPE_INFO(SurfacePointList, "{DBA02848-2131-4279-BDEF-3581B76AB736}"); + + SurfacePointList() = default; + ~SurfacePointList() = default; + + //! Constructor for creating a SurfacePointList from a list of SurfacePoint data. + //! Primarily used as a convenience for unit tests. + //! @param surfacePoints - An initial set of SurfacePoint points to store in the SurfacePointList. + SurfacePointList(AZStd::initializer_list surfacePoints); + + //! Add a surface point to the list. + //! @param entityId - The entity creating the surface point. + //! @param position - The position of the surface point. + //! @param normal - The normal for the surface point. + //! @param weights - The surface tags and weights for this surface point. + void AddSurfacePoint(const AZ::EntityId& entityId, + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& weights); + + //! Clear the surface point list. + void Clear(); + + //! Preallocate space in the list based on the maximum number of output points per input point we can generate. + //! @param maxPointsPerInput - The maximum number of output points per input point. + void ReserveSpace(size_t maxPointsPerInput); + + //! Check if the surface point list is empty. + //! @return - true if empty, false if it contains points. + bool IsEmpty() const; + + //! Get the size of the surface point list. + //! @return - The number of valid points in the list. + size_t GetSize() const; + + //! Enumerate every surface point and call a callback for each point found. + void EnumeratePoints(AZStd::function< + bool(const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& surfaceWeights)> pointCallback) const; + + //! Modify the surface weights for each surface point in the list. + void ModifySurfaceWeights( + const AZ::EntityId& currentEntityId, + AZStd::function modificationWeightCallback); + + //! Get the surface point with the highest Z value. + AzFramework::SurfaceData::SurfacePoint GetHighestSurfacePoint() const; + + //! Remove any points that don't contain any of the provided surface tags. + void FilterPoints(const SurfaceTagVector& desiredTags); + + protected: + // These are kept in separate parallel vectors instead of a single struct so that it's possible to pass just specific data + // "channels" into other methods as span<> without having to pass the full struct into the span<>. Specifically, we want to be + // able to pass spans of the positions down through nesting gradient/surface calls. + // A side benefit is that profiling showed the data access to be faster than packing all the fields into a single struct. + AZStd::vector m_surfaceCreatorIdList; + AZStd::vector m_surfacePositionList; + AZStd::vector m_surfaceNormalList; + AZStd::vector m_surfaceWeightsList; + + AZ::Aabb m_pointBounds = AZ::Aabb::CreateNull(); }; - using SurfacePointList = AZStd::vector; using SurfacePointLists = AZStd::vector; struct SurfaceDataRegistryEntry diff --git a/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h b/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h index 2a4a39ee3b..be053db5b9 100644 --- a/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h +++ b/Gems/SurfaceData/Code/Include/SurfaceData/Utility/SurfaceDataUtility.h @@ -88,48 +88,6 @@ namespace SurfaceData const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd, AZ::Vector3& outPosition, AZ::Vector3& outNormal); - AZ_INLINE void AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight, SurfaceTagWeightMap& weights) - { - weights.clear(); - weights.reserve(tags.size()); - for (auto& tag : tags) - { - weights[tag] = weight; - } - } - - AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const AZ::Crc32 tag, const float value) - { - const auto maskItr = masks.find(tag); - const float valueOld = maskItr != masks.end() ? maskItr->second : 0.0f; - masks[tag] = AZ::GetMax(value, valueOld); - } - - AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& masks, const SurfaceTagVector& tags, const float value) - { - for (const auto& tag : tags) - { - AddMaxValueForMasks(masks, tag, value); - } - } - - AZ_INLINE void AddMaxValueForMasks(SurfaceTagWeightMap& outMasks, const SurfaceTagWeightMap& inMasks) - { - for (const auto& inMask : inMasks) - { - AddMaxValueForMasks(outMasks, inMask.first, inMask.second); - } - } - - template - AZ_INLINE void AddItemIfNotFound(Container& container, const Element& element) - { - if (AZStd::find(container.begin(), container.end(), element) == container.end()) - { - container.insert(container.end(), element); - } - } - template AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag) { @@ -137,26 +95,7 @@ namespace SurfaceData } template - AZ_INLINE bool HasMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags) - { - for (const auto& sampleTag : sampleTags) - { - if (HasMatchingTag(sourceTags, sampleTag)) - { - return true; - } - } - - return false; - } - - AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag) - { - return sourceTags.find(sampleTag) != sourceTags.end(); - } - - template - AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags) + AZ_INLINE bool HasAnyMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags) { for (const auto& sampleTag : sampleTags) { @@ -168,36 +107,8 @@ namespace SurfaceData return false; } - - AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag, float valueMin, float valueMax) - { - auto maskItr = sourceTags.find(sampleTag); - return maskItr != sourceTags.end() && valueMin <= maskItr->second && valueMax >= maskItr->second; - } - - template - AZ_INLINE bool HasMatchingTags( - const SurfaceTagWeightMap& sourceTags, const SampleContainer& sampleTags, float valueMin, float valueMax) - { - for (const auto& sampleTag : sampleTags) - { - if (HasMatchingTag(sourceTags, sampleTag, valueMin, valueMax)) - { - return true; - } - } - - return false; - } - - template - AZ_INLINE void RemoveUnassignedTags(const SourceContainer& sourceTags) - { - sourceTags.erase(AZStd::remove(sourceTags.begin(), sourceTags.end(), Constants::s_unassignedTagCrc), sourceTags.end()); - } - - template - AZ_INLINE bool HasValidTags(const SourceContainer& sourceTags) + + AZ_INLINE bool HasValidTags(const SurfaceTagVector& sourceTags) { for (const auto& sourceTag : sourceTags) { @@ -209,18 +120,6 @@ namespace SurfaceData return false; } - AZ_INLINE bool HasValidTags(const SurfaceTagWeightMap& sourceTags) - { - for (const auto& sourceTag : sourceTags) - { - if (sourceTag.first != Constants::s_unassignedTagCrc) - { - return true; - } - } - return false; - } - // Utility method to compare two AABBs for overlapping XY coordinates while ignoring the Z coordinates. AZ_INLINE bool AabbOverlaps2D(const AZ::Aabb& box1, const AZ::Aabb& box2) { diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp index dc4e5e6e74..e51fbc09b2 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.cpp @@ -132,7 +132,7 @@ namespace SurfaceData Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId()); // Update the cached collider data and bounds, then register the surface data provider / modifier - AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); + m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f); UpdateColliderData(); } @@ -237,12 +237,7 @@ namespace SurfaceData if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal)) { - SurfacePoint point; - point.m_entityId = GetEntityId(); - point.m_position = hitPosition; - point.m_normal = hitNormal; - point.m_masks = m_newPointWeights; - surfacePointList.push_back(AZStd::move(point)); + surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights); } } @@ -252,20 +247,22 @@ namespace SurfaceData if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty()) { - const AZ::EntityId entityId = GetEntityId(); - for (auto& point : surfacePointList) - { - if (point.m_entityId != entityId && m_colliderBounds.Contains(point.m_position)) + surfacePointList.ModifySurfaceWeights( + GetEntityId(), + [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights) { - AZ::Vector3 hitPosition; - AZ::Vector3 hitNormal; - constexpr bool queryPointOnly = true; - if (DoRayTrace(point.m_position, queryPointOnly, hitPosition, hitNormal)) + if (m_colliderBounds.Contains(position)) { - AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f); + AZ::Vector3 hitPosition; + AZ::Vector3 hitNormal; + constexpr bool queryPointOnly = true; + if (DoRayTrace(position, queryPointOnly, hitPosition, hitNormal)) + { + // If the query point collides with the volume, add all our modifier tags with a weight of 1.0f. + weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f); + } } - } - } + }); } } diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h index 50c84f9519..2ef25774aa 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataColliderComponent.h @@ -101,6 +101,6 @@ namespace SurfaceData AZStd::atomic_bool m_refresh{ false }; mutable AZStd::shared_mutex m_cacheMutex; AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull(); - SurfaceTagWeightMap m_newPointWeights; + SurfaceTagWeights m_newPointWeights; }; } diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp index 682bac150a..b84607782e 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.cpp @@ -89,7 +89,7 @@ namespace SurfaceData LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); // Update the cached shape data and bounds, then register the surface data provider / modifier - AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f, m_newPointWeights); + m_newPointWeights.AssignSurfaceTagWeights(m_configuration.m_providerTags, 1.0f); UpdateShapeData(); } @@ -155,12 +155,8 @@ namespace SurfaceData LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance); if (hitShape) { - SurfacePoint point; - point.m_entityId = GetEntityId(); - point.m_position = rayOrigin + intersectionDistance * rayDirection; - point.m_normal = AZ::Vector3::CreateAxisZ(); - point.m_masks = m_newPointWeights; - surfacePointList.push_back(AZStd::move(point)); + AZ::Vector3 position = rayOrigin + intersectionDistance * rayDirection; + surfacePointList.AddSurfacePoint(GetEntityId(), position, AZ::Vector3::CreateAxisZ(), m_newPointWeights); } } } @@ -173,20 +169,19 @@ namespace SurfaceData { const AZ::EntityId entityId = GetEntityId(); LmbrCentral::ShapeComponentRequestsBus::Event( - GetEntityId(), + entityId, [entityId, this, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape) { - for (auto& point : surfacePointList) + surfacePointList.ModifySurfaceWeights( + entityId, + [this, shape](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights) { - if (point.m_entityId != entityId && m_shapeBounds.Contains(point.m_position)) + if (m_shapeBounds.Contains(position) && shape->IsPointInside(position)) { - bool inside = shape->IsPointInside(point.m_position); - if (inside) - { - AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f); - } + // If the point is inside our shape, add all our modifier tags with a weight of 1.0f. + weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f); } - } + }); }); } } diff --git a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h index 59b7950412..25ac491809 100644 --- a/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h +++ b/Gems/SurfaceData/Code/Source/Components/SurfaceDataShapeComponent.h @@ -97,6 +97,6 @@ namespace SurfaceData AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull(); bool m_shapeBoundsIsValid = false; static const float s_rayAABBHeightPadding; - SurfaceTagWeightMap m_newPointWeights; + SurfaceTagWeights m_newPointWeights; }; } diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp index 66f282d12b..66b8c83a58 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.cpp @@ -45,14 +45,10 @@ namespace SurfaceData if (auto behaviorContext = azrtti_cast(context)) { - behaviorContext->Class() + behaviorContext->Class() ->Constructor() ->Attribute(AZ::Script::Attributes::Category, "Vegetation") ->Attribute(AZ::Script::Attributes::Module, "surface_data") - ->Property("entityId", BehaviorValueProperty(&SurfacePoint::m_entityId)) - ->Property("position", BehaviorValueProperty(&SurfacePoint::m_position)) - ->Property("normal", BehaviorValueProperty(&SurfacePoint::m_normal)) - ->Property("masks", BehaviorValueProperty(&SurfacePoint::m_masks)) ; behaviorContext->Class() @@ -182,11 +178,12 @@ namespace SurfaceData void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const { const bool useTagFilters = HasValidTags(desiredTags); - const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); + const bool hasModifierTags = useTagFilters && HasAnyMatchingTags(desiredTags, m_registeredModifierTags); AZStd::shared_lock registrationLock(m_registrationMutex); - surfacePointList.clear(); + surfacePointList.Clear(); + surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size()); //gather all intersecting points for (const auto& entryPair : m_registeredSurfaceDataProviders) @@ -195,14 +192,14 @@ namespace SurfaceData const SurfaceDataRegistryEntry& entry = entryPair.second; if (!entry.m_bounds.IsValid() || AabbContains2D(entry.m_bounds, inPosition)) { - if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, entry.m_tags)) + if (!useTagFilters || hasModifierTags || HasAnyMatchingTags(desiredTags, entry.m_tags)) { SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList); } } } - if (!surfacePointList.empty()) + if (!surfacePointList.IsEmpty()) { //modify or annotate reported points for (const auto& entryPair : m_registeredSurfaceDataModifiers) @@ -221,10 +218,8 @@ namespace SurfaceData // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. if (useTagFilters) { - FilterPoints(surfacePointList, desiredTags); + surfacePointList.FilterPoints(desiredTags); } - - CombineAndSortNeighboringPoints(surfacePointList); } } @@ -260,8 +255,13 @@ namespace SurfaceData surfacePointLists.clear(); surfacePointLists.resize(totalQueryPositions); + for (auto& surfacePointList : surfacePointLists) + { + surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size()); + } + const bool useTagFilters = HasValidTags(desiredTags); - const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); + const bool hasModifierTags = useTagFilters && HasAnyMatchingTags(desiredTags, m_registeredModifierTags); // Loop through each data provider, and query all the points for each one. This allows us to check the tags and the overall // AABB bounds just once per provider, instead of once per point. It also allows for an eventual optimization in which we could @@ -270,7 +270,7 @@ namespace SurfaceData { bool hasInfiniteBounds = !provider.m_bounds.IsValid(); - if (!useTagFilters || hasModifierTags || HasMatchingTags(desiredTags, provider.m_tags)) + if (!useTagFilters || hasModifierTags || HasAnyMatchingTags(desiredTags, provider.m_tags)) { for (size_t index = 0; index < totalQueryPositions; index++) { @@ -299,7 +299,7 @@ namespace SurfaceData { const auto& inPosition = inPositions[index]; SurfacePointList& surfacePointList = surfacePointLists[index]; - if (!surfacePointList.empty()) + if (!surfacePointList.IsEmpty()) { if (hasInfiniteBounds || AabbContains2D(entry.m_bounds, inPosition)) { @@ -315,82 +315,14 @@ namespace SurfaceData // same XY coordinates and extremely similar Z values. This produces results that are sorted in decreasing Z order. // Also, this filters out any remaining points that don't match the desired tag list. This can happen when a surface provider // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't. - for (auto& surfacePointList : surfacePointLists) + if (useTagFilters) { - if (useTagFilters) + for (auto& surfacePointList : surfacePointLists) { - FilterPoints(surfacePointList, desiredTags); + surfacePointList.FilterPoints(desiredTags); } - CombineAndSortNeighboringPoints(surfacePointList); - } - - } - - void SurfaceDataSystemComponent::FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const - { - // Before sorting and combining, filter out any points that don't match our search tags. - sourcePointList.erase( - AZStd::remove_if( - sourcePointList.begin(), sourcePointList.end(), - [desiredTags](SurfacePoint& point) -> bool - { - return !HasMatchingTags(point.m_masks, desiredTags); - }), - sourcePointList.end()); - } - - void SurfaceDataSystemComponent::CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const - { - // If there's only 0 or 1 point, there is no sorting or combining that needs to happen, so just return. - if (sourcePointList.size() <= 1) - { - return; } - // Efficient point consolidation requires the points to be pre-sorted so we are only comparing/combining neighbors. - // Sort XY points together, with decreasing Z. - AZStd::sort(sourcePointList.begin(), sourcePointList.end(), [](const SurfacePoint& a, const SurfacePoint& b) - { - // Our goal is to have identical XY values sorted adjacent to each other with decreasing Z. - // We sort increasing Y, then increasing X, then decreasing Z, because we need to compare all 3 values for a - // stable sort. The choice of increasing Y first is because we'll often generate the points as ranges of X values within - // ranges of Y values, so this will produce the most usable and expected output sort. - if (a.m_position.GetY() != b.m_position.GetY()) - { - return a.m_position.GetY() < b.m_position.GetY(); - } - if (a.m_position.GetX() != b.m_position.GetX()) - { - return a.m_position.GetX() < b.m_position.GetX(); - } - if (a.m_position.GetZ() != b.m_position.GetZ()) - { - return a.m_position.GetZ() > b.m_position.GetZ(); - } - - // If we somehow ended up with two points with identical positions getting generated, use the entity ID as the tiebreaker - // to guarantee a stable sort. We should never have two identical positions generated from the same entity. - return a.m_entityId < b.m_entityId; - }); - - // iterate over subsequent source points for comparison and consolidation with the last added target/unique point - for (auto pointItr = sourcePointList.begin() + 1; pointItr < sourcePointList.end();) - { - auto prevPointItr = pointItr - 1; - - // (Someday we should add a configurable tolerance for comparison) - if (pointItr->m_position.IsClose(prevPointItr->m_position) && pointItr->m_normal.IsClose(prevPointItr->m_normal)) - { - // consolidate points with similar attributes by adding masks/weights to the previous point and deleting this point. - AddMaxValueForMasks(prevPointItr->m_masks, pointItr->m_masks); - - pointItr = sourcePointList.erase(pointItr); - } - else - { - pointItr++; - } - } } SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry) diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h index 6ec2cab4eb..bb554cec78 100644 --- a/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h +++ b/Gems/SurfaceData/Code/Source/SurfaceDataSystemComponent.h @@ -58,9 +58,6 @@ namespace SurfaceData void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override; private: - void FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const; - void CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const; - SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry); SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle); bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds); diff --git a/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp new file mode 100644 index 0000000000..3071fd02ee --- /dev/null +++ b/Gems/SurfaceData/Code/Source/SurfaceDataTypes.cpp @@ -0,0 +1,319 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +namespace SurfaceData +{ + void SurfaceTagWeights::AssignSurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights) + { + m_weights.clear(); + m_weights.reserve(weights.size()); + for (auto& weight : weights) + { + m_weights.emplace(weight.m_surfaceType, weight.m_weight); + } + } + + void SurfaceTagWeights::AssignSurfaceTagWeights(const SurfaceTagVector& tags, float weight) + { + m_weights.clear(); + m_weights.reserve(tags.size()); + for (auto& tag : tags) + { + m_weights[tag] = weight; + } + } + + void SurfaceTagWeights::AddSurfaceTagWeight(const AZ::Crc32 tag, const float value) + { + m_weights[tag] = value; + } + + void SurfaceTagWeights::Clear() + { + m_weights.clear(); + } + + size_t SurfaceTagWeights::GetSize() const + { + return m_weights.size(); + } + + AzFramework::SurfaceData::SurfaceTagWeightList SurfaceTagWeights::GetSurfaceTagWeightList() const + { + AzFramework::SurfaceData::SurfaceTagWeightList weights; + weights.reserve(m_weights.size()); + for (auto& weight : m_weights) + { + weights.emplace_back(weight.first, weight.second); + } + return weights; + } + + bool SurfaceTagWeights::operator==(const SurfaceTagWeights& rhs) const + { + // If the lists are different sizes, they're not equal. + if (m_weights.size() != rhs.m_weights.size()) + { + return false; + } + + for (auto& weight : m_weights) + { + auto rhsWeight = rhs.m_weights.find(weight.first); + if ((rhsWeight == rhs.m_weights.end()) || (rhsWeight->second != weight.second)) + { + return false; + } + } + + // All the entries matched, and the lists are the same size, so they're equal. + return true; + } + + bool SurfaceTagWeights::SurfaceWeightsAreEqual(const AzFramework::SurfaceData::SurfaceTagWeightList& compareWeights) const + { + // If the lists are different sizes, they're not equal. + if (m_weights.size() != compareWeights.size()) + { + return false; + } + + for (auto& weight : m_weights) + { + auto maskEntry = AZStd::find_if( + compareWeights.begin(), compareWeights.end(), + [weight](const AzFramework::SurfaceData::SurfaceTagWeight& compareWeight) -> bool + { + return (weight.first == compareWeight.m_surfaceType) && (weight.second == compareWeight.m_weight); + }); + + // If we didn't find a match, they're not equal. + if (maskEntry == compareWeights.end()) + { + return false; + } + } + + // All the entries matched, and the lists are the same size, so they're equal. + return true; + } + + void SurfaceTagWeights::EnumerateWeights(AZStd::function weightCallback) const + { + for (auto& [tag, weight] : m_weights) + { + if (!weightCallback(tag, weight)) + { + break; + } + } + } + + bool SurfaceTagWeights::HasValidTags() const + { + for (const auto& sourceTag : m_weights) + { + if (sourceTag.first != Constants::s_unassignedTagCrc) + { + return true; + } + } + return false; + } + + bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag) const + { + return m_weights.find(sampleTag) != m_weights.end(); + } + + bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags) const + { + for (const auto& sampleTag : sampleTags) + { + if (HasMatchingTag(sampleTag)) + { + return true; + } + } + + return false; + } + + bool SurfaceTagWeights::HasMatchingTag(const AZ::Crc32& sampleTag, float weightMin, float weightMax) const + { + auto maskItr = m_weights.find(sampleTag); + return maskItr != m_weights.end() && weightMin <= maskItr->second && weightMax >= maskItr->second; + } + + bool SurfaceTagWeights::HasAnyMatchingTags(const SurfaceTagVector& sampleTags, float weightMin, float weightMax) const + { + for (const auto& sampleTag : sampleTags) + { + if (HasMatchingTag(sampleTag, weightMin, weightMax)) + { + return true; + } + } + + return false; + } + + + + + SurfacePointList::SurfacePointList(AZStd::initializer_list surfacePoints) + { + ReserveSpace(surfacePoints.size()); + + for (auto& point : surfacePoints) + { + SurfaceTagWeights weights(point.m_surfaceTags); + AddSurfacePoint(AZ::EntityId(), point.m_position, point.m_normal, weights); + } + } + + void SurfacePointList::AddSurfacePoint(const AZ::EntityId& entityId, + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& masks) + { + // When adding a surface point, we'll either merge it with a similar existing point, or else add it in order of + // decreasing Z, so that our final results are sorted. + + for (size_t index = 0; index < m_surfacePositionList.size(); ++index) + { + // (Someday we should add a configurable tolerance for comparison) + if (m_surfacePositionList[index].IsClose(position) && m_surfaceNormalList[index].IsClose(normal)) + { + // consolidate points with similar attributes by adding masks/weights to the similar point instead of adding a new one. + m_surfaceWeightsList[index].AddSurfaceWeightsIfGreater(masks); + return; + } + else if (m_surfacePositionList[index].GetZ() < position.GetZ()) + { + m_pointBounds.AddPoint(position); + m_surfacePositionList.insert(m_surfacePositionList.begin() + index, position); + m_surfaceNormalList.insert(m_surfaceNormalList.begin() + index, normal); + m_surfaceWeightsList.insert(m_surfaceWeightsList.begin() + index, masks); + m_surfaceCreatorIdList.insert(m_surfaceCreatorIdList.begin() + index, entityId); + return; + } + } + + // The point wasn't merged and the sort puts it at the end, so just add the point to the end of the list. + m_pointBounds.AddPoint(position); + m_surfacePositionList.emplace_back(position); + m_surfaceNormalList.emplace_back(normal); + m_surfaceWeightsList.emplace_back(masks); + m_surfaceCreatorIdList.emplace_back(entityId); + } + + void SurfacePointList::Clear() + { + m_surfacePositionList.clear(); + m_surfaceNormalList.clear(); + m_surfaceWeightsList.clear(); + m_surfaceCreatorIdList.clear(); + } + + void SurfacePointList::ReserveSpace(size_t maxPointsPerInput) + { + AZ_Assert( + m_surfacePositionList.size() < maxPointsPerInput, + "Trying to reserve space on a list that is already using more points than requested."); + + m_surfaceCreatorIdList.reserve(maxPointsPerInput); + m_surfacePositionList.reserve(maxPointsPerInput); + m_surfaceNormalList.reserve(maxPointsPerInput); + m_surfaceWeightsList.reserve(maxPointsPerInput); + } + + bool SurfacePointList::IsEmpty() const + { + return m_surfacePositionList.empty(); + } + + size_t SurfacePointList::GetSize() const + { + return m_surfacePositionList.size(); + } + + void SurfacePointList::EnumeratePoints( + AZStd::function + pointCallback) const + { + for (size_t index = 0; index < m_surfacePositionList.size(); index++) + { + if (!pointCallback(m_surfacePositionList[index], m_surfaceNormalList[index], m_surfaceWeightsList[index])) + { + break; + } + } + } + + void SurfacePointList::ModifySurfaceWeights( + const AZ::EntityId& currentEntityId, + AZStd::function modificationWeightCallback) + { + for (size_t index = 0; index < m_surfacePositionList.size(); index++) + { + if (m_surfaceCreatorIdList[index] != currentEntityId) + { + modificationWeightCallback(m_surfacePositionList[index], m_surfaceWeightsList[index]); + } + } + } + + AzFramework::SurfaceData::SurfacePoint SurfacePointList::GetHighestSurfacePoint() const + { + AzFramework::SurfaceData::SurfacePoint point; + point.m_position = m_surfacePositionList.front(); + point.m_normal = m_surfaceNormalList.front(); + point.m_surfaceTags = m_surfaceWeightsList.front().GetSurfaceTagWeightList(); + + return point; + } + + void SurfacePointList::FilterPoints(const SurfaceTagVector& desiredTags) + { + // Filter out any points that don't match our search tags. + // This has to be done after the Surface Modifiers have processed the points, not at point insertion time, because + // Surface Modifiers add tags to existing points. + size_t listSize = m_surfacePositionList.size(); + size_t index = 0; + for (; index < listSize; index++) + { + if (!m_surfaceWeightsList[index].HasAnyMatchingTags(desiredTags)) + { + break; + } + } + + if (index != listSize) + { + size_t next = index + 1; + for (; next < listSize; ++next) + { + if (m_surfaceWeightsList[index].HasAnyMatchingTags(desiredTags)) + { + m_surfaceCreatorIdList[index] = m_surfaceCreatorIdList[next]; + m_surfacePositionList[index] = m_surfacePositionList[next]; + m_surfaceNormalList[index] = m_surfaceNormalList[next]; + m_surfaceWeightsList[index] = m_surfaceWeightsList[next]; + ++index; + } + } + + m_surfaceCreatorIdList.resize(index); + m_surfacePositionList.resize(index); + m_surfaceNormalList.resize(index); + m_surfaceWeightsList.resize(index); + } + } +} diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp index 65487c155b..226a711cff 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataBenchmarks.cpp @@ -190,7 +190,7 @@ namespace UnitTest for (float x = 0.0f; x < worldSize; x += 1.0f) { AZ::Vector3 queryPosition(x, y, 0.0f); - points.clear(); + points.Clear(); SurfaceData::SurfaceDataSystemRequestBus::Broadcast( &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points); diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp index c51ea49f14..884c831393 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataColliderComponentTest.cpp @@ -26,7 +26,8 @@ namespace UnitTest : public AzPhysics::SimulatedBodyComponentRequestsBus::Handler { public: - MockPhysicsWorldBusProvider(const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const SurfaceData::SurfacePoint& hitResult) + MockPhysicsWorldBusProvider( + const AZ::EntityId& id, AZ::Vector3 inPosition, bool setHitResult, const AzFramework::SurfaceData::SurfacePoint& hitResult) { AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(id); @@ -77,51 +78,36 @@ namespace UnitTest { protected: // Create a new SurfacePoint with the given fields. - SurfaceData::SurfacePoint CreateSurfacePoint(AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector> tags) + AzFramework::SurfaceData::SurfacePoint CreateSurfacePoint( + AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector> tags) { - SurfaceData::SurfacePoint point; - point.m_entityId = id; + AzFramework::SurfaceData::SurfacePoint point; point.m_position = position; point.m_normal = normal; for (auto& tag : tags) { - point.m_masks[SurfaceData::SurfaceTag(tag.first)] = tag.second; + point.m_surfaceTags.emplace_back(SurfaceData::SurfaceTag(tag.first), tag.second); } return point; } // Compare two surface points. - bool SurfacePointsAreEqual(const SurfaceData::SurfacePoint& lhs, const SurfaceData::SurfacePoint& rhs) + bool SurfacePointsAreEqual( + const AZ::Vector3& lhsPosition, + const AZ::Vector3& lhsNormal, + const SurfaceData::SurfaceTagWeights& lhsMasks, + const AzFramework::SurfaceData::SurfacePoint& rhs) { - if ((lhs.m_entityId != rhs.m_entityId) - || (lhs.m_position != rhs.m_position) - || (lhs.m_normal != rhs.m_normal) - || (lhs.m_masks.size() != rhs.m_masks.size())) - { - return false; - } - - for (auto& mask : lhs.m_masks) - { - auto maskEntry = rhs.m_masks.find(mask.first); - if (maskEntry == rhs.m_masks.end()) - { - return false; - } - if (maskEntry->second != mask.second) - { - return false; - } - } - - return true; + return ((lhsPosition == rhs.m_position) + && (lhsNormal == rhs.m_normal) + && (lhsMasks.SurfaceWeightsAreEqual(rhs.m_surfaceTags))); } // Common test function for testing the "Provider" functionality of the component. // Given a set of tags and an expected output, check to see if the component provides the // expected output point. void TestSurfaceDataColliderProvider(AZStd::vector providerTags, bool pointOnProvider, - AZ::Vector3 queryPoint, const SurfaceData::SurfacePoint& expectedOutput) + AZ::Vector3 queryPoint, const AzFramework::SurfaceData::SurfacePoint& expectedOutput) { // This lets our component register with surfaceData successfully. MockSurfaceDataSystem mockSurfaceDataSystem; @@ -135,8 +121,6 @@ namespace UnitTest // Create the test entity with the SurfaceDataCollider component and the required physics collider dependency auto entity = CreateEntity(); - // Initialize our Entity ID to the one passed in on the expectedOutput - entity->SetId(expectedOutput.m_entityId); // Create the components CreateComponent(entity.get()); CreateComponent(entity.get(), config); @@ -155,17 +139,25 @@ namespace UnitTest queryPoint, pointList); if (pointOnProvider) { - ASSERT_TRUE(pointList.size() == 1); - EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); + ASSERT_EQ(pointList.GetSize(), 1); + pointList.EnumeratePoints( + [this, expectedOutput]( + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool + { + EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput)); + return true; + }); } else { - EXPECT_TRUE(pointList.empty()); + EXPECT_TRUE(pointList.IsEmpty()); } } void TestSurfaceDataColliderModifier(AZStd::vector modifierTags, - const SurfaceData::SurfacePoint& input, bool pointInCollider, const SurfaceData::SurfacePoint& expectedOutput) + const AzFramework::SurfaceData::SurfacePoint& input, + bool pointInCollider, + const AzFramework::SurfaceData::SurfacePoint& expectedOutput) { // This lets our component register with surfaceData successfully. MockSurfaceDataSystem mockSurfaceDataSystem; @@ -191,11 +183,18 @@ namespace UnitTest EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); // Call ModifySurfacePoints and verify the results - SurfaceData::SurfacePointList pointList; - pointList.emplace_back(input); + // Add the surface point with a different entity ID than the entity doing the modification, so that the point doesn't get + // filtered out. + SurfaceData::SurfacePointList pointList = { input }; SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); - ASSERT_TRUE(pointList.size() == 1); - EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); + ASSERT_EQ(pointList.GetSize(), 1); + pointList.EnumeratePoints( + [this, expectedOutput]( + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool + { + EXPECT_TRUE(SurfacePointsAreEqual(position, normal, masks, expectedOutput)); + return true; + }); } }; @@ -232,7 +231,8 @@ namespace UnitTest // Set the expected output to an arbitrary entity ID, position, and normal. // We'll use this to initialize the mock physics, so the output of the query should match. const char* tag = "test_mask"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), + AzFramework::SurfaceData::SurfacePoint expectedOutput = + CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), { AZStd::make_pair(tag, 1.0f) }); // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision @@ -248,7 +248,8 @@ namespace UnitTest // Set the expected output to an arbitrary entity ID, position, and normal. // We'll use this to initialize the mock physics. const char* tag = "test_mask"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), + AzFramework::SurfaceData::SurfacePoint expectedOutput = + CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), { AZStd::make_pair(tag, 1.0f) }); // Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide @@ -266,9 +267,9 @@ namespace UnitTest // We'll use this to initialize the mock physics. const char* tag1 = "test_mask1"; const char* tag2 = "test_mask2"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), - { AZStd::make_pair(tag1, 1.0f), - AZStd::make_pair(tag2, 1.0f) }); + AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint( + AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), + { AZStd::make_pair(tag1, 1.0f), AZStd::make_pair(tag2, 1.0f) }); // Query from the same XY, but one unit higher on Z, just so we can verify that the output returns the collision // result, not the input point. @@ -281,11 +282,12 @@ namespace UnitTest // Verify that for a point inside the collider, the output point contains the correct tag and value. // Set arbitrary input data - SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input, but with an added tag / value const char* tag = "test_mask"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, - { AZStd::make_pair(tag, 1.0f) }); + AzFramework::SurfaceData::SurfacePoint expectedOutput = + CreateSurfacePoint(input.m_position, input.m_normal, + { AZStd::make_pair(tag, 1.0f) }); constexpr bool pointInCollider = true; TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput); @@ -296,10 +298,10 @@ namespace UnitTest // Verify that for a point outside the collider, the output point contains no tags / values. // Set arbitrary input data - SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input - no extra tags / values should be added. const char* tag = "test_mask"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, {}); + AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_position, input.m_normal, {}); constexpr bool pointInCollider = true; TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput); @@ -310,11 +312,12 @@ namespace UnitTest // Verify that if the component has multiple tags, all of them get put on the output with the same value. // Set arbitrary input data - SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); + AzFramework::SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), {}); // Output should match the input, but with two added tags const char* tag1 = "test_mask1"; const char* tag2 = "test_mask2"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, + AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint( + input.m_position, input.m_normal, { AZStd::make_pair(tag1, 1.0f), AZStd::make_pair(tag2, 1.0f) }); constexpr bool pointInCollider = true; @@ -328,11 +331,13 @@ namespace UnitTest // Set arbitrary input data const char* preservedTag = "preserved_tag"; - SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), + AzFramework::SurfaceData::SurfacePoint input = + CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(preservedTag, 1.0f) }); // Output should match the input, but with two added tags const char* modifierTag = "modifier_tag"; - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, + AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint( + input.m_position, input.m_normal, { AZStd::make_pair(preservedTag, 1.0f), AZStd::make_pair(modifierTag, 1.0f) }); constexpr bool pointInCollider = true; @@ -349,10 +354,12 @@ namespace UnitTest float inputValue = 0.25f; // Set arbitrary input data - SurfaceData::SurfacePoint input = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), + AzFramework::SurfaceData::SurfacePoint input = + CreateSurfacePoint(AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair(tag, inputValue) }); // Output should match the input, except that the value on the tag gets the higher modifier value - SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, + AzFramework::SurfaceData::SurfacePoint expectedOutput = + CreateSurfacePoint(input.m_position, input.m_normal, { AZStd::make_pair(tag, 1.0f) }); constexpr bool pointInCollider = true; diff --git a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp index 2d6d64f65d..39659313f5 100644 --- a/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp +++ b/Gems/SurfaceData/Code/Tests/SurfaceDataTest.cpp @@ -54,7 +54,7 @@ class MockSurfaceProvider } private: - AZStd::unordered_map, SurfaceData::SurfacePointList> m_GetSurfacePoints; + AZStd::unordered_map, AZStd::vector> m_GetSurfacePoints; SurfaceData::SurfaceTagVector m_tags; ProviderType m_providerType; AZ::EntityId m_id; @@ -71,15 +71,16 @@ class MockSurfaceProvider { for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX()) { - SurfaceData::SurfacePointList points; + AZStd::vector points; for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ()) { - SurfaceData::SurfacePoint point; - point.m_entityId = m_id; + AzFramework::SurfaceData::SurfacePoint point; point.m_position = AZ::Vector3(x, y, z); point.m_normal = AZ::Vector3::CreateAxisZ(); - AddMaxValueForMasks(point.m_masks, m_tags, 1.0f); - + for (auto& tag : m_tags) + { + point.m_surfaceTags.emplace_back(tag, 1.0f); + } points.push_back(point); } m_GetSurfacePoints[AZStd::pair(x, y)] = points; @@ -150,7 +151,8 @@ class MockSurfaceProvider { for (auto& point : surfacePoints->second) { - surfacePointList.push_back(point); + SurfaceData::SurfaceTagWeights weights(point.m_surfaceTags); + surfacePointList.AddSurfacePoint(m_id, point.m_position, point.m_normal, weights); } } } @@ -159,16 +161,17 @@ class MockSurfaceProvider // SurfaceDataModifierRequestBus void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override { - for (auto& point : surfacePointList) - { - auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(point.m_position.GetX(), point.m_position.GetY())); - - if (surfacePoints != m_GetSurfacePoints.end()) + surfacePointList.ModifySurfaceWeights( + AZ::EntityId(), + [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights) { - AddMaxValueForMasks(point.m_masks, m_tags, 1.0f); - } - } + auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(position.GetX(), position.GetY())); + if (surfacePoints != m_GetSurfacePoints.end()) + { + weights.AddSurfaceWeightsIfGreater(m_tags, 1.0f); + } + }); } SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle; @@ -205,42 +208,49 @@ public: } void CompareSurfacePointListWithGetSurfacePoints( - const AZStd::vector& queryPositions, SurfaceData::SurfacePointLists surfacePointLists, + const AZStd::vector& queryPositions, SurfaceData::SurfacePointLists& surfacePointLists, const SurfaceData::SurfaceTagVector& testTags) { - SurfaceData::SurfacePointLists singleQueryPointLists; + AZStd::vector singleQueryResults; for (auto& queryPosition : queryPositions) { SurfaceData::SurfacePointList tempSingleQueryPointList; SurfaceData::SurfaceDataSystemRequestBus::Broadcast( &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, tempSingleQueryPointList); - singleQueryPointLists.push_back(tempSingleQueryPointList); + tempSingleQueryPointList.EnumeratePoints( + [&singleQueryResults]( + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool + { + AzFramework::SurfaceData::SurfacePoint point; + point.m_position = position; + point.m_normal = normal; + point.m_surfaceTags = masks.GetSurfaceTagWeightList(); + singleQueryResults.emplace_back(AZStd::move(point)); + return true; + }); } - // Verify the two point lists are the same size, then verify that each point in each list is equal. - ASSERT_EQ(singleQueryPointLists.size(), surfacePointLists.size()); + // Verify that each point in each list is equal. + AzFramework::SurfaceData::SurfacePoint* singleQueryPoint = singleQueryResults.begin(); for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++) { auto& surfacePointList = surfacePointLists[listIndex]; - auto& singleQueryPointList = singleQueryPointLists[listIndex]; - - ASSERT_EQ(singleQueryPointList.size(), surfacePointList.size()); - for (size_t index = 0; index < surfacePointList.size(); index++) - { - SurfaceData::SurfacePoint& point1 = surfacePointList[index]; - SurfaceData::SurfacePoint& point2 = singleQueryPointList[index]; - - EXPECT_EQ(point1.m_entityId, point2.m_entityId); - EXPECT_EQ(point1.m_position, point2.m_position); - EXPECT_EQ(point1.m_normal, point2.m_normal); - ASSERT_EQ(point1.m_masks.size(), point2.m_masks.size()); - for (auto& mask : point1.m_masks) + surfacePointList.EnumeratePoints( + [&singleQueryPoint, singleQueryResults]( + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool { - EXPECT_EQ(mask.second, point2.m_masks[mask.first]); - } - } + EXPECT_NE(singleQueryPoint, singleQueryResults.end()); + + EXPECT_EQ(position, singleQueryPoint->m_position); + EXPECT_EQ(normal, singleQueryPoint->m_normal); + EXPECT_TRUE(masks.SurfaceWeightsAreEqual(singleQueryPoint->m_surfaceTags)); + ++singleQueryPoint; + return true; + }); } + + EXPECT_EQ(singleQueryPoint, singleQueryResults.end()); } @@ -488,13 +498,18 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion) // We *could* check every mask as well for completeness, but that seems like overkill. for (auto& pointList : availablePointsPerPosition) { - EXPECT_EQ(pointList.size(), 2); - EXPECT_EQ(pointList[0].m_position.GetZ(), 4.0f); - EXPECT_EQ(pointList[1].m_position.GetZ(), 0.0f); - for (auto& point : pointList) - { - EXPECT_EQ(point.m_masks.size(), providerTags.size()); - } + EXPECT_EQ(pointList.GetSize(), 2); + float expectedZ = 4.0f; + pointList.EnumeratePoints( + [providerTags, + &expectedZ](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) -> bool + { + EXPECT_EQ(position.GetZ(), expectedZ); + EXPECT_EQ(masks.GetSize(), providerTags.size()); + expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; + return true; + }); } } @@ -523,7 +538,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingMas // any of the masks from our mock surface provider. for (auto& queryPosition : availablePointsPerPosition) { - EXPECT_TRUE(queryPosition.empty()); + EXPECT_TRUE(queryPosition.IsEmpty()); } } @@ -551,7 +566,7 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_NoMatchingReg // our surface provider. for (auto& pointList : availablePointsPerPosition) { - EXPECT_TRUE(pointList.empty()); + EXPECT_TRUE(pointList.IsEmpty()); } } @@ -601,14 +616,17 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_ProviderModif // and each point should have both the "test_surface1" and "test_surface2" tag. for (auto& pointList : availablePointsPerPosition) { - EXPECT_EQ(pointList.size(), 2); + EXPECT_EQ(pointList.GetSize(), 2); float expectedZ = 4.0f; - for (auto& point : pointList) - { - EXPECT_EQ(point.m_position.GetZ(), expectedZ); - EXPECT_EQ(point.m_masks.size(), 2); - expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; - } + pointList.EnumeratePoints( + [&expectedZ](const AZ::Vector3& position, + [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool + { + EXPECT_EQ(position.GetZ(), expectedZ); + EXPECT_EQ(masks.GetSize(), 2); + expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; + return true; + }); } } } @@ -648,14 +666,20 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_SimilarPoints // should have both surface tags on them. for (auto& pointList : availablePointsPerPosition) { - EXPECT_EQ(pointList.size(), 2); - float expectedZ = 4.0005f; - for (auto& point : pointList) - { - EXPECT_EQ(point.m_position.GetZ(), expectedZ); - EXPECT_EQ(point.m_masks.size(), 2); - expectedZ = (expectedZ == 4.0005f) ? 0.0005f : 4.0005f; - } + EXPECT_EQ(pointList.GetSize(), 2); + float expectedZ = 4.0f; + pointList.EnumeratePoints( + [&expectedZ]( + const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) -> bool + { + // Similar points get merged, but there's no guarantee which value will be kept, so we set our comparison tolerance + // high enough to allow both x.0 and x.0005 to pass. + EXPECT_NEAR(position.GetZ(), expectedZ, 0.001f); + EXPECT_EQ(masks.GetSize(), 2); + expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; + return true; + }); } } @@ -693,11 +717,14 @@ TEST_F(SurfaceDataTestApp, SurfaceData_TestSurfacePointsFromRegion_DissimilarPoi // because the points are far enough apart that they won't merge. for (auto& pointList : availablePointsPerPosition) { - EXPECT_EQ(pointList.size(), 4); - for (auto& point : pointList) - { - EXPECT_EQ(point.m_masks.size(), 1); - } + EXPECT_EQ(pointList.GetSize(), 4); + pointList.EnumeratePoints( + []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal, + const SurfaceData::SurfaceTagWeights& masks) -> bool + { + EXPECT_EQ(masks.GetSize(), 1); + return true; + }); } } diff --git a/Gems/SurfaceData/Code/surfacedata_files.cmake b/Gems/SurfaceData/Code/surfacedata_files.cmake index 4b8ac914d7..1c36ca43a0 100644 --- a/Gems/SurfaceData/Code/surfacedata_files.cmake +++ b/Gems/SurfaceData/Code/surfacedata_files.cmake @@ -19,6 +19,7 @@ set(FILES Include/SurfaceData/Utility/SurfaceDataUtility.h Source/SurfaceDataSystemComponent.cpp Source/SurfaceDataSystemComponent.h + Source/SurfaceDataTypes.cpp Source/SurfaceTag.cpp Source/Components/SurfaceDataColliderComponent.cpp Source/Components/SurfaceDataColliderComponent.h diff --git a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp index 9a9cb24e4c..3092f7c000 100644 --- a/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp +++ b/Gems/Terrain/Code/Source/Components/TerrainSurfaceDataSystemComponent.cpp @@ -159,25 +159,13 @@ namespace Terrain const bool isHole = !isTerrainValidAtPoint; - SurfaceData::SurfacePoint point; - point.m_entityId = GetEntityId(); - point.m_position = terrainSurfacePoint.m_position; - point.m_normal = terrainSurfacePoint.m_normal; - - // Preallocate enough space for all of our terrain's surface tags, plus the default "terrain" / "terrainHole" tag. - point.m_masks.reserve(terrainSurfacePoint.m_surfaceTags.size() + 1); - - // Add all of the surface tags that the terrain has at this point. - for (auto& tag : terrainSurfacePoint.m_surfaceTags) - { - point.m_masks[tag.m_surfaceType] = tag.m_weight; - } + SurfaceData::SurfaceTagWeights weights(terrainSurfacePoint.m_surfaceTags); // Always add a "terrain" or "terrainHole" tag. const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc; - point.m_masks[terrainTag] = 1.0f; + weights.AddSurfaceTagWeight(terrainTag, 1.0f); - surfacePointList.push_back(AZStd::move(point)); + surfacePointList.AddSurfacePoint(GetEntityId(), terrainSurfacePoint.m_position, terrainSurfacePoint.m_normal, weights); } AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const diff --git a/Gems/Vegetation/Code/Include/Vegetation/Ebuses/AreaRequestBus.h b/Gems/Vegetation/Code/Include/Vegetation/Ebuses/AreaRequestBus.h index 3323e2a805..605369671d 100644 --- a/Gems/Vegetation/Code/Include/Vegetation/Ebuses/AreaRequestBus.h +++ b/Gems/Vegetation/Code/Include/Vegetation/Ebuses/AreaRequestBus.h @@ -56,12 +56,12 @@ namespace Vegetation ClaimHandle m_handle; AZ::Vector3 m_position; AZ::Vector3 m_normal; - SurfaceData::SurfaceTagWeightMap m_masks; + SurfaceData::SurfaceTagWeights m_masks; }; struct ClaimContext { - SurfaceData::SurfaceTagWeightMap m_masks; + SurfaceData::SurfaceTagWeights m_masks; AZStd::vector m_availablePoints; AZStd::function m_existedCallback; AZStd::function m_createdCallback; diff --git a/Gems/Vegetation/Code/Include/Vegetation/InstanceData.h b/Gems/Vegetation/Code/Include/Vegetation/InstanceData.h index 7e685105f7..b13bef5ae7 100644 --- a/Gems/Vegetation/Code/Include/Vegetation/InstanceData.h +++ b/Gems/Vegetation/Code/Include/Vegetation/InstanceData.h @@ -34,7 +34,7 @@ namespace Vegetation AZ::Quaternion m_rotation = AZ::Quaternion::CreateIdentity(); AZ::Quaternion m_alignment = AZ::Quaternion::CreateIdentity(); float m_scale = 1.0f; - SurfaceData::SurfaceTagWeightMap m_masks; //[LY-90908] remove when surface mask filtering is done in area + SurfaceData::SurfaceTagWeights m_masks; //[LY-90908] remove when surface mask filtering is done in area DescriptorPtr m_descriptorPtr; // Determine if two different sets of instance data are similar enough to be considered the same when placing diff --git a/Gems/Vegetation/Code/Source/AreaSystemComponent.cpp b/Gems/Vegetation/Code/Source/AreaSystemComponent.cpp index 9cd9c1a7cb..e575480ff4 100644 --- a/Gems/Vegetation/Code/Source/AreaSystemComponent.cpp +++ b/Gems/Vegetation/Code/Source/AreaSystemComponent.cpp @@ -1091,7 +1091,7 @@ namespace Vegetation const float vegStep = sectorSizeInMeters / static_cast(sectorDensity); //build a free list of all points in the sector for areas to consume - sectorInfo.m_baseContext.m_masks.clear(); + sectorInfo.m_baseContext.m_masks.Clear(); sectorInfo.m_baseContext.m_availablePoints.clear(); sectorInfo.m_baseContext.m_availablePoints.reserve(sectorDensity * sectorDensity); @@ -1127,16 +1127,19 @@ namespace Vegetation uint claimIndex = 0; for (auto& availablePoints : availablePointsPerPosition) { - for (auto& surfacePoint : availablePoints) - { - sectorInfo.m_baseContext.m_availablePoints.push_back(); - ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back(); - claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex); - claimPoint.m_position = surfacePoint.m_position; - claimPoint.m_normal = surfacePoint.m_normal; - claimPoint.m_masks = surfacePoint.m_masks; - SurfaceData::AddMaxValueForMasks(sectorInfo.m_baseContext.m_masks, surfacePoint.m_masks); - } + availablePoints.EnumeratePoints( + [this, §orInfo, + &claimIndex](const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool + { + sectorInfo.m_baseContext.m_availablePoints.push_back(); + ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back(); + claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex); + claimPoint.m_position = position; + claimPoint.m_normal = normal; + claimPoint.m_masks = masks; + sectorInfo.m_baseContext.m_masks.AddSurfaceWeightsIfGreater(masks); + return true; + }); } } diff --git a/Gems/Vegetation/Code/Source/Components/PositionModifierComponent.cpp b/Gems/Vegetation/Code/Source/Components/PositionModifierComponent.cpp index 17457786f2..ed81ebae87 100644 --- a/Gems/Vegetation/Code/Source/Components/PositionModifierComponent.cpp +++ b/Gems/Vegetation/Code/Source/Components/PositionModifierComponent.cpp @@ -306,31 +306,40 @@ namespace Vegetation m_surfaceTagsToSnapToCombined.clear(); m_surfaceTagsToSnapToCombined.reserve( m_configuration.m_surfaceTagsToSnapTo.size() + - instanceData.m_masks.size()); + instanceData.m_masks.GetSize()); m_surfaceTagsToSnapToCombined.insert(m_surfaceTagsToSnapToCombined.end(), m_configuration.m_surfaceTagsToSnapTo.begin(), m_configuration.m_surfaceTagsToSnapTo.end()); - for (const auto& maskPair : instanceData.m_masks) - { - m_surfaceTagsToSnapToCombined.push_back(maskPair.first); - } + instanceData.m_masks.EnumerateWeights( + [this](AZ::Crc32 surfaceType, [[maybe_unused]] float weight) + { + m_surfaceTagsToSnapToCombined.push_back(surfaceType); + return true; + }); //get the intersection data at the new position - m_points.clear(); + m_points.Clear(); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, m_surfaceTagsToSnapToCombined, m_points); - if (!m_points.empty()) - { - //sort the intersection data by distance from the new position in case there are multiple intersections at different or unrelated heights - AZStd::sort(m_points.begin(), m_points.end(), [&instanceData](const SurfaceData::SurfacePoint& a, const SurfaceData::SurfacePoint& b) + + // Get the point with the closest distance from the new position in case there are multiple intersections at different or + // unrelated heights + float closestPointDistanceSq = AZStd::numeric_limits::max(); + AZ::Vector3 originalInstanceDataPosition = instanceData.m_position; + m_points.EnumeratePoints( + [&instanceData, originalInstanceDataPosition, &closestPointDistanceSq]( + const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool { - return a.m_position.GetDistanceSq(instanceData.m_position) < b.m_position.GetDistanceSq(instanceData.m_position); + float distanceSq = position.GetDistanceSq(originalInstanceDataPosition); + if (distanceSq < closestPointDistanceSq) + { + instanceData.m_position = position; + instanceData.m_normal = normal; + instanceData.m_masks = masks; + closestPointDistanceSq = distanceSq; + } + return true; }); - - instanceData.m_position = m_points[0].m_position; - instanceData.m_normal = m_points[0].m_normal; - instanceData.m_masks = m_points[0].m_masks; - } } instanceData.m_position.SetZ(instanceData.m_position.GetZ() + delta.GetZ()); diff --git a/Gems/Vegetation/Code/Source/Components/SpawnerComponent.cpp b/Gems/Vegetation/Code/Source/Components/SpawnerComponent.cpp index 404e425489..cc7b75f867 100644 --- a/Gems/Vegetation/Code/Source/Components/SpawnerComponent.cpp +++ b/Gems/Vegetation/Code/Source/Components/SpawnerComponent.cpp @@ -416,9 +416,9 @@ namespace Vegetation AZ_PROFILE_FUNCTION(Entity); //reject entire spawner if there are inclusion tags to consider that don't exist in the context - if (SurfaceData::HasValidTags(context.m_masks) && + if (context.m_masks.HasValidTags() && SurfaceData::HasValidTags(m_inclusiveTagsToConsider) && - !SurfaceData::HasMatchingTags(context.m_masks, m_inclusiveTagsToConsider)) + !context.m_masks.HasAnyMatchingTags(m_inclusiveTagsToConsider)) { VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::MarkAreaRejectedByMask, GetEntityId())); return; diff --git a/Gems/Vegetation/Code/Source/Components/SurfaceMaskDepthFilterComponent.cpp b/Gems/Vegetation/Code/Source/Components/SurfaceMaskDepthFilterComponent.cpp index b4e65beb3f..aa6c3a8440 100644 --- a/Gems/Vegetation/Code/Source/Components/SurfaceMaskDepthFilterComponent.cpp +++ b/Gems/Vegetation/Code/Source/Components/SurfaceMaskDepthFilterComponent.cpp @@ -210,26 +210,38 @@ namespace Vegetation float lowerZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_lowerDistanceInMeters : m_configuration.m_lowerDistance; float upperZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_upperDistanceInMeters : m_configuration.m_upperDistance; + bool passesFilter = false; + if (!surfaceTagsToCompare.empty()) { - m_points.clear(); + m_points.Clear(); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, surfaceTagsToCompare, m_points); float instanceZ = instanceData.m_position.GetZ(); - for (auto& point : m_points) - { - float pointZ = point.m_position.GetZ(); - float zDistance = instanceZ - pointZ; - if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange) + m_points.EnumeratePoints( + [instanceZ, lowerZDistanceRange, upperZDistanceRange, &passesFilter]( + const AZ::Vector3& position, + [[maybe_unused]] const AZ::Vector3& normal, [[maybe_unused]] const SurfaceData::SurfaceTagWeights& masks) -> bool { + float pointZ = position.GetZ(); + float zDistance = instanceZ - pointZ; + if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange) + { + passesFilter = true; + return false; + } return true; - } - } + }); } - // if we get here instance is marked filtered - VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceDepthMaskFilter"))); - return false; + if (!passesFilter) + { + // if we get here instance is marked filtered + VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast( + &DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceDepthMaskFilter"))); + } + + return passesFilter; } FilterStage SurfaceMaskDepthFilterComponent::GetFilterStage() const diff --git a/Gems/Vegetation/Code/Source/Components/SurfaceMaskFilterComponent.cpp b/Gems/Vegetation/Code/Source/Components/SurfaceMaskFilterComponent.cpp index 9be6764460..23ef5ab3cb 100644 --- a/Gems/Vegetation/Code/Source/Components/SurfaceMaskFilterComponent.cpp +++ b/Gems/Vegetation/Code/Source/Components/SurfaceMaskFilterComponent.cpp @@ -281,14 +281,15 @@ namespace Vegetation const float exclusiveWeightMax = AZ::GetMax(m_configuration.m_exclusiveWeightMin, m_configuration.m_exclusiveWeightMax); if (useCompTags && - SurfaceData::HasMatchingTags(instanceData.m_masks, m_configuration.m_exclusiveSurfaceMasks, exclusiveWeightMin, exclusiveWeightMax)) + instanceData.m_masks.HasAnyMatchingTags(m_configuration.m_exclusiveSurfaceMasks, exclusiveWeightMin, exclusiveWeightMax)) { VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter"))); return false; } if (useDescTags && - SurfaceData::HasMatchingTags(instanceData.m_masks, instanceData.m_descriptorPtr->m_exclusiveSurfaceFilterTags, exclusiveWeightMin, exclusiveWeightMax)) + instanceData.m_masks.HasAnyMatchingTags( + instanceData.m_descriptorPtr->m_exclusiveSurfaceFilterTags, exclusiveWeightMin, exclusiveWeightMax)) { VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter"))); return false; @@ -299,13 +300,14 @@ namespace Vegetation const float inclusiveWeightMax = AZ::GetMax(m_configuration.m_inclusiveWeightMin, m_configuration.m_inclusiveWeightMax); if (useCompTags && - SurfaceData::HasMatchingTags(instanceData.m_masks, m_configuration.m_inclusiveSurfaceMasks, inclusiveWeightMin, inclusiveWeightMax)) + instanceData.m_masks.HasAnyMatchingTags(m_configuration.m_inclusiveSurfaceMasks, inclusiveWeightMin, inclusiveWeightMax)) { return true; } if (useDescTags && - SurfaceData::HasMatchingTags(instanceData.m_masks, instanceData.m_descriptorPtr->m_inclusiveSurfaceFilterTags, inclusiveWeightMin, inclusiveWeightMax)) + instanceData.m_masks.HasAnyMatchingTags( + instanceData.m_descriptorPtr->m_inclusiveSurfaceFilterTags, inclusiveWeightMin, inclusiveWeightMax)) { return true; } diff --git a/Gems/Vegetation/Code/Source/Debugger/DebugComponent.cpp b/Gems/Vegetation/Code/Source/Debugger/DebugComponent.cpp index b415a7c41c..abb577acfc 100644 --- a/Gems/Vegetation/Code/Source/Debugger/DebugComponent.cpp +++ b/Gems/Vegetation/Code/Source/Debugger/DebugComponent.cpp @@ -862,7 +862,7 @@ void DebugComponent::PrepareNextReport() SurfaceData::SurfacePointList points; SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, pos, SurfaceData::SurfaceTagVector(), points); - timing.m_worldPosition = points.empty() ? pos : points.front().m_position; + timing.m_worldPosition = points.IsEmpty() ? pos : points.GetHighestSurfacePoint().m_position; return timing; }, [](const SectorTracker& sectorTracker, SectorTiming& sectorTiming) diff --git a/Gems/Vegetation/Code/Tests/VegetationComponentFilterTests.cpp b/Gems/Vegetation/Code/Tests/VegetationComponentFilterTests.cpp index bb1efb5ebd..377ed31890 100644 --- a/Gems/Vegetation/Code/Tests/VegetationComponentFilterTests.cpp +++ b/Gems/Vegetation/Code/Tests/VegetationComponentFilterTests.cpp @@ -80,7 +80,7 @@ namespace UnitTest }); Vegetation::InstanceData vegInstance; - vegInstance.m_masks[maskValue] = 1.0f; + vegInstance.m_masks.AddSurfaceTagWeight(maskValue, 1.0f); // passes { @@ -119,8 +119,7 @@ namespace UnitTest MockSurfaceHandler mockSurfaceHandler; mockSurfaceHandler.m_outPosition = AZ::Vector3::CreateZero(); mockSurfaceHandler.m_outNormal = AZ::Vector3::CreateAxisZ(); - mockSurfaceHandler.m_outMasks.clear(); - mockSurfaceHandler.m_outMasks[SurfaceData::Constants::s_unassignedTagCrc] = 1.0f; + mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(SurfaceData::Constants::s_unassignedTagCrc, 1.0f); // passes { diff --git a/Gems/Vegetation/Code/Tests/VegetationComponentModifierTests.cpp b/Gems/Vegetation/Code/Tests/VegetationComponentModifierTests.cpp index e60a681512..f9ee408140 100644 --- a/Gems/Vegetation/Code/Tests/VegetationComponentModifierTests.cpp +++ b/Gems/Vegetation/Code/Tests/VegetationComponentModifierTests.cpp @@ -71,7 +71,7 @@ namespace UnitTest MockSurfaceHandler mockSurfaceHandler; mockSurfaceHandler.m_outPosition = AZ::Vector3(vegInstance.m_position.GetX(), vegInstance.m_position.GetY(), 6.0f); mockSurfaceHandler.m_outNormal = AZ::Vector3(0.0f, 0.0f, 1.0f); - mockSurfaceHandler.m_outMasks[crcMask] = 1.0f; + mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(crcMask, 1.0f); entity->Deactivate(); config.m_autoSnapToSurface = true; diff --git a/Gems/Vegetation/Code/Tests/VegetationMocks.h b/Gems/Vegetation/Code/Tests/VegetationMocks.h index c1d83fb68b..d77862d578 100644 --- a/Gems/Vegetation/Code/Tests/VegetationMocks.h +++ b/Gems/Vegetation/Code/Tests/VegetationMocks.h @@ -329,15 +329,11 @@ namespace UnitTest AZ::Vector3 m_outPosition = {}; AZ::Vector3 m_outNormal = {}; - SurfaceData::SurfaceTagWeightMap m_outMasks; + SurfaceData::SurfaceTagWeights m_outMasks; void GetSurfacePoints([[maybe_unused]] const AZ::Vector3& inPosition, [[maybe_unused]] const SurfaceData::SurfaceTagVector& masks, SurfaceData::SurfacePointList& surfacePointList) const override { ++m_count; - SurfaceData::SurfacePoint outPoint; - outPoint.m_position = m_outPosition; - outPoint.m_normal = m_outNormal; - SurfaceData::AddMaxValueForMasks(outPoint.m_masks, m_outMasks); - surfacePointList.push_back(outPoint); + surfacePointList.AddSurfacePoint(AZ::EntityId(), m_outPosition, m_outNormal, m_outMasks); } void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,