载入中...
搜索中...
未找到
UrbanGenerator.cpp
浏览该文件的文档.
2
5
6#include <algorithm>
7#include <cmath>
8#include <cstdint>
9#include <functional>
10#include <limits>
11#include <numeric>
12#include <queue>
13#include <random>
14#include <string>
15#include <unordered_map>
16#include <unordered_set>
17#include <utility>
18#include <vector>
19
20namespace eve::procgen::urban {
21namespace {
22
23constexpr double kPi = 3.14159265358979323846;
24
25} // namespace
26
28 std::vector<Polygon> parcels; // CCW world-coord rings
30 std::vector<Vec2> corners;
31 std::vector<Parcel> rings; // rings over corners
32 std::vector<GraphEdge> edges;
33 std::vector<std::vector<int>> parcelEdges; // edge ids per parcel
34 std::vector<char> edgeStreet; // mirrors edges[].isStreet
35 std::vector<char> cornerOnLandBoundary;
36 std::vector<std::pair<Vec2, Vec2>> streetSegs; // street network in world coords
37 std::vector<char> landEdgeStreet; // per original land edge
38 std::vector<std::vector<int>> streetChains; // street polylines as corner indices
39 std::vector<int> junctionVertices;
40};
41
42namespace {
43
44struct EdgeKey {
45 int a = 0;
46 int b = 0;
47 bool operator==(const EdgeKey& o) const { return a == o.a && b == o.b; }
48};
49
50struct EdgeKeyHash {
51 size_t operator()(const EdgeKey& k) const { return (size_t(uint32_t(k.a)) << 32) ^ size_t(uint32_t(k.b)); }
52};
53
54EdgeKey makeKey(int a, int b) {
55 if (a > b) std::swap(a, b);
56 return {a, b};
57}
58
60class WeldMap {
61public:
62 explicit WeldMap(double eps) : eps_(eps) {}
63
64 int add(const Vec2& p, std::vector<Vec2>& corners) {
65 const int qx = int(std::floor(p.x / eps_));
66 const int qy = int(std::floor(p.y / eps_));
67 for (int dx = -1; dx <= 1; ++dx) {
68 for (int dy = -1; dy <= 1; ++dy) {
69 auto it = buckets_.find(key(qx + dx, qy + dy));
70 if (it == buckets_.end()) continue;
71 for (const int idx : it->second) {
72 if (distance(p, corners[size_t(idx)]) <= eps_ * 1.5) return idx;
73 }
74 }
75 }
76 const int idx = int(corners.size());
77 corners.push_back(p);
78 buckets_[key(qx, qy)].push_back(idx);
79 return idx;
80 }
81
82private:
83 static uint64_t key(int qx, int qy) { return (uint64_t(uint32_t(qx)) << 32) ^ uint64_t(uint32_t(qy)); }
84
85 double eps_;
86 std::unordered_map<uint64_t, std::vector<int>> buckets_;
87};
88
90std::vector<char> rollBoundaryStreetFlags(const Polygon& land, int mode, double fraction, std::mt19937& rng) {
91 const size_t n = land.size();
92 std::vector<char> flags(n, 1);
93 if (mode == 1) {
94 std::fill(flags.begin(), flags.end(), 0);
95 return flags;
96 }
97 if (mode == 2 && n > 0) {
98 std::bernoulli_distribution dist(std::clamp(fraction, 0.0, 1.0));
99 int any = 0;
100 for (size_t i = 0; i < n; ++i) {
101 flags[i] = dist(rng) ? 1 : 0;
102 any += flags[i];
103 }
104 if (any == 0) flags[0] = 1; // keep at least one street
105 }
106 return flags;
107}
108
109double landScale(const Polygon& land) {
110 if (land.empty()) return 1.0;
111 double minX = land[0].x, minY = land[0].y, maxX = land[0].x, maxY = land[0].y;
112 for (const Vec2& p : land) {
113 minX = std::min(minX, p.x);
114 minY = std::min(minY, p.y);
115 maxX = std::max(maxX, p.x);
116 maxY = std::max(maxY, p.y);
117 }
118 return std::max(1.0, std::max(maxX - minX, maxY - minY));
119}
120
121bool cornerOnLand(const Vec2& p, const Polygon& land, double eps) {
122 const size_t n = land.size();
123 for (size_t i = 0; i < n; ++i) {
124 const Vec2& a = land[i];
125 const Vec2& b = land[size_t((i + 1) % n)];
126 if (distanceToSegment(p, a, b) <= eps) return true;
127 }
128 return false;
129}
130
132bool edgeIsOnStreet(const Vec2& a, const Vec2& b, const std::vector<std::pair<Vec2, Vec2>>& streetSegs, double eps) {
133 for (const auto& s : streetSegs) {
134 if (distanceToSegment(a, s.first, s.second) <= eps && distanceToSegment(b, s.first, s.second) <= eps)
135 return true;
136 }
137 return false;
138}
139
140void rebuildGraph(UrbanGenerator::GenState& st) {
141 const double eps = 1e-6 * landScale(st.land);
142 st.corners.clear();
143 st.rings.clear();
144 st.rings.resize(st.parcels.size());
145 WeldMap weld(eps);
146
147 for (size_t p = 0; p < st.parcels.size(); ++p) {
148 Parcel ring;
149 ring.ring.reserve(st.parcels[p].size());
150 for (const Vec2& v : st.parcels[p]) ring.ring.push_back(weld.add(v, st.corners));
151 st.rings[size_t(p)] = std::move(ring);
152 }
153
154 std::unordered_map<EdgeKey, int, EdgeKeyHash> edgeMap;
155 st.edges.clear();
156 st.parcelEdges.assign(st.parcels.size(), {});
157 for (size_t p = 0; p < st.rings.size(); ++p) {
158 const std::vector<int>& ring = st.rings[p].ring;
159 const size_t m = ring.size();
160 for (size_t i = 0; i < m; ++i) {
161 const EdgeKey k = makeKey(ring[i], ring[size_t((i + 1) % m)]);
162 auto it = edgeMap.find(k);
163 int eid = -1;
164 if (it == edgeMap.end()) {
165 eid = int(st.edges.size());
166 st.edges.push_back({k.a, k.b, false});
167 edgeMap[k] = eid;
168 } else {
169 eid = it->second;
170 }
171 st.parcelEdges[size_t(p)].push_back(eid);
172 }
173 }
174
175 st.edgeStreet.assign(st.edges.size(), 0);
176 for (size_t i = 0; i < st.edges.size(); ++i) {
177 const Vec2& a = st.corners[size_t(st.edges[i].a)];
178 const Vec2& b = st.corners[size_t(st.edges[i].b)];
179 st.edgeStreet[size_t(i)] = edgeIsOnStreet(a, b, st.streetSegs, eps) ? 1 : 0;
180 st.edges[size_t(i)].isStreet = st.edgeStreet[size_t(i)] != 0;
181 }
182
183 st.cornerOnLandBoundary.assign(st.corners.size(), 0);
184 for (size_t i = 0; i < st.corners.size(); ++i) {
185 if (cornerOnLand(st.corners[i], st.land, eps)) st.cornerOnLandBoundary[size_t(i)] = 1;
186 }
187}
188
189struct UnionFind {
190 explicit UnionFind(int n) : parent_(size_t(n)) { std::iota(parent_.begin(), parent_.end(), 0); }
191 int find(int x) {
192 while (parent_[size_t(x)] != x) {
193 parent_[size_t(x)] = parent_[size_t(parent_[size_t(x)])];
194 x = parent_[size_t(x)];
195 }
196 return x;
197 }
198 void unite(int a, int b) {
199 const int ra = find(a);
200 const int rb = find(b);
201 if (ra != rb) parent_[size_t(ra)] = rb;
202 }
203
204private:
205 std::vector<int> parent_;
206};
207
208struct AccessPath {
209 std::vector<int> vertices;
210 std::vector<int> edges;
211 double length = 0.0;
212 int turns = 0;
213 int covered = 0;
214};
215
216double interiorAngle(const std::vector<Vec2>& corners, const std::vector<int>& ring, size_t i) {
217 const size_t m = ring.size();
218 if (m < 3) return 0.0;
219 const Vec2& prev = corners[size_t(ring[(i + m - 1) % m])];
220 const Vec2& cur = corners[size_t(ring[i])];
221 const Vec2& next = corners[size_t(ring[(i + 1) % m])];
222 const Vec2 u = normalize(prev - cur);
223 const Vec2 v = normalize(next - cur);
224 double a = std::acos(std::clamp(dot(u, v), -1.0, 1.0));
225 if (cross(u, v) < 0.0) a = 2.0 * kPi - a;
226 return a;
227}
228
229} // namespace
230
231UrbanGenerator::UrbanGenerator(UrbanOptions opts) : opts_(std::move(opts)), rng_(opts_.seed) {}
232
233namespace {
234
236void optimizeLayoutGeometry(UrbanGenerator::GenState& st, const UrbanOptions& opts) {
237 const double scale = landScale(st.land);
238 const double eps = 1e-6 * scale;
239 const int n = int(st.corners.size());
240 const int parcels = int(st.rings.size());
241 double landA = 0.0;
242 for (const Polygon& poly : st.parcels) landA += area(poly);
243 const double parcelScale = std::sqrt(std::max(1e-9, landA) / double(std::max(1, parcels)));
244 const std::vector<Vec2> v0 = st.corners;
245 if (n == 0 || parcels == 0) return;
246
247 // Approximate-polygon corners per parcel.
248 std::vector<std::vector<int>> approxCorners;
249 std::vector<size_t> approxSides;
250 approxCorners.resize(size_t(parcels));
251 approxSides.resize(size_t(parcels));
252 for (int p = 0; p < parcels; ++p) {
253 const std::vector<int>& ring = st.rings[size_t(p)].ring;
254 const size_t m = ring.size();
255 for (size_t i = 0; i < m; ++i) {
256 const int prev = ring[(i + m - 1) % m];
257 const int cur = ring[i];
258 const int next = ring[(i + 1) % m];
259 if (!isCollinear(st.corners[size_t(prev)], st.corners[size_t(cur)], st.corners[size_t(next)])) {
260 approxCorners[size_t(p)].push_back(cur);
261 }
262 }
263 approxSides[size_t(p)] = approxCorners[size_t(p)].size();
264 }
265
266 // Land-boundary corners: those connecting two collinear boundary edges may slide
267 // (paper Eq. 9); all other boundary corners are fixed (paper Eq. 10).
268 std::vector<char> fixed(size_t(n), 0);
269 std::vector<char> slide(size_t(n), 0);
270 {
271 std::vector<std::pair<double, int>> boundary;
272 for (int v = 0; v < n; ++v) {
273 if (!st.cornerOnLandBoundary[size_t(v)]) continue;
274 BoundaryPosition pos;
275 closestPointOnBoundary(st.land, st.corners[size_t(v)], &pos);
276 double s = 0.0;
277 for (int e = 0; e < pos.edgeIndex; ++e)
278 s += distance(st.land[size_t(e)], st.land[size_t((e + 1) % st.land.size())]);
279 s +=
280 pos.t * distance(st.land[size_t(pos.edgeIndex)], st.land[size_t((pos.edgeIndex + 1) % st.land.size())]);
281 boundary.emplace_back(s, v);
282 }
283 std::sort(boundary.begin(), boundary.end());
284 const size_t m = boundary.size();
285 for (size_t i = 0; i < m; ++i) {
286 const int v = boundary[i].second;
287 if (m == 1) {
288 fixed[size_t(v)] = 1;
289 continue;
290 }
291 const int prev = boundary[(i + m - 1) % m].second;
292 const int next = boundary[(i + 1) % m].second;
293 if (isCollinear(st.corners[size_t(prev)], st.corners[size_t(v)], st.corners[size_t(next)])) {
294 slide[size_t(v)] = 1;
295 } else {
296 fixed[size_t(v)] = 1;
297 }
298 }
299 }
300
301 std::vector<Vec2> moves(size_t(n), {0, 0});
302 const double maxMove = 0.04 * parcelScale;
303 for (int iter = 0; iter < std::max(1, opts.optimizeIterations); ++iter) {
304 std::fill(moves.begin(), moves.end(), Vec2{0, 0});
305
306 // E_side: Laplacian over collinear parcel side runs.
307 for (int p = 0; p < parcels; ++p) {
308 const std::vector<int>& ring = st.rings[size_t(p)].ring;
309 const size_t m = ring.size();
310 for (size_t i = 0; i < m; ++i) {
311 const int prev = ring[(i + m - 1) % m];
312 const int cur = ring[i];
313 const int next = ring[(i + 1) % m];
314 if (!isCollinear(st.corners[size_t(prev)], st.corners[size_t(cur)], st.corners[size_t(next)])) continue;
315 const Vec2 L = st.corners[size_t(prev)] - st.corners[size_t(cur)] * 2.0 + st.corners[size_t(next)];
316 moves[size_t(cur)] = moves[size_t(cur)] + L * opts.optSide;
317 }
318 }
319 // E_stre: Laplacian over street chains (interior vertices).
320 for (const std::vector<int>& chain : st.streetChains) {
321 for (size_t i = 1; i + 1 < chain.size(); ++i) {
322 const Vec2 L = st.corners[size_t(chain[i - 1])] - st.corners[size_t(chain[i])] * 2.0 +
323 st.corners[size_t(chain[i + 1])];
324 moves[size_t(chain[i])] = moves[size_t(chain[i])] + L * opts.optStre;
325 }
326 }
327 // E_junc: at junctions, push incident street directions toward 90°.
328 for (const int jv : st.junctionVertices) {
329 std::vector<Vec2> dirs;
330 for (const std::vector<int>& chain : st.streetChains) {
331 if (chain.size() >= 2 && chain.front() == jv)
332 dirs.push_back(st.corners[size_t(chain[1])] - st.corners[size_t(jv)]);
333 if (chain.size() >= 2 && chain.back() == jv)
334 dirs.push_back(st.corners[size_t(chain[chain.size() - 2])] - st.corners[size_t(jv)]);
335 }
336 for (size_t a = 0; a < dirs.size(); ++a) {
337 for (size_t b = a + 1; b < dirs.size(); ++b) {
338 const double la = length(dirs[a]);
339 const double lb = length(dirs[b]);
340 if (la < 1e-9 || lb < 1e-9) continue;
341 const double s = dot(dirs[a], dirs[b]) / (la * lb);
342 // Opposite (collinear) directions are the same street passing through;
343 // only genuine crossings should be pushed toward 90°.
344 if (s < -0.707) continue;
345 const Vec2 pa = normalize(perpendicular(dirs[b])) * (s * 0.5);
346 const Vec2 pb = normalize(perpendicular(dirs[a])) * (s * 0.5);
347 moves[size_t(jv)] = moves[size_t(jv)] - (pa + pb) * opts.optJunc * 0.5;
348 for (const auto& chain : st.streetChains) {
349 if (chain.size() < 2) continue;
350 if (chain.front() == jv) moves[size_t(chain[1])] = moves[size_t(chain[1])] + pa * opts.optJunc;
351 if (chain.back() == jv)
352 moves[size_t(chain[chain.size() - 2])] =
353 moves[size_t(chain[chain.size() - 2])] + pb * opts.optJunc;
354 }
355 }
356 }
357 }
358 // E_regu: rotate each approximate-polygon corner toward the regular angle
359 // (paper Eq. 4; target angle (n-2)π/n of the approximate polygon).
360 for (int v = 0; v < n; ++v) {
361 if (fixed[size_t(v)]) continue;
362 double err = 0.0;
363 Vec2 bisector{0, 0};
364 for (int p = 0; p < parcels; ++p) {
365 const auto& ring = st.rings[size_t(p)].ring;
366 const size_t sides = approxSides[size_t(p)];
367 if (sides < 3) continue;
368 for (size_t i = 0; i < ring.size(); ++i) {
369 if (ring[i] != v) continue;
370 bool isCorner = false;
371 for (const int c : approxCorners[size_t(p)])
372 if (c == v) {
373 isCorner = true;
374 break;
375 }
376 if (!isCorner) continue;
377 const double target = (double(sides) - 2.0) * kPi / double(sides);
378 const double theta = interiorAngle(st.corners, ring, i);
379 const size_t m = ring.size();
380 const Vec2 prev = st.corners[size_t(ring[(i + m - 1) % m])];
381 const Vec2 next = st.corners[size_t(ring[(i + 1) % m])];
382 const Vec2 b =
383 normalize(normalize(prev - st.corners[size_t(v)]) + normalize(next - st.corners[size_t(v)]));
384 if (length(b) > 0.5) {
385 err += std::clamp(target - theta, -0.6, 0.6) * 0.5;
386 bisector = bisector + b;
387 }
388 }
389 }
390 if (std::fabs(err) > 1e-6 && length(bisector) > 0.5) {
391 moves[size_t(v)] = moves[size_t(v)] + normalize(bisector) * (err * opts.optRegu * 0.15 * parcelScale);
392 }
393 }
394 // E_close: gentle anchor.
395 for (int v = 0; v < n; ++v) {
396 moves[size_t(v)] = moves[size_t(v)] + (v0[size_t(v)] - st.corners[size_t(v)]) * (opts.optClose * 0.04);
397 }
398
399 std::vector<Vec2> nextPos = st.corners;
400 for (int v = 0; v < n; ++v) {
401 if (fixed[size_t(v)]) continue;
402 Vec2 d = moves[size_t(v)];
403 const double dl = length(d);
404 if (dl > maxMove) d = d * (maxMove / dl);
405 Vec2 np = st.corners[size_t(v)] + d;
406 if (slide[size_t(v)]) {
407 BoundaryPosition pos;
408 closestPointOnBoundary(st.land, st.corners[size_t(v)], &pos);
409 const Vec2& e0 = st.land[size_t(pos.edgeIndex)];
410 const Vec2& e1 = st.land[size_t((pos.edgeIndex + 1) % st.land.size())];
411 int n0 = -1, n1 = -1;
412 for (int u = 0; u < n; ++u) {
413 if (!st.cornerOnLandBoundary[size_t(u)] || u == v) continue;
414 if (distance(st.corners[size_t(u)], e0) <= eps * 4.0) n0 = u;
415 if (distance(st.corners[size_t(u)], e1) <= eps * 4.0) n1 = u;
416 }
417 if (n0 >= 0 && n1 >= 0) {
418 const Vec2 dir = normalize(st.corners[size_t(n1)] - st.corners[size_t(n0)]);
419 const double span = std::max(1e-9, distance(st.corners[size_t(n0)], st.corners[size_t(n1)]));
420 double t = dot(np - st.corners[size_t(n0)], dir) / span;
421 t = std::clamp(t, 0.01, 0.99);
422 np = st.corners[size_t(n0)] + dir * (t * span);
423 } else {
424 np = st.corners[size_t(v)];
425 }
426 }
427 nextPos[size_t(v)] = np;
428 }
429 // Validate: every parcel stays simple, CCW, with positive area.
430 bool ok = true;
431 for (int p = 0; p < parcels; ++p) {
432 const std::vector<int>& ring = st.rings[size_t(p)].ring;
433 Polygon poly;
434 poly.reserve(ring.size());
435 for (const int c : ring) poly.push_back(nextPos[size_t(c)]);
436 if (!polygonIsSimple(poly) || area(poly) < 1e-9 || signedArea(poly) < 0.0) {
437 ok = false;
438 break;
439 }
440 }
441 if (!ok) break;
442 // Moving corners may have collided: verify the welded corner count is unchanged.
443 {
444 WeldMap weld(eps);
445 std::vector<Vec2> check;
446 check.reserve(size_t(n));
447 for (const Vec2& c : nextPos) weld.add(c, check);
448 if (int(check.size()) != n) break;
449 }
450 st.corners = nextPos;
451 }
452
453 // Write the moved corners back into the world-coord parcel rings.
454 for (int p = 0; p < parcels; ++p) {
455 const std::vector<int>& ring = st.rings[size_t(p)].ring;
456 for (size_t i = 0; i < ring.size(); ++i) st.parcels[size_t(p)][i] = st.corners[size_t(ring[i])];
457 }
458}
459
460} // namespace
461
462bool UrbanGenerator::buildBoundaryStreets(GenState& st) {
463 st.land = opts_.land;
464 if (st.land.size() < 3) return false;
465 ensureCCW(st.land);
466 cleanupRing(st.land);
467 if (st.land.size() < 3) return false;
468 st.landEdgeStreet = rollBoundaryStreetFlags(st.land, opts_.boundaryStreetMode, opts_.boundaryStreetFraction, rng_);
469 st.streetSegs.clear();
470 const size_t n = st.land.size();
471 for (size_t i = 0; i < n; ++i) {
472 if (st.landEdgeStreet[i]) {
473 st.streetSegs.emplace_back(st.land[i], st.land[size_t((i + 1) % n)]);
474 }
475 }
476 return true;
477}
478
479bool UrbanGenerator::generate(std::string* error) {
480 layout_.clear();
481 if (opts_.land.size() < 3) {
482 if (error) *error = "urban: land polygon needs at least 3 vertices";
483 return false;
484 }
485 if (opts_.minParcelArea <= 0.0) {
486 if (error) *error = "urban: minParcelArea must be positive";
487 return false;
488 }
489 GenState st;
490 st.parcels.push_back(opts_.land);
491 if (!buildBoundaryStreets(st)) {
492 if (error) *error = "urban: invalid land polygon";
493 return false;
494 }
495
496 rebuildGraph(st);
497 int level = 0;
498 for (; level < std::max(1, opts_.maxLevels); ++level) {
499 std::vector<Polygon> next;
500 splitAllParcels(st, next);
501 if (next.size() == st.parcels.size()) break; // nothing splittable
502 st.parcels = std::move(next);
503 rebuildGraph(st);
504 removeShortEdges(st);
505 rebuildGraph(st);
506 generateStreets(st, level + 1);
507 if (opts_.targetParcels > 0 && int(st.parcels.size()) >= opts_.targetParcels) break;
508 }
509 layout_.levelsUsed = level;
510
511 decomposeStreets(st);
512 if (opts_.optimize) {
513 // Optimization moves vertices but never changes topology (corner count is
514 // validated inside the solver), so street flags transfer by edge index.
515 const std::vector<char> streetFlags = st.edgeStreet;
516 const std::vector<Polygon> preParcels = st.parcels;
517 const std::vector<Vec2> preCorners = st.corners;
518 const auto preSegs = st.streetSegs;
519 double preIrr = 0.0;
520 for (const Polygon& poly : st.parcels)
521 preIrr += shapeIrregularity(approximatePolygon(poly), opts_.gammaAngle, opts_.gammaSide);
522 preIrr = st.parcels.empty() ? 0.0 : preIrr / double(st.parcels.size());
523 optimizeLayoutGeometry(st, opts_);
524 st.edgeStreet = streetFlags;
525 for (size_t e = 0; e < st.edges.size(); ++e) st.edges[size_t(e)].isStreet = streetFlags[size_t(e)] != 0;
526 st.streetSegs.clear();
527 for (size_t e = 0; e < st.edges.size(); ++e) {
528 if (streetFlags[size_t(e)]) {
529 st.streetSegs.emplace_back(st.corners[size_t(st.edges[e].a)], st.corners[size_t(st.edges[e].b)]);
530 }
531 }
532 decomposeStreets(st);
533 double postIrr = 0.0;
534 for (const Polygon& poly : st.parcels)
535 postIrr += shapeIrregularity(approximatePolygon(poly), opts_.gammaAngle, opts_.gammaSide);
536 postIrr = st.parcels.empty() ? 0.0 : postIrr / double(st.parcels.size());
537 // The relaxation must not hurt the layout: if irregularity got worse, keep the
538 // pre-optimization geometry (the initial layout is already regular).
539 if (postIrr > preIrr + 1e-9) {
540 st.parcels = preParcels;
541 st.corners = preCorners;
542 st.streetSegs = preSegs;
543 st.edgeStreet = streetFlags;
544 for (size_t e = 0; e < st.edges.size(); ++e) st.edges[size_t(e)].isStreet = streetFlags[size_t(e)] != 0;
545 decomposeStreets(st);
546 }
547 }
548 finalizeStats(st);
549 return true;
550}
551
552void UrbanGenerator::splitAllParcels(UrbanGenerator::GenState& st, std::vector<Polygon>& nextParcels) {
553 nextParcels.clear();
554 nextParcels.reserve(st.parcels.size() * 2);
555 for (const Polygon& poly : st.parcels) {
556 if (opts_.targetParcels > 0 && int(nextParcels.size()) >= opts_.targetParcels) {
557 nextParcels.push_back(poly);
558 continue;
559 }
560 Polygon a, b;
561 if (splitOneParcel(st, poly, a, b)) {
562 nextParcels.push_back(std::move(a));
563 nextParcels.push_back(std::move(b));
564 } else {
565 nextParcels.push_back(poly);
566 }
567 }
568}
569
570bool UrbanGenerator::splitOneParcel(const GenState& st, const Polygon& poly, Polygon& outA, Polygon& outB) const {
571 if (area(poly) < opts_.minParcelArea * 2.0) return false;
572 std::vector<SplitCandidate> cands = generateSplitCandidates(poly, 20, opts_.minParcelArea);
573 if (cands.empty()) return false;
574
575 const double eps = 1e-6 * landScale(st.land);
576 const Vec2 axis = opts_.orientation == 2 ? Vec2{0.0, 1.0}
577 : opts_.orientation == 1 ? Vec2{1.0, 0.0}
578 : Vec2{0.0, 0.0};
579 const auto accessRatio = [&](const Polygon& half) {
580 const size_t sides = approximatePolygon(half).size();
581 if (sides < 2) return 0.0;
582 const double avgSide = perimeter(half) / double(sides);
583 if (avgSide <= 1e-9) return 0.0;
584 double streetLen = 0.0;
585 const size_t m = half.size();
586 for (size_t i = 0; i < m; ++i) {
587 if (edgeIsOnStreet(half[i], half[size_t((i + 1) % m)], st.streetSegs, eps))
588 streetLen += distance(half[i], half[size_t((i + 1) % m)]);
589 }
590 return streetLen / avgSide;
591 };
592
593 double bestScore = -std::numeric_limits<double>::infinity();
594 Polygon bestA, bestB;
595 for (const SplitCandidate& cand : cands) {
596 Polygon a, b;
597 double frac = 0.0;
598 if (!validSplit(poly, cand.line, opts_.minParcelArea, &a, &b, &frac)) continue;
599 const double qSize = frac / std::max(1e-9, 1.0 - frac); // Si/Sj, Si < Sj
600 const double ia = shapeIrregularity(approximatePolygon(a), opts_.gammaAngle, opts_.gammaSide);
601 const double ib = shapeIrregularity(approximatePolygon(b), opts_.gammaAngle, opts_.gammaSide);
602 const double qRegu = 1.0 / (1.0 + ia + ib);
603 const double ra = accessRatio(a);
604 const double rb = accessRatio(b);
605 const double qAcce = (ra >= opts_.accessThreshold && rb >= opts_.accessThreshold) ? 2.0 : 1.0;
606 double qOrient = 0.0;
607 if (opts_.orientation != 0) {
608 const Vec2 chord = normalize(cand.line.back() - cand.line.front());
609 qOrient = std::fabs(dot(chord, axis));
610 }
611 const double score = opts_.lambdaSize * qSize + opts_.lambdaRegu * qRegu + opts_.lambdaAcce * qAcce +
612 opts_.lambdaOrient * qOrient;
613 if (score > bestScore) {
614 bestScore = score;
615 bestA = std::move(a);
616 bestB = std::move(b);
617 }
618 }
619 if (bestScore <= -std::numeric_limits<double>::infinity() / 2.0) return false;
620 outA = std::move(bestA);
621 outB = std::move(bestB);
622 return true;
623}
624
625void UrbanGenerator::removeShortEdges(UrbanGenerator::GenState& st) {
626 const double eps = 1e-6 * landScale(st.land);
627 for (int pass = 0; pass < 24; ++pass) {
628 bool removed = false;
629 const size_t ecount = st.edges.size();
630 for (size_t e = 0; e < ecount && !removed; ++e) {
631 int p1 = -1, p2 = -1;
632 for (size_t p = 0; p < st.parcelEdges.size(); ++p) {
633 for (const int pe : st.parcelEdges[size_t(p)]) {
634 if (pe == int(e)) {
635 if (p1 < 0)
636 p1 = int(p);
637 else
638 p2 = int(p);
639 }
640 }
641 }
642 if (p1 < 0 || p2 < 0) continue;
643 const Vec2& a = st.corners[size_t(st.edges[e].a)];
644 const Vec2& b = st.corners[size_t(st.edges[e].b)];
645 const double len = distance(a, b);
646 const auto avgSide = [&](int p) {
647 const Polygon approx = approximatePolygon(st.parcels[size_t(p)]);
648 return approx.empty() ? 0.0 : perimeter(approx) / double(approx.size());
649 };
650 if (len >= opts_.shortEdgeFactor * std::min(avgSide(p1), avgSide(p2))) continue;
651 const Vec2 m = (a + b) * 0.5;
652 // Simulate the weld first: every affected parcel must stay a valid polygon
653 // (the paper's vertex merge must not collapse a thin neighbouring parcel).
654 bool safe = true;
655 for (const Polygon& poly : st.parcels) {
656 Polygon sim = poly;
657 for (Vec2& v : sim) {
658 if (distance(v, a) <= eps || distance(v, b) <= eps) v = m;
659 }
660 cleanupRing(sim, eps);
661 if (sim.size() < 3 || area(sim) < opts_.minParcelArea * 0.5 || !polygonIsSimple(sim)) {
662 safe = false;
663 break;
664 }
665 }
666 if (!safe) continue;
667 for (Polygon& poly : st.parcels) {
668 for (Vec2& v : poly) {
669 if (distance(v, a) <= eps || distance(v, b) <= eps) v = m;
670 }
671 cleanupRing(poly, eps);
672 ensureCCW(poly);
673 }
674 removed = true;
675 }
676 if (!removed) break;
677 rebuildGraph(st);
678 }
679}
680
681void UrbanGenerator::generateStreets(UrbanGenerator::GenState& st, int level) {
682 const int n = int(st.parcels.size());
683 auto reachableOf = [&](int p) {
684 for (const int e : st.parcelEdges[size_t(p)])
685 if (st.edgeStreet[size_t(e)]) return true;
686 return false;
687 };
688 std::vector<char> reachable(size_t(n), 0);
689 for (int p = 0; p < n; ++p) reachable[size_t(p)] = reachableOf(p) ? 1 : 0;
690
691 for (int round = 0; round < 8; ++round) {
692 UnionFind uf(n);
693 std::vector<std::vector<int>> edgeParcels(st.edges.size());
694 for (int p = 0; p < n; ++p) {
695 if (reachable[size_t(p)]) continue;
696 for (const int e : st.parcelEdges[size_t(p)]) edgeParcels[size_t(e)].push_back(p);
697 }
698 for (size_t e = 0; e < edgeParcels.size(); ++e)
699 for (size_t k = 1; k < edgeParcels[e].size(); ++k) uf.unite(edgeParcels[e][0], edgeParcels[e][k]);
700 std::vector<std::vector<int>> groups;
701 std::vector<int> groupOf(size_t(n), -1);
702 for (int p = 0; p < n; ++p) {
703 if (reachable[size_t(p)]) continue;
704 const int root = uf.find(p);
705 if (groupOf[size_t(root)] < 0) {
706 groupOf[size_t(root)] = int(groups.size());
707 groups.push_back({});
708 }
709 groups[size_t(groupOf[size_t(root)])].push_back(p);
710 }
711 bool changed = false;
712 for (const std::vector<int>& group : groups) {
713 // Recursively find I/L-shaped street accesses covering every parcel of the
714 // group (paper Section 4.2 step 2), then connect the combined access to the
715 // existing street network once (step 3).
716 std::vector<char> groupEdge(st.edges.size(), 0);
717 for (const int p : group)
718 for (const int e : st.parcelEdges[size_t(p)]) groupEdge[size_t(e)] = 1;
719
720 std::vector<std::vector<int>> vertexEdges(st.corners.size());
721 std::vector<char> border(st.corners.size(), 0);
722 for (size_t e = 0; e < st.edges.size(); ++e) {
723 vertexEdges[size_t(st.edges[e].a)].push_back(int(e));
724 vertexEdges[size_t(st.edges[e].b)].push_back(int(e));
725 }
726 for (size_t v = 0; v < st.corners.size(); ++v) {
727 if (st.cornerOnLandBoundary[size_t(v)]) {
728 border[size_t(v)] = 1;
729 continue;
730 }
731 for (const int e : vertexEdges[size_t(v)]) {
732 if (!groupEdge[size_t(e)]) {
733 border[size_t(v)] = 1;
734 break;
735 }
736 }
737 }
738
739 std::vector<int> accessEdges;
740 std::vector<int> accessVertices;
741 std::vector<char> uncovered(size_t(n), 0);
742 for (const int p : group) uncovered[size_t(p)] = 1;
743 const int maxLen = 10;
744 for (int accessRound = 0; accessRound < 6; ++accessRound) {
745 bool anyUncovered = false;
746 for (const int p : group)
747 if (uncovered[size_t(p)]) {
748 anyUncovered = true;
749 break;
750 }
751 if (!anyUncovered) break;
752 // DFS for I/L-shaped paths (<=maxLen edges, <=1 turn) maximizing coverage.
753 AccessPath best;
754 best.covered = -1;
755 std::vector<char> visited(st.corners.size(), 0);
756 std::vector<int> path;
757 std::vector<int> pathEdges;
758 std::vector<char> pathCovers(size_t(n), 0);
759 int coveredNow = 0;
760
761 std::function<void(int, int, double, int)> dfs = [&](int v, int depth, double len, int turns) {
762 if (depth > 0 && border[size_t(v)] && turns <= 1) {
763 const double score = double(coveredNow) * 1e9 - double(turns) * 1e6 - len;
764 const double bestScore = double(best.covered) * 1e9 - double(best.turns) * 1e6 - best.length;
765 if (score > bestScore) {
766 AccessPath cand;
767 cand.vertices = path;
768 cand.edges = pathEdges;
769 cand.length = len;
770 cand.turns = turns;
771 cand.covered = coveredNow;
772 best = std::move(cand);
773 }
774 }
775 if (depth >= maxLen) return;
776 // Prefer continuing straight (no new turn) and short edges.
777 std::vector<int> order = vertexEdges[size_t(v)];
778 std::stable_sort(order.begin(), order.end(), [&](int a, int b) {
779 auto turnCost = [&](int eid) {
780 if (pathEdges.empty()) return 0.0;
781 const int pe = pathEdges.back();
782 const int w = st.edges[size_t(pe)].a == v ? st.edges[size_t(pe)].b : st.edges[size_t(pe)].a;
783 const int nv =
784 st.edges[size_t(eid)].a == v ? st.edges[size_t(eid)].b : st.edges[size_t(eid)].a;
785 return includedAngleDeg(st.corners[size_t(w)] - st.corners[size_t(v)],
786 st.corners[size_t(nv)] - st.corners[size_t(v)]);
787 };
788 const double ca = turnCost(a);
789 const double cb = turnCost(b);
790 if (ca != cb) return ca > cb; // larger included angle = straighter
791 return distance(st.corners[size_t(st.edges[size_t(a)].a)],
792 st.corners[size_t(st.edges[size_t(a)].b)]) <
793 distance(st.corners[size_t(st.edges[size_t(b)].a)],
794 st.corners[size_t(st.edges[size_t(b)].b)]);
795 });
796 for (const int eid : order) {
797 if (!groupEdge[size_t(eid)]) continue;
798 if (std::find(pathEdges.begin(), pathEdges.end(), eid) != pathEdges.end()) continue;
799 const int nv = st.edges[size_t(eid)].a == v ? st.edges[size_t(eid)].b : st.edges[size_t(eid)].a;
800 if (visited[size_t(nv)]) continue;
801 const Vec2& pa = st.corners[size_t(v)];
802 const Vec2& pb = st.corners[size_t(nv)];
803 double newLen = len + distance(pa, pb);
804 int newTurns = turns;
805 if (!pathEdges.empty()) {
806 const int pe = pathEdges.back();
807 const int w = st.edges[size_t(pe)].a == v ? st.edges[size_t(pe)].b : st.edges[size_t(pe)].a;
808 if (includedAngleDeg(st.corners[size_t(w)] - pa, pb - pa) <= 135.0) ++newTurns;
809 }
810 if (newTurns > 1) continue;
811 visited[size_t(nv)] = 1;
812 path.push_back(nv);
813 pathEdges.push_back(eid);
814 for (const int p : group) {
815 if (uncovered[size_t(p)] && !pathCovers[size_t(p)] &&
816 std::find(st.parcelEdges[size_t(p)].begin(), st.parcelEdges[size_t(p)].end(), eid) !=
817 st.parcelEdges[size_t(p)].end()) {
818 pathCovers[size_t(p)] = 1;
819 ++coveredNow;
820 }
821 }
822 dfs(nv, depth + 1, newLen, newTurns);
823 coveredNow = 0;
824 std::fill(pathCovers.begin(), pathCovers.end(), 0);
825 for (const int pe : pathEdges) {
826 for (const int p : group) {
827 if (uncovered[size_t(p)] && !pathCovers[size_t(p)] &&
828 std::find(st.parcelEdges[size_t(p)].begin(), st.parcelEdges[size_t(p)].end(), pe) !=
829 st.parcelEdges[size_t(p)].end()) {
830 pathCovers[size_t(p)] = 1;
831 ++coveredNow;
832 }
833 }
834 }
835 path.pop_back();
836 pathEdges.pop_back();
837 visited[size_t(nv)] = 0;
838 }
839 };
840
841 for (size_t v = 0; v < st.corners.size(); ++v) {
842 if (!border[size_t(v)]) continue;
843 visited[size_t(v)] = 1;
844 path.push_back(int(v));
845 dfs(int(v), 0, 0.0, 0);
846 path.pop_back();
847 visited[size_t(v)] = 0;
848 }
849 if (best.edges.empty()) break;
850
851 for (const int e : best.edges) accessEdges.push_back(e);
852 for (const int v : best.vertices) accessVertices.push_back(v);
853 // Mark covered parcels done so the next access targets the remainder.
854 for (const int pe : best.edges) {
855 for (int p = 0; p < n; ++p) {
856 if (uncovered[size_t(p)] &&
857 std::find(st.parcelEdges[size_t(p)].begin(), st.parcelEdges[size_t(p)].end(), pe) !=
858 st.parcelEdges[size_t(p)].end()) {
859 uncovered[size_t(p)] = 0;
860 }
861 }
862 }
863 }
864
865 for (const int e : accessEdges) {
866 if (!st.edgeStreet[size_t(e)]) {
867 st.edgeStreet[size_t(e)] = 1;
868 st.edges[size_t(e)].isStreet = true;
869 st.streetSegs.emplace_back(st.corners[size_t(st.edges[e].a)], st.corners[size_t(st.edges[e].b)]);
870 }
871 }
872 if (!accessVertices.empty()) connectAccessToNetwork(st, accessVertices);
873 changed = true;
874 }
875 if (!changed) break;
876 for (int p = 0; p < n; ++p) reachable[size_t(p)] = reachableOf(p) ? 1 : 0;
877 }
878 reconnectStreetEnds(st, level);
879}
880
881void UrbanGenerator::connectAccessToNetwork(UrbanGenerator::GenState& st, const std::vector<int>& accessVertices,
882 const std::vector<char>* excludeTargets) {
883 if (accessVertices.empty()) return;
884 std::vector<char> isAccess(st.corners.size(), 0);
885 for (const int v : accessVertices) isAccess[size_t(v)] = 1;
886 std::vector<char> target(st.corners.size(), 0);
887 int targetCount = 0;
888 for (size_t e = 0; e < st.edges.size(); ++e) {
889 if (!st.edgeStreet[e]) continue;
890 for (const int v : {st.edges[e].a, st.edges[e].b}) {
891 if (!isAccess[size_t(v)] && !target[size_t(v)] && (!excludeTargets || !(*excludeTargets)[size_t(v)])) {
892 target[size_t(v)] = 1;
893 ++targetCount;
894 }
895 }
896 }
897 if (targetCount == 0) return;
898
899 struct State {
900 int v = -1;
901 int pe = -1;
902 double cost = 0.0;
903 };
904 struct Cmp {
905 bool operator()(const State& a, const State& b) const { return a.cost > b.cost; }
906 };
907 std::priority_queue<State, std::vector<State>, Cmp> pq;
908 std::unordered_map<uint64_t, double> best;
909 std::unordered_map<uint64_t, uint64_t> parent;
910 auto stateId = [&](int v, int pe) { return (uint64_t(uint32_t(v)) << 32) ^ uint64_t(uint32_t(pe + 1)); };
911 std::unordered_set<uint64_t> startStates;
912
913 std::vector<std::vector<int>> vertexEdges(st.corners.size());
914 for (size_t e = 0; e < st.edges.size(); ++e) {
915 vertexEdges[size_t(st.edges[e].a)].push_back(int(e));
916 vertexEdges[size_t(st.edges[e].b)].push_back(int(e));
917 }
918 for (const int v : accessVertices) {
919 for (const int e : vertexEdges[size_t(v)]) {
920 const uint64_t id = stateId(v, e);
921 best[id] = 0.0;
922 startStates.insert(id);
923 pq.push({v, e, 0.0});
924 }
925 }
926
927 State goal{-1, -1, std::numeric_limits<double>::infinity()};
928 while (!pq.empty()) {
929 const State s = pq.top();
930 pq.pop();
931 const uint64_t sid = stateId(s.v, s.pe);
932 auto it = best.find(sid);
933 if (it == best.end() || s.cost > it->second + 1e-12) continue;
934 if (target[size_t(s.v)] && startStates.count(stateId(s.v, s.pe)) == 0) {
935 goal = s;
936 break;
937 }
938 for (const int e : vertexEdges[size_t(s.v)]) {
939 if (e == s.pe) continue;
940 const int nv = st.edges[size_t(e)].a == s.v ? st.edges[size_t(e)].b : st.edges[size_t(e)].a;
941 const Vec2& u = st.corners[size_t(s.v)];
942 const int w = st.edges[size_t(s.pe)].a == s.v ? st.edges[size_t(s.pe)].b : st.edges[size_t(s.pe)].a;
943 const Vec2& v = st.corners[size_t(nv)];
944 const double angle = includedAngleDeg(st.corners[size_t(w)] - u, v - u);
945 const double turnCost = angle <= 135.0 ? opts_.dijkstraJunctionWeight : 0.0;
946 const double nc = s.cost + distance(u, v) + turnCost;
947 const uint64_t nid = stateId(nv, e);
948 auto nit = best.find(nid);
949 if (nit == best.end() || nc < nit->second - 1e-12) {
950 best[nid] = nc;
951 parent[nid] = sid;
952 pq.push({nv, e, nc});
953 }
954 }
955 }
956 if (goal.v < 0) return;
957
958 std::vector<int> edgesOut;
959 {
960 int v = goal.v;
961 int pe = goal.pe;
962 while (pe >= 0) {
963 edgesOut.push_back(pe);
964 const uint64_t id = stateId(v, pe);
965 if (startStates.count(id) != 0) break;
966 auto pit = parent.find(id);
967 if (pit == parent.end()) break;
968 const uint64_t nid = pit->second;
969 v = int(nid >> 32);
970 pe = int(uint32_t(nid)) - 1;
971 }
972 std::reverse(edgesOut.begin(), edgesOut.end());
973 }
974 for (const int e : edgesOut) {
975 if (!st.edgeStreet[size_t(e)]) {
976 st.edgeStreet[size_t(e)] = 1;
977 st.edges[size_t(e)].isStreet = true;
978 st.streetSegs.emplace_back(st.corners[size_t(st.edges[e].a)], st.corners[size_t(st.edges[e].b)]);
979 }
980 }
981}
982
983void UrbanGenerator::reconnectStreetEnds(UrbanGenerator::GenState& st, int level) {
984 if (opts_.streetPattern == 3) return; // tree: keep cul-de-sacs
985 if (opts_.streetPattern == 2 && level >= opts_.culDeSacAfterLevel) return;
986 for (int round = 0; round < 8; ++round) {
987 std::vector<int> ends;
988 std::vector<int> streetDegree(st.corners.size(), 0);
989 for (size_t e = 0; e < st.edges.size(); ++e) {
990 if (!st.edgeStreet[e]) continue;
991 ++streetDegree[size_t(st.edges[e].a)];
992 ++streetDegree[size_t(st.edges[e].b)];
993 }
994 for (size_t v = 0; v < st.corners.size(); ++v)
995 if (streetDegree[size_t(v)] == 1) ends.push_back(int(v));
996 if (ends.empty()) break;
997 bool connectedAny = false;
998 for (const int v : ends) {
999 std::vector<char> exclude(st.corners.size(), 0);
1000 exclude[size_t(v)] = 1;
1001 for (size_t e = 0; e < st.edges.size(); ++e) {
1002 if (!st.edgeStreet[e]) continue;
1003 if (st.edges[e].a == v) exclude[size_t(st.edges[e].b)] = 1;
1004 if (st.edges[e].b == v) exclude[size_t(st.edges[e].a)] = 1;
1005 }
1006 const size_t before = st.streetSegs.size();
1007 connectAccessToNetwork(st, {v}, &exclude);
1008 if (st.streetSegs.size() != before) connectedAny = true;
1009 }
1010 if (!connectedAny) break;
1011 }
1012}
1013
1014void UrbanGenerator::decomposeStreets(UrbanGenerator::GenState& st) {
1015 std::vector<std::vector<int>> adj(st.corners.size());
1016 std::vector<char> used(st.edges.size(), 0);
1017 for (size_t e = 0; e < st.edges.size(); ++e) {
1018 if (!st.edgeStreet[e]) continue;
1019 adj[size_t(st.edges[e].a)].push_back(int(e));
1020 adj[size_t(st.edges[e].b)].push_back(int(e));
1021 }
1022 auto isJunctionOrEnd = [&](int v) {
1023 if (adj[size_t(v)].size() != 2) return true;
1024 const int e1 = adj[size_t(v)][0];
1025 const int e2 = adj[size_t(v)][1];
1026 const int w1 = st.edges[size_t(e1)].a == v ? st.edges[size_t(e1)].b : st.edges[size_t(e1)].a;
1027 const int w2 = st.edges[size_t(e2)].a == v ? st.edges[size_t(e2)].b : st.edges[size_t(e2)].a;
1028 return includedAngleDeg(st.corners[size_t(w1)] - st.corners[size_t(v)],
1029 st.corners[size_t(w2)] - st.corners[size_t(v)]) <= 135.0;
1030 };
1031
1032 layout_.streets.clear();
1033 st.streetChains.clear();
1034 st.junctionVertices.clear();
1035 int junctions = 0;
1036 int ends = 0;
1037 for (size_t v = 0; v < st.corners.size(); ++v) {
1038 if (adj[size_t(v)].empty()) continue;
1039 if (adj[size_t(v)].size() == 1)
1040 ++ends;
1041 else if (isJunctionOrEnd(int(v)))
1042 ++junctions;
1043 }
1044
1045 auto walk = [&](int startV, int firstE, bool allowLoop) {
1046 (void)allowLoop;
1047 std::vector<int> chain;
1048 chain.push_back(startV);
1049 int v = startV;
1050 int e = firstE;
1051 int guard = 0;
1052 while (guard++ < 100000) {
1053 used[size_t(e)] = 1;
1054 const int nv = st.edges[size_t(e)].a == v ? st.edges[size_t(e)].b : st.edges[size_t(e)].a;
1055 chain.push_back(nv);
1056 v = nv;
1057 if (isJunctionOrEnd(v)) break;
1058 const int nextE = adj[size_t(v)][0] == e ? adj[size_t(v)][1] : adj[size_t(v)][0];
1059 if (used[size_t(nextE)]) break;
1060 e = nextE;
1061 }
1062 if (chain.size() >= 2) {
1063 st.streetChains.push_back(chain);
1064 const int f = chain.front();
1065 const int b = chain.back();
1066 if (isJunctionOrEnd(f) && adj[size_t(f)].size() != 1 &&
1067 std::find(st.junctionVertices.begin(), st.junctionVertices.end(), f) == st.junctionVertices.end())
1068 st.junctionVertices.push_back(f);
1069 if (isJunctionOrEnd(b) && adj[size_t(b)].size() != 1 &&
1070 std::find(st.junctionVertices.begin(), st.junctionVertices.end(), b) == st.junctionVertices.end())
1071 st.junctionVertices.push_back(b);
1072 }
1073 };
1074
1075 for (size_t v = 0; v < st.corners.size(); ++v) {
1076 if (isJunctionOrEnd(int(v))) {
1077 for (const int e : adj[size_t(v)])
1078 if (!used[size_t(e)]) walk(int(v), e, false);
1079 }
1080 }
1081 for (size_t e = 0; e < st.edges.size(); ++e) {
1082 if (st.edgeStreet[e] && !used[e]) walk(st.edges[e].a, int(e), true);
1083 }
1084 layout_.streetJunctions = junctions;
1085 layout_.streetEnds = ends;
1086
1087 for (const std::vector<int>& chain : st.streetChains) {
1088 Street s;
1089 s.width = opts_.streetWidth;
1090 for (const int c : chain) s.pts.push_back(st.corners[size_t(c)]);
1091 layout_.streets.push_back(std::move(s));
1092 }
1093}
1094
1095void UrbanGenerator::finalizeStats(UrbanGenerator::GenState& st) {
1096 layout_.corners = st.corners;
1097 layout_.parcels = st.rings;
1098 layout_.edges = st.edges;
1099 layout_.streetSegments.clear();
1100 for (size_t e = 0; e < st.edges.size(); ++e)
1101 if (st.edgeStreet[e]) layout_.streetSegments.emplace_back(st.edges[e].a, st.edges[e].b);
1102 layout_.totalStreetLength = 0.0;
1103 for (const Street& s : layout_.streets) layout_.totalStreetLength += polylineLength(s.pts);
1104
1105 double sum = 0.0;
1106 double mn = std::numeric_limits<double>::infinity();
1107 double mx = 0.0;
1108 for (const Polygon& poly : st.parcels) {
1109 const double irr = shapeIrregularity(approximatePolygon(poly), opts_.gammaAngle, opts_.gammaSide);
1110 sum += irr;
1111 mn = std::min(mn, irr);
1112 mx = std::max(mx, irr);
1113 }
1114 layout_.avgIrregularity = st.parcels.empty() ? 0.0 : sum / double(st.parcels.size());
1115 layout_.minIrregularity = st.parcels.empty() ? 0.0 : mn;
1116 layout_.maxIrregularity = st.parcels.empty() ? 0.0 : mx;
1117}
1118
1119} // namespace eve::procgen::urban
LogicalId target
double score
Definition Agent.cpp:50
float w
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
const std::string & s
building::EdgeCurveGroup group
float length
Definition CaveMesh.cpp:94
glm::vec4 p[6]
eve::resource::CostSpec cost
std::uint32_t key
float u
Definition Grass.cpp:233
float area
Definition Grass.cpp:62
glm::vec3 n
Definition Grass.cpp:63
bool border
float v
std::int32_t second
std::int32_t c
std::array< float, 3 > scale
std::int32_t parent
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
std::vector< std::int32_t > order
int level
uint32_t groups
Definition OnnxGpgpu.cpp:39
std::string error
Definition Package.cpp:60
int idx
float f
std::vector< Point > vertices
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
float d
float t
int removed
float dy
float dx
std::map< Cell, int > best
CommandLogBoundary boundary
Anchor rule, see above.
std::uint64_t round
int sides
Definition TreeMesh.cpp:322
float size
Definition TreeMesh.cpp:156
V3 dir
Definition TreeMesh.cpp:150
std::uint32_t depth
std::vector< int > edges
int covered
int turns
float m[16]
float qy
float qx
float angle
bool generate(std::string *error=nullptr)
Run the pipeline. On failure error receives a human-readable reason.
UrbanGenerator(UrbanOptions opts)
Urban generator.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Polygon approximatePolygon(const Polygon &ring)
Simplify a ring to its approximate polygon: consecutive edges with included angle > 135° are merged i...
Vec2 perpendicular(const Vec2 &a)
Perpendicular.
Definition UrbanTypes.h:50
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.
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),...
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
double perimeter(const Polygon &poly)
Perimeter length of a closed ring.
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.
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.
std::vector< SplitCandidate > generateSplitCandidates(const Polygon &poly, int maxCandidates, double minHalfArea)
Generate candidate streamlines for binary partitioning of poly.
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°).
int axis(int64_t a, size_t rank)
Axis.
State
Lifecycle state of a generic transaction plan.
std::vector< std::pair< Vec2, Vec2 > > streetSegs
std::vector< std::vector< int > > streetChains
std::vector< std::vector< int > > parcelEdges
All user-facing controls for the urban generator. Defaults follow the paper (λ=0.3/0....
Definition UrbanTypes.h:117