123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- /*
- * 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 <Tests/GradientSignalTestFixtures.h>
- #include <AzTest/AzTest.h>
- #include <AzCore/Asset/AssetManager.h>
- #include <AzCore/Memory/PoolAllocator.h>
- #include <AzCore/Math/Vector2.h>
- #include <AZTestShared/Math/MathTestHelpers.h>
- #include <GradientSignal/Components/GradientTransformComponent.h>
- namespace UnitTest
- {
- struct GradientSignalTransformTestsFixture : public GradientSignalTest
- {
- // By default, we'll use a shape half extents of (5, 10, 20) for every test, and a world translation of (100, 200, 300).
- struct GradientTransformSetupData
- {
- GradientSignal::WrappingType m_wrappingType{ GradientSignal::WrappingType::None };
- AZ::Vector3 m_shapeHalfExtents{ 5.0f, 10.0f, 20.0f };
- AZ::Vector3 m_worldTranslation{ 100.0f, 200.0f, 300.0f };
- float m_frequencyZoom{ 1.0f };
- };
- struct GradientTransformTestData
- {
- AZ::Vector3 m_positionToTest;
- AZ::Vector3 m_expectedOutputUVW;
- bool m_expectedOutputRejectionResult;
- };
- static constexpr float UvEpsilon = GradientSignal::GradientTransform::UvEpsilon;
- void TestGradientTransform(const GradientTransformSetupData& setup, const GradientTransformTestData& test)
- {
- AZ::Aabb shapeBounds = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3::CreateZero(), setup.m_shapeHalfExtents);
- AZ::Matrix3x4 transform = AZ::Matrix3x4::CreateTranslation(setup.m_worldTranslation);
- float frequencyZoom = setup.m_frequencyZoom;
- GradientSignal::WrappingType wrappingType = setup.m_wrappingType;
- AZ::Vector3 outUVW;
- bool wasPointRejected;
- // Perform the query with a 3D gradient and verify that the results match expectations.
- GradientSignal::GradientTransform gradientTransform3d(shapeBounds, transform, true, frequencyZoom, wrappingType);
- gradientTransform3d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
- EXPECT_THAT(outUVW, IsClose(test.m_expectedOutputUVW));
- EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
- // Perform the query with a 2D gradient and verify that the results match, but always returns a W value of 0.
- GradientSignal::GradientTransform gradientTransform2d(shapeBounds, transform, false, frequencyZoom, wrappingType);
- gradientTransform2d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
- EXPECT_THAT(outUVW, IsClose(AZ::Vector3(test.m_expectedOutputUVW.GetX(), test.m_expectedOutputUVW.GetY(), 0.0f)));
- EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
- }
- };
- TEST_F(GradientSignalTransformTestsFixture, UnboundedWrappingReturnsTranslatedInput)
- {
- GradientTransformSetupData setup = { GradientSignal::WrappingType::None };
- GradientTransformTestData test = {
- // Input position to query
- { 0.0f, 0.0f, 0.0f },
- // Output: For no wrapping, the output is just the input position offset by the world translation.
- { -100.0f, -200.0f, -300.0f }, false
- };
- TestGradientTransform(setup, test);
- }
- TEST_F(GradientSignalTransformTestsFixture, ClampToEdgeReturnsValuesClampedToShapeBounds)
- {
- GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToEdge };
- GradientTransformTestData tests[] = {
- // Test: Input point far below minimum shape bounds
- // Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
- { { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input point directly on minimum shape bounds
- // Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
- { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
-
- // Test: Input point inside shape bounds
- // Our input point is inside the shape bounds, so the result is just input - translation.
- { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
- // Test: Input point directly on maximum shape bounds
- // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
- // expected results are the max shape corner - epsilon.
- { { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
- // Test: Input point far above maximum shape bounds
- // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
- // expected results are the max shape corner - epsilon.
- { { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
- };
- for (auto& test : tests)
- {
- TestGradientTransform(setup, test);
- }
- }
- TEST_F(GradientSignalTransformTestsFixture, MirrorReturnsValuesMirroredBasedOnShapeBounds)
- {
- /* Here's how the results are expected to work for various inputs when using Mirror wrapping.
- * This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
- * Inputs: Outputs:
- * ... ...
- * (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20) // forward mirror
- * (85, 170, 240) - (95, 190, 280) (5, 10, 20) to (-5, -10, -20) // back mirror
- * (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
- * (105, 210, 320) - (115, 230, 360) (5, 10, 20) to (-5, -10, -20) // back mirror
- * (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20) // forward mirror
- * ... ...
- * When below the starting point, both forward and back mirrors will be adjusted by UvEpsilon except for points that fall on the
- * shape minimums.
- * When above the starting point, only back mirrors will be adjusted by UvEpsilon.
- */
- GradientTransformSetupData setup = { GradientSignal::WrappingType::Mirror };
- GradientTransformTestData tests[] = {
- // Test: Input exactly 2x below minimum bounds
- // When landing exactly on the 2x boundary, we return the minumum shape bounds. There is no adjustment by epsilon
- // on the minimum side of the bounds, even when we're in a mirror below the shape bounds.
- { { 75.0f, 150.0f, 200.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input within 2nd mirror repeat below minimum bounds
- // The second mirror repeat should go forward in values, but will be adjusted by UvEpsilon since we're below the
- // minimum bounds.
- { { 84.0f, 168.0f, 237.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
- // Test: Input exactly 1x below minimum bounds.
- // When landing exactly on the 1x boundary, we return the maximum shape bounds minus epsilon.
- { { 85.0f, 170.0f, 240.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
- // Test: Input within 1st mirror repeat below minimum bounds
- // The first mirror repeat should go backwards in values, but will be adjusted by UvEpsilon since we're below the
- // minimum bounds.
- { { 94.0f, 188.0f, 277.0f }, { -4.0f - UvEpsilon, -8.0f - UvEpsilon, -17.0f - UvEpsilon }, false },
- // Test: Input inside shape bounds
- // The translated input position is (1, 2, 3) is inside the shape bounds, so we should just get the translated
- // position back as output.
- { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
- // Test: Input within 1st mirror repeat above maximum bounds
- // The first mirror repeat should go backwards in values. We're above the maximum bounds, so the expected result
- // is (4, 8, 17) minus an epsilon.
- { { 106.0f, 212.0f, 323.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
- // Test: Input exactly 2x above minimum bounds.
- // When landing exactly on the 2x boundary, we return the exact minimum value again.
- { { 115.0f, 230.0f, 360.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input within 2nd mirror repeat above maximum bounds
- // The second mirror repeat should go forwards in values. We're above the maximum bounds, so the expected result
- // is (-4, -8, -17) with no epsilon.
- { { 116.0f, 232.0f, 363.0f }, { -4.0f, -8.0f, -17.0f }, false },
- // Test: Input exactly 2x above maximum bounds
- // When landing exactly on the 2x boundary, we return the maximum adjusted by the epsilon again.
- { { 125.0f, 250.0f, 400.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false }
- };
- for (auto& test : tests)
- {
- TestGradientTransform(setup, test);
- }
- }
- TEST_F(GradientSignalTransformTestsFixture, RepeatReturnsRepeatingValuesBasedOnShapeBounds)
- {
- /* Here's how the results are expected to work for various inputs when using Repeat wrapping.
- * This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
- * Inputs: Outputs:
- * ... ...
- * (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20)
- * (85, 170, 240) - (95, 190, 280) (-5, -10, -20) to (5, 10, 20)
- * (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
- * (105, 210, 320) - (115, 230, 360) (-5, -10, -20) to (5, 10, 20)
- * (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20)
- * ... ...
- * Every shape min/max boundary point below the starting point will have the max shape value.
- * Every shape min/max boundary point above the starting point with have the min shape value.
- */
- GradientTransformSetupData setup = { GradientSignal::WrappingType::Repeat };
- GradientTransformTestData tests[] = {
- // Test: 2x below minimum shape bounds
- // We're on a shape boundary below the minimum bounds, so it should return the maximum.
- { { 75.0f, 150.0f, 200.0f }, { 5.0f, 10.0f, 20.0f }, false },
- // Test: Input within 2nd repeat below minimum shape bounds
- // Every repeat should go forwards in values.
- { { 76.0f, 152.0f, 203.0f }, { -4.0f, -8.0f, -17.0f }, false },
- // Test: 1x below minimum shape bounds
- // We're on a shape boundary below the minimum bounds, so it should return the maximum.
- { { 85.0f, 170.0f, 240.0f }, { 5.0f, 10.0f, 20.0f }, false },
- // Test: Input within 1st repeat below minimum shape bounds
- // Every repeat should go forwards in values.
- { { 86.0f, 172.0f, 243.0f }, { -4.0f, -8.0f, -17.0f }, false },
- // Test: Input exactly on minimum shape bounds
- // This should return the actual minimum bounds.
- { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input inside shape bounds
- // This should return the mapped value.
- { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
- // Test: Input exactly on maximum shape bounds
- // We're on a shape boundary above the minimum bounds, so it should return the minimum.
- { { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input within 1st repeat above maximum shape bounds
- // Every repeat should go forwards in values.
- { { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
- // Test: 1x above maximum shape bounds
- // We're on a shape boundary above the minimum bounds, so it should return the minimum.
- { { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input within 2nd repeat above maximum shape bounds
- // Every repeat should go forwards in values.
- { { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
- };
- for (auto& test : tests)
- {
- TestGradientTransform(setup, test);
- }
- }
- TEST_F(GradientSignalTransformTestsFixture, ClampToZeroReturnsClampedValuesBasedOnShapeBounds)
- {
- GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToZero };
- GradientTransformTestData tests[] = {
- // Test: Input point far below minimum shape bounds
- // Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
- // Points outside the shape bounds should return "true" for rejected.
- { { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, true },
- // Test: Input point directly on minimum shape bounds
- // Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
- { { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
- // Test: Input point inside shape bounds
- // Our input point is inside the shape bounds, so the result is just input - translation.
- { { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
- // Test: Input point directly on maximum shape bounds
- // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
- // expected results are the max shape corner - epsilon.
- // Points outside the shape bounds (which includes the maximum edge of the shape bounds) should return "true" for rejected.
- { { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
- // Test: Input point far above maximum shape bounds
- // On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
- // expected results are the max shape corner - epsilon.
- // Points outside the shape bounds should return "true" for rejected.
- { { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
- };
- for (auto& test : tests)
- {
- TestGradientTransform(setup, test);
- }
- }
- }
|