OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
FootprintGeometry.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Caleb Buahin
3
12
13#ifndef OPENSWMM_FOOTPRINT_GEOMETRY_HPP
14#define OPENSWMM_FOOTPRINT_GEOMETRY_HPP
15#include <algorithm>
16#include <functional>
17#include <cmath>
18#include <numeric>
19#include <limits>
20#include <string>
21#include <vector>
23struct Point {
24 double x = 0, y = 0;
25};
26using Ring = std::vector<Point>;
35inline double cross(Point a, Point b, Point c) {
36 return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
37}
38
46inline double signedArea(const Ring& p) {
47 if (p.size() < 3)
48 return 0;
49 double sum = 0;
50 for (std::size_t i = 1; i + 1 < p.size(); ++i)
51 sum += cross(p[0], p[i], p[i + 1]);
52 return .5 * sum;
53}
54
61inline double tolerance(const Ring& p) {
62 if (p.empty())
63 return 0;
64 double span = 0;
65 for (auto v : p)
66 span = std::max(span, std::max(std::abs(v.x - p[0].x), std::abs(v.y - p[0].y)));
67 return 128 * std::numeric_limits<double>::epsilon() * std::max(span * span, 1e-24);
68}
69
78inline bool between(Point a, Point b, Point p, double eps) {
79 return std::abs(cross(a, b, p)) <= eps && p.x >= std::min(a.x, b.x) &&
80 p.x <= std::max(a.x, b.x) && p.y >= std::min(a.y, b.y) && p.y <= std::max(a.y, b.y);
81}
82
92inline bool intersects(Point a, Point b, Point c, Point d, double eps) {
93 const auto x = cross(a, b, c), y = cross(a, b, d), z = cross(c, d, a), w = cross(c, d, b);
94 if (((x > eps && y < -eps) || (x < -eps && y > eps)) &&
95 ((z > eps && w < -eps) || (z < -eps && w > eps)))
96 return true;
97 return between(a, b, c, eps) || between(a, b, d, eps) || between(c, d, a, eps) ||
98 between(c, d, b, eps);
99}
100// Validate simple rings, then triangulate: disconnected intersections of a
101// concave footprint remain a sum of disjoint triangles, not a bridged ring.
117inline std::string triangulate(Ring& p, std::vector<Ring>& triangles,
118 const std::function<bool()>& running = {}) {
119 triangles.clear();
120 if (p.size() > 1 && p.front().x == p.back().x && p.front().y == p.back().y)
121 p.pop_back();
122 if (p.size() < 3)
123 return "Missing polygon (unmapped).";
124 for (auto v : p)
125 if (!std::isfinite(v.x) || !std::isfinite(v.y))
126 return "Non-finite polygon coordinate.";
127 const double eps = tolerance(p);
128 for (std::size_t i = 0; i < p.size(); ++i) {
129 if (running && !running())
130 return "Preview cancelled.";
131 const auto j = (i + 1) % p.size();
132 if (p[i].x == p[j].x && p[i].y == p[j].y)
133 return "Duplicate polygon vertex.";
134 for (std::size_t k = i + 1; k < p.size(); ++k) {
135 const auto l = (k + 1) % p.size();
136 if (j == k || l == i)
137 continue;
138 if (intersects(p[i], p[j], p[k], p[l], eps))
139 return "Self-intersecting polygon; holes/multipart rings are unsupported.";
140 }
141 }
142 if (std::abs(signedArea(p)) <= eps)
143 return "Degenerate polygon.";
144 if (signedArea(p) < 0)
145 std::reverse(p.begin(), p.end());
146 // Collinear boundary points are valid but do not need to become ears.
147 bool changed = true;
148 while (changed && p.size() > 3) {
149 changed = false;
150 for (std::size_t i = 0; i < p.size(); ++i) {
151 if (std::abs(cross(p[(i + p.size() - 1) % p.size()], p[i], p[(i + 1) % p.size()])) <=
152 eps) {
153 p.erase(p.begin() + i);
154 changed = true;
155 break;
156 }
157 }
158 }
159 std::vector<int> ids(p.size());
160 std::iota(ids.begin(), ids.end(), 0);
161 while (ids.size() > 3) {
162 if (running && !running())
163 return "Preview cancelled.";
164 bool found = false;
165 for (std::size_t k = 0; k < ids.size(); ++k) {
166 const int a = ids[(k + ids.size() - 1) % ids.size()], b = ids[k],
167 c = ids[(k + 1) % ids.size()];
168 if (cross(p[a], p[b], p[c]) <= eps)
169 continue;
170 bool contains = false;
171 for (int d : ids)
172 if (d != a && d != b && d != c && cross(p[a], p[b], p[d]) >= -eps &&
173 cross(p[b], p[c], p[d]) >= -eps && cross(p[c], p[a], p[d]) >= -eps) {
174 contains = true;
175 break;
176 }
177 if (contains)
178 continue;
179 triangles.push_back({p[a], p[b], p[c]});
180 ids.erase(ids.begin() + k);
181 found = true;
182 break;
183 }
184 if (!found)
185 return "Polygon triangulation failed; repair topology.";
186 }
187 triangles.push_back({p[ids[0]], p[ids[1]], p[ids[2]]});
188 return {};
189}
190
202inline double intersectionArea(Ring subject, const Ring& clip) {
203 for (std::size_t i = 0; i < clip.size() && !subject.empty(); ++i) {
204 const auto a = clip[i], b = clip[(i + 1) % clip.size()];
205 Ring next;
206 for (std::size_t k = 0; k < subject.size(); ++k) {
207 const auto p = subject[k], q = subject[(k + 1) % subject.size()];
208 const auto sp = cross(a, b, p), sq = cross(a, b, q);
209 if (sp >= 0)
210 next.push_back(p);
211 if ((sp >= 0) != (sq >= 0)) {
212 const auto t = sp / (sp - sq);
213 next.push_back({p.x + t * (q.x - p.x), p.y + t * (q.y - p.y)});
214 }
215 }
216 subject = std::move(next);
217 }
218 return std::abs(signedArea(subject));
219}
220}
221#endif
bool contains(const std::vector< double > &x, const std::vector< double > &y, Ring ring, double px, double py)
Definition PolygonRings.hpp:31
Definition FootprintGeometry.hpp:22
std::string triangulate(Ring &p, std::vector< Ring > &triangles, const std::function< bool()> &running={})
Validate a simple ring and triangulate it by counterclockwise ear clipping.
Definition FootprintGeometry.hpp:117
std::vector< Point > Ring
Definition FootprintGeometry.hpp:26
double signedArea(const Ring &p)
Sum a polygon ring's signed triangle-fan area.
Definition FootprintGeometry.hpp:46
double tolerance(const Ring &p)
Derive a scale-relative tolerance for ring cross products and areas.
Definition FootprintGeometry.hpp:61
bool between(Point a, Point b, Point p, double eps)
Test whether a point lies on a closed segment within a cross-product tolerance.
Definition FootprintGeometry.hpp:78
double intersectionArea(Ring subject, const Ring &clip)
Clip a polygon against a counterclockwise convex ring and return its area.
Definition FootprintGeometry.hpp:202
double cross(Point a, Point b, Point c)
Compute the oriented twice-area of a triangle in planar coordinates.
Definition FootprintGeometry.hpp:35
bool intersects(Point a, Point b, Point c, Point d, double eps)
Test closed planar segments for proper crossing or endpoint/collinear contact.
Definition FootprintGeometry.hpp:92
double * y
Definition odesolve.c:37
Definition FootprintGeometry.hpp:23
double y
Definition FootprintGeometry.hpp:24
double x
Definition FootprintGeometry.hpp:24