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