Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/tests/core/math/test_geometry_2d.h
10278 views
1
/**************************************************************************/
2
/* test_geometry_2d.h */
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
#pragma once
32
33
#include "core/math/geometry_2d.h"
34
35
#include "thirdparty/doctest/doctest.h"
36
37
namespace TestGeometry2D {
38
39
TEST_CASE("[Geometry2D] Point in circle") {
40
CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(0, 0), 1.0));
41
42
CHECK(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(11.99, 0), 12));
43
CHECK(Geometry2D::is_point_in_circle(Vector2(-11.99, 0), Vector2(0, 0), 12));
44
45
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(0, 0), Vector2(12.01, 0), 12));
46
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(-12.01, 0), Vector2(0, 0), 12));
47
48
CHECK(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.7));
49
CHECK_FALSE(Geometry2D::is_point_in_circle(Vector2(7, -42), Vector2(4, -40), 3.5));
50
51
// This tests points on the edge of the circle. They are treated as being inside the circle.
52
CHECK(Geometry2D::is_point_in_circle(Vector2(1.0, 0.0), Vector2(0, 0), 1.0));
53
CHECK(Geometry2D::is_point_in_circle(Vector2(0.0, -1.0), Vector2(0, 0), 1.0));
54
}
55
56
TEST_CASE("[Geometry2D] Point in triangle") {
57
CHECK(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
58
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(-1.01, 1.0), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
59
60
CHECK(Geometry2D::is_point_in_triangle(Vector2(3, 2.5), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
61
CHECK(Geometry2D::is_point_in_triangle(Vector2(-3, -2.5), Vector2(-1, -4), Vector2(-3, -2), Vector2(-5, -4)));
62
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 0), Vector2(1, 4), Vector2(3, 2), Vector2(5, 4)));
63
64
// This tests points on the edge of the triangle. They are treated as being outside the triangle.
65
// In `is_point_in_circle` and `is_point_in_polygon` they are treated as being inside, so in order the make
66
// the behavior consistent this may change in the future (see issue #44717 and PR #44274).
67
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(1, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
68
CHECK_FALSE(Geometry2D::is_point_in_triangle(Vector2(0, 1), Vector2(-1, 1), Vector2(0, -1), Vector2(1, 1)));
69
}
70
71
TEST_CASE("[Geometry2D] Point in polygon") {
72
Vector<Vector2> p;
73
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
74
75
p.push_back(Vector2(-88, 120));
76
p.push_back(Vector2(-74, -38));
77
p.push_back(Vector2(135, -145));
78
p.push_back(Vector2(425, 70));
79
p.push_back(Vector2(68, 112));
80
p.push_back(Vector2(-120, 370));
81
p.push_back(Vector2(-323, -145));
82
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-350, 0), p));
83
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-110, 60), p));
84
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(412, 96), p));
85
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(83, 130), p));
86
CHECK_FALSE(Geometry2D::is_point_in_polygon(Vector2(-320, -153), p));
87
88
CHECK(Geometry2D::is_point_in_polygon(Vector2(0, 0), p));
89
CHECK(Geometry2D::is_point_in_polygon(Vector2(-230, 0), p));
90
CHECK(Geometry2D::is_point_in_polygon(Vector2(130, -110), p));
91
CHECK(Geometry2D::is_point_in_polygon(Vector2(370, 55), p));
92
CHECK(Geometry2D::is_point_in_polygon(Vector2(-160, 190), p));
93
94
// This tests points on the edge of the polygon. They are treated as being inside the polygon.
95
int c = p.size();
96
for (int i = 0; i < c; i++) {
97
const Vector2 &p1 = p[i];
98
CHECK(Geometry2D::is_point_in_polygon(p1, p));
99
100
const Vector2 &p2 = p[(i + 1) % c];
101
Vector2 midpoint((p1 + p2) * 0.5);
102
CHECK(Geometry2D::is_point_in_polygon(midpoint, p));
103
}
104
}
105
106
TEST_CASE("[Geometry2D] Polygon clockwise") {
107
Vector<Vector2> p;
108
CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
109
110
p.push_back(Vector2(5, -5));
111
p.push_back(Vector2(-1, -5));
112
p.push_back(Vector2(-5, -1));
113
p.push_back(Vector2(-1, 3));
114
p.push_back(Vector2(1, 5));
115
CHECK(Geometry2D::is_polygon_clockwise(p));
116
117
p.reverse();
118
CHECK_FALSE(Geometry2D::is_polygon_clockwise(p));
119
}
120
121
TEST_CASE("[Geometry2D] Line intersection") {
122
Vector2 r;
123
CHECK(Geometry2D::line_intersects_line(Vector2(2, 0), Vector2(0, 1), Vector2(0, 2), Vector2(1, 0), r));
124
CHECK(r.is_equal_approx(Vector2(2, 2)));
125
126
CHECK(Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(4, 1), Vector2(-1, -1), r));
127
CHECK(r.is_equal_approx(Vector2(1.5, -1.5)));
128
129
CHECK(Geometry2D::line_intersects_line(Vector2(-1, 0), Vector2(-1, -1), Vector2(1, 0), Vector2(1, -1), r));
130
CHECK(r.is_equal_approx(Vector2(0, 1)));
131
132
CHECK_FALSE_MESSAGE(
133
Geometry2D::line_intersects_line(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(1, -1), r),
134
"Parallel lines should not intersect.");
135
}
136
137
TEST_CASE("[Geometry2D] Segment intersection") {
138
Vector2 r;
139
140
CHECK(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), &r));
141
CHECK(r.is_equal_approx(Vector2(0, 0)));
142
143
CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(0.1, 0.1), &r));
144
CHECK_FALSE(Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0.1, 0.1), Vector2(1, 1), &r));
145
146
CHECK_FALSE_MESSAGE(
147
Geometry2D::segment_intersects_segment(Vector2(-1, 1), Vector2(1, -1), Vector2(0, 1), Vector2(2, -1), &r),
148
"Parallel segments should not intersect.");
149
150
CHECK_FALSE_MESSAGE(
151
Geometry2D::segment_intersects_segment(Vector2(1, 2), Vector2(3, 2), Vector2(0, 2), Vector2(-2, 2), &r),
152
"Non-overlapping collinear segments should not intersect.");
153
154
CHECK_MESSAGE(
155
Geometry2D::segment_intersects_segment(Vector2(0, 0), Vector2(0, 1), Vector2(0, 0), Vector2(1, 0), &r),
156
"Touching segments should intersect.");
157
CHECK(r.is_equal_approx(Vector2(0, 0)));
158
159
CHECK_MESSAGE(
160
Geometry2D::segment_intersects_segment(Vector2(0, 1), Vector2(0, 0), Vector2(0, 0), Vector2(1, 0), &r),
161
"Touching segments should intersect.");
162
CHECK(r.is_equal_approx(Vector2(0, 0)));
163
}
164
165
TEST_CASE("[Geometry2D] Segment intersection with circle") {
166
constexpr real_t minus_one = -1.0;
167
constexpr real_t zero = 0.0;
168
constexpr real_t one_quarter = 0.25;
169
constexpr real_t three_quarters = 0.75;
170
constexpr real_t one = 1.0;
171
172
CHECK_MESSAGE(
173
Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(4, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
174
"Segment from inside to outside of circle should intersect it.");
175
CHECK_MESSAGE(
176
Geometry2D::segment_intersects_circle(Vector2(4, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(three_quarters),
177
"Segment from outside to inside of circle should intersect it.");
178
179
CHECK_MESSAGE(
180
Geometry2D::segment_intersects_circle(Vector2(-2, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
181
"Segment running through circle should intersect it.");
182
CHECK_MESSAGE(
183
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(-2, 0), Vector2(0, 0), 1.0) == doctest::Approx(one_quarter),
184
"Segment running through circle should intersect it.");
185
186
CHECK_MESSAGE(
187
Geometry2D::segment_intersects_circle(Vector2(0, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
188
"Segment starting inside the circle and ending on the circle should intersect it");
189
CHECK_MESSAGE(
190
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(0, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
191
"Segment starting on the circle and going inwards should intersect it");
192
CHECK_MESSAGE(
193
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(zero),
194
"Segment starting on the circle and going outwards should intersect it");
195
CHECK_MESSAGE(
196
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(1, 0), Vector2(0, 0), 1.0) == doctest::Approx(one),
197
"Segment starting outside the circle and ending on the circle intersect it");
198
199
CHECK_MESSAGE(
200
Geometry2D::segment_intersects_circle(Vector2(-1, 0), Vector2(1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
201
"Segment completely within the circle should not intersect it");
202
CHECK_MESSAGE(
203
Geometry2D::segment_intersects_circle(Vector2(1, 0), Vector2(-1, 0), Vector2(0, 0), 2.0) == doctest::Approx(minus_one),
204
"Segment completely within the circle should not intersect it");
205
CHECK_MESSAGE(
206
Geometry2D::segment_intersects_circle(Vector2(2, 0), Vector2(3, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
207
"Segment completely outside the circle should not intersect it");
208
CHECK_MESSAGE(
209
Geometry2D::segment_intersects_circle(Vector2(3, 0), Vector2(2, 0), Vector2(0, 0), 1.0) == doctest::Approx(minus_one),
210
"Segment completely outside the circle should not intersect it");
211
}
212
213
TEST_CASE("[Geometry2D] Segment intersection with polygon") {
214
Vector<Point2> a;
215
216
a.push_back(Point2(-2, 2));
217
a.push_back(Point2(3, 4));
218
a.push_back(Point2(1, 1));
219
a.push_back(Point2(2, -2));
220
a.push_back(Point2(-1, -1));
221
222
CHECK_MESSAGE(
223
Geometry2D::is_segment_intersecting_polygon(Vector2(0, 2), Vector2(2, 2), a),
224
"Segment from inside to outside of polygon should intersect it.");
225
CHECK_MESSAGE(
226
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(0, 2), a),
227
"Segment from outside to inside of polygon should intersect it.");
228
229
CHECK_MESSAGE(
230
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 4), Vector2(3, 3), a),
231
"Segment running through polygon should intersect it.");
232
CHECK_MESSAGE(
233
Geometry2D::is_segment_intersecting_polygon(Vector2(3, 3), Vector2(2, 4), a),
234
"Segment running through polygon should intersect it.");
235
236
CHECK_MESSAGE(
237
Geometry2D::is_segment_intersecting_polygon(Vector2(0, 0), Vector2(1, 1), a),
238
"Segment starting inside the polygon and ending on the polygon should intersect it");
239
CHECK_MESSAGE(
240
Geometry2D::is_segment_intersecting_polygon(Vector2(1, 1), Vector2(0, 0), a),
241
"Segment starting on the polygon and going inwards should intersect it");
242
CHECK_MESSAGE(
243
Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 2), Vector2(-2, -1), a),
244
"Segment starting on the polygon and going outwards should intersect it");
245
CHECK_MESSAGE(
246
Geometry2D::is_segment_intersecting_polygon(Vector2(-2, 1), Vector2(-2, 2), a),
247
"Segment starting outside the polygon and ending on the polygon intersect it");
248
249
CHECK_FALSE_MESSAGE(
250
Geometry2D::is_segment_intersecting_polygon(Vector2(-1, 2), Vector2(1, -1), a),
251
"Segment completely within the polygon should not intersect it");
252
CHECK_FALSE_MESSAGE(
253
Geometry2D::is_segment_intersecting_polygon(Vector2(1, -1), Vector2(-1, 2), a),
254
"Segment completely within the polygon should not intersect it");
255
CHECK_FALSE_MESSAGE(
256
Geometry2D::is_segment_intersecting_polygon(Vector2(2, 2), Vector2(2, -1), a),
257
"Segment completely outside the polygon should not intersect it");
258
CHECK_FALSE_MESSAGE(
259
Geometry2D::is_segment_intersecting_polygon(Vector2(2, -1), Vector2(2, 2), a),
260
"Segment completely outside the polygon should not intersect it");
261
}
262
263
TEST_CASE("[Geometry2D] Closest point to segment") {
264
Vector2 a = Vector2(-4, -4);
265
Vector2 b = Vector2(4, 4);
266
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(4.1, 4.1), a, b).is_equal_approx(Vector2(4, 4)));
267
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-4.1, -4.1), a, b).is_equal_approx(Vector2(-4, -4)));
268
CHECK(Geometry2D::get_closest_point_to_segment(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
269
270
a = Vector2(1, -2);
271
b = Vector2(1, -2);
272
CHECK_MESSAGE(
273
Geometry2D::get_closest_point_to_segment(Vector2(-3, 4), a, b).is_equal_approx(Vector2(1, -2)),
274
"Line segment is only a single point. This point should be the closest.");
275
}
276
277
TEST_CASE("[Geometry2D] Closest point to uncapped segment") {
278
constexpr Vector2 a = Vector2(-4, -4);
279
constexpr Vector2 b = Vector2(4, 4);
280
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-1, 1), a, b).is_equal_approx(Vector2(0, 0)));
281
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(-4, -6), a, b).is_equal_approx(Vector2(-5, -5)));
282
CHECK(Geometry2D::get_closest_point_to_segment_uncapped(Vector2(4, 6), a, b).is_equal_approx(Vector2(5, 5)));
283
}
284
285
TEST_CASE("[Geometry2D] Closest points between segments") {
286
Vector2 c1, c2;
287
// Basis Path Testing suite
288
SUBCASE("[Geometry2D] Both segments degenerate to a point") {
289
Geometry2D::get_closest_points_between_segments(Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), Vector2(0, 0), c1, c2);
290
CHECK(c1.is_equal_approx(Vector2(0, 0)));
291
CHECK(c2.is_equal_approx(Vector2(0, 0)));
292
}
293
294
SUBCASE("[Geometry2D] Closest point on second segment trajectory is above [0,1]") {
295
Geometry2D::get_closest_points_between_segments(Vector2(50, -25), Vector2(50, -10), Vector2(-50, 10), Vector2(-40, 10), c1, c2);
296
CHECK(c1.is_equal_approx(Vector2(50, -10)));
297
CHECK(c2.is_equal_approx(Vector2(-40, 10)));
298
}
299
300
SUBCASE("[Geometry2D] Parallel segments") {
301
Geometry2D::get_closest_points_between_segments(Vector2(2, 1), Vector2(4, 3), Vector2(2, 3), Vector2(4, 5), c1, c2);
302
CHECK(c1.is_equal_approx(Vector2(3, 2)));
303
CHECK(c2.is_equal_approx(Vector2(2, 3)));
304
}
305
306
SUBCASE("[Geometry2D] Closest point on second segment trajectory is within [0,1]") {
307
Geometry2D::get_closest_points_between_segments(Vector2(2, 4), Vector2(2, 3), Vector2(1, 1), Vector2(4, 4), c1, c2);
308
CHECK(c1.is_equal_approx(Vector2(2, 3)));
309
CHECK(c2.is_equal_approx(Vector2(2.5, 2.5)));
310
}
311
312
SUBCASE("[Geometry2D] Closest point on second segment trajectory is below [0,1]") {
313
Geometry2D::get_closest_points_between_segments(Vector2(-20, -20), Vector2(-10, -40), Vector2(10, 25), Vector2(25, 40), c1, c2);
314
CHECK(c1.is_equal_approx(Vector2(-20, -20)));
315
CHECK(c2.is_equal_approx(Vector2(10, 25)));
316
}
317
318
SUBCASE("[Geometry2D] Second segment degenerates to a point") {
319
Geometry2D::get_closest_points_between_segments(Vector2(1, 2), Vector2(2, 1), Vector2(3, 3), Vector2(3, 3), c1, c2);
320
CHECK(c1.is_equal_approx(Vector2(1.5, 1.5)));
321
CHECK(c2.is_equal_approx(Vector2(3, 3)));
322
}
323
324
SUBCASE("[Geometry2D] First segment degenerates to a point") {
325
Geometry2D::get_closest_points_between_segments(Vector2(1, 1), Vector2(1, 1), Vector2(2, 2), Vector2(4, 4), c1, c2);
326
CHECK(c1.is_equal_approx(Vector2(1, 1)));
327
CHECK(c2.is_equal_approx(Vector2(2, 2)));
328
}
329
// End Basis Path Testing suite
330
331
SUBCASE("[Geometry2D] Segments are equal vectors") {
332
Geometry2D::get_closest_points_between_segments(Vector2(2, 2), Vector2(3, 3), Vector2(4, 4), Vector2(4, 5), c1, c2);
333
CHECK(c1.is_equal_approx(Vector2(3, 3)));
334
CHECK(c2.is_equal_approx(Vector2(4, 4)));
335
}
336
337
SUBCASE("[Geometry2D] Standard case") {
338
Geometry2D::get_closest_points_between_segments(Vector2(0, 1), Vector2(-2, -1), Vector2(0, 0), Vector2(2, -2), c1, c2);
339
CHECK(c1.is_equal_approx(Vector2(-0.5, 0.5)));
340
CHECK(c2.is_equal_approx(Vector2(0, 0)));
341
}
342
343
SUBCASE("[Geometry2D] Segments intersect") {
344
Geometry2D::get_closest_points_between_segments(Vector2(-1, 1), Vector2(1, -1), Vector2(1, 1), Vector2(-1, -1), c1, c2);
345
CHECK(c1.is_equal_approx(Vector2(0, 0)));
346
CHECK(c2.is_equal_approx(Vector2(0, 0)));
347
}
348
}
349
350
TEST_CASE("[Geometry2D] Make atlas") {
351
Vector<Point2i> result;
352
Size2i size;
353
354
Vector<Size2i> r;
355
r.push_back(Size2i(2, 2));
356
Geometry2D::make_atlas(r, result, size);
357
CHECK(size == Size2i(2, 2));
358
CHECK(result.size() == r.size());
359
360
r.clear();
361
result.clear();
362
r.push_back(Size2i(1, 2));
363
r.push_back(Size2i(3, 4));
364
r.push_back(Size2i(5, 6));
365
r.push_back(Size2i(7, 8));
366
Geometry2D::make_atlas(r, result, size);
367
CHECK(result.size() == r.size());
368
}
369
370
TEST_CASE("[Geometry2D] Polygon intersection") {
371
Vector<Point2> a;
372
Vector<Point2> b;
373
Vector<Vector<Point2>> r;
374
375
a.push_back(Point2(30, 60));
376
a.push_back(Point2(70, 5));
377
a.push_back(Point2(200, 40));
378
a.push_back(Point2(80, 200));
379
380
SUBCASE("[Geometry2D] Both polygons are empty") {
381
r = Geometry2D::intersect_polygons(Vector<Point2>(), Vector<Point2>());
382
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The intersection should also be empty.");
383
}
384
385
SUBCASE("[Geometry2D] One polygon is empty") {
386
r = Geometry2D::intersect_polygons(a, b);
387
REQUIRE_MESSAGE(r.is_empty(), "One polygon is empty. The intersection should also be empty.");
388
}
389
390
SUBCASE("[Geometry2D] Basic intersection") {
391
b.push_back(Point2(200, 300));
392
b.push_back(Point2(90, 200));
393
b.push_back(Point2(50, 100));
394
b.push_back(Point2(200, 90));
395
r = Geometry2D::intersect_polygons(a, b);
396
REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
397
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
398
CHECK(r[0][0].is_equal_approx(Point2(86.52174, 191.30436)));
399
CHECK(r[0][1].is_equal_approx(Point2(50, 100)));
400
CHECK(r[0][2].is_equal_approx(Point2(160.52632, 92.63157)));
401
}
402
403
SUBCASE("[Geometry2D] Intersection with one polygon being completely inside the other polygon") {
404
b.push_back(Point2(80, 100));
405
b.push_back(Point2(50, 50));
406
b.push_back(Point2(150, 50));
407
r = Geometry2D::intersect_polygons(a, b);
408
REQUIRE_MESSAGE(r.size() == 1, "The polygons should intersect each other with 1 resulting intersection polygon.");
409
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting intersection polygon should have 3 vertices.");
410
CHECK(r[0][0].is_equal_approx(b[0]));
411
CHECK(r[0][1].is_equal_approx(b[1]));
412
CHECK(r[0][2].is_equal_approx(b[2]));
413
}
414
415
SUBCASE("[Geometry2D] No intersection with 2 non-empty polygons") {
416
b.push_back(Point2(150, 150));
417
b.push_back(Point2(250, 100));
418
b.push_back(Point2(300, 200));
419
r = Geometry2D::intersect_polygons(a, b);
420
REQUIRE_MESSAGE(r.is_empty(), "The polygons should not intersect each other.");
421
}
422
423
SUBCASE("[Geometry2D] Intersection with 2 resulting polygons") {
424
a.clear();
425
a.push_back(Point2(70, 5));
426
a.push_back(Point2(140, 7));
427
a.push_back(Point2(100, 52));
428
a.push_back(Point2(170, 50));
429
a.push_back(Point2(60, 125));
430
b.push_back(Point2(70, 105));
431
b.push_back(Point2(115, 55));
432
b.push_back(Point2(90, 15));
433
b.push_back(Point2(160, 50));
434
r = Geometry2D::intersect_polygons(a, b);
435
REQUIRE_MESSAGE(r.size() == 2, "The polygons should intersect each other with 2 resulting intersection polygons.");
436
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting intersection polygon should have 4 vertices.");
437
CHECK(r[0][0].is_equal_approx(Point2(70, 105)));
438
CHECK(r[0][1].is_equal_approx(Point2(115, 55)));
439
CHECK(r[0][2].is_equal_approx(Point2(112.894737, 51.63158)));
440
CHECK(r[0][3].is_equal_approx(Point2(159.509537, 50.299728)));
441
442
REQUIRE_MESSAGE(r[1].size() == 3, "The intersection polygon should have 3 vertices.");
443
CHECK(r[1][0].is_equal_approx(Point2(119.692307, 29.846149)));
444
CHECK(r[1][1].is_equal_approx(Point2(107.706421, 43.33028)));
445
CHECK(r[1][2].is_equal_approx(Point2(90, 15)));
446
}
447
}
448
449
TEST_CASE("[Geometry2D] Merge polygons") {
450
Vector<Point2> a;
451
Vector<Point2> b;
452
Vector<Vector<Point2>> r;
453
454
a.push_back(Point2(225, 180));
455
a.push_back(Point2(160, 230));
456
a.push_back(Point2(20, 212));
457
a.push_back(Point2(50, 115));
458
459
SUBCASE("[Geometry2D] Both polygons are empty") {
460
r = Geometry2D::merge_polygons(Vector<Point2>(), Vector<Point2>());
461
REQUIRE_MESSAGE(r.is_empty(), "Both polygons are empty. The union should also be empty.");
462
}
463
464
SUBCASE("[Geometry2D] One polygon is empty") {
465
r = Geometry2D::merge_polygons(a, b);
466
REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting merged polygon.");
467
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting merged polygon should have 4 vertices.");
468
CHECK(r[0][0].is_equal_approx(a[0]));
469
CHECK(r[0][1].is_equal_approx(a[1]));
470
CHECK(r[0][2].is_equal_approx(a[2]));
471
CHECK(r[0][3].is_equal_approx(a[3]));
472
}
473
474
SUBCASE("[Geometry2D] Basic merge with 2 polygons") {
475
b.push_back(Point2(180, 190));
476
b.push_back(Point2(60, 140));
477
b.push_back(Point2(160, 80));
478
r = Geometry2D::merge_polygons(a, b);
479
REQUIRE_MESSAGE(r.size() == 1, "The merged polygons should result in 1 polygon.");
480
REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon should have 7 vertices.");
481
CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
482
CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
483
CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
484
CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
485
CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
486
CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
487
CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
488
}
489
490
SUBCASE("[Geometry2D] Merge with 2 resulting merged polygons (outline and hole)") {
491
b.push_back(Point2(180, 190));
492
b.push_back(Point2(140, 125));
493
b.push_back(Point2(60, 140));
494
b.push_back(Point2(160, 80));
495
r = Geometry2D::merge_polygons(a, b);
496
REQUIRE_MESSAGE(r.size() == 2, "The merged polygons should result in 2 polygons.");
497
498
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The merged polygon (outline) should be counter-clockwise.");
499
REQUIRE_MESSAGE(r[0].size() == 7, "The resulting merged polygon (outline) should have 7 vertices.");
500
CHECK(r[0][0].is_equal_approx(Point2(174.791077, 161.350967)));
501
CHECK(r[0][1].is_equal_approx(Point2(225, 180)));
502
CHECK(r[0][2].is_equal_approx(Point2(160, 230)));
503
CHECK(r[0][3].is_equal_approx(Point2(20, 212)));
504
CHECK(r[0][4].is_equal_approx(Point2(50, 115)));
505
CHECK(r[0][5].is_equal_approx(Point2(81.911758, 126.852943)));
506
CHECK(r[0][6].is_equal_approx(Point2(160, 80)));
507
508
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting merged polygon (hole) should be clockwise.");
509
REQUIRE_MESSAGE(r[1].size() == 3, "The resulting merged polygon (hole) should have 3 vertices.");
510
CHECK(r[1][0].is_equal_approx(Point2(98.083069, 132.859421)));
511
CHECK(r[1][1].is_equal_approx(Point2(158.689453, 155.370377)));
512
CHECK(r[1][2].is_equal_approx(Point2(140, 125)));
513
}
514
}
515
516
TEST_CASE("[Geometry2D] Clip polygons") {
517
Vector<Point2> a;
518
Vector<Point2> b;
519
Vector<Vector<Point2>> r;
520
521
a.push_back(Point2(225, 180));
522
a.push_back(Point2(160, 230));
523
a.push_back(Point2(20, 212));
524
a.push_back(Point2(50, 115));
525
526
SUBCASE("[Geometry2D] Both polygons are empty") {
527
r = Geometry2D::clip_polygons(Vector<Point2>(), Vector<Point2>());
528
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The clip should also be empty.");
529
}
530
531
SUBCASE("[Geometry2D] Basic clip with one result polygon") {
532
b.push_back(Point2(250, 170));
533
b.push_back(Point2(175, 270));
534
b.push_back(Point2(120, 260));
535
b.push_back(Point2(25, 80));
536
r = Geometry2D::clip_polygons(a, b);
537
REQUIRE_MESSAGE(r.size() == 1, "The clipped polygons should result in 1 polygon.");
538
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped polygon should have 3 vertices.");
539
CHECK(r[0][0].is_equal_approx(Point2(100.102173, 222.298843)));
540
CHECK(r[0][1].is_equal_approx(Point2(20, 212)));
541
CHECK(r[0][2].is_equal_approx(Point2(47.588089, 122.798492)));
542
}
543
544
SUBCASE("[Geometry2D] Polygon b completely overlaps polygon a") {
545
b.push_back(Point2(250, 170));
546
b.push_back(Point2(175, 270));
547
b.push_back(Point2(10, 210));
548
b.push_back(Point2(55, 80));
549
r = Geometry2D::clip_polygons(a, b);
550
CHECK_MESSAGE(r.is_empty(), "Polygon 'b' completely overlaps polygon 'a'. This should result in no clipped polygons.");
551
}
552
553
SUBCASE("[Geometry2D] Polygon a completely overlaps polygon b") {
554
b.push_back(Point2(150, 200));
555
b.push_back(Point2(65, 190));
556
b.push_back(Point2(80, 140));
557
r = Geometry2D::clip_polygons(a, b);
558
REQUIRE_MESSAGE(r.size() == 2, "Polygon 'a' completely overlaps polygon 'b'. This should result in 2 clipped polygons.");
559
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting clipped polygon should have 4 vertices.");
560
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting clipped polygon (outline) should be counter-clockwise.");
561
CHECK(r[0][0].is_equal_approx(a[0]));
562
CHECK(r[0][1].is_equal_approx(a[1]));
563
CHECK(r[0][2].is_equal_approx(a[2]));
564
CHECK(r[0][3].is_equal_approx(a[3]));
565
REQUIRE_MESSAGE(r[1].size() == 3, "The resulting clipped polygon should have 3 vertices.");
566
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting clipped polygon (hole) should be clockwise.");
567
CHECK(r[1][0].is_equal_approx(b[1]));
568
CHECK(r[1][1].is_equal_approx(b[0]));
569
CHECK(r[1][2].is_equal_approx(b[2]));
570
}
571
}
572
573
TEST_CASE("[Geometry2D] Exclude polygons") {
574
Vector<Point2> a;
575
Vector<Point2> b;
576
Vector<Vector<Point2>> r;
577
578
a.push_back(Point2(225, 180));
579
a.push_back(Point2(160, 230));
580
a.push_back(Point2(20, 212));
581
a.push_back(Point2(50, 115));
582
583
SUBCASE("[Geometry2D] Both polygons are empty") {
584
r = Geometry2D::exclude_polygons(Vector<Point2>(), Vector<Point2>());
585
CHECK_MESSAGE(r.is_empty(), "Both polygons are empty. The excluded polygon should also be empty.");
586
}
587
588
SUBCASE("[Geometry2D] One polygon is empty") {
589
r = Geometry2D::exclude_polygons(a, b);
590
REQUIRE_MESSAGE(r.size() == 1, "One polygon is non-empty. There should be 1 resulting excluded polygon.");
591
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
592
CHECK(r[0][0].is_equal_approx(a[0]));
593
CHECK(r[0][1].is_equal_approx(a[1]));
594
CHECK(r[0][2].is_equal_approx(a[2]));
595
CHECK(r[0][3].is_equal_approx(a[3]));
596
}
597
598
SUBCASE("[Geometry2D] Exclude with 2 resulting polygons (outline and hole)") {
599
b.push_back(Point2(140, 160));
600
b.push_back(Point2(150, 220));
601
b.push_back(Point2(40, 200));
602
b.push_back(Point2(60, 140));
603
r = Geometry2D::exclude_polygons(a, b);
604
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting excluded polygons (outline and hole).");
605
REQUIRE_MESSAGE(r[0].size() == 4, "The resulting excluded polygon should have 4 vertices.");
606
REQUIRE_MESSAGE(!Geometry2D::is_polygon_clockwise(r[0]), "The resulting excluded polygon (outline) should be counter-clockwise.");
607
CHECK(r[0][0].is_equal_approx(a[0]));
608
CHECK(r[0][1].is_equal_approx(a[1]));
609
CHECK(r[0][2].is_equal_approx(a[2]));
610
CHECK(r[0][3].is_equal_approx(a[3]));
611
REQUIRE_MESSAGE(r[1].size() == 4, "The resulting excluded polygon should have 4 vertices.");
612
REQUIRE_MESSAGE(Geometry2D::is_polygon_clockwise(r[1]), "The resulting excluded polygon (hole) should be clockwise.");
613
CHECK(r[1][0].is_equal_approx(Point2(40, 200)));
614
CHECK(r[1][1].is_equal_approx(Point2(150, 220)));
615
CHECK(r[1][2].is_equal_approx(Point2(140, 160)));
616
CHECK(r[1][3].is_equal_approx(Point2(60, 140)));
617
}
618
}
619
620
TEST_CASE("[Geometry2D] Intersect polyline with polygon") {
621
Vector<Vector2> l;
622
Vector<Vector2> p;
623
Vector<Vector<Point2>> r;
624
625
l.push_back(Vector2(100, 90));
626
l.push_back(Vector2(120, 250));
627
628
p.push_back(Vector2(225, 180));
629
p.push_back(Vector2(160, 230));
630
p.push_back(Vector2(20, 212));
631
p.push_back(Vector2(50, 115));
632
633
SUBCASE("[Geometry2D] Both line and polygon are empty") {
634
r = Geometry2D::intersect_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
635
CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The intersection line should also be empty.");
636
}
637
638
SUBCASE("[Geometry2D] Line is non-empty and polygon is empty") {
639
r = Geometry2D::intersect_polyline_with_polygon(l, Vector<Vector2>());
640
CHECK_MESSAGE(r.is_empty(), "The polygon is empty while the line is non-empty. The intersection line should be empty.");
641
}
642
643
SUBCASE("[Geometry2D] Basic intersection with 1 resulting intersection line") {
644
r = Geometry2D::intersect_polyline_with_polygon(l, p);
645
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting intersection line.");
646
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
647
CHECK(r[0][0].is_equal_approx(Vector2(105.711609, 135.692886)));
648
CHECK(r[0][1].is_equal_approx(Vector2(116.805809, 224.446457)));
649
}
650
651
SUBCASE("[Geometry2D] Complex intersection with 2 resulting intersection lines") {
652
l.clear();
653
l.push_back(Vector2(100, 90));
654
l.push_back(Vector2(190, 255));
655
l.push_back(Vector2(135, 260));
656
l.push_back(Vector2(57, 200));
657
l.push_back(Vector2(50, 170));
658
l.push_back(Vector2(15, 155));
659
r = Geometry2D::intersect_polyline_with_polygon(l, p);
660
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting intersection lines.");
661
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting intersection line should have 2 vertices.");
662
CHECK(r[0][0].is_equal_approx(Vector2(129.804565, 144.641693)));
663
CHECK(r[0][1].is_equal_approx(Vector2(171.527084, 221.132996)));
664
REQUIRE_MESSAGE(r[1].size() == 4, "The resulting intersection line should have 4 vertices.");
665
CHECK(r[1][0].is_equal_approx(Vector2(83.15609, 220.120087)));
666
CHECK(r[1][1].is_equal_approx(Vector2(57, 200)));
667
CHECK(r[1][2].is_equal_approx(Vector2(50, 170)));
668
CHECK(r[1][3].is_equal_approx(Vector2(34.980492, 163.563065)));
669
}
670
}
671
672
TEST_CASE("[Geometry2D] Clip polyline with polygon") {
673
Vector<Vector2> l;
674
Vector<Vector2> p;
675
Vector<Vector<Point2>> r;
676
677
l.push_back(Vector2(70, 140));
678
l.push_back(Vector2(160, 320));
679
680
p.push_back(Vector2(225, 180));
681
p.push_back(Vector2(160, 230));
682
p.push_back(Vector2(20, 212));
683
p.push_back(Vector2(50, 115));
684
685
SUBCASE("[Geometry2D] Both line and polygon are empty") {
686
r = Geometry2D::clip_polyline_with_polygon(Vector<Vector2>(), Vector<Vector2>());
687
CHECK_MESSAGE(r.is_empty(), "Both line and polygon are empty. The clipped line should also be empty.");
688
}
689
690
SUBCASE("[Geometry2D] Polygon is empty and line is non-empty") {
691
r = Geometry2D::clip_polyline_with_polygon(l, Vector<Vector2>());
692
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
693
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
694
CHECK(r[0][0].is_equal_approx(l[0]));
695
CHECK(r[0][1].is_equal_approx(l[1]));
696
}
697
698
SUBCASE("[Geometry2D] Basic clip with 1 resulting clipped line") {
699
r = Geometry2D::clip_polyline_with_polygon(l, p);
700
REQUIRE_MESSAGE(r.size() == 1, "There should be 1 resulting clipped line.");
701
REQUIRE_MESSAGE(r[0].size() == 2, "The resulting clipped line should have 2 vertices.");
702
CHECK(r[0][0].is_equal_approx(Vector2(111.908401, 223.816803)));
703
CHECK(r[0][1].is_equal_approx(Vector2(160, 320)));
704
}
705
706
SUBCASE("[Geometry2D] Complex clip with 2 resulting clipped lines") {
707
l.clear();
708
l.push_back(Vector2(55, 70));
709
l.push_back(Vector2(50, 190));
710
l.push_back(Vector2(120, 165));
711
l.push_back(Vector2(122, 250));
712
l.push_back(Vector2(160, 320));
713
r = Geometry2D::clip_polyline_with_polygon(l, p);
714
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines.");
715
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices.");
716
CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757)));
717
CHECK(r[0][1].is_equal_approx(Vector2(122, 250)));
718
CHECK(r[0][2].is_equal_approx(Vector2(160, 320)));
719
REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices.");
720
CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
721
CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
722
}
723
}
724
725
TEST_CASE("[Geometry2D] Convex hull") {
726
Vector<Point2> a;
727
Vector<Point2> r;
728
729
a.push_back(Point2(-4, -8));
730
a.push_back(Point2(-10, -4));
731
a.push_back(Point2(8, 2));
732
a.push_back(Point2(-6, 10));
733
a.push_back(Point2(-12, 4));
734
a.push_back(Point2(10, -8));
735
a.push_back(Point2(4, 8));
736
737
SUBCASE("[Geometry2D] No points") {
738
r = Geometry2D::convex_hull(Vector<Vector2>());
739
740
CHECK_MESSAGE(r.is_empty(), "The convex hull should be empty if there are no input points.");
741
}
742
743
SUBCASE("[Geometry2D] Single point") {
744
Vector<Point2> b;
745
b.push_back(Point2(4, -3));
746
747
r = Geometry2D::convex_hull(b);
748
REQUIRE_MESSAGE(r.size() == 1, "Convex hull should contain 1 point.");
749
CHECK(r[0].is_equal_approx(b[0]));
750
}
751
752
SUBCASE("[Geometry2D] All points form the convex hull") {
753
r = Geometry2D::convex_hull(a);
754
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
755
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
756
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
757
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
758
CHECK(r[3].is_equal_approx(Point2(10, -8)));
759
CHECK(r[4].is_equal_approx(Point2(8, 2)));
760
CHECK(r[5].is_equal_approx(Point2(4, 8)));
761
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
762
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
763
}
764
765
SUBCASE("[Geometry2D] Add extra points inside original convex hull") {
766
a.push_back(Point2(-4, -8));
767
a.push_back(Point2(0, 0));
768
a.push_back(Point2(0, 8));
769
a.push_back(Point2(-10, -3));
770
a.push_back(Point2(9, -4));
771
a.push_back(Point2(6, 4));
772
773
r = Geometry2D::convex_hull(a);
774
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
775
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
776
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
777
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
778
CHECK(r[3].is_equal_approx(Point2(10, -8)));
779
CHECK(r[4].is_equal_approx(Point2(8, 2)));
780
CHECK(r[5].is_equal_approx(Point2(4, 8)));
781
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
782
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
783
}
784
785
SUBCASE("[Geometry2D] Add extra points on border of original convex hull") {
786
a.push_back(Point2(9, -3));
787
a.push_back(Point2(-2, -8));
788
789
r = Geometry2D::convex_hull(a);
790
REQUIRE_MESSAGE(r.size() == 8, "Convex hull should contain 8 points.");
791
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
792
CHECK(r[1].is_equal_approx(Point2(-10, -4)));
793
CHECK(r[2].is_equal_approx(Point2(-4, -8)));
794
CHECK(r[3].is_equal_approx(Point2(10, -8)));
795
CHECK(r[4].is_equal_approx(Point2(8, 2)));
796
CHECK(r[5].is_equal_approx(Point2(4, 8)));
797
CHECK(r[6].is_equal_approx(Point2(-6, 10)));
798
CHECK(r[7].is_equal_approx(Point2(-12, 4)));
799
}
800
801
SUBCASE("[Geometry2D] Add extra points outside border of original convex hull") {
802
a.push_back(Point2(-11, -1));
803
a.push_back(Point2(7, 6));
804
805
r = Geometry2D::convex_hull(a);
806
REQUIRE_MESSAGE(r.size() == 10, "Convex hull should contain 10 points.");
807
CHECK(r[0].is_equal_approx(Point2(-12, 4)));
808
CHECK(r[1].is_equal_approx(Point2(-11, -1)));
809
CHECK(r[2].is_equal_approx(Point2(-10, -4)));
810
CHECK(r[3].is_equal_approx(Point2(-4, -8)));
811
CHECK(r[4].is_equal_approx(Point2(10, -8)));
812
CHECK(r[5].is_equal_approx(Point2(8, 2)));
813
CHECK(r[6].is_equal_approx(Point2(7, 6)));
814
CHECK(r[7].is_equal_approx(Point2(4, 8)));
815
CHECK(r[8].is_equal_approx(Point2(-6, 10)));
816
CHECK(r[9].is_equal_approx(Point2(-12, 4)));
817
}
818
}
819
820
TEST_CASE("[Geometry2D] Bresenham line") {
821
Vector<Vector2i> r;
822
823
SUBCASE("[Geometry2D] Single point") {
824
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(0, 0));
825
826
REQUIRE_MESSAGE(r.size() == 1, "The Bresenham line should contain exactly one point.");
827
CHECK(r[0] == Vector2i(0, 0));
828
}
829
830
SUBCASE("[Geometry2D] Line parallel to x-axis") {
831
r = Geometry2D::bresenham_line(Point2i(1, 2), Point2i(5, 2));
832
833
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
834
CHECK(r[0] == Vector2i(1, 2));
835
CHECK(r[1] == Vector2i(2, 2));
836
CHECK(r[2] == Vector2i(3, 2));
837
CHECK(r[3] == Vector2i(4, 2));
838
CHECK(r[4] == Vector2i(5, 2));
839
}
840
841
SUBCASE("[Geometry2D] 45 degree line from the origin") {
842
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 4));
843
844
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
845
CHECK(r[0] == Vector2i(0, 0));
846
CHECK(r[1] == Vector2i(1, 1));
847
CHECK(r[2] == Vector2i(2, 2));
848
CHECK(r[3] == Vector2i(3, 3));
849
CHECK(r[4] == Vector2i(4, 4));
850
}
851
852
SUBCASE("[Geometry2D] Sloped line going up one unit") {
853
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 1));
854
855
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
856
CHECK(r[0] == Vector2i(0, 0));
857
CHECK(r[1] == Vector2i(1, 0));
858
CHECK(r[2] == Vector2i(2, 0));
859
CHECK(r[3] == Vector2i(3, 1));
860
CHECK(r[4] == Vector2i(4, 1));
861
}
862
863
SUBCASE("[Geometry2D] Sloped line going up two units") {
864
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(4, 2));
865
866
REQUIRE_MESSAGE(r.size() == 5, "The Bresenham line should contain exactly five points.");
867
CHECK(r[0] == Vector2i(0, 0));
868
CHECK(r[1] == Vector2i(1, 0));
869
CHECK(r[2] == Vector2i(2, 1));
870
CHECK(r[3] == Vector2i(3, 1));
871
CHECK(r[4] == Vector2i(4, 2));
872
}
873
874
SUBCASE("[Geometry2D] Long sloped line") {
875
r = Geometry2D::bresenham_line(Point2i(0, 0), Point2i(11, 5));
876
877
REQUIRE_MESSAGE(r.size() == 12, "The Bresenham line should contain exactly twelve points.");
878
CHECK(r[0] == Vector2i(0, 0));
879
CHECK(r[1] == Vector2i(1, 0));
880
CHECK(r[2] == Vector2i(2, 1));
881
CHECK(r[3] == Vector2i(3, 1));
882
CHECK(r[4] == Vector2i(4, 2));
883
CHECK(r[5] == Vector2i(5, 2));
884
CHECK(r[6] == Vector2i(6, 3));
885
CHECK(r[7] == Vector2i(7, 3));
886
CHECK(r[8] == Vector2i(8, 4));
887
CHECK(r[9] == Vector2i(9, 4));
888
CHECK(r[10] == Vector2i(10, 5));
889
CHECK(r[11] == Vector2i(11, 5));
890
}
891
}
892
} // namespace TestGeometry2D
893
894