Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/math/geometry_2d.cpp
9903 views
1
/**************************************************************************/
2
/* geometry_2d.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 "geometry_2d.h"
32
33
#include "thirdparty/clipper2/include/clipper2/clipper.h"
34
#include "thirdparty/misc/polypartition.h"
35
#define STB_RECT_PACK_IMPLEMENTATION
36
#include "thirdparty/misc/stb_rect_pack.h"
37
38
const int clipper_precision = 5; // Based on CMP_EPSILON.
39
const double clipper_scale = Math::pow(10.0, clipper_precision);
40
41
void Geometry2D::merge_many_polygons(const Vector<Vector<Vector2>> &p_polygons, Vector<Vector<Vector2>> &r_out_polygons, Vector<Vector<Vector2>> &r_out_holes) {
42
using namespace Clipper2Lib;
43
44
PathsD subjects;
45
for (const Vector<Vector2> &polygon : p_polygons) {
46
PathD path(polygon.size());
47
for (int i = 0; i < polygon.size(); i++) {
48
const Vector2 &point = polygon[i];
49
path[i] = PointD(point.x, point.y);
50
}
51
subjects.push_back(path);
52
}
53
54
PathsD solution = Union(subjects, FillRule::NonZero);
55
solution = SimplifyPaths(solution, 0.01);
56
57
r_out_polygons.clear();
58
r_out_holes.clear();
59
for (PathsD::size_type i = 0; i < solution.size(); ++i) {
60
PathD &path = solution[i];
61
62
Vector<Point2> output_polygon;
63
output_polygon.resize(path.size());
64
for (PathsD::size_type j = 0; j < path.size(); ++j) {
65
output_polygon.set(j, Vector2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
66
}
67
if (IsPositive(path)) {
68
r_out_polygons.push_back(output_polygon);
69
} else {
70
r_out_holes.push_back(output_polygon);
71
}
72
}
73
}
74
75
Vector<Vector<Vector2>> Geometry2D::decompose_many_polygons_in_convex(const Vector<Vector<Point2>> &p_polygons, const Vector<Vector<Point2>> &p_holes) {
76
Vector<Vector<Vector2>> decomp;
77
List<TPPLPoly> in_poly, out_poly;
78
79
for (const Vector<Vector2> &polygon : p_polygons) {
80
TPPLPoly inp;
81
inp.Init(polygon.size());
82
for (int i = 0; i < polygon.size(); i++) {
83
inp.GetPoint(i) = polygon[i];
84
}
85
inp.SetOrientation(TPPL_ORIENTATION_CCW);
86
in_poly.push_back(inp);
87
}
88
for (const Vector<Vector2> &polygon : p_holes) {
89
TPPLPoly inp;
90
inp.Init(polygon.size());
91
for (int i = 0; i < polygon.size(); i++) {
92
inp.GetPoint(i) = polygon[i];
93
}
94
inp.SetOrientation(TPPL_ORIENTATION_CW);
95
inp.SetHole(true);
96
in_poly.push_back(inp);
97
}
98
TPPLPartition tpart;
99
if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { // Failed.
100
ERR_PRINT("Convex decomposing failed!");
101
return decomp;
102
}
103
104
decomp.resize(out_poly.size());
105
int idx = 0;
106
for (TPPLPoly &tp : out_poly) {
107
decomp.write[idx].resize(tp.GetNumPoints());
108
109
for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
110
decomp.write[idx].write[i] = tp.GetPoint(i);
111
}
112
113
idx++;
114
}
115
116
return decomp;
117
}
118
119
Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &p_polygon) {
120
return Geometry2D::decompose_many_polygons_in_convex({ p_polygon }, {});
121
}
122
123
struct _AtlasWorkRect {
124
Size2i s;
125
Point2i p;
126
int idx = 0;
127
_FORCE_INLINE_ bool operator<(const _AtlasWorkRect &p_r) const { return s.width > p_r.s.width; }
128
};
129
130
struct _AtlasWorkRectResult {
131
Vector<_AtlasWorkRect> result;
132
int max_w = 0;
133
int max_h = 0;
134
};
135
136
void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_result, Size2i &r_size) {
137
// Super simple, almost brute force scanline stacking fitter.
138
// It's pretty basic for now, but it tries to make sure that the aspect ratio of the
139
// resulting atlas is somehow square. This is necessary because video cards have limits
140
// on texture size (usually 2048 or 4096), so the squarer a texture, the more the chances
141
// that it will work in every hardware.
142
// For example, it will prioritize a 1024x1024 atlas (works everywhere) instead of a
143
// 256x8192 atlas (won't work anywhere).
144
145
ERR_FAIL_COND(p_rects.is_empty());
146
for (int i = 0; i < p_rects.size(); i++) {
147
ERR_FAIL_COND(p_rects[i].width <= 0);
148
ERR_FAIL_COND(p_rects[i].height <= 0);
149
}
150
151
Vector<_AtlasWorkRect> wrects;
152
wrects.resize(p_rects.size());
153
for (int i = 0; i < p_rects.size(); i++) {
154
wrects.write[i].s = p_rects[i];
155
wrects.write[i].idx = i;
156
}
157
wrects.sort();
158
int widest = wrects[0].s.width;
159
160
Vector<_AtlasWorkRectResult> results;
161
162
for (int i = 0; i <= 12; i++) {
163
int w = 1 << i;
164
int max_h = 0;
165
int max_w = 0;
166
if (w < widest) {
167
continue;
168
}
169
170
Vector<int> hmax;
171
hmax.resize(w);
172
for (int j = 0; j < w; j++) {
173
hmax.write[j] = 0;
174
}
175
176
// Place them.
177
int ofs = 0;
178
int limit_h = 0;
179
for (int j = 0; j < wrects.size(); j++) {
180
if (ofs + wrects[j].s.width > w) {
181
ofs = 0;
182
}
183
184
int from_y = 0;
185
for (int k = 0; k < wrects[j].s.width; k++) {
186
if (hmax[ofs + k] > from_y) {
187
from_y = hmax[ofs + k];
188
}
189
}
190
191
wrects.write[j].p.x = ofs;
192
wrects.write[j].p.y = from_y;
193
int end_h = from_y + wrects[j].s.height;
194
int end_w = ofs + wrects[j].s.width;
195
if (ofs == 0) {
196
limit_h = end_h;
197
}
198
199
for (int k = 0; k < wrects[j].s.width; k++) {
200
hmax.write[ofs + k] = end_h;
201
}
202
203
if (end_h > max_h) {
204
max_h = end_h;
205
}
206
207
if (end_w > max_w) {
208
max_w = end_w;
209
}
210
211
if (ofs == 0 || end_h > limit_h) { // While h limit not reached, keep stacking.
212
ofs += wrects[j].s.width;
213
}
214
}
215
216
_AtlasWorkRectResult result;
217
result.result = wrects;
218
result.max_h = max_h;
219
result.max_w = max_w;
220
results.push_back(result);
221
}
222
223
// Find the result with the best aspect ratio.
224
225
int best = -1;
226
real_t best_aspect = 1e20;
227
228
for (int i = 0; i < results.size(); i++) {
229
real_t h = next_power_of_2((uint32_t)results[i].max_h);
230
real_t w = next_power_of_2((uint32_t)results[i].max_w);
231
real_t aspect = h > w ? h / w : w / h;
232
if (aspect < best_aspect) {
233
best = i;
234
best_aspect = aspect;
235
}
236
}
237
238
r_result.resize(p_rects.size());
239
240
for (int i = 0; i < p_rects.size(); i++) {
241
r_result.write[results[best].result[i].idx] = results[best].result[i].p;
242
}
243
244
r_size = Size2(results[best].max_w, results[best].max_h);
245
}
246
247
Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
248
using namespace Clipper2Lib;
249
250
ClipType op = ClipType::Union;
251
252
switch (p_op) {
253
case OPERATION_UNION:
254
op = ClipType::Union;
255
break;
256
case OPERATION_DIFFERENCE:
257
op = ClipType::Difference;
258
break;
259
case OPERATION_INTERSECTION:
260
op = ClipType::Intersection;
261
break;
262
case OPERATION_XOR:
263
op = ClipType::Xor;
264
break;
265
}
266
267
PathD path_a(p_polypath_a.size());
268
for (int i = 0; i != p_polypath_a.size(); ++i) {
269
path_a[i] = PointD(p_polypath_a[i].x, p_polypath_a[i].y);
270
}
271
PathD path_b(p_polypath_b.size());
272
for (int i = 0; i != p_polypath_b.size(); ++i) {
273
path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y);
274
}
275
276
ClipperD clp(clipper_precision); // Scale points up internally to attain the desired precision.
277
clp.PreserveCollinear(false); // Remove redundant vertices.
278
if (is_a_open) {
279
clp.AddOpenSubject({ path_a });
280
} else {
281
clp.AddSubject({ path_a });
282
}
283
clp.AddClip({ path_b });
284
285
PathsD paths;
286
287
if (is_a_open) {
288
PolyTreeD tree; // Needed to populate polylines.
289
clp.Execute(op, FillRule::EvenOdd, tree, paths);
290
} else {
291
clp.Execute(op, FillRule::EvenOdd, paths); // Works on closed polygons only.
292
}
293
294
Vector<Vector<Point2>> polypaths;
295
for (PathsD::size_type i = 0; i < paths.size(); ++i) {
296
const PathD &path = paths[i];
297
298
Vector<Vector2> polypath;
299
for (PathsD::size_type j = 0; j < path.size(); ++j) {
300
polypath.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
301
}
302
polypaths.push_back(polypath);
303
}
304
return polypaths;
305
}
306
307
Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
308
using namespace Clipper2Lib;
309
310
JoinType jt = JoinType::Square;
311
312
switch (p_join_type) {
313
case JOIN_SQUARE:
314
jt = JoinType::Square;
315
break;
316
case JOIN_ROUND:
317
jt = JoinType::Round;
318
break;
319
case JOIN_MITER:
320
jt = JoinType::Miter;
321
break;
322
}
323
324
EndType et = EndType::Polygon;
325
326
switch (p_end_type) {
327
case END_POLYGON:
328
et = EndType::Polygon;
329
break;
330
case END_JOINED:
331
et = EndType::Joined;
332
break;
333
case END_BUTT:
334
et = EndType::Butt;
335
break;
336
case END_SQUARE:
337
et = EndType::Square;
338
break;
339
case END_ROUND:
340
et = EndType::Round;
341
break;
342
}
343
344
PathD polypath(p_polypath.size());
345
for (int i = 0; i != p_polypath.size(); ++i) {
346
polypath[i] = PointD(p_polypath[i].x, p_polypath[i].y);
347
}
348
349
// Inflate/deflate.
350
PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, clipper_precision, 0.25 * clipper_scale);
351
// Here the points are scaled up internally and
352
// the arc_tolerance is scaled accordingly
353
// to attain the desired precision.
354
355
Vector<Vector<Point2>> polypaths;
356
for (PathsD::size_type i = 0; i < paths.size(); ++i) {
357
const PathD &path = paths[i];
358
359
Vector<Vector2> polypath2;
360
for (PathsD::size_type j = 0; j < path.size(); ++j) {
361
polypath2.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
362
}
363
polypaths.push_back(polypath2);
364
}
365
return polypaths;
366
}
367
368
Vector<Vector3i> Geometry2D::partial_pack_rects(const Vector<Vector2i> &p_sizes, const Size2i &p_atlas_size) {
369
Vector<stbrp_node> nodes;
370
nodes.resize(p_atlas_size.width);
371
memset(nodes.ptrw(), 0, sizeof(stbrp_node) * nodes.size());
372
373
stbrp_context context;
374
stbrp_init_target(&context, p_atlas_size.width, p_atlas_size.height, nodes.ptrw(), p_atlas_size.width);
375
376
Vector<stbrp_rect> rects;
377
rects.resize(p_sizes.size());
378
379
for (int i = 0; i < p_sizes.size(); i++) {
380
rects.write[i].id = i;
381
rects.write[i].w = p_sizes[i].width;
382
rects.write[i].h = p_sizes[i].height;
383
rects.write[i].x = 0;
384
rects.write[i].y = 0;
385
rects.write[i].was_packed = 0;
386
}
387
388
stbrp_pack_rects(&context, rects.ptrw(), rects.size());
389
390
Vector<Vector3i> ret;
391
ret.resize(p_sizes.size());
392
393
for (int i = 0; i < p_sizes.size(); i++) {
394
ret.write[rects[i].id] = Vector3i(rects[i].x, rects[i].y, rects[i].was_packed != 0 ? 1 : 0);
395
}
396
397
return ret;
398
}
399
400