presentation/GraphGeometry.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QColor> | ||
| 4 | #include <QHash> | ||
| 5 | #include <QPainter> | ||
| 6 | #include <QList> | ||
| 7 | #include <QPointF> | ||
| 8 | #include <QRectF> | ||
| 9 | |||
| 10 | #include <vector> | ||
| 11 | |||
| 12 | // Sprint 105 S105.4.2 (D-6): the relationship graph's lines and family areas | ||
| 13 | // as triangles for the scene graph, in screen pixels — widths, dashes, | ||
| 14 | // arrowheads and rings keep their pixel size at every zoom, and a zoom or a | ||
| 15 | // pan only rebuilds these vertices (GraphLinesItem, GraphAreasItem). Every | ||
| 16 | // edge is feathered over one pixel (alpha), which is the antialiasing. Pure. | ||
| 17 | namespace GraphGeometry { | ||
| 18 | |||
| 19 | // The layout of QSGGeometry::defaultAttributes_ColoredPoint2D (premultiplied). | ||
| 20 | struct Vertex { | ||
| 21 | float x = 0; | ||
| 22 | float y = 0; | ||
| 23 | unsigned char r = 0; | ||
| 24 | unsigned char g = 0; | ||
| 25 | unsigned char b = 0; | ||
| 26 | unsigned char a = 0; | ||
| 27 | }; | ||
| 28 | |||
| 29 | // Where no line is drawn: the names on the canvas and the signs of couples. | ||
| 30 | // A line is gone within Clear px of a box and fades back in up to Fade px | ||
| 31 | // (the Canvas version erased the boxes in three growing steps). | ||
| 32 | class Cutouts { | ||
| 33 | public: | ||
| 34 | static constexpr double Clear = 2; | ||
| 35 | static constexpr double Fade = 9; | ||
| 36 | void clear(); | ||
| 37 | void add(const QRectF &box); | ||
| 38 | 7141 | bool isEmpty() const { return m_boxes.isEmpty(); } | |
| 39 | 2716165 | const QList<QRectF> &boxes() const { return m_boxes; } | |
| 40 | // 0 inside a box or within Clear, 1 from Fade px away, smooth between. | ||
| 41 | double alphaAt(QPointF point) const; | ||
| 42 | // Whether a segment passes within Fade of a box (it then needs vertices | ||
| 43 | // close enough to carry the fade). | ||
| 44 | bool near(QPointF a, QPointF b) const; | ||
| 45 | // The boxes registered in the cells the segment a→b crosses (each once): | ||
| 46 | // every box a point of the segment can be near. | ||
| 47 | QList<int> candidates(QPointF a, QPointF b) const; | ||
| 48 | void candidates(QPointF a, QPointF b, QList<int> *found) const; | ||
| 49 | // The parts of the segment a→b (t in 0…1, merged, in order) that pass | ||
| 50 | // within Fade of a box: only there are extra vertices needed. | ||
| 51 | QList<QPair<double, double>> nearParts(QPointF a, QPointF b) const; | ||
| 52 | // The same for boxes already known to be the segment's candidates. | ||
| 53 | QList<QPair<double, double>> nearParts(QPointF a, QPointF b, const QList<int> &boxes) const; | ||
| 54 | // alphaAt() asking only these boxes (a segment's candidates). | ||
| 55 | double alphaAmong(QPointF point, const QList<int> &boxes) const; | ||
| 56 | |||
| 57 | private: | ||
| 58 | // Boxes by grid cell (Cell px, the boxes grown by Fade): a point asks only | ||
| 59 | // the boxes of its own cell. Built on the first question after a change. | ||
| 60 | static constexpr double Cell = 48; | ||
| 61 | void index() const; | ||
| 62 | |||
| 63 | 141213 | static quint64 keyOf(int column, int row) { return (quint64(quint32(column)) << 32) | quint32(row); } | |
| 64 | QList<QRectF> m_boxes; | ||
| 65 | mutable QHash<quint64, QList<int>> m_cells; | ||
| 66 | mutable bool m_indexed = false; | ||
| 67 | }; | ||
| 68 | |||
| 69 | // What the graph items draw with: the scene graph's triangles (Mesh) or, | ||
| 70 | // on Qt Quick's software renderer, which draws no custom geometry, a | ||
| 71 | // QPainter into an image (PainterSink). | ||
| 72 | class Sink { | ||
| 73 | public: | ||
| 74 | 141 | virtual ~Sink() = default; | |
| 75 | // Only what reaches into `viewport` is drawn (empty = everything). | ||
| 76 | 136 | void setViewport(const QRectF &viewport) { m_viewport = viewport; } | |
| 77 | // A polyline `width` px wide; `dash` alternates on/off lengths in px | ||
| 78 | // (empty: solid); `cuts` may be null. A closed outline repeats no point. | ||
| 79 | virtual void stroke(const QList<QPointF> &points, double width, const QList<double> &dash, const QColor &colour, | ||
| 80 | double alpha, const Cutouts *cuts, bool closed = false) = 0; | ||
| 81 | // Triangles (three points each): fills of large areas. | ||
| 82 | virtual void fill(const QList<QPointF> &triangles, const QColor &colour, double alpha) = 0; | ||
| 83 | // A filled triangle (an arrowhead); `cuts` as for a stroke. | ||
| 84 | virtual void triangle(QPointF a, QPointF b, QPointF c, const QColor &colour, double alpha, | ||
| 85 | const Cutouts *cuts = nullptr) = 0; | ||
| 86 | // A band between two polylines of equal length (outer[i] ↔ inner[i]) with | ||
| 87 | // alpha from `outerAlpha` to `innerAlpha`: the glow inside a border. | ||
| 88 | virtual void band(const QList<QPointF> &outer, const QList<QPointF> &inner, const QColor &colour, double outerAlpha, | ||
| 89 | double innerAlpha, bool closed) = 0; | ||
| 90 | |||
| 91 | protected: | ||
| 92 | QRectF m_viewport; | ||
| 93 | }; | ||
| 94 | |||
| 95 | // Indexed triangles: the cross-sections of a line are shared by its | ||
| 96 | // consecutive segments (four vertices per point, 18 indices per segment). | ||
| 97 | class Mesh : public Sink { | ||
| 98 | public: | ||
| 99 | void clear() { | ||
| 100 | m_vertices.clear(); | ||
| 101 | m_indices.clear(); | ||
| 102 | } | ||
| 103 | 14 | const std::vector<Vertex> &vertices() const { return m_vertices; } | |
| 104 | 2 | const std::vector<quint32> &indices() const { return m_indices; } | |
| 105 | void stroke(const QList<QPointF> &points, double width, const QList<double> &dash, const QColor &colour, | ||
| 106 | double alpha, const Cutouts *cuts, bool closed = false) override; | ||
| 107 | // Not feathered. | ||
| 108 | void fill(const QList<QPointF> &triangles, const QColor &colour, double alpha) override; | ||
| 109 | // Feathered over one pixel. | ||
| 110 | void triangle(QPointF a, QPointF b, QPointF c, const QColor &colour, double alpha, | ||
| 111 | const Cutouts *cuts = nullptr) override; | ||
| 112 | void band(const QList<QPointF> &outer, const QList<QPointF> &inner, const QColor &colour, double outerAlpha, | ||
| 113 | double innerAlpha, bool closed) override; | ||
| 114 | |||
| 115 | private: | ||
| 116 | void emitStrip(const QPointF *points, const double *cutAlpha, int n, double width, const QColor &colour, double alpha, | ||
| 117 | bool closed); | ||
| 118 | // A vertex; its index is returned (not yet used by a triangle). | ||
| 119 | quint32 vertex(QPointF point, const QColor &colour, double alpha); | ||
| 120 | // A vertex used once, in the order of the triangles. | ||
| 121 | void push(QPointF point, const QColor &colour, double alpha); | ||
| 122 | std::vector<Vertex> m_vertices; | ||
| 123 | std::vector<quint32> m_indices; | ||
| 124 | // Scratch buffers of stroke()/emitStrip(), kept between lines. | ||
| 125 | std::vector<QPointF> m_line, m_dense, m_dash, m_normals, m_miters; | ||
| 126 | std::vector<double> m_denseAlpha, m_dashAlpha, m_stretch; | ||
| 127 | std::vector<qint64> m_first; | ||
| 128 | QList<int> m_boxes, m_local; | ||
| 129 | QRgb m_colourKey = 0; | ||
| 130 | bool m_colourValid = false; | ||
| 131 | float m_red = 0, m_green = 0, m_blue = 0, m_alpha = 0; | ||
| 132 | }; | ||
| 133 | |||
| 134 | // The same drawing with a QPainter (antialiased strokes, dashes by the pen; | ||
| 135 | // the cut-outs are clipped out without the fade). | ||
| 136 | class PainterSink : public Sink { | ||
| 137 | public: | ||
| 138 | 129 | explicit PainterSink(QPainter *painter) : m_painter(painter) {} | |
| 139 | void stroke(const QList<QPointF> &points, double width, const QList<double> &dash, const QColor &colour, | ||
| 140 | double alpha, const Cutouts *cuts, bool closed = false) override; | ||
| 141 | void fill(const QList<QPointF> &triangles, const QColor &colour, double alpha) override; | ||
| 142 | void triangle(QPointF a, QPointF b, QPointF c, const QColor &colour, double alpha, | ||
| 143 | const Cutouts *cuts = nullptr) override; | ||
| 144 | void band(const QList<QPointF> &outer, const QList<QPointF> &inner, const QColor &colour, double outerAlpha, | ||
| 145 | double innerAlpha, bool closed) override; | ||
| 146 | |||
| 147 | private: | ||
| 148 | void clipFor(const Cutouts *cuts); | ||
| 149 | QPainter *m_painter; | ||
| 150 | bool m_clipSet = false; | ||
| 151 | const Cutouts *m_clipped = nullptr; | ||
| 152 | qsizetype m_clippedBoxes = -1; | ||
| 153 | }; | ||
| 154 | |||
| 155 | // A quadratic curve a → (control c) → b as points, at most `tolerance` px | ||
| 156 | // off the true curve (a included, b included). | ||
| 157 | QList<QPointF> quadratic(QPointF a, QPointF c, QPointF b, double tolerance = 0.5); | ||
| 158 | // A polyline with its corners rounded (radius px, at most half of either | ||
| 159 | // segment), as the Canvas's arcTo drew the family tree's elbows. | ||
| 160 | QList<QPointF> rounded(const QList<QPointF> &points, double radius); | ||
| 161 | // Inserts points so no segment is longer than `step` (for fades along it). | ||
| 162 | QList<QPointF> densified(const QList<QPointF> &points, double step, bool closed = false); | ||
| 163 | // The point halfway along a polyline and the polyline's length. | ||
| 164 | QPointF middle(const QList<QPointF> &points, double *length = nullptr); | ||
| 165 | // A closed loop with fewer points, none further than `tolerance` from the | ||
| 166 | // original outline (Douglas–Peucker on the two halves of the loop). | ||
| 167 | QList<QPointF> simplified(const QList<QPointF> &loop, double tolerance); | ||
| 168 | // The even-odd region of `loops` (outlines and holes in any orientation, | ||
| 169 | // not crossing each other) as triangles, three points each. | ||
| 170 | QList<QPointF> triangulate(const QList<QList<QPointF>> &loops); | ||
| 171 | // Signed area (positive: counter-clockwise in a y-up frame). | ||
| 172 | double signedArea(const QList<QPointF> &loop); | ||
| 173 | bool contains(const QList<QPointF> &loop, QPointF point); | ||
| 174 | |||
| 175 | } // namespace GraphGeometry | ||
| 176 |