| #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; |
| } |