| #include "rive/math/rectangles_to_contour.hpp" | |
| #include <algorithm> | |
| #include <unordered_map> | |
| using namespace rive; | |
| struct RectEvent | |
| { | |
| int index = 0; | |
| float size = 0; | |
| int type = 0; | |
| float x = 0; | |
| float y = 0; | |
| float getValue(int axis) const { return axis == 0 ? x : y; } | |
| }; | |
| std::vector<Rect> subdivideRectangles(const std::vector<Rect>& rects) | |
| { | |
| if (rects.empty()) | |
| { | |
| return rects; | |
| } | |
| std::vector<std::vector<std::vector<float>>> vrects; | |
| for (const auto& rect : rects) | |
| { | |
| vrects.push_back({{rect.left, rect.top}, {rect.right, rect.bottom}}); | |
| } | |
| auto sortEvents = [&](int axisA, int axisB) -> std::vector<RectEvent> { | |
| std::vector<RectEvent> result; | |
| int index = 0; | |
| for (const auto& rect : vrects) | |
| { | |
| int mindex = 0; | |
| for (const auto& e : rect) | |
| { | |
| RectEvent event; | |
| event.type = mindex; | |
| event.index = index; | |
| event.y = axisB == 0 ? e[axisA] : e[axisB]; | |
| event.x = axisB == 0 ? e[axisB] : e[axisA]; | |
| event.size = mindex++ == 0 ? rect[1][axisB] - e[axisB] | |
| : e[axisB] - rect[0][axisB]; | |
| result.push_back(event); | |
| } | |
| ++index; | |
| } | |
| std::sort(result.begin(), | |
| result.end(), | |
| [&](const RectEvent& a, const RectEvent& b) { | |
| return a.getValue(axisB) < b.getValue(axisB); | |
| }); | |
| std::sort(result.begin(), | |
| result.end(), | |
| [&](const RectEvent& a, const RectEvent& b) { | |
| return a.getValue(axisA) < b.getValue(axisA); | |
| }); | |
| return result; | |
| }; | |
| auto ev = sortEvents(0, 1); | |
| auto eh = sortEvents(1, 0); | |
| std::vector<bool> included(vrects.size(), false); | |
| included[ev[0].index] = true; | |
| int opened = 0; | |
| float beginY = 0; | |
| std::vector<Rect> result; | |
| for (size_t i = 0; i < ev.size() - 1; ++i) | |
| { | |
| const auto& eventV = ev[i]; | |
| included[eventV.index] = (eventV.type == 0); | |
| const auto& next = ev[i + 1]; | |
| float beginX = eventV.x; | |
| float endX = next.x; | |
| float deltaX = next.x - eventV.x; | |
| if (deltaX == 0) | |
| { | |
| continue; | |
| } | |
| for (size_t j = 0; j < eh.size(); ++j) | |
| { | |
| const auto& eventH = eh[j]; | |
| if (included[eventH.index]) | |
| { | |
| if (eventH.type == 0) | |
| { | |
| ++opened; | |
| if (opened == 1) | |
| { | |
| beginY = eventH.y; | |
| } | |
| } | |
| else | |
| { | |
| --opened; | |
| size_t n = 1; | |
| while (j + n < eh.size() && !included[eh[j + n].index]) | |
| { | |
| ++n; | |
| } | |
| const auto* next = | |
| (j + n < eh.size()) ? &eh[j + n] : nullptr; | |
| if (!next || (opened == 0 && next->y != eventH.y)) | |
| { | |
| float endY = eventH.y; | |
| result.push_back( | |
| Rect::fromLTRB(beginX, beginY, endX, endY)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| void RectanglesToContour::addRect(const Rect& rect) | |
| { | |
| if (!rects.empty()) | |
| { | |
| auto& last = rects.back(); | |
| if (last.top == rect.top && last.bottom == rect.bottom && | |
| last.right == rect.left) | |
| { | |
| rects.pop_back(); | |
| rects.emplace_back( | |
| Rect::fromLTRB(last.left, last.top, rect.right, last.bottom)); | |
| return; | |
| } | |
| } | |
| rects.push_back(rect); | |
| } | |
| template <typename Compare> | |
| std::vector<Vec2D> sortPoints(const std::unordered_set<Vec2D>& points, | |
| Compare comp) | |
| { | |
| std::vector<Vec2D> sortedPoints(points.begin(), points.end()); | |
| std::sort(sortedPoints.begin(), sortedPoints.end(), comp); | |
| return sortedPoints; | |
| } | |
| std::vector<std::vector<Vec2D>> extractPolygons( | |
| std::unordered_map<Vec2D, Vec2D>& edgesH, | |
| std::unordered_map<Vec2D, Vec2D>& edgesV) | |
| { | |
| std::vector<std::vector<Vec2D>> polygons; | |
| while (!edgesH.empty()) | |
| { | |
| Vec2D start = edgesH.begin()->first; | |
| edgesH.erase(start); | |
| std::vector<PolygonPoint> polygon = {PolygonPoint(start, 0)}; | |
| while (true) | |
| { | |
| auto& curr = polygon.back(); | |
| if (curr.dir == 0) | |
| { | |
| if (edgesV.find(curr.vec) != edgesV.end()) | |
| { | |
| auto next = edgesV[curr.vec]; | |
| edgesV.erase(curr.vec); | |
| polygon.emplace_back(next, 1); | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| if (edgesH.find(curr.vec) != edgesH.end()) | |
| { | |
| auto next = edgesH[curr.vec]; | |
| edgesH.erase(curr.vec); | |
| polygon.emplace_back(next, 0); | |
| } | |
| else | |
| { | |
| break; | |
| } | |
| } | |
| if (polygon.front() == polygon.back()) | |
| { | |
| polygon.pop_back(); | |
| break; | |
| } | |
| } | |
| std::vector<Vec2D> poly; | |
| std::transform(polygon.begin(), | |
| polygon.end(), | |
| std::back_inserter(poly), | |
| [](const PolygonPoint& pp) { return pp.vec; }); | |
| polygons.push_back(poly); | |
| for (const Vec2D& vertex : poly) | |
| { | |
| edgesH.erase(vertex); | |
| edgesV.erase(vertex); | |
| } | |
| } | |
| return polygons; | |
| } | |
| std::vector<std::vector<Vec2D>> RectanglesToContour::computeContours() | |
| { | |
| auto subdividedRects = subdivideRectangles(rects); | |
| for (const auto& rect : subdividedRects) | |
| { | |
| addUniquePoint(Vec2D(rect.left, rect.top)); | |
| addUniquePoint(Vec2D(rect.right, rect.top)); | |
| addUniquePoint(Vec2D(rect.right, rect.bottom)); | |
| addUniquePoint(Vec2D(rect.left, rect.bottom)); | |
| } | |
| auto sortX = sortPoints(uniquePoints, [](const Vec2D& a, const Vec2D& b) { | |
| return a.x < b.x || (a.x == b.x && a.y < b.y); | |
| }); | |
| auto sortY = sortPoints(uniquePoints, [](const Vec2D& a, const Vec2D& b) { | |
| return a.y < b.y || (a.y == b.y && a.x < b.x); | |
| }); | |
| std::unordered_map<Vec2D, Vec2D> edgesH, edgesV; | |
| size_t i = 0; | |
| while (i < sortY.size()) | |
| { | |
| float currY = sortY[i].y; | |
| while (sortY[i].y == currY && i < sortY.size()) | |
| { | |
| edgesH[sortY[i]] = sortY[i + 1]; | |
| edgesH[sortY[i + 1]] = sortY[i]; | |
| i += 2; | |
| } | |
| } | |
| i = 0; | |
| while (i < sortX.size()) | |
| { | |
| float currX = sortX[i].x; | |
| while (sortX[i].x == currX && i < sortX.size()) | |
| { | |
| edgesV[sortX[i]] = sortX[i + 1]; | |
| edgesV[sortX[i + 1]] = sortX[i]; | |
| i += 2; | |
| } | |
| } | |
| return extractPolygons(edgesH, edgesV); | |
| } | |
| void RectanglesToContour::addUniquePoint(const Vec2D& point) | |
| { | |
| if (!uniquePoints.insert(point).second) | |
| { | |
| uniquePoints.erase(point); | |
| } | |
| } | |
| std::vector<std::vector<Vec2D>> RectanglesToContour::makeSelectionContours( | |
| const std::vector<Rect>& rects) | |
| { | |
| RectanglesToContour converter; | |
| // Add rectangles to the converter | |
| for (const auto& rect : rects) | |
| { | |
| converter.addRect(rect); | |
| } | |
| std::vector<std::vector<Vec2D>> renderContours; | |
| auto contours = converter.computeContours(); | |
| // Process the contours to create renderContours | |
| for (const auto& contour : contours) | |
| { | |
| if (contour.empty()) | |
| { | |
| continue; | |
| } | |
| std::vector<Vec2D> renderContour = {contour.front()}; | |
| for (auto it = std::next(contour.begin()); it != contour.end(); ++it) | |
| { | |
| if (renderContour.back() == *it) | |
| { | |
| continue; | |
| } | |
| renderContour.push_back(*it); | |
| } | |
| renderContours.push_back(renderContour); | |
| } | |
| return renderContours; | |
| } |