blob: 35483665c6b1b17d624a70981d6930cbe16b674b [file] [log] [blame]
/*
* 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 "rive_file_reader.hpp"
#include "rive/animation/state_machine_instance.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);
REQUIRE(iter.next() == nullptr);
path.moveTo(1, 2);
iter.rewind(&path);
REQUIRE(iter.next() == nullptr);
path.lineTo(4, 6);
iter.rewind(&path);
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.rewind(&path);
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 = make_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);
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, tol);
auto cm = iter.next();
REQUIRE(nearly_eq(cm->length(), 2 * r * math::PI, tol));
REQUIRE(!iter.next());
}
TEST_CASE("bad contour", "[contourmeasure]")
{
auto file = ReadRiveFile("../../test/assets/zombie_skins.riv");
auto artboard = file->artboard()->instance();
REQUIRE(artboard != nullptr);
auto machine = artboard->defaultStateMachine();
machine->advanceAndApply(0.0f);
}
// NaN paths don't return contours.
TEST_CASE("nan-path", "[contourmeasure]")
{
RawPath path;
path.lineTo(1, 2);
path.cubicTo(3, 4, 5, 6, 7, 8);
path.cubicTo(9, 10, 11, 12, 13, 14);
path.cubicTo(15, 16, 17, 18, 19, 20);
{
ContourMeasureIter iter(&path);
auto cm = iter.next();
CHECK(cm != nullptr);
CHECK(std::isfinite(cm->length()));
CHECK(iter.next() == nullptr);
}
{
auto nan = std::numeric_limits<float>::quiet_NaN();
RawPath path_ = path.transform(Mat2D(nan, nan, nan, nan, nan, nan));
ContourMeasureIter iter(&path_);
CHECK(iter.next() == nullptr);
}
}
// Regression test for a crash found by fuzzing.
TEST_CASE("fuzz_issue_7295", "[MetricsPath]")
{
NoOpFactory factory;
RawPath innerPath;
innerPath.moveTo(.0f, -20.5f);
innerPath.cubicTo(11.3218384f, -20.5f, 20.5f, -11.3218384f, 20.5f, .0f);
innerPath.cubicTo(20.5f, 11.3218384f, 11.3218384f, 20.5f, .0f, 20.5f);
innerPath.cubicTo(-11.3218384f, 20.5f, -20.5f, 11.3218384f, -20.5f, .0f);
innerPath.cubicTo(-20.5f, -11.3218384f, -11.3218384f, -20.5f, .0f, -20.5f);
RawPath outerPath;
Mat2D transform(1.f, .0f, .0f, 1.f, -134217728.f, -134217728.f);
outerPath.addPath(innerPath, &transform);
auto contour = ContourMeasureIter(&outerPath).next();
RawPath result;
contour->getSegment(.0f, 168.389008f, &result, true);
CHECK(math::nearly_equal(contour->length(), 168.389008f));
}