48260486fb
* Change flow so that TerrainSystem stops responding during deactivation. Some systems might accidentally try to call back to the TerrainSystem inside a DestroyBegin notification, so make sure it stops listening before sending out the notification. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Change gradients to cache and use a GradientTransform instance. In my local test case, calling EBus on every call took 337 ms, using a lambda to wrap the calls took 197 ms, and using the fully cached version took 170 ms. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Remove the wrappingTransform function and go back to the switch statement. There was a bit of overhead to each function call due to using AZStd::function that just isn't necessary for this use case. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add profile markers to the heightfield updates so that they're more visible. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Cleared state while component is deactivated. The state was getting refreshed even while the component was in a deactivated state, which meant that it wasn't properly notifying of state changes when it became active since it wasn't detecting an actual change. By clearing the state when deactivated, and ensuring the state isn't getting refreshed *while* deactivated, the notifications work properly. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Fixed compile warning on unit test. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Addressed PR feedback - changed comments, reduced mutex scope Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
168 lines
7.4 KiB
C++
168 lines
7.4 KiB
C++
/*
|
|
* 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 <AzCore/Math/MathUtils.h>
|
|
#include <GradientSignal/GradientTransform.h>
|
|
|
|
|
|
namespace GradientSignal
|
|
{
|
|
GradientTransform::GradientTransform(
|
|
const AZ::Aabb& shapeBounds, const AZ::Matrix3x4& transform, bool use3d,
|
|
float frequencyZoom, GradientSignal::WrappingType wrappingType)
|
|
: m_shapeBounds(shapeBounds)
|
|
, m_inverseTransform(transform.GetInverseFull())
|
|
, m_frequencyZoom(frequencyZoom)
|
|
, m_wrappingType(wrappingType)
|
|
, m_alwaysAcceptPoint(true)
|
|
{
|
|
// If we want this to be a 2D gradient lookup, we always want to set the W result in the output to 0.
|
|
// The easiest / cheapest way to make this happen is just to clear out the third row in the inverseTransform.
|
|
if (!use3d)
|
|
{
|
|
m_inverseTransform.SetRow(2, AZ::Vector4::CreateZero());
|
|
}
|
|
|
|
// If we have invalid shape bounds, reset the wrapping type back to None. Wrapping won't work without valid bounds.
|
|
if (!m_shapeBounds.IsValid())
|
|
{
|
|
m_wrappingType = WrappingType::None;
|
|
}
|
|
|
|
// ClampToZero is the only wrapping type that allows us to return a "pointIsRejected" result for points that fall
|
|
// outside the shape bounds.
|
|
if (m_wrappingType == WrappingType::ClampToZero)
|
|
{
|
|
m_alwaysAcceptPoint = false;
|
|
}
|
|
|
|
m_normalizeExtentsReciprocal = AZ::Vector3(
|
|
AZ::IsClose(0.0f, m_shapeBounds.GetXExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetXExtent()),
|
|
AZ::IsClose(0.0f, m_shapeBounds.GetYExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetYExtent()),
|
|
AZ::IsClose(0.0f, m_shapeBounds.GetZExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetZExtent()));
|
|
}
|
|
|
|
void GradientTransform::TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
|
|
{
|
|
// Transform coordinate into "local" relative space of shape bounds, and set W to 0 if this is a 2D gradient.
|
|
outUVW = m_inverseTransform * inPosition;
|
|
|
|
// For most wrapping types, we always accept the point, but for ClampToZero we only accept it if it's within
|
|
// the shape bounds. We don't use m_shapeBounds.Contains() here because Contains() is inclusive on all edges.
|
|
// For uv consistency between clamped and unclamped states, we only want to accept uv ranges of [min, max),
|
|
// so we specifically need to exclude the max edges here.
|
|
bool wasPointAccepted = m_alwaysAcceptPoint ||
|
|
(outUVW.IsGreaterEqualThan(m_shapeBounds.GetMin()) && outUVW.IsLessThan(m_shapeBounds.GetMax()));
|
|
wasPointRejected = !wasPointAccepted;
|
|
|
|
switch (m_wrappingType)
|
|
{
|
|
default:
|
|
case WrappingType::None:
|
|
outUVW = GetUnboundedPointInAabb(outUVW, m_shapeBounds);
|
|
break;
|
|
case WrappingType::ClampToEdge:
|
|
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
|
|
break;
|
|
case WrappingType::ClampToZero:
|
|
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
|
|
break;
|
|
case WrappingType::Mirror:
|
|
outUVW = GetMirroredPointInAabb(outUVW, m_shapeBounds);
|
|
break;
|
|
case WrappingType::Repeat:
|
|
outUVW = GetWrappedPointInAabb(outUVW, m_shapeBounds);
|
|
break;
|
|
}
|
|
|
|
outUVW *= m_frequencyZoom;
|
|
}
|
|
|
|
void GradientTransform::TransformPositionToUVWNormalized(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
|
|
{
|
|
TransformPositionToUVW(inPosition, outUVW, wasPointRejected);
|
|
|
|
// This effectively does AZ::LerpInverse(bounds.GetMin(), bounds.GetMax(), point) if shouldNormalize is true,
|
|
// and just returns outUVW if shouldNormalize is false.
|
|
outUVW = m_normalizeExtentsReciprocal * (outUVW - m_shapeBounds.GetMin());
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::NoTransform(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
|
|
{
|
|
return point;
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
|
|
{
|
|
return point;
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
|
|
{
|
|
// We want the clamped sampling states to clamp uvs to the [min, max) range.
|
|
return point.GetClamp(bounds.GetMin(), bounds.GetMax() - AZ::Vector3(UvEpsilon));
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::GetWrappedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
|
|
{
|
|
return AZ::Vector3(
|
|
AZ::Wrap(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
|
|
AZ::Wrap(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
|
|
AZ::Wrap(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
|
|
{
|
|
/* For mirroring, we want to produce the following pattern:
|
|
* [min, max) : value
|
|
* [max, min) : max - value - epsilon
|
|
* [min, max) : value
|
|
* [max, min) : max - value - epsilon
|
|
* ...
|
|
* The epsilon is because we always want to keep our output values in the [min, max) range. We apply the epsilon to all
|
|
* the mirrored values so that we get consistent spacing between the values.
|
|
*/
|
|
|
|
auto GetMirror = [](float value, float min, float max) -> float
|
|
{
|
|
// To calculate the mirror value, we move our value into relative space of [0, rangeX2), then use
|
|
// the first half of the range for our "[min, max)" range, and the second half for our "[max, min)" mirrored range.
|
|
|
|
float relativeValue = value - min;
|
|
float range = max - min;
|
|
float rangeX2 = range * 2.0f;
|
|
|
|
// A positive relativeValue will produce a value of [0, rangeX2) from a single mod, but a negative relativeValue
|
|
// will produce a value of (-rangeX2, 0]. Adding rangeX2 to the result and taking the mod again puts us back in
|
|
// the range of [0, rangeX2) for both negative and positive values. This keeps our mirroring pattern consistent and
|
|
// unbroken across both negative and positive coordinate space.
|
|
relativeValue = AZ::Mod(AZ::Mod(relativeValue, rangeX2) + rangeX2, rangeX2);
|
|
|
|
// [range, rangeX2) is our mirrored range, so flip the value when we're in this range and apply the epsilon so that
|
|
// we never return the max value, and so that our mirrored values have consistent spacing in the results.
|
|
if (relativeValue >= range)
|
|
{
|
|
relativeValue = rangeX2 - (relativeValue + UvEpsilon);
|
|
}
|
|
|
|
return relativeValue + min;
|
|
};
|
|
|
|
return AZ::Vector3(
|
|
GetMirror(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
|
|
GetMirror(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
|
|
GetMirror(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
|
|
}
|
|
|
|
AZ::Vector3 GradientTransform::GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
|
|
{
|
|
return point - bounds.GetMin();
|
|
}
|
|
}
|