Optimize SurfaceData bulk queries (#7593)
* Add comparison operators to SurfaceTagWeight. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Changed AddSurfaceTagWeight to always combine weights. This simplifies the API a bit and defines the behavior if someone ever tries to add a duplicate tag. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added benchmarks for measuring the performance-critical APIs. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Changed SurfaceTagWeights to a fixed_vector. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add inPosition to AddSurfacePoint. This will be used to detect which input the surface point is associated with. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add inPositionIndex to the appropriate APIs. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switched Gradient Surface benchmarks to use actual surface components. The gradient unit tests and benchmarks were previously using a mock surface data system, which led to misleading benchmark results. Now, the actual SurfaceData system gets constructed, and the tests use a mock provider, but the benchmarks use actual shape providers for more realistic benchmarking. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed unit tests to have better query ranges. Half of each previous range was querying outside the surface provider's data. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * First attempt at removing SurfacePointLists. This currently runs significantly slower than the previous code but passes the unit tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Another attempt at optimization. This one runs faster than the previous, but still slow. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix the cmake dependency so that the gradient tests rebuild SurfaceData.dll when run. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switch SurfaceAltitudeGradient over to the new bulk API. Also, optimized the non-bulk API by having it reuse the SurfacePointList to avoid the repeated allocation / deallocation cost. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switched to using an indirect index so that all allocations are consecutive in our reserved buffer. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switched back to SurfaceTagWeight again. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added runtime dependency to LmbrCentral for unit tests. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Switched code over to use the full EnumeratePoints in most cases. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added knowledge of max surface point creation into the system. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add generic GetSurfacePointsFromList API implementation for surface providers. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed implementation to use the correct maximum number of input points based on the surface providers being queried. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix out-of-bounds references on empty lists. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fix memory allocation that caused benchmark runs to crash. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Starting to clean up the API. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Move SurfacePointList into separate files for easier maintainability. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed bug where too many points were filtered out due to using the position Z as a part of the AABB check. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Made FilterPoints an internal part of SurfacePointList so we can choose when and how to perform the filtering. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Final cleanup / comments. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added includes for non-unity builds. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Changed how unit tests initialize the mock lists to try and fix the linux errors. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed compile error. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>monroegm-disable-blank-issue-2
parent
d2cdb511d0
commit
8b82041361
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AzCore/Math/Aabb.h>
|
||||||
|
#include <AzCore/Math/Crc.h>
|
||||||
|
#include <AzCore/Math/Vector3.h>
|
||||||
|
#include <AzCore/Memory/SystemAllocator.h>
|
||||||
|
#include <AzCore/std/string/string.h>
|
||||||
|
#include <AzCore/std/containers/span.h>
|
||||||
|
#include <AzFramework/SurfaceData/SurfaceData.h>
|
||||||
|
#include <SurfaceData/SurfaceDataTypes.h>
|
||||||
|
#include <SurfaceData/SurfaceTag.h>
|
||||||
|
|
||||||
|
namespace SurfaceData
|
||||||
|
{
|
||||||
|
//! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights.
|
||||||
|
//! This class is specifically designed to be used in the following ways.
|
||||||
|
//!
|
||||||
|
//! List construction:
|
||||||
|
//! StartListConstruction() - This clears the structure, temporarily holds on to the list of input positions, and preallocates the data.
|
||||||
|
//! AddSurfacePoint() - Add surface points to the list. They're expected to get added in input position order.
|
||||||
|
//! ModifySurfaceWeights() - Modify the surface weights for the set of input points.
|
||||||
|
//! FilterPoints() - Remove any generated surface points that don't fit the filter criteria
|
||||||
|
//! EndListConstruction() - "Freeze" and compact the data.
|
||||||
|
//!
|
||||||
|
//! List usage:
|
||||||
|
//! Any of the query APIs can be used in any order after the list has finished being constructed.
|
||||||
|
//!
|
||||||
|
//! This class is specifically designed around the usage patterns described above to minimize the amount of allocations and data
|
||||||
|
//! shifting that needs to occur. There are some tricky bits that need to be accounted for:
|
||||||
|
//! * Tracking which input positions each output point belongs to.
|
||||||
|
//! * Support for merging similar surface points together, which causes us to keep them sorted for easier comparisons.
|
||||||
|
//! * Each surface provider will add points in input position order, but we call each provider separately, so the added points will
|
||||||
|
//! show up like (0, 1, 2, 3), (0, 1, 3), (0, 0, 1, 2, 3), etc. We don't want to call each surface provider per-point, because that
|
||||||
|
//! incurs a lot of avoidable overhead in each provider.
|
||||||
|
//! * Output points get optionally filtered out at the very end if they don't match any of the filter tags passed in.
|
||||||
|
//!
|
||||||
|
//! The solution is that we always add new surface point data to the end of their respective vectors, but we also keep a helper
|
||||||
|
//! structure that's a list of lists of sorted indices. We can incrementally re-sort the indices quickly without having to shift
|
||||||
|
//! all the surface point data around.
|
||||||
|
class SurfacePointList
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AZ_CLASS_ALLOCATOR(SurfacePointList, AZ::SystemAllocator, 0);
|
||||||
|
AZ_TYPE_INFO(SurfacePointList, "{DBA02848-2131-4279-BDEF-3581B76AB736}");
|
||||||
|
|
||||||
|
SurfacePointList() = default;
|
||||||
|
~SurfacePointList() = default;
|
||||||
|
|
||||||
|
// ---------- List Construction APIs -------------
|
||||||
|
|
||||||
|
//! Clear the surface point list.
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
//! Constructor for creating a SurfacePointList from a list of SurfacePoint data.
|
||||||
|
//! Primarily used as a convenience for unit tests.
|
||||||
|
//! @param surfacePoints - A set of SurfacePoint points to store in the SurfacePointList.
|
||||||
|
//! Each point that's passed in will be treated as both the input and output position.
|
||||||
|
//! The list will be fully constructed and queryable after this runs.
|
||||||
|
SurfacePointList(AZStd::span<const AzFramework::SurfaceData::SurfacePoint> surfacePoints);
|
||||||
|
|
||||||
|
//! Start construction of a SurfacePointList from a list of SurfacePoint data.
|
||||||
|
//! Primarily used as a convenience for unit tests.
|
||||||
|
//! @param surfacePoints - A set of SurfacePoint points to store in the SurfacePointList.
|
||||||
|
//! The list will remain in the "constructing" state after this is called, so it will still be possible to add/modify
|
||||||
|
//! points, and EndListConstruction() will still need to be called.
|
||||||
|
void StartListConstruction(AZStd::span<const AzFramework::SurfaceData::SurfacePoint> surfacePoints);
|
||||||
|
|
||||||
|
//! Start construction of a SurfacePointList.
|
||||||
|
//! @param inPositions - the list of input positions that will be used to generate this list. This list is expected to remain
|
||||||
|
//! valid until EndListConstruction() is called.
|
||||||
|
//! @param maxPointsPerInput - the maximum number of potential surface points that will be generated for each input. This is used
|
||||||
|
//! for allocating internal structures during list construction and is enforced to be correct.
|
||||||
|
//! @param filterTags - optional list of tags to filter the generated surface points by. If this list is provided, every surface
|
||||||
|
//! point remaining in the list after construction will contain at least one of these tags. If the list is empty, all generated
|
||||||
|
//! points will remain in the list. The filterTags list is expected to remain valid until EndListConstruction() is called.
|
||||||
|
void StartListConstruction(AZStd::span<const AZ::Vector3> inPositions, size_t maxPointsPerInput,
|
||||||
|
AZStd::span<const SurfaceTag> filterTags);
|
||||||
|
|
||||||
|
//! Add a surface point to the list.
|
||||||
|
//! To use this method optimally, the points should get added in increasing inPosition index order.
|
||||||
|
//! @param entityId - The entity creating the surface point.
|
||||||
|
//! @param inPosition - The input position that produced this 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& inPosition,
|
||||||
|
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& weights);
|
||||||
|
|
||||||
|
//! Modify the surface weights for each surface point in the list.
|
||||||
|
//! @param currentEntityId - The entity currently modifying the surface weights. This is used to prevent entities from modifying
|
||||||
|
//! any points they created.
|
||||||
|
//! @param modificationWeightCallback - The function to call for each surface point to process.
|
||||||
|
void ModifySurfaceWeights(
|
||||||
|
const AZ::EntityId& currentEntityId,
|
||||||
|
AZStd::function<void(const AZ::Vector3& position, SurfaceTagWeights& surfaceWeights)> modificationWeightCallback);
|
||||||
|
|
||||||
|
//! End construction of the SurfacePointList.
|
||||||
|
//! After this is called, surface points can no longer be added or modified, and all of the query APIs can start getting used.
|
||||||
|
void EndListConstruction();
|
||||||
|
|
||||||
|
// ---------- List Query APIs -------------
|
||||||
|
|
||||||
|
//! Return whether or not the entire surface point list is empty.
|
||||||
|
//! @return True if empty, false if it contains any points.
|
||||||
|
bool IsEmpty() const;
|
||||||
|
|
||||||
|
//! Return whether or not a given input position index has any output points associated with it.
|
||||||
|
//! @param inputPositionIndex - The input position to look for output points for.
|
||||||
|
//! @return True if empty, false if there is at least one output point associated with the input position.
|
||||||
|
bool IsEmpty(size_t inputPositionIndex) const;
|
||||||
|
|
||||||
|
//! Return the total number of output points generated.
|
||||||
|
//! @return The total number of output points generated.
|
||||||
|
size_t GetSize() const;
|
||||||
|
|
||||||
|
//! Return the total number of output points generated from a specific input position index.
|
||||||
|
//! @param inputPositionIndex - The input position to look for output points for.
|
||||||
|
//! @return The total number of output points generated from a specific input position index.
|
||||||
|
size_t GetSize(size_t inputPositionIndex) const;
|
||||||
|
|
||||||
|
//! Return the total number of input positions.
|
||||||
|
//! Normally the caller would already be expected to know this, but in the case of using region-based queries, the number of
|
||||||
|
//! input positions might not be entirely obvious.
|
||||||
|
//! @return The total number of input positions used to generate the outputs.
|
||||||
|
size_t GetInputPositionSize() const
|
||||||
|
{
|
||||||
|
return m_inputPositionSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Enumerate every surface point and call a callback for each point found.
|
||||||
|
//! Note: There is no guaranteed order to which the points will be enumerated.
|
||||||
|
//! @pointCallback - The method to call with each surface point.
|
||||||
|
void EnumeratePoints(
|
||||||
|
AZStd::function<bool(
|
||||||
|
size_t inputPositionIndex, const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& surfaceWeights)>
|
||||||
|
pointCallback) const;
|
||||||
|
|
||||||
|
//! Enumerate every surface point for a given input position and call a callback for each point found.
|
||||||
|
//! Note: There is no guaranteed order to which the points will be enumerated.
|
||||||
|
//! @inputPositionIndex - The input position to get the outputs for.
|
||||||
|
//! @pointCallback - The method to call with each surface point.
|
||||||
|
void EnumeratePoints(
|
||||||
|
size_t inputPositionIndex,
|
||||||
|
AZStd::function<bool(
|
||||||
|
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& surfaceWeights)> pointCallback) const;
|
||||||
|
|
||||||
|
//! Get the surface point with the highest Z value for a given input position.
|
||||||
|
//! @inputPositionIndex - The input position to get the highest surface point for.
|
||||||
|
//! @return The surface point with the highest Z value for the given input position.
|
||||||
|
AzFramework::SurfaceData::SurfacePoint GetHighestSurfacePoint(size_t inputPositionIndex) const;
|
||||||
|
|
||||||
|
//! Get the AABB that encapsulates all of the generated output surface points.
|
||||||
|
//! @return The AABB surrounding all the output surface points.
|
||||||
|
AZ::Aabb GetSurfacePointAabb() const
|
||||||
|
{
|
||||||
|
return m_surfacePointBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Remove any output surface points that don't contain any of the provided surface tags.
|
||||||
|
void FilterPoints(AZStd::span<const SurfaceTag> desiredTags);
|
||||||
|
|
||||||
|
// Get the input position index associated with a specific input position.
|
||||||
|
size_t GetInPositionIndexFromPosition(const AZ::Vector3& inPosition) const;
|
||||||
|
|
||||||
|
// Get the first entry in the sortedSurfacePointIndices list for the given input position index.
|
||||||
|
size_t GetSurfacePointStartIndexFromInPositionIndex(size_t inPositionIndex) const;
|
||||||
|
|
||||||
|
// During list construction, keep track of the tags to filter the output points to.
|
||||||
|
// These will be used at the end of list construction to remove any output points that don't contain any of these tags.
|
||||||
|
// (If the list is empty, all output points will be retained)
|
||||||
|
AZStd::span<const SurfaceTag> m_filterTags;
|
||||||
|
|
||||||
|
// During list construction, keep track of all the input positions that we'll generate outputs for.
|
||||||
|
// Note that after construction is complete, we'll only know how *many* input positions, but not their values.
|
||||||
|
// This keeps us from copying data that the caller should already have. We can't assume the lifetime of that data though,
|
||||||
|
// so we won't hold on to the span<> after construction.
|
||||||
|
AZStd::span<const AZ::Vector3> m_inputPositions;
|
||||||
|
|
||||||
|
// The total number of input positions that we have. We keep this value separately so that we can still know the quantity
|
||||||
|
// after list construction when our m_inputPositions span<> has become invalid.
|
||||||
|
size_t m_inputPositionSize = 0;
|
||||||
|
|
||||||
|
// The last input position index that we used when adding points.
|
||||||
|
// This is used by GetInPositionIndexFromPosition() as an optimization to reduce search times for converting input positions
|
||||||
|
// to indices without needing to construct a separate search structure.
|
||||||
|
// Because we know surface points will get added in input position order, we'll always start looking for our next input position
|
||||||
|
// with the last one we used.
|
||||||
|
mutable size_t m_lastInputPositionIndex = 0;
|
||||||
|
|
||||||
|
// This list is the size of m_inputPositions.size() and contains the number of output surface points that we've generated for
|
||||||
|
// each input point.
|
||||||
|
AZStd::vector<size_t> m_numSurfacePointsPerInput;
|
||||||
|
|
||||||
|
// The AABB surrounding all the surface points. We build this up incrementally as we add each surface point into the list.
|
||||||
|
AZ::Aabb m_surfacePointBounds = AZ::Aabb::CreateNull();
|
||||||
|
|
||||||
|
// The maximum number of output points that can be generated for each input.
|
||||||
|
size_t m_maxSurfacePointsPerInput = 0;
|
||||||
|
|
||||||
|
// State tracker to determine whether or not the list is currently under construction.
|
||||||
|
// This is used to verify that the construction APIs are only used during construction, and the query APIs are only used
|
||||||
|
// after construction is complete.
|
||||||
|
bool m_listIsBeingConstructed = false;
|
||||||
|
|
||||||
|
// List of lists that's used to index into our storage vectors for all the surface point data.
|
||||||
|
// The surface points are stored sequentially in creation order in the storage vectors.
|
||||||
|
// This should be thought of as a nested array - m_sortedSurfacePointIndices[m_inputPositionSize][m_maxSurfacePointsPerInput].
|
||||||
|
// For each input position, the list of indices are kept sorted in decreasing Z order.
|
||||||
|
AZStd::vector<size_t> m_sortedSurfacePointIndices;
|
||||||
|
|
||||||
|
// Storage vectors for keeping track of all the created surface point data.
|
||||||
|
// 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.
|
||||||
|
AZStd::vector<AZ::Vector3> m_surfacePositionList;
|
||||||
|
AZStd::vector<AZ::Vector3> m_surfaceNormalList;
|
||||||
|
AZStd::vector<SurfaceTagWeights> m_surfaceWeightsList;
|
||||||
|
AZStd::vector<AZ::EntityId> m_surfaceCreatorIdList;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,361 @@
|
|||||||
|
/*
|
||||||
|
* 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 <SurfaceData/SurfacePointList.h>
|
||||||
|
|
||||||
|
namespace SurfaceData
|
||||||
|
{
|
||||||
|
size_t SurfacePointList::GetInPositionIndexFromPosition(const AZ::Vector3& inPosition) const
|
||||||
|
{
|
||||||
|
// Given an input position, find the input position index that's associated with it.
|
||||||
|
// We'll bias towards always having a position that's the same or further in our input list than before,
|
||||||
|
// so we'll do a linear search that starts with the last input position we used, and goes forward (and wraps around)
|
||||||
|
// until we've searched them all.
|
||||||
|
// Our expectation is that most of the time, we'll only have to compare 0-1 input positions.
|
||||||
|
|
||||||
|
size_t inPositionIndex = m_lastInputPositionIndex;
|
||||||
|
bool foundMatch = false;
|
||||||
|
for (size_t indexCounter = 0; indexCounter < m_inputPositions.size(); indexCounter++)
|
||||||
|
{
|
||||||
|
if (m_inputPositions[inPositionIndex] == inPosition)
|
||||||
|
{
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
inPositionIndex = (inPositionIndex + 1) % m_inputPositions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
AZ_Assert(foundMatch, "Couldn't find input position!");
|
||||||
|
m_lastInputPositionIndex = inPositionIndex;
|
||||||
|
return inPositionIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SurfacePointList::GetSurfacePointStartIndexFromInPositionIndex(size_t inPositionIndex) const
|
||||||
|
{
|
||||||
|
// Index to the first output surface point for this input position.
|
||||||
|
return inPositionIndex * m_maxSurfacePointsPerInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
SurfacePointList::SurfacePointList(AZStd::span<const AzFramework::SurfaceData::SurfacePoint> surfacePoints)
|
||||||
|
{
|
||||||
|
// Construct and finalize the list with the set of passed-in surface points.
|
||||||
|
// This is primarily a convenience for unit tests.
|
||||||
|
StartListConstruction(surfacePoints);
|
||||||
|
EndListConstruction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::StartListConstruction(AZStd::span<const AzFramework::SurfaceData::SurfacePoint> surfacePoints)
|
||||||
|
{
|
||||||
|
// Construct the list with the set of passed-in surface points but don't finalize it.
|
||||||
|
// This is primarily a convenience for unit tests that want to test surface modifiers with specific inputs.
|
||||||
|
|
||||||
|
surfacePoints.begin();
|
||||||
|
StartListConstruction(AZStd::span<const AZ::Vector3>(&(surfacePoints.begin()->m_position), 1), surfacePoints.size(), {});
|
||||||
|
|
||||||
|
for (auto& point : surfacePoints)
|
||||||
|
{
|
||||||
|
SurfaceTagWeights weights(point.m_surfaceTags);
|
||||||
|
AddSurfacePoint(AZ::EntityId(), point.m_position, point.m_position, point.m_normal, weights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::StartListConstruction(
|
||||||
|
AZStd::span<const AZ::Vector3> inPositions, size_t maxPointsPerInput, AZStd::span<const SurfaceTag> filterTags)
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to start list construction on a list currently under construction.");
|
||||||
|
AZ_Assert(m_surfacePositionList.empty(), "Trying to reserve space on a list that is already being used.");
|
||||||
|
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
m_listIsBeingConstructed = true;
|
||||||
|
|
||||||
|
// Save off working references to the data we'll need during list construction.
|
||||||
|
// These references need to remain valid during construction, but not afterwards.
|
||||||
|
m_filterTags = filterTags;
|
||||||
|
m_inputPositions = inPositions;
|
||||||
|
m_inputPositionSize = inPositions.size();
|
||||||
|
m_maxSurfacePointsPerInput = maxPointsPerInput;
|
||||||
|
|
||||||
|
size_t outputReserveSize = inPositions.size() * m_maxSurfacePointsPerInput;
|
||||||
|
|
||||||
|
// Reserve enough space to have one value per input position, and initialize it to 0.
|
||||||
|
m_numSurfacePointsPerInput.resize(m_inputPositionSize);
|
||||||
|
|
||||||
|
// Reserve enough space to have maxSurfacePointsPerInput entries per input position, and initialize them all to 0.
|
||||||
|
m_sortedSurfacePointIndices.resize(outputReserveSize);
|
||||||
|
|
||||||
|
// Reserve enough space for all our possible output surface points, but don't initialize them.
|
||||||
|
m_surfaceCreatorIdList.reserve(outputReserveSize);
|
||||||
|
m_surfacePositionList.reserve(outputReserveSize);
|
||||||
|
m_surfaceNormalList.reserve(outputReserveSize);
|
||||||
|
m_surfaceWeightsList.reserve(outputReserveSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::Clear()
|
||||||
|
{
|
||||||
|
m_listIsBeingConstructed = false;
|
||||||
|
|
||||||
|
m_lastInputPositionIndex = 0;
|
||||||
|
m_inputPositionSize = 0;
|
||||||
|
m_maxSurfacePointsPerInput = 0;
|
||||||
|
|
||||||
|
m_filterTags = {};
|
||||||
|
m_inputPositions = {};
|
||||||
|
|
||||||
|
m_sortedSurfacePointIndices.clear();
|
||||||
|
m_numSurfacePointsPerInput.clear();
|
||||||
|
m_surfacePositionList.clear();
|
||||||
|
m_surfaceNormalList.clear();
|
||||||
|
m_surfaceWeightsList.clear();
|
||||||
|
m_surfaceCreatorIdList.clear();
|
||||||
|
|
||||||
|
m_surfacePointBounds = AZ::Aabb::CreateNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::AddSurfacePoint(
|
||||||
|
const AZ::EntityId& entityId, const AZ::Vector3& inPosition,
|
||||||
|
const AZ::Vector3& position, const AZ::Vector3& normal, const SurfaceTagWeights& masks)
|
||||||
|
{
|
||||||
|
AZ_Assert(m_listIsBeingConstructed, "Trying to add surface points to a SurfacePointList that isn't under construction.");
|
||||||
|
|
||||||
|
// Find the inPositionIndex that matches the inPosition.
|
||||||
|
size_t inPositionIndex = GetInPositionIndexFromPosition(inPosition);
|
||||||
|
|
||||||
|
// Find the first SurfacePoint that either matches the inPosition, or that starts the range for the next inPosition after this one.
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inPositionIndex);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
size_t surfacePointInsertIndex = surfacePointStartIndex;
|
||||||
|
|
||||||
|
for (; surfacePointInsertIndex < (surfacePointStartIndex + m_numSurfacePointsPerInput[inPositionIndex]); ++surfacePointInsertIndex)
|
||||||
|
{
|
||||||
|
// (Someday we should add a configurable tolerance for comparison)
|
||||||
|
if (m_surfacePositionList[m_sortedSurfacePointIndices[surfacePointInsertIndex]].IsClose(position) &&
|
||||||
|
m_surfaceNormalList[m_sortedSurfacePointIndices[surfacePointInsertIndex]].IsClose(normal))
|
||||||
|
{
|
||||||
|
// consolidate points with similar attributes by adding masks/weights to the similar point instead of adding a new one.
|
||||||
|
m_surfaceWeightsList[m_sortedSurfacePointIndices[surfacePointInsertIndex]].AddSurfaceTagWeights(masks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (m_surfacePositionList[m_sortedSurfacePointIndices[surfacePointInsertIndex]].GetZ() < position.GetZ())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've made it here, we're adding the point, not merging it.
|
||||||
|
|
||||||
|
// Verify we aren't adding more points than expected.
|
||||||
|
AZ_Assert(m_numSurfacePointsPerInput[inPositionIndex] < m_maxSurfacePointsPerInput, "Adding too many surface points.");
|
||||||
|
|
||||||
|
// Expand our output AABB to include this point.
|
||||||
|
m_surfacePointBounds.AddPoint(position);
|
||||||
|
|
||||||
|
// If this isn't the first output for this input position, shift our sorted indices for this input position to make room for
|
||||||
|
// the new entry.
|
||||||
|
if (m_numSurfacePointsPerInput[inPositionIndex] > 0)
|
||||||
|
{
|
||||||
|
size_t startIndex = surfacePointInsertIndex;
|
||||||
|
size_t endIndex = surfacePointStartIndex + m_numSurfacePointsPerInput[inPositionIndex];
|
||||||
|
|
||||||
|
AZStd::move_backward(
|
||||||
|
m_sortedSurfacePointIndices.begin() + startIndex, m_sortedSurfacePointIndices.begin() + endIndex,
|
||||||
|
m_sortedSurfacePointIndices.begin() + endIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_numSurfacePointsPerInput[inPositionIndex]++;
|
||||||
|
|
||||||
|
// Insert the new sorted index that references into our storage vectors.
|
||||||
|
m_sortedSurfacePointIndices[surfacePointInsertIndex] = m_surfacePositionList.size();
|
||||||
|
|
||||||
|
// Add the new point to the back of our storage vectors.
|
||||||
|
m_surfacePositionList.emplace_back(position);
|
||||||
|
m_surfaceNormalList.emplace_back(normal);
|
||||||
|
m_surfaceWeightsList.emplace_back(masks);
|
||||||
|
m_surfaceCreatorIdList.emplace_back(entityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::ModifySurfaceWeights(
|
||||||
|
const AZ::EntityId& currentEntityId,
|
||||||
|
AZStd::function<void(const AZ::Vector3& position, SurfaceData::SurfaceTagWeights& surfaceWeights)> modificationWeightCallback)
|
||||||
|
{
|
||||||
|
AZ_Assert(m_listIsBeingConstructed, "Trying to modify surface weights on a SurfacePointList that isn't under construction.");
|
||||||
|
|
||||||
|
// For every valid output point, call the modification callback only if it doesn't match the entity that created the point.
|
||||||
|
for (size_t inputIndex = 0; (inputIndex < m_inputPositionSize); inputIndex++)
|
||||||
|
{
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inputIndex);
|
||||||
|
for (size_t index = surfacePointStartIndex; (index < (surfacePointStartIndex + m_numSurfacePointsPerInput[inputIndex]));
|
||||||
|
index++)
|
||||||
|
{
|
||||||
|
if (m_surfaceCreatorIdList[m_sortedSurfacePointIndices[index]] != currentEntityId)
|
||||||
|
{
|
||||||
|
modificationWeightCallback(
|
||||||
|
m_surfacePositionList[m_sortedSurfacePointIndices[index]],
|
||||||
|
m_surfaceWeightsList[m_sortedSurfacePointIndices[index]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::FilterPoints(AZStd::span<const SurfaceTag> desiredTags)
|
||||||
|
{
|
||||||
|
AZ_Assert(m_listIsBeingConstructed, "Trying to filter a SurfacePointList that isn't under construction.");
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// The algorithm below is basically an "erase_if" that's operating across multiple storage vectors and using one level of
|
||||||
|
// indirection to keep our sorted indices valid.
|
||||||
|
// At some point we might want to consider modifying this to compact the final storage to the minimum needed.
|
||||||
|
for (size_t inputIndex = 0; (inputIndex < m_inputPositionSize); inputIndex++)
|
||||||
|
{
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inputIndex);
|
||||||
|
size_t listSize = (surfacePointStartIndex + m_numSurfacePointsPerInput[inputIndex]);
|
||||||
|
size_t index = surfacePointStartIndex;
|
||||||
|
for (; index < listSize; index++)
|
||||||
|
{
|
||||||
|
if (!m_surfaceWeightsList[m_sortedSurfacePointIndices[index]].HasAnyMatchingTags(desiredTags))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != listSize)
|
||||||
|
{
|
||||||
|
size_t next = index + 1;
|
||||||
|
for (; next < listSize; ++next)
|
||||||
|
{
|
||||||
|
if (m_surfaceWeightsList[m_sortedSurfacePointIndices[index]].HasAnyMatchingTags(desiredTags))
|
||||||
|
{
|
||||||
|
m_sortedSurfacePointIndices[index] = AZStd::move(m_sortedSurfacePointIndices[next]);
|
||||||
|
m_surfaceCreatorIdList[m_sortedSurfacePointIndices[index]] =
|
||||||
|
AZStd::move(m_surfaceCreatorIdList[m_sortedSurfacePointIndices[next]]);
|
||||||
|
m_surfacePositionList[m_sortedSurfacePointIndices[index]] =
|
||||||
|
AZStd::move(m_surfacePositionList[m_sortedSurfacePointIndices[next]]);
|
||||||
|
m_surfaceNormalList[m_sortedSurfacePointIndices[index]] =
|
||||||
|
AZStd::move(m_surfaceNormalList[m_sortedSurfacePointIndices[next]]);
|
||||||
|
m_surfaceWeightsList[m_sortedSurfacePointIndices[index]] =
|
||||||
|
AZStd::move(m_surfaceWeightsList[m_sortedSurfacePointIndices[next]]);
|
||||||
|
|
||||||
|
m_numSurfacePointsPerInput[inputIndex]--;
|
||||||
|
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::EndListConstruction()
|
||||||
|
{
|
||||||
|
AZ_Assert(m_listIsBeingConstructed, "Trying to end list construction on a SurfacePointList that isn't under construction.");
|
||||||
|
|
||||||
|
// Now that we've finished adding and modifying points, filter out any points that don't match the filterTags list, if we have one.
|
||||||
|
if (!m_filterTags.empty())
|
||||||
|
{
|
||||||
|
FilterPoints(m_filterTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_listIsBeingConstructed = false;
|
||||||
|
m_inputPositions = {};
|
||||||
|
m_filterTags = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SurfacePointList::IsEmpty() const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
return m_surfacePositionList.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SurfacePointList::IsEmpty(size_t inputPositionIndex) const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
return (m_inputPositionSize == 0) || (m_numSurfacePointsPerInput[inputPositionIndex] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SurfacePointList::GetSize() const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
return m_surfacePositionList.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SurfacePointList::GetSize(size_t inputPositionIndex) const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
return (m_inputPositionSize == 0) ? 0 : (m_numSurfacePointsPerInput[inputPositionIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::EnumeratePoints(
|
||||||
|
size_t inputPositionIndex,
|
||||||
|
AZStd::function<bool(const AZ::Vector3&, const AZ::Vector3&, const SurfaceData::SurfaceTagWeights&)>
|
||||||
|
pointCallback) const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inputPositionIndex);
|
||||||
|
for (size_t index = surfacePointStartIndex;
|
||||||
|
(index < (surfacePointStartIndex + m_numSurfacePointsPerInput[inputPositionIndex])); index++)
|
||||||
|
{
|
||||||
|
if (!pointCallback(
|
||||||
|
m_surfacePositionList[m_sortedSurfacePointIndices[index]], m_surfaceNormalList[m_sortedSurfacePointIndices[index]],
|
||||||
|
m_surfaceWeightsList[m_sortedSurfacePointIndices[index]]))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePointList::EnumeratePoints(
|
||||||
|
AZStd::function<bool(size_t inputPositionIndex, const AZ::Vector3&, const AZ::Vector3&, const SurfaceData::SurfaceTagWeights&)>
|
||||||
|
pointCallback) const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
for (size_t inputIndex = 0; (inputIndex < m_inputPositionSize); inputIndex++)
|
||||||
|
{
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inputIndex);
|
||||||
|
for (size_t index = surfacePointStartIndex; (index < (surfacePointStartIndex + m_numSurfacePointsPerInput[inputIndex]));
|
||||||
|
index++)
|
||||||
|
{
|
||||||
|
if (!pointCallback(
|
||||||
|
inputIndex, m_surfacePositionList[m_sortedSurfacePointIndices[index]],
|
||||||
|
m_surfaceNormalList[m_sortedSurfacePointIndices[index]], m_surfaceWeightsList[m_sortedSurfacePointIndices[index]]))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AzFramework::SurfaceData::SurfacePoint SurfacePointList::GetHighestSurfacePoint([[maybe_unused]] size_t inputPositionIndex) const
|
||||||
|
{
|
||||||
|
AZ_Assert(!m_listIsBeingConstructed, "Trying to query a SurfacePointList that's still under construction.");
|
||||||
|
|
||||||
|
if (m_numSurfacePointsPerInput[inputPositionIndex] == 0)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t surfacePointStartIndex = GetSurfacePointStartIndexFromInPositionIndex(inputPositionIndex);
|
||||||
|
AzFramework::SurfaceData::SurfacePoint point;
|
||||||
|
point.m_position = m_surfacePositionList[m_sortedSurfacePointIndices[surfacePointStartIndex]];
|
||||||
|
point.m_normal = m_surfaceNormalList[m_sortedSurfacePointIndices[surfacePointStartIndex]];
|
||||||
|
point.m_surfaceTags = m_surfaceWeightsList[m_sortedSurfacePointIndices[surfacePointStartIndex]].GetSurfaceTagWeightList();
|
||||||
|
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue