| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include <rive/math/contour_measure.hpp> |
| #include <rive/math/math_types.hpp> |
| #include <rive/math/raw_path.hpp> |
| #include <rive/math/vec2d.hpp> |
| |
| #include <catch.hpp> |
| #include <cstdio> |
| |
| using namespace rive; |
| |
| static bool nearly_eq(float a, float b, float tolerance) { |
| assert(tolerance >= 0); |
| const float diff = std::abs(a - b); |
| const float max = std::max(std::abs(a), std::abs(b)); |
| const float allowed = tolerance * max; |
| if (diff > allowed) { |
| printf("%g %g delta %g allowed %g\n", a, b, diff, allowed); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool nearly_eq(Vec2D a, Vec2D b, float tol) { |
| return nearly_eq(a.x, b.x, tol) && nearly_eq(a.y, b.y, tol); |
| } |
| |
| TEST_CASE("contour-basics", "[contourmeasure]") { |
| const float tol = 0.000001f; |
| |
| RawPath path; |
| ContourMeasureIter iter(path, false); |
| REQUIRE(iter.next() == nullptr); |
| |
| path.moveTo(1, 2); |
| iter.reset(path, false); |
| REQUIRE(iter.next() == nullptr); |
| |
| path.lineTo(4, 6); |
| iter.reset(path, false); |
| auto cm = iter.next(); |
| REQUIRE(cm); |
| REQUIRE(nearly_eq(cm->length(), 5, tol)); |
| REQUIRE(iter.next() == nullptr); |
| |
| // check the mid-points of a rectangle |
| |
| path = RawPath(); |
| const float w = 4, h = 6; |
| path.addRect({0, 0, w, h}, PathDirection::cw); |
| iter.reset(path, false); |
| cm = iter.next(); |
| REQUIRE(cm); |
| REQUIRE(nearly_eq(cm->length(), 2 * (w + h), tol)); |
| const float midDistances[] = { |
| w / 2, |
| w + h / 2, |
| w + h + w / 2, |
| w + h + w + h / 2, |
| }; |
| const ContourMeasure::PosTan midPoints[] = { |
| {{w / 2, 0}, {1, 0}}, |
| {{w, h / 2}, {0, 1}}, |
| {{w / 2, h}, {-1, 0}}, |
| {{0, h / 2}, {0, -1}}, |
| }; |
| for (int i = 0; i < 4; ++i) { |
| auto rec = cm->getPosTan(midDistances[i]); |
| REQUIRE(nearly_eq(rec.pos, midPoints[i].pos, tol)); |
| REQUIRE(nearly_eq(rec.tan, midPoints[i].tan, tol)); |
| } |
| REQUIRE(iter.next() == nullptr); |
| } |
| |
| TEST_CASE("multi-contours", "[contourmeasure]") { |
| const Vec2D pts[] = { |
| {0, 0}, |
| {3, 0}, |
| {3, 4}, |
| }; |
| auto span = Span(pts, sizeof(pts) / sizeof(pts[0])); |
| |
| // We expect 3 measurable contours out of this: 7, 16, 7 |
| // the others should be skipped since they are empty (len == 0) |
| |
| RawPath path; |
| path.addPoly(span, false); // len == 7 |
| |
| path.addPoly(span, true); // len == 12 |
| |
| // should be skipped (lengh == 0) |
| path.moveTo(0, 0); |
| |
| // should be skipped (lengh == 0) |
| path.moveTo(0, 0); |
| path.close(); |
| |
| // should be skipped (lengh == 0) |
| path.moveTo(0, 0); |
| path.lineTo(0, 0); |
| |
| // should be skipped (lengh == 0) |
| path.moveTo(0, 0); |
| path.lineTo(0, 0); |
| path.close(); |
| |
| path.addPoly(span, false); // len == 7 |
| |
| ContourMeasureIter iter(path, false); |
| auto cm = iter.next(); |
| REQUIRE(cm->length() == 7); |
| cm = iter.next(); |
| REQUIRE(cm->length() == 12); |
| cm = iter.next(); |
| REQUIRE(cm->length() == 7); |
| cm = iter.next(); |
| REQUIRE(!cm); |
| } |
| |
| TEST_CASE("contour-oval", "[contourmeasure]") { |
| const float tol = 0.0075f; |
| |
| const float r = 10; |
| RawPath path; |
| path.addOval({-r, -r, r, r}, PathDirection::cw); |
| ContourMeasureIter iter(path, false); |
| |
| auto cm = iter.next(); |
| REQUIRE(nearly_eq(cm->length(), 2 * r * math::PI, tol)); |
| REQUIRE(!iter.next()); |
| } |