| #include <catch.hpp> |
| #include "rive/math/mat2d.hpp" |
| #include "rive/math/math_types.hpp" |
| |
| namespace rive |
| { |
| TEST_CASE("mapPoints", "[Mat2D]") |
| { |
| std::vector<Vec2D> testPts{{0, 0}, {1, 0}, {0, 1}, {-1, 0}, {0, -1}}; |
| for (int i = 0; i < 100; ++i) |
| { |
| testPts.push_back( |
| {static_cast<float>(rand() % 201 - 100), static_cast<float>(rand() % 201 - 100)}); |
| } |
| size_t n = testPts.size(); |
| std::vector<Vec2D> dstPts(n); |
| std::vector<Vec2D> expectedPts(n); |
| |
| auto checkMatrix = [&](Mat2D m) { |
| // Map a single point. |
| m.mapPoints(dstPts.data(), testPts.data() + 2, 1); |
| expectedPts[0] = m * testPts[2]; |
| CHECK(dstPts[0] == expectedPts[0]); |
| |
| // Map n - 1 points (ensures we test one even-length and one odd-length array). |
| m.mapPoints(dstPts.data(), testPts.data() + 1, n - 1); |
| for (size_t i = 0; i < n - 1; ++i) |
| expectedPts[i] = m * testPts[i + 1]; |
| CHECK(std::equal(dstPts.begin(), dstPts.end() - 1, expectedPts.begin())); |
| |
| // Map n points. |
| m.mapPoints(dstPts.data(), testPts.data(), n); |
| for (size_t i = 0; i < n; ++i) |
| expectedPts[i] = m * testPts[i]; |
| CHECK(dstPts == expectedPts); |
| }; |
| checkMatrix(Mat2D()); |
| checkMatrix(Mat2D(1, 0, 0, 1, 2, -3)); |
| checkMatrix(Mat2D(4, 0, 0, -5, 0, 0)); |
| checkMatrix(Mat2D(4, 0, 0, 5, -6, 7)); |
| checkMatrix(Mat2D(0, 8, 9, 0, 10, 11)); |
| checkMatrix(Mat2D(-12, -13, -14, -15, -16, -17)); |
| checkMatrix(Mat2D(18, 19, 20, 21, 22, 23)); |
| checkMatrix(Mat2D(-25, 26, 27, -28, 29, -30)); |
| } |
| |
| // Check Mat2D::findMaxScale. |
| TEST_CASE("findMaxScale", "[Mat2D]") |
| { |
| Mat2D identity; |
| // CHECK(1 == identity.getMinScale()); |
| CHECK(1 == identity.findMaxScale()); |
| // success = identity.getMinMaxScales(scales); |
| // CHECK(success && 1 == scales[0] && 1 == scales[1]); |
| |
| Mat2D scale = Mat2D::fromScale(2, 4); |
| // CHECK(2 == scale.getMinScale()); |
| CHECK(4 == scale.findMaxScale()); |
| // success = scale.getMinMaxScales(scales); |
| // CHECK(success && 2 == scales[0] && 4 == scales[1]); |
| |
| Mat2D transpose = Mat2D(0, |
| 3, |
| 6, |
| 0, |
| std::numeric_limits<float>::quiet_NaN(), |
| std::numeric_limits<float>::infinity()); |
| CHECK(transpose.findMaxScale() == 6); |
| |
| Mat2D rot90Scale = Mat2D::fromRotation(math::PI / 2); |
| rot90Scale = Mat2D::fromScale(1.f / 4, 1.f / 2) * rot90Scale; |
| // CHECK(1.f / 4 == rot90Scale.getMinScale()); |
| CHECK(1.f / 2 == rot90Scale.findMaxScale()); |
| // success = rot90Scale.getMinMaxScales(scales); |
| // CHECK(success && 1.f / 4 == scales[0] && 1.f / 2 == scales[1]); |
| |
| Mat2D rotate = Mat2D::fromRotation(128 * math::PI / 180); |
| // CHECK(math::nearly_equal(1, rotate.getMinScale(), math::EPSILON)); |
| CHECK(math::nearly_equal(1, rotate.findMaxScale(), math::EPSILON)); |
| // success = rotate.getMinMaxScales(scales); |
| // CHECK(success); |
| // CHECK(math::nearly_equal(1, scales[0], math::EPSILON)); |
| // CHECK(math::nearly_equal(1, scales[1], math::EPSILON)); |
| |
| Mat2D translate = Mat2D::fromTranslate(10, -5); |
| // CHECK(1 == translate.getMinScale()); |
| CHECK(1 == translate.findMaxScale()); |
| // success = translate.getMinMaxScales(scales); |
| // CHECK(success && 1 == scales[0] && 1 == scales[1]); |
| |
| // skbug.com/4718 |
| Mat2D big(2.39394089e+36f, |
| 3.9159619e+36f, |
| 8.85347779e+36f, |
| 1.44823453e+37f, |
| 9.26526204e+36f, |
| 1.51559342e+37f); |
| CHECK(big.findMaxScale() == 0); |
| |
| // // skbug.com/4718 |
| // Mat2D givingNegativeNearlyZeros(0.00436534f, |
| // 0.00358857f, |
| // 0.114138f, |
| // 0.0936228f, |
| // 0.37141f, |
| // -0.0174198f); |
| // success = givingNegativeNearlyZeros.getMinMaxScales(scales); |
| // CHECK(success && 0 == scales[0]); |
| |
| constexpr int kNumBaseMats = 4; |
| Mat2D baseMats[kNumBaseMats] = {scale, rot90Scale, rotate, translate}; |
| constexpr int kNumMats = 2 * kNumBaseMats; |
| Mat2D mats[kNumMats]; |
| for (size_t i = 0; i < kNumBaseMats; ++i) |
| { |
| mats[i] = baseMats[i]; |
| bool invertible = mats[i].invert(&mats[i + kNumBaseMats]); |
| REQUIRE(invertible); |
| } |
| srand(0); |
| for (int m = 0; m < 1000; ++m) |
| { |
| Mat2D mat; |
| for (int i = 0; i < 4; ++i) |
| { |
| int x = rand() % kNumMats; |
| mat = mats[x] * mat; |
| } |
| |
| // float minScale = mat.findMinScale(); |
| float maxScale = mat.findMaxScale(); |
| // REQUIRE(minScale >= 0); |
| REQUIRE(maxScale >= 0); |
| |
| // test a bunch of vectors. All should be scaled by between minScale and maxScale |
| // (modulo some error) and we should find a vector that is scaled by almost each. |
| static const float gVectorScaleTol = (105 * 1.f) / 100; |
| static const float gCloseScaleTol = (97 * 1.f) / 100; |
| float max = 0, min = std::numeric_limits<float>::max(); |
| constexpr int kNumVectors = 1000; |
| Vec2D vectors[kNumVectors]; |
| for (size_t i = 0; i < kNumVectors; ++i) |
| { |
| vectors[i].x = rand() * 2.f / static_cast<float>(RAND_MAX) - 1; |
| vectors[i].y = rand() * 2.f / static_cast<float>(RAND_MAX) - 1; |
| vectors[i] = vectors[i].normalized(); |
| vectors[i] = {mat[0] * vectors[i].x + mat[2] * vectors[i].y, |
| mat[1] * vectors[i].x + mat[3] * vectors[i].y}; |
| } |
| for (size_t i = 0; i < kNumVectors; ++i) |
| { |
| float d = vectors[i].length(); |
| REQUIRE(d / maxScale < gVectorScaleTol); |
| // REQUIRE(minScale / d < gVectorScaleTol); |
| if (max < d) |
| { |
| max = d; |
| } |
| if (min > d) |
| { |
| min = d; |
| } |
| } |
| REQUIRE(max / maxScale >= gCloseScaleTol); |
| // REQUIRE(minScale / min >= gCloseScaleTol); |
| } |
| } |
| |
| TEST_CASE("mapBoundingBox", "[Mat2D]") |
| { |
| const std::vector<Vec2D> testPts{{0, 0}, {1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {-1, -1}}; |
| std::vector<Vec2D> mappedPts(testPts.size()); |
| auto checkMatrix = [&](Mat2D m) { |
| // Check zero points. |
| AABB mappedBbox = m.mapBoundingBox(nullptr, 0); |
| CHECK(mappedBbox.left() == 0); |
| CHECK(mappedBbox.top() == 0); |
| CHECK(mappedBbox.right() == 0); |
| CHECK(mappedBbox.bottom() == 0); |
| |
| // Find a single point boinding box. |
| for (const Vec2D pt : testPts) |
| { |
| mappedBbox = m.mapBoundingBox(&pt, 1); |
| Vec2D mappedPt; |
| m.mapPoints(&mappedPt, &pt, 1); |
| CHECK(mappedBbox.left() == Approx(mappedPt.x)); |
| CHECK(mappedBbox.top() == Approx(mappedPt.y)); |
| CHECK(mappedBbox.right() == Approx(mappedPt.x)); |
| CHECK(mappedBbox.bottom() == Approx(mappedPt.y)); |
| } |
| |
| // Check n - 1 points (ensures we test one even-length and one odd-length array). |
| m.mapPoints(mappedPts.data(), testPts.data() + 1, testPts.size() - 1); |
| mappedBbox = m.mapBoundingBox(testPts.data() + 1, testPts.size() - 1); |
| AABB testBbox = {1e9f, 1e9f, -1e9f, -1e9f}; |
| for (size_t i = 0; i < testPts.size() - 1; ++i) |
| { |
| AABB::expandTo(testBbox, mappedPts[i]); |
| } |
| CHECK(mappedBbox.left() == Approx(testBbox.left())); |
| CHECK(mappedBbox.top() == Approx(testBbox.top())); |
| CHECK(mappedBbox.right() == Approx(testBbox.right())); |
| CHECK(mappedBbox.bottom() == Approx(testBbox.bottom())); |
| |
| // Check n points. |
| m.mapPoints(mappedPts.data(), testPts.data(), testPts.size()); |
| mappedBbox = m.mapBoundingBox(testPts.data(), testPts.size()); |
| testBbox = {1e9f, 1e9f, -1e9f, -1e9f}; |
| for (size_t i = 0; i < testPts.size(); ++i) |
| { |
| AABB::expandTo(testBbox, mappedPts[i]); |
| } |
| CHECK(mappedBbox.left() == Approx(testBbox.left())); |
| CHECK(mappedBbox.top() == Approx(testBbox.top())); |
| CHECK(mappedBbox.right() == Approx(testBbox.right())); |
| CHECK(mappedBbox.bottom() == Approx(testBbox.bottom())); |
| |
| // Check mapping of a bounding box. |
| Vec2D bboxPts[4] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}}; |
| AABB mappedBboxFromPts = m.mapBoundingBox(bboxPts, 4); |
| AABB mappedBboxFromAABB = m.mapBoundingBox(AABB{0, 0, 1, 1}); |
| CHECK(mappedBboxFromPts.left() == Approx(mappedBboxFromAABB.left())); |
| CHECK(mappedBboxFromPts.top() == Approx(mappedBboxFromAABB.top())); |
| CHECK(mappedBboxFromPts.right() == Approx(mappedBboxFromAABB.right())); |
| CHECK(mappedBboxFromPts.bottom() == Approx(mappedBboxFromAABB.bottom())); |
| }; |
| checkMatrix(Mat2D()); |
| checkMatrix(Mat2D(1, 0, 0, 1, 2, -3)); |
| checkMatrix(Mat2D(4, 0, 0, -5, 0, 0)); |
| checkMatrix(Mat2D(4, 0, 0, 5, -6, 7)); |
| checkMatrix(Mat2D(0, 8, 9, 0, 10, 11)); |
| checkMatrix(Mat2D(-12, -13, -14, -15, -16, -17)); |
| checkMatrix(Mat2D(18, 19, 20, 21, 22, 23)); |
| checkMatrix(Mat2D(-25, 26, 27, -28, 29, -30)); |
| |
| // Mapping empty or NaN points returns 0. |
| CHECK(Mat2D().mapBoundingBox(nullptr, 0) == AABB()); |
| auto nan = std::numeric_limits<float>::quiet_NaN(); |
| CHECK(Mat2D().mapBoundingBox(AABB{nan, nan, nan, nan}) == AABB()); |
| |
| // NaN values are otherwise ignored. |
| CHECK(Mat2D().mapBoundingBox(AABB{-1, -1, 1, 1}) == AABB{-1, -1, 1, 1}); |
| CHECK(Mat2D().mapBoundingBox(AABB{nan, -1, 1, 1}) == AABB{1, -1, 1, 1}); |
| CHECK(Mat2D().mapBoundingBox(AABB{-1, nan, 1, 1}) == AABB{-1, 1, 1, 1}); |
| CHECK(Mat2D().mapBoundingBox(AABB{-1, -1, nan, 1}) == AABB{-1, -1, -1, 1}); |
| CHECK(Mat2D().mapBoundingBox(AABB{-1, -1, 1, nan}) == AABB{-1, -1, 1, -1}); |
| |
| // When AABB::height() inf - inf, the result is nan. |
| auto inf = std::numeric_limits<float>::infinity(); |
| CHECK(Mat2D().mapBoundingBox(AABB{0, inf, 0, nan}).height() == 0); |
| CHECK(Mat2D().mapBoundingBox(AABB{0, -inf, 0, nan}).height() == 0); |
| CHECK(Mat2D().mapBoundingBox(AABB{inf, 0, nan, 0}).width() == 0); |
| CHECK(Mat2D().mapBoundingBox(AABB{-inf, 0, nan, 0}).width() == 0); |
| CHECK(Mat2D().mapBoundingBox(AABB{inf, 0, inf, 0}).width() == 0); |
| CHECK(Mat2D().mapBoundingBox(AABB{0, -inf, 0, -inf}).height() == 0); |
| } |
| } // namespace rive |