KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_poly_ystripes_index.cpp
Go to the documentation of this file.
1/*
2 * This program is part of KiCad, a free EDA CAD application.
3 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
20
24
25#include <cstdlib>
26#include <random>
27#include <vector>
28
29namespace
30{
31
32SHAPE_POLY_SET makeSquare( int aSize )
33{
34 SHAPE_POLY_SET poly;
35 SHAPE_LINE_CHAIN outline;
36
37 outline.Append( 0, 0 );
38 outline.Append( aSize, 0 );
39 outline.Append( aSize, aSize );
40 outline.Append( 0, aSize );
41 outline.SetClosed( true );
42 poly.AddOutline( outline );
43
44 return poly;
45}
46
47
48std::vector<VECTOR2I> generateRandomPoints( const BOX2I& aBBox, int aCount, uint32_t aSeed )
49{
50 std::mt19937 gen( aSeed );
51 std::vector<VECTOR2I> points;
52 points.reserve( aCount );
53
54 std::uniform_int_distribution<int> distX( aBBox.GetLeft(), aBBox.GetRight() );
55 std::uniform_int_distribution<int> distY( aBBox.GetTop(), aBBox.GetBottom() );
56
57 for( int i = 0; i < aCount; i++ )
58 points.emplace_back( distX( gen ), distY( gen ) );
59
60 return points;
61}
62
63} // anonymous namespace
64
65
66BOOST_AUTO_TEST_SUITE( PolyYStripesIndex )
67
68
69BOOST_AUTO_TEST_CASE( CorrectnessAllStrategiesAgree )
70{
71 // An axis-aligned square is the one subject whose containment is decidable without consulting
72 // any implementation under test, so the sweep can assert truth rather than consensus.
73 constexpr int SIZE = 1000000;
74 constexpr int MARGIN = 1000;
75
76 SHAPE_POLY_SET square = makeSquare( SIZE );
77
78 POLY_YSTRIPES_INDEX ystripes;
79 ystripes.Build( square );
80
81 BOX2I sampleArea( VECTOR2I( -SIZE / 2, -SIZE / 2 ), VECTOR2I( 2 * SIZE, 2 * SIZE ) );
82
83 int tested = 0;
84 int inside = 0;
85
86 auto nearEdge = []( int v, int lo, int hi )
87 {
88 return std::abs( v - lo ) <= MARGIN || std::abs( v - hi ) <= MARGIN;
89 };
90
91 for( const VECTOR2I& pt : generateRandomPoints( sampleArea, 10000, 42 ) )
92 {
93 // Points hugging an edge are genuinely ambiguous; they are not what this case is about
94 if( nearEdge( pt.x, 0, SIZE ) || nearEdge( pt.y, 0, SIZE ) )
95 continue;
96
97 const bool expected = pt.x > 0 && pt.x < SIZE && pt.y > 0 && pt.y < SIZE;
98
99 tested++;
100 inside += expected ? 1 : 0;
101
102 BOOST_TEST_CONTEXT( "point (" << pt.x << ", " << pt.y << ")" )
103 {
104 BOOST_CHECK_EQUAL( square.Contains( pt ), expected );
105 BOOST_CHECK_EQUAL( ystripes.Contains( pt ), expected );
106 }
107 }
108
109 // A sweep that sampled only one side of the boundary would prove nothing
110 BOOST_CHECK_GT( tested, 5000 );
111 BOOST_CHECK_GT( inside, 0 );
112 BOOST_CHECK_LT( inside, tested );
113}
114
115
116BOOST_AUTO_TEST_CASE( CorrectnessEdgeCases )
117{
118 SHAPE_POLY_SET square = makeSquare( 1000000 );
119
120 POLY_YSTRIPES_INDEX ystripes;
121 ystripes.Build( square );
122
123 struct TEST_POINT
124 {
125 VECTOR2I pt;
126 bool expectedInside;
127 std::string desc;
128 };
129
130 const std::vector<TEST_POINT> cases = {
131 { { 500000, 500000 }, true, "center" },
132 { { 100000, 100000 }, true, "inside near corner" },
133 { { 900000, 900000 }, true, "inside far corner" },
134 { { -100000, 500000 }, false, "outside left" },
135 { { 500000, -100000 }, false, "outside above" },
136 { { 1500000, 500000 }, false, "outside right" },
137 { { 500000, 1500000 }, false, "outside below" },
138 { { 500000, -1000000 }, false, "outside Y range above" },
139 { { 500000, 3000000 }, false, "outside Y range below" },
140 };
141
142 for( const TEST_POINT& tc : cases )
143 {
144 BOOST_TEST_CONTEXT( tc.desc )
145 {
146 BOOST_CHECK_EQUAL( square.Contains( tc.pt ), tc.expectedInside );
147 BOOST_CHECK_EQUAL( ystripes.Contains( tc.pt ), tc.expectedInside );
148 }
149 }
150}
151
152
153BOOST_AUTO_TEST_CASE( CorrectnessPolygonWithHoles )
154{
155 SHAPE_POLY_SET poly;
156 SHAPE_LINE_CHAIN outline;
157
158 outline.Append( 0, 0 );
159 outline.Append( 1000, 0 );
160 outline.Append( 1000, 1000 );
161 outline.Append( 0, 1000 );
162 outline.SetClosed( true );
163 poly.AddOutline( outline );
164
165 SHAPE_LINE_CHAIN hole;
166 hole.Append( 400, 400 );
167 hole.Append( 600, 400 );
168 hole.Append( 600, 600 );
169 hole.Append( 400, 600 );
170 hole.SetClosed( true );
171 poly.AddHole( hole );
172
173 POLY_YSTRIPES_INDEX ystripes;
174 ystripes.Build( poly );
175
176 const std::vector<std::pair<VECTOR2I, bool>> cases = {
177 { { 100, 100 }, true },
178 { { 500, 500 }, false },
179 { { 1500, 500 }, false },
180 { { 800, 200 }, true },
181 };
182
183 for( const auto& [pt, expected] : cases )
184 {
185 BOOST_TEST_CONTEXT( "point (" << pt.x << ", " << pt.y << ")" )
186 {
187 BOOST_CHECK_EQUAL( ystripes.Contains( pt ), expected );
188 BOOST_CHECK_EQUAL( poly.Contains( pt ), expected );
189 }
190 }
191
192 // The proximity fallback must skip hole edges. Probe a vertical one; Build() drops
193 // horizontal edges, so a point near the hole's top would never reach that branch
194 BOOST_CHECK( !ystripes.Contains( VECTOR2I( 410, 500 ), 20 ) );
195
196 // Same distance from a real outline edge, to prove the probe above is not passing
197 // simply because the fallback found nothing
198 BOOST_CHECK( ystripes.Contains( VECTOR2I( -10, 500 ), 20 ) );
199
200 BOX2I bbox = poly.BBox();
201 int mismatches = 0;
202
203 for( const VECTOR2I& pt : generateRandomPoints( bbox, 10000, 42 ) )
204 {
205 if( ystripes.Contains( pt ) != poly.Contains( pt ) )
206 mismatches++;
207 }
208
209 BOOST_CHECK_EQUAL( mismatches, 0 );
210}
211
212
double square(double x)
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
Y-stripe spatial index for efficient point-in-polygon containment testing.
bool Contains(const VECTOR2I &aPt, int aAccuracy=0) const
Test whether a point is inside the indexed polygon set.
void Build(const SHAPE_POLY_SET &aPolySet)
Build the spatial index from a SHAPE_POLY_SET's outlines and holes.
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_AUTO_TEST_CASE(CorrectnessAllStrategiesAgree)
BOOST_TEST_CONTEXT("Test Clearance")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683