KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_poly_triangulation.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 3
9 * of the License, or (at your option) any later version.
10 */
11
16#include <trigo.h>
17#include <thread>
18#include <chrono>
19#include <future>
20#include <filesystem>
21#include <fstream>
22
24#include <qa_utils/numeric.h>
26
27#include "geom_test_utils.h"
28
29BOOST_AUTO_TEST_SUITE( PolygonTriangulation )
30
32{
33 static std::vector<double> PartitionAreaFractions( POLYGON_TRIANGULATION& aTriangulator,
34 const SHAPE_LINE_CHAIN& aPoly,
35 size_t aTargetLeaves )
36 {
37 return aTriangulator.PartitionAreaFractionsForTesting( aPoly, aTargetLeaves );
38 }
39};
40
41namespace fs = std::filesystem;
42
43// Helper class to properly manage TRIANGULATED_POLYGON lifecycle
45{
46public:
47 TRIANGULATION_TEST_FIXTURE() : m_result( std::make_unique<SHAPE_POLY_SET::TRIANGULATED_POLYGON>(0) )
48 {
49 }
50
52
53 std::unique_ptr<POLYGON_TRIANGULATION> CreateTriangulator()
54 {
55 return std::make_unique<POLYGON_TRIANGULATION>( *m_result );
56 }
57
58private:
59 std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON> m_result;
60};
61
62// Helper function to create a simple square
63SHAPE_LINE_CHAIN createSquare( int size = 100, VECTOR2I offset = VECTOR2I(0, 0) )
64{
66 chain.Append( offset.x, offset.y );
67 chain.Append( offset.x + size, offset.y );
68 chain.Append( offset.x + size, offset.y + size );
69 chain.Append( offset.x, offset.y + size );
70 chain.SetClosed( true );
71 return chain;
72}
73
74// Helper function to create a triangle
75SHAPE_LINE_CHAIN createTriangle( int size = 100, VECTOR2I offset = VECTOR2I(0, 0) )
76{
78 chain.Append( offset.x, offset.y );
79 chain.Append( offset.x + size, offset.y );
80 chain.Append( offset.x + size/2, offset.y + size );
81 chain.SetClosed( true );
82 return chain;
83}
84
85// Helper function to create a complex concave polygon
87{
89 chain.Append( 0, 0 );
90 chain.Append( size, 0 );
91 chain.Append( size, size/2 );
92 chain.Append( size/2, size/2 ); // Create concave section
93 chain.Append( size/2, size );
94 chain.Append( 0, size );
95 chain.SetClosed( true );
96 return chain;
97}
98
99SHAPE_LINE_CHAIN createSerpentinePolygon( int step = 20000, int teeth = 16 )
100{
102 chain.Append( 0, 0 );
103
104 int x = 0;
105
106 for( int ii = 0; ii < teeth; ++ii )
107 {
108 x += step;
109 chain.Append( x, 0 );
110 chain.Append( x, step * 3 );
111 x += step;
112 chain.Append( x, step * 3 );
113 chain.Append( x, step * 4 );
114 }
115
116 chain.Append( 0, step * 4 );
117 chain.SetClosed( true );
118 return chain;
119}
120
121// Helper function to validate triangulation result with comprehensive checks
123 const SHAPE_LINE_CHAIN& original, bool strict = true )
124{
125 // Basic validation
126 if( result.GetVertexCount() == 0 )
127 return false;
128
129 size_t triangleCount = result.GetTriangleCount();
130 if( triangleCount == 0 )
131 return false;
132
133 // Validate triangle topology
134 for( size_t i = 0; i < triangleCount; i++ )
135 {
136 const auto& triangle = result.Triangles()[i];
137
138 // Check valid vertex indices
139 if( triangle.a >= (int)result.GetVertexCount() ||
140 triangle.b >= (int)result.GetVertexCount() ||
141 triangle.c >= (int)result.GetVertexCount() )
142 {
143 return false;
144 }
145
146 // Triangle vertices should not be the same
147 if( triangle.a == triangle.b || triangle.b == triangle.c || triangle.a == triangle.c )
148 return false;
149
150 // Check triangle area is positive (counter-clockwise orientation)
151 if( strict && triangle.Area() <= 0 )
152 return false;
153 }
154
155 // Validate that original vertices are preserved
156 if( strict && result.GetVertexCount() >= original.PointCount() )
157 {
158 const auto& vertices = result.Vertices();
159 for( int i = 0; i < original.PointCount(); i++ )
160 {
161 bool found = false;
162 for( size_t j = 0; j < vertices.size(); j++ )
163 {
164 if( vertices[j] == original.CPoint( i ) )
165 {
166 found = true;
167 break;
168 }
169 }
170 if( !found )
171 return false;
172 }
173 }
174
175 return true;
176}
177
179{
180 int count = 0;
181
182 for( const auto& tri : aResult.Triangles() )
183 {
184 VECTOR2I pa = tri.GetPoint( 0 );
185 VECTOR2I pb = tri.GetPoint( 1 );
186 VECTOR2I pc = tri.GetPoint( 2 );
187
188 double ab = pa.Distance( pb );
189 double bc = pb.Distance( pc );
190 double ca = pc.Distance( pa );
191
192 double longest = std::max( { ab, bc, ca } );
193 double shortest = std::min( { ab, bc, ca } );
194
195 if( shortest > 0.0 && longest / shortest > 10.0 )
196 ++count;
197 }
198
199 return count;
200}
201
202bool parsePolyFileForTest( const fs::path& aPath, std::vector<SHAPE_POLY_SET>& aZones )
203{
204 std::ifstream file( aPath );
205
206 if( !file.is_open() )
207 return false;
208
209 std::string content( ( std::istreambuf_iterator<char>( file ) ),
210 std::istreambuf_iterator<char>() );
211
212 size_t zonePos = 0;
213
214 while( ( zonePos = content.find( "(zone (layer \"", zonePos ) ) != std::string::npos )
215 {
216 size_t polysetStart = content.find( "polyset ", zonePos );
217
218 if( polysetStart != std::string::npos )
219 {
220 SHAPE_POLY_SET polySet;
221 std::string remainder = content.substr( polysetStart );
222 std::stringstream ss( remainder );
223
224 if( polySet.Parse( ss ) )
225 aZones.push_back( std::move( polySet ) );
226 }
227
228 size_t layerEnd = content.find( "\")", zonePos + 14 );
229
230 if( layerEnd == std::string::npos )
231 break;
232
233 zonePos = layerEnd + 1;
234 }
235
236 return !aZones.empty();
237}
238
239double computeBoardSpikeyRatio( const fs::path& aPath )
240{
241 std::vector<SHAPE_POLY_SET> zones;
242
243 if( !parsePolyFileForTest( aPath, zones ) )
244 return 1.0;
245
246 int totalTriangles = 0;
247 int totalSpikey = 0;
248
249 for( SHAPE_POLY_SET& polySet : zones )
250 {
251 polySet.CacheTriangulation();
252
253 for( unsigned int i = 0; i < polySet.TriangulatedPolyCount(); ++i )
254 {
255 const auto* triPoly = polySet.TriangulatedPolygon( static_cast<int>( i ) );
256 totalTriangles += triPoly->GetTriangleCount();
257 totalSpikey += countSpikeyTriangles( *triPoly );
258 }
259 }
260
261 return totalTriangles > 0 ? static_cast<double>( totalSpikey ) / totalTriangles : 0.0;
262}
263
264// Core functionality tests
265BOOST_AUTO_TEST_CASE( BasicTriangleTriangulation )
266{
268 auto triangulator = fixture.CreateTriangulator();
269
270 SHAPE_LINE_CHAIN triangle = createTriangle();
271
272 bool success = triangulator->TesselatePolygon( triangle, nullptr );
273
274 BOOST_TEST( success );
275 BOOST_TEST( fixture.GetResult().GetVertexCount() == 3 );
276 BOOST_TEST( fixture.GetResult().GetTriangleCount() == 1 );
277 BOOST_TEST( validateTriangulation( fixture.GetResult(), triangle ) );
278}
279
280BOOST_AUTO_TEST_CASE( BasicSquareTriangulation )
281{
283 auto triangulator = fixture.CreateTriangulator();
284
286
287 bool success = triangulator->TesselatePolygon( square, nullptr );
288
289 BOOST_TEST( success );
290 BOOST_TEST( fixture.GetResult().GetVertexCount() == 4 );
291 BOOST_TEST( fixture.GetResult().GetTriangleCount() == 2 );
293}
294
295BOOST_AUTO_TEST_CASE( SplitFirstFracturePartitionProducesMultipleLeaves )
296{
298 auto triangulator = fixture.CreateTriangulator();
300 std::vector<double> fractions =
302 serpentine, 4 );
303
304 BOOST_TEST( fractions.size() == 4 );
305
306 for( double fraction : fractions )
307 BOOST_TEST( fraction > 0.15 );
308}
309
310BOOST_AUTO_TEST_CASE( EarLookaheadImprovesBadTriangulationCase )
311{
312 fs::path polyPath = fs::path( __FILE__ ).parent_path().parent_path().parent_path().parent_path()
313 .parent_path() / "data/triangulation/bad_triangulation_case.kicad_polys";
314
315 BOOST_TEST( fs::exists( polyPath ) );
316 BOOST_TEST( computeBoardSpikeyRatio( polyPath ) < 0.47 );
317}
318
319BOOST_AUTO_TEST_CASE( ConcavePolygonTriangulation )
320{
322 auto triangulator = fixture.CreateTriangulator();
323
324 SHAPE_LINE_CHAIN concave = createConcavePolygon(100000);
325
326 bool success = triangulator->TesselatePolygon( concave, nullptr );
327
328 BOOST_TEST( success );
329
330 const auto& result = fixture.GetResult();
331 bool isValid = validateTriangulation( result, concave );
332 size_t triangleCount = result.GetTriangleCount();
333
334 // Print diagnostic information if validation fails or triangle count is unexpected
335 if( !success || !isValid || triangleCount < 4 )
336 {
337 std::cout << "\n=== ConcavePolygonTriangulation Diagnostic Output ===" << std::endl;
338 std::cout << "Success: " << (success ? "true" : "false") << std::endl;
339 std::cout << "Validation: " << (isValid ? "true" : "false") << std::endl;
340 std::cout << "Triangle count: " << triangleCount << " (expected >= 4)" << std::endl;
341 std::cout << "Vertex count: " << result.GetVertexCount() << std::endl;
342
343 // Print input polygon vertices
344 std::cout << "\nInput polygon vertices (" << concave.PointCount() << " points):" << std::endl;
345 for( int i = 0; i < concave.PointCount(); i++ )
346 {
347 VECTOR2I pt = concave.CPoint( i );
348 std::cout << " [" << i << "]: (" << pt.x << ", " << pt.y << ")" << std::endl;
349 }
350
351 // Print result vertices
352 std::cout << "\nResult vertices (" << result.GetVertexCount() << " points):" << std::endl;
353 const auto& vertices = result.Vertices();
354 for( size_t i = 0; i < vertices.size(); i++ )
355 {
356 std::cout << " [" << i << "]: (" << vertices[i].x << ", " << vertices[i].y << ")" << std::endl;
357 }
358
359 // Print triangles
360 std::cout << "\nTriangles found (" << triangleCount << " triangles):" << std::endl;
361 const auto& triangles = result.Triangles();
362 for( size_t i = 0; i < triangles.size(); i++ )
363 {
364 const auto& tri = triangles[i];
365 VECTOR2I va = vertices[tri.a];
366 VECTOR2I vb = vertices[tri.b];
367 VECTOR2I vc = vertices[tri.c];
368 double area = tri.Area();
369
370 std::cout << " Triangle[" << i << "]: indices(" << tri.a << "," << tri.b << "," << tri.c << ")" << std::endl;
371 std::cout << " A: (" << va.x << ", " << va.y << ")" << std::endl;
372 std::cout << " B: (" << vb.x << ", " << vb.y << ")" << std::endl;
373 std::cout << " C: (" << vc.x << ", " << vc.y << ")" << std::endl;
374 std::cout << " Area: " << area << std::endl;
375
376 // Check for degenerate triangles
377 if( area <= 0 )
378 std::cout << " *** DEGENERATE TRIANGLE (area <= 0) ***" << std::endl;
379 if( tri.a == tri.b || tri.b == tri.c || tri.a == tri.c )
380 std::cout << " *** INVALID TRIANGLE (duplicate vertex indices) ***" << std::endl;
381 }
382
383 // Additional diagnostic information
384 if( triangleCount > 0 )
385 {
386 double totalArea = 0.0;
387 for( const auto& tri : triangles )
388 totalArea += tri.Area();
389 std::cout << "\nTotal triangulated area: " << totalArea << std::endl;
390
391 // Calculate expected area of concave polygon for comparison
392 double originalArea = std::abs( concave.Area() );
393 std::cout << "Original polygon area: " << originalArea << std::endl;
394 std::cout << "Area difference: " << std::abs( totalArea - originalArea ) << std::endl;
395 }
396
397 std::cout << "================================================\n" << std::endl;
398 }
399
400 BOOST_TEST( success );
401 BOOST_TEST( isValid );
402 // L-shaped concave polygons should have 4 triangles
403 BOOST_TEST( triangleCount == 4 );
404}
405
406
407BOOST_AUTO_TEST_CASE( HintDataOptimization )
408{
409 // First triangulation without hint
411 auto triangulator1 = fixture1.CreateTriangulator();
413
414 bool success1 = triangulator1->TesselatePolygon( square, nullptr );
415 BOOST_TEST( success1 );
416
417 // Second triangulation with hint data from first
419 auto triangulator2 = fixture2.CreateTriangulator();
420
421 bool success2 = triangulator2->TesselatePolygon( square, &fixture1.GetResult() );
422 BOOST_TEST( success2 );
423
424 // Results should be identical when hint is applicable
425 BOOST_TEST( fixture1.GetResult().GetVertexCount() == fixture2.GetResult().GetVertexCount() );
426 BOOST_TEST( fixture1.GetResult().GetTriangleCount() == fixture2.GetResult().GetTriangleCount() );
427}
428
429BOOST_AUTO_TEST_CASE( HintDataOptimizationWithSimplifiedInput )
430{
431 SHAPE_LINE_CHAIN noisySquare;
432 noisySquare.Append( 0, 0 );
433 noisySquare.Append( 100, 0 );
434 noisySquare.Append( 100, 10 );
435 noisySquare.Append( 100, 100 );
436 noisySquare.Append( 0, 100 );
437 noisySquare.SetClosed( true );
438
439 TRIANGULATION_TEST_FIXTURE hintFixture;
440 auto hintTriangulator = hintFixture.CreateTriangulator();
441
442 bool success1 = hintTriangulator->TesselatePolygon( noisySquare, nullptr );
443 BOOST_TEST( success1 );
444
445 auto poisonedTriangles = hintFixture.GetResult().Triangles();
446 BOOST_REQUIRE_GE( poisonedTriangles.size(), 2U );
447 std::reverse( poisonedTriangles.begin(), poisonedTriangles.end() );
448 hintFixture.GetResult().SetTriangles( poisonedTriangles );
449
451 auto triangulator = fixture.CreateTriangulator();
452
453 bool success2 = triangulator->TesselatePolygon( noisySquare, &hintFixture.GetResult() );
454 BOOST_TEST( success2 );
455 BOOST_TEST( fixture.GetResult().GetTriangleCount() == poisonedTriangles.size() );
456
457 for( size_t i = 0; i < poisonedTriangles.size(); ++i )
458 {
459 BOOST_TEST( fixture.GetResult().Triangles()[i].a == poisonedTriangles[i].a );
460 BOOST_TEST( fixture.GetResult().Triangles()[i].b == poisonedTriangles[i].b );
461 BOOST_TEST( fixture.GetResult().Triangles()[i].c == poisonedTriangles[i].c );
462 }
463}
464
465BOOST_AUTO_TEST_CASE( HintDataInvalidation )
466{
467 // Create hint data with different vertex count
468 TRIANGULATION_TEST_FIXTURE hintFixture;
469 auto hintTriangulator = hintFixture.CreateTriangulator();
470 SHAPE_LINE_CHAIN triangle = createTriangle();
471 hintTriangulator->TesselatePolygon( triangle, nullptr );
472
473 // Try to use hint with different polygon (should ignore hint)
475 auto triangulator = fixture.CreateTriangulator();
477
478 bool success = triangulator->TesselatePolygon( square, &hintFixture.GetResult() );
479 BOOST_TEST( success );
481}
482
483// Degenerate case handling
484BOOST_AUTO_TEST_CASE( DegeneratePolygons )
485{
487 auto triangulator = fixture.CreateTriangulator();
488
489 // Test empty polygon
491 bool success = triangulator->TesselatePolygon( empty, nullptr );
492 BOOST_TEST( success ); // Should handle gracefully
493
494 // Test single point
496 auto triangulator2 = fixture2.CreateTriangulator();
497 SHAPE_LINE_CHAIN singlePoint;
498 singlePoint.Append( 0, 0 );
499 singlePoint.SetClosed( true );
500 success = triangulator2->TesselatePolygon( singlePoint, nullptr );
501 BOOST_TEST( success ); // Should handle gracefully
502
503 // Test two points (line segment)
505 auto triangulator3 = fixture3.CreateTriangulator();
506 SHAPE_LINE_CHAIN line;
507 line.Append( 0, 0 );
508 line.Append( 100, 0 );
509 line.SetClosed( true );
510 success = triangulator3->TesselatePolygon( line, nullptr );
511 BOOST_TEST( success ); // Should handle gracefully
512}
513
514BOOST_AUTO_TEST_CASE( ZeroAreaPolygon )
515{
517 auto triangulator = fixture.CreateTriangulator();
518
519 // Create a polygon with zero area (all points collinear)
520 SHAPE_LINE_CHAIN zeroArea;
521 zeroArea.Append( 0, 0 );
522 zeroArea.Append( 100, 0 );
523 zeroArea.Append( 50, 0 );
524 zeroArea.Append( 25, 0 );
525 zeroArea.SetClosed( true );
526
527 bool success = triangulator->TesselatePolygon( zeroArea, nullptr );
528
529 BOOST_TEST( success ); // Should handle gracefully without crashing
530}
531
532// Memory management and lifecycle tests
533BOOST_AUTO_TEST_CASE( MemoryManagement )
534{
535 // Test that multiple triangulations properly manage memory
536 for( int i = 0; i < 100; i++ )
537 {
539 auto triangulator = fixture.CreateTriangulator();
540
541 SHAPE_LINE_CHAIN poly = createSquare( 100 + i, VECTOR2I( i, i ) );
542 bool success = triangulator->TesselatePolygon( poly, nullptr );
543
544 BOOST_TEST( success );
545 BOOST_TEST( validateTriangulation( fixture.GetResult(), poly, false ) );
546 }
547}
548
549BOOST_AUTO_TEST_CASE( LargePolygonStressTest )
550{
552 auto triangulator = fixture.CreateTriangulator();
553
554 // Create a large polygon (regular polygon with many vertices)
555 SHAPE_LINE_CHAIN largePoly;
556 int numVertices = 1000;
557 int radius = 10000;
558
559 for( int i = 0; i < numVertices; i++ )
560 {
561 double angle = 2.0 * M_PI * i / numVertices;
562 int x = static_cast<int>( radius * cos( angle ) );
563 int y = static_cast<int>( radius * sin( angle ) );
564 largePoly.Append( x, y );
565 }
566 largePoly.SetClosed( true );
567
568 auto start = std::chrono::high_resolution_clock::now();
569 bool success = triangulator->TesselatePolygon( largePoly, nullptr );
570 auto end = std::chrono::high_resolution_clock::now();
571
572 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( end - start );
573
574 BOOST_TEST( success );
575 if( success )
576 {
577 BOOST_TEST( validateTriangulation( fixture.GetResult(), largePoly, false ) );
578 BOOST_TEST( fixture.GetResult().GetTriangleCount() > 0 );
579 }
580
581 // Performance check - should complete in reasonable time
582 BOOST_TEST( duration.count() < 10000 ); // Less than 10 seconds
583}
584
585// Thread safety tests (following SHAPE_POLY_SET patterns)
586BOOST_AUTO_TEST_CASE( ConcurrentTriangulation )
587{
588 const int numThreads = 4;
589 const int numTriangulationsPerThread = 10;
590
591 std::vector<std::future<bool>> futures;
592
593 for( int t = 0; t < numThreads; t++ )
594 {
595 futures.push_back( std::async( std::launch::async, [t, numTriangulationsPerThread]()
596 {
597 for( int i = 0; i < numTriangulationsPerThread; i++ )
598 {
600 auto triangulator = fixture.CreateTriangulator();
601
602 // Create unique polygon for each thread/iteration
603 SHAPE_LINE_CHAIN poly = createSquare( 100 + t * 10 + i, VECTOR2I( t * 100, i * 100 ) );
604
605 bool success = triangulator->TesselatePolygon( poly, nullptr );
606 if( !success || !validateTriangulation( fixture.GetResult(), poly, false ) )
607 return false;
608 }
609 return true;
610 }));
611 }
612
613 // Wait for all threads and check results
614 for( auto& future : futures )
615 {
616 BOOST_TEST( future.get() );
617 }
618}
619
620// Edge case and robustness tests
621BOOST_AUTO_TEST_CASE( SelfIntersectingPolygon )
622{
624 auto triangulator = fixture.CreateTriangulator();
625
626 // Create a bowtie (self-intersecting polygon)
627 SHAPE_LINE_CHAIN bowtie;
628 bowtie.Append( 0, 0 );
629 bowtie.Append( 100, 100 );
630 bowtie.Append( 100, 0 );
631 bowtie.Append( 0, 100 );
632 bowtie.SetClosed( true );
633
634 bool success = triangulator->TesselatePolygon( bowtie, nullptr );
635
636 // Algorithm should handle self-intersecting polygons
637 BOOST_TEST( success );
638 if( success )
639 {
640 BOOST_TEST( validateTriangulation( fixture.GetResult(), bowtie, false ) );
641 }
642}
643
644
657BOOST_AUTO_TEST_CASE( Issue18083_SelfIntersectingPolygonArea )
658{
659 SHAPE_POLY_SET polySet;
660 SHAPE_LINE_CHAIN outline;
661
662 // Coordinates from the issue (converted to internal units: 1mm = 1000000)
663 const int SCALE = 1000000;
664 outline.Append( 165 * SCALE, 87 * SCALE );
665 outline.Append( 179 * SCALE, 87 * SCALE );
666 outline.Append( 174 * SCALE, 94 * SCALE );
667 outline.Append( 169 * SCALE, 87 * SCALE );
668 outline.Append( 167 * SCALE, 94 * SCALE );
669 outline.SetClosed( true );
670
671 polySet.AddOutline( outline );
672
673 // Verify the polygon is detected as self-intersecting
674 BOOST_TEST( polySet.IsSelfIntersecting() );
675
676 // Triangulate via SHAPE_POLY_SET
677 polySet.CacheTriangulation();
679
680 // Calculate the triangulated area
681 double triangulatedArea = 0.0;
682
683 for( int ii = 0; ii < polySet.TriangulatedPolyCount(); ii++ )
684 {
685 const auto triPoly = polySet.TriangulatedPolygon( ii );
686
687 for( const auto& tri : triPoly->Triangles() )
688 triangulatedArea += std::abs( tri.Area() );
689 }
690
691 // The expected total area is 49 mm² (14 mm² + 35 mm² for the two triangular lobes)
692 // Triangle 1: (165,87) - (169,87) - (167,94) = base 4mm, height 7mm = 14 mm²
693 // Triangle 2: (169,87) - (179,87) - (174,94) = base 10mm, height 7mm = 35 mm²
694 double expectedAreaMmSq = 49.0 * SCALE * SCALE;
695
696 // The triangulated area should match the expected area
697 BOOST_TEST( std::abs( triangulatedArea - expectedAreaMmSq ) < expectedAreaMmSq * 0.01,
698 "Triangulated area should match expected area of 49 mm²" );
699}
700
701BOOST_AUTO_TEST_CASE( NearlyCollinearVertices )
702{
704 auto triangulator = fixture.CreateTriangulator();
705
706 // Create a polygon with vertices that are nearly collinear
707 SHAPE_LINE_CHAIN nearlyCollinear;
708 nearlyCollinear.Append( 0, 0 );
709 nearlyCollinear.Append( 1000000, 0 );
710 nearlyCollinear.Append( 2000000, 1 ); // Very small deviation
711 nearlyCollinear.Append( 3000000, 0 );
712 nearlyCollinear.Append( 1500000, 1000000 );
713 nearlyCollinear.SetClosed( true );
714
715 bool success = triangulator->TesselatePolygon( nearlyCollinear, nullptr );
716
717 BOOST_TEST( success );
718 if( success )
719 {
720 BOOST_TEST( validateTriangulation( fixture.GetResult(), nearlyCollinear, false ) );
721 }
722}
723
724BOOST_AUTO_TEST_CASE( DuplicateVertices )
725{
727 auto triangulator = fixture.CreateTriangulator();
728
729 // Create a square with duplicate vertices
731 duplicate.Append( 0, 0 );
732 duplicate.Append( 0, 0 ); // Duplicate
733 duplicate.Append( 100, 0 );
734 duplicate.Append( 100, 0 ); // Duplicate
735 duplicate.Append( 100, 100 );
736 duplicate.Append( 100, 100 ); // Duplicate
737 duplicate.Append( 0, 100 );
738 duplicate.Append( 0, 100 ); // Duplicate
739 duplicate.SetClosed( true );
740
741 bool success = triangulator->TesselatePolygon( duplicate, nullptr );
742
743 BOOST_TEST( success );
744 if( success )
745 {
746 BOOST_TEST( validateTriangulation( fixture.GetResult(), duplicate, false ) );
747 }
748}
749
750BOOST_AUTO_TEST_CASE( ExtremeCoordinates )
751{
753 auto triangulator = fixture.CreateTriangulator();
754
755 // Test with very large coordinates
756 SHAPE_LINE_CHAIN extreme;
757 int large = 1000000000; // 1 billion
758 extreme.Append( 0, 0 );
759 extreme.Append( large, 0 );
760 extreme.Append( large, large );
761 extreme.Append( 0, large );
762 extreme.SetClosed( true );
763
764 bool success = triangulator->TesselatePolygon( extreme, nullptr );
765
766 BOOST_TEST( success );
767 if( success )
768 {
769 BOOST_TEST( validateTriangulation( fixture.GetResult(), extreme, false ) );
770 }
771}
772
773// Error recovery and cleanup tests
774BOOST_AUTO_TEST_CASE( ErrorRecoveryAndCleanup )
775{
777 auto triangulator = fixture.CreateTriangulator();
778
779 // Try a series of operations, some of which might fail
780 std::vector<SHAPE_LINE_CHAIN> testPolygons;
781
782 // Valid polygon
783 testPolygons.push_back( createSquare() );
784
785 // Degenerate polygon
786 SHAPE_LINE_CHAIN degenerate;
787 degenerate.Append( 0, 0 );
788 degenerate.SetClosed( true );
789 testPolygons.push_back( degenerate );
790
791 // Another valid polygon
792 testPolygons.push_back( createTriangle() );
793
794 for( const auto& poly : testPolygons )
795 {
796 // Each triangulation should start with a clean state
797 bool success = triangulator->TesselatePolygon( poly, nullptr );
798 // Even if triangulation fails, it should not crash
799 success |= fixture.GetResult().GetTriangleCount() > 0;
800 BOOST_TEST( success );
801 }
802}
803
804// Integration tests with TRIANGULATED_POLYGON interface
805BOOST_AUTO_TEST_CASE( TriangulatedPolygonInterface )
806{
808 auto triangulator = fixture.CreateTriangulator();
809
811 bool success = triangulator->TesselatePolygon( square, nullptr );
812
813 BOOST_TEST( success );
814
815 const auto& result = fixture.GetResult();
816
817 // Test GetTriangle method
818 if( result.GetTriangleCount() > 0 )
819 {
820 VECTOR2I a, b, c;
821 result.GetTriangle( 0, a, b, c );
822
823 // Vertices should be valid points from the square
824 BOOST_TEST( (a.x >= 0 && a.x <= 100 && a.y >= 0 && a.y <= 100) );
825 BOOST_TEST( (b.x >= 0 && b.x <= 100 && b.y >= 0 && b.y <= 100) );
826 BOOST_TEST( (c.x >= 0 && c.x <= 100 && c.y >= 0 && c.y <= 100) );
827 }
828
829 // Test triangle iteration
830 for( const auto& tri : result.Triangles() )
831 {
832 BOOST_TEST( tri.GetPointCount() == 3 );
833 BOOST_TEST( tri.GetSegmentCount() == 3 );
834 BOOST_TEST( tri.Area() > 0 );
835 BOOST_TEST( tri.IsClosed() );
836 BOOST_TEST( tri.IsSolid() );
837 }
838}
839
840BOOST_AUTO_TEST_CASE( SourceOutlineIndexTracking )
841{
843 auto triangulator = fixture.CreateTriangulator();
844
845 // Test that source outline index is properly maintained
846 const int expectedOutlineIndex = 5;
847
848 // Create triangulated polygon with specific source index
849 SHAPE_POLY_SET::TRIANGULATED_POLYGON result( expectedOutlineIndex );
850 POLYGON_TRIANGULATION localTriangulator( result );
851
852 SHAPE_LINE_CHAIN triangle = createTriangle();
853 bool success = localTriangulator.TesselatePolygon( triangle, nullptr );
854
855 BOOST_TEST( success );
856 BOOST_TEST( result.GetSourceOutlineIndex() == expectedOutlineIndex );
857}
858
859// Performance regression tests
860BOOST_AUTO_TEST_CASE( PerformanceRegression )
861{
862 // Test various polygon sizes to ensure performance scales reasonably
863 std::vector<int> testSizes = { 10, 50, 100, 500, 1000 };
864 std::vector<long long> durations;
865
866 for( int size : testSizes )
867 {
869 auto triangulator = fixture.CreateTriangulator();
870
871 // Create regular polygon
872 SHAPE_LINE_CHAIN poly;
873 for( int i = 0; i < size; i++ )
874 {
875 double angle = 2.0 * M_PI * i / size;
876 int x = static_cast<int>( 1000 * cos( angle ) );
877 int y = static_cast<int>( 1000 * sin( angle ) );
878 poly.Append( x, y );
879 }
880 poly.SetClosed( true );
881
882 auto start = std::chrono::high_resolution_clock::now();
883 bool success = triangulator->TesselatePolygon( poly, nullptr );
884 auto end = std::chrono::high_resolution_clock::now();
885
886 BOOST_TEST( success );
887
888 auto duration = std::chrono::duration_cast<std::chrono::microseconds>( end - start );
889 durations.push_back( duration.count() );
890 }
891
892 // Check that performance scales reasonably (shouldn't be exponential)
893 // This is a basic sanity check - actual performance will vary by hardware
894 for( size_t i = 1; i < durations.size(); i++ )
895 {
896 double scaleFactor = static_cast<double>( durations[i] ) / durations[i-1];
897 double sizeFactor = static_cast<double>( testSizes[i] ) / testSizes[i-1];
898
899 // Performance shouldn't be worse than O(n^2) in most cases
900 BOOST_TEST( scaleFactor < sizeFactor * sizeFactor * 2 );
901 }
902}
903
909BOOST_AUTO_TEST_CASE( ParallelPartitionTriangulation )
910{
911 SHAPE_POLY_SET polySet;
912 SHAPE_LINE_CHAIN outline;
913 constexpr int vertexCount = 120000;
914 constexpr int centerX = 5000000;
915 constexpr int centerY = 5000000;
916 constexpr int radius = 4000000;
917
918 for( int i = 0; i < vertexCount; ++i )
919 {
920 double angle = 2.0 * M_PI * i / vertexCount;
921 int x = centerX + static_cast<int>( radius * cos( angle ) );
922 int y = centerY + static_cast<int>( radius * sin( angle ) );
923 outline.Append( x, y );
924 }
925
926 outline.SetClosed( true );
927 polySet.AddOutline( outline );
928
929 std::atomic<int> tasksSubmitted( 0 );
930
932 [&tasksSubmitted]( std::function<void()> aTask )
933 {
934 tasksSubmitted++;
935 std::thread( std::move( aTask ) ).detach();
936 };
937
938 polySet.CacheTriangulation( false, submitter );
939
941 BOOST_TEST( tasksSubmitted.load() > 0 );
942
943 double originalArea = std::abs( outline.Area() );
944 double triArea = 0.0;
945
946 for( unsigned int i = 0; i < polySet.TriangulatedPolyCount(); i++ )
947 {
948 const auto* triPoly = polySet.TriangulatedPolygon( static_cast<int>( i ) );
949
950 for( const auto& tri : triPoly->Triangles() )
951 triArea += std::abs( tri.Area() );
952 }
953
954 // Partitioning may clip edges, so allow a few percent tolerance
955 if( originalArea > 0.0 )
956 {
957 double coverage = triArea / originalArea;
958 BOOST_TEST( coverage > 0.90 );
959 BOOST_TEST( coverage < 1.10 );
960 }
961}
962
972BOOST_AUTO_TEST_CASE( Issue24059_HoleEliminationSentinelRemoval )
973{
974 SHAPE_LINE_CHAIN outer;
975 outer.Append( 0, 0 );
976 outer.Append( 10000, 0 );
977 outer.Append( 10000, 10000 );
978 outer.Append( 0, 10000 );
979 outer.SetClosed( true );
980
981 SHAPE_LINE_CHAIN hole;
982 hole.Append( 0, 0 );
983 hole.Append( 1000, 1000 );
984 hole.Append( 0, 2000 );
985 hole.SetClosed( true );
986
988 polygon.push_back( outer );
989 polygon.push_back( hole );
990
992 auto triangulator = fixture.CreateTriangulator();
993
994 std::atomic<bool> finished( false );
995 bool result = false;
996
997 std::thread worker( [&]()
998 {
999 result = triangulator->TesselatePolygon( polygon, nullptr );
1000 finished.store( true );
1001 } );
1002
1003 worker.detach();
1004
1005 auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds( 5 );
1006
1007 while( !finished.load() && std::chrono::steady_clock::now() < deadline )
1008 std::this_thread::sleep_for( std::chrono::milliseconds( 50 ) );
1009
1010 BOOST_CHECK_MESSAGE( finished.load(), "TesselatePolygon hung (issue #24059)" );
1011
1012 if( finished.load() )
1013 {
1014 BOOST_TEST( result );
1015 BOOST_TEST( fixture.GetResult().GetTriangleCount() > 0 );
1016 }
1017}
1018
1036BOOST_AUTO_TEST_CASE( Issue24121_DegenerateHoleAllDuplicates )
1037{
1038 SHAPE_LINE_CHAIN outer;
1039 outer.Append( 0, 0 );
1040 outer.Append( 10000, 0 );
1041 outer.Append( 10000, 10000 );
1042 outer.Append( 0, 10000 );
1043 outer.SetClosed( true );
1044
1045 // Hole with all coincident points: simplification leaves one vertex, then the
1046 // duplicate-tail cleanup formerly self-removed it and produced a nullptr-next
1047 // ring that crashed eliminateHoles().
1048 SHAPE_LINE_CHAIN hole;
1049 hole.Append( 5000, 5000 );
1050 hole.Append( 5000, 5000 );
1051 hole.Append( 5000, 5000 );
1052 hole.Append( 5000, 5000 );
1053 hole.SetClosed( true );
1054
1056 polygon.push_back( outer );
1057 polygon.push_back( hole );
1058
1060 auto triangulator = fixture.CreateTriangulator();
1061
1062 bool result = triangulator->TesselatePolygon( polygon, nullptr );
1063
1064 BOOST_TEST( result );
1065 BOOST_TEST( fixture.GetResult().GetTriangleCount() > 0 );
1066}
1067
1074BOOST_AUTO_TEST_CASE( Issue24121_DegenerateHoleBelowSimplification )
1075{
1076 SHAPE_LINE_CHAIN outer;
1077 outer.Append( 0, 0 );
1078 outer.Append( 10000, 0 );
1079 outer.Append( 10000, 10000 );
1080 outer.Append( 0, 10000 );
1081 outer.SetClosed( true );
1082
1083 // All four points lie within a 4-unit box, well below the default 50-unit
1084 // simplification threshold, so addVertex() in createRing() collapses them
1085 // to a single vertex.
1086 SHAPE_LINE_CHAIN tinyHole;
1087 tinyHole.Append( 5000, 5000 );
1088 tinyHole.Append( 5002, 5000 );
1089 tinyHole.Append( 5002, 5002 );
1090 tinyHole.Append( 5000, 5002 );
1091 tinyHole.SetClosed( true );
1092
1094 polygon.push_back( outer );
1095 polygon.push_back( tinyHole );
1096
1098 auto triangulator = fixture.CreateTriangulator();
1099
1100 bool result = triangulator->TesselatePolygon( polygon, nullptr );
1101
1102 BOOST_TEST( result );
1103 BOOST_TEST( fixture.GetResult().GetTriangleCount() > 0 );
1104}
1105
1106BOOST_AUTO_TEST_CASE( CacheTriangulation_AfterMove_FreshPoly )
1107{
1108 SHAPE_LINE_CHAIN outline;
1109 outline.Append( 0, 0 );
1110 outline.Append( 10000, 0 );
1111 outline.Append( 10000, 10000 );
1112 outline.Append( 0, 10000 );
1113 outline.SetClosed( true );
1114
1115 SHAPE_POLY_SET polySet;
1116 polySet.AddOutline( outline );
1117
1118 BOOST_TEST( !polySet.IsTriangulationUpToDate() );
1119
1120 // Move() sets m_hashValid=true without triangulating
1121 polySet.Move( { 1, 1 } );
1122 polySet.CacheTriangulation();
1123
1125 BOOST_TEST( polySet.TriangulatedPolyCount() > 0 );
1126}
1127
1128BOOST_AUTO_TEST_CASE( CacheTriangulation_AfterUpdateTriangulationDataHash )
1129{
1130 SHAPE_LINE_CHAIN outline;
1131 outline.Append( 0, 0 );
1132 outline.Append( 10000, 0 );
1133 outline.Append( 10000, 10000 );
1134 outline.Append( 0, 10000 );
1135 outline.SetClosed( true );
1136
1137 SHAPE_POLY_SET polySet;
1138 polySet.AddOutline( outline );
1139
1141 polySet.CacheTriangulation();
1142
1144 BOOST_TEST( polySet.TriangulatedPolyCount() > 0 );
1145}
1146
1147namespace
1148{
1149double meshMinAngleDeg( const SHAPE_POLY_SET::TRIANGULATED_POLYGON& aTri )
1150{
1151 double minAngle = 180.0;
1152
1153 for( const auto& tri : aTri.Triangles() )
1154 {
1155 minAngle = std::min( minAngle, GEOM_TEST::TriangleMinAngleDeg( tri.GetPoint( 0 ),
1156 tri.GetPoint( 1 ),
1157 tri.GetPoint( 2 ) ) );
1158 }
1159
1160 return minAngle;
1161}
1162
1163
1164double meshArea( const SHAPE_POLY_SET::TRIANGULATED_POLYGON& aTri )
1165{
1166 double area = 0.0;
1167
1168 for( const auto& tri : aTri.Triangles() )
1169 area += tri.Area();
1170
1171 return area;
1172}
1173
1174
1175bool meshHasEdge( const SHAPE_POLY_SET::TRIANGULATED_POLYGON& aTri, int aU, int aV )
1176{
1177 for( const auto& tri : aTri.Triangles() )
1178 {
1179 bool hasU = tri.a == aU || tri.b == aU || tri.c == aU;
1180 bool hasV = tri.a == aV || tri.b == aV || tri.c == aV;
1181
1182 if( hasU && hasV )
1183 return true;
1184 }
1185
1186 return false;
1187}
1188
1189
1190int meshSpikeyCount( const SHAPE_POLY_SET::TRIANGULATED_POLYGON& aTri )
1191{
1192 int count = 0;
1193
1194 for( const auto& tri : aTri.Triangles() )
1195 {
1196 VECTOR2I a = tri.GetPoint( 0 ), b = tri.GetPoint( 1 ), c = tri.GetPoint( 2 );
1197
1198 if( KIGEOM::IsSliverTriangle( a, b, c ) )
1199 count++;
1200 }
1201
1202 return count;
1203}
1204} // namespace
1205
1206
1207BOOST_AUTO_TEST_CASE( RefineFlipsNonDelaunayDiagonal )
1208{
1209 // Vertex 3 lies inside the circumcircle of 0-1-2, so diagonal 0-2 is non-Delaunay and must
1210 // flip to 1-3.
1212 tp.AddVertex( VECTOR2I( 0, 0 ) );
1213 tp.AddVertex( VECTOR2I( 10000, 0 ) );
1214 tp.AddVertex( VECTOR2I( 10000, 10000 ) );
1215 tp.AddVertex( VECTOR2I( 2000, 8000 ) );
1216 tp.AddTriangle( 0, 1, 2 );
1217 tp.AddTriangle( 0, 2, 3 );
1218
1219 double beforeAngle = meshMinAngleDeg( tp );
1220 double beforeArea = meshArea( tp );
1221
1222 tp.Refine();
1223
1224 BOOST_CHECK_EQUAL( tp.GetTriangleCount(), 2u );
1225 BOOST_CHECK_CLOSE( meshArea( tp ), beforeArea, 0.001 );
1226 BOOST_CHECK_GT( meshMinAngleDeg( tp ), beforeAngle );
1227
1228 BOOST_CHECK( meshHasEdge( tp, 0, 1 ) );
1229 BOOST_CHECK( meshHasEdge( tp, 1, 2 ) );
1230 BOOST_CHECK( meshHasEdge( tp, 2, 3 ) );
1231 BOOST_CHECK( meshHasEdge( tp, 0, 3 ) );
1232
1233 BOOST_CHECK( !meshHasEdge( tp, 0, 2 ) );
1234 BOOST_CHECK( meshHasEdge( tp, 1, 3 ) );
1235}
1236
1237
1238BOOST_AUTO_TEST_CASE( RefinePrefersFewerSliversOverDelaunay )
1239{
1240 // Diagonal 0-2 is Delaunay-legal but produces a sliver; 1-3 produces none. Refine must prefer
1241 // the fewer-sliver diagonal over the Delaunay one.
1243 tp.AddVertex( VECTOR2I( 0, 0 ) );
1244 tp.AddVertex( VECTOR2I( 284000, 42000 ) );
1245 tp.AddVertex( VECTOR2I( 276000, 68000 ) );
1246 tp.AddVertex( VECTOR2I( 38000, 126000 ) );
1247 tp.AddTriangle( 0, 1, 2 );
1248 tp.AddTriangle( 0, 2, 3 );
1249
1250 // Precondition: 0-2 is Delaunay-legal, so a Delaunay-only refine would not touch it.
1252 VECTOR2I( 276000, 68000 ),
1253 VECTOR2I( 38000, 126000 ) ) );
1254 BOOST_REQUIRE_EQUAL( meshSpikeyCount( tp ), 1 );
1255
1256 double beforeArea = meshArea( tp );
1257
1258 tp.Refine();
1259
1260 BOOST_CHECK_EQUAL( tp.GetTriangleCount(), 2u );
1261 BOOST_CHECK_CLOSE( meshArea( tp ), beforeArea, 0.001 );
1262 BOOST_CHECK_EQUAL( meshSpikeyCount( tp ), 0 );
1263 BOOST_CHECK( !meshHasEdge( tp, 0, 2 ) );
1264 BOOST_CHECK( meshHasEdge( tp, 1, 3 ) );
1265}
1266
1267
1268BOOST_AUTO_TEST_CASE( RefineLeavesDelaunayMeshUnchanged )
1269{
1270 // An already-Delaunay square triangulation must be a fixed point: no spurious flips that
1271 // would churn the mesh or, worse, oscillate.
1273 tp.AddVertex( VECTOR2I( 0, 0 ) );
1274 tp.AddVertex( VECTOR2I( 10000, 0 ) );
1275 tp.AddVertex( VECTOR2I( 10000, 10000 ) );
1276 tp.AddVertex( VECTOR2I( 0, 10000 ) );
1277 tp.AddTriangle( 0, 1, 2 );
1278 tp.AddTriangle( 0, 2, 3 );
1279
1280 double beforeArea = meshArea( tp );
1281
1282 tp.Refine();
1283
1284 BOOST_CHECK_EQUAL( tp.GetTriangleCount(), 2u );
1285 BOOST_CHECK_CLOSE( meshArea( tp ), beforeArea, 0.001 );
1286 BOOST_CHECK( meshHasEdge( tp, 0, 2 ) );
1287}
1288
1289
1290BOOST_AUTO_TEST_CASE( PredicatesHandleFullCoordinateRange )
1291{
1292 // The 4e9 nm width exceeds a 32-bit difference; a->b->c is counter-clockwise and must read so.
1293 VECTOR2I a( -2000000000, 0 );
1294 VECTOR2I b( 2000000000, 0 );
1295 VECTOR2I c( 2000000000, 1000 );
1296
1299
1300 // The same triangle is a needle; the sliver must still register at this span.
1301 BOOST_CHECK( KIGEOM::IsSliverTriangle( a, b, c ) );
1302}
1303
1304
1305BOOST_AUTO_TEST_CASE( RefinePreservesNonManifoldEdge )
1306{
1307 // Three triangles share edge 0-1, as a hole-bridge pinch produces. That non-manifold edge must
1308 // act as a boundary and never flip.
1310 tp.AddVertex( VECTOR2I( 0, 0 ) );
1311 tp.AddVertex( VECTOR2I( 100000, 0 ) );
1312 tp.AddVertex( VECTOR2I( 50000, 30000 ) );
1313 tp.AddVertex( VECTOR2I( 50000, -30000 ) );
1314 tp.AddVertex( VECTOR2I( 50000, 60000 ) );
1315 tp.AddTriangle( 0, 1, 2 );
1316 tp.AddTriangle( 0, 1, 3 );
1317 tp.AddTriangle( 0, 1, 4 );
1318
1319 std::vector<int> before;
1320
1321 for( const auto& tri : tp.Triangles() )
1322 {
1323 before.push_back( tri.a );
1324 before.push_back( tri.b );
1325 before.push_back( tri.c );
1326 }
1327
1328 tp.Refine();
1329
1330 std::vector<int> after;
1331
1332 for( const auto& tri : tp.Triangles() )
1333 {
1334 after.push_back( tri.a );
1335 after.push_back( tri.b );
1336 after.push_back( tri.c );
1337 }
1338
1339 BOOST_CHECK( before == after );
1340 BOOST_CHECK( meshHasEdge( tp, 0, 1 ) );
1341}
1342
1343
1344BOOST_AUTO_TEST_CASE( DecimateRemovesCollinearRun )
1345{
1346 // A square subdivided into many exactly-collinear points must triangulate down to the two
1347 // triangles the plain square produces; the subdivision points carry no geometry.
1349 const int size = 1000000;
1350 const int steps = 20;
1351
1352 for( int i = 0; i < steps; i++ )
1353 chain.Append( i * size / steps, 0 );
1354
1355 for( int i = 0; i < steps; i++ )
1356 chain.Append( size, i * size / steps );
1357
1358 for( int i = 0; i < steps; i++ )
1359 chain.Append( size - i * size / steps, size );
1360
1361 for( int i = 0; i < steps; i++ )
1362 chain.Append( 0, size - i * size / steps );
1363
1364 chain.SetClosed( true );
1365
1367 auto triangulator = fixture.CreateTriangulator();
1368 BOOST_REQUIRE( triangulator->TesselatePolygon( chain, nullptr ) );
1369
1370 BOOST_CHECK_EQUAL( fixture.GetResult().GetTriangleCount(), 2u );
1371 BOOST_CHECK_CLOSE( meshArea( fixture.GetResult() ), (double) size * size, 1e-6 );
1372}
1373
1374
1375BOOST_AUTO_TEST_CASE( DecimateKeepsVerticesBeyondBand )
1376{
1377 // Teeth of amplitude just past the simplification level must all survive. The teeth are
1378 // tiny and the valley deep, so the area budget alone would let every one go: only the band
1379 // test can hold them, which is what this pins.
1381 const int amp = TRIANGULATESIMPLIFICATIONLEVEL * 2;
1382 const int halfPitch = 25000;
1383 const int teeth = 10;
1384
1385 for( int i = 0; i <= teeth * 2; i++ )
1386 chain.Append( i * halfPitch, ( i % 2 ) ? amp : 0 );
1387
1388 chain.Append( teeth * 2 * halfPitch, -5000000 );
1389 chain.Append( 0, -5000000 );
1390 chain.SetClosed( true );
1391
1393 auto triangulator = fixture.CreateTriangulator();
1394 BOOST_REQUIRE( triangulator->TesselatePolygon( chain, nullptr ) );
1395
1397 static_cast<size_t>( chain.PointCount() ) - 2 );
1398}
1399
1400
1401BOOST_AUTO_TEST_CASE( DecimateKeepsFractureBridges )
1402{
1403 // Fracturing bakes the hole into the outline through a zero-width corridor whose feet
1404 // split an outline edge into exactly-collinear pieces. With the outline edges subdivided
1405 // there is a real run to collapse, so decimation runs on the fractured ring: it must fold
1406 // the subdivided edges away (triangle count near the plain four-corner result) yet never
1407 // cross the corridor and seal the hole (area preserved).
1408 const int size = 1000000;
1409 const int steps = 25;
1410 SHAPE_LINE_CHAIN outline;
1411
1412 for( int i = 0; i < steps; i++ )
1413 outline.Append( i * size / steps, 0 );
1414
1415 for( int i = 0; i < steps; i++ )
1416 outline.Append( size, i * size / steps );
1417
1418 for( int i = 0; i < steps; i++ )
1419 outline.Append( size - i * size / steps, size );
1420
1421 for( int i = 0; i < steps; i++ )
1422 outline.Append( 0, size - i * size / steps );
1423
1424 outline.SetClosed( true );
1425
1426 SHAPE_LINE_CHAIN hole;
1427 hole.Append( 400000, 400000 );
1428 hole.Append( 400000, 600000 );
1429 hole.Append( 600000, 600000 );
1430 hole.Append( 600000, 400000 );
1431 hole.SetClosed( true );
1432
1433 SHAPE_POLY_SET poly;
1434 poly.AddOutline( outline );
1435 poly.AddHole( hole );
1436
1437 const double area = poly.Area();
1438
1439 poly.Fracture();
1440 poly.CacheTriangulation( false );
1441
1442 double meshTotal = 0.0;
1443 size_t triangles = 0;
1444
1445 for( unsigned i = 0; i < poly.TriangulatedPolyCount(); i++ )
1446 {
1447 triangles += poly.TriangulatedPolygon( i )->GetTriangleCount();
1448
1449 for( const auto& tri : poly.TriangulatedPolygon( i )->Triangles() )
1450 meshTotal += tri.Area();
1451 }
1452
1453 BOOST_CHECK_CLOSE( meshTotal, area, 1e-6 );
1454
1455 // The 100 subdivision points carry no geometry; without decimation the fractured ring
1456 // keeps them all and the mesh is an order of magnitude larger.
1457 BOOST_CHECK_LT( triangles, 20u );
1458}
1459
double square(double x)
bool TesselatePolygon(const SHAPE_POLY_SET::POLYGON &aPolygon, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
Triangulate a polygon with holes by bridging holes directly into the outer ring's VERTEX linked list,...
std::vector< double > PartitionAreaFractionsForTesting(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
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.
int PointCount() const
Return the number of points (vertices) in this line chain.
double Area(bool aAbsolute=true) const
Return the area of this chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const std::deque< TRI > & Triangles() const
void SetTriangles(const std::deque< TRI > &aTriangles)
Represent a set of closed polygons.
bool IsTriangulationUpToDate() const
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
double Area()
Return the area of this poly set.
bool Parse(std::stringstream &aStream) override
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
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 UpdateTriangulationDataHash()
void Move(const VECTOR2I &aVector) override
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
std::function< void(std::function< void()>)> TASK_SUBMITTER
Callback that submits a unit of work for asynchronous execution.
bool IsSelfIntersecting() const
Check whether any of the polygons in the set is self intersecting.
SHAPE_POLY_SET::TRIANGULATED_POLYGON & GetResult()
std::unique_ptr< POLYGON_TRIANGULATION > CreateTriangulator()
std::unique_ptr< SHAPE_POLY_SET::TRIANGULATED_POLYGON > m_result
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
static bool empty(const wxTextEntryBase *aCtrl)
Exact orientation and in-circle predicates over integer coordinates.
double TriangleMinAngleDeg(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
The smallest interior angle of a triangle, in degrees; near zero for a sliver.
int OrientationSign(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
Orientation of triangle (a, b, c): +1 counter-clockwise, -1 clockwise, 0 collinear.
bool InCircleDelaunayLegal(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c, const VECTOR2I &p)
True when p is outside the circumcircle of CCW triangle (a, b, c): the shared edge is already Delauna...
bool IsSliverTriangle(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
A triangle is a sliver when its longest edge exceeds ten times its shortest.
STL namespace.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
Numerical test predicates.
#define TRIANGULATESIMPLIFICATIONLEVEL
static std::vector< double > PartitionAreaFractions(POLYGON_TRIANGULATION &aTriangulator, const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST(netlist.find("R_G1 ARM_OUT1 DIE_B R='0.001 / ((SW_STATE)") !=std::string::npos)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
bool parsePolyFileForTest(const fs::path &aPath, std::vector< SHAPE_POLY_SET > &aZones)
SHAPE_LINE_CHAIN createConcavePolygon(int size=100)
int countSpikeyTriangles(const SHAPE_POLY_SET::TRIANGULATED_POLYGON &aResult)
SHAPE_LINE_CHAIN createSquare(int size=100, VECTOR2I offset=VECTOR2I(0, 0))
double computeBoardSpikeyRatio(const fs::path &aPath)
bool validateTriangulation(const SHAPE_POLY_SET::TRIANGULATED_POLYGON &result, const SHAPE_LINE_CHAIN &original, bool strict=true)
SHAPE_LINE_CHAIN createTriangle(int size=100, VECTOR2I offset=VECTOR2I(0, 0))
BOOST_AUTO_TEST_CASE(BasicTriangleTriangulation)
SHAPE_LINE_CHAIN createSerpentinePolygon(int step=20000, int teeth=16)
const SHAPE_LINE_CHAIN chain
int radius
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
#define M_PI
static thread_pool * tp
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683