blob: 2b978315fa5a64eba2b51467e67007744f24a4aa [file] [log] [blame] [edit]
/*
* Copyright 2022 Rive
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/math/hit_test.hpp"
using namespace rivegm;
using V2D = rive::Vec2D;
class IPathSink
{
public:
virtual ~IPathSink() {}
virtual void move(V2D) = 0;
virtual void line(V2D) = 0;
virtual void cubic(V2D, V2D, V2D) = 0;
virtual void close() = 0;
};
class RenderPathSink : public IPathSink
{
rive::RenderPath* m_Path;
public:
RenderPathSink(rive::RenderPath* rp) : m_Path(rp) {}
void move(V2D v) override { m_Path->move(v); }
void line(V2D v) override { m_Path->line(v); }
void cubic(V2D a, V2D b, V2D c) override { m_Path->cubic(a, b, c); }
void close() override { m_Path->close(); }
};
class HitTesterSink : public IPathSink
{
rive::HitTester* m_HT;
public:
HitTesterSink(rive::HitTester* ht) : m_HT(ht) {}
void move(V2D v) override { m_HT->move(v); }
void line(V2D v) override { m_HT->line(v); }
void cubic(V2D a, V2D b, V2D c) override { m_HT->cubic(a, b, c); }
void close() override { m_HT->close(); }
};
class BounderSink : public IPathSink
{
rive::AABB m_Bounds;
bool m_First = true;
void join(V2D v)
{
assert(!m_First);
m_Bounds.minX = std::min(m_Bounds.minX, v.x);
m_Bounds.minY = std::min(m_Bounds.minY, v.y);
m_Bounds.maxX = std::max(m_Bounds.maxX, v.x);
m_Bounds.maxY = std::max(m_Bounds.maxY, v.y);
}
public:
BounderSink() {}
rive::AABB bounds() const
{
assert(!m_First);
return m_Bounds;
}
void move(V2D v) override
{
if (m_First)
{
m_Bounds.minX = m_Bounds.maxX = v.x;
m_Bounds.minY = m_Bounds.maxY = v.y;
m_First = false;
}
else
{
this->join(v);
}
}
void line(V2D v) override { this->join(v); }
void cubic(V2D a, V2D b, V2D c) override
{
this->join(a);
this->join(b);
this->join(c);
}
void close() override {}
};
class HitTestGM : public GM
{
rive::FillRule m_FillRule;
public:
HitTestGM(rive::FillRule fr) : GM(320, 460), m_FillRule(fr) {}
void onDraw(rive::Renderer* renderer) override
{
auto make = [](IPathSink& sink) {
sink.move({100, 10});
sink.line({200, 100});
sink.line({50, 230});
sink.close();
sink.move({120, 100});
sink.line({300, 40});
sink.line({250, 200});
sink.close();
sink.move({10, 300});
sink.cubic({110, 150}, {210, 450}, {310, 300});
sink.close();
};
Path path;
path->fillRule(m_FillRule);
{
RenderPathSink rps(path.get());
make(rps);
}
BounderSink bounder;
make(bounder);
const int tol = 4;
const auto bounds = bounder.bounds().round().inset(-tol, -tol);
Paint paint;
paint->color(0xFFDDDDDD);
draw_rect(renderer, bounds, paint.get());
paint->color(0xFFFF0000);
renderer->drawPath(path.get(), paint.get());
rive::HitTester tester;
HitTesterSink hts(&tester);
paint->color(0x800000FF);
for (int y = bounds.top; y < bounds.bottom; ++y)
{
for (int x = bounds.left; x < bounds.right; ++x)
{
const int t = 1;
rive::IAABB r = {x, y, x + 1, y + 1};
tester.reset(r.inset(-t, -t));
make(hts);
if (tester.test(m_FillRule))
{
draw_rect(renderer, r, paint.get());
}
}
}
}
};
GMREGISTER(hittest_evenOdd, return new HitTestGM(rive::FillRule::evenOdd))
GMREGISTER(hittest_nonZero, return new HitTestGM(rive::FillRule::nonZero))