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