Skip to content

[RFC] GraphicsView: general polygon to QPainterPath converter #2021

Open
@chgans

Description

@chgans

Proposal

I was poking around with General_polygon_set_2 and Arr_circle_segment_traits_2, and I wanted to display such polygons in a QGraphicsView. So i wrote a function that convert such polygons to QPainterPath. This way the polygons can easily be inserted into a QGraphicsScene using QGraphicsPathItem. This has been tested with extensive input data sets.

This function could be added to CGAL::Qt::Converter, after being made templatised on a Traits_2.

Would you be interested if i do a pull request?

Source Code

#include <boost/function_output_iterator.hpp>

#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Gps_circle_segment_traits_2.h>
#include <CGAL/General_polygon_set_2.h>

#include <QtGui/QPainterPath>

#include <QWidgets/QGraphicsScene>
#include <QWidgets/QGraphicsScene>
#include <QWidgets/QGraphicsView>

typedef CGAL::Exact_predicates_exact_constructions_kernel Kernel;
typedef CGAL::Gps_circle_segment_traits_2<Kernel>         Traits_2;
typedef CGAL::General_polygon_set_2<Traits_2>             Polygon_set_2;
typedef Traits_2::General_polygon_2                       Polygon_2;
typedef Traits_2::General_polygon_with_holes_2            Polygon_with_holes_2;
typedef Traits_2::Curve_2                                 Curve_2;
typedef Traits_2::X_monotone_curve_2                      X_monotone_curve_2;

static QPainterPath construct_path(const Polygon_2 &pgn)
{
    QPainterPath result;

    Q_ASSERT(pgn.orientation() == CGAL::CLOCKWISE ||
             pgn.orientation() == CGAL::COUNTERCLOCKWISE);

    // Degenerate polygon, ring.size() < 3
    if (pgn.orientation() == CGAL::ZERO) {
        qWarning() << "construct_path: Ignoring degenerated polygon";
        return  result;
    }

    const bool isClockwise = pgn.orientation() == CGAL::CLOCKWISE;

    Polygon_2::Curve_const_iterator current = pgn.curves_begin();
    Polygon_2::Curve_const_iterator end = pgn.curves_end();

    result.moveTo(CGAL::to_double(current->source().x()),
                  CGAL::to_double(current->source().y()));

    do {
        const Polygon_2::X_monotone_curve_2 &curve = *current;
        const auto &source = curve.source();
        const auto &target = curve.target();

        if (curve.is_linear()) {
            result.lineTo(QPointF(CGAL::to_double(target.x()),
                                  CGAL::to_double(target.y())));
        }
        else if (curve.is_circular()) {
            const auto bbox = curve.supporting_circle().bbox();
            const QRectF rect(QPointF(bbox.xmin(), bbox.ymin()),
                              QPointF(bbox.xmax(), bbox.ymax()));
            const auto center = curve.supporting_circle().center();
            const double asource = qAtan2(CGAL::to_double(source.y() - center.y()),
                                          CGAL::to_double(source.x() - center.x()));
            const double atarget = qAtan2(CGAL::to_double(target.y() - center.y()),
                                          CGAL::to_double(target.x() - center.x()));
            double aspan = atarget - asource;
            if (aspan < -CGAL_PI || (qFuzzyCompare(aspan, -CGAL_PI) && !isClockwise))
                aspan += 2.0*CGAL_PI;
            else if (aspan > CGAL_PI || (qFuzzyCompare(aspan, CGAL_PI) && isClockwise))
                aspan -= 2.0*CGAL_PI;
            result.arcTo(rect, qRadiansToDegrees(-asource), qRadiansToDegrees(-aspan));
        }
        else { // ?!?
            Q_UNREACHABLE();
        }
    } while (++current != end);

    return result;
}

Demo code

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QGraphicsScene scene;
    Polygon_set_2 S;

    S.join(construct_polygon(Circle_2(Point_2(0.5, 0.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(5.5, 0.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(5, 5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(1, 5), 1)));
    S.join(construct_polygon(Point_2(1, 0), Point_2(5, 0),
                             Point_2(5, 2), Point_2(1, 2)));
    S.join(construct_polygon(Point_2(1, 4), Point_2(5, 4),
                             Point_2(5, 6), Point_2(1, 6)));
    S.join(construct_polygon(Point_2(0, 1), Point_2(2, 1),
                             Point_2(2, 5), Point_2(0, 5)));
    S.join(construct_polygon(Point_2(4, 1), Point_2(6, 1),
                             Point_2(6, 5), Point_2(4, 5)));
    S.join(construct_polygon(Circle_2(Point_2(8, 2.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(9, 2.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(10, 2.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(9, 5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(3, 1.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(3, 4.5), 1)));
    S.join(construct_polygon(Circle_2(Point_2(1.5, 3), 1)));
    S.join(construct_polygon(Circle_2(Point_2(4.5, 3), 1)));

    // Invert the above and "capture" them into a box
    // This allow to test for CW polygons with various arc configuration
    S.complement();
    S.intersection(construct_polygon(Point_2(-1, -1), Point_2(13, -1),
                                     Point_2(13, 7), Point_2(-1, 7)));

    // Insert a copy of the first polygons, placed above the previous box
    // This allow to test for CCW polygons with various arc configuration
    S.join(construct_polygon(Circle_2(Point_2(0.5, 0.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(5.5, 0.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(5, 5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(1, 5+10), 1)));
    S.join(construct_polygon(Point_2(1, 0+10), Point_2(5, 0+10),
                             Point_2(5, 2+10), Point_2(1, 2+10)));
    S.join(construct_polygon(Point_2(1, 4+10), Point_2(5, 4+10),
                             Point_2(5, 6+10), Point_2(1, 6+10)));
    S.join(construct_polygon(Point_2(0, 1+10), Point_2(2, 1+10),
                             Point_2(2, 5+10), Point_2(0, 5+10)));
    S.join(construct_polygon(Point_2(4, 1+10), Point_2(6, 1+10),
                             Point_2(6, 5+10), Point_2(4, 5+10)));
    S.join(construct_polygon(Circle_2(Point_2(8, 2.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(9, 2.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(10, 2.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(9, 5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(3, 1.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(3, 4.5+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(1.5, 3+10), 1)));
    S.join(construct_polygon(Circle_2(Point_2(4.5, 3+10), 1)));

    // Insert the resulting polygons into the graphics scene
    S.polygons_with_holes(boost::make_function_output_iterator([&scene](const Polygon_with_holes_2 &pgn) {
        if (!pgn.is_unbounded())
            scene.addPath(construct_path(pgn.outer_boundary()), QPen(Qt::green, 0.0), Qt::darkGreen);

        Polygon_with_holes_2::Hole_const_iterator current = pgn.holes_begin();
        Polygon_with_holes_2::Hole_const_iterator end = pgn.holes_end();
        while (current != end) {
            scene.addPath(construct_path(*current), QPen(Qt::red, 0.0), Qt::darkRed);
            current++;
        }
    }));

    // And show the result
    QGraphicsView view;
    view.setBackgroundBrush(QColor("#073642"));
    view.setInteractive(true);
    view.setDragMode(QGraphicsView::ScrollHandDrag);
    scene.setSceneRect(scene.itemsBoundingRect());
    view.setScene(&scene);
    view.fitInView(scene.sceneRect(), Qt::KeepAspectRatio);
    view.scale(7, -7); // Revert Y-axis

    view.show();
    return app.exec();
}

Screenshot

screenshot_20170404_195601

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions