You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
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();
|
|
}
|
|
}
|