KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_shape_poly_set_triangle_index.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
27
28#include <boost/test/unit_test.hpp>
29
30#include <limits>
31#include <vector>
32
36#include <geometry/shape_rect.h>
37
38namespace
39{
40
42SHAPE_LINE_CHAIN MakeNgon( const VECTOR2I& aCentre, int aRadius, int aVertices )
43{
45
46 for( int ii = 0; ii < aVertices; ++ii )
47 {
48 double angle = 2.0 * M_PI * ii / aVertices;
49 chain.Append( aCentre.x + KiROUND( aRadius * cos( angle ) ), aCentre.y + KiROUND( aRadius * sin( angle ) ) );
50 }
51
52 chain.SetClosed( true );
53 return chain;
54}
55
56
58SHAPE_POLY_SET MakeBlob()
59{
60 SHAPE_POLY_SET poly;
61 poly.AddOutline( MakeNgon( VECTOR2I( 0, 0 ), 1000000, 180 ) );
62 poly.AddHole( MakeNgon( VECTOR2I( -400000, 0 ), 150000, 60 ) );
63 poly.AddHole( MakeNgon( VECTOR2I( 400000, 250000 ), 120000, 60 ) );
64 poly.CacheTriangulation();
65 return poly;
66}
67
68
74struct PROBE_RESULT
75{
76 bool collides;
77 int actual;
78
79 bool operator==( const PROBE_RESULT& aOther ) const
80 {
81 return collides == aOther.collides && actual == aOther.actual;
82 }
83};
84
85
86std::ostream& operator<<( std::ostream& aStream, const PROBE_RESULT& aResult )
87{
88 return aStream << "{ collides=" << aResult.collides << " actual=" << aResult.actual << " }";
89}
90
91
92PROBE_RESULT Probe( const SHAPE_POLY_SET& aPoly, const SHAPE& aShape, int aClearance )
93{
94 PROBE_RESULT r;
95 r.actual = -1;
96 r.collides = aPoly.Collide( &aShape, aClearance, &r.actual, nullptr );
97 return r;
98}
99
100
105PROBE_RESULT ReferenceScan( const SHAPE_POLY_SET& aPoly, const SHAPE& aShape, int aClearance )
106{
107 PROBE_RESULT r;
108 r.collides = false;
109 r.actual = -1;
110
111 int best = std::numeric_limits<int>::max();
112
113 for( unsigned ii = 0; ii < aPoly.TriangulatedPolyCount(); ++ii )
114 {
116
117 for( const SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
118 {
119 int triActual;
120
121 if( aShape.Collide( &tri, aClearance, &triActual, nullptr ) )
122 best = std::min( best, triActual );
123 }
124 }
125
126 if( best < std::numeric_limits<int>::max() )
127 {
128 r.collides = true;
129 r.actual = std::max( 0, best );
130 }
131
132 return r;
133}
134
135
142bool ProbeBool( const SHAPE_POLY_SET& aPoly, const SHAPE& aShape, int aClearance )
143{
144 return aPoly.Collide( &aShape, aClearance );
145}
146
147
149bool ReferenceScanBool( const SHAPE_POLY_SET& aPoly, const SHAPE& aShape, int aClearance )
150{
151 for( unsigned ii = 0; ii < aPoly.TriangulatedPolyCount(); ++ii )
152 {
154
155 for( const SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
156 {
157 if( aShape.Collide( &tri, aClearance ) )
158 return true;
159 }
160 }
161
162 return false;
163}
164
165
173void WarmAndRequireIndexed( SHAPE_POLY_SET& aPoly )
174{
175 SHAPE_RECT warmup( VECTOR2I( 0, 0 ), 1000, 1000 );
176 aPoly.Collide( &warmup, 0 );
177
179 BOOST_REQUIRE( aPoly.TriangulationIndex() != nullptr );
180}
181
182
184const std::vector<VECTOR2I> g_probes = {
185 VECTOR2I( 0, 0 ), // deep inside
186 VECTOR2I( 999000, 0 ), // just inside the outline
187 VECTOR2I( 1001000, 0 ), // just outside the outline
188 VECTOR2I( -400000, 0 ), // centre of hole 1
189 VECTOR2I( -250000, 0 ), // just outside hole 1
190 VECTOR2I( 400000, 250000 ), // centre of hole 2
191 VECTOR2I( 3000000, 3000000 ) // far away
192};
193
194} // namespace
195
196
197BOOST_AUTO_TEST_SUITE( SPSTriangleIndex )
198
199
200
205BOOST_AUTO_TEST_CASE( FirstColdCollideBuildsTheIndex )
206{
207 SHAPE_POLY_SET poly;
208 poly.AddOutline( MakeNgon( VECTOR2I( 0, 0 ), 1000000, 180 ) );
209 poly.AddHole( MakeNgon( VECTOR2I( -400000, 0 ), 150000, 60 ) );
210 poly.AddHole( MakeNgon( VECTOR2I( 400000, 250000 ), 120000, 60 ) );
211
212 SHAPE_RECT probe( VECTOR2I( 0, 0 ), 1000, 1000 );
213 poly.Collide( &probe, 0 );
214
216 BOOST_REQUIRE( poly.TriangulationIndex() != nullptr );
217}
218
219
224BOOST_AUTO_TEST_CASE( TriPointersSurviveMove )
225{
226 SHAPE_POLY_SET poly = MakeBlob();
227
228 WarmAndRequireIndexed( poly );
229
230 std::vector<const SHAPE*> before;
231 poly.GetIndexableSubshapes( before );
232 BOOST_REQUIRE( before.size() > 64 );
233
234 std::vector<VECTOR2I> firstVertexBefore;
235
236 for( const SHAPE* s : before )
237 firstVertexBefore.push_back( static_cast<const SHAPE_LINE_CHAIN_BASE*>( s )->GetPoint( 0 ) );
238
239 const VECTOR2I delta( 123456, -654321 );
240 poly.Move( delta );
241
242 std::vector<const SHAPE*> after;
243 poly.GetIndexableSubshapes( after );
244
245 BOOST_REQUIRE_EQUAL( before.size(), after.size() );
246
247 for( size_t ii = 0; ii < before.size(); ++ii )
248 {
249 // Same objects, not clones: a DRC_RTREE built before the Move must stay valid.
250 BOOST_CHECK_EQUAL( (const void*) before[ii], (const void*) after[ii] );
251
252 VECTOR2I moved = static_cast<const SHAPE_LINE_CHAIN_BASE*>( after[ii] )->GetPoint( 0 );
253 BOOST_CHECK_EQUAL( moved, firstVertexBefore[ii] + delta );
254 }
255}
256
257
263BOOST_AUTO_TEST_CASE( CompoundNearMissNotPruned )
264{
265 SHAPE_POLY_SET poly = MakeBlob();
266
267 // Two segments forming a compound, sitting a known distance outside the outline.
268 const int gap = 20000;
269 const VECTOR2I a( 1000000 + gap, -100000 );
270 const VECTOR2I b( 1000000 + gap, 100000 );
271
272 SHAPE_COMPOUND compound;
273 compound.AddShape( new SHAPE_SEGMENT( a, b, 0 ) );
274 compound.AddShape( new SHAPE_SEGMENT( b, b + VECTOR2I( 50000, 0 ), 0 ) );
275
276 // Comfortably clear.
277 BOOST_CHECK_EQUAL( Probe( poly, compound, gap / 2 ), ReferenceScan( poly, compound, gap / 2 ) );
278
279 // Clearance large enough that the near-miss triangles matter: a pruned candidate set
280 // would report no collision here while the full scan reports one.
281 BOOST_CHECK_EQUAL( Probe( poly, compound, gap * 4 ), ReferenceScan( poly, compound, gap * 4 ) );
282
283 BOOST_CHECK( Probe( poly, compound, gap * 4 ).collides );
284}
285
286
293BOOST_AUTO_TEST_CASE( MinDistanceMatchesFullScan )
294{
295 SHAPE_POLY_SET poly = MakeBlob();
296
297 const std::vector<std::pair<VECTOR2I, int>> cases = {
298 { VECTOR2I( 1000000, 0 ), 0 }, // on the outline, between two triangles
299 { VECTOR2I( 1010000, 0 ), 10000 }, // exactly at clearance distance
300 { VECTOR2I( 1010000, 0 ), 9999 }, // one IU inside
301 { VECTOR2I( 1010000, 0 ), 10001 }, // one IU outside
302 { VECTOR2I( -400000, 0 ), 100000 }, // inside a hole, reaching its rim
303 { VECTOR2I( 0, 999000 ), 5000 } // near the outline on another axis
304 };
305
306 for( const auto& [pt, clearance] : cases )
307 {
308 SHAPE_RECT probe( pt, 1, 1 );
309 BOOST_CHECK_EQUAL( Probe( poly, probe, clearance ), ReferenceScan( poly, probe, clearance ) );
310 }
311}
312
313
319BOOST_AUTO_TEST_CASE( SubThresholdPolygonMatchesFullScan )
320{
321 SHAPE_POLY_SET poly;
322 poly.AddOutline( MakeNgon( VECTOR2I( 0, 0 ), 1000000, 8 ) ); // 8 vertices -> ~6 triangles
323 poly.CacheTriangulation();
324
325 size_t triangles = 0;
326
327 for( unsigned ii = 0; ii < poly.TriangulatedPolyCount(); ++ii )
328 triangles += poly.TriangulatedPolygon( ii )->GetTriangleCount();
329
330 // If this ever exceeds the threshold (RTREE_THRESHOLD, 32) the case stops testing what it
331 // claims to.
332 BOOST_REQUIRE( triangles < 32 );
333
334 const std::vector<VECTOR2I> probes = { VECTOR2I( 0, 0 ), VECTOR2I( 900000, 0 ), VECTOR2I( 1100000, 0 ),
335 VECTOR2I( 0, 900000 ) };
336
337 for( const VECTOR2I& pt : probes )
338 {
339 SHAPE_RECT probe( pt, 1000, 1000 );
340 BOOST_CHECK_EQUAL( Probe( poly, probe, 0 ), ReferenceScan( poly, probe, 0 ) );
341 BOOST_CHECK_EQUAL( ProbeBool( poly, probe, 0 ), ReferenceScanBool( poly, probe, 0 ) );
342 }
343}
344
345
357BOOST_AUTO_TEST_CASE( CheckIndexDropped )
358{
359 // Move: the index is dropped, and the rebuilt one must describe the new positions.
360 SHAPE_POLY_SET poly = MakeBlob();
361 WarmAndRequireIndexed( poly );
362
363 const VECTOR2I delta( 5000000, 0 );
364 poly.Move( delta );
365 BOOST_CHECK( poly.TriangulationIndex() == nullptr );
366
367 for( const VECTOR2I& pt : g_probes )
368 {
369 SHAPE_RECT shifted( pt + delta, 1000, 1000 );
370 BOOST_CHECK_EQUAL( Probe( poly, shifted, 0 ), ReferenceScan( poly, shifted, 0 ) );
371 BOOST_CHECK_EQUAL( ProbeBool( poly, shifted, 0 ), ReferenceScanBool( poly, shifted, 0 ) );
372
373 // The old location must now be empty.
374 SHAPE_RECT stale( pt, 1000, 1000 );
375 BOOST_CHECK_EQUAL( Probe( poly, stale, 0 ), ReferenceScan( poly, stale, 0 ) );
376 }
377
378 // RemoveAllContours discards the triangles outright.
379 poly = MakeBlob();
380 WarmAndRequireIndexed( poly );
381 poly.RemoveAllContours();
382 BOOST_CHECK( poly.TriangulationIndex() == nullptr );
383
384 // Re-triangulating replaces every polygon, so the index must not survive it either.
385 poly = MakeBlob();
386 WarmAndRequireIndexed( poly );
387 poly.AddHole( MakeNgon( VECTOR2I( 0, -500000 ), 100000, 60 ) );
388 poly.CacheTriangulation();
389 BOOST_CHECK( poly.TriangulationIndex() == nullptr );
390
391 for( const VECTOR2I& pt : g_probes )
392 {
393 SHAPE_RECT probe( pt, 1000, 1000 );
394 BOOST_CHECK_EQUAL( Probe( poly, probe, 0 ), ReferenceScan( poly, probe, 0 ) );
395 BOOST_CHECK_EQUAL( ProbeBool( poly, probe, 0 ), ReferenceScanBool( poly, probe, 0 ) );
396 }
397
398 // The new hole must read as empty.
399 SHAPE_RECT inNewHole( VECTOR2I( 0, -500000 ), 1000, 1000 );
400 BOOST_CHECK_EQUAL( Probe( poly, inNewHole, 0 ), ReferenceScan( poly, inNewHole, 0 ) );
401
402 // Copy assignment, against a blob far from the first.
403 SHAPE_POLY_SET target = MakeBlob();
404 WarmAndRequireIndexed( target );
405
406 SHAPE_POLY_SET other;
407 other.AddOutline( MakeNgon( VECTOR2I( 8000000, 8000000 ), 900000, 180 ) );
408 other.CacheTriangulation();
409
410 target = other;
411
412 BOOST_REQUIRE( target.TriangulatedPolyCount() > 0 );
413 BOOST_CHECK( target.TriangulationIndex() == nullptr );
414
415 const std::vector<VECTOR2I> probes = {
416 VECTOR2I( 8000000, 8000000 ), // inside the new geometry
417 VECTOR2I( 8890000, 8000000 ), // just inside its edge
418 VECTOR2I( 8910000, 8000000 ), // just outside its edge
419 VECTOR2I( 0, 0 ) // where the OLD geometry used to be
420 };
421
422 for( const VECTOR2I& pt : probes )
423 {
424 SHAPE_RECT probe( pt, 1000, 1000 );
425 BOOST_CHECK_EQUAL( Probe( target, probe, 0 ), ReferenceScan( target, probe, 0 ) );
426 BOOST_CHECK_EQUAL( ProbeBool( target, probe, 0 ), ReferenceScanBool( target, probe, 0 ) );
427 }
428}
429
430
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
void AddShape(SHAPE *aShape)
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const std::deque< TRI > & Triangles() const
Represent a set of closed polygons.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
virtual void GetIndexableSubshapes(std::vector< const SHAPE * > &aSubshapes) const override
const TRIANGULATION_INDEX * TriangulationIndex() const
The spatial index over the cached triangulation, or nullptr when none was built.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={}, bool aBuildIndexes=false)
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
void Move(const VECTOR2I &aVector) override
An abstract shape on 2D plane.
Definition shape.h:124
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:181
std::ostream & operator<<(std::ostream &aStream, const EDA_TEXT &aText)
FILE_INFO Probe(const wxString &aPath)
Identify a file; projects are parsed fully, while designs and libraries are only inspected....
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
bool moved
const SHAPE_LINE_CHAIN chain
int clearance
BOOST_AUTO_TEST_CASE(FirstColdCollideBuildsTheIndex)
A cold Collide() must come back indexed.
BOOST_CHECK_EQUAL(result, "25.4")
int delta
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708