Skip to content

Commit e3792af

Browse files
committed
added unit test for shape inflate
1 parent 08fef59 commit e3792af

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

test/irregular/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ target_sources(PackingSolver_irregular_test PRIVATE
55
shape_convex_hull_test.cpp
66
shape_self_intersections_removal_test.cpp
77
shape_trapezoidation_test.cpp
8+
shape_inflate_test.cpp
89
irregular_test.cpp)
910
target_include_directories(PackingSolver_irregular_test PRIVATE
1011
${PROJECT_SOURCE_DIR}/src)
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
#include "irregular/shape.hpp"
2+
#include "irregular/shape_self_intersections_removal.hpp"
3+
4+
#include <gtest/gtest.h>
5+
6+
using namespace packingsolver;
7+
using namespace packingsolver::irregular;
8+
9+
// build square
10+
Shape build_square(LengthDbl x, LengthDbl y, LengthDbl size)
11+
{
12+
return build_polygon_shape({
13+
{x, y},
14+
{x + size, y},
15+
{x + size, y + size},
16+
{x, y + size}
17+
});
18+
}
19+
20+
// build triangle
21+
Shape build_triangle(LengthDbl x, LengthDbl y, LengthDbl size)
22+
{
23+
return build_polygon_shape({
24+
{x, y},
25+
{x + size, y},
26+
{x + size / 2, y + size}
27+
});
28+
}
29+
30+
// test 1: test basic inflation - no holes
31+
TEST(IrregularShapeInflate, BasicInflate)
32+
{
33+
// build a simple square
34+
Shape square = build_square(0, 0, 10);
35+
36+
// inflate shape
37+
LengthDbl inflation_value = 2.0;
38+
Shape inflated_square = inflate(square, inflation_value);
39+
40+
// verify the inflated shape
41+
// the square's edges should be moved outwards by inflation_value units
42+
EXPECT_EQ(inflated_square.elements.size(), square.elements.size() * 2);
43+
44+
// expected new boundary coordinates
45+
LengthDbl expected_min_x = -inflation_value;
46+
LengthDbl expected_min_y = -inflation_value;
47+
LengthDbl expected_max_x = 10 + inflation_value;
48+
LengthDbl expected_max_y = 10 + inflation_value;
49+
50+
// check if the inflated shape contains the expected external points
51+
bool contains_min_x = false;
52+
bool contains_min_y = false;
53+
bool contains_max_x = false;
54+
bool contains_max_y = false;
55+
56+
for (const ShapeElement& element : inflated_square.elements) {
57+
if (element.start.x <= expected_min_x || element.end.x <= expected_min_x) {
58+
contains_min_x = true;
59+
}
60+
if (element.start.y <= expected_min_y || element.end.y <= expected_min_y) {
61+
contains_min_y = true;
62+
}
63+
if (element.start.x >= expected_max_x || element.end.x >= expected_max_x) {
64+
contains_max_x = true;
65+
}
66+
if (element.start.y >= expected_max_y || element.end.y >= expected_max_y) {
67+
contains_max_y = true;
68+
}
69+
}
70+
71+
EXPECT_TRUE(contains_min_x);
72+
EXPECT_TRUE(contains_min_y);
73+
EXPECT_TRUE(contains_max_x);
74+
EXPECT_TRUE(contains_max_y);
75+
}
76+
77+
// test 2: test inflation with holes
78+
TEST(IrregularShapeInflate, InflateWithHoles)
79+
{
80+
// build a square as the outer shape
81+
Shape outer_square = build_square(0, 0, 20);
82+
83+
// build a small square as the hole
84+
Shape hole = build_square(5, 5, 10);
85+
86+
// inflate the shape with the hole
87+
LengthDbl inflation_value = 2.0;
88+
Shape inflated_shape = inflate(outer_square, inflation_value, {hole});
89+
90+
// verify the inflated shape
91+
// the outer boundary should be expanded, and the hole should be contracted
92+
93+
// expected new outer boundary coordinates
94+
LengthDbl expected_outer_min_x = -inflation_value;
95+
LengthDbl expected_outer_min_y = -inflation_value;
96+
LengthDbl expected_outer_max_x = 20 + inflation_value;
97+
LengthDbl expected_outer_max_y = 20 + inflation_value;
98+
99+
// verify the inflated shape
100+
bool contains_outer_min_x = false;
101+
bool contains_outer_min_y = false;
102+
bool contains_outer_max_x = false;
103+
bool contains_outer_max_y = false;
104+
105+
for (const ShapeElement& element : inflated_shape.elements) {
106+
if (element.start.x <= expected_outer_min_x || element.end.x <= expected_outer_min_x) {
107+
contains_outer_min_x = true;
108+
}
109+
if (element.start.y <= expected_outer_min_y || element.end.y <= expected_outer_min_y) {
110+
contains_outer_min_y = true;
111+
}
112+
if (element.start.x >= expected_outer_max_x || element.end.x >= expected_outer_max_x) {
113+
contains_outer_max_x = true;
114+
}
115+
if (element.start.y >= expected_outer_max_y || element.end.y >= expected_outer_max_y) {
116+
contains_outer_max_y = true;
117+
}
118+
}
119+
120+
EXPECT_TRUE(contains_outer_min_x);
121+
EXPECT_TRUE(contains_outer_min_y);
122+
EXPECT_TRUE(contains_outer_max_x);
123+
EXPECT_TRUE(contains_outer_max_y);
124+
}
125+
126+
// test 3: test the case that may cause self-intersections
127+
TEST(IrregularShapeInflate, SelfIntersectingInflate)
128+
{
129+
// build a "U" shape, larger inflation will cause self-intersections between the two arms
130+
Shape u_shape = build_polygon_shape({
131+
{0, 0},
132+
{0, 10},
133+
{2, 10},
134+
{2, 2},
135+
{8, 2},
136+
{8, 10},
137+
{10, 10},
138+
{10, 0}
139+
});
140+
141+
// use a larger inflation value, ensure self-intersections will be produced
142+
LengthDbl inflation_value = 2.0;
143+
Shape inflated_shape = inflate(u_shape, inflation_value);
144+
145+
// verify the inflated shape has no self-intersections
146+
// first check if the self-intersections removal function works properly
147+
auto processed = remove_self_intersections(inflated_shape);
148+
Shape no_self_intersections = processed.first;
149+
std::vector<Shape> holes = processed.second;
150+
151+
// after self-intersections removal, we should get a shape with no self-intersections and possible holes
152+
// U shape inflation will cause a hole in the middle
153+
EXPECT_FALSE(holes.empty()); // should at least produce one hole
154+
155+
// verify the processed shape is still complete
156+
EXPECT_GT(no_self_intersections.elements.size(), 0);
157+
}
158+
159+
// test 4: test shape deflation
160+
TEST(IrregularShapeInflate, Deflate)
161+
{
162+
// build a large square
163+
Shape square = build_square(0, 0, 20);
164+
165+
// deflate shape (use negative inflation value)
166+
LengthDbl deflation_value = -5.0;
167+
Shape deflated_square = inflate(square, deflation_value);
168+
169+
// verify the deflated shape
170+
// the square's edges should be moved inwards by |deflation_value| units
171+
172+
// expected new boundary coordinates
173+
LengthDbl expected_min_x = -deflation_value;
174+
LengthDbl expected_min_y = -deflation_value;
175+
LengthDbl expected_max_x = 20 + deflation_value;
176+
LengthDbl expected_max_y = 20 + deflation_value;
177+
178+
// check the boundary of the deflated shape
179+
LengthDbl actual_min_x = std::numeric_limits<LengthDbl>::max();
180+
LengthDbl actual_min_y = std::numeric_limits<LengthDbl>::max();
181+
LengthDbl actual_max_x = std::numeric_limits<LengthDbl>::lowest();
182+
LengthDbl actual_max_y = std::numeric_limits<LengthDbl>::lowest();
183+
184+
for (const ShapeElement& element : deflated_square.elements) {
185+
actual_min_x = std::min({actual_min_x, element.start.x, element.end.x});
186+
actual_min_y = std::min({actual_min_y, element.start.y, element.end.y});
187+
actual_max_x = std::max({actual_max_x, element.start.x, element.end.x});
188+
actual_max_y = std::max({actual_max_y, element.start.y, element.end.y});
189+
}
190+
191+
// allow some tolerance
192+
const LengthDbl tolerance = 0.01;
193+
194+
EXPECT_NEAR(actual_min_x, expected_min_x, tolerance);
195+
EXPECT_NEAR(actual_min_y, expected_min_y, tolerance);
196+
EXPECT_NEAR(actual_max_x, expected_max_x, tolerance);
197+
EXPECT_NEAR(actual_max_y, expected_max_y, tolerance);
198+
}
199+
200+
// test 5: test the case that the shape is too small
201+
TEST(IrregularShapeInflate, TinyShape)
202+
{
203+
// build a very small square
204+
Shape tiny_square = build_square(0, 0, 0.1);
205+
206+
// inflate shape
207+
LengthDbl inflation_value = 0.5;
208+
Shape inflated_square = inflate(tiny_square, inflation_value);
209+
210+
// verify the inflated shape
211+
EXPECT_GT(inflated_square.elements.size(), 0);
212+
}
213+
214+
// test 6: test the case that the inflation value is too large
215+
TEST(IrregularShapeInflate, LargeInflation)
216+
{
217+
// build a square
218+
Shape square = build_square(0, 0, 10);
219+
220+
// use a large inflation value
221+
LengthDbl inflation_value = 100.0;
222+
Shape inflated_square = inflate(square, inflation_value);
223+
224+
// verify the inflated shape
225+
EXPECT_GT(inflated_square.elements.size(), 0);
226+
227+
// after inflation, the square's edges should be moved outwards by inflation_value units
228+
// expected new boundary coordinates
229+
LengthDbl expected_min_x = -inflation_value;
230+
LengthDbl expected_min_y = -inflation_value;
231+
LengthDbl expected_max_x = 10 + inflation_value;
232+
LengthDbl expected_max_y = 10 + inflation_value;
233+
234+
// check the boundary of the inflated shape
235+
LengthDbl actual_min_x = std::numeric_limits<LengthDbl>::max();
236+
LengthDbl actual_min_y = std::numeric_limits<LengthDbl>::max();
237+
LengthDbl actual_max_x = std::numeric_limits<LengthDbl>::lowest();
238+
LengthDbl actual_max_y = std::numeric_limits<LengthDbl>::lowest();
239+
240+
for (const ShapeElement& element : inflated_square.elements) {
241+
actual_min_x = std::min({actual_min_x, element.start.x, element.end.x});
242+
actual_min_y = std::min({actual_min_y, element.start.y, element.end.y});
243+
actual_max_x = std::max({actual_max_x, element.start.x, element.end.x});
244+
actual_max_y = std::max({actual_max_y, element.start.y, element.end.y});
245+
}
246+
247+
// allow some tolerance
248+
const LengthDbl tolerance = 0.1;
249+
250+
EXPECT_NEAR(actual_min_x, expected_min_x, tolerance);
251+
EXPECT_NEAR(actual_min_y, expected_min_y, tolerance);
252+
EXPECT_NEAR(actual_max_x, expected_max_x, tolerance);
253+
EXPECT_NEAR(actual_max_y, expected_max_y, tolerance);
254+
}

0 commit comments

Comments
 (0)