KiCad PCB EDA Suite
Loading...
Searching...
No Matches
snap_resolver.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 * 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
20#include <snap/snap_resolver.h>
21
22#include "snap_manifold.h"
23
24#include <mmh3_hash.h>
25
26#include <algorithm>
27#include <array>
28#include <cmath>
29#include <iomanip>
30#include <ostream>
31#include <sstream>
32#include <tuple>
33
34
35namespace
36{
37constexpr double LINE_TOLERANCE_IU = 2.0;
38constexpr double RANK_EPSILON = 1e-12;
39
40
41const char* statusName( SNAP_RESULT_STATUS aStatus )
42{
43 switch( aStatus )
44 {
45 case SNAP_RESULT_STATUS::SUCCESS: return "SUCCESS";
46 case SNAP_RESULT_STATUS::BASE_CONFLICT: return "BASE_CONFLICT";
47 case SNAP_RESULT_STATUS::INCOMPATIBLE: return "INCOMPATIBLE";
48 case SNAP_RESULT_STATUS::NONCONVERGENT: return "NONCONVERGENT";
49 case SNAP_RESULT_STATUS::INVALID_GEOMETRY: return "INVALID_GEOMETRY";
50 case SNAP_RESULT_STATUS::BUDGET_EXHAUSTED: return "BUDGET_EXHAUSTED";
51 }
52
53 return "UNKNOWN";
54}
55
56
57const char* relationName( SNAP_RELATION aRelation )
58{
59 switch( aRelation )
60 {
61 case SNAP_RELATION::COINCIDENCE: return "COINCIDENCE";
62 case SNAP_RELATION::X_COORDINATE: return "X_COORDINATE";
63 case SNAP_RELATION::Y_COORDINATE: return "Y_COORDINATE";
64 case SNAP_RELATION::POINT_ON_LINE: return "POINT_ON_LINE";
65 case SNAP_RELATION::POINT_ON_RAY: return "POINT_ON_RAY";
66 case SNAP_RELATION::POINT_ON_SEGMENT: return "POINT_ON_SEGMENT";
67 case SNAP_RELATION::POINT_ON_CIRCLE: return "POINT_ON_CIRCLE";
68 case SNAP_RELATION::POINT_ON_ARC: return "POINT_ON_ARC";
69 case SNAP_RELATION::ANGLE: return "ANGLE";
70 case SNAP_RELATION::TANGENT: return "TANGENT";
71 case SNAP_RELATION::NORMAL: return "NORMAL";
72 case SNAP_RELATION::BBOX_ALIGNMENT: return "BBOX_ALIGNMENT";
73 case SNAP_RELATION::BBOX_EQUAL_GAP: return "BBOX_EQUAL_GAP";
74 case SNAP_RELATION::GRID_X: return "GRID_X";
75 case SNAP_RELATION::GRID_Y: return "GRID_Y";
76 }
77
78 return "UNKNOWN";
79}
80
81
82const char* referenceKindName( SNAP_REFERENCE_KIND aKind )
83{
84 switch( aKind )
85 {
86 case SNAP_REFERENCE_KIND::NONE: return "NONE";
87 case SNAP_REFERENCE_KIND::BOUNDS_FEATURE: return "BOUNDS_FEATURE";
88 case SNAP_REFERENCE_KIND::ANCHOR_POINT: return "ANCHOR_POINT";
89 }
90
91 return "UNKNOWN";
92}
93
94
95std::string stableIdString( const SNAP_STABLE_ID& aId )
96{
97 std::ostringstream stream;
98 stream << static_cast<int>( aId.kind ) << ':';
99
100 for( uint8_t byte : aId.target )
101 stream << std::hex << std::setfill( '0' ) << std::setw( 2 ) << static_cast<int>( byte );
102
103 stream << std::dec << ':' << aId.featureIndex << ':' << aId.solutionBranch;
104 return stream.str();
105}
106
107
108SNAP_TARGET_ID hashTarget( MMH3_HASH& aHash )
109{
110 HASH_128 digest = aHash.digest();
111 SNAP_TARGET_ID bytes;
112 std::copy( std::begin( digest.Value8 ), std::end( digest.Value8 ), bytes.begin() );
113 return bytes;
114}
115
116
117SNAP_TARGET_ID idFingerprint( const SNAP_STABLE_ID& aId )
118{
119 MMH3_HASH hash( 0x53494446 );
120 hash.addData( aId.target.data(), aId.target.size() );
121 hash.add( static_cast<int32_t>( aId.kind ) );
122 hash.add( aId.featureIndex );
123 hash.add( aId.solutionBranch );
124 return hashTarget( hash );
125}
126
127
128SNAP_TARGET_ID pairTarget( const SNAP_TARGET_ID& aFirst, const SNAP_TARGET_ID& aSecond )
129{
130 MMH3_HASH hash( 0x53494450 );
131 hash.addData( aFirst.data(), aFirst.size() );
132 hash.addData( aSecond.data(), aSecond.size() );
133 return hashTarget( hash );
134}
135
136
137std::optional<bool> layoutAxis( const SNAP_CANDIDATE& aCandidate )
138{
140 return std::nullopt;
141
142 return std::abs( aCandidate.direction.x ) >= std::abs( aCandidate.direction.y );
143}
144
145
146struct EQUATION
147{
148 double a;
149 double b;
150 double c;
151 bool exact;
152};
153
154
155VECTOR2I nearestOnManifold( const INTERSECTABLE_GEOM& aGeometry, const VECTOR2I& aPoint )
156{
157 return std::visit(
158 [&]( const auto& aShape )
159 {
160 return aShape.NearestPoint( aPoint );
161 },
162 aGeometry );
163}
164
165
166std::vector<VECTOR2I> manifoldIntersections( const INTERSECTABLE_GEOM& aFirst, const INTERSECTABLE_GEOM& aSecond,
167 const VECTOR2I& aSource )
168{
169 std::vector<VECTOR2I> result;
170 const CIRCLE* circle = std::get_if<CIRCLE>( &aFirst );
171 const LINE* line = std::get_if<LINE>( &aSecond );
172
173 if( !circle || !line )
174 {
175 circle = std::get_if<CIRCLE>( &aSecond );
176 line = std::get_if<LINE>( &aFirst );
177 }
178
179 if( circle && line )
180 {
181 // CIRCLE::IntersectLine uses a 4 IU tangent tolerance, wider than snap exactness permits.
182 const SEG& segment = line->GetContainedSeg();
183 VECTOR2D origin( segment.A );
184 VECTOR2D direction( segment.B - segment.A );
185 VECTOR2D center( circle->Center );
186 double divisor = direction.SquaredEuclideanNorm();
187 double parameter = ( center - origin ).Dot( direction ) / divisor;
188 VECTOR2D projection = origin + direction * parameter;
189 double perpendicularSquared = ( projection - center ).SquaredEuclideanNorm();
190 double radiusSquared = static_cast<double>( circle->Radius ) * circle->Radius;
191
192 if( perpendicularSquared <= radiusSquared )
193 {
194 double offset = std::sqrt( std::max( 0.0, radiusSquared - perpendicularSquared ) / divisor );
195 VECTOR2D first = projection + direction * offset;
196 VECTOR2D second = projection - direction * offset;
197 result.emplace_back( KiROUND( first.x ), KiROUND( first.y ) );
198 result.emplace_back( KiROUND( second.x ), KiROUND( second.y ) );
199 }
200 }
201 else
202 {
203 std::visit( INTERSECTION_VISITOR( aSecond, result ), aFirst );
204 }
205
206 std::sort( result.begin(), result.end(),
207 [&]( const VECTOR2I& aLeft, const VECTOR2I& aRight )
208 {
209 return std::tuple( aLeft.SquaredDistance( aSource ), aLeft.x, aLeft.y )
210 < std::tuple( aRight.SquaredDistance( aSource ), aRight.x, aRight.y );
211 } );
212 result.erase( std::unique( result.begin(), result.end() ), result.end() );
213 return result;
214}
215
216
217LINE equationLine( const EQUATION& aEquation )
218{
219 VECTOR2D origin;
220
221 if( std::abs( aEquation.a ) > std::abs( aEquation.b ) )
222 origin = VECTOR2D( aEquation.c / aEquation.a, 0.0 );
223 else
224 origin = VECTOR2D( 0.0, aEquation.c / aEquation.b );
225
226 VECTOR2D direction( aEquation.b, -aEquation.a );
227 double length = direction.EuclideanNorm();
228 direction = direction * ( 1000000.0 / length );
229
230 VECTOR2I integerOrigin( KiROUND( origin.x ), KiROUND( origin.y ) );
231 VECTOR2I integerEnd( KiROUND( origin.x + direction.x ), KiROUND( origin.y + direction.y ) );
232 return LINE( integerOrigin, integerEnd );
233}
234
235
236int subtypeRank( SNAP_CANDIDATE_SUBTYPE aSubtype )
237{
238 switch( aSubtype )
239 {
249 case SNAP_CANDIDATE_SUBTYPE::CURSOR: return 0;
250 }
251
252 return 0;
253}
254
255
256std::vector<EQUATION> equations( const std::vector<SNAP_CANDIDATE>& aCandidates )
257{
258 std::vector<EQUATION> result;
259
260 for( const SNAP_CANDIDATE& candidate : aCandidates )
261 {
262 switch( candidate.relation )
263 {
267 result.push_back( { 1.0, 0.0, candidate.origin.x, true } );
268 result.push_back( { 0.0, 1.0, candidate.origin.y, true } );
269 break;
270
272 case SNAP_RELATION::GRID_X: result.push_back( { 1.0, 0.0, candidate.origin.x, true } ); break;
273
275 case SNAP_RELATION::GRID_Y: result.push_back( { 0.0, 1.0, candidate.origin.y, true } ); break;
276
279 if( candidate.direction.x != 0.0 )
280 result.push_back( { 1.0, 0.0, candidate.origin.x, true } );
281 else if( candidate.direction.y != 0.0 )
282 result.push_back( { 0.0, 1.0, candidate.origin.y, true } );
283 break;
284
289 {
290 double a = -candidate.direction.y;
291 double b = candidate.direction.x;
292 result.push_back( { a, b, a * candidate.origin.x + b * candidate.origin.y, false } );
293 break;
294 }
295
296 default: break;
297 }
298 }
299
300 return result;
301}
302
303
304bool solve( const SNAP_SOURCE_CONTEXT& aContext, const std::vector<SNAP_CANDIDATE>& aCandidates, VECTOR2I& aPosition,
305 int& aRemainingDof, std::vector<double>& aResiduals )
306{
307 std::vector<EQUATION> constraints = equations( aCandidates );
308 std::vector<const INTERSECTABLE_GEOM*> nonlinear;
309 std::optional<EQUATION> first;
310 std::optional<EQUATION> second;
311
312 for( const SNAP_CANDIDATE& candidate : aCandidates )
313 {
314 if( candidate.manifold
315 && ( std::holds_alternative<CIRCLE>( *candidate.manifold )
316 || std::holds_alternative<SHAPE_ARC>( *candidate.manifold ) ) )
317 {
318 nonlinear.push_back( &*candidate.manifold );
319 }
320 }
321
322 for( const EQUATION& equation : constraints )
323 {
324 double norm = std::hypot( equation.a, equation.b );
325
326 if( norm <= RANK_EPSILON )
327 return false;
328
329 if( !first )
330 {
331 first = equation;
332 continue;
333 }
334
335 double determinant = first->a * equation.b - equation.a * first->b;
336
337 if( std::abs( determinant ) > RANK_EPSILON )
338 {
339 second = equation;
340 break;
341 }
342 }
343
344 double x = aContext.sourcePoint.x;
345 double y = aContext.sourcePoint.y;
346
347 if( first && second )
348 {
349 double determinant = first->a * second->b - second->a * first->b;
350 x = ( first->c * second->b - second->c * first->b ) / determinant;
351 y = ( first->a * second->c - second->a * first->c ) / determinant;
352 aRemainingDof = 0;
353 }
354 else if( first )
355 {
356 double divisor = first->a * first->a + first->b * first->b;
357 double delta = ( first->c - first->a * x - first->b * y ) / divisor;
358 x += delta * first->a;
359 y += delta * first->b;
360 aRemainingDof = 1;
361 }
362 else
363 {
364 aRemainingDof = 2;
365 }
366
367 if( !second && !nonlinear.empty() )
368 {
369 std::vector<VECTOR2I> points;
370
371 if( first )
372 {
373 INTERSECTABLE_GEOM line = equationLine( *first );
374 points = manifoldIntersections( line, *nonlinear.front(), aContext.sourcePoint );
375 }
376 else if( nonlinear.size() >= 2 )
377 {
378 points = manifoldIntersections( *nonlinear[0], *nonlinear[1], aContext.sourcePoint );
379 }
380 else
381 {
382 points.push_back( nearestOnManifold( *nonlinear.front(), aContext.sourcePoint ) );
383 }
384
385 if( points.empty() )
386 return false;
387
388 x = points.front().x;
389 y = points.front().y;
390 aRemainingDof = first || nonlinear.size() >= 2 ? 0 : 1;
391 }
392
393 aPosition = VECTOR2I( KiROUND( x ), KiROUND( y ) );
394 aResiduals.clear();
395
396 for( const EQUATION& equation : constraints )
397 {
398 double residual = std::abs( equation.a * aPosition.x + equation.b * aPosition.y - equation.c )
399 / std::hypot( equation.a, equation.b );
400 aResiduals.push_back( residual );
401
402 if( equation.exact )
403 {
404 if( residual != 0.0 )
405 return false;
406 }
407 else if( residual > LINE_TOLERANCE_IU )
408 {
409 return false;
410 }
411 }
412
413 for( const SNAP_CANDIDATE& candidate : aCandidates )
414 {
415 if( candidate.manifold && snapManifoldDistance( *candidate.manifold, aPosition ) > LINE_TOLERANCE_IU )
416 {
417 return false;
418 }
419
420 if( !candidate.finite )
421 continue;
422
423 VECTOR2D offset( aPosition.x - candidate.origin.x, aPosition.y - candidate.origin.y );
424 double divisor = candidate.direction.SquaredEuclideanNorm();
425
426 if( divisor <= RANK_EPSILON )
427 return false;
428
429 double parameter = offset.Dot( candidate.direction ) / divisor;
430
431 if( parameter < candidate.domainStart || parameter > candidate.domainEnd )
432 return false;
433 }
434
435 return true;
436}
437} // namespace
438
439
440std::ostream& operator<<( std::ostream& aStream, SNAP_RESULT_STATUS aStatus )
441{
442 return aStream << statusName( aStatus );
443}
444
445
446bool SNAP_STABLE_ID::operator<( const SNAP_STABLE_ID& aOther ) const
447{
448 return std::tie( kind, target, featureIndex, solutionBranch )
449 < std::tie( aOther.kind, aOther.target, aOther.featureIndex, aOther.solutionBranch );
450}
451
452
453SNAP_STABLE_ID MakeDerivedSnapId( SNAP_ID_KIND aKind, const SNAP_STABLE_ID& aSource, int aFeatureIndex,
454 int aSolutionBranch )
455{
456 return { aKind, idFingerprint( aSource ), aFeatureIndex, aSolutionBranch };
457}
458
459
461 int aSolutionBranch )
462{
463 const SNAP_STABLE_ID* first = &aFirst;
464 const SNAP_STABLE_ID* second = &aSecond;
465
466 if( *second < *first )
467 std::swap( first, second );
468
469 return { SNAP_ID_KIND::INTERSECTION, pairTarget( idFingerprint( *first ), idFingerprint( *second ) ), 0,
470 aSolutionBranch };
471}
472
473
474SNAP_STABLE_ID MakePointSnapId( SNAP_ID_KIND aKind, const VECTOR2I& aPoint, int aFeatureIndex )
475{
476 MMH3_HASH hash( 0x534E4150 );
477 hash.add( static_cast<int32_t>( aKind ) );
478 hash.add( aPoint.x );
479 hash.add( aPoint.y );
480 hash.add( aFeatureIndex );
481 return { aKind, hashTarget( hash ), aFeatureIndex, 0 };
482}
483
484
485SNAP_STABLE_ID MakeCompositeSnapId( SNAP_ID_KIND aKind, const std::vector<SNAP_TARGET_ID>& aTargets, int aFeatureIndex )
486{
487 std::vector<SNAP_TARGET_ID> targets = aTargets;
488 std::sort( targets.begin(), targets.end() );
489
490 MMH3_HASH hash( 0x53494443 );
491 hash.add( static_cast<int32_t>( aKind ) );
492 hash.add( static_cast<uint32_t>( targets.size() ) );
493
494 for( const SNAP_TARGET_ID& target : targets )
495 hash.addData( target.data(), target.size() );
496
497 hash.add( aFeatureIndex );
498 return { aKind, hashTarget( hash ), aFeatureIndex, 0 };
499}
500
501
503 const VECTOR2I& aPoint, double aResidual )
504{
505 SNAP_CANDIDATE candidate;
506 candidate.id = std::move( aId );
507 candidate.priority = aPriority;
508 candidate.subtype = aSubtype;
510 candidate.origin = VECTOR2D( aPoint );
511 candidate.normalizedScreenResidual = aResidual;
512 candidate.consumedDof = 2;
513 return candidate;
514}
515
516
518 const VECTOR2I& aOrigin, const VECTOR2D& aDirection, double aResidual )
519{
520 SNAP_CANDIDATE candidate;
521 candidate.id = std::move( aId );
522 candidate.priority = aPriority;
523 candidate.subtype = aSubtype;
525 candidate.origin = VECTOR2D( aOrigin );
526 candidate.direction = aDirection;
527 candidate.normalizedScreenResidual = aResidual;
528 candidate.consumedDof = 1;
529
530 return candidate;
531}
532
533
535 int aCoordinate, double aResidual )
536{
537 SNAP_CANDIDATE candidate;
538 candidate.id = std::move( aId );
539 candidate.priority = aPriority;
540 candidate.subtype = aSubtype;
542 candidate.origin = VECTOR2D( aCoordinate, 0.0 );
543 candidate.direction = VECTOR2D( 1.0, 0.0 );
544 candidate.normalizedScreenResidual = aResidual;
545 candidate.consumedDof = 1;
546 return candidate;
547}
548
549
551 int aCoordinate, double aResidual )
552{
553 SNAP_CANDIDATE candidate;
554 candidate.id = std::move( aId );
555 candidate.priority = aPriority;
556 candidate.subtype = aSubtype;
558 candidate.origin = VECTOR2D( 0.0, aCoordinate );
559 candidate.direction = VECTOR2D( 0.0, 1.0 );
560 candidate.normalizedScreenResidual = aResidual;
561 candidate.consumedDof = 1;
562 return candidate;
563}
564
565
566bool SNAP_RESULT::Accepted( const SNAP_STABLE_ID& aId ) const
567{
568 return std::find( accepted.begin(), accepted.end(), aId ) != accepted.end();
569}
570
571
573{
574 m_candidates.emplace_back( std::move( aCandidate ) );
575}
576
577
579{
580 m_candidates.clear();
581}
582
583
584void SNAP_RESOLVER::SetRetainedCandidate( std::optional<SNAP_STABLE_ID> aId )
585{
586 m_retainedCandidate = std::move( aId );
587}
588
589
590void SNAP_RESOLVER::SetStickyCandidates( std::vector<SNAP_STABLE_ID> aIds )
591{
592 m_stickyCandidates = std::move( aIds );
593}
594
595
596void SNAP_RESOLVER::SetRankingHysteresis( double aNormalizedResidual )
597{
598 m_rankingHysteresis = std::max( 0.0, aNormalizedResidual );
599}
600
601
603{
605 return true;
606
607 return std::find( m_stickyCandidates.begin(), m_stickyCandidates.end(), aId ) != m_stickyCandidates.end();
608}
609
610
612{
613 m_feasibilityCallback = std::move( aCallback );
614}
615
616
618{
619 m_traceCallback = std::move( aCallback );
620}
621
622
624{
625 m_clock = std::move( aClock );
626}
627
628
629void SNAP_RESOLVER::SetDeadline( CLOCK::duration aDeadline )
630{
631 m_deadline = aDeadline;
632}
633
634
636{
637 struct RANKED
638 {
639 const SNAP_CANDIDATE* candidate;
640 int subtypeRank;
641 bool hysteresis;
642 double effectiveResidual;
643 };
644
645 std::vector<RANKED> ranked;
646 ranked.reserve( m_candidates.size() );
647
648 for( const SNAP_CANDIDATE& candidate : m_candidates )
649 {
650 const bool hysteresis = hasHysteresis( candidate.id );
651 ranked.push_back(
652 { &candidate, subtypeRank( candidate.subtype ), hysteresis,
653 std::max( 0.0, candidate.normalizedScreenResidual - ( hysteresis ? m_rankingHysteresis : 0.0 ) ) } );
654 }
655
656 const auto trace = [&]( const std::string& aMessage )
657 {
658 if( m_traceCallback )
659 m_traceCallback( aMessage );
660 };
661
662 std::sort( ranked.begin(), ranked.end(),
663 []( const RANKED& aLeft, const RANKED& aRight )
664 {
665 const SNAP_CANDIDATE& left = *aLeft.candidate;
666 const SNAP_CANDIDATE& right = *aRight.candidate;
667
668 return std::forward_as_tuple( left.priority, aLeft.subtypeRank, -left.consumedDof,
669 left.referenceAffinity, aLeft.effectiveResidual, !aLeft.hysteresis,
670 left.id )
671 < std::forward_as_tuple( right.priority, aRight.subtypeRank, -right.consumedDof,
672 right.referenceAffinity, aRight.effectiveResidual,
673 !aRight.hysteresis, right.id );
674 } );
675
676 if( m_traceCallback )
677 {
678 std::ostringstream stream;
679 stream << "resolve source=(" << aContext.sourcePoint.x << ',' << aContext.sourcePoint.y
680 << ") candidates=" << ranked.size() << " sticky=" << m_stickyCandidates.size()
681 << " reference=" << referenceKindName( aContext.referencePreference.kind )
682 << " x-feature=" << aContext.referencePreference.horizontalFeature
683 << " y-feature=" << aContext.referencePreference.verticalFeature;
684 trace( stream.str() );
685
686 for( size_t i = 0; i < ranked.size(); ++i )
687 {
688 const SNAP_CANDIDATE& candidate = *ranked[i].candidate;
689 stream.str( {} );
690 stream.clear();
691 stream << "rank index=" << i << " id=" << stableIdString( candidate.id )
692 << " relation=" << relationName( candidate.relation ) << " origin=(" << candidate.origin.x << ','
693 << candidate.origin.y << ')' << " residual=" << candidate.normalizedScreenResidual
694 << " affinity=" << candidate.referenceAffinity << " sticky=" << ranked[i].hysteresis;
695 trace( stream.str() );
696 }
697 }
698
700 result.position = aContext.sourcePoint;
701 std::vector<SNAP_CANDIDATE> acceptedCandidates;
702 std::vector<bool> processed( ranked.size() );
703 const CLOCK::time_point start = m_clock();
704 bool budgetExhausted = false;
705 acceptedCandidates.reserve( 5 );
706
708 {
709 result = m_feasibilityCallback( aContext, {} );
710
711 if( result.status != SNAP_RESULT_STATUS::SUCCESS )
712 {
713 if( m_traceCallback )
714 {
715 std::ostringstream stream;
716 stream << "result status=" << statusName( result.status ) << " position=(" << result.position.x << ','
717 << result.position.y << ") accepted=[]";
718 trace( stream.str() );
719 }
720
721 return result;
722 }
723 }
724
725 const auto trialCandidate = [&]( const SNAP_CANDIDATE& aCandidate, SNAP_RESULT& aTrialResult )
726 {
727 const size_t baseSize = acceptedCandidates.size();
728 acceptedCandidates.push_back( aCandidate );
729 bool accepted;
730
732 {
733 aTrialResult = m_feasibilityCallback( aContext, acceptedCandidates );
734 accepted = aTrialResult.status == SNAP_RESULT_STATUS::SUCCESS;
735 }
736 else
737 {
738 accepted = solve( aContext, acceptedCandidates, aTrialResult.position, aTrialResult.remainingDof,
739 aTrialResult.quantizedResiduals );
740 }
741
742 if( m_traceCallback )
743 {
744 std::ostringstream stream;
745 stream << "trial id=" << stableIdString( aCandidate.id ) << " accepted=" << accepted
746 << " affinity=" << aCandidate.referenceAffinity << " status=" << statusName( aTrialResult.status )
747 << " position=(" << aTrialResult.position.x << ',' << aTrialResult.position.y << ')'
748 << " remaining=" << aTrialResult.remainingDof << " base=" << baseSize;
749 trace( stream.str() );
750 }
751
752 acceptedCandidates.pop_back();
753 return accepted;
754 };
755
756 const auto acceptCandidate = [&]( size_t aIndex )
757 {
758 const SNAP_CANDIDATE& candidate = *ranked[aIndex].candidate;
759 SNAP_RESULT trialResult;
760
761 if( trialCandidate( candidate, trialResult ) )
762 {
763 acceptedCandidates.push_back( candidate );
764 result = std::move( trialResult );
765 }
766
767 processed[aIndex] = true;
768 };
769
770 for( size_t i = 0; i < ranked.size() && result.remainingDof > 0; ++i )
771 {
772 if( ranked[i].candidate->priority == SNAP_PRIORITY_TIER::AUTHORED_INTRINSIC )
773 acceptCandidate( i );
774 }
775
776 std::optional<size_t> acceptedAngle;
777 SNAP_RESULT acceptedAngleResult;
778
779 for( size_t i = 0; i < ranked.size() && result.remainingDof > 0; ++i )
780 {
781 const SNAP_CANDIDATE& candidate = *ranked[i].candidate;
782
783 if( candidate.priority != SNAP_PRIORITY_TIER::ANGLE )
784 continue;
785
786 SNAP_RESULT trialResult;
787
788 if( trialCandidate( candidate, trialResult ) && !acceptedAngle )
789 {
790 acceptedAngle = i;
791 acceptedAngleResult = std::move( trialResult );
792 }
793
794 processed[i] = true;
795 }
796
797 if( acceptedAngle )
798 {
799 acceptedCandidates.push_back( *ranked[*acceptedAngle].candidate );
800 result = std::move( acceptedAngleResult );
801 }
802
803 for( size_t i = 0; i < ranked.size() && result.remainingDof > 0; ++i )
804 {
805 if( !processed[i] && ranked[i].candidate->subtype == SNAP_CANDIDATE_SUBTYPE::INTRINSIC_ANCHOR )
806 {
807 acceptCandidate( i );
808 break;
809 }
810 }
811
813 {
814 for( size_t i = 0; i < ranked.size() && result.remainingDof > 0; ++i )
815 {
816 if( !processed[i] && ranked[i].candidate->id == *m_retainedCandidate )
817 {
818 acceptCandidate( i );
819 break;
820 }
821 }
822 }
823
824 for( size_t i = 0; i < ranked.size(); ++i )
825 {
826 const SNAP_CANDIDATE& candidate = *ranked[i].candidate;
827
828 if( processed[i] )
829 continue;
830
831 if( result.remainingDof == 0 )
832 break;
833
834 if( m_clock() - start >= m_deadline )
835 {
836 budgetExhausted = true;
837 break;
838 }
839
840 const std::optional<bool> axis = layoutAxis( candidate );
841
842 if( axis
843 && std::any_of( acceptedCandidates.begin(), acceptedCandidates.end(),
844 [&]( const SNAP_CANDIDATE& aAccepted )
845 {
846 return layoutAxis( aAccepted ) == axis;
847 } ) )
848 {
849 processed[i] = true;
850 continue;
851 }
852
853 acceptCandidate( i );
854
855 if( m_clock() - start >= m_deadline && i + 1 != ranked.size() )
856 {
857 budgetExhausted = true;
858 break;
859 }
860 }
861
862 for( const SNAP_CANDIDATE& candidate : acceptedCandidates )
863 {
864 result.accepted.push_back( candidate.id );
865
866 result.guides.insert( result.guides.end(), candidate.guides.begin(), candidate.guides.end() );
867 }
868
869 if( budgetExhausted )
871
872 if( m_traceCallback )
873 {
874 std::ostringstream stream;
875 stream << "result status=" << statusName( result.status ) << " position=(" << result.position.x << ','
876 << result.position.y << ") accepted=[";
877
878 for( size_t i = 0; i < result.accepted.size(); ++i )
879 {
880 if( i )
881 stream << ',';
882
883 stream << stableIdString( result.accepted[i] );
884 }
885
886 stream << ']';
887 trace( stream.str() );
888 }
889
890 return result;
891}
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
Definition line.h:32
const SEG & GetContainedSeg() const
Gets the (one of the infinite number of) segments that the line passes through.
Definition line.h:45
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void addData(const uint8_t *data, size_t length)
Definition mmh3_hash.h:69
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
bool hasHysteresis(const SNAP_STABLE_ID &aId) const
True when a candidate should earn the ranking hysteresis (retained or sticky).
SNAP_RESULT Resolve(const SNAP_SOURCE_CONTEXT &aContext) const
double m_rankingHysteresis
FEASIBILITY_CALLBACK m_feasibilityCallback
std::function< SNAP_RESULT(const SNAP_SOURCE_CONTEXT &, const std::vector< SNAP_CANDIDATE > &)> FEASIBILITY_CALLBACK
std::function< void(const std::string &)> TRACE_CALLBACK
void SetFeasibilityCallback(FEASIBILITY_CALLBACK aCallback)
void SetStickyCandidates(std::vector< SNAP_STABLE_ID > aIds)
Bias the ranking toward candidates accepted on a previous resolve so the chosen snap stays put until ...
TRACE_CALLBACK m_traceCallback
void SetDeadline(CLOCK::duration aDeadline)
void SetClock(CLOCK_CALLBACK aClock)
std::optional< SNAP_STABLE_ID > m_retainedCandidate
CLOCK::duration m_deadline
void SetTraceCallback(TRACE_CALLBACK aCallback)
std::vector< SNAP_CANDIDATE > m_candidates
void AddCandidate(SNAP_CANDIDATE aCandidate)
void SetRankingHysteresis(double aNormalizedResidual)
Set how strongly a candidate with hysteresis is favoured, as a fraction of the snap radius.
CLOCK_CALLBACK m_clock
std::function< CLOCK::time_point()> CLOCK_CALLBACK
void SetRetainedCandidate(std::optional< SNAP_STABLE_ID > aId)
std::vector< SNAP_STABLE_ID > m_stickyCandidates
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, BOX2I > INTERSECTABLE_GEOM
A variant type that can hold any of the supported geometry types for intersection calculations.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
double snapManifoldDistance(const INTERSECTABLE_GEOM &aGeometry, const VECTOR2I &aPoint)
Distance from a point to the nearest point of a snap manifold shape.
SNAP_STABLE_ID MakeIntersectionSnapId(const SNAP_STABLE_ID &aFirst, const SNAP_STABLE_ID &aSecond, int aSolutionBranch)
SNAP_STABLE_ID MakeDerivedSnapId(SNAP_ID_KIND aKind, const SNAP_STABLE_ID &aSource, int aFeatureIndex, int aSolutionBranch)
std::ostream & operator<<(std::ostream &aStream, SNAP_RESULT_STATUS aStatus)
SNAP_STABLE_ID MakeCompositeSnapId(SNAP_ID_KIND aKind, const std::vector< SNAP_TARGET_ID > &aTargets, int aFeatureIndex)
SNAP_STABLE_ID MakePointSnapId(SNAP_ID_KIND aKind, const VECTOR2I &aPoint, int aFeatureIndex)
SNAP_REFERENCE_KIND
SNAP_RELATION
SNAP_ID_KIND
SNAP_PRIORITY_TIER
SNAP_CANDIDATE_SUBTYPE
std::array< uint8_t, 16 > SNAP_TARGET_ID
SNAP_RESULT_STATUS
A storage class for 128-bit hash value.
Definition hash_128.h:32
uint8_t Value8[16]
Definition hash_128.h:55
A visitor that visits INTERSECTABLE_GEOM variant objects with another (which is held as state: m_othe...
SNAP_PRIORITY_TIER priority
static SNAP_CANDIDATE Point(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aPoint, double aResidual)
double normalizedScreenResidual
SNAP_STABLE_ID id
static SNAP_CANDIDATE Line(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aOrigin, const VECTOR2D &aDirection, double aResidual)
static SNAP_CANDIDATE AxisY(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
static SNAP_CANDIDATE AxisX(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, int aCoordinate, double aResidual)
SNAP_RELATION relation
SNAP_CANDIDATE_SUBTYPE subtype
SNAP_REFERENCE_KIND kind
std::vector< SNAP_STABLE_ID > accepted
bool Accepted(const SNAP_STABLE_ID &aId) const
SNAP_REFERENCE_PREFERENCE referencePreference
SNAP_ID_KIND kind
SNAP_TARGET_ID target
bool operator<(const SNAP_STABLE_ID &aOther) const
VECTOR2I center
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682