26#include <nlohmann/json.hpp>
39namespace fs = std::filesystem;
50 SHAPE_POLY_SET polySet;
57 std::vector<ZONE_ENTRY> zones;
67 int triangleCount = 0;
69 double originalArea = 0.0;
70 double triangulatedArea = 0.0;
71 double areaCoverage = 0.0;
72 double meanTriArea = 0.0;
73 double stddevTriArea = 0.0;
74 int spikeyTriangles = 0;
75 double spikeyRatio = 0.0;
79 double minAnglePctl1 = 0.0;
80 double minAnglePctl5 = 0.0;
81 double minAnglePctl50 = 0.0;
82 int trisBelow5Deg = 0;
83 int trisBelow10Deg = 0;
84 int trisBelow15Deg = 0;
85 double meanRadiusRatio = 0.0;
89 std::vector<double> minAngles;
97 int triangleCount = 0;
98 double areaCoverage = 0.0;
99 double spikeyRatio = 0.0;
100 double stddevTriArea = 0.0;
101 int spikeyTriangles = 0;
102 double originalArea = 0.0;
103 double minAnglePctl1 = 0.0;
104 double minAnglePctl5 = 0.0;
105 int trisBelow10Deg = 0;
112 std::vector<BASELINE_ZONE> zones;
118 std::map<std::string, BASELINE_BOARD> boards;
119 int totalTriangles = 0;
120 int totalSpikeyTri = 0;
121 int64_t totalTimeUs = 0;
122 double spikeyRatio = 0.0;
138struct ZONE_COMPARISON
145 int baseTriangles = 0;
146 int curTriangles = 0;
147 double baseSpikeyRatio = 0.0;
148 double curSpikeyRatio = 0.0;
149 double baseStddev = 0.0;
150 double curStddev = 0.0;
151 double baseCoverage = 0.0;
152 double curCoverage = 0.0;
153 double baseMinAngleP5 = 0.0;
154 double curMinAngleP5 = 0.0;
155 int baseBelow10Deg = 0;
156 int curBelow10Deg = 0;
157 int64_t baseTimeUs = 0;
158 int64_t curTimeUs = 0;
160 double spikeyDeltaPp()
const {
return ( curSpikeyRatio - baseSpikeyRatio ) * 100.0; }
163 double minAngleP5DeltaDeg()
const {
return curMinAngleP5 - baseMinAngleP5; }
165 double triangleDeltaPct()
const
167 if( baseTriangles == 0 )
168 return curTriangles == 0 ? 0.0 : 100.0;
170 return ( curTriangles - baseTriangles ) /
static_cast<double>( baseTriangles ) * 100.0;
173 double stddevDeltaPct()
const
175 if( baseStddev == 0.0 )
176 return curStddev == 0.0 ? 0.0 : 100.0;
178 return ( curStddev - baseStddev ) / baseStddev * 100.0;
181 double timeDeltaPct()
const
183 if( baseTimeUs == 0 )
184 return curTimeUs == 0 ? 0.0 : 100.0;
186 return static_cast<double>( curTimeUs - baseTimeUs ) /
static_cast<double>( baseTimeUs )
192BASELINE_DATA LoadBaseline(
const fs::path& aJsonPath )
194 BASELINE_DATA baseline;
196 if( !fs::exists( aJsonPath ) )
199 std::ifstream file( aJsonPath );
201 if( !file.is_open() )
208 j = nlohmann::json::parse( file );
210 catch(
const nlohmann::json::exception& )
215 if( j.contains(
"metadata" ) )
217 baseline.boardCount = j[
"metadata"].value(
"board_count", 0 );
218 baseline.zoneCount = j[
"metadata"].value(
"zone_count", 0 );
221 if( j.contains(
"global" ) )
223 baseline.totalTriangles = j[
"global"].value(
"total_triangles", 0 );
224 baseline.totalSpikeyTri = j[
"global"].value(
"total_spikey_triangles", 0 );
225 baseline.totalTimeUs = j[
"global"].value(
"total_time_us", (int64_t) 0 );
226 baseline.spikeyRatio = j[
"global"].value(
"spikey_ratio", 0.0 );
229 if( j.contains(
"boards" ) )
231 for(
const auto& boardJson : j[
"boards"] )
233 std::string source = boardJson.value(
"source",
"" );
234 BASELINE_BOARD board;
236 if( boardJson.contains(
"zones" ) )
238 for(
const auto& zoneJson : boardJson[
"zones"] )
241 zone.layer = zoneJson.value(
"layer",
"" );
242 zone.net = zoneJson.value(
"net",
"" );
243 zone.triangleCount = zoneJson.value(
"triangle_count", 0 );
244 zone.areaCoverage = zoneJson.value(
"area_coverage", 0.0 );
245 zone.spikeyRatio = zoneJson.value(
"spikey_ratio", 0.0 );
246 zone.stddevTriArea = zoneJson.value(
"stddev_triangle_area_nm2", 0.0 );
247 zone.spikeyTriangles = zoneJson.value(
"spikey_triangles", 0 );
248 zone.originalArea = zoneJson.value(
"original_area_nm2", 0.0 );
249 zone.minAnglePctl1 = zoneJson.value(
"min_angle_p1_deg", 0.0 );
250 zone.minAnglePctl5 = zoneJson.value(
"min_angle_p5_deg", 0.0 );
251 zone.trisBelow10Deg = zoneJson.value(
"tris_below_10deg", 0 );
252 zone.timeUs = zoneJson.value(
"time_us", (int64_t) 0 );
253 board.zones.push_back( zone );
257 baseline.boards[source] = std::move( board );
261 baseline.valid =
true;
266bool ParsePolyFile(
const fs::path& aPath, BOARD_ENTRY& aBoard )
268 std::ifstream file( aPath );
270 if( !file.is_open() )
273 std::string content( ( std::istreambuf_iterator<char>( file ) ),
274 std::istreambuf_iterator<char>() );
276 size_t srcStart = content.find(
"(source \"" );
278 if( srcStart != std::string::npos )
281 size_t srcEnd = content.find(
"\")", srcStart );
283 if( srcEnd != std::string::npos )
284 aBoard.source = content.substr( srcStart, srcEnd - srcStart );
289 while( ( zonePos = content.find(
"(zone (layer \"", zonePos ) ) != std::string::npos )
293 size_t layerStart = zonePos + 14;
294 size_t layerEnd = content.find(
"\")", layerStart );
295 entry.layer = content.substr( layerStart, layerEnd - layerStart );
297 size_t netStart = content.find(
"(net \"", layerEnd );
299 if( netStart != std::string::npos )
302 size_t netEnd = content.find(
"\")", netStart );
303 entry.net = content.substr( netStart, netEnd - netStart );
306 size_t ocStart = content.find(
"(outline_count ", layerEnd );
308 if( ocStart != std::string::npos )
311 entry.outlineCount = std::stoi( content.substr( ocStart ) );
314 size_t vcStart = content.find(
"(vertex_count ", layerEnd );
316 if( vcStart != std::string::npos )
319 entry.vertexCount = std::stoi( content.substr( vcStart ) );
322 size_t polysetStart = content.find(
"polyset ", zonePos );
324 if( polysetStart != std::string::npos )
326 std::string remainder = content.substr( polysetStart );
327 std::stringstream ss( remainder );
329 if( entry.polySet.
Parse( ss ) )
330 aBoard.zones.push_back( std::move( entry ) );
333 zonePos = layerEnd + 1;
336 return !aBoard.zones.empty();
347 double s = ( ab + bc + ca ) / 2.0;
349 if( s <= 0.0 || ab <= 0.0 || bc <= 0.0 || ca <= 0.0 )
352 double area = std::sqrt( std::max( 0.0, s * ( s - ab ) * ( s - bc ) * ( s - ca ) ) );
354 return 8.0 * area * area / ( s * ab * bc * ca );
359double Percentile(
const std::vector<double>& aSorted,
double aPct )
361 if( aSorted.empty() )
364 if( aSorted.size() == 1 )
365 return aSorted.front();
367 double rank = aPct / 100.0 *
static_cast<double>( aSorted.size() - 1 );
368 size_t lo =
static_cast<size_t>( std::floor( rank ) );
369 size_t hi =
static_cast<size_t>( std::ceil( rank ) );
370 double frac = rank -
static_cast<double>( lo );
372 return aSorted[lo] + frac * ( aSorted[hi] - aSorted[lo] );
376ZONE_STATS ComputeZoneStats( ZONE_ENTRY& aZone )
379 stats.layer = aZone.layer;
380 stats.net = aZone.net;
381 stats.outlineCount = aZone.outlineCount;
382 stats.vertexCount = aZone.vertexCount;
383 stats.originalArea = aZone.polySet.
Area();
390 stats.timeUs =
static_cast<int64_t
>( timer.
msecs() * 1000.0 );
392 std::vector<double> triAreas;
398 for(
const auto& tri : triPoly->Triangles() )
399 triAreas.push_back( tri.Area() );
402 stats.triangleCount =
static_cast<int>( triAreas.size() );
403 stats.triangulatedArea = std::accumulate( triAreas.begin(), triAreas.end(), 0.0 );
405 if( stats.originalArea > 0.0 )
406 stats.areaCoverage = stats.triangulatedArea / stats.originalArea;
408 if( !triAreas.empty() )
410 stats.meanTriArea = stats.triangulatedArea /
static_cast<double>( triAreas.size() );
412 double sumSqDiff = 0.0;
414 for(
double a : triAreas )
416 double diff = a - stats.meanTriArea;
417 sumSqDiff += diff * diff;
420 stats.stddevTriArea = std::sqrt( sumSqDiff /
static_cast<double>( triAreas.size() ) );
423 double radiusRatioSum = 0.0;
429 for(
const auto& tri : triPoly->Triangles() )
436 stats.spikeyTriangles++;
439 stats.minAngles.push_back( minAngle );
442 stats.trisBelow5Deg++;
444 if( minAngle < 10.0 )
445 stats.trisBelow10Deg++;
447 if( minAngle < 15.0 )
448 stats.trisBelow15Deg++;
450 radiusRatioSum += TriangleRadiusRatio( pa, pb, pc );
454 if( stats.triangleCount > 0 )
456 stats.spikeyRatio =
static_cast<double>( stats.spikeyTriangles ) / stats.triangleCount;
457 stats.meanRadiusRatio = radiusRatioSum / stats.triangleCount;
459 std::vector<double> sorted = stats.minAngles;
460 std::sort( sorted.begin(), sorted.end() );
461 stats.minAnglePctl1 = Percentile( sorted, 1.0 );
462 stats.minAnglePctl5 = Percentile( sorted, 5.0 );
463 stats.minAnglePctl50 = Percentile( sorted, 50.0 );
470nlohmann::json ZoneStatsToJson(
const ZONE_STATS& aStats )
473 j[
"layer"] = aStats.layer;
474 j[
"net"] = aStats.net;
475 j[
"outline_count"] = aStats.outlineCount;
476 j[
"vertex_count"] = aStats.vertexCount;
477 j[
"triangle_count"] = aStats.triangleCount;
478 j[
"time_us"] = aStats.timeUs;
479 j[
"original_area_nm2"] = aStats.originalArea;
480 j[
"triangulated_area_nm2"] = aStats.triangulatedArea;
481 j[
"area_coverage"] = aStats.areaCoverage;
482 j[
"mean_triangle_area_nm2"] = aStats.meanTriArea;
483 j[
"stddev_triangle_area_nm2"] = aStats.stddevTriArea;
484 j[
"spikey_triangles"] = aStats.spikeyTriangles;
485 j[
"spikey_ratio"] = aStats.spikeyRatio;
486 j[
"min_angle_p1_deg"] = aStats.minAnglePctl1;
487 j[
"min_angle_p5_deg"] = aStats.minAnglePctl5;
488 j[
"min_angle_p50_deg"] = aStats.minAnglePctl50;
489 j[
"tris_below_5deg"] = aStats.trisBelow5Deg;
490 j[
"tris_below_10deg"] = aStats.trisBelow10Deg;
491 j[
"tris_below_15deg"] = aStats.trisBelow15Deg;
492 j[
"mean_radius_ratio"] = aStats.meanRadiusRatio;
497ZONE_COMPARISON CompareZone(
const std::string& aSource,
const ZONE_STATS& aCurrent,
498 const BASELINE_ZONE* aBaseline )
501 cmp.source = aSource;
502 cmp.layer = aCurrent.layer;
503 cmp.net = aCurrent.net;
504 cmp.curTriangles = aCurrent.triangleCount;
505 cmp.curSpikeyRatio = aCurrent.spikeyRatio;
506 cmp.curStddev = aCurrent.stddevTriArea;
507 cmp.curCoverage = aCurrent.areaCoverage;
508 cmp.curMinAngleP5 = aCurrent.minAnglePctl5;
509 cmp.curBelow10Deg = aCurrent.trisBelow10Deg;
510 cmp.curTimeUs = aCurrent.timeUs;
514 cmp.type = CHANGE_TYPE::UNCHANGED;
518 cmp.baseTriangles = aBaseline->triangleCount;
519 cmp.baseSpikeyRatio = aBaseline->spikeyRatio;
520 cmp.baseStddev = aBaseline->stddevTriArea;
521 cmp.baseCoverage = aBaseline->areaCoverage;
522 cmp.baseMinAngleP5 = aBaseline->minAnglePctl5;
523 cmp.baseBelow10Deg = aBaseline->trisBelow10Deg;
524 cmp.baseTimeUs = aBaseline->timeUs;
526 bool coverageBroke = aCurrent.originalArea > 0.0
527 && ( aCurrent.areaCoverage < 0.99 || aCurrent.areaCoverage > 1.01 );
528 bool newFailure = aCurrent.triangleCount == 0 && aBaseline->triangleCount > 0
529 && aCurrent.originalArea > 0.0;
531 if( coverageBroke || newFailure )
533 cmp.type = CHANGE_TYPE::BREAKING;
556 double regressScore = ( cmp.baseMinAngleP5 - cmp.curMinAngleP5 )
557 + ( cmp.curBelow10Deg - cmp.baseBelow10Deg )
558 / std::max( 1.0,
static_cast<double>( cmp.baseTriangles ) )
561 std::vector<AXIS> axes = {
562 { regressScore, 1.0,
true },
563 { cmp.triangleDeltaPct(), 5.0,
true },
566 cmp.type = CHANGE_TYPE::UNCHANGED;
568 for(
const AXIS& axis : axes )
570 double signedWorse = axis.lowerBetter ? axis.delta : -axis.delta;
572 if( signedWorse > axis.threshold )
574 cmp.type = CHANGE_TYPE::REGRESSION;
578 if( signedWorse < -axis.threshold )
580 cmp.type = CHANGE_TYPE::IMPROVEMENT;
589std::string FormatSign(
double aValue,
const std::string& aSuffix )
591 std::ostringstream ss;
592 ss << std::fixed << std::setprecision( 1 );
597 ss << aValue << aSuffix;
602std::string FormatZoneDetail(
const ZONE_COMPARISON& aCmp )
604 std::ostringstream ss;
605 ss <<
" " << aCmp.source <<
" " << aCmp.layer <<
" \"" << aCmp.net <<
"\"" <<
"\n";
606 ss << std::fixed << std::setprecision( 1 );
607 ss <<
" time: " << FormatSign( aCmp.timeDeltaPct(),
"%" );
608 ss <<
" minAngleP5: " << aCmp.baseMinAngleP5 <<
" -> " << aCmp.curMinAngleP5
609 <<
" deg (" << FormatSign( aCmp.minAngleP5DeltaDeg(),
"deg" ) <<
")";
610 ss <<
" <10deg: " << aCmp.baseBelow10Deg <<
" -> " << aCmp.curBelow10Deg;
611 ss <<
"\n spikey: " << ( aCmp.baseSpikeyRatio * 100.0 ) <<
"% -> "
612 << ( aCmp.curSpikeyRatio * 100.0 ) <<
"% (" << FormatSign( aCmp.spikeyDeltaPp(),
"pp" )
614 ss <<
" triangles: " << aCmp.baseTriangles <<
" -> " << aCmp.curTriangles
615 <<
" (" << FormatSign( aCmp.triangleDeltaPct(),
"%" ) <<
")";
617 if( aCmp.baseStddev > 0.0 || aCmp.curStddev > 0.0 )
619 ss <<
" stddev: " << FormatSign( aCmp.stddevDeltaPct(),
"%" );
626void OutputComparisonReport(
const BASELINE_DATA& aBaseline,
627 const std::vector<ZONE_COMPARISON>& aComparisons,
628 int aTotalTriangles,
int aTotalSpikeyTri,
int aTotalZones )
630 std::vector<ZONE_COMPARISON> breaking;
631 std::vector<ZONE_COMPARISON> regressions;
632 std::vector<ZONE_COMPARISON> improvements;
635 for(
const auto& cmp : aComparisons )
639 case CHANGE_TYPE::BREAKING: breaking.push_back( cmp );
break;
640 case CHANGE_TYPE::REGRESSION: regressions.push_back( cmp );
break;
641 case CHANGE_TYPE::IMPROVEMENT: improvements.push_back( cmp );
break;
642 case CHANGE_TYPE::UNCHANGED: unchanged++;
break;
646 std::sort( improvements.begin(), improvements.end(),
647 [](
const ZONE_COMPARISON& a,
const ZONE_COMPARISON& b )
649 return a.spikeyDeltaPp() < b.spikeyDeltaPp();
652 std::sort( regressions.begin(), regressions.end(),
653 [](
const ZONE_COMPARISON& a,
const ZONE_COMPARISON& b )
655 return a.spikeyDeltaPp() > b.spikeyDeltaPp();
658 std::ostringstream report;
659 report << std::fixed << std::setprecision( 1 );
661 report <<
"\n=== Triangulation Comparison vs Baseline ===\n\n";
663 report <<
"Baseline: " << aBaseline.boardCount <<
" boards, "
664 << aBaseline.zoneCount <<
" zones\n";
665 report <<
"Current: " << aTotalZones <<
" zones\n\n";
667 double baseSpikey = aBaseline.spikeyRatio * 100.0;
668 double curSpikey = aTotalTriangles > 0
669 ?
static_cast<double>( aTotalSpikeyTri ) / aTotalTriangles * 100.0
672 report <<
"Global:\n";
673 report <<
" Triangles: " << aBaseline.totalTriangles <<
" -> " << aTotalTriangles
674 <<
" (" << FormatSign(
675 aTotalTriangles - aBaseline.totalTriangles == 0
677 : ( aTotalTriangles - aBaseline.totalTriangles )
678 /
static_cast<double>( aBaseline.totalTriangles )
682 report <<
" Spikey: " << baseSpikey <<
"% -> " << curSpikey <<
"% ("
683 << FormatSign( curSpikey - baseSpikey,
"pp" ) <<
")\n";
684 report <<
" Spikey ct: " << aBaseline.totalSpikeyTri <<
" -> " << aTotalSpikeyTri
687 report <<
"BREAKING: " << breaking.size() <<
" zones\n";
689 for(
const auto& cmp : breaking )
690 report << FormatZoneDetail( cmp ) <<
"\n";
692 if( breaking.empty() )
693 report <<
" (none)\n";
695 report <<
"\nREGRESSIONS: " << regressions.size() <<
" zones"
696 <<
" (lexicographic: regularity worse, else triangles >+5%)\n";
700 for(
const auto& cmp : regressions )
704 report <<
" ... and " << ( regressions.size() - 20 ) <<
" more\n";
708 report << FormatZoneDetail( cmp ) <<
"\n";
712 if( regressions.empty() )
713 report <<
" (none)\n";
715 report <<
"\nIMPROVEMENTS: " << improvements.size() <<
" zones\n";
719 for(
const auto& cmp : improvements )
723 report <<
" ... and " << ( improvements.size() - 20 ) <<
" more\n";
727 report << FormatZoneDetail( cmp ) <<
"\n";
731 if( improvements.empty() )
732 report <<
" (none)\n";
734 report <<
"\nSummary: " << improvements.size() <<
" improved, "
735 << regressions.size() <<
" regressed, "
736 << breaking.size() <<
" breaking, "
737 << unchanged <<
" unchanged\n";
741 BOOST_CHECK_MESSAGE( breaking.empty(),
742 std::to_string( breaking.size() )
743 +
" zone(s) have breaking triangulation changes" );
747std::string GetTriangulationDataDir()
760 std::string dataDir = GetTriangulationDataDir();
762 if( !fs::exists( dataDir ) || fs::is_empty( dataDir ) )
764 BOOST_TEST_MESSAGE(
"No triangulation data in " << dataDir <<
", skipping benchmark" );
768 fs::path
jsonPath = fs::path( dataDir ) /
"triangulation_status.json";
769 BASELINE_DATA baseline = LoadBaseline(
jsonPath );
774 << baseline.zoneCount <<
" zones, "
775 << baseline.totalTriangles <<
" triangles" );
784 for(
const auto& entry : fs::directory_iterator( dataDir ) )
786 if( entry.path().extension() ==
".kicad_polys" )
796 if(
const char* variant = std::getenv(
"KICAD_TRI_VARIANT" ) )
802 int totalBelow10 = 0;
807 std::vector<double> globalMinAngles;
809 std::vector<ZONE_COMPARISON> comparisons;
815 if( !ParsePolyFile( polyFile, board ) )
821 int boardTriangles = 0;
823 int64_t boardTimeUs = 0;
825 const BASELINE_BOARD* baseBoard =
nullptr;
826 auto it = baseline.boards.find( board.source );
828 if( it != baseline.boards.end() )
829 baseBoard = &it->second;
831 for(
size_t zi = 0; zi < board.zones.size(); zi++ )
833 ZONE_STATS stats = ComputeZoneStats( board.zones[zi] );
836 stats.triangleCount > 0 || stats.originalArea == 0.0,
837 board.source +
" " + stats.layer +
" " + stats.net
838 +
" produced 0 triangles with non-zero area" );
840 if( stats.originalArea > 0.0 )
843 stats.areaCoverage > 0.999 && stats.areaCoverage < 1.001,
844 board.source +
" " + stats.layer +
" " + stats.net
845 +
" area coverage: " + std::to_string( stats.areaCoverage ) );
848 boardTriangles += stats.triangleCount;
849 boardSpikey += stats.spikeyTriangles;
850 boardTimeUs += stats.timeUs;
851 totalBelow10 += stats.trisBelow10Deg;
852 globalMinAngles.insert( globalMinAngles.end(), stats.minAngles.begin(),
853 stats.minAngles.end() );
858 const BASELINE_ZONE* baseZone =
nullptr;
860 if( baseBoard && zi < baseBoard->zones.size() )
861 baseZone = &baseBoard->zones[zi];
863 comparisons.push_back( CompareZone( board.source, stats, baseZone ) );
879 std::sort( globalMinAngles.begin(), globalMinAngles.end() );
881 <<
" p5: " << Percentile( globalMinAngles, 5.0 )
882 <<
" p50: " << Percentile( globalMinAngles, 50.0 )
883 <<
" (<10deg: " << totalBelow10 <<
" = "
888 << baseline.totalTimeUs / 1000.0 <<
" ms)" );
899 if( !std::getenv(
"KICAD_TRI_UPDATE_BASELINE" ) )
905 std::string dataDir = GetTriangulationDataDir();
907 if( !fs::exists( dataDir ) || fs::is_empty( dataDir ) )
915 for(
const auto& entry : fs::directory_iterator( dataDir ) )
917 if( entry.path().extension() ==
".kicad_polys" )
935 if( !ParsePolyFile( polyFile, board ) )
938 nlohmann::json boardJson;
939 boardJson[
"source"] = board.source;
940 nlohmann::json zonesJson = nlohmann::json::array();
942 int boardTriangles = 0;
944 double boardTimeUs = 0.0;
946 for( ZONE_ENTRY& zone : board.zones )
948 ZONE_STATS stats = ComputeZoneStats( zone );
949 zonesJson.push_back( ZoneStatsToJson( stats ) );
951 boardTriangles += stats.triangleCount;
952 boardSpikey += stats.spikeyTriangles;
953 boardTimeUs +=
static_cast<double>( stats.timeUs );
958 boardJson[
"zones"] = zonesJson;
960 nlohmann::json boardTotals;
961 boardTotals[
"triangle_count"] = boardTriangles;
962 boardTotals[
"time_us"] =
static_cast<int64_t
>( boardTimeUs );
963 boardTotals[
"spikey_ratio"] = boardTriangles > 0
964 ?
static_cast<double>( boardSpikey ) / boardTriangles
966 boardJson[
"board_totals"] = boardTotals;
992 fs::path
jsonPath = fs::path( dataDir ) /
"triangulation_status.json";
A small class to help profiling.
void Stop()
Save the time when this function was called, and set the counter stane to stop.
double msecs(bool aSinceLast=false)
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.
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
CHANGE_TYPE
Types of changes.
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.
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.
std::string GetTestDataRootDir()
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_TEST_MESSAGE("Polyline has "<< chain.PointCount()<< " points")
nlohmann::json metadataJson
nlohmann::json boardsJson
nlohmann::json globalJson
BOOST_AUTO_TEST_CASE(BenchmarkAllExtractedPolygons)
std::vector< fs::path > polyFiles
std::ofstream jsonFile(jsonPath)
nlohmann::json jsonOutput
VECTOR2< int32_t > VECTOR2I