presentation/GraphGeometry.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "GraphGeometry.h" | ||
| 2 | |||
| 3 | #include <QLineF> | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <limits> | ||
| 7 | #include <cmath> | ||
| 8 | |||
| 9 | namespace GraphGeometry { | ||
| 10 | |||
| 11 | namespace { | ||
| 12 | |||
| 13 | 371338 | double smoothstep(double edge0, double edge1, double x) { | |
| 14 |
1/1✓ Branch 2 → 3 taken 371338 times.
|
371338 | const double t = std::clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); |
| 15 | 371338 | return t * t * (3 - 2 * t); | |
| 16 | } | ||
| 17 | |||
| 18 | 639121 | double cross(QPointF a, QPointF b) { return a.x() * b.y() - a.y() * b.x(); } | |
| 19 | 829964 | double dot(QPointF a, QPointF b) { return a.x() * b.x() + a.y() * b.y(); } | |
| 20 | 2094544 | double length(QPointF a) { return std::hypot(a.x(), a.y()); } | |
| 21 | |||
| 22 | 722307 | QPointF normalOf(QPointF a, QPointF b) { | |
| 23 | 722307 | const QPointF d = b - a; | |
| 24 | 722307 | const double l = length(d); | |
| 25 |
2/2✓ Branch 4 → 5 taken 722153 times.
✓ Branch 4 → 8 taken 154 times.
|
722307 | return l > 1e-9 ? QPointF(-d.y() / l, d.x() / l) : QPointF(0, 0); |
| 26 | } | ||
| 27 | |||
| 28 | // Consecutive points closer than 0.01 px say nothing about a direction. | ||
| 29 | 982 | QList<QPointF> withoutRepeats(const QList<QPointF> &points) { | |
| 30 | 982 | QList<QPointF> out; | |
| 31 |
1/1✓ Branch 3 → 4 taken 982 times.
|
982 | out.reserve(points.size()); |
| 32 |
2/2✓ Branch 19 → 6 taken 12652 times.
✓ Branch 19 → 20 taken 982 times.
|
13634 | for (const QPointF &point : points) |
| 33 |
7/7✓ Branch 8 → 9 taken 11670 times.
✓ Branch 8 → 13 taken 982 times.
✓ Branch 9 → 10 taken 11670 times.
✓ Branch 12 → 13 taken 11636 times.
✓ Branch 12 → 14 taken 34 times.
✓ Branch 15 → 16 taken 12618 times.
✓ Branch 15 → 17 taken 34 times.
|
12652 | if (out.isEmpty() || length(point - out.last()) > 0.01) |
| 34 |
1/1✓ Branch 16 → 17 taken 12618 times.
|
12618 | out.append(point); |
| 35 | 982 | return out; | |
| 36 | ✗ | } | |
| 37 | |||
| 38 | 1 | QRectF boundsOf(const QList<QPointF> &points) { | |
| 39 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
|
1 | if (points.isEmpty()) |
| 40 | ✗ | return {}; | |
| 41 | 1 | double left = points.first().x(), right = left, top = points.first().y(), bottom = top; | |
| 42 |
2/2✓ Branch 22 → 11 taken 3 times.
✓ Branch 22 → 23 taken 1 time.
|
4 | for (const QPointF &p : points) { |
| 43 | 3 | left = std::min(left, p.x()); | |
| 44 | 3 | right = std::max(right, p.x()); | |
| 45 | 3 | top = std::min(top, p.y()); | |
| 46 | 3 | bottom = std::max(bottom, p.y()); | |
| 47 | } | ||
| 48 | 1 | return QRectF(QPointF(left, top), QPointF(right, bottom)); | |
| 49 | } | ||
| 50 | |||
| 51 | } // namespace | ||
| 52 | |||
| 53 | ✗ | void Cutouts::clear() { | |
| 54 | ✗ | m_boxes.clear(); | |
| 55 | ✗ | m_cells.clear(); | |
| 56 | ✗ | m_indexed = false; | |
| 57 | ✗ | } | |
| 58 | |||
| 59 | 1644 | void Cutouts::add(const QRectF &box) { | |
| 60 |
3/6✓ Branch 3 → 4 taken 1644 times.
✗ Branch 3 → 7 not taken.
✓ Branch 5 → 6 taken 1644 times.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 1644 times.
✗ Branch 8 → 11 not taken.
|
1644 | if (box.width() > 0 && box.height() > 0) { |
| 61 | 1644 | m_boxes.append(box); | |
| 62 | 1644 | m_indexed = false; | |
| 63 | } | ||
| 64 | 1644 | } | |
| 65 | |||
| 66 | 24706 | void Cutouts::index() const { | |
| 67 |
2/2✓ Branch 2 → 3 taken 24703 times.
✓ Branch 2 → 4 taken 3 times.
|
24706 | if (m_indexed) |
| 68 | 24703 | return; | |
| 69 | 3 | m_cells.clear(); | |
| 70 |
2/2✓ Branch 23 → 6 taken 302 times.
✓ Branch 23 → 24 taken 3 times.
|
305 | for (int i = 0; i < m_boxes.size(); ++i) { |
| 71 | 302 | const QRectF reach = m_boxes.at(i).adjusted(-Fade, -Fade, Fade, Fade); | |
| 72 | 302 | const int left = int(std::floor(reach.left() / Cell)), right = int(std::floor(reach.right() / Cell)); | |
| 73 | 302 | const int top = int(std::floor(reach.top() / Cell)), bottom = int(std::floor(reach.bottom() / Cell)); | |
| 74 |
2/2✓ Branch 20 → 13 taken 847 times.
✓ Branch 20 → 21 taken 302 times.
|
1149 | for (int column = left; column <= right; ++column) |
| 75 |
2/2✓ Branch 18 → 14 taken 1514 times.
✓ Branch 18 → 19 taken 847 times.
|
2361 | for (int row = top; row <= bottom; ++row) |
| 76 |
2/2✓ Branch 15 → 16 taken 1514 times.
✓ Branch 16 → 17 taken 1514 times.
|
1514 | m_cells[keyOf(column, row)].append(i); |
| 77 | } | ||
| 78 | 3 | m_indexed = true; | |
| 79 | } | ||
| 80 | |||
| 81 | 5 | double Cutouts::alphaAt(QPointF point) const { | |
| 82 |
1/1✓ Branch 2 → 3 taken 5 times.
|
5 | index(); |
| 83 | 5 | const auto cell = m_cells.constFind(keyOf(int(std::floor(point.x() / Cell)), int(std::floor(point.y() / Cell)))); | |
| 84 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 5 times.
|
5 | if (cell == m_cells.cend()) |
| 85 | ✗ | return 1; | |
| 86 | 5 | double nearest = Fade; | |
| 87 |
2/2✓ Branch 35 → 14 taken 5 times.
✓ Branch 35 → 36 taken 3 times.
|
8 | for (int i : *cell) { |
| 88 | 5 | const QRectF &box = m_boxes.at(i); | |
| 89 |
1/1✓ Branch 20 → 21 taken 5 times.
|
5 | const double dx = std::max({box.left() - point.x(), 0.0, point.x() - box.right()}); |
| 90 |
2/2✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 4 times.
|
5 | if (dx >= nearest) |
| 91 | 1 | continue; | |
| 92 |
1/1✓ Branch 27 → 28 taken 4 times.
|
4 | const double dy = std::max({box.top() - point.y(), 0.0, point.y() - box.bottom()}); |
| 93 |
1/2✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 4 times.
|
4 | if (dy >= nearest) |
| 94 | ✗ | continue; | |
| 95 | 4 | nearest = std::min(nearest, std::hypot(dx, dy)); | |
| 96 |
2/2✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 33 taken 2 times.
|
4 | if (nearest <= Clear) |
| 97 | 2 | return 0; | |
| 98 | } | ||
| 99 |
1/1✓ Branch 36 → 37 taken 3 times.
|
3 | return smoothstep(Clear, Fade, nearest); |
| 100 | } | ||
| 101 | |||
| 102 | 4 | QList<int> Cutouts::candidates(QPointF a, QPointF b) const { | |
| 103 | 4 | QList<int> out; | |
| 104 |
1/1✓ Branch 2 → 3 taken 4 times.
|
4 | candidates(a, b, &out); |
| 105 | 4 | return out; | |
| 106 | ✗ | } | |
| 107 | |||
| 108 | 24701 | void Cutouts::candidates(QPointF a, QPointF b, QList<int> *found) const { | |
| 109 |
1/1✓ Branch 2 → 3 taken 24701 times.
|
24701 | index(); |
| 110 | 24701 | QList<int> &out = *found; | |
| 111 |
1/1✓ Branch 3 → 4 taken 24701 times.
|
24701 | out.clear(); |
| 112 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 24701 times.
|
24701 | if (m_cells.isEmpty()) |
| 113 | ✗ | return; | |
| 114 | // Every cell the segment crosses (Amanatides–Woo); a box is registered in | ||
| 115 | // every cell within Fade of it, so a point near a box lies in one of them. | ||
| 116 | 24701 | int column = int(std::floor(a.x() / Cell)), row = int(std::floor(a.y() / Cell)); | |
| 117 | 24701 | const int lastColumn = int(std::floor(b.x() / Cell)), lastRow = int(std::floor(b.y() / Cell)); | |
| 118 | 24701 | const QPointF d = b - a; | |
| 119 |
4/4✓ Branch 13 → 14 taken 10575 times.
✓ Branch 13 → 15 taken 14126 times.
✓ Branch 17 → 18 taken 11361 times.
✓ Branch 17 → 19 taken 13340 times.
|
24701 | const int stepX = d.x() > 0 ? 1 : -1, stepY = d.y() > 0 ? 1 : -1; |
| 120 | 24701 | const double inf = std::numeric_limits<double>::infinity(); | |
| 121 |
4/4✓ Branch 22 → 23 taken 21131 times.
✓ Branch 22 → 29 taken 3570 times.
✓ Branch 23 → 24 taken 10575 times.
✓ Branch 23 → 25 taken 10556 times.
|
24701 | double nextX = std::abs(d.x()) > 1e-12 ? ((column + (stepX > 0 ? 1 : 0)) * Cell - a.x()) / d.x() : inf; |
| 122 |
4/4✓ Branch 32 → 33 taken 21119 times.
✓ Branch 32 → 39 taken 3582 times.
✓ Branch 33 → 34 taken 11361 times.
✓ Branch 33 → 35 taken 9758 times.
|
24701 | double nextY = std::abs(d.y()) > 1e-12 ? ((row + (stepY > 0 ? 1 : 0)) * Cell - a.y()) / d.y() : inf; |
| 123 |
2/2✓ Branch 42 → 43 taken 21131 times.
✓ Branch 42 → 46 taken 3570 times.
|
24701 | const double deltaX = std::abs(d.x()) > 1e-12 ? Cell / std::abs(d.x()) : inf; |
| 124 |
2/2✓ Branch 49 → 50 taken 21119 times.
✓ Branch 49 → 53 taken 3582 times.
|
24701 | const double deltaY = std::abs(d.y()) > 1e-12 ? Cell / std::abs(d.y()) : inf; |
| 125 | // S106-944: the walk crosses |Δcolumn| + |Δrow| + 1 cells; a fixed cap of | ||
| 126 | // 4096 cut long segments short (their far-end labels got no fade). The | ||
| 127 | // bound stays as a guard against non-finite input. | ||
| 128 | 24701 | const qint64 cells = qint64(std::abs(qint64(lastColumn) - column)) + std::abs(qint64(lastRow) - row) + 1; | |
| 129 | 24701 | const qint64 limit = std::min<qint64>(cells, qint64(1) << 22); | |
| 130 |
1/2✓ Branch 85 → 58 taken 139694 times.
✗ Branch 85 → 86 not taken.
|
139694 | for (qint64 guard = 0; guard < limit; ++guard) { |
| 131 | 139694 | const auto cell = m_cells.constFind(keyOf(column, row)); | |
| 132 |
2/2✓ Branch 62 → 63 taken 122683 times.
✓ Branch 62 → 74 taken 17011 times.
|
139694 | if (cell != m_cells.cend()) |
| 133 |
2/2✓ Branch 72 → 66 taken 423165 times.
✓ Branch 72 → 73 taken 122683 times.
|
545848 | for (int i : *cell) |
| 134 |
2/2✓ Branch 68 → 69 taken 226560 times.
✓ Branch 68 → 70 taken 196605 times.
|
423165 | if (!out.contains(i)) |
| 135 |
1/1✓ Branch 69 → 70 taken 226560 times.
|
226560 | out.append(i); |
| 136 |
4/4✓ Branch 74 → 75 taken 46905 times.
✓ Branch 74 → 77 taken 92789 times.
✓ Branch 75 → 76 taken 24701 times.
✓ Branch 75 → 77 taken 22204 times.
|
139694 | if (column == lastColumn && row == lastRow) |
| 137 | 24701 | break; | |
| 138 |
2/2✓ Branch 77 → 78 taken 76080 times.
✓ Branch 77 → 79 taken 38913 times.
|
114993 | if (nextX < nextY) { |
| 139 | 76080 | column += stepX; | |
| 140 | 76080 | nextX += deltaX; | |
| 141 | } else { | ||
| 142 | 38913 | row += stepY; | |
| 143 | 38913 | nextY += deltaY; | |
| 144 | } | ||
| 145 |
3/4✓ Branch 80 → 81 taken 9 times.
✓ Branch 80 → 83 taken 114984 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 9 times.
|
114993 | if (nextX > 1 + deltaX && nextY > 1 + deltaY) |
| 146 | ✗ | break; | |
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | 3 | bool Cutouts::near(QPointF a, QPointF b) const { | |
| 151 | 3 | const QRectF reach = QRectF(a, b).normalized().adjusted(-Fade, -Fade, Fade, Fade); | |
| 152 |
5/5✓ Branch 5 → 6 taken 3 times.
✓ Branch 6 → 7 taken 3 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 16 → 9 taken 3 times.
✓ Branch 16 → 17 taken 1 time.
|
4 | for (int i : candidates(a, b)) |
| 153 |
2/2✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 14 taken 1 time.
|
3 | if (reach.intersects(m_boxes.at(i))) |
| 154 |
2/2✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 22 taken 2 times.
|
3 | return true; |
| 155 | 1 | return false; | |
| 156 | } | ||
| 157 | |||
| 158 | namespace { | ||
| 159 | // Liang–Barsky: the part [t0, t1] of a→b inside `rect`; false when none. | ||
| 160 | 333552 | bool clipTo(QPointF a, QPointF b, const QRectF &rect, double *t0, double *t1) { | |
| 161 | 333552 | const QPointF d = b - a; | |
| 162 | 333552 | *t0 = 0; | |
| 163 | 333552 | *t1 = 1; | |
| 164 | 333552 | const double p[4] = {-d.x(), d.x(), -d.y(), d.y()}; | |
| 165 | 333552 | const double q[4] = {a.x() - rect.left(), rect.right() - a.x(), a.y() - rect.top(), rect.bottom() - a.y()}; | |
| 166 |
2/2✓ Branch 31 → 16 taken 1162001 times.
✓ Branch 31 → 32 taken 183297 times.
|
1345298 | for (int k = 0; k < 4; ++k) { |
| 167 |
2/2✓ Branch 17 → 18 taken 249421 times.
✓ Branch 17 → 21 taken 912580 times.
|
1162001 | if (std::abs(p[k]) < 1e-12) { |
| 168 |
2/2✓ Branch 18 → 19 taken 50106 times.
✓ Branch 18 → 20 taken 199315 times.
|
249421 | if (q[k] < 0) |
| 169 | 150255 | return false; | |
| 170 | 199315 | continue; | |
| 171 | } | ||
| 172 | 912580 | const double r = q[k] / p[k]; | |
| 173 |
2/2✓ Branch 21 → 22 taken 457760 times.
✓ Branch 21 → 24 taken 454820 times.
|
912580 | if (p[k] < 0) |
| 174 | 457760 | *t0 = std::max(*t0, r); | |
| 175 | else | ||
| 176 | 454820 | *t1 = std::min(*t1, r); | |
| 177 |
2/2✓ Branch 26 → 27 taken 100149 times.
✓ Branch 26 → 28 taken 812431 times.
|
912580 | if (*t0 > *t1) |
| 178 | 100149 | return false; | |
| 179 | } | ||
| 180 | 183297 | return true; | |
| 181 | } | ||
| 182 | } // namespace | ||
| 183 | |||
| 184 | ✗ | QList<QPair<double, double>> Cutouts::nearParts(QPointF a, QPointF b) const { return nearParts(a, b, candidates(a, b)); } | |
| 185 | |||
| 186 | 700001 | double Cutouts::alphaAmong(QPointF point, const QList<int> &boxes) const { | |
| 187 | 700001 | double nearest = Fade; | |
| 188 |
2/2✓ Branch 25 → 4 taken 1215369 times.
✓ Branch 25 → 26 taken 371335 times.
|
1586704 | for (int i : boxes) { |
| 189 | 1215369 | const QRectF &box = m_boxes.at(i); | |
| 190 |
1/1✓ Branch 10 → 11 taken 1215369 times.
|
1215369 | const double dx = std::max({box.left() - point.x(), 0.0, point.x() - box.right()}); |
| 191 |
2/2✓ Branch 11 → 12 taken 252443 times.
✓ Branch 11 → 13 taken 962926 times.
|
1215369 | if (dx >= nearest) |
| 192 | 252443 | continue; | |
| 193 |
1/1✓ Branch 17 → 18 taken 962926 times.
|
962926 | const double dy = std::max({box.top() - point.y(), 0.0, point.y() - box.bottom()}); |
| 194 |
2/2✓ Branch 18 → 19 taken 202685 times.
✓ Branch 18 → 20 taken 760241 times.
|
962926 | if (dy >= nearest) |
| 195 | 202685 | continue; | |
| 196 | 760241 | nearest = std::min(nearest, std::hypot(dx, dy)); | |
| 197 |
2/2✓ Branch 21 → 22 taken 328666 times.
✓ Branch 21 → 23 taken 431575 times.
|
760241 | if (nearest <= Clear) |
| 198 | 328666 | return 0; | |
| 199 | } | ||
| 200 |
1/1✓ Branch 26 → 27 taken 371335 times.
|
371335 | return smoothstep(Clear, Fade, nearest); |
| 201 | } | ||
| 202 | |||
| 203 | 24137 | QList<QPair<double, double>> Cutouts::nearParts(QPointF a, QPointF b, const QList<int> &boxes) const { | |
| 204 | 24137 | QList<QPair<double, double>> parts; | |
| 205 |
2/2✓ Branch 24 → 4 taken 226556 times.
✓ Branch 24 → 25 taken 24137 times.
|
250693 | for (int i : boxes) { |
| 206 | 226556 | const QRectF &box = m_boxes.at(i); | |
| 207 | 226556 | double outer0 = 0, outer1 = 0, inner0 = 0, inner1 = 0; | |
| 208 |
3/3✓ Branch 7 → 8 taken 226556 times.
✓ Branch 8 → 9 taken 119560 times.
✓ Branch 8 → 10 taken 106996 times.
|
226556 | if (!clipTo(a, b, box.adjusted(-Fade, -Fade, Fade, Fade), &outer0, &outer1)) |
| 209 | 119560 | continue; | |
| 210 | // Within Clear of the box the line is gone: vertices only where it fades. | ||
| 211 |
3/3✓ Branch 11 → 12 taken 106996 times.
✓ Branch 12 → 13 taken 76301 times.
✓ Branch 12 → 18 taken 30695 times.
|
106996 | if (clipTo(a, b, box.adjusted(-Clear, -Clear, Clear, Clear), &inner0, &inner1)) { |
| 212 |
1/1✓ Branch 14 → 15 taken 76301 times.
|
76301 | parts.append({outer0, inner0}); |
| 213 |
1/1✓ Branch 16 → 17 taken 76301 times.
|
76301 | parts.append({inner1, outer1}); |
| 214 | } else { | ||
| 215 |
1/1✓ Branch 19 → 20 taken 30695 times.
|
30695 | parts.append({outer0, outer1}); |
| 216 | } | ||
| 217 | } | ||
| 218 |
3/3✓ Branch 25 → 26 taken 24137 times.
✓ Branch 26 → 27 taken 24137 times.
✓ Branch 27 → 28 taken 24137 times.
|
24137 | std::sort(parts.begin(), parts.end()); |
| 219 | 24137 | QList<QPair<double, double>> merged; | |
| 220 |
2/2✓ Branch 46 → 31 taken 183297 times.
✓ Branch 46 → 47 taken 24137 times.
|
207434 | for (const auto &part : std::as_const(parts)) { |
| 221 |
7/7✓ Branch 33 → 34 taken 163864 times.
✓ Branch 33 → 37 taken 19433 times.
✓ Branch 34 → 35 taken 163864 times.
✓ Branch 35 → 36 taken 71638 times.
✓ Branch 35 → 37 taken 92226 times.
✓ Branch 38 → 39 taken 71638 times.
✓ Branch 38 → 43 taken 111659 times.
|
183297 | if (!merged.isEmpty() && part.first <= merged.last().second) |
| 222 |
2/2✓ Branch 39 → 40 taken 71638 times.
✓ Branch 41 → 42 taken 71638 times.
|
71638 | merged.last().second = std::max(merged.last().second, part.second); |
| 223 | else | ||
| 224 |
1/1✓ Branch 43 → 44 taken 111659 times.
|
111659 | merged.append(part); |
| 225 | } | ||
| 226 | 24137 | return merged; | |
| 227 | 24137 | } | |
| 228 | |||
| 229 |
2/2✓ Branch 2 → 3 taken 3 times.
✓ Branch 3 → 4 taken 3 times.
|
3 | void Mesh::push(QPointF point, const QColor &colour, double alpha) { m_indices.push_back(vertex(point, colour, alpha)); } |
| 230 | |||
| 231 | 2089875 | quint32 Mesh::vertex(QPointF point, const QColor &colour, double alpha) { | |
| 232 | // The colour's channels are read once per colour, not per vertex. | ||
| 233 |
5/6✓ Branch 3 → 4 taken 2089864 times.
✓ Branch 3 → 5 taken 11 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 2089864 times.
✓ Branch 7 → 8 taken 11 times.
✓ Branch 7 → 14 taken 2089864 times.
|
2089875 | if (colour.rgba() != m_colourKey || !m_colourValid) { |
| 234 | 11 | m_colourKey = colour.rgba(); | |
| 235 | 11 | m_colourValid = true; | |
| 236 | 11 | m_red = float(colour.redF()); | |
| 237 | 11 | m_green = float(colour.greenF()); | |
| 238 | 11 | m_blue = float(colour.blueF()); | |
| 239 | 11 | m_alpha = float(colour.alphaF()); | |
| 240 | } | ||
| 241 | // S106-946: a NaN alpha (std::clamp keeps NaN, and NaN → unsigned char | ||
| 242 | // is undefined) draws nothing. | ||
| 243 | 2089875 | const float product = m_alpha * float(alpha); | |
| 244 |
3/3✓ Branch 15 → 16 taken 3 times.
✓ Branch 15 → 17 taken 2089872 times.
✓ Branch 17 → 18 taken 2089872 times.
|
2089875 | const float a = (std::isnan(product) ? 0.0f : std::clamp(product, 0.0f, 1.0f)) * 255.0f; |
| 245 | 2089875 | Vertex v; | |
| 246 | 2089875 | v.x = float(point.x()); | |
| 247 | 2089875 | v.y = float(point.y()); | |
| 248 | 2089875 | v.r = static_cast<unsigned char>(m_red * a + 0.5f); | |
| 249 | 2089875 | v.g = static_cast<unsigned char>(m_green * a + 0.5f); | |
| 250 | 2089875 | v.b = static_cast<unsigned char>(m_blue * a + 0.5f); | |
| 251 | 2089875 | v.a = static_cast<unsigned char>(a + 0.5f); | |
| 252 |
1/1✓ Branch 21 → 22 taken 2089875 times.
|
2089875 | m_vertices.push_back(v); |
| 253 | 4179750 | return quint32(m_vertices.size() - 1); | |
| 254 | } | ||
| 255 | |||
| 256 | 7144 | void Mesh::stroke(const QList<QPointF> &points, double width, const QList<double> &dash, const QColor &colour, | |
| 257 | double alpha, const Cutouts *cuts, bool closed) { | ||
| 258 |
2/4✓ Branch 2 → 3 taken 7144 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 7144 times.
|
7144 | if (alpha <= 0 || width <= 0) |
| 259 | 4763 | return; | |
| 260 | // The buffers are members, reused from line to line: a frame builds a | ||
| 261 | // thousand lines and their dashes without allocating for each. | ||
| 262 | 7144 | m_line.clear(); | |
| 263 |
2/2✓ Branch 21 → 8 taken 31844 times.
✓ Branch 21 → 22 taken 7144 times.
|
38988 | for (const QPointF &point : points) |
| 264 |
4/6✓ Branch 10 → 11 taken 24700 times.
✓ Branch 10 → 15 taken 7144 times.
✓ Branch 14 → 15 taken 24700 times.
✗ Branch 14 → 16 not taken.
✓ Branch 17 → 18 taken 31844 times.
✗ Branch 17 → 19 not taken.
|
31844 | if (m_line.empty() || length(point - m_line.back()) > 0.01) |
| 265 |
1/1✓ Branch 18 → 19 taken 31844 times.
|
31844 | m_line.push_back(point); |
| 266 |
1/2✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 7144 times.
|
7144 | if (m_line.size() < 2) |
| 267 | ✗ | return; | |
| 268 |
2/2✓ Branch 26 → 27 taken 7141 times.
✓ Branch 26 → 53 taken 3 times.
|
7144 | if (!m_viewport.isEmpty()) { |
| 269 | 7141 | double left = m_line.front().x(), right = left, top = m_line.front().y(), bottom = top; | |
| 270 |
2/2✓ Branch 44 → 33 taken 31838 times.
✓ Branch 44 → 45 taken 7141 times.
|
38979 | for (const QPointF &p : m_line) { |
| 271 | 31838 | left = std::min(left, p.x()); | |
| 272 | 31838 | right = std::max(right, p.x()); | |
| 273 | 31838 | top = std::min(top, p.y()); | |
| 274 | 31838 | bottom = std::max(bottom, p.y()); | |
| 275 | } | ||
| 276 |
2/2✓ Branch 50 → 51 taken 1 time.
✓ Branch 50 → 52 taken 7140 times.
|
7141 | if (!QRectF(QPointF(left, top), QPointF(right, bottom)).adjusted(-width - 2, -width - 2, width + 2, width + 2).intersects(m_viewport)) |
| 277 | 1 | return; | |
| 278 | } | ||
| 279 |
1/4✗ Branch 53 → 54 not taken.
✓ Branch 53 → 57 taken 7143 times.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
|
7143 | const bool solidLoop = closed && dash.isEmpty(); |
| 280 |
1/4✗ Branch 58 → 59 not taken.
✓ Branch 58 → 62 taken 7143 times.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 62 not taken.
|
7143 | if (closed && !solidLoop) |
| 281 | ✗ | m_line.push_back(m_line.front()); | |
| 282 | // Per point the cut alpha; near a box extra points 3 px apart carry the | ||
| 283 | // fade, each asking only the boxes the segment's cells hold. | ||
| 284 | 7143 | m_dense.clear(); | |
| 285 | 7143 | m_denseAlpha.clear(); | |
| 286 |
5/6✓ Branch 64 → 65 taken 7141 times.
✓ Branch 64 → 68 taken 2 times.
✓ Branch 66 → 67 taken 7141 times.
✗ Branch 66 → 68 not taken.
✓ Branch 69 → 70 taken 7141 times.
✓ Branch 69 → 138 taken 2 times.
|
7143 | if (cuts && !cuts->isEmpty()) { |
| 287 | 7141 | const int count = int(m_line.size()); | |
| 288 |
1/2✗ Branch 71 → 72 not taken.
✓ Branch 71 → 73 taken 7141 times.
|
7141 | const int last = solidLoop ? count : count - 1; |
| 289 |
2/2✓ Branch 134 → 75 taken 24697 times.
✓ Branch 134 → 135 taken 7141 times.
|
31838 | for (int i = 0; i < last; ++i) { |
| 290 | 24697 | const QPointF a = m_line[i], b = m_line[(i + 1) % count]; | |
| 291 |
1/1✓ Branch 77 → 78 taken 24697 times.
|
24697 | cuts->candidates(a, b, &m_boxes); |
| 292 | // The boxes that matter near a point or a stretch of the segment: a | ||
| 293 | // long segment's cells hold many boxes, a few pixels of it one or two. | ||
| 294 | 142923 | const auto local = [&](const QRectF &around) { | |
| 295 | 142923 | m_local.clear(); | |
| 296 |
2/2✓ Branch 14 → 6 taken 2714583 times.
✓ Branch 14 → 15 taken 142923 times.
|
2857506 | for (int box : std::as_const(m_boxes)) |
| 297 |
2/2✓ Branch 10 → 11 taken 230337 times.
✓ Branch 10 → 12 taken 2484246 times.
|
2714583 | if (around.intersects(cuts->boxes().at(box))) |
| 298 |
1/1✓ Branch 11 → 12 taken 230337 times.
|
230337 | m_local.append(box); |
| 299 |
2/2✓ Branch 16 → 17 taken 12637 times.
✓ Branch 16 → 18 taken 130286 times.
|
142923 | return m_local.isEmpty() ? 1.0 : -1.0; |
| 300 | 24697 | }; | |
| 301 | 24697 | const double fade = Cutouts::Fade; | |
| 302 | 31264 | const auto alphaNear = [&](QPointF p) { | |
| 303 |
4/4✓ Branch 5 → 6 taken 31264 times.
✓ Branch 6 → 7 taken 12077 times.
✓ Branch 6 → 8 taken 19187 times.
✓ Branch 8 → 9 taken 19187 times.
|
31264 | return local(QRectF(p.x() - fade, p.y() - fade, 2 * fade, 2 * fade)) > 0 ? 1.0 : cuts->alphaAmong(p, m_local); |
| 304 | 24697 | }; | |
| 305 |
2/2✓ Branch 78 → 79 taken 7141 times.
✓ Branch 78 → 87 taken 17556 times.
|
24697 | if (i == 0) { |
| 306 |
1/1✓ Branch 79 → 80 taken 7141 times.
|
7141 | m_dense.push_back(a); |
| 307 |
4/4✓ Branch 81 → 82 taken 14 times.
✓ Branch 81 → 83 taken 7127 times.
✓ Branch 83 → 84 taken 7127 times.
✓ Branch 85 → 86 taken 7141 times.
|
7141 | m_denseAlpha.push_back(m_boxes.isEmpty() ? 1.0 : alphaNear(a)); |
| 308 | } | ||
| 309 |
2/2✓ Branch 88 → 89 taken 24137 times.
✓ Branch 88 → 126 taken 560 times.
|
24697 | if (!m_boxes.isEmpty()) { |
| 310 | 24137 | const double segment = length(b - a); | |
| 311 |
5/5✓ Branch 91 → 92 taken 24137 times.
✓ Branch 92 → 93 taken 24137 times.
✓ Branch 93 → 94 taken 24137 times.
✓ Branch 123 → 95 taken 111659 times.
✓ Branch 123 → 124 taken 24137 times.
|
135796 | for (const auto &[from, to] : cuts->nearParts(a, b, m_boxes)) { |
| 312 | 111659 | const QPointF start = a + (b - a) * from, end = a + (b - a) * to; | |
| 313 |
1/1✓ Branch 107 → 108 taken 111659 times.
|
111659 | local(QRectF(start, end).normalized().adjusted(-fade, -fade, fade, fade)); |
| 314 | 111659 | const int parts = std::max(1, int(std::ceil((to - from) * segment / 3))); | |
| 315 |
2/2✓ Branch 120 → 110 taken 724676 times.
✓ Branch 120 → 121 taken 111659 times.
|
836335 | for (int k = 0; k <= parts; ++k) { |
| 316 | 724676 | const double t = from + (to - from) * k / parts; | |
| 317 |
4/4✓ Branch 110 → 111 taken 702626 times.
✓ Branch 110 → 119 taken 22050 times.
✓ Branch 111 → 112 taken 680814 times.
✓ Branch 111 → 119 taken 21812 times.
|
724676 | if (t > 1e-6 && t < 1 - 1e-6) { |
| 318 | 680814 | const QPointF at = a + (b - a) * t; | |
| 319 |
1/1✓ Branch 115 → 116 taken 680814 times.
|
680814 | m_dense.push_back(at); |
| 320 |
2/2✓ Branch 116 → 117 taken 680814 times.
✓ Branch 117 → 118 taken 680814 times.
|
680814 | m_denseAlpha.push_back(cuts->alphaAmong(at, m_local)); |
| 321 | } | ||
| 322 | } | ||
| 323 | 24137 | } | |
| 324 | } | ||
| 325 |
1/1✓ Branch 126 → 127 taken 24697 times.
|
24697 | m_dense.push_back(b); |
| 326 |
4/4✓ Branch 128 → 129 taken 560 times.
✓ Branch 128 → 130 taken 24137 times.
✓ Branch 130 → 131 taken 24137 times.
✓ Branch 132 → 133 taken 24697 times.
|
24697 | m_denseAlpha.push_back(m_boxes.isEmpty() ? 1.0 : alphaNear(b)); |
| 327 | } | ||
| 328 |
1/2✗ Branch 135 → 136 not taken.
✓ Branch 135 → 142 taken 7141 times.
|
7141 | if (solidLoop) { // the loop's first point again |
| 329 | ✗ | m_dense.pop_back(); | |
| 330 | ✗ | m_denseAlpha.pop_back(); | |
| 331 | } | ||
| 332 | } else { | ||
| 333 |
1/1✓ Branch 138 → 139 taken 2 times.
|
2 | m_dense = m_line; |
| 334 |
1/1✓ Branch 140 → 141 taken 2 times.
|
2 | m_denseAlpha.assign(m_line.size(), 1.0); |
| 335 | } | ||
| 336 | 7143 | double total = 0; | |
| 337 |
2/2✓ Branch 147 → 144 taken 4762 times.
✓ Branch 147 → 148 taken 7143 times.
|
11905 | for (double d : dash) |
| 338 | 4762 | total += d; | |
| 339 |
6/8✓ Branch 149 → 150 taken 2381 times.
✓ Branch 149 → 152 taken 4762 times.
✓ Branch 150 → 151 taken 2381 times.
✗ Branch 150 → 152 not taken.
✗ Branch 151 → 152 not taken.
✓ Branch 151 → 153 taken 2381 times.
✓ Branch 154 → 155 taken 4762 times.
✓ Branch 154 → 160 taken 2381 times.
|
7143 | if (dash.isEmpty() || total <= 0 || solidLoop) { |
| 340 |
1/1✓ Branch 158 → 159 taken 4762 times.
|
4762 | emitStrip(m_dense.data(), m_denseAlpha.data(), int(m_dense.size()), width, colour, alpha, solidLoop); |
| 341 | 4762 | return; | |
| 342 | } | ||
| 343 | // The dashes (on, off, on, off … px); a dash's ends get the alpha | ||
| 344 | // interpolated there, and reach half the width beyond them, as the | ||
| 345 | // Canvas's round caps did (a 1 px dot of a dotted line is a visible dot). | ||
| 346 | 2381 | const double cap = std::max(width, 1.0) / 2; | |
| 347 | 101412 | const auto flush = [&] { | |
| 348 |
2/2✓ Branch 3 → 4 taken 101411 times.
✓ Branch 3 → 13 taken 1 time.
|
101412 | if (m_dash.size() >= 2) { |
| 349 | 202822 | const auto extend = [&](size_t end, size_t inner) { | |
| 350 | 202822 | const QPointF d = m_dash[end] - m_dash[inner]; | |
| 351 | 202822 | const double l = length(d); | |
| 352 |
2/2✓ Branch 6 → 7 taken 202710 times.
✓ Branch 6 → 12 taken 112 times.
|
202822 | if (l > 1e-9) |
| 353 | 202710 | m_dash[end] += d / l * cap; | |
| 354 | 304233 | }; | |
| 355 | 101411 | extend(0, 1); | |
| 356 | 101411 | extend(m_dash.size() - 1, m_dash.size() - 2); | |
| 357 |
1/1✓ Branch 11 → 12 taken 101411 times.
|
101411 | emitStrip(m_dash.data(), m_dashAlpha.data(), int(m_dash.size()), width, colour, alpha, false); |
| 358 | } | ||
| 359 | 101412 | m_dash.clear(); | |
| 360 | 101412 | m_dashAlpha.clear(); | |
| 361 | 101412 | }; | |
| 362 | 2381 | int index = 0; | |
| 363 | 2381 | double left = dash.first(); | |
| 364 | 2381 | m_dash.clear(); | |
| 365 | 2381 | m_dashAlpha.clear(); | |
| 366 |
1/1✓ Branch 165 → 166 taken 2381 times.
|
2381 | m_dash.push_back(m_dense.front()); |
| 367 |
1/1✓ Branch 167 → 168 taken 2381 times.
|
2381 | m_dashAlpha.push_back(m_denseAlpha.front()); |
| 368 |
2/2✓ Branch 199 → 169 taken 227928 times.
✓ Branch 199 → 200 taken 2381 times.
|
230309 | for (size_t i = 1; i < m_dense.size(); ++i) { |
| 369 | 227928 | const QPointF from = m_dense[i - 1], to = m_dense[i]; | |
| 370 | 227928 | const double whole = length(to - from); | |
| 371 | 227928 | double done = 0; | |
| 372 |
2/2✓ Branch 196 → 174 taken 423574 times.
✓ Branch 196 → 197 taken 227928 times.
|
651502 | while (whole - done > 0) { |
| 373 | 423574 | const double step = std::min(whole - done, left); | |
| 374 | 423574 | done += step; | |
| 375 |
1/2✓ Branch 175 → 176 taken 423574 times.
✗ Branch 175 → 177 not taken.
|
423574 | const double t = whole > 0 ? done / whole : 1; |
| 376 | 423574 | const QPointF at = from + (to - from) * t; | |
| 377 | 423574 | const double cut = m_denseAlpha[i - 1] + (m_denseAlpha[i] - m_denseAlpha[i - 1]) * t; | |
| 378 |
2/2✓ Branch 184 → 185 taken 244722 times.
✓ Branch 184 → 187 taken 178852 times.
|
423574 | if (index % 2 == 0) { |
| 379 |
1/1✓ Branch 185 → 186 taken 244722 times.
|
244722 | m_dash.push_back(at); |
| 380 |
1/1✓ Branch 186 → 187 taken 244722 times.
|
244722 | m_dashAlpha.push_back(cut); |
| 381 | } | ||
| 382 | 423574 | left -= step; | |
| 383 |
2/2✓ Branch 187 → 188 taken 198944 times.
✓ Branch 187 → 195 taken 224630 times.
|
423574 | if (left <= 1e-9) { |
| 384 |
2/2✓ Branch 188 → 189 taken 99913 times.
✓ Branch 188 → 190 taken 99031 times.
|
198944 | if (index % 2 == 0) { |
| 385 |
1/1✓ Branch 189 → 192 taken 99913 times.
|
99913 | flush(); |
| 386 | } else { | ||
| 387 |
1/1✓ Branch 190 → 191 taken 99031 times.
|
99031 | m_dash.push_back(at); |
| 388 |
1/1✓ Branch 191 → 192 taken 99031 times.
|
99031 | m_dashAlpha.push_back(cut); |
| 389 | } | ||
| 390 | 198944 | index = (index + 1) % int(dash.size()); | |
| 391 | 198944 | left = dash.at(index); | |
| 392 | } | ||
| 393 | } | ||
| 394 | } | ||
| 395 |
2/2✓ Branch 200 → 201 taken 1499 times.
✓ Branch 200 → 202 taken 882 times.
|
2381 | if (index % 2 == 0) |
| 396 |
1/1✓ Branch 201 → 202 taken 1499 times.
|
1499 | flush(); |
| 397 | } | ||
| 398 | |||
| 399 | // Per segment three quads across the line: feather, core, feather — the | ||
| 400 | // cross-sections at shared points use the mitred normal, so consecutive | ||
| 401 | // segments meet without overlap (no darker joints on faded lines). | ||
| 402 | 106173 | void Mesh::emitStrip(const QPointF *points, const double *cutAlpha, int n, double width, const QColor &colour, double alpha, | |
| 403 | bool closed) { | ||
| 404 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 106173 times.
|
106173 | if (n < 2) |
| 405 | ✗ | return; | |
| 406 | // Thinner than a pixel: one pixel, fainter. | ||
| 407 | 106173 | const double half = std::max(width / 2, 0.5); | |
| 408 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 106173 times.
|
106173 | const double shown = width < 1 ? alpha * width : alpha; |
| 409 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 106173 times.
|
106173 | const int segments = closed ? n : n - 1; |
| 410 |
1/1✓ Branch 11 → 12 taken 106173 times.
|
106173 | m_normals.resize(size_t(segments)); |
| 411 |
2/2✓ Branch 16 → 13 taken 722307 times.
✓ Branch 16 → 17 taken 106173 times.
|
828480 | for (int i = 0; i < segments; ++i) |
| 412 | 722307 | m_normals[size_t(i)] = normalOf(points[i], points[(i + 1) % n]); | |
| 413 |
1/1✓ Branch 17 → 18 taken 106173 times.
|
106173 | m_miters.resize(size_t(n)); |
| 414 |
1/1✓ Branch 18 → 19 taken 106173 times.
|
106173 | m_stretch.resize(size_t(n)); |
| 415 |
2/2✓ Branch 39 → 20 taken 828480 times.
✓ Branch 39 → 40 taken 106173 times.
|
934653 | for (int i = 0; i < n; ++i) { |
| 416 | 828480 | QPointF before, after; | |
| 417 |
1/2✗ Branch 20 → 21 not taken.
✓ Branch 20 → 24 taken 828480 times.
|
828480 | if (closed) { |
| 418 | ✗ | before = m_normals[size_t((i - 1 + n) % n)]; | |
| 419 | ✗ | after = m_normals[size_t(i % n)]; | |
| 420 | } else { | ||
| 421 | 828480 | before = m_normals[size_t(std::max(0, i - 1))]; | |
| 422 | 828480 | after = m_normals[size_t(std::min(i, segments - 1))]; | |
| 423 | } | ||
| 424 | 828480 | QPointF miter = before + after; | |
| 425 | 828480 | const double l = length(miter); | |
| 426 |
2/2✓ Branch 31 → 32 taken 828368 times.
✓ Branch 31 → 33 taken 112 times.
|
828480 | miter = l > 1e-9 ? miter / l : after; |
| 427 | 828480 | m_miters[size_t(i)] = miter; | |
| 428 | 828480 | m_stretch[size_t(i)] = 1 / std::max(0.25, dot(miter, after)); | |
| 429 | } | ||
| 430 | 106173 | const double offsets[4] = {-(half + 1), -half, half, half + 1}; | |
| 431 | 106173 | const double edge[4] = {0, 1, 1, 0}; | |
| 432 | // Four vertices per point (feather, core, core, feather), only for points | ||
| 433 | // of segments that are drawn. | ||
| 434 |
1/1✓ Branch 40 → 41 taken 106173 times.
|
106173 | m_first.assign(size_t(n), -1); |
| 435 | 855506 | const auto crossSection = [&](int i) { | |
| 436 |
2/2✓ Branch 3 → 4 taken 333038 times.
✓ Branch 3 → 6 taken 522468 times.
|
855506 | if (m_first[size_t(i)] >= 0) |
| 437 | 333038 | return quint32(m_first[size_t(i)]); | |
| 438 | 522468 | quint32 base = 0; | |
| 439 |
2/2✓ Branch 15 → 7 taken 2089872 times.
✓ Branch 15 → 16 taken 522468 times.
|
2612340 | for (int k = 0; k < 4; ++k) { |
| 440 |
1/1✓ Branch 11 → 12 taken 2089872 times.
|
2089872 | const quint32 at = vertex(points[i] + m_miters[size_t(i)] * (offsets[k] * m_stretch[size_t(i)]), colour, |
| 441 | 2089872 | shown * edge[k] * cutAlpha[i]); | |
| 442 |
2/2✓ Branch 12 → 13 taken 522468 times.
✓ Branch 12 → 14 taken 1567404 times.
|
2089872 | if (k == 0) |
| 443 | 522468 | base = at; | |
| 444 | } | ||
| 445 | 522468 | m_first[size_t(i)] = base; | |
| 446 | 522468 | return base; | |
| 447 | 106173 | }; | |
| 448 |
2/2✓ Branch 65 → 42 taken 722307 times.
✓ Branch 65 → 66 taken 106173 times.
|
828480 | for (int i = 0; i < segments; ++i) { |
| 449 | 722307 | const int j = (i + 1) % n; | |
| 450 |
4/4✓ Branch 42 → 43 taken 326153 times.
✓ Branch 42 → 45 taken 396154 times.
✓ Branch 43 → 44 taken 294554 times.
✓ Branch 43 → 45 taken 31599 times.
|
722307 | if (cutAlpha[i] <= 0 && cutAlpha[j] <= 0) |
| 451 | 294554 | continue; | |
| 452 | 427753 | const QPointF a = points[i], b = points[j]; | |
| 453 |
2/2✓ Branch 46 → 47 taken 427742 times.
✓ Branch 46 → 54 taken 11 times.
|
427753 | if (!m_viewport.isEmpty()) { |
| 454 | 427742 | const QRectF box = QRectF(a, b).normalized().adjusted(-half - 2, -half - 2, half + 2, half + 2); | |
| 455 |
1/2✗ Branch 51 → 52 not taken.
✓ Branch 51 → 53 taken 427742 times.
|
427742 | if (!box.intersects(m_viewport)) |
| 456 | ✗ | continue; | |
| 457 | } | ||
| 458 |
2/2✓ Branch 54 → 55 taken 427753 times.
✓ Branch 55 → 56 taken 427753 times.
|
427753 | const quint32 from = crossSection(i), to = crossSection(j); |
| 459 |
2/2✓ Branch 61 → 57 taken 1283259 times.
✓ Branch 61 → 62 taken 427753 times.
|
1711012 | for (quint32 k = 0; k < 3; ++k) |
| 460 |
1/1✓ Branch 59 → 60 taken 1283259 times.
|
1283259 | m_indices.insert(m_indices.end(), {from + k, from + k + 1, to + k + 1, from + k, to + k + 1, to + k}); |
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 464 | 2 | void Mesh::fill(const QList<QPointF> &triangles, const QColor &colour, double alpha) { | |
| 465 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
|
2 | if (alpha <= 0) |
| 466 | ✗ | return; | |
| 467 |
2/2✓ Branch 27 → 5 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
|
4 | for (qsizetype i = 0; i + 2 < triangles.size(); i += 3) { |
| 468 |
9/12✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 15 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 15 not taken.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 1 time.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
|
2 | if (!m_viewport.isEmpty() && !boundsOf({triangles.at(i), triangles.at(i + 1), triangles.at(i + 2)}).intersects(m_viewport)) |
| 469 | 1 | continue; | |
| 470 |
2/2✓ Branch 24 → 21 taken 3 times.
✓ Branch 24 → 25 taken 1 time.
|
4 | for (qsizetype k = 0; k < 3; ++k) |
| 471 | 3 | push(triangles.at(i + k), colour, alpha); | |
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | ✗ | void Mesh::triangle(QPointF a, QPointF b, QPointF c, const QColor &colour, double alpha, const Cutouts *cuts) { | |
| 476 | ✗ | if (alpha <= 0) | |
| 477 | ✗ | return; | |
| 478 | // Small: one cut alpha for the whole arrowhead, from its middle. | ||
| 479 | ✗ | if (cuts && !cuts->isEmpty()) | |
| 480 | ✗ | alpha *= cuts->alphaAt((a + b + c) / 3); | |
| 481 | ✗ | if (alpha <= 0) | |
| 482 | ✗ | return; | |
| 483 | ✗ | push(a, colour, alpha); | |
| 484 | ✗ | push(b, colour, alpha); | |
| 485 | ✗ | push(c, colour, alpha); | |
| 486 | ✗ | const QPointF centre = (a + b + c) / 3; | |
| 487 | ✗ | const QPointF corners[3] = {a, b, c}; | |
| 488 | ✗ | for (int k = 0; k < 3; ++k) { | |
| 489 | ✗ | const QPointF p = corners[k], q = corners[(k + 1) % 3]; | |
| 490 | ✗ | QPointF out = normalOf(p, q); | |
| 491 | ✗ | if (dot(out, (p + q) / 2 - centre) < 0) | |
| 492 | ✗ | out = -out; | |
| 493 | ✗ | push(p, colour, alpha); | |
| 494 | ✗ | push(q, colour, alpha); | |
| 495 | ✗ | push(q + out, colour, 0); | |
| 496 | ✗ | push(p, colour, alpha); | |
| 497 | ✗ | push(q + out, colour, 0); | |
| 498 | ✗ | push(p + out, colour, 0); | |
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | ✗ | void Mesh::band(const QList<QPointF> &outer, const QList<QPointF> &inner, const QColor &colour, double outerAlpha, | |
| 503 | double innerAlpha, bool closed) { | ||
| 504 | ✗ | const int n = int(std::min(outer.size(), inner.size())); | |
| 505 | ✗ | if (n < 2) | |
| 506 | ✗ | return; | |
| 507 | ✗ | const int segments = closed ? n : n - 1; | |
| 508 | ✗ | for (int i = 0; i < segments; ++i) { | |
| 509 | ✗ | const int j = (i + 1) % n; | |
| 510 | ✗ | if (!m_viewport.isEmpty() && | |
| 511 | ✗ | !boundsOf({outer.at(i), outer.at(j), inner.at(i), inner.at(j)}).intersects(m_viewport)) | |
| 512 | ✗ | continue; | |
| 513 | ✗ | push(outer.at(i), colour, outerAlpha); | |
| 514 | ✗ | push(outer.at(j), colour, outerAlpha); | |
| 515 | ✗ | push(inner.at(j), colour, innerAlpha); | |
| 516 | ✗ | push(outer.at(i), colour, outerAlpha); | |
| 517 | ✗ | push(inner.at(j), colour, innerAlpha); | |
| 518 | ✗ | push(inner.at(i), colour, innerAlpha); | |
| 519 | } | ||
| 520 | } | ||
| 521 | |||
| 522 | 2771 | QList<QPointF> quadratic(QPointF a, QPointF c, QPointF b, double tolerance) { | |
| 523 | 2771 | const double bend = length(a - 2 * c + b); | |
| 524 |
1/1✓ Branch 7 → 8 taken 2771 times.
|
2771 | const int steps = std::clamp(int(std::ceil(std::sqrt(bend / (4 * std::max(tolerance, 0.05))))), 1, 64); |
| 525 | 2771 | QList<QPointF> out; | |
| 526 |
1/1✓ Branch 8 → 9 taken 2771 times.
|
2771 | out.reserve(steps + 1); |
| 527 |
2/2✓ Branch 17 → 10 taken 12900 times.
✓ Branch 17 → 18 taken 2771 times.
|
15671 | for (int i = 0; i <= steps; ++i) { |
| 528 | 12900 | const double t = double(i) / steps, u = 1 - t; | |
| 529 |
1/1✓ Branch 15 → 16 taken 12900 times.
|
12900 | out.append(a * (u * u) + c * (2 * u * t) + b * (t * t)); |
| 530 | } | ||
| 531 | 2771 | return out; | |
| 532 | ✗ | } | |
| 533 | |||
| 534 | 1195 | QList<QPointF> rounded(const QList<QPointF> &points, double radius) { | |
| 535 |
5/6✓ Branch 3 → 4 taken 941 times.
✓ Branch 3 → 5 taken 254 times.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 941 times.
✓ Branch 7 → 8 taken 254 times.
✓ Branch 7 → 9 taken 941 times.
|
1195 | if (points.size() < 3 || radius <= 0) |
| 536 | 254 | return points; | |
| 537 |
1/1✓ Branch 10 → 11 taken 941 times.
|
941 | QList<QPointF> out{points.first()}; |
| 538 |
2/2✓ Branch 48 → 12 taken 1484 times.
✓ Branch 48 → 49 taken 941 times.
|
2425 | for (int i = 1; i + 1 < points.size(); ++i) { |
| 539 | 1484 | const QPointF a = points.at(i - 1), c = points.at(i), b = points.at(i + 1); | |
| 540 | 1484 | const double in = length(c - a), outLength = length(b - c); | |
| 541 |
2/4✓ Branch 19 → 20 taken 1484 times.
✗ Branch 19 → 21 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 23 taken 1484 times.
|
1484 | if (in < 1e-6 || outLength < 1e-6) { |
| 542 | ✗ | out.append(c); | |
| 543 | ✗ | continue; | |
| 544 | } | ||
| 545 | 1484 | const QPointF toA = (a - c) / in, toB = (b - c) / outLength; | |
| 546 |
1/1✓ Branch 28 → 29 taken 1484 times.
|
1484 | const double angle = std::acos(std::clamp(dot(toA, toB), -1.0, 1.0)); |
| 547 | // A tangent distance as arcTo's for a circle of `radius` in this corner. | ||
| 548 |
2/4✓ Branch 29 → 30 taken 1484 times.
✗ Branch 29 → 32 not taken.
✓ Branch 30 → 31 taken 1484 times.
✗ Branch 30 → 32 not taken.
|
1484 | const double tangent = angle > 1e-3 && angle < M_PI - 1e-3 ? radius / std::tan(angle / 2) : 0; |
| 549 |
1/1✓ Branch 33 → 34 taken 1484 times.
|
1484 | const double reach = std::min({tangent, in / 2, outLength / 2}); |
| 550 |
1/2✗ Branch 34 → 35 not taken.
✓ Branch 34 → 37 taken 1484 times.
|
1484 | if (reach < 0.5) { |
| 551 | ✗ | out.append(c); | |
| 552 | ✗ | continue; | |
| 553 | } | ||
| 554 |
1/1✓ Branch 41 → 42 taken 1484 times.
|
1484 | const QList<QPointF> arc = quadratic(c + toA * reach, c, c + toB * reach, 0.25); |
| 555 |
1/1✓ Branch 42 → 43 taken 1484 times.
|
1484 | out.append(arc); |
| 556 | 1484 | } | |
| 557 |
1/1✓ Branch 50 → 51 taken 941 times.
|
941 | out.append(points.last()); |
| 558 |
1/1✓ Branch 51 → 52 taken 941 times.
|
941 | return withoutRepeats(out); |
| 559 | 941 | } | |
| 560 | |||
| 561 | ✗ | QList<QPointF> densified(const QList<QPointF> &points, double step, bool closed) { | |
| 562 | ✗ | if (points.size() < 2 || step <= 0) | |
| 563 | ✗ | return points; | |
| 564 | ✗ | QList<QPointF> out{points.first()}; | |
| 565 | ✗ | const int segments = closed ? int(points.size()) : int(points.size()) - 1; | |
| 566 | ✗ | for (int i = 0; i < segments; ++i) { | |
| 567 | ✗ | const QPointF a = points.at(i), b = points.at((i + 1) % points.size()); | |
| 568 | ✗ | const int parts = std::max(1, int(std::ceil(length(b - a) / step))); | |
| 569 | ✗ | for (int k = 1; k <= parts; ++k) | |
| 570 | ✗ | out.append(a + (b - a) * (double(k) / parts)); | |
| 571 | } | ||
| 572 | ✗ | if (closed) | |
| 573 | ✗ | out.removeLast(); | |
| 574 | ✗ | return out; | |
| 575 | ✗ | } | |
| 576 | |||
| 577 | 96 | QPointF middle(const QList<QPointF> &points, double *total) { | |
| 578 | 96 | double sum = 0; | |
| 579 |
2/2✓ Branch 9 → 3 taken 211 times.
✓ Branch 9 → 10 taken 96 times.
|
307 | for (int i = 1; i < points.size(); ++i) |
| 580 | 211 | sum += length(points.at(i) - points.at(i - 1)); | |
| 581 |
1/2✓ Branch 10 → 11 taken 96 times.
✗ Branch 10 → 12 not taken.
|
96 | if (total) |
| 582 | 96 | *total = sum; | |
| 583 |
1/2✗ Branch 13 → 14 not taken.
✓ Branch 13 → 17 taken 96 times.
|
96 | if (points.isEmpty()) |
| 584 | ✗ | return {}; | |
| 585 | 96 | double left = sum / 2; | |
| 586 |
1/2✓ Branch 33 → 18 taken 153 times.
✗ Branch 33 → 34 not taken.
|
153 | for (int i = 1; i < points.size(); ++i) { |
| 587 | 153 | const double step = length(points.at(i) - points.at(i - 1)); | |
| 588 |
3/4✓ Branch 22 → 23 taken 96 times.
✓ Branch 22 → 31 taken 57 times.
✓ Branch 23 → 24 taken 96 times.
✗ Branch 23 → 31 not taken.
|
153 | if (step >= left && step > 0) |
| 589 | 96 | return points.at(i - 1) + (points.at(i) - points.at(i - 1)) * (left / step); | |
| 590 | 57 | left -= step; | |
| 591 | } | ||
| 592 | ✗ | return points.first(); | |
| 593 | } | ||
| 594 | |||
| 595 | namespace { | ||
| 596 | 2394 | void simplifyRange(const QList<QPointF> &points, int first, int last, double tolerance, QList<bool> *keep) { | |
| 597 |
2/2✓ Branch 2 → 3 taken 81 times.
✓ Branch 2 → 4 taken 2313 times.
|
2394 | if (last <= first + 1) |
| 598 | 1230 | return; | |
| 599 | 2313 | const QLineF chord(points.at(first), points.at(last)); | |
| 600 | 2313 | double farthest = -1; | |
| 601 | 2313 | int index = -1; | |
| 602 |
2/2✓ Branch 25 → 8 taken 38649 times.
✓ Branch 25 → 26 taken 2313 times.
|
40962 | for (int i = first + 1; i < last; ++i) { |
| 603 | 38649 | const QPointF p = points.at(i); | |
| 604 | 38649 | const QPointF d = chord.p2() - chord.p1(); | |
| 605 | 38649 | const double l = length(d); | |
| 606 |
1/2✓ Branch 13 → 14 taken 38649 times.
✗ Branch 13 → 19 not taken.
|
38649 | const double distance = l > 1e-9 ? std::abs(cross(d, p - chord.p1())) / l : length(p - chord.p1()); |
| 607 |
2/2✓ Branch 22 → 23 taken 18340 times.
✓ Branch 22 → 24 taken 20309 times.
|
38649 | if (distance > farthest) { |
| 608 | 18340 | farthest = distance; | |
| 609 | 18340 | index = i; | |
| 610 | } | ||
| 611 | } | ||
| 612 |
2/2✓ Branch 26 → 27 taken 1149 times.
✓ Branch 26 → 28 taken 1164 times.
|
2313 | if (farthest <= tolerance) |
| 613 | 1149 | return; | |
| 614 |
1/1✓ Branch 28 → 29 taken 1164 times.
|
1164 | (*keep)[index] = true; |
| 615 |
1/1✓ Branch 29 → 30 taken 1164 times.
|
1164 | simplifyRange(points, first, index, tolerance, keep); |
| 616 |
1/1✓ Branch 30 → 31 taken 1164 times.
|
1164 | simplifyRange(points, index, last, tolerance, keep); |
| 617 | } | ||
| 618 | } // namespace | ||
| 619 | |||
| 620 | 33 | QList<QPointF> simplified(const QList<QPointF> &loop, double tolerance) { | |
| 621 | 33 | const int n = int(loop.size()); | |
| 622 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 33 times.
|
33 | if (n < 5) |
| 623 | ✗ | return loop; | |
| 624 | 33 | int far = 0; | |
| 625 | 33 | double best = -1; | |
| 626 |
2/2✓ Branch 13 → 6 taken 7687 times.
✓ Branch 13 → 14 taken 33 times.
|
7720 | for (int i = 1; i < n; ++i) { |
| 627 | 7687 | const double d = length(loop.at(i) - loop.first()); | |
| 628 |
2/2✓ Branch 10 → 11 taken 3610 times.
✓ Branch 10 → 12 taken 4077 times.
|
7687 | if (d > best) { |
| 629 | 3610 | best = d; | |
| 630 | 3610 | far = i; | |
| 631 | } | ||
| 632 | } | ||
| 633 | 33 | QList<QPointF> ring = loop; | |
| 634 |
1/1✓ Branch 16 → 17 taken 33 times.
|
33 | ring.append(loop.first()); |
| 635 |
1/1✓ Branch 18 → 19 taken 33 times.
|
33 | QList<bool> keep(ring.size(), false); |
| 636 |
3/3✓ Branch 20 → 21 taken 33 times.
✓ Branch 21 → 22 taken 33 times.
✓ Branch 22 → 23 taken 33 times.
|
33 | keep[0] = keep[far] = keep[ring.size() - 1] = true; |
| 637 |
1/1✓ Branch 23 → 24 taken 33 times.
|
33 | simplifyRange(ring, 0, far, tolerance, &keep); |
| 638 |
1/1✓ Branch 25 → 26 taken 33 times.
|
33 | simplifyRange(ring, far, int(ring.size()) - 1, tolerance, &keep); |
| 639 | 33 | QList<QPointF> out; | |
| 640 |
2/2✓ Branch 33 → 27 taken 7720 times.
✓ Branch 33 → 34 taken 33 times.
|
7753 | for (int i = 0; i + 1 < ring.size(); ++i) |
| 641 |
2/2✓ Branch 28 → 29 taken 1230 times.
✓ Branch 28 → 31 taken 6490 times.
|
7720 | if (keep.at(i)) |
| 642 |
1/1✓ Branch 30 → 31 taken 1230 times.
|
1230 | out.append(ring.at(i)); |
| 643 |
1/2✓ Branch 35 → 36 taken 33 times.
✗ Branch 35 → 37 not taken.
|
33 | return out.size() >= 3 ? out : loop; |
| 644 | 33 | } | |
| 645 | |||
| 646 | 111 | double signedArea(const QList<QPointF> &loop) { | |
| 647 | 111 | double sum = 0; | |
| 648 |
2/2✓ Branch 12 → 3 taken 7148 times.
✓ Branch 12 → 13 taken 111 times.
|
7259 | for (int i = 0; i < loop.size(); ++i) { |
| 649 | 7148 | const QPointF a = loop.at(i), b = loop.at((i + 1) % loop.size()); | |
| 650 | 7148 | sum += a.x() * b.y() - b.x() * a.y(); | |
| 651 | } | ||
| 652 | 111 | return sum / 2; | |
| 653 | } | ||
| 654 | |||
| 655 | 14 | bool contains(const QList<QPointF> &loop, QPointF point) { | |
| 656 | 14 | bool inside = false; | |
| 657 |
2/2✓ Branch 26 → 4 taken 307 times.
✓ Branch 26 → 27 taken 14 times.
|
321 | for (int i = 0, j = int(loop.size()) - 1; i < loop.size(); j = i++) { |
| 658 | 307 | const QPointF a = loop.at(i), b = loop.at(j); | |
| 659 |
4/4✓ Branch 10 → 11 taken 14 times.
✓ Branch 10 → 21 taken 293 times.
✓ Branch 22 → 23 taken 6 times.
✓ Branch 22 → 24 taken 301 times.
|
321 | if ((a.y() > point.y()) != (b.y() > point.y()) && |
| 660 |
2/2✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 21 taken 8 times.
|
14 | point.x() < (b.x() - a.x()) * (point.y() - a.y()) / (b.y() - a.y()) + a.x()) |
| 661 | 6 | inside = !inside; | |
| 662 | } | ||
| 663 | 14 | return inside; | |
| 664 | } | ||
| 665 | |||
| 666 | namespace { | ||
| 667 | |||
| 668 | 16 | bool segmentsCross(QPointF a, QPointF b, QPointF c, QPointF d) { | |
| 669 | 16 | const double d1 = cross(b - a, c - a), d2 = cross(b - a, d - a); | |
| 670 | 16 | const double d3 = cross(d - c, a - c), d4 = cross(d - c, b - c); | |
| 671 |
6/10✓ Branch 14 → 15 taken 4 times.
✓ Branch 14 → 16 taken 12 times.
✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 18 not taken.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 16 → 23 taken 12 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 23 taken 4 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
|
16 | return ((d1 > 1e-9 && d2 < -1e-9) || (d1 < -1e-9 && d2 > 1e-9)) && |
| 672 |
0/6✗ Branch 19 → 20 not taken.
✗ Branch 19 → 22 not taken.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 23 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
|
16 | ((d3 > 1e-9 && d4 < -1e-9) || (d3 < -1e-9 && d4 > 1e-9)); |
| 673 | } | ||
| 674 | |||
| 675 | 198900 | bool inTriangle(QPointF p, QPointF a, QPointF b, QPointF c) { | |
| 676 | 198900 | const double d1 = cross(b - a, p - a), d2 = cross(c - b, p - b), d3 = cross(a - c, p - c); | |
| 677 |
6/6✓ Branch 11 → 12 taken 196962 times.
✓ Branch 11 → 15 taken 1938 times.
✓ Branch 12 → 13 taken 183075 times.
✓ Branch 12 → 15 taken 13887 times.
✓ Branch 13 → 14 taken 54 times.
✓ Branch 13 → 15 taken 183021 times.
|
198900 | return d1 >= -1e-9 && d2 >= -1e-9 && d3 >= -1e-9; |
| 678 | } | ||
| 679 | |||
| 680 | // Joins a hole into the outline through its rightmost point and the nearest | ||
| 681 | // outline point that sees it (no edge crossed): one polygon with a slit. | ||
| 682 | 2 | QList<QPointF> bridged(const QList<QPointF> &outline, const QList<QPointF> &hole, const QList<QList<QPointF>> &others) { | |
| 683 | 2 | int m = 0; | |
| 684 |
2/2✓ Branch 11 → 3 taken 6 times.
✓ Branch 11 → 12 taken 2 times.
|
8 | for (int i = 1; i < hole.size(); ++i) |
| 685 |
2/2✓ Branch 7 → 8 taken 2 times.
✓ Branch 7 → 9 taken 4 times.
|
6 | if (hole.at(i).x() > hole.at(m).x()) |
| 686 | 2 | m = i; | |
| 687 | 2 | const QPointF from = hole.at(m); | |
| 688 | 2 | QList<int> order; | |
| 689 |
2/2✓ Branch 17 → 14 taken 8 times.
✓ Branch 17 → 18 taken 2 times.
|
10 | for (int i = 0; i < outline.size(); ++i) |
| 690 |
1/1✓ Branch 14 → 15 taken 8 times.
|
8 | order.append(i); |
| 691 |
3/3✓ Branch 18 → 19 taken 2 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
|
2 | std::sort(order.begin(), order.end(), |
| 692 | 10 | [&](int a, int b) { return length(outline.at(a) - from) < length(outline.at(b) - from); }); | |
| 693 | 2 | const auto visible = [&](QPointF to) { | |
| 694 | 4 | const auto crossesLoop = [&](const QList<QPointF> &loop) { | |
| 695 |
2/2✓ Branch 11 → 3 taken 16 times.
✓ Branch 11 → 12 taken 4 times.
|
20 | for (int i = 0; i < loop.size(); ++i) |
| 696 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 16 times.
|
16 | if (segmentsCross(from, to, loop.at(i), loop.at((i + 1) % loop.size()))) |
| 697 | ✗ | return true; | |
| 698 | 4 | return false; | |
| 699 | 2 | }; | |
| 700 |
3/6✓ Branch 3 → 4 taken 2 times.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 2 times.
|
2 | if (crossesLoop(outline) || crossesLoop(hole)) |
| 701 | ✗ | return false; | |
| 702 |
1/2✗ Branch 18 → 12 not taken.
✓ Branch 18 → 19 taken 2 times.
|
2 | for (const QList<QPointF> &other : others) |
| 703 | ✗ | if (crossesLoop(other)) | |
| 704 | ✗ | return false; | |
| 705 | 2 | return true; | |
| 706 | 2 | }; | |
| 707 |
2/3✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 2 times.
✓ Branch 24 → 25 taken 2 times.
|
2 | int p = order.isEmpty() ? 0 : order.first(); |
| 708 |
1/2✓ Branch 36 → 29 taken 2 times.
✗ Branch 36 → 37 not taken.
|
2 | for (int candidate : std::as_const(order)) |
| 709 |
2/3✓ Branch 31 → 32 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
✗ Branch 32 → 34 not taken.
|
2 | if (visible(outline.at(candidate))) { |
| 710 | 2 | p = candidate; | |
| 711 | 2 | break; | |
| 712 | } | ||
| 713 | 2 | QList<QPointF> out; | |
| 714 |
1/1✓ Branch 39 → 40 taken 2 times.
|
2 | out.reserve(outline.size() + hole.size() + 2); |
| 715 |
2/2✓ Branch 44 → 41 taken 6 times.
✓ Branch 44 → 45 taken 2 times.
|
8 | for (int i = 0; i <= p; ++i) |
| 716 |
1/1✓ Branch 42 → 43 taken 6 times.
|
6 | out.append(outline.at(i)); |
| 717 |
2/2✓ Branch 51 → 46 taken 8 times.
✓ Branch 51 → 52 taken 2 times.
|
10 | for (int k = 0; k < hole.size(); ++k) |
| 718 |
1/1✓ Branch 48 → 49 taken 8 times.
|
8 | out.append(hole.at((m + k) % hole.size())); |
| 719 |
1/1✓ Branch 53 → 54 taken 2 times.
|
2 | out.append(hole.at(m)); |
| 720 |
2/2✓ Branch 59 → 55 taken 4 times.
✓ Branch 59 → 60 taken 2 times.
|
6 | for (int i = p; i < outline.size(); ++i) |
| 721 |
1/1✓ Branch 56 → 57 taken 4 times.
|
4 | out.append(outline.at(i)); |
| 722 | 2 | return out; | |
| 723 | 2 | } | |
| 724 | |||
| 725 | 39 | void earClip(const QList<QPointF> &polygon, QList<QPointF> *triangles) { | |
| 726 | 39 | QList<int> ring; | |
| 727 |
2/2✓ Branch 6 → 3 taken 3520 times.
✓ Branch 6 → 7 taken 39 times.
|
3559 | for (int i = 0; i < polygon.size(); ++i) |
| 728 |
1/1✓ Branch 3 → 4 taken 3520 times.
|
3520 | ring.append(i); |
| 729 | 39 | int i = 0; | |
| 730 | 39 | int sinceEar = 0; | |
| 731 |
2/2✓ Branch 58 → 8 taken 3708 times.
✓ Branch 58 → 59 taken 39 times.
|
3747 | while (ring.size() > 3) { |
| 732 | 3708 | const int n = int(ring.size()); | |
| 733 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 3708 times.
|
3708 | if (sinceEar > n) |
| 734 | ✗ | break; // a whole round without an ear (rounding at a bridge): the rest stays empty | |
| 735 | 3708 | i %= n; | |
| 736 | 3708 | const int prev = ring.at((i - 1 + n) % n), cur = ring.at(i), next = ring.at((i + 1) % n); | |
| 737 | 3708 | const QPointF a = polygon.at(prev), b = polygon.at(cur), c = polygon.at(next); | |
| 738 | 3708 | const double turn = cross(b - a, c - b); | |
| 739 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 3708 times.
|
3708 | if (std::abs(turn) <= 1e-9) { |
| 740 | ✗ | ring.removeAt(i); // collinear: drops out without a triangle | |
| 741 | ✗ | sinceEar = 0; | |
| 742 | ✗ | continue; | |
| 743 | } | ||
| 744 | 3708 | bool ear = turn > 0; | |
| 745 |
4/4✓ Branch 47 → 48 taken 209490 times.
✓ Branch 47 → 49 taken 3403 times.
✓ Branch 48 → 25 taken 209185 times.
✓ Branch 48 → 49 taken 305 times.
|
212893 | for (int k = 0; k < n && ear; ++k) { |
| 746 | 209185 | const int index = ring.at(k); | |
| 747 |
6/6✓ Branch 26 → 27 taken 205780 times.
✓ Branch 26 → 29 taken 3405 times.
✓ Branch 27 → 28 taken 202349 times.
✓ Branch 27 → 29 taken 3431 times.
✓ Branch 28 → 29 taken 3443 times.
✓ Branch 28 → 30 taken 198906 times.
|
209185 | if (index == prev || index == cur || index == next) |
| 748 | 10285 | continue; | |
| 749 | 198906 | const QPointF p = polygon.at(index); | |
| 750 |
7/8✓ Branch 32 → 33 taken 198906 times.
✗ Branch 32 → 37 not taken.
✓ Branch 34 → 35 taken 198902 times.
✓ Branch 34 → 37 taken 4 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 36 → 38 taken 198900 times.
✓ Branch 39 → 40 taken 6 times.
✓ Branch 39 → 41 taken 198900 times.
|
198906 | if (p == a || p == b || p == c) |
| 751 | 6 | continue; // the two ends of a bridge | |
| 752 |
2/2✓ Branch 42 → 43 taken 54 times.
✓ Branch 42 → 44 taken 198846 times.
|
198900 | if (inTriangle(p, a, b, c)) |
| 753 | 54 | ear = false; | |
| 754 | } | ||
| 755 |
2/2✓ Branch 49 → 50 taken 3403 times.
✓ Branch 49 → 55 taken 305 times.
|
3708 | if (ear) { |
| 756 |
2/2✓ Branch 50 → 51 taken 3403 times.
✓ Branch 51 → 52 taken 3403 times.
|
3403 | triangles->append({a, b, c}); |
| 757 |
1/1✓ Branch 53 → 54 taken 3403 times.
|
3403 | ring.removeAt(i); |
| 758 | 3403 | sinceEar = 0; | |
| 759 | } else { | ||
| 760 | 305 | ++i; | |
| 761 | 305 | ++sinceEar; | |
| 762 | } | ||
| 763 | } | ||
| 764 |
1/2✓ Branch 60 → 61 taken 39 times.
✗ Branch 60 → 71 not taken.
|
39 | if (ring.size() == 3) |
| 765 |
2/2✓ Branch 67 → 68 taken 39 times.
✓ Branch 68 → 69 taken 39 times.
|
39 | triangles->append({polygon.at(ring.at(0)), polygon.at(ring.at(1)), polygon.at(ring.at(2))}); |
| 766 | 39 | } | |
| 767 | |||
| 768 | } // namespace | ||
| 769 | |||
| 770 | 36 | QList<QPointF> triangulate(const QList<QList<QPointF>> &loops) { | |
| 771 | 36 | QList<QList<QPointF>> clean; | |
| 772 |
2/2✓ Branch 29 → 4 taken 41 times.
✓ Branch 29 → 30 taken 36 times.
|
77 | for (const QList<QPointF> &loop : loops) { |
| 773 |
1/1✓ Branch 5 → 6 taken 41 times.
|
41 | QList<QPointF> points = withoutRepeats(loop); |
| 774 |
5/8✓ Branch 7 → 8 taken 41 times.
✗ Branch 7 → 14 not taken.
✓ Branch 8 → 9 taken 41 times.
✓ Branch 9 → 10 taken 41 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 41 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 41 times.
|
41 | if (points.size() >= 2 && length(points.first() - points.last()) <= 0.01) |
| 775 | ✗ | points.removeLast(); | |
| 776 |
3/6✓ Branch 18 → 19 taken 41 times.
✗ Branch 18 → 23 not taken.
✓ Branch 21 → 22 taken 41 times.
✗ Branch 21 → 23 not taken.
✓ Branch 24 → 25 taken 41 times.
✗ Branch 24 → 26 not taken.
|
41 | if (points.size() >= 3 && std::abs(signedArea(points)) > 1e-6) |
| 777 |
1/1✓ Branch 25 → 26 taken 41 times.
|
41 | clean.append(points); |
| 778 | 41 | } | |
| 779 | // Depth: how many other loops enclose a loop. Even depth bounds the | ||
| 780 | // region (an outline), odd depth is a hole of the outline around it. | ||
| 781 | 36 | QList<int> depth; | |
| 782 |
2/2✓ Branch 48 → 31 taken 41 times.
✓ Branch 48 → 49 taken 36 times.
|
77 | for (int i = 0; i < clean.size(); ++i) { |
| 783 | 41 | int d = 0; | |
| 784 |
2/2✓ Branch 44 → 32 taken 53 times.
✓ Branch 44 → 45 taken 41 times.
|
94 | for (int j = 0; j < clean.size(); ++j) |
| 785 |
6/6✓ Branch 32 → 33 taken 12 times.
✓ Branch 32 → 39 taken 41 times.
✓ Branch 37 → 38 taken 4 times.
✓ Branch 37 → 39 taken 8 times.
✓ Branch 40 → 41 taken 4 times.
✓ Branch 40 → 42 taken 49 times.
|
53 | if (j != i && contains(clean.at(j), clean.at(i).first())) |
| 786 | 4 | ++d; | |
| 787 |
1/1✓ Branch 45 → 46 taken 41 times.
|
41 | depth.append(d); |
| 788 | } | ||
| 789 | 36 | QList<QPointF> triangles; | |
| 790 |
2/2✓ Branch 104 → 50 taken 41 times.
✓ Branch 104 → 105 taken 36 times.
|
77 | for (int i = 0; i < clean.size(); ++i) { |
| 791 |
2/2✓ Branch 51 → 52 taken 2 times.
✓ Branch 51 → 53 taken 39 times.
|
41 | if (depth.at(i) % 2 != 0) |
| 792 | 2 | continue; | |
| 793 | 39 | QList<QPointF> outline = clean.at(i); | |
| 794 |
2/2✓ Branch 56 → 57 taken 32 times.
✓ Branch 56 → 60 taken 7 times.
|
39 | if (signedArea(outline) < 0) |
| 795 |
3/3✓ Branch 57 → 58 taken 32 times.
✓ Branch 58 → 59 taken 32 times.
✓ Branch 59 → 60 taken 32 times.
|
32 | std::reverse(outline.begin(), outline.end()); |
| 796 | 39 | QList<QList<QPointF>> holes; | |
| 797 |
2/2✓ Branch 84 → 61 taken 48 times.
✓ Branch 84 → 85 taken 39 times.
|
87 | for (int j = 0; j < clean.size(); ++j) |
| 798 |
5/6✓ Branch 63 → 64 taken 2 times.
✓ Branch 63 → 70 taken 46 times.
✓ Branch 68 → 69 taken 2 times.
✗ Branch 68 → 70 not taken.
✓ Branch 71 → 72 taken 2 times.
✓ Branch 71 → 82 taken 46 times.
|
48 | if (depth.at(j) == depth.at(i) + 1 && contains(clean.at(i), clean.at(j).first())) { |
| 799 | 2 | QList<QPointF> hole = clean.at(j); | |
| 800 |
1/2✓ Branch 75 → 76 taken 2 times.
✗ Branch 75 → 79 not taken.
|
2 | if (signedArea(hole) > 0) |
| 801 |
3/3✓ Branch 76 → 77 taken 2 times.
✓ Branch 77 → 78 taken 2 times.
✓ Branch 78 → 79 taken 2 times.
|
2 | std::reverse(hole.begin(), hole.end()); |
| 802 |
1/1✓ Branch 79 → 80 taken 2 times.
|
2 | holes.append(hole); |
| 803 | 2 | } | |
| 804 | // Rightmost holes first, so a later bridge never crosses an earlier one. | ||
| 805 |
3/3✓ Branch 85 → 86 taken 39 times.
✓ Branch 86 → 87 taken 39 times.
✓ Branch 87 → 88 taken 39 times.
|
39 | std::sort(holes.begin(), holes.end(), [](const QList<QPointF> &a, const QList<QPointF> &b) { |
| 806 | ✗ | return boundsOf(a).right() > boundsOf(b).right(); | |
| 807 | }); | ||
| 808 |
2/2✓ Branch 97 → 89 taken 2 times.
✓ Branch 97 → 98 taken 39 times.
|
41 | for (int h = 0; h < holes.size(); ++h) |
| 809 |
2/2✓ Branch 89 → 90 taken 2 times.
✓ Branch 91 → 92 taken 2 times.
|
2 | outline = bridged(outline, holes.at(h), holes.mid(h + 1)); |
| 810 |
1/1✓ Branch 98 → 99 taken 39 times.
|
39 | earClip(outline, &triangles); |
| 811 | 39 | } | |
| 812 | 36 | return triangles; | |
| 813 | 36 | } | |
| 814 | |||
| 815 | } // namespace GraphGeometry | ||
| 816 |