SurfacePoint data structure encapsulations (#7413)

* First pass at encapsulating SurfacePointList.
The biggest challenge in optimizing SurfacePointList(s) usage is the overall memory management associated with it. There are M surface points with N surface mask entries created for every input point, which leads to a lot of container reallocation and memory shuffling when processing multiple input points. By encapsulating the list, it should become easier to preallocate the entries, as well as keep "helper data" around for managing the bookkeeping to associate the input points with the output points.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Small fixes and TODO reminders.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Encapsulate surface point creation and separate EnumeratePoints out from modifications.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Start removing SurfacePoint from the exposed API.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Changed SurfacePointList to split out the surface point storage to allow for span<> usage over time.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Removed entity id

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Removed SurfacePoint from SurfaceData, changed all remaining uses to AzFramework::SurfaceData::SurfacePoint.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Encapsulated SurfaceTagWeightMap and renamed to SurfaceTagWeights.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Fixed make file.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Better commenting and parameter naming.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>

* Renamed methods to be more descriptive.

Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
monroegm-disable-blank-issue-2
Mike Balfour 4 years ago committed by GitHub
parent ff4412db7c
commit d9ba0af645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -95,7 +95,7 @@ namespace SurfaceData
m_refresh = false; m_refresh = false;
// Update the cached mesh data and bounds, then register the surface data provider // 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(); UpdateMeshData();
} }
@ -178,12 +178,7 @@ namespace SurfaceData
AZ::Vector3 hitNormal; AZ::Vector3 hitNormal;
if (DoRayTrace(inPosition, hitPosition, hitNormal)) if (DoRayTrace(inPosition, hitPosition, hitNormal))
{ {
SurfacePoint point; surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights);
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }

@ -103,6 +103,6 @@ namespace SurfaceData
AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity(); AZ::Transform m_meshWorldTMInverse = AZ::Transform::CreateIdentity();
AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne(); AZ::Vector3 m_meshNonUniformScale = AZ::Vector3::CreateOne();
AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_meshBounds = AZ::Aabb::CreateNull();
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -109,14 +109,14 @@ namespace GradientSignal
private: private:
static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax) static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, float altitudeMin, float altitudeMax)
{ {
if (points.empty()) if (points.IsEmpty())
{ {
return 0.0f; return 0.0f;
} }
// GetSurfacePoints (which was used to populate the points list) always returns points in decreasing height order, so the // 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. // 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. // 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); return GetRatio(altitudeMin, altitudeMax, highestAltitude);

@ -85,13 +85,18 @@ namespace GradientSignal
{ {
float result = 0.0f; 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) masks.EnumerateWeights(
{ [&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool
result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result); {
} result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result);
} return true;
});
return true;
});
return result; return result;
} }

@ -125,7 +125,7 @@ namespace GradientSignal
private: private:
float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const
{ {
if (points.empty()) if (points.IsEmpty())
{ {
return 0.0f; return 0.0f;
} }
@ -133,9 +133,9 @@ namespace GradientSignal
// Assuming our surface normal vector is actually normalized, we can get the slope // 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()). // by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()).
AZ_Assert( 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"); "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". // 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) // (We want our 0-1 range to be linear across the range of angles)
const float slopeAngle = acosf(slope); const float slopeAngle = acosf(slope);

@ -224,10 +224,9 @@ namespace GradientSignal
validShapeBounds = m_cachedShapeConstraintBounds.IsValid(); validShapeBounds = m_cachedShapeConstraintBounds.IsValid();
} }
const AZ::EntityId entityId = GetEntityId(); surfacePointList.ModifySurfaceWeights(
for (auto& point : surfacePointList) GetEntityId(),
{ [this, validShapeBounds, shapeConstraintBounds](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (point.m_entityId != entityId)
{ {
bool inBounds = true; bool inBounds = true;
@ -236,28 +235,26 @@ namespace GradientSignal
if (validShapeBounds) if (validShapeBounds)
{ {
inBounds = false; inBounds = false;
if (shapeConstraintBounds.Contains(point.m_position)) if (shapeConstraintBounds.Contains(position))
{ {
LmbrCentral::ShapeComponentRequestsBus::EventResult(inBounds, m_configuration.m_shapeConstraintEntityId, LmbrCentral::ShapeComponentRequestsBus::EventResult(
&LmbrCentral::ShapeComponentRequestsBus::Events::IsPointInside, point.m_position); 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 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) if (inBounds)
{ {
const GradientSampleParams sampleParams = { point.m_position }; const GradientSampleParams sampleParams = { position };
const float value = m_gradientSampler.GetValue(sampleParams); const float value = m_gradientSampler.GetValue(sampleParams);
if (value >= m_configuration.m_thresholdMin && if (value >= m_configuration.m_thresholdMin && value <= m_configuration.m_thresholdMax)
value <= m_configuration.m_thresholdMax)
{ {
SurfaceData::AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, value); weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, value);
} }
} }
});
}
}
} }
} }

@ -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 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++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points);
outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax); outValues[index] = CalculateAltitudeRatio(points, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax);
} }

@ -198,7 +198,7 @@ namespace GradientSignal
for (size_t index = 0; index < positions.size(); index++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points);
outValues[index] = GetMaxSurfaceWeight(points); outValues[index] = GetMaxSurfaceWeight(points);
} }

@ -239,7 +239,7 @@ namespace GradientSignal
for (size_t index = 0; index < positions.size(); index++) for (size_t index = 0; index < positions.size(); index++)
{ {
points.clear(); points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points); surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points);
outValues[index] = GetSlopeRatio(points, angleMin, angleMax); outValues[index] = GetSlopeRatio(points, angleMin, angleMax);
} }

@ -98,10 +98,9 @@ namespace GradientSignal
return [this]([[maybe_unused]] float sampleValue, const GradientSampleParams& params) return [this]([[maybe_unused]] float sampleValue, const GradientSampleParams& params)
{ {
// Create a fake surface point with the position we're sampling. // Create a fake surface point with the position we're sampling.
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_position = params.m_position; point.m_position = params.m_position;
SurfaceData::SurfacePointList pointList; SurfaceData::SurfacePointList pointList = { { point } };
pointList.emplace_back(point);
// Send it into the component, see what emerges // Send it into the component, see what emerges
m_component.ModifySurfacePoints(pointList); 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 // 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. // the underlying logic ever changes to allow separate ranges per tag.
float result = 0.0f; 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; return result;
}; };
} }

@ -66,7 +66,7 @@ namespace UnitTest
float falloffMidpoint, float falloffRange, float falloffStrength) float falloffMidpoint, float falloffRange, float falloffStrength)
{ {
MockSurfaceDataSystem mockSurfaceDataSystem; 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. // 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++) 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. // 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 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(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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.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. // 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; 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. // 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 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(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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 2.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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 5.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)] = { { entityShape->GetId(), AZ::Vector3(0.0f, 0.0f, 10.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. // We set the min/max to 0-10, but don't set a shape.
GradientSignal::SurfaceAltitudeGradientConfig config; GradientSignal::SurfaceAltitudeGradientConfig config;
@ -634,13 +634,13 @@ namespace UnitTest
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
// Altitude value below min - should result in 0.0f. // 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. // 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. // 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. // 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, // 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. // it should be easier to verify that we're successfully clamping to 0 and 1.
@ -668,14 +668,15 @@ namespace UnitTest
}; };
MockSurfaceDataSystem mockSurfaceDataSystem; 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. // 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 y = 0; y < dataSize; y++)
{ {
for (int x = 0; x < dataSize; x++) 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<float>(x), static_cast<float>(y))] = { { point } }; mockSurfaceDataSystem.m_GetSurfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] = { { point } };
} }
} }

@ -19,27 +19,53 @@ namespace UnitTest
struct GradientSignalSurfaceTestsFixture struct GradientSignalSurfaceTestsFixture
: public GradientSignalTest : public GradientSignalTest
{ {
void SetSurfacePoint(SurfaceData::SurfacePoint& point, AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags) void SetSurfacePoint(AzFramework::SurfaceData::SurfacePoint& point, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
{ {
point.m_entityId = id;
point.m_position = position; point.m_position = position;
point.m_normal = normal; point.m_normal = normal;
for (auto& tag : tags) 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) if ((lhs.m_position != rhs.m_position) || (lhs.m_normal != rhs.m_normal)
&& (lhs.m_position == rhs.m_position) || (lhs.m_surfaceTags.size() != rhs.m_surfaceTags.size()))
&& (lhs.m_normal == rhs.m_normal) {
&& (lhs.m_masks == rhs.m_masks); 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<AZStd::string> tags, bool usesShape, void TestGradientSurfaceDataComponent(float gradientValue, float thresholdMin, float thresholdMax, AZStd::vector<AZStd::string> 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. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -83,10 +109,17 @@ namespace UnitTest
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call ModifySurfacePoints and verify the results // Call ModifySurfacePoints and verify the results
SurfaceData::SurfacePointList pointList; SurfaceData::SurfacePointList pointList = { { input } };
pointList.emplace_back(input);
SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); 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 // Verify that for a gradient value within the threshold, the output point contains the
// correct tag and gradient value. // correct tag and gradient value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // 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 // 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<AZStd::string, float>(tag, gradientValue) }); SetSurfacePoint(expectedOutput, input.m_position, input.m_normal, { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value 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. // Verify that for a gradient value outside the threshold, the output point contains no tags / values.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Choose a value outside the threshold range // Choose a value outside the threshold range
float gradientValue = 0.05f; float gradientValue = 0.05f;
// Set arbitrary input data // 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. // 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( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value 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. // Verify that if the component has multiple tags, all of them get put on the output with the same gradient value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; const char* tag2 = "test_mask2";
@ -158,9 +191,9 @@ namespace UnitTest
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // 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 // 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<AZStd::string, float>(tag1, gradientValue), AZStd::make_pair<AZStd::string, float>(tag2, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag1, gradientValue), AZStd::make_pair<AZStd::string, float>(tag2, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -178,8 +211,8 @@ namespace UnitTest
// Verify that the output contains input tags that are NOT on the modification list and adds any // 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 // new tags that weren't in the input
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* preservedTag = "preserved_tag"; const char* preservedTag = "preserved_tag";
const char* modifierTag = "modifier_tag"; const char* modifierTag = "modifier_tag";
@ -187,9 +220,9 @@ namespace UnitTest
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) });
// Output should match the input, but with two added tags // 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<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, gradientValue) });
TestGradientSurfaceDataComponent( 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. // Verify that if the input has a higher value on the tag than the modifier, it keeps the higher value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
@ -216,9 +249,9 @@ namespace UnitTest
float inputValue = 0.75f; float inputValue = 0.75f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input - the higher input value on the tag is preserved // 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<AZStd::string, float>(tag, inputValue) }); { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
TestGradientSurfaceDataComponent( 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. // Verify that if the input has a lower value on the tag than the modifier, it keeps the higher value.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
@ -245,9 +278,9 @@ namespace UnitTest
float inputValue = 0.25f; float inputValue = 0.25f;
// Set arbitrary input data // Set arbitrary input data
SetSurfacePoint(input, AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) }); SetSurfacePoint(input, AZ::Vector3(1.0f), AZ::Vector3(0.0f), { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input, except that the value on the tag gets the higher modifier value // 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<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -264,17 +297,17 @@ namespace UnitTest
{ {
// Verify that if no shape has been added, the component modifies points in unbounded space // Verify that if no shape has been added, the component modifies points in unbounded space
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; float gradientValue = 0.5f;
// Set arbitrary input data, but with a point that's extremely far away in space // 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. // 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<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -292,17 +325,17 @@ namespace UnitTest
// Verify that if a shape constraint is added, points within the shape are still modified. // 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. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; 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) // 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. // 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<AZStd::string, float>(tag, gradientValue) }); { AZStd::make_pair<AZStd::string, float>(tag, gradientValue) });
TestGradientSurfaceDataComponent( TestGradientSurfaceDataComponent(
@ -320,17 +353,17 @@ namespace UnitTest
// Verify that if a shape constraint is added, points outside the shape are not modified. // 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. // Our default mock shape is a cube that exists from -0.5 to 0.5 in space.
SurfaceData::SurfacePoint input; AzFramework::SurfaceData::SurfacePoint input;
SurfaceData::SurfacePoint expectedOutput; AzFramework::SurfaceData::SurfacePoint expectedOutput;
const char* tag = "test_mask"; const char* tag = "test_mask";
// Select a gradient value within the threshold range below // Select a gradient value within the threshold range below
float gradientValue = 0.5f; 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) // 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 // 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( TestGradientSurfaceDataComponent(
gradientValue, // constant gradient value gradientValue, // constant gradient value

@ -84,7 +84,7 @@ namespace UnitTest
AZStd::unique_ptr<MockSurfaceDataSystem> GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox) AZStd::unique_ptr<MockSurfaceDataSystem> GradientSignalBaseFixture::CreateMockSurfaceDataSystem(const AZ::Aabb& spawnerBox)
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
AZStd::unique_ptr<MockSurfaceDataSystem> mockSurfaceDataSystem = AZStd::make_unique<MockSurfaceDataSystem>(); AZStd::unique_ptr<MockSurfaceDataSystem> mockSurfaceDataSystem = AZStd::make_unique<MockSurfaceDataSystem>();
// Give the mock surface data a bunch of fake point values to return. // Give the mock surface data a bunch of fake point values to return.
@ -101,7 +101,8 @@ namespace UnitTest
// Create an arbitrary normal value. // Create an arbitrary normal value.
point.m_normal = point.m_position.GetNormalized(); point.m_normal = point.m_position.GetNormalized();
// Create an arbitrary surface value. // 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 } }; mockSurfaceDataSystem->m_GetSurfacePoints[AZStd::make_pair(x, y)] = { { point } };
} }

@ -37,7 +37,7 @@ ly_add_target(
PUBLIC PUBLIC
Include Include
BUILD_DEPENDENCIES BUILD_DEPENDENCIES
PRIVATE PUBLIC
Gem::SurfaceData.Static Gem::SurfaceData.Static
Gem::LmbrCentral Gem::LmbrCentral
RUNTIME_DEPENDENCIES RUNTIME_DEPENDENCIES

@ -9,31 +9,215 @@
#pragma once #pragma once
#include <AzCore/Math/Aabb.h> #include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector3.h> #include <AzCore/Math/Vector3.h>
#include <AzCore/Memory/SystemAllocator.h> #include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_set.h> #include <AzCore/std/containers/unordered_set.h>
#include <AzFramework/SurfaceData/SurfaceData.h>
#include <SurfaceData/SurfaceTag.h> #include <SurfaceData/SurfaceTag.h>
namespace SurfaceData namespace SurfaceData
{ {
//map of id or crc to contribution factor //map of id or crc to contribution factor
using SurfaceTagWeightMap = AZStd::unordered_map<AZ::Crc32, float>;
using SurfaceTagNameSet = AZStd::unordered_set<AZStd::string>; using SurfaceTagNameSet = AZStd::unordered_set<AZStd::string>;
using SurfaceTagVector = AZStd::vector<SurfaceTag>; using SurfaceTagVector = AZStd::vector<SurfaceTag>;
struct SurfacePoint final //! SurfaceTagWeights stores a collection of surface tags and weights.
class SurfaceTagWeights
{ {
AZ_CLASS_ALLOCATOR(SurfacePoint, AZ::SystemAllocator, 0); public:
AZ_TYPE_INFO(SurfacePoint, "{0DC7E720-68D6-47D4-BB6D-B89EF23C5A5C}"); SurfaceTagWeights() = default;
AZ::EntityId m_entityId; //! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList.
AZ::Vector3 m_position; //! @param weights - The list of weights to assign to the new instance.
AZ::Vector3 m_normal; SurfaceTagWeights(const AzFramework::SurfaceData::SurfaceTagWeightList& weights)
SurfaceTagWeightMap m_masks; {
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<bool(AZ::Crc32 tag, float weight)> 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<AZ::Crc32, float> 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<const AzFramework::SurfaceData::SurfacePoint> 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<void(const AZ::Vector3& position, SurfaceTagWeights& surfaceWeights)> 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<AZ::EntityId> m_surfaceCreatorIdList;
AZStd::vector<AZ::Vector3> m_surfacePositionList;
AZStd::vector<AZ::Vector3> m_surfaceNormalList;
AZStd::vector<SurfaceTagWeights> m_surfaceWeightsList;
AZ::Aabb m_pointBounds = AZ::Aabb::CreateNull();
}; };
using SurfacePointList = AZStd::vector<SurfacePoint>;
using SurfacePointLists = AZStd::vector<SurfacePointList>; using SurfacePointLists = AZStd::vector<SurfacePointList>;
struct SurfaceDataRegistryEntry struct SurfaceDataRegistryEntry

@ -88,48 +88,6 @@ namespace SurfaceData
const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd, const AZ::Vector3& rayStart, const AZ::Vector3& rayEnd,
AZ::Vector3& outPosition, AZ::Vector3& outNormal); 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<typename Container, typename Element>
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<typename SourceContainer> template<typename SourceContainer>
AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag) AZ_INLINE bool HasMatchingTag(const SourceContainer& sourceTags, const AZ::Crc32& sampleTag)
{ {
@ -137,7 +95,7 @@ namespace SurfaceData
} }
template<typename SourceContainer, typename SampleContainer> template<typename SourceContainer, typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags) AZ_INLINE bool HasAnyMatchingTags(const SourceContainer& sourceTags, const SampleContainer& sampleTags)
{ {
for (const auto& sampleTag : sampleTags) for (const auto& sampleTag : sampleTags)
{ {
@ -150,54 +108,7 @@ namespace SurfaceData
return false; return false;
} }
AZ_INLINE bool HasMatchingTag(const SurfaceTagWeightMap& sourceTags, const AZ::Crc32& sampleTag) AZ_INLINE bool HasValidTags(const SurfaceTagVector& sourceTags)
{
return sourceTags.find(sampleTag) != sourceTags.end();
}
template<typename SampleContainer>
AZ_INLINE bool HasMatchingTags(const SurfaceTagWeightMap& 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, float valueMin, float valueMax)
{
auto maskItr = sourceTags.find(sampleTag);
return maskItr != sourceTags.end() && valueMin <= maskItr->second && valueMax >= maskItr->second;
}
template<typename SampleContainer>
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<typename SourceContainer>
AZ_INLINE void RemoveUnassignedTags(const SourceContainer& sourceTags)
{
sourceTags.erase(AZStd::remove(sourceTags.begin(), sourceTags.end(), Constants::s_unassignedTagCrc), sourceTags.end());
}
template<typename SourceContainer>
AZ_INLINE bool HasValidTags(const SourceContainer& sourceTags)
{ {
for (const auto& sourceTag : sourceTags) for (const auto& sourceTag : sourceTags)
{ {
@ -209,18 +120,6 @@ namespace SurfaceData
return false; 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. // 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) AZ_INLINE bool AabbOverlaps2D(const AZ::Aabb& box1, const AZ::Aabb& box2)
{ {

@ -132,7 +132,7 @@ namespace SurfaceData
Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId()); Physics::ColliderComponentEventBus::Handler::BusConnect(GetEntityId());
// Update the cached collider data and bounds, then register the surface data provider / modifier // 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(); UpdateColliderData();
} }
@ -237,12 +237,7 @@ namespace SurfaceData
if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal)) if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal))
{ {
SurfacePoint point; surfacePointList.AddSurfacePoint(GetEntityId(), hitPosition, hitNormal, m_newPointWeights);
point.m_entityId = GetEntityId();
point.m_position = hitPosition;
point.m_normal = hitNormal;
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }
@ -252,20 +247,22 @@ namespace SurfaceData
if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty()) if (m_colliderBounds.IsValid() && !m_configuration.m_modifierTags.empty())
{ {
const AZ::EntityId entityId = GetEntityId(); surfacePointList.ModifySurfaceWeights(
for (auto& point : surfacePointList) GetEntityId(),
{ [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (point.m_entityId != entityId && m_colliderBounds.Contains(point.m_position))
{ {
AZ::Vector3 hitPosition; if (m_colliderBounds.Contains(position))
AZ::Vector3 hitNormal;
constexpr bool queryPointOnly = true;
if (DoRayTrace(point.m_position, queryPointOnly, hitPosition, hitNormal))
{ {
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);
}
} }
} });
}
} }
} }

@ -101,6 +101,6 @@ namespace SurfaceData
AZStd::atomic_bool m_refresh{ false }; AZStd::atomic_bool m_refresh{ false };
mutable AZStd::shared_mutex m_cacheMutex; mutable AZStd::shared_mutex m_cacheMutex;
AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_colliderBounds = AZ::Aabb::CreateNull();
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -89,7 +89,7 @@ namespace SurfaceData
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId()); LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
// Update the cached shape data and bounds, then register the surface data provider / modifier // 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(); UpdateShapeData();
} }
@ -155,12 +155,8 @@ namespace SurfaceData
LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance); LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance);
if (hitShape) if (hitShape)
{ {
SurfacePoint point; AZ::Vector3 position = rayOrigin + intersectionDistance * rayDirection;
point.m_entityId = GetEntityId(); surfacePointList.AddSurfacePoint(GetEntityId(), position, AZ::Vector3::CreateAxisZ(), m_newPointWeights);
point.m_position = rayOrigin + intersectionDistance * rayDirection;
point.m_normal = AZ::Vector3::CreateAxisZ();
point.m_masks = m_newPointWeights;
surfacePointList.push_back(AZStd::move(point));
} }
} }
} }
@ -173,20 +169,19 @@ namespace SurfaceData
{ {
const AZ::EntityId entityId = GetEntityId(); const AZ::EntityId entityId = GetEntityId();
LmbrCentral::ShapeComponentRequestsBus::Event( LmbrCentral::ShapeComponentRequestsBus::Event(
GetEntityId(), entityId,
[entityId, this, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape) [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 the point is inside our shape, add all our modifier tags with a weight of 1.0f.
if (inside) weights.AddSurfaceWeightsIfGreater(m_configuration.m_modifierTags, 1.0f);
{
AddMaxValueForMasks(point.m_masks, m_configuration.m_modifierTags, 1.0f);
}
} }
} });
}); });
} }
} }

@ -97,6 +97,6 @@ namespace SurfaceData
AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull(); AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull();
bool m_shapeBoundsIsValid = false; bool m_shapeBoundsIsValid = false;
static const float s_rayAABBHeightPadding; static const float s_rayAABBHeightPadding;
SurfaceTagWeightMap m_newPointWeights; SurfaceTagWeights m_newPointWeights;
}; };
} }

@ -45,14 +45,10 @@ namespace SurfaceData
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context)) if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->Class<SurfacePoint>() behaviorContext->Class<SurfacePointList>()
->Constructor() ->Constructor()
->Attribute(AZ::Script::Attributes::Category, "Vegetation") ->Attribute(AZ::Script::Attributes::Category, "Vegetation")
->Attribute(AZ::Script::Attributes::Module, "surface_data") ->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<SurfaceDataSystemComponent>() behaviorContext->Class<SurfaceDataSystemComponent>()
@ -182,11 +178,12 @@ namespace SurfaceData
void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const void SurfaceDataSystemComponent::GetSurfacePoints(const AZ::Vector3& inPosition, const SurfaceTagVector& desiredTags, SurfacePointList& surfacePointList) const
{ {
const bool useTagFilters = HasValidTags(desiredTags); const bool useTagFilters = HasValidTags(desiredTags);
const bool hasModifierTags = useTagFilters && HasMatchingTags(desiredTags, m_registeredModifierTags); const bool hasModifierTags = useTagFilters && HasAnyMatchingTags(desiredTags, m_registeredModifierTags);
AZStd::shared_lock<decltype(m_registrationMutex)> registrationLock(m_registrationMutex); AZStd::shared_lock<decltype(m_registrationMutex)> registrationLock(m_registrationMutex);
surfacePointList.clear(); surfacePointList.Clear();
surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size());
//gather all intersecting points //gather all intersecting points
for (const auto& entryPair : m_registeredSurfaceDataProviders) for (const auto& entryPair : m_registeredSurfaceDataProviders)
@ -195,14 +192,14 @@ namespace SurfaceData
const SurfaceDataRegistryEntry& entry = entryPair.second; const SurfaceDataRegistryEntry& entry = entryPair.second;
if (!entry.m_bounds.IsValid() || AabbContains2D(entry.m_bounds, inPosition)) 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); SurfaceDataProviderRequestBus::Event(entryAddress, &SurfaceDataProviderRequestBus::Events::GetSurfacePoints, inPosition, surfacePointList);
} }
} }
} }
if (!surfacePointList.empty()) if (!surfacePointList.IsEmpty())
{ {
//modify or annotate reported points //modify or annotate reported points
for (const auto& entryPair : m_registeredSurfaceDataModifiers) 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. // doesn't add a desired tag, and a surface modifier has the *potential* to add it, but then doesn't.
if (useTagFilters) if (useTagFilters)
{ {
FilterPoints(surfacePointList, desiredTags); surfacePointList.FilterPoints(desiredTags);
} }
CombineAndSortNeighboringPoints(surfacePointList);
} }
} }
@ -260,8 +255,13 @@ namespace SurfaceData
surfacePointLists.clear(); surfacePointLists.clear();
surfacePointLists.resize(totalQueryPositions); surfacePointLists.resize(totalQueryPositions);
for (auto& surfacePointList : surfacePointLists)
{
surfacePointList.ReserveSpace(m_registeredSurfaceDataProviders.size());
}
const bool useTagFilters = HasValidTags(desiredTags); 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 // 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 // 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(); 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++) for (size_t index = 0; index < totalQueryPositions; index++)
{ {
@ -299,7 +299,7 @@ namespace SurfaceData
{ {
const auto& inPosition = inPositions[index]; const auto& inPosition = inPositions[index];
SurfacePointList& surfacePointList = surfacePointLists[index]; SurfacePointList& surfacePointList = surfacePointLists[index];
if (!surfacePointList.empty()) if (!surfacePointList.IsEmpty())
{ {
if (hasInfiniteBounds || AabbContains2D(entry.m_bounds, inPosition)) 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. // 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 // 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. // 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) SurfaceDataRegistryHandle SurfaceDataSystemComponent::RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry)

@ -58,9 +58,6 @@ namespace SurfaceData
void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override; void RefreshSurfaceData(const AZ::Aabb& dirtyArea) override;
private: private:
void FilterPoints(SurfacePointList& sourcePointList, const SurfaceTagVector& desiredTags) const;
void CombineAndSortNeighboringPoints(SurfacePointList& sourcePointList) const;
SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry); SurfaceDataRegistryHandle RegisterSurfaceDataProviderInternal(const SurfaceDataRegistryEntry& entry);
SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle); SurfaceDataRegistryEntry UnregisterSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle);
bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds); bool UpdateSurfaceDataProviderInternal(const SurfaceDataRegistryHandle& handle, const SurfaceDataRegistryEntry& entry, AZ::Aabb& oldBounds);

@ -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 <SurfaceData/Utility/SurfaceDataUtility.h>
#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
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<bool(AZ::Crc32 tag, float weight)> 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<const AzFramework::SurfaceData::SurfacePoint> 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<bool(const AZ::Vector3&, const AZ::Vector3&, const SurfaceData::SurfaceTagWeights&)>
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<void(const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& surfaceWeights)> 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);
}
}
}

@ -190,7 +190,7 @@ namespace UnitTest
for (float x = 0.0f; x < worldSize; x += 1.0f) for (float x = 0.0f; x < worldSize; x += 1.0f)
{ {
AZ::Vector3 queryPosition(x, y, 0.0f); AZ::Vector3 queryPosition(x, y, 0.0f);
points.clear(); points.Clear();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast( SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points); &SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, filterTags, points);

@ -26,7 +26,8 @@ namespace UnitTest
: public AzPhysics::SimulatedBodyComponentRequestsBus::Handler : public AzPhysics::SimulatedBodyComponentRequestsBus::Handler
{ {
public: 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); AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(id);
@ -77,51 +78,36 @@ namespace UnitTest
{ {
protected: protected:
// Create a new SurfacePoint with the given fields. // Create a new SurfacePoint with the given fields.
SurfaceData::SurfacePoint CreateSurfacePoint(AZ::EntityId id, AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags) AzFramework::SurfaceData::SurfacePoint CreateSurfacePoint(
AZ::Vector3 position, AZ::Vector3 normal, AZStd::vector<AZStd::pair<AZStd::string, float>> tags)
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_entityId = id;
point.m_position = position; point.m_position = position;
point.m_normal = normal; point.m_normal = normal;
for (auto& tag : tags) 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; return point;
} }
// Compare two surface points. // 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) return ((lhsPosition == rhs.m_position)
|| (lhs.m_position != rhs.m_position) && (lhsNormal == rhs.m_normal)
|| (lhs.m_normal != rhs.m_normal) && (lhsMasks.SurfaceWeightsAreEqual(rhs.m_surfaceTags)));
|| (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;
} }
// Common test function for testing the "Provider" functionality of the component. // 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 // Given a set of tags and an expected output, check to see if the component provides the
// expected output point. // expected output point.
void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> providerTags, bool pointOnProvider, void TestSurfaceDataColliderProvider(AZStd::vector<AZStd::string> 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. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -135,8 +121,6 @@ namespace UnitTest
// Create the test entity with the SurfaceDataCollider component and the required physics collider dependency // Create the test entity with the SurfaceDataCollider component and the required physics collider dependency
auto entity = CreateEntity(); auto entity = CreateEntity();
// Initialize our Entity ID to the one passed in on the expectedOutput
entity->SetId(expectedOutput.m_entityId);
// Create the components // Create the components
CreateComponent<MockPhysicsColliderComponent>(entity.get()); CreateComponent<MockPhysicsColliderComponent>(entity.get());
CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config); CreateComponent<SurfaceData::SurfaceDataColliderComponent>(entity.get(), config);
@ -155,17 +139,25 @@ namespace UnitTest
queryPoint, pointList); queryPoint, pointList);
if (pointOnProvider) if (pointOnProvider)
{ {
ASSERT_TRUE(pointList.size() == 1); ASSERT_EQ(pointList.GetSize(), 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); 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 else
{ {
EXPECT_TRUE(pointList.empty()); EXPECT_TRUE(pointList.IsEmpty());
} }
} }
void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> modifierTags, void TestSurfaceDataColliderModifier(AZStd::vector<AZStd::string> 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. // This lets our component register with surfaceData successfully.
MockSurfaceDataSystem mockSurfaceDataSystem; MockSurfaceDataSystem mockSurfaceDataSystem;
@ -191,11 +183,18 @@ namespace UnitTest
EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle); EXPECT_TRUE(modifierHandle != SurfaceData::InvalidSurfaceDataRegistryHandle);
// Call ModifySurfacePoints and verify the results // Call ModifySurfacePoints and verify the results
SurfaceData::SurfacePointList pointList; // Add the surface point with a different entity ID than the entity doing the modification, so that the point doesn't get
pointList.emplace_back(input); // filtered out.
SurfaceData::SurfacePointList pointList = { input };
SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList); SurfaceData::SurfaceDataModifierRequestBus::Event(modifierHandle, &SurfaceData::SurfaceDataModifierRequestBus::Events::ModifySurfacePoints, pointList);
ASSERT_TRUE(pointList.size() == 1); ASSERT_EQ(pointList.GetSize(), 1);
EXPECT_TRUE(SurfacePointsAreEqual(pointList[0], expectedOutput)); 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. // 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. // We'll use this to initialize the mock physics, so the output of the query should match.
const char* tag = "test_mask"; 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<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(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 // 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. // Set the expected output to an arbitrary entity ID, position, and normal.
// We'll use this to initialize the mock physics. // We'll use this to initialize the mock physics.
const char* tag = "test_mask"; 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<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
// Query from the same XY, but one unit higher on Z. However, we're also telling our test to provide // 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. // We'll use this to initialize the mock physics.
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; const char* tag2 = "test_mask2";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(AZ::EntityId(0x12345678), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(), AzFramework::SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(
{ AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZ::Vector3(1.0f), AZ::Vector3::CreateAxisZ(),
AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(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 // 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. // 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. // Verify that for a point inside the collider, the output point contains the correct tag and value.
// Set arbitrary input data // 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 // Output should match the input, but with an added tag / value
const char* tag = "test_mask"; const char* tag = "test_mask";
SurfaceData::SurfacePoint expectedOutput = CreateSurfacePoint(input.m_entityId, input.m_position, input.m_normal, AzFramework::SurfaceData::SurfacePoint expectedOutput =
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) }); CreateSurfacePoint(input.m_position, input.m_normal,
{ AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, pointInCollider, expectedOutput); 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. // Verify that for a point outside the collider, the output point contains no tags / values.
// Set arbitrary input data // 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. // Output should match the input - no extra tags / values should be added.
const char* tag = "test_mask"; 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; constexpr bool pointInCollider = true;
TestSurfaceDataColliderModifier({ tag }, input, !pointInCollider, expectedOutput); 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. // Verify that if the component has multiple tags, all of them get put on the output with the same value.
// Set arbitrary input data // 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 // Output should match the input, but with two added tags
const char* tag1 = "test_mask1"; const char* tag1 = "test_mask1";
const char* tag2 = "test_mask2"; 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<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag1, 1.0f), AZStd::make_pair<AZStd::string, float>(tag2, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
@ -328,11 +331,13 @@ namespace UnitTest
// Set arbitrary input data // Set arbitrary input data
const char* preservedTag = "preserved_tag"; 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<AZStd::string, float>(preservedTag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f) });
// Output should match the input, but with two added tags // Output should match the input, but with two added tags
const char* modifierTag = "modifier_tag"; 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<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(preservedTag, 1.0f), AZStd::make_pair<AZStd::string, float>(modifierTag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;
@ -349,10 +354,12 @@ namespace UnitTest
float inputValue = 0.25f; float inputValue = 0.25f;
// Set arbitrary input data // 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<AZStd::string, float>(tag, inputValue) }); { AZStd::make_pair<AZStd::string, float>(tag, inputValue) });
// Output should match the input, except that the value on the tag gets the higher modifier value // 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<AZStd::string, float>(tag, 1.0f) }); { AZStd::make_pair<AZStd::string, float>(tag, 1.0f) });
constexpr bool pointInCollider = true; constexpr bool pointInCollider = true;

@ -54,7 +54,7 @@ class MockSurfaceProvider
} }
private: private:
AZStd::unordered_map<AZStd::pair<float, float>, SurfaceData::SurfacePointList> m_GetSurfacePoints; AZStd::unordered_map<AZStd::pair<float, float>, AZStd::vector<AzFramework::SurfaceData::SurfacePoint>> m_GetSurfacePoints;
SurfaceData::SurfaceTagVector m_tags; SurfaceData::SurfaceTagVector m_tags;
ProviderType m_providerType; ProviderType m_providerType;
AZ::EntityId m_id; AZ::EntityId m_id;
@ -71,15 +71,16 @@ class MockSurfaceProvider
{ {
for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX()) for (float x = start.GetX(); x < end.GetX(); x += stepSize.GetX())
{ {
SurfaceData::SurfacePointList points; AZStd::vector<AzFramework::SurfaceData::SurfacePoint> points;
for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ()) for (float z = start.GetZ(); z < end.GetZ(); z += stepSize.GetZ())
{ {
SurfaceData::SurfacePoint point; AzFramework::SurfaceData::SurfacePoint point;
point.m_entityId = m_id;
point.m_position = AZ::Vector3(x, y, z); point.m_position = AZ::Vector3(x, y, z);
point.m_normal = AZ::Vector3::CreateAxisZ(); 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); points.push_back(point);
} }
m_GetSurfacePoints[AZStd::pair<float, float>(x, y)] = points; m_GetSurfacePoints[AZStd::pair<float, float>(x, y)] = points;
@ -150,7 +151,8 @@ class MockSurfaceProvider
{ {
for (auto& point : surfacePoints->second) 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 // SurfaceDataModifierRequestBus
void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override void ModifySurfacePoints(SurfaceData::SurfacePointList& surfacePointList) const override
{ {
for (auto& point : surfacePointList) surfacePointList.ModifySurfaceWeights(
{ AZ::EntityId(),
auto surfacePoints = m_GetSurfacePoints.find(AZStd::make_pair(point.m_position.GetX(), point.m_position.GetY())); [this](const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& weights)
if (surfacePoints != m_GetSurfacePoints.end())
{ {
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; SurfaceData::SurfaceDataRegistryHandle m_providerHandle = SurfaceData::InvalidSurfaceDataRegistryHandle;
@ -205,42 +208,49 @@ public:
} }
void CompareSurfacePointListWithGetSurfacePoints( void CompareSurfacePointListWithGetSurfacePoints(
const AZStd::vector<AZ::Vector3>& queryPositions, SurfaceData::SurfacePointLists surfacePointLists, const AZStd::vector<AZ::Vector3>& queryPositions, SurfaceData::SurfacePointLists& surfacePointLists,
const SurfaceData::SurfaceTagVector& testTags) const SurfaceData::SurfaceTagVector& testTags)
{ {
SurfaceData::SurfacePointLists singleQueryPointLists; AZStd::vector<AzFramework::SurfaceData::SurfacePoint> singleQueryResults;
for (auto& queryPosition : queryPositions) for (auto& queryPosition : queryPositions)
{ {
SurfaceData::SurfacePointList tempSingleQueryPointList; SurfaceData::SurfacePointList tempSingleQueryPointList;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast( SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, queryPosition, testTags, tempSingleQueryPointList); &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. // Verify that each point in each list is equal.
ASSERT_EQ(singleQueryPointLists.size(), surfacePointLists.size()); AzFramework::SurfaceData::SurfacePoint* singleQueryPoint = singleQueryResults.begin();
for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++) for (size_t listIndex = 0; listIndex < surfacePointLists.size(); listIndex++)
{ {
auto& surfacePointList = surfacePointLists[listIndex]; auto& surfacePointList = surfacePointLists[listIndex];
auto& singleQueryPointList = singleQueryPointLists[listIndex]; surfacePointList.EnumeratePoints(
[&singleQueryPoint, singleQueryResults](
ASSERT_EQ(singleQueryPointList.size(), surfacePointList.size()); const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
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)
{ {
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. // We *could* check every mask as well for completeness, but that seems like overkill.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
EXPECT_EQ(pointList[0].m_position.GetZ(), 4.0f); float expectedZ = 4.0f;
EXPECT_EQ(pointList[1].m_position.GetZ(), 0.0f); pointList.EnumeratePoints(
for (auto& point : pointList) [providerTags,
{ &expectedZ](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), providerTags.size()); 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. // any of the masks from our mock surface provider.
for (auto& queryPosition : availablePointsPerPosition) 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. // our surface provider.
for (auto& pointList : availablePointsPerPosition) 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. // and each point should have both the "test_surface1" and "test_surface2" tag.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
float expectedZ = 4.0f; float expectedZ = 4.0f;
for (auto& point : pointList) pointList.EnumeratePoints(
{ [&expectedZ](const AZ::Vector3& position,
EXPECT_EQ(point.m_position.GetZ(), expectedZ); [[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
EXPECT_EQ(point.m_masks.size(), 2); {
expectedZ = (expectedZ == 4.0f) ? 0.0f : 4.0f; 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. // should have both surface tags on them.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 2); EXPECT_EQ(pointList.GetSize(), 2);
float expectedZ = 4.0005f; float expectedZ = 4.0f;
for (auto& point : pointList) pointList.EnumeratePoints(
{ [&expectedZ](
EXPECT_EQ(point.m_position.GetZ(), expectedZ); const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), 2); const SurfaceData::SurfaceTagWeights& masks) -> bool
expectedZ = (expectedZ == 4.0005f) ? 0.0005f : 4.0005f; {
} // 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. // because the points are far enough apart that they won't merge.
for (auto& pointList : availablePointsPerPosition) for (auto& pointList : availablePointsPerPosition)
{ {
EXPECT_EQ(pointList.size(), 4); EXPECT_EQ(pointList.GetSize(), 4);
for (auto& point : pointList) pointList.EnumeratePoints(
{ []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
EXPECT_EQ(point.m_masks.size(), 1); const SurfaceData::SurfaceTagWeights& masks) -> bool
} {
EXPECT_EQ(masks.GetSize(), 1);
return true;
});
} }
} }

@ -19,6 +19,7 @@ set(FILES
Include/SurfaceData/Utility/SurfaceDataUtility.h Include/SurfaceData/Utility/SurfaceDataUtility.h
Source/SurfaceDataSystemComponent.cpp Source/SurfaceDataSystemComponent.cpp
Source/SurfaceDataSystemComponent.h Source/SurfaceDataSystemComponent.h
Source/SurfaceDataTypes.cpp
Source/SurfaceTag.cpp Source/SurfaceTag.cpp
Source/Components/SurfaceDataColliderComponent.cpp Source/Components/SurfaceDataColliderComponent.cpp
Source/Components/SurfaceDataColliderComponent.h Source/Components/SurfaceDataColliderComponent.h

@ -159,25 +159,13 @@ namespace Terrain
const bool isHole = !isTerrainValidAtPoint; const bool isHole = !isTerrainValidAtPoint;
SurfaceData::SurfacePoint point; SurfaceData::SurfaceTagWeights weights(terrainSurfacePoint.m_surfaceTags);
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;
}
// Always add a "terrain" or "terrainHole" tag. // Always add a "terrain" or "terrainHole" tag.
const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc; 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 AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const

@ -56,12 +56,12 @@ namespace Vegetation
ClaimHandle m_handle; ClaimHandle m_handle;
AZ::Vector3 m_position; AZ::Vector3 m_position;
AZ::Vector3 m_normal; AZ::Vector3 m_normal;
SurfaceData::SurfaceTagWeightMap m_masks; SurfaceData::SurfaceTagWeights m_masks;
}; };
struct ClaimContext struct ClaimContext
{ {
SurfaceData::SurfaceTagWeightMap m_masks; SurfaceData::SurfaceTagWeights m_masks;
AZStd::vector<ClaimPoint> m_availablePoints; AZStd::vector<ClaimPoint> m_availablePoints;
AZStd::function<bool(const ClaimPoint&, const InstanceData&)> m_existedCallback; AZStd::function<bool(const ClaimPoint&, const InstanceData&)> m_existedCallback;
AZStd::function<void(const ClaimPoint&, const InstanceData&)> m_createdCallback; AZStd::function<void(const ClaimPoint&, const InstanceData&)> m_createdCallback;

@ -34,7 +34,7 @@ namespace Vegetation
AZ::Quaternion m_rotation = AZ::Quaternion::CreateIdentity(); AZ::Quaternion m_rotation = AZ::Quaternion::CreateIdentity();
AZ::Quaternion m_alignment = AZ::Quaternion::CreateIdentity(); AZ::Quaternion m_alignment = AZ::Quaternion::CreateIdentity();
float m_scale = 1.0f; 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; DescriptorPtr m_descriptorPtr;
// Determine if two different sets of instance data are similar enough to be considered the same when placing // Determine if two different sets of instance data are similar enough to be considered the same when placing

@ -1091,7 +1091,7 @@ namespace Vegetation
const float vegStep = sectorSizeInMeters / static_cast<float>(sectorDensity); const float vegStep = sectorSizeInMeters / static_cast<float>(sectorDensity);
//build a free list of all points in the sector for areas to consume //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.clear();
sectorInfo.m_baseContext.m_availablePoints.reserve(sectorDensity * sectorDensity); sectorInfo.m_baseContext.m_availablePoints.reserve(sectorDensity * sectorDensity);
@ -1127,16 +1127,19 @@ namespace Vegetation
uint claimIndex = 0; uint claimIndex = 0;
for (auto& availablePoints : availablePointsPerPosition) for (auto& availablePoints : availablePointsPerPosition)
{ {
for (auto& surfacePoint : availablePoints) availablePoints.EnumeratePoints(
{ [this, &sectorInfo,
sectorInfo.m_baseContext.m_availablePoints.push_back(); &claimIndex](const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back(); {
claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex); sectorInfo.m_baseContext.m_availablePoints.push_back();
claimPoint.m_position = surfacePoint.m_position; ClaimPoint& claimPoint = sectorInfo.m_baseContext.m_availablePoints.back();
claimPoint.m_normal = surfacePoint.m_normal; claimPoint.m_handle = CreateClaimHandle(sectorInfo, ++claimIndex);
claimPoint.m_masks = surfacePoint.m_masks; claimPoint.m_position = position;
SurfaceData::AddMaxValueForMasks(sectorInfo.m_baseContext.m_masks, surfacePoint.m_masks); claimPoint.m_normal = normal;
} claimPoint.m_masks = masks;
sectorInfo.m_baseContext.m_masks.AddSurfaceWeightsIfGreater(masks);
return true;
});
} }
} }

@ -306,31 +306,40 @@ namespace Vegetation
m_surfaceTagsToSnapToCombined.clear(); m_surfaceTagsToSnapToCombined.clear();
m_surfaceTagsToSnapToCombined.reserve( m_surfaceTagsToSnapToCombined.reserve(
m_configuration.m_surfaceTagsToSnapTo.size() + m_configuration.m_surfaceTagsToSnapTo.size() +
instanceData.m_masks.size()); instanceData.m_masks.GetSize());
m_surfaceTagsToSnapToCombined.insert(m_surfaceTagsToSnapToCombined.end(), m_surfaceTagsToSnapToCombined.insert(m_surfaceTagsToSnapToCombined.end(),
m_configuration.m_surfaceTagsToSnapTo.begin(), m_configuration.m_surfaceTagsToSnapTo.end()); m_configuration.m_surfaceTagsToSnapTo.begin(), m_configuration.m_surfaceTagsToSnapTo.end());
for (const auto& maskPair : instanceData.m_masks) instanceData.m_masks.EnumerateWeights(
{ [this](AZ::Crc32 surfaceType, [[maybe_unused]] float weight)
m_surfaceTagsToSnapToCombined.push_back(maskPair.first); {
} m_surfaceTagsToSnapToCombined.push_back(surfaceType);
return true;
});
//get the intersection data at the new position //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); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, m_surfaceTagsToSnapToCombined, m_points);
if (!m_points.empty())
{ // Get the point with the closest distance from the new position in case there are multiple intersections at different or
//sort the intersection data by distance from the new position in case there are multiple intersections at different or unrelated heights // unrelated heights
AZStd::sort(m_points.begin(), m_points.end(), [&instanceData](const SurfaceData::SurfacePoint& a, const SurfaceData::SurfacePoint& b) float closestPointDistanceSq = AZStd::numeric_limits<float>::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()); instanceData.m_position.SetZ(instanceData.m_position.GetZ() + delta.GetZ());

@ -416,9 +416,9 @@ namespace Vegetation
AZ_PROFILE_FUNCTION(Entity); AZ_PROFILE_FUNCTION(Entity);
//reject entire spawner if there are inclusion tags to consider that don't exist in the context //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::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())); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::MarkAreaRejectedByMask, GetEntityId()));
return; return;

@ -210,26 +210,38 @@ namespace Vegetation
float lowerZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_lowerDistanceInMeters : m_configuration.m_lowerDistance; 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; float upperZDistanceRange = useOverrides ? instanceData.m_descriptorPtr->m_surfaceTagDistance.m_upperDistanceInMeters : m_configuration.m_upperDistance;
bool passesFilter = false;
if (!surfaceTagsToCompare.empty()) if (!surfaceTagsToCompare.empty())
{ {
m_points.clear(); m_points.Clear();
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, surfaceTagsToCompare, m_points); SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, instanceData.m_position, surfaceTagsToCompare, m_points);
float instanceZ = instanceData.m_position.GetZ(); float instanceZ = instanceData.m_position.GetZ();
for (auto& point : m_points) m_points.EnumeratePoints(
{ [instanceZ, lowerZDistanceRange, upperZDistanceRange, &passesFilter](
float pointZ = point.m_position.GetZ(); const AZ::Vector3& position,
float zDistance = instanceZ - pointZ; [[maybe_unused]] const AZ::Vector3& normal, [[maybe_unused]] const SurfaceData::SurfaceTagWeights& masks) -> bool
if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange)
{ {
float pointZ = position.GetZ();
float zDistance = instanceZ - pointZ;
if (lowerZDistanceRange <= zDistance && zDistance <= upperZDistanceRange)
{
passesFilter = true;
return false;
}
return true; return true;
} });
}
} }
// if we get here instance is marked filtered if (!passesFilter)
VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceDepthMaskFilter"))); {
return false; // 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 FilterStage SurfaceMaskDepthFilterComponent::GetFilterStage() const

@ -281,14 +281,15 @@ namespace Vegetation
const float exclusiveWeightMax = AZ::GetMax(m_configuration.m_exclusiveWeightMin, m_configuration.m_exclusiveWeightMax); const float exclusiveWeightMax = AZ::GetMax(m_configuration.m_exclusiveWeightMin, m_configuration.m_exclusiveWeightMax);
if (useCompTags && 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"))); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter")));
return false; return false;
} }
if (useDescTags && 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"))); VEG_PROFILE_METHOD(DebugNotificationBus::TryQueueBroadcast(&DebugNotificationBus::Events::FilterInstance, instanceData.m_id, AZStd::string_view("SurfaceMaskFilter")));
return false; return false;
@ -299,13 +300,14 @@ namespace Vegetation
const float inclusiveWeightMax = AZ::GetMax(m_configuration.m_inclusiveWeightMin, m_configuration.m_inclusiveWeightMax); const float inclusiveWeightMax = AZ::GetMax(m_configuration.m_inclusiveWeightMin, m_configuration.m_inclusiveWeightMax);
if (useCompTags && 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; return true;
} }
if (useDescTags && 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; return true;
} }

@ -862,7 +862,7 @@ void DebugComponent::PrepareNextReport()
SurfaceData::SurfacePointList points; SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints, pos, SurfaceData::SurfaceTagVector(), 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; return timing;
}, },
[](const SectorTracker& sectorTracker, SectorTiming& sectorTiming) [](const SectorTracker& sectorTracker, SectorTiming& sectorTiming)

@ -80,7 +80,7 @@ namespace UnitTest
}); });
Vegetation::InstanceData vegInstance; Vegetation::InstanceData vegInstance;
vegInstance.m_masks[maskValue] = 1.0f; vegInstance.m_masks.AddSurfaceTagWeight(maskValue, 1.0f);
// passes // passes
{ {
@ -119,8 +119,7 @@ namespace UnitTest
MockSurfaceHandler mockSurfaceHandler; MockSurfaceHandler mockSurfaceHandler;
mockSurfaceHandler.m_outPosition = AZ::Vector3::CreateZero(); mockSurfaceHandler.m_outPosition = AZ::Vector3::CreateZero();
mockSurfaceHandler.m_outNormal = AZ::Vector3::CreateAxisZ(); mockSurfaceHandler.m_outNormal = AZ::Vector3::CreateAxisZ();
mockSurfaceHandler.m_outMasks.clear(); mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(SurfaceData::Constants::s_unassignedTagCrc, 1.0f);
mockSurfaceHandler.m_outMasks[SurfaceData::Constants::s_unassignedTagCrc] = 1.0f;
// passes // passes
{ {

@ -71,7 +71,7 @@ namespace UnitTest
MockSurfaceHandler mockSurfaceHandler; MockSurfaceHandler mockSurfaceHandler;
mockSurfaceHandler.m_outPosition = AZ::Vector3(vegInstance.m_position.GetX(), vegInstance.m_position.GetY(), 6.0f); 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_outNormal = AZ::Vector3(0.0f, 0.0f, 1.0f);
mockSurfaceHandler.m_outMasks[crcMask] = 1.0f; mockSurfaceHandler.m_outMasks.AddSurfaceTagWeight(crcMask, 1.0f);
entity->Deactivate(); entity->Deactivate();
config.m_autoSnapToSurface = true; config.m_autoSnapToSurface = true;

@ -329,15 +329,11 @@ namespace UnitTest
AZ::Vector3 m_outPosition = {}; AZ::Vector3 m_outPosition = {};
AZ::Vector3 m_outNormal = {}; 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 void GetSurfacePoints([[maybe_unused]] const AZ::Vector3& inPosition, [[maybe_unused]] const SurfaceData::SurfaceTagVector& masks, SurfaceData::SurfacePointList& surfacePointList) const override
{ {
++m_count; ++m_count;
SurfaceData::SurfacePoint outPoint; surfacePointList.AddSurfacePoint(AZ::EntityId(), m_outPosition, m_outNormal, m_outMasks);
outPoint.m_position = m_outPosition;
outPoint.m_normal = m_outNormal;
SurfaceData::AddMaxValueForMasks(outPoint.m_masks, m_outMasks);
surfacePointList.push_back(outPoint);
} }
void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags, void GetSurfacePointsFromRegion([[maybe_unused]] const AZ::Aabb& inRegion, [[maybe_unused]] const AZ::Vector2 stepSize, [[maybe_unused]] const SurfaceData::SurfaceTagVector& desiredTags,

Loading…
Cancel
Save