载入中...
搜索中...
未找到
UrbanGeometry.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6#include <vector>
7
8namespace eve::procgen::urban {
9namespace {
10
11constexpr double kEps = 1e-9;
12
13int wrapIndex(int i, int n) {
14 if (n <= 0) return 0;
15 return ((i % n) + n) % n;
16}
17
18bool samePoint(const Vec2& a, const Vec2& b, double eps) {
19 return std::fabs(a.x - b.x) <= eps && std::fabs(a.y - b.y) <= eps;
20}
21
22bool onSegmentTol(const Vec2& p, const Vec2& a, const Vec2& b, double eps) {
23 const Vec2 ab = b - a;
24 const double l2 = lengthSq(ab);
25 if (l2 <= eps * eps) return samePoint(p, a, eps);
26 const double t = dot(p - a, ab) / l2;
27 if (t < -eps || t > 1.0 + eps) return false;
28 const Vec2 proj = a + ab * t;
29 return distance(p, proj) <= eps;
30}
31
32} // namespace
33
34double signedArea(const Polygon& poly) {
35 const size_t n = poly.size();
36 if (n < 3) return 0.0;
37 double s = 0.0;
38 for (size_t i = 0; i < n; ++i) {
39 const Vec2& a = poly[i];
40 const Vec2& b = poly[wrapIndex(int(i) + 1, int(n))];
41 s += cross(a, b);
42 }
43 return s * 0.5;
44}
45
46double area(const Polygon& poly) { return std::fabs(signedArea(poly)); }
47
48double polylineLength(const Polyline& pl) {
49 double l = 0.0;
50 for (size_t i = 1; i < pl.size(); ++i) l += distance(pl[i - 1], pl[i]);
51 return l;
52}
53
54Vec2 centroid(const Polygon& poly) {
55 const size_t n = poly.size();
56 if (n == 0) return {0, 0};
57 if (n == 1) return poly[0];
58 if (n == 2) return (poly[0] + poly[1]) * 0.5;
59 double a = 0.0;
60 Vec2 c{0, 0};
61 for (size_t i = 0; i < n; ++i) {
62 const Vec2& p = poly[i];
63 const Vec2& q = poly[wrapIndex(int(i) + 1, int(n))];
64 const double cr = cross(p, q);
65 a += cr;
66 c = c + (p + q) * cr;
67 }
68 if (std::fabs(a) < 1e-12) return poly[0];
69 return c / (3.0 * a);
70}
71
72double perimeter(const Polygon& poly) {
73 double p = 0.0;
74 for (size_t i = 0; i < poly.size(); ++i) p += distance(poly[i], poly[wrapIndex(int(i) + 1, int(poly.size()))]);
75 return p;
76}
77
78bool ensureCCW(Polygon& poly) {
79 if (signedArea(poly) < 0.0) {
80 std::reverse(poly.begin(), poly.end());
81 return true;
82 }
83 return false;
84}
85
86void cleanupRing(Polygon& poly, double eps) {
87 Polygon out;
88 out.reserve(poly.size());
89 for (const Vec2& p : poly) {
90 if (!out.empty() && samePoint(out.back(), p, eps)) continue;
91 // Drop points that lie (almost) on the segment between their neighbours.
92 if (out.size() >= 2) {
93 const Vec2& a = out[out.size() - 2];
94 const Vec2& b = out.back();
95 if (distanceToSegment(p, a, b) <= eps) {
96 out.back() = p;
97 continue;
98 }
99 }
100 out.push_back(p);
101 }
102 // Wrap-around cleanup: first vs last.
103 while (out.size() > 3 && samePoint(out.front(), out.back(), eps)) out.pop_back();
104 if (out.size() >= 3) {
105 const Vec2& a = out[out.size() - 1];
106 const Vec2& b = out[0];
107 if (distanceToSegment(out[1], a, b) <= eps) out[1] = b;
108 }
109 poly = std::move(out);
110}
111
112bool pointInPolygon(const Vec2& p, const Polygon& poly) {
113 if (poly.size() < 3) return false;
114 bool inside = false;
115 for (size_t i = 0, j = poly.size() - 1; i < poly.size(); j = i++) {
116 const Vec2& a = poly[i];
117 const Vec2& b = poly[j];
118 if (pointOnSegment(p, a, b)) return true;
119 const bool crosses = ((a.y > p.y) != (b.y > p.y)) && (p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x);
120 if (crosses) inside = !inside;
121 }
122 return inside;
123}
124
125bool pointOnSegment(const Vec2& p, const Vec2& a, const Vec2& b, double eps) { return onSegmentTol(p, a, b, eps); }
126
127bool segmentsIntersect(const Vec2& a, const Vec2& b, const Vec2& c, const Vec2& d, Vec2* out) {
128 const Vec2 ab = b - a;
129 const Vec2 cd = d - c;
130 const Vec2 ac = c - a;
131 const double denom = cross(ab, cd);
132 const double eps = 1e-12;
133 if (std::fabs(denom) <= eps) return false; // parallel (collinear handled by point tests)
134 const double t = cross(ac, cd) / denom;
135 const double u = cross(ac, ab) / denom;
136 if (t < -eps || t > 1.0 + eps || u < -eps || u > 1.0 + eps) return false;
137 if (out) *out = a + ab * t;
138 return true;
139}
140
141bool segmentIntersectsPolyline(const Vec2& a, const Vec2& b, const Polyline& pl) {
142 for (size_t i = 1; i < pl.size(); ++i) {
143 Vec2 hit;
144 if (segmentsIntersect(a, b, pl[i - 1], pl[i], &hit)) return true;
145 }
146 return false;
147}
148
150 for (size_t i = 1; i < pl.size(); ++i) {
151 for (size_t j = i + 2; j < pl.size(); ++j) {
152 Vec2 hit;
153 if (segmentsIntersect(pl[i - 1], pl[i], pl[j - 1], pl[j], &hit)) return true;
154 }
155 }
156 return false;
157}
158
159bool polygonIsSimple(const Polygon& poly) {
160 const size_t n = poly.size();
161 if (n < 3) return false;
162 for (size_t i = 0; i < n; ++i) {
163 const Vec2& a = poly[i];
164 const Vec2& b = poly[wrapIndex(int(i) + 1, int(n))];
165 for (size_t j = i + 1; j < n; ++j) {
166 const size_t jn = wrapIndex(int(j) + 1, int(n));
167 // Adjacent segments share an endpoint and are allowed to touch.
168 if (j == i + 1) continue; // share poly[j]
169 if (i == 0 && j == n - 1) continue; // wrap-around pair shares poly[0]
170 Vec2 hit;
171 if (segmentsIntersect(a, b, poly[j], poly[jn], &hit)) return false;
172 }
173 }
174 return area(poly) > 1e-12;
175}
176
177double closestPointOnSegment(const Vec2& p, const Vec2& a, const Vec2& b, Vec2* out) {
178 const Vec2 ab = b - a;
179 const double l2 = lengthSq(ab);
180 if (l2 <= 1e-18) {
181 if (out) *out = a;
182 return distance(p, a);
183 }
184 const double t = std::clamp(dot(p - a, ab) / l2, 0.0, 1.0);
185 if (out) *out = a + ab * t;
186 return distance(p, a + ab * t);
187}
188
190 const size_t n = poly.size();
191 if (n == 0) return p;
192 double best = std::numeric_limits<double>::infinity();
193 Vec2 bestP = poly[0];
194 int bestEdge = 0;
195 double bestT = 0.0;
196 for (size_t i = 0; i < n; ++i) {
197 const Vec2& a = poly[i];
198 const Vec2& b = poly[wrapIndex(int(i) + 1, int(n))];
199 const Vec2 ab = b - a;
200 const double l2 = lengthSq(ab);
201 double t = 0.0;
202 if (l2 > 1e-18) t = std::clamp(dot(p - a, ab) / l2, 0.0, 1.0);
203 const Vec2 q = a + ab * t;
204 const double d = distance(p, q);
205 if (d < best) {
206 best = d;
207 bestP = q;
208 bestEdge = int(i);
209 bestT = t;
210 }
211 }
212 if (pos) {
213 pos->edgeIndex = bestEdge;
214 pos->t = bestT;
215 }
216 return bestP;
217}
218
220 const size_t n = poly.size();
221 if (n == 0) return {0, 0};
222 const double total = perimeter(poly);
223 double t = std::fmod(s, total);
224 if (t < 0) t += total;
225 double acc = 0.0;
226 for (size_t i = 0; i < n; ++i) {
227 const Vec2& a = poly[i];
228 const Vec2& b = poly[wrapIndex(int(i) + 1, int(n))];
229 const double seg = distance(a, b);
230 if (acc + seg >= t || i + 1 == n) {
231 const double f = seg > 1e-12 ? (t - acc) / seg : 0.0;
232 if (pos) {
233 pos->edgeIndex = int(i);
234 pos->t = std::clamp(f, 0.0, 1.0);
235 }
236 return a + (b - a) * f;
237 }
238 acc += seg;
239 }
240 if (pos) {
241 pos->edgeIndex = 0;
242 pos->t = 0.0;
243 }
244 return poly[0];
245}
246
247std::vector<BoundarySample> sampleBoundary(const Polygon& poly, int count) {
248 std::vector<BoundarySample> samples;
249 const size_t n = poly.size();
250 if (n < 3) return samples;
251 const int k = std::max(8, count);
252 const double total = perimeter(poly);
253 samples.reserve(size_t(k));
254 for (int i = 0; i < k; ++i) {
255 const double s = double(i) * total / double(k);
257 const Vec2 p = pointAtBoundaryLength(poly, s, &pos);
258 const Vec2& a = poly[size_t(pos.edgeIndex)];
259 const Vec2& b = poly[wrapIndex(pos.edgeIndex + 1, int(n))];
260 const Vec2 dir = normalize(b - a);
261 samples.push_back({p, std::atan2(dir.y, dir.x), s});
262 }
263 return samples;
264}
265
266double includedAngleDeg(const Vec2& u, const Vec2& v) {
267 const Vec2 nu = normalize(u);
268 const Vec2 nv = normalize(v);
269 const double c = std::clamp(dot(nu, nv), -1.0, 1.0);
270 return std::acos(c) * 180.0 / 3.14159265358979323846;
271}
272
273bool isCollinear(const Vec2& prev, const Vec2& shared, const Vec2& next) {
274 return includedAngleDeg(prev - shared, next - shared) > 135.0;
275}
276
278 const size_t n = ring.size();
279 if (n <= 3) return ring;
280 Polygon out;
281 // Group maximal runs of collinear consecutive edges; each run keeps first+last vertex.
282 std::vector<char> merged(n, 0);
283 for (size_t i = 0; i < n; ++i) {
284 const Vec2& prev = ring[wrapIndex(int(i) - 1, int(n))];
285 const Vec2& cur = ring[i];
286 const Vec2& next = ring[wrapIndex(int(i) + 1, int(n))];
287 if (isCollinear(prev, cur, next)) merged[i] = 1;
288 }
289 for (size_t i = 0; i < n; ++i) {
290 if (merged[i]) continue;
291 out.push_back(ring[i]);
292 }
293 if (out.size() < 3) return ring;
294 // Wrap-around: if the ring starts/ends with merged vertices, drop them at the seam.
295 while (out.size() > 3) {
296 const Vec2& a = out[out.size() - 2];
297 const Vec2& b = out.back();
298 const Vec2& c = out[0];
299 if (isCollinear(a, b, c)) {
300 out.pop_back();
301 continue;
302 }
303 const Vec2& d = out[1];
304 if (isCollinear(b, out[0], d)) {
305 out.erase(out.begin());
306 continue;
307 }
308 break;
309 }
310 cleanupRing(out);
311 return out;
312}
313
314double shapeIrregularity(const Polygon& approxRing, double gammaAngle, double gammaSide) {
315 const size_t n = approxRing.size();
316 if (n < 3) return std::numeric_limits<double>::infinity();
317 // Interior angles.
318 std::vector<double> angles(n);
319 double angleMean = 0.0;
320 for (size_t i = 0; i < n; ++i) {
321 const Vec2& prev = approxRing[wrapIndex(int(i) - 1, int(n))];
322 const Vec2& cur = approxRing[i];
323 const Vec2& next = approxRing[wrapIndex(int(i) + 1, int(n))];
324 const Vec2 u = normalize(prev - cur);
325 const Vec2 v = normalize(next - cur);
326 const double c = std::clamp(dot(u, v), -1.0, 1.0);
327 // Interior angle in [0, 2π): the smaller angle may be the exterior one for reflex corners.
328 // Use the angle that keeps the polygon inside; for simple CCW polygons the interior
329 // angle is the one on the left side of the direction change.
330 double a = std::acos(c);
331 if (cross(u, v) < 0.0) a = 2.0 * 3.14159265358979323846 - a;
332 angles[i] = a;
333 angleMean += a;
334 }
335 angleMean /= double(n);
336 double sideMean = 0.0;
337 std::vector<double> sides(n);
338 for (size_t i = 0; i < n; ++i) {
339 const double l = distance(approxRing[i], approxRing[wrapIndex(int(i) + 1, int(n))]);
340 sides[i] = l;
341 sideMean += l;
342 }
343 sideMean /= double(n);
344 double angleVar = 0.0;
345 double sideVar = 0.0;
346 for (size_t i = 0; i < n; ++i) {
347 const double da = angles[i] - angleMean;
348 angleVar += da * da;
349 const double dl = sides[i] - sideMean;
350 sideVar += dl * dl;
351 }
352 angleVar /= double(n);
353 sideVar /= double(n);
354 if (sideMean <= 1e-12) return std::numeric_limits<double>::infinity();
355 return gammaAngle * angleVar + gammaSide * sideVar / (sideMean * sideMean);
356}
357
358namespace {
359
361Polygon boundaryChain(const Polygon& poly, const BoundaryPosition& from, const BoundaryPosition& to, const Vec2& pFrom,
362 const Vec2& pTo) {
363 const size_t n = poly.size();
364 Polygon chain;
365 const int startEdge = from.edgeIndex;
366 const int endEdge = to.edgeIndex;
367 if (startEdge == endEdge) {
368 if (from.t <= to.t) {
369 // Both points on the same edge, A before B: the forward chain is the short sub-segment.
370 chain.push_back(pFrom);
371 if (!(pFrom == pTo)) chain.push_back(pTo);
372 return chain;
373 }
374 // A after B on the same edge: the forward chain wraps all the way around.
375 chain.push_back(pFrom);
376 int e = startEdge;
377 while (true) {
378 e = wrapIndex(e + 1, int(n));
379 if (e == endEdge) break;
380 chain.push_back(poly[size_t(e)]);
381 }
382 chain.push_back(poly[size_t(endEdge)]);
383 chain.push_back(pTo);
384 return chain;
385 }
386 chain.push_back(pFrom);
387 int e = wrapIndex(startEdge + 1, int(n));
388 while (e != endEdge) {
389 chain.push_back(poly[size_t(e)]);
390 e = wrapIndex(e + 1, int(n));
391 }
392 if (to.t > 1e-12) chain.push_back(poly[size_t(endEdge)]);
393 chain.push_back(pTo);
394 return chain;
395}
396
397} // namespace
398
399bool splitPolygonByPolyline(const Polygon& poly, const Polyline& split, const BoundaryPosition& posA,
400 const BoundaryPosition& posB, Polygon& outA, Polygon& outB) {
401 if (poly.size() < 3 || split.size() < 2) return false;
402 if (posA.edgeIndex == posB.edgeIndex && std::fabs(posA.t - posB.t) < 1e-9) {
403 return false; // degenerate: same boundary point
404 }
405 const Vec2 pa = split.front();
406 const Vec2 pb = split.back();
407
408 Polygon chainAB = boundaryChain(poly, posA, posB, pa, pb); // boundary A -> B (CCW)
409 Polygon chainBA = boundaryChain(poly, posB, posA, pb, pa); // boundary B -> A (CCW)
410
411 // Loop 1: chain(A->B) + reversed split (B->A)
412 outA = chainAB;
413 for (auto it = split.rbegin(); it != split.rend(); ++it) outA.push_back(*it);
414 // Loop 2: chain(B->A) + split (A->B)
415 outB = chainBA;
416 for (const Vec2& p : split) outB.push_back(p);
417
418 cleanupRing(outA);
419 cleanupRing(outB);
420 ensureCCW(outA);
421 ensureCCW(outB);
422 return polygonIsSimple(outA) && polygonIsSimple(outB);
423}
424
425bool validSplit(const Polygon& poly, const Polyline& split, double minHalfArea, Polygon* outA, Polygon* outB,
426 double* fracA) {
427 if (split.size() < 2) return false;
428 // Endpoints must lie on the boundary.
429 BoundaryPosition posA, posB;
430 const Vec2 pa = closestPointOnBoundary(poly, split.front(), &posA);
431 const Vec2 pb = closestPointOnBoundary(poly, split.back(), &posB);
432 const double snapTol = 1e-4 * std::max(1.0, perimeter(poly));
433 if (distance(pa, split.front()) > snapTol || distance(pb, split.back()) > snapTol) return false;
434 if (distance(pa, pb) <= 1e-9) return false;
435 // Every segment midpoint must be strictly inside the polygon (endpoints on the boundary).
436 for (size_t i = 1; i < split.size(); ++i) {
437 const Vec2 mid = (split[i - 1] + split[i]) * 0.5;
438 if (!pointInPolygon(mid, poly)) return false;
439 }
440 if (polylineSelfIntersects(split)) return false;
441
442 Polygon a, b;
443 Polyline snapped = split;
444 snapped.front() = pa;
445 snapped.back() = pb;
446 if (!splitPolygonByPolyline(poly, snapped, posA, posB, a, b)) return false;
447 const double areaPoly = area(poly);
448 const double areaA = area(a);
449 const double areaB = area(b);
450 if (areaPoly <= 1e-12) return false;
451 // Sum-of-areas invariant.
452 if (std::fabs(areaA + areaB - areaPoly) > 0.02 * areaPoly) return false;
453 if (areaA < minHalfArea || areaB < minHalfArea) return false;
454 const double fa = areaA / areaPoly;
455 if (fa < 0.02 || fa > 0.98) return false;
456 if (outA) *outA = std::move(a);
457 if (outB) *outB = std::move(b);
458 if (fracA) *fracA = fa;
459 return true;
460}
461
462bool triangulatePolygon(const Polygon& poly, std::vector<int>& outTriangles) {
463 const size_t n = poly.size();
464 outTriangles.clear();
465 if (n < 3) return false;
466 if (!polygonIsSimple(poly)) return false;
467 std::vector<int> idx(n);
468 for (size_t i = 0; i < n; ++i) idx[size_t(i)] = int(i);
469 int guard = 0;
470 while (idx.size() > 3 && guard++ < 100000) {
471 bool clipped = false;
472 const size_t m = idx.size();
473 for (size_t i = 0; i < m; ++i) {
474 const int iPrev = idx[wrapIndex(int(i) - 1, int(m))];
475 const int iCur = idx[size_t(i)];
476 const int iNext = idx[wrapIndex(int(i) + 1, int(m))];
477 const Vec2& a = poly[size_t(iPrev)];
478 const Vec2& b = poly[size_t(iCur)];
479 const Vec2& c = poly[size_t(iNext)];
480 // Convex ear? For CCW polygon, cross(ab, bc) > 0 means the corner is convex.
481 if (cross(b - a, c - b) <= 0.0) continue;
482 // No other vertex inside the ear triangle.
483 bool empty = true;
484 for (size_t j = 0; j < m; ++j) {
485 const int v = idx[size_t(j)];
486 if (v == iPrev || v == iCur || v == iNext) continue;
487 if (pointInPolygon(poly[size_t(v)], {a, b, c})) {
488 empty = false;
489 break;
490 }
491 }
492 if (!empty) continue;
493 outTriangles.push_back(iPrev);
494 outTriangles.push_back(iCur);
495 outTriangles.push_back(iNext);
496 idx.erase(idx.begin() + long(i));
497 clipped = true;
498 break;
499 }
500 if (!clipped) {
501 outTriangles.clear();
502 return false;
503 }
504 }
505 if (idx.size() == 3) {
506 outTriangles.push_back(idx[0]);
507 outTriangles.push_back(idx[1]);
508 outTriangles.push_back(idx[2]);
509 }
510 return outTriangles.size() >= 3;
511}
512
513double distanceToSegment(const Vec2& p, const Vec2& a, const Vec2& b) {
514 Vec2 unused;
515 return closestPointOnSegment(p, a, b, &unused);
516}
517
518} // namespace eve::procgen::urban
int mid
Definition AnimSmr.cpp:120
std::string from
const std::string & s
bool split
Definition CaveMesh.cpp:123
glm::vec4 p[6]
float u
Definition Grass.cpp:233
float area
Definition Grass.cpp:62
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
std::uint32_t ab
std::uint32_t ac
float v
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
Vec3 centroid
int idx
float f
bool hit
float d
float t
glm::mat4 proj
std::uint32_t count
std::map< Cell, int > best
int sides
Definition TreeMesh.cpp:322
V3 dir
Definition TreeMesh.cpp:150
std::vector< char > inside
float m[16]
Polygon approximatePolygon(const Polygon &ring)
Simplify a ring to its approximate polygon: consecutive edges with included angle > 135° are merged i...
bool validSplit(const Polygon &poly, const Polyline &split, double minHalfArea, Polygon *outA, Polygon *outB, double *fracA)
Validity of a candidate split: both halves simple, positive area, inside the original.
std::vector< Vec2 > Polyline
Open polyline (e.g. a streamline candidate or a street centerline).
Definition UrbanTypes.h:57
double includedAngleDeg(const Vec2 &u, const Vec2 &v)
Included angle in degrees at the shared vertex between edge u (prev->shared) and v (shared->next),...
bool pointInPolygon(const Vec2 &p, const Polygon &poly)
Point-in-polygon test (ray casting; boundary counts as inside).
Vec2 pointAtBoundaryLength(const Polygon &poly, double s, BoundaryPosition *pos)
Interpolate the boundary point at arc length s in [0, perimeter).
bool splitPolygonByPolyline(const Polygon &poly, const Polyline &split, const BoundaryPosition &posA, const BoundaryPosition &posB, Polygon &outA, Polygon &outB)
Split a CCW simple polygon by a polyline whose endpoints lie on the boundary and whose interior point...
bool triangulatePolygon(const Polygon &poly, std::vector< int > &outTriangles)
Triangulate a simple polygon by ear clipping; returns CCW triangles (3*i..3*i+2).
bool polylineSelfIntersects(const Polyline &pl)
True if any two non-adjacent segments of the open polyline cross.
double closestPointOnSegment(const Vec2 &p, const Vec2 &a, const Vec2 &b, Vec2 *out)
Closest point on segment a-b; returns distance and writes out.
double distanceToSegment(const Vec2 &p, const Vec2 &a, const Vec2 &b)
Raster helper: does the pixel-center fall within eps of segment a-b?
Vec2 normalize(const Vec2 &a)
Normalize.
Definition UrbanTypes.h:44
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
std::vector< Vec2 > Polygon
Closed polygon ring, stored CCW, without repeating the first point.
Definition UrbanTypes.h:55
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
bool segmentsIntersect(const Vec2 &a, const Vec2 &b, const Vec2 &c, const Vec2 &d, Vec2 *out)
True if the open segments a-b and c-d properly cross; out receives the crossing.
double perimeter(const Polygon &poly)
Perimeter length of a closed ring.
double lengthSq(const Vec2 &a)
Length sq.
Definition UrbanTypes.h:42
std::vector< BoundarySample > sampleBoundary(const Polygon &poly, int count)
Uniformly sample the polygon boundary (approx. count samples, at least 8). Samples are ordered along ...
bool polygonIsSimple(const Polygon &poly)
Simple polygon test: no self intersections among non-adjacent ring edges.
double signedArea(const Polygon &poly)
Return the raw (possibly negative) signed area of a polygon ring.
bool pointOnSegment(const Vec2 &p, const Vec2 &a, const Vec2 &b, double eps)
True if p lies on segment a-b (within tolerance).
double shapeIrregularity(const Polygon &approxRing, double gammaAngle, double gammaSide)
Shape irregularity metric of Eq. (1) over the approximate polygon: I = γ1·(1/N)·Σ(θi−θ̄)² + γ2·(1/(N·...
void cleanupRing(Polygon &poly, double eps)
Remove consecutive duplicate points (within eps) and points that create zero spikes.
double polylineLength(const Polyline &pl)
Total length of an open polyline.
bool segmentIntersectsPolyline(const Vec2 &a, const Vec2 &b, const Polyline &pl)
True if a segment crosses any segment of an open polyline (excluding shared endpoints).
Vec2 closestPointOnBoundary(const Polygon &poly, const Vec2 &p, BoundaryPosition *pos)
Closest point on the polygon boundary; writes edge index + t in [0,1].
bool ensureCCW(Polygon &poly)
Ensure the ring is CCW (positive signed area); returns whether it was flipped.
bool isCollinear(const Vec2 &prev, const Vec2 &shared, const Vec2 &next)
True when two consecutive edges are considered collinear (included angle > 135°).
Where a point sits on the polygon boundary: edge edgeIndex at parameter t in [0,1].
Definition UrbanTypes.h:152
Minimal 2D vector used by the urban layout algorithms (paper coordinates).
Definition UrbanTypes.h:15