a10bf92739
* First version of GradientTransform class. The gradient transform logic is getting encapsulated into a class so that it can be cached and used by components in a much more optimal way than making ebus calls to the GradientTransform component on every transformed point. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Moved GradientTransform into its own source files. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Clean up and simplify GradientTransform logic. Added extensive commenting and split TransformPositionToUVW into a separate method for normalizing (TransformPositionToUVWNormalized) so that there doesn't need to be any conditional logic. There's no runtime variance as to which one needs to be called from a given call site. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Added unit tests for GradientTransform. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Add comparison operators to GradientTransform so we can easily tell when it has changed. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> * Updated comments to be more Doxygen-friendly. Signed-off-by: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com>
164 lines
7.4 KiB
C++
164 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_wrappingTransform(NoTransform)
|
|
, 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());
|
|
}
|
|
|
|
// Set up the appropriate wrapping transform function for the the given wrapping type.
|
|
// Also note that ClampToZero is the only wrapping type that allows us to return a "pointIsRejected" result
|
|
// for points that fall outside the shape bounds.
|
|
if (m_shapeBounds.IsValid())
|
|
{
|
|
switch (wrappingType)
|
|
{
|
|
default:
|
|
case WrappingType::None:
|
|
m_wrappingTransform = GetUnboundedPointInAabb;
|
|
break;
|
|
case WrappingType::ClampToEdge:
|
|
m_wrappingTransform = GetClampedPointInAabb;
|
|
break;
|
|
case WrappingType::ClampToZero:
|
|
m_alwaysAcceptPoint = false;
|
|
m_wrappingTransform = GetClampedPointInAabb;
|
|
break;
|
|
case WrappingType::Mirror:
|
|
m_wrappingTransform = GetMirroredPointInAabb;
|
|
break;
|
|
case WrappingType::Repeat:
|
|
m_wrappingTransform = GetWrappedPointInAabb;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
outUVW = m_wrappingTransform(outUVW, m_shapeBounds);
|
|
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();
|
|
}
|
|
}
|