GradientTransform.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Math/MathUtils.h>
  9. #include <GradientSignal/GradientTransform.h>
  10. namespace GradientSignal
  11. {
  12. GradientTransform::GradientTransform(
  13. const AZ::Aabb& shapeBounds, const AZ::Matrix3x4& transform, bool use3d,
  14. float frequencyZoom, GradientSignal::WrappingType wrappingType)
  15. : m_shapeBounds(shapeBounds)
  16. , m_transform(transform)
  17. , m_inverseTransform(transform.GetInverseFull())
  18. , m_frequencyZoom(frequencyZoom)
  19. , m_wrappingType(wrappingType)
  20. , m_alwaysAcceptPoint(true)
  21. {
  22. // If we want this to be a 2D gradient lookup, we always want to set the W result in the output to 0.
  23. // The easiest / cheapest way to make this happen is just to clear out the third row in the inverseTransform.
  24. if (!use3d)
  25. {
  26. m_inverseTransform.SetRow(2, AZ::Vector4::CreateZero());
  27. }
  28. // If we have invalid shape bounds, reset the wrapping type back to None. Wrapping won't work without valid bounds.
  29. if (!m_shapeBounds.IsValid())
  30. {
  31. m_wrappingType = WrappingType::None;
  32. }
  33. // ClampToZero is the only wrapping type that allows us to return a "pointIsRejected" result for points that fall
  34. // outside the shape bounds.
  35. if (m_wrappingType == WrappingType::ClampToZero)
  36. {
  37. m_alwaysAcceptPoint = false;
  38. }
  39. m_normalizeExtentsReciprocal = AZ::Vector3(
  40. AZ::IsClose(0.0f, m_shapeBounds.GetXExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetXExtent()),
  41. AZ::IsClose(0.0f, m_shapeBounds.GetYExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetYExtent()),
  42. AZ::IsClose(0.0f, m_shapeBounds.GetZExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetZExtent()));
  43. }
  44. void GradientTransform::TransformLocalPositionToUVW(
  45. const AZ::Vector3& inLocalPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
  46. {
  47. outUVW = inLocalPosition;
  48. // For most wrapping types, we always accept the point, but for ClampToZero we only accept it if it's within
  49. // the shape bounds. We don't use m_shapeBounds.Contains() here because Contains() is inclusive on all edges.
  50. // For uv consistency between clamped and unclamped states, we only want to accept uv ranges of [min, max),
  51. // so we specifically need to exclude the max edges here.
  52. bool wasPointAccepted = m_alwaysAcceptPoint ||
  53. (outUVW.IsGreaterEqualThan(m_shapeBounds.GetMin()) && outUVW.IsLessThan(m_shapeBounds.GetMax()));
  54. wasPointRejected = !wasPointAccepted;
  55. switch (m_wrappingType)
  56. {
  57. default:
  58. case WrappingType::None:
  59. outUVW = GetUnboundedPointInAabb(outUVW, m_shapeBounds);
  60. break;
  61. case WrappingType::ClampToEdge:
  62. outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
  63. break;
  64. case WrappingType::ClampToZero:
  65. outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
  66. break;
  67. case WrappingType::Mirror:
  68. outUVW = GetMirroredPointInAabb(outUVW, m_shapeBounds);
  69. break;
  70. case WrappingType::Repeat:
  71. outUVW = GetWrappedPointInAabb(outUVW, m_shapeBounds);
  72. break;
  73. }
  74. outUVW *= m_frequencyZoom;
  75. }
  76. void GradientTransform::TransformLocalPositionToUVWNormalized(
  77. const AZ::Vector3& inLocalPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
  78. {
  79. TransformLocalPositionToUVW(inLocalPosition, outUVW, wasPointRejected);
  80. // This effectively does AZ::LerpInverse(bounds.GetMin(), bounds.GetMax(), point) if shouldNormalize is true,
  81. // and just returns outUVW if shouldNormalize is false.
  82. outUVW = m_normalizeExtentsReciprocal * (outUVW - m_shapeBounds.GetMin());
  83. }
  84. void GradientTransform::TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
  85. {
  86. // Transform coordinate into "local" relative space of shape bounds, and set W to 0 if this is a 2D gradient.
  87. AZ::Vector3 inLocalPosition = m_inverseTransform * inPosition;
  88. TransformLocalPositionToUVW(inLocalPosition, outUVW, wasPointRejected);
  89. }
  90. void GradientTransform::TransformPositionToUVWNormalized(
  91. const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
  92. {
  93. // Transform coordinate into "local" relative space of shape bounds, and set W to 0 if this is a 2D gradient.
  94. AZ::Vector3 inLocalPosition = m_inverseTransform * inPosition;
  95. TransformLocalPositionToUVWNormalized(inLocalPosition, outUVW, wasPointRejected);
  96. }
  97. WrappingType GradientTransform::GetWrappingType() const
  98. {
  99. return m_wrappingType;
  100. }
  101. AZ::Aabb GradientTransform::GetBounds() const
  102. {
  103. return m_shapeBounds;
  104. }
  105. AZ::Vector3 GradientTransform::GetScale() const
  106. {
  107. return m_transform.RetrieveScale();
  108. }
  109. float GradientTransform::GetFrequencyZoom() const
  110. {
  111. return m_frequencyZoom;
  112. }
  113. AZ::Matrix3x4 GradientTransform::GetTransformMatrix() const
  114. {
  115. return m_transform;
  116. }
  117. void GradientTransform::GetMinMaxUvwValues(AZ::Vector3& minUvw, AZ::Vector3& maxUvw) const
  118. {
  119. // Get the UVW values at the min & max corners.
  120. bool wasPointRejected;
  121. TransformLocalPositionToUVW(m_shapeBounds.GetMin(), minUvw, wasPointRejected);
  122. TransformLocalPositionToUVW(m_shapeBounds.GetMax(), maxUvw, wasPointRejected);
  123. }
  124. void GradientTransform::GetMinMaxUvwValuesNormalized(AZ::Vector3& minUvw, AZ::Vector3& maxUvw) const
  125. {
  126. // Get the normalized UVW values at the min & max corners.
  127. bool wasPointRejected;
  128. TransformLocalPositionToUVWNormalized(m_shapeBounds.GetMin(), minUvw, wasPointRejected);
  129. TransformLocalPositionToUVWNormalized(m_shapeBounds.GetMax(), maxUvw, wasPointRejected);
  130. }
  131. AZ::Vector3 GradientTransform::NoTransform(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
  132. {
  133. return point;
  134. }
  135. AZ::Vector3 GradientTransform::GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
  136. {
  137. return point;
  138. }
  139. AZ::Vector3 GradientTransform::GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
  140. {
  141. // We want the clamped sampling states to clamp uvs to the [min, max) range.
  142. return point.GetClamp(bounds.GetMin(), bounds.GetMax() - AZ::Vector3(UvEpsilon));
  143. }
  144. AZ::Vector3 GradientTransform::GetWrappedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
  145. {
  146. return AZ::Vector3(
  147. AZ::Wrap(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
  148. AZ::Wrap(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
  149. AZ::Wrap(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
  150. }
  151. AZ::Vector3 GradientTransform::GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
  152. {
  153. /* For mirroring, we want to produce the following pattern:
  154. * [min, max) : value
  155. * [max, min) : max - value - epsilon
  156. * [min, max) : value
  157. * [max, min) : max - value - epsilon
  158. * ...
  159. * The epsilon is because we always want to keep our output values in the [min, max) range. We apply the epsilon to all
  160. * the mirrored values so that we get consistent spacing between the values.
  161. */
  162. auto GetMirror = [](float value, float min, float max) -> float
  163. {
  164. // To calculate the mirror value, we move our value into relative space of [0, rangeX2), then use
  165. // the first half of the range for our "[min, max)" range, and the second half for our "[max, min)" mirrored range.
  166. float relativeValue = value - min;
  167. float range = max - min;
  168. float rangeX2 = range * 2.0f;
  169. // A positive relativeValue will produce a value of [0, rangeX2) from a single mod, but a negative relativeValue
  170. // will produce a value of (-rangeX2, 0]. Adding rangeX2 to the result and taking the mod again puts us back in
  171. // the range of [0, rangeX2) for both negative and positive values. This keeps our mirroring pattern consistent and
  172. // unbroken across both negative and positive coordinate space.
  173. relativeValue = AZ::Mod(AZ::Mod(relativeValue, rangeX2) + rangeX2, rangeX2);
  174. // [range, rangeX2) is our mirrored range, so flip the value when we're in this range and apply the epsilon so that
  175. // we never return the max value, and so that our mirrored values have consistent spacing in the results.
  176. if (relativeValue >= range)
  177. {
  178. relativeValue = rangeX2 - (relativeValue + UvEpsilon);
  179. }
  180. return relativeValue + min;
  181. };
  182. return AZ::Vector3(
  183. GetMirror(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
  184. GetMirror(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
  185. GetMirror(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
  186. }
  187. AZ::Vector3 GradientTransform::GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
  188. {
  189. return point - bounds.GetMin();
  190. }
  191. }