| /* |
| * 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)) |