SWMMVis  6.0.0-alpha.4
Qt6/C++ GIS-based graphical user interface for the SWMMVis engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
marchingtriangles.h
Go to the documentation of this file.
1
31#ifndef OPENSWMMVIS_CONTOUR_MARCHINGTRIANGLES_H
32#define OPENSWMMVIS_CONTOUR_MARCHINGTRIANGLES_H
33
34#include <QColor>
35#include <QPointF>
36
37#include <algorithm>
38#include <vector>
39
40namespace OpenSWMM::Contour {
41
51inline QColor viridisAt(double t)
52{
53 struct Stop { double t; quint8 r, g, b; };
54 static const Stop stops[] = {
55 {0.00, 0x44, 0x01, 0x54},
56 {0.25, 0x3b, 0x52, 0x8b},
57 {0.50, 0x21, 0x91, 0x8c},
58 {0.75, 0x5e, 0xc9, 0x62},
59 {1.00, 0xfd, 0xe7, 0x25},
60 };
61 constexpr int N = int(sizeof(stops) / sizeof(*stops));
62 t = std::clamp(t, 0.0, 1.0);
63 int i = 0;
64 while (i < N - 2 && stops[i + 1].t <= t) ++i;
65 const Stop &lo = stops[i], &hi = stops[i + 1];
66 const double f = (hi.t > lo.t) ? (t - lo.t) / (hi.t - lo.t) : 0.0;
67 const int r = int(lo.r + f * (hi.r - lo.r) + 0.5);
68 const int g = int(lo.g + f * (hi.g - lo.g) + 0.5);
69 const int b = int(lo.b + f * (hi.b - lo.b) + 0.5);
70 return QColor(std::clamp(r, 0, 255),
71 std::clamp(g, 0, 255),
72 std::clamp(b, 0, 255));
73}
74
77{
78 QPointF a;
79 QPointF b;
80 double level;
81};
82
104template <typename TriRange, typename Extract>
105std::vector<IsoLineSegment>
106marchingTriangles(const TriRange &tris,
107 const std::vector<double> &levels,
108 Extract extract)
109{
110 std::vector<IsoLineSegment> out;
111 if (levels.empty()) return out;
112
113 out.reserve(/* heuristic */ levels.size() * 32);
114
115 QPointF p0, p1, p2;
116 double v0, v1, v2;
117
118 for (const auto &t : tris) {
119 extract(t, p0, p1, p2, v0, v1, v2);
120
121 // Triangle-level early-out: clamp range so far-out levels skip
122 // immediately. Computed once per triangle.
123 const double vMin = std::min({v0, v1, v2});
124 const double vMax = std::max({v0, v1, v2});
125 if (!(vMax > vMin)) continue; // degenerate (flat) triangle
126
127 for (const double level : levels) {
128 if (level < vMin || level > vMax) continue;
129
130 // Classify vertices: bit i = 1 means vertex i is below `level`.
131 // Cases 0 (000) and 7 (111) produce no segment.
132 const int below = (v0 < level ? 1 : 0)
133 | (v1 < level ? 2 : 0)
134 | (v2 < level ? 4 : 0);
135 if (below == 0 || below == 7) continue;
136
137 // Edges are (0,1), (1,2), (0,2). An edge is "crossed" iff its
138 // two endpoints have opposite below-flags. Exactly two edges
139 // are crossed in every below ∈ {1,2,3,4,5,6} case.
140 QPointF endpoints[2];
141 int k = 0;
142
143 auto cross = [&](const QPointF &pa, double va,
144 const QPointF &pb, double vb) {
145 const double denom = vb - va;
146 if (denom == 0.0) {
147 endpoints[k++] = pa;
148 return;
149 }
150 const double t = (level - va) / denom;
151 endpoints[k++] = QPointF(pa.x() + t * (pb.x() - pa.x()),
152 pa.y() + t * (pb.y() - pa.y()));
153 };
154
155 const bool b0 = (below & 1);
156 const bool b1 = (below & 2);
157 const bool b2 = (below & 4);
158
159 if (b0 != b1) cross(p0, v0, p1, v1);
160 if (b1 != b2) cross(p1, v1, p2, v2);
161 if (b2 != b0) cross(p2, v2, p0, v0);
162
163 // k must be exactly 2 for cases 1..6. If a vertex sits exactly
164 // on the level both incident edges register a "crossing" at
165 // that vertex — emit the degenerate segment regardless so the
166 // line topology stays closed; downstream renderers handle
167 // zero-length segments gracefully (single pixel or nothing).
168 if (k == 2) {
169 out.push_back({endpoints[0], endpoints[1], level});
170 }
171 }
172 }
173 return out;
174}
175
185inline std::vector<double>
186evenlySpacedLevels(double vMin, double vMax, int count)
187{
188 std::vector<double> out;
189 if (count < 1 || !(vMax > vMin)) return out;
190 out.reserve(size_t(count));
191 const double step = (vMax - vMin) / double(count + 1);
192 for (int i = 1; i <= count; ++i)
193 out.push_back(vMin + step * double(i));
194 return out;
195}
196
206inline std::vector<double>
207evenlySpacedLevelsInclusive(double vMin, double vMax, int levelCount)
208{
209 std::vector<double> out;
210 if (levelCount < 2 || !(vMax > vMin)) return out;
211 out.reserve(size_t(levelCount));
212 const double step = (vMax - vMin) / double(levelCount - 1);
213 for (int i = 0; i < levelCount; ++i)
214 out.push_back(vMin + step * double(i));
215 out.back() = vMax; // guard against floating-point drift on the last sample
216 return out;
217}
218
222{
223 std::vector<QPointF> verts;
224 double bandLo;
225 double bandHi;
228};
229
230namespace detail {
231
242inline void clipHalfplane(const std::vector<QPointF> &inPts,
243 const std::vector<double> &inVals,
244 double L,
245 bool keepAbove,
246 std::vector<QPointF> &outPts,
247 std::vector<double> &outVals)
248{
249 outPts.clear();
250 outVals.clear();
251 const size_t n = inPts.size();
252 if (n == 0) return;
253
254 auto isInside = [&](double v) {
255 return keepAbove ? (v >= L) : (v <= L);
256 };
257
258 for (size_t i = 0; i < n; ++i) {
259 const size_t j = (i + 1) % n;
260 const QPointF &pi = inPts[i], &pj = inPts[j];
261 const double vi = inVals[i], vj = inVals[j];
262 const bool inI = isInside(vi);
263 const bool inJ = isInside(vj);
264
265 if (inI) {
266 outPts.push_back(pi);
267 outVals.push_back(vi);
268 }
269 if (inI != inJ) {
270 const double denom = vj - vi;
271 if (denom == 0.0) {
272 outPts.push_back(pi);
273 outVals.push_back(L);
274 } else {
275 const double t = (L - vi) / denom;
276 outPts.push_back(QPointF(pi.x() + t * (pj.x() - pi.x()),
277 pi.y() + t * (pj.y() - pi.y())));
278 outVals.push_back(L);
279 }
280 }
281 }
282}
283
284} // namespace detail
285
302template <typename TriRange, typename Extract>
303std::vector<IsoBandPolygon>
304marchingTrianglesIsobands(const TriRange &tris,
305 const std::vector<double> &levels,
306 Extract extract,
307 bool clampUniformOutsideRange = true)
308{
309 std::vector<IsoBandPolygon> out;
310 if (levels.size() < 2) return out;
311 out.reserve(/* heuristic */ levels.size() * 32);
312
313 QPointF p0, p1, p2;
314 double v0, v1, v2;
315
316 // Scratch buffers reused across iterations
317 std::vector<QPointF> a, b;
318 std::vector<double> aV, bV;
319 a.reserve(8); b.reserve(8);
320 aV.reserve(8); bV.reserve(8);
321
322 for (const auto &t : tris) {
323 extract(t, p0, p1, p2, v0, v1, v2);
324
325 const double vMin = std::min({v0, v1, v2});
326 const double vMax = std::max({v0, v1, v2});
327 if (!(vMax > vMin)) {
328 // Uniform-value triangle: fill it entirely with the band that
329 // contains that value. Skipping is correct for isolines (no line
330 // crosses a flat triangle) but WRONG for filled bands — it would
331 // leave a hole wherever the field is locally flat (e.g. ponded
332 // water reads one depth across the cell). Band selection mirrors
333 // the renderer's per-cell bucketing: upper_bound over the interior
334 // breaks, clamped so values at/below the first level land in band 0
335 // and values at/above the last level land in the last band.
336 //
337 // Depth renderers pass clampUniformOutsideRange=false so a flat dry
338 // cell below the first break (the dry threshold) remains
339 // transparent instead of being painted as the first wet band.
340 if (!clampUniformOutsideRange
341 && (vMin < levels.front() || vMin > levels.back()))
342 continue;
343 const int nb = int(levels.size()) - 1; // number of bands (>= 1)
344 int k = int(std::upper_bound(levels.begin() + 1, levels.end() - 1,
345 vMin)
346 - (levels.begin() + 1));
347 k = std::clamp(k, 0, nb - 1);
349 bp.verts = {p0, p1, p2};
350 bp.bandLo = levels[size_t(k)];
351 bp.bandHi = levels[size_t(k) + 1];
352 bp.bandIndex = k;
353 out.push_back(std::move(bp));
354 continue;
355 }
356
357 for (size_t k = 0; k + 1 < levels.size(); ++k) {
358 const double L1 = levels[k];
359 const double L2 = levels[k + 1];
360 if (L2 < vMin || L1 > vMax) continue;
361 if (!(L2 > L1)) continue; // degenerate band
362
363 // Initialise polygon = triangle
364 a = {p0, p1, p2};
365 aV = {v0, v1, v2};
366
367 // Clip 1: keep value >= L1
368 detail::clipHalfplane(a, aV, L1, /*keepAbove=*/true, b, bV);
369 // Clip 2: keep value <= L2
370 detail::clipHalfplane(b, bV, L2, /*keepAbove=*/false, a, aV);
371
372 if (a.size() >= 3) {
374 bp.verts = a;
375 bp.bandLo = L1;
376 bp.bandHi = L2;
377 bp.bandIndex = int(k);
378 out.push_back(std::move(bp));
379 }
380 }
381 }
382 return out;
383}
384
385} // namespace OpenSWMM::Contour
386
387#endif // OPENSWMMVIS_CONTOUR_MARCHINGTRIANGLES_H
size_t i
Definition contourjob.cpp:27
DiagramType t
Definition diagramspec.cpp:20
int vb
Definition inpmeshreader.cpp:49
int va
Definition inpmeshreader.cpp:49
std::size_t n
Definition mesh2dresultsexport.cpp:426
int k
Definition mesh2dresultsexport.cpp:549
double step
Definition meshcellparams.cpp:28
int b
local residual indices, a < b
Definition meshquadmatch.cpp:46
int a
Definition meshquadmatch.cpp:46
void clipHalfplane(const std::vector< QPointF > &inPts, const std::vector< double > &inVals, double L, bool keepAbove, std::vector< QPointF > &outPts, std::vector< double > &outVals)
Sutherland-Hodgman clip of a convex polygon against the half-plane defined by value >= L (when keepAb...
Definition marchingtriangles.h:242
Definition contourchain.h:23
std::vector< IsoLineSegment > marchingTriangles(const TriRange &tris, const std::vector< double > &levels, Extract extract)
Generate iso-line segments for every (triangle, level) pair where the level crosses the triangle's va...
Definition marchingtriangles.h:106
std::vector< double > evenlySpacedLevels(double vMin, double vMax, int count)
Convenience helper that generates N evenly-spaced contour levels between vMin and vMax (exclusive of ...
Definition marchingtriangles.h:186
std::vector< double > evenlySpacedLevelsInclusive(double vMin, double vMax, int levelCount)
Convenience helper that returns levelCount evenly-spaced break levels INCLUSIVE of both endpoints (vM...
Definition marchingtriangles.h:207
std::vector< IsoBandPolygon > marchingTrianglesIsobands(const TriRange &tris, const std::vector< double > &levels, Extract extract, bool clampUniformOutsideRange=true)
Generate filled iso-band polygons for every (triangle, band) pair.
Definition marchingtriangles.h:304
QColor viridisAt(double t)
Sample a 5-stop Viridis-inspired colour ramp at t in [0, 1].
Definition marchingtriangles.h:51
int count
Definition numberformat.cpp:101
QVector< int > v
pool vertex indices
Definition pslgminsize.cpp:159
One filled-isoband polygon in scene space. Convex (Sutherland- Hodgman of a triangle yields a convex ...
Definition marchingtriangles.h:222
double bandHi
upper break level
Definition marchingtriangles.h:225
std::vector< QPointF > verts
convex polygon vertices in order
Definition marchingtriangles.h:223
int bandIndex
Definition marchingtriangles.h:226
double bandLo
lower break level
Definition marchingtriangles.h:224
A single contour iso-line segment in scene space.
Definition marchingtriangles.h:77
double level
the iso-level this segment was generated for
Definition marchingtriangles.h:80
QPointF a
Definition marchingtriangles.h:78
QPointF b
Definition marchingtriangles.h:79