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,