Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/resources/2d/polygon_path_finder.cpp
9898 views
1
/**************************************************************************/
2
/* polygon_path_finder.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "polygon_path_finder.h"
32
#include "core/math/geometry_2d.h"
33
34
bool PolygonPathFinder::_is_point_inside(const Vector2 &p_point) const {
35
int crosses = 0;
36
37
for (const Edge &E : edges) {
38
const Edge &e = E;
39
40
Vector2 a = points[e.points[0]].pos;
41
Vector2 b = points[e.points[1]].pos;
42
43
if (Geometry2D::segment_intersects_segment(a, b, p_point, outside_point, nullptr)) {
44
crosses++;
45
}
46
}
47
48
return crosses & 1;
49
}
50
51
void PolygonPathFinder::setup(const Vector<Vector2> &p_points, const Vector<int> &p_connections) {
52
ERR_FAIL_COND(p_connections.size() & 1);
53
54
points.clear();
55
edges.clear();
56
57
//insert points
58
59
int point_count = p_points.size();
60
points.resize(point_count + 2);
61
bounds = Rect2();
62
63
for (int i = 0; i < p_points.size(); i++) {
64
points.write[i].pos = p_points[i];
65
points.write[i].penalty = 0;
66
67
outside_point = i == 0 ? p_points[0] : p_points[i].max(outside_point);
68
69
if (i == 0) {
70
bounds.position = points[i].pos;
71
} else {
72
bounds.expand_to(points[i].pos);
73
}
74
}
75
76
outside_point.x += 20.451 + Math::randf() * 10.2039;
77
outside_point.y += 21.193 + Math::randf() * 12.5412;
78
79
//insert edges (which are also connections)
80
81
for (int i = 0; i < p_connections.size(); i += 2) {
82
Edge e(p_connections[i], p_connections[i + 1]);
83
ERR_FAIL_INDEX(e.points[0], point_count);
84
ERR_FAIL_INDEX(e.points[1], point_count);
85
points.write[p_connections[i]].connections.insert(p_connections[i + 1]);
86
points.write[p_connections[i + 1]].connections.insert(p_connections[i]);
87
edges.insert(e);
88
}
89
90
//fill the remaining connections based on visibility
91
92
for (int i = 0; i < point_count; i++) {
93
for (int j = i + 1; j < point_count; j++) {
94
if (edges.has(Edge(i, j))) {
95
continue; //if in edge ignore
96
}
97
98
Vector2 from = points[i].pos;
99
Vector2 to = points[j].pos;
100
101
if (!_is_point_inside(from * 0.5 + to * 0.5)) { //connection between points in inside space
102
continue;
103
}
104
105
bool valid = true;
106
107
for (const Edge &E : edges) {
108
const Edge &e = E;
109
if (e.points[0] == i || e.points[1] == i || e.points[0] == j || e.points[1] == j) {
110
continue;
111
}
112
113
Vector2 a = points[e.points[0]].pos;
114
Vector2 b = points[e.points[1]].pos;
115
116
if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) {
117
valid = false;
118
break;
119
}
120
}
121
122
if (valid) {
123
points.write[i].connections.insert(j);
124
points.write[j].connections.insert(i);
125
}
126
}
127
}
128
}
129
130
Vector<Vector2> PolygonPathFinder::find_path(const Vector2 &p_from, const Vector2 &p_to) {
131
Vector<Vector2> path;
132
133
Vector2 from = p_from;
134
Vector2 to = p_to;
135
Edge ignore_from_edge(-1, -1);
136
Edge ignore_to_edge(-1, -1);
137
138
if (!_is_point_inside(from)) {
139
float closest_dist = 1e20f;
140
Vector2 closest_point;
141
142
for (const Edge &E : edges) {
143
const Edge &e = E;
144
const Vector2 segment_a = points[e.points[0]].pos;
145
const Vector2 segment_b = points[e.points[1]].pos;
146
Vector2 closest = Geometry2D::get_closest_point_to_segment(from, segment_a, segment_b);
147
float d = from.distance_squared_to(closest);
148
149
if (d < closest_dist) {
150
ignore_from_edge = E;
151
closest_dist = d;
152
closest_point = closest;
153
}
154
}
155
156
from = closest_point;
157
};
158
159
if (!_is_point_inside(to)) {
160
float closest_dist = 1e20f;
161
Vector2 closest_point;
162
163
for (const Edge &E : edges) {
164
const Edge &e = E;
165
const Vector2 segment_a = points[e.points[0]].pos;
166
const Vector2 segment_b = points[e.points[1]].pos;
167
Vector2 closest = Geometry2D::get_closest_point_to_segment(to, segment_a, segment_b);
168
float d = to.distance_squared_to(closest);
169
170
if (d < closest_dist) {
171
ignore_to_edge = E;
172
closest_dist = d;
173
closest_point = closest;
174
}
175
}
176
177
to = closest_point;
178
};
179
180
//test direct connection
181
{
182
bool can_see_eachother = true;
183
184
for (const Edge &E : edges) {
185
const Edge &e = E;
186
if (e.points[0] == ignore_from_edge.points[0] && e.points[1] == ignore_from_edge.points[1]) {
187
continue;
188
}
189
if (e.points[0] == ignore_to_edge.points[0] && e.points[1] == ignore_to_edge.points[1]) {
190
continue;
191
}
192
193
Vector2 a = points[e.points[0]].pos;
194
Vector2 b = points[e.points[1]].pos;
195
196
if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) {
197
can_see_eachother = false;
198
break;
199
}
200
}
201
202
if (can_see_eachother) {
203
path.push_back(from);
204
path.push_back(to);
205
return path;
206
}
207
}
208
209
//add to graph
210
211
int aidx = points.size() - 2;
212
int bidx = points.size() - 1;
213
points.write[aidx].pos = from;
214
points.write[bidx].pos = to;
215
points.write[aidx].distance = 0;
216
points.write[bidx].distance = 0;
217
points.write[aidx].prev = -1;
218
points.write[bidx].prev = -1;
219
points.write[aidx].penalty = 0;
220
points.write[bidx].penalty = 0;
221
222
for (int i = 0; i < points.size() - 2; i++) {
223
bool valid_a = true;
224
bool valid_b = true;
225
points.write[i].prev = -1;
226
points.write[i].distance = 0;
227
228
if (!_is_point_inside(from * 0.5 + points[i].pos * 0.5)) {
229
valid_a = false;
230
}
231
232
if (!_is_point_inside(to * 0.5 + points[i].pos * 0.5)) {
233
valid_b = false;
234
}
235
236
for (const Edge &E : edges) {
237
const Edge &e = E;
238
239
if (e.points[0] == i || e.points[1] == i) {
240
continue;
241
}
242
243
Vector2 a = points[e.points[0]].pos;
244
Vector2 b = points[e.points[1]].pos;
245
246
if (valid_a) {
247
if (e.points[0] != ignore_from_edge.points[1] &&
248
e.points[1] != ignore_from_edge.points[1] &&
249
e.points[0] != ignore_from_edge.points[0] &&
250
e.points[1] != ignore_from_edge.points[0]) {
251
if (Geometry2D::segment_intersects_segment(a, b, from, points[i].pos, nullptr)) {
252
valid_a = false;
253
}
254
}
255
}
256
257
if (valid_b) {
258
if (e.points[0] != ignore_to_edge.points[1] &&
259
e.points[1] != ignore_to_edge.points[1] &&
260
e.points[0] != ignore_to_edge.points[0] &&
261
e.points[1] != ignore_to_edge.points[0]) {
262
if (Geometry2D::segment_intersects_segment(a, b, to, points[i].pos, nullptr)) {
263
valid_b = false;
264
}
265
}
266
}
267
268
if (!valid_a && !valid_b) {
269
break;
270
}
271
}
272
273
if (valid_a) {
274
points.write[i].connections.insert(aidx);
275
points.write[aidx].connections.insert(i);
276
}
277
278
if (valid_b) {
279
points.write[i].connections.insert(bidx);
280
points.write[bidx].connections.insert(i);
281
}
282
}
283
//solve graph
284
285
HashSet<int> open_list;
286
287
points.write[aidx].distance = 0;
288
points.write[aidx].prev = aidx;
289
for (const int &E : points[aidx].connections) {
290
open_list.insert(E);
291
points.write[E].distance = from.distance_to(points[E].pos);
292
points.write[E].prev = aidx;
293
}
294
295
bool found_route = false;
296
297
while (true) {
298
if (open_list.is_empty()) {
299
print_verbose("Open list empty.");
300
break;
301
}
302
//check open list
303
304
int least_cost_point = -1;
305
float least_cost = 1e30;
306
307
//this could be faster (cache previous results)
308
for (const int &E : open_list) {
309
const Point &p = points[E];
310
float cost = p.distance;
311
cost += p.pos.distance_to(to);
312
cost += p.penalty;
313
314
if (cost < least_cost) {
315
least_cost_point = E;
316
least_cost = cost;
317
}
318
}
319
320
const Point &np = points[least_cost_point];
321
//open the neighbors for search
322
323
for (const int &E : np.connections) {
324
Point &p = points.write[E];
325
float distance = np.pos.distance_to(p.pos) + np.distance;
326
327
if (p.prev != -1) {
328
//oh this was visited already, can we win the cost?
329
330
if (p.distance > distance) {
331
p.prev = least_cost_point; //reassign previous
332
p.distance = distance;
333
}
334
} else {
335
//add to open neighbors
336
337
p.prev = least_cost_point;
338
p.distance = distance;
339
open_list.insert(E);
340
341
if (E == bidx) {
342
//oh my reached end! stop algorithm
343
found_route = true;
344
break;
345
}
346
}
347
}
348
349
if (found_route) {
350
break;
351
}
352
353
open_list.erase(least_cost_point);
354
}
355
356
if (found_route) {
357
int at = bidx;
358
path.push_back(points[at].pos);
359
do {
360
at = points[at].prev;
361
path.push_back(points[at].pos);
362
} while (at != aidx);
363
364
path.reverse();
365
}
366
367
for (int i = 0; i < points.size() - 2; i++) {
368
points.write[i].connections.erase(aidx);
369
points.write[i].connections.erase(bidx);
370
points.write[i].prev = -1;
371
points.write[i].distance = 0;
372
}
373
374
points.write[aidx].connections.clear();
375
points.write[aidx].prev = -1;
376
points.write[aidx].distance = 0;
377
points.write[bidx].connections.clear();
378
points.write[bidx].prev = -1;
379
points.write[bidx].distance = 0;
380
381
return path;
382
}
383
384
void PolygonPathFinder::_set_data(const Dictionary &p_data) {
385
ERR_FAIL_COND(!p_data.has("points"));
386
ERR_FAIL_COND(!p_data.has("connections"));
387
ERR_FAIL_COND(!p_data.has("segments"));
388
ERR_FAIL_COND(!p_data.has("bounds"));
389
390
Vector<Vector2> p = p_data["points"];
391
Array c = p_data["connections"];
392
393
ERR_FAIL_COND(c.size() != p.size());
394
if (c.size()) {
395
return;
396
}
397
398
int pc = p.size();
399
points.resize(pc + 2);
400
401
const Vector2 *pr = p.ptr();
402
for (int i = 0; i < pc; i++) {
403
points.write[i].pos = pr[i];
404
Vector<int> con = c[i];
405
const int *cr = con.ptr();
406
int cc = con.size();
407
for (int j = 0; j < cc; j++) {
408
points.write[i].connections.insert(cr[j]);
409
}
410
}
411
412
if (p_data.has("penalties")) {
413
Vector<real_t> penalties = p_data["penalties"];
414
if (penalties.size() == pc) {
415
const real_t *pr2 = penalties.ptr();
416
for (int i = 0; i < pc; i++) {
417
points.write[i].penalty = pr2[i];
418
}
419
}
420
}
421
422
Vector<int> segs = p_data["segments"];
423
int sc = segs.size();
424
ERR_FAIL_COND(sc & 1);
425
const int *sr = segs.ptr();
426
for (int i = 0; i < sc; i += 2) {
427
Edge e(sr[i], sr[i + 1]);
428
edges.insert(e);
429
}
430
bounds = p_data["bounds"];
431
}
432
433
Dictionary PolygonPathFinder::_get_data() const {
434
Dictionary d;
435
Vector<Vector2> p;
436
Vector<int> ind;
437
Array path_connections;
438
p.resize(MAX(0, points.size() - 2));
439
path_connections.resize(MAX(0, points.size() - 2));
440
ind.resize(edges.size() * 2);
441
Vector<real_t> penalties;
442
penalties.resize(MAX(0, points.size() - 2));
443
{
444
Vector2 *wp = p.ptrw();
445
real_t *pw = penalties.ptrw();
446
447
for (int i = 0; i < points.size() - 2; i++) {
448
wp[i] = points[i].pos;
449
pw[i] = points[i].penalty;
450
Vector<int> c;
451
c.resize(points[i].connections.size());
452
{
453
int *cw = c.ptrw();
454
int idx = 0;
455
for (const int &E : points[i].connections) {
456
cw[idx++] = E;
457
}
458
}
459
path_connections[i] = c;
460
}
461
}
462
{
463
int *iw = ind.ptrw();
464
int idx = 0;
465
for (const Edge &E : edges) {
466
iw[idx++] = E.points[0];
467
iw[idx++] = E.points[1];
468
}
469
}
470
471
d["bounds"] = bounds;
472
d["points"] = p;
473
d["penalties"] = penalties;
474
d["connections"] = path_connections;
475
d["segments"] = ind;
476
477
return d;
478
}
479
480
bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const {
481
return _is_point_inside(p_point);
482
}
483
484
Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const {
485
float closest_dist = 1e20f;
486
Vector2 closest_point;
487
488
for (const Edge &E : edges) {
489
const Edge &e = E;
490
const Vector2 segment_a = points[e.points[0]].pos;
491
const Vector2 segment_b = points[e.points[1]].pos;
492
Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, segment_a, segment_b);
493
float d = p_point.distance_squared_to(closest);
494
495
if (d < closest_dist) {
496
closest_dist = d;
497
closest_point = closest;
498
}
499
}
500
501
ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2());
502
503
return closest_point;
504
}
505
506
Vector<Vector2> PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const {
507
Vector<Vector2> inters;
508
509
for (const Edge &E : edges) {
510
Vector2 a = points[E.points[0]].pos;
511
Vector2 b = points[E.points[1]].pos;
512
513
Vector2 res;
514
if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) {
515
inters.push_back(res);
516
}
517
}
518
519
return inters;
520
}
521
522
Rect2 PolygonPathFinder::get_bounds() const {
523
return bounds;
524
}
525
526
void PolygonPathFinder::set_point_penalty(int p_point, float p_penalty) {
527
ERR_FAIL_INDEX(p_point, points.size() - 2);
528
points.write[p_point].penalty = p_penalty;
529
}
530
531
float PolygonPathFinder::get_point_penalty(int p_point) const {
532
ERR_FAIL_INDEX_V(p_point, points.size() - 2, 0);
533
return points[p_point].penalty;
534
}
535
536
void PolygonPathFinder::_bind_methods() {
537
ClassDB::bind_method(D_METHOD("setup", "points", "connections"), &PolygonPathFinder::setup);
538
ClassDB::bind_method(D_METHOD("find_path", "from", "to"), &PolygonPathFinder::find_path);
539
ClassDB::bind_method(D_METHOD("get_intersections", "from", "to"), &PolygonPathFinder::get_intersections);
540
ClassDB::bind_method(D_METHOD("get_closest_point", "point"), &PolygonPathFinder::get_closest_point);
541
ClassDB::bind_method(D_METHOD("is_point_inside", "point"), &PolygonPathFinder::is_point_inside);
542
ClassDB::bind_method(D_METHOD("set_point_penalty", "idx", "penalty"), &PolygonPathFinder::set_point_penalty);
543
ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty);
544
545
ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds);
546
ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data);
547
ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data);
548
549
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
550
}
551
552
PolygonPathFinder::PolygonPathFinder() {
553
}
554
555