-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathplanner.cc
More file actions
293 lines (247 loc) · 10 KB
/
planner.cc
File metadata and controls
293 lines (247 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#include <optional>
#include <cmath>
#include <utility>
#include <cstdlib>
#include <iostream>
#include "planner.h"
#include "cityLayout.h"
#include "vect2.h"
#include "util.h"
// has a city state
// city state has a list of planned roads, built roads, intersections, and lots
//planner step
//sample
// extend
// goals
// constraints
// check block if necessary
// update traffic and land use
// BUILD
// a thought on tunnels. Maybe they are like roads that get extended to negate terrain slope
Planner::Planner(CityLayout city, double growthSpeed): city{city}, growthSpeed{growthSpeed}, highwayMode{true}, quarter{std::vector<size_t>()}, quarterEdge{0}, sampleGoals{std::vector<std::unique_ptr<SampleGoal>>()}, extensionGoals{std::vector<std::unique_ptr<ExtensionGoal>>()}, roadConstraints{std::vector<std::unique_ptr<RoadConstraint>>()} {}
size_t Planner::nextHighway(size_t edge) const {
size_t curr = city.next(edge);
while (!city.roadIsHighway(city.road(curr))) {
curr = city.nextOutEdgeCW(curr);
}
return curr;
}
std::vector<std::pair<double, double>> Planner::getQuarterConstraints(size_t node) const {
std::vector<std::pair<double, double>> constraints;
size_t curr = quarterEdge;
do {
if (city.tip(curr) == node) {
constraints.emplace_back((city.nodePos(city.tip(nextHighway(curr))) - city.nodePos(node)).worldAngle(), (city.nodePos(city.root(curr)) - city.nodePos(node)).worldAngle());
}
curr = nextHighway(curr);
} while (curr != quarterEdge);
return constraints;
}
std::optional<double> Planner::getExtensionAngle(size_t node) { // ASSUMES NODE IS EITHER A HIGHWAY OR A MEMBER OF QUARTER AND HAS AT LEAST ONE POTENTIAL EXTENSION
std::vector<std::pair<double, double>> constraints;
if (!highwayMode && city.nodeIsHighway(node)) {
constraints = getQuarterConstraints(node);
}
if ((!highwayMode && city.nodeIsHighway(node) && constraints.size() == 0)) {
return std::nullopt;
}
if (city.degree(node) == 1) {
// pick straightest extension
double baseAngle = city.lockedExt(node)[0];
double straightest = city.potentialExt(node)[0];
double straightestDiff = angleDiff(baseAngle + M_PI, straightest);
for (auto pot : city.potentialExt(node)) {
double potDiff = angleDiff(baseAngle + M_PI, pot);
if (potDiff < straightestDiff) {
straightest = pot;
straightestDiff = potDiff;
}
}
return std::optional<double>{straightest};
} else {
if (highwayMode || !city.nodeIsHighway(node)) {
// pick random extension from all extensions
int r = std::rand() % city.potentialExt(node).size();
return std::optional<double>{city.potentialExt(node)[r]};
} else {
// constrain all potential exts. pick random one
std::vector<double> angles;
for (auto a : city.potentialExt(node)) {
for (auto con : constraints) {
if (angleBetweenCCW(con.first, a, con.second)) {
angles.push_back(a);
}
}
}
if (angles.size() > 0) {
int r = std::rand() % angles.size();
return std::optional<double>{angles[r]};
}
}
}
return std::nullopt;
}
bool Planner::isValidSample(size_t node) {
if (city.potentialExt(node).size() > 0) {
if (highwayMode) {
return city.nodeIsHighway(node);
} else {
if (city.nodeIsHighway(node)) {
if (getExtensionAngle(node)) {
return true;
}
} else {
for (auto n : quarter) {
if (node == n) {
return true;
}
}
}
}
}
return false;
}
std::optional<size_t> Planner::roadPlanningSample() {
std::vector<double> weights;
std::vector<size_t> nodes;
double sum = 0.0;
for (size_t i = 0; i < city.numNodes(); ++i) {
if (isValidSample(i)) {
for (auto &goal : sampleGoals) { // sum is built in currently. We could have a "weighted sum" goal instead
weights.push_back(goal->weigh(i, highwayMode, city) * goal->weight);
nodes.push_back(i);
}
sum += weights.back();
}
}
double r = static_cast<double>(std::rand()) / static_cast<double>(RAND_MAX) * sum;
double prefix = 0.0;
for (size_t i = 0; i < nodes.size(); ++i) {
prefix += weights[i];
if (r < prefix || i == nodes.size() - 1) {
return std::optional<size_t>{nodes[i]};
}
}
return std::nullopt;
}
std::pair<double, double> Planner::applyExtensionGoals(size_t node, double base_angle) {
double sum_weights = 0.0;
double sum_dev = 0.0;
double sum_len = 0.0;
for (auto &goal : extensionGoals) {
std::pair<double, double> params = goal->evaluate(node, base_angle, highwayMode, city);
sum_dev += params.first;
sum_len += params.second;
sum_weights += goal->weight;
}
double angle = 0;
double length = 0;
if (sum_weights != 0) {
angle = base_angle + sum_dev / sum_weights;
length = sum_len / sum_weights;
}
return std::pair<double, double>{angle, length};
}
void Planner::step() {
// BUG! minor streets are escaping their quarters! Disasterous!
int num_samples = std::max(int(city.numNodes() * growthSpeed), 1); // in future versions this may be abstracted to allow more ways to manipulate growth
for (int i = 0; i < num_samples; ++i) {
// precompute data for relevant sample goals.
for (auto &goal : sampleGoals) {
try {
PrecomputeGoal &pg = dynamic_cast<PrecomputeGoal&>(*goal);
pg.update(city);
}
catch (std::bad_cast &) {}
}
// get a sample
std::optional<size_t> toExtend = roadPlanningSample();
// if no sample either switch mode or fail
if (!toExtend) {
if (highwayMode) {
throw NoSample{};
}
//std::cout << "No sample. Switching modes." << std::endl;
highwayMode = true;
--i;
continue;
}
// apply extensional goals
double baseAngle = *getExtensionAngle(*toExtend);
std::pair<double, double> params = applyExtensionGoals(*toExtend, baseAngle);
double angle = params.first;
double length = params.second;
//need to make sure extension doesnt push road out of quarter bounds
if (!highwayMode && city.nodeIsHighway(*toExtend)) {
std::vector<std::pair<double, double>> constraints = getQuarterConstraints(*toExtend);
bool bounded = false;
for (auto con : constraints) {
if (angleBetweenCCW(con.first, angle, con.second)) {
bounded = true;
break;
}
}
if (!bounded) {
//std::cout << "Out of Quarter failure: " << *toExtend << ", " << baseAngle << ", " << angle << std::endl;
city.nodeEraseClosestExt(*toExtend, baseAngle);
--i;
continue;
}
}
Extension ex = Extension{*toExtend, city.nodePos(*toExtend) + Vect2(std::cos(angle), std::sin(angle)).scale(length), highwayMode};
// apply constraints
bool failed = false;
for (auto &constraint : roadConstraints) {
if (constraint->apply(ex, city)) {
failed = true;
break;
}
}
if (failed) {
city.nodeEraseClosestExt(*toExtend, baseAngle);
--i;
continue;
}
std::optional<size_t> newEdge = city.extendNode(ex.base, ex.tip, ex.highway);
if (!newEdge) {
//std::cout << "Invariant failure." << std::endl;
city.nodeEraseClosestExt(*toExtend, baseAngle);
--i;
continue;
}
if (highwayMode) {
// if we created a new quarter (does not include splitting quarters) then switch modes and set the quarter edge
std::optional<size_t> blockF = city.block(*newEdge);
std::optional<size_t> blockR = city.block(city.twin(*newEdge));
if (blockF && !blockR) {
//std::cout << "New Quarter. Switching Modes." << std::endl;
highwayMode = false;
quarter.clear();
quarterEdge = *newEdge;
} else if (blockR && !blockF) {
//std::cout << "New Quarter. Switching Modes." << std::endl;
highwayMode = false;
quarter.clear();
quarterEdge = city.twin(*newEdge);
}
} else {
// if we added a minor node put it in the current quarter
if (!city.nodeIsHighway(city.tip(*newEdge))) {
bool dup = false;
for (auto node : quarter) {
if (node == city.tip(*newEdge)) dup = true;
}
if (!dup) quarter.push_back(city.tip(*newEdge));
}
}
// check extended intersection then add.
// try new methods in the future but for now just do one pass over the constraint stack
// use illegal area rules from og parish muller article
// add road segment to data structure, maintain halfedges, update blocks, intersections, give it a name, etc
// consume an extension direction when connecting maybe? if no valid extenion in a certain threshold angle then reject? (if this rejection feature is in place then a subdivision may need to be undone)
// maybe add a feature to not destroy peoples lots / buildings
}
// traffic and land use
// district / heuristic based traffic sampling
// high traffic streets and or highways get cool names
}