38constexpr double LINE_TOLERANCE_IU = 2.0;
39constexpr double RANK_EPSILON = 1e-12;
98 std::ostringstream stream;
99 stream << static_cast<int>( aId.
kind ) <<
':';
101 for( uint8_t
byte : aId.
target )
102 stream << std::hex << std::setfill( '0' ) << std::setw( 2 ) << static_cast<int>(
byte );
113 std::copy( std::begin( digest.
Value8 ), std::end( digest.
Value8 ), bytes.begin() );
122 hash.add(
static_cast<int32_t
>( aId.
kind ) );
125 return hashTarget( hash );
132 hash.addData( aFirst.data(), aFirst.size() );
133 hash.addData( aSecond.data(), aSecond.size() );
134 return hashTarget( hash );
159 [&](
const auto& aShape )
161 return aShape.NearestPoint( aPoint );
170 std::vector<VECTOR2I>
result;
172 const LINE* line = std::get_if<LINE>( &aSecond );
176 circle = std::get_if<CIRCLE>( &aSecond );
177 line = std::get_if<LINE>( &aFirst );
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;
193 if( perpendicularSquared <= radiusSquared )
195 double offset = std::sqrt( std::max( 0.0, radiusSquared - perpendicularSquared ) / divisor );
196 VECTOR2D first = projection + direction * offset;
197 VECTOR2D second = projection - direction * offset;
210 return std::tuple( aLeft.SquaredDistance( aSource ), aLeft.x, aLeft.y )
211 < std::tuple( aRight.SquaredDistance( aSource ), aRight.x, aRight.y );
218LINE equationLine(
const EQUATION& aEquation )
223 origin =
VECTOR2D( aEquation.c / aEquation.a, 0.0 );
225 origin =
VECTOR2D( 0.0, aEquation.c / aEquation.b );
227 VECTOR2D direction( aEquation.b, -aEquation.a );
228 double length = direction.EuclideanNorm();
229 direction = direction * ( 1000000.0 / length );
233 return LINE( integerOrigin, integerEnd );
257std::vector<EQUATION> equations(
const std::vector<SNAP_CANDIDATE>& aCandidates )
259 std::vector<EQUATION>
result;
263 switch( candidate.relation )
268 result.push_back( { 1.0, 0.0, candidate.origin.x,
true } );
269 result.push_back( { 0.0, 1.0, candidate.origin.y,
true } );
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 } );
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 } );
306 int& aRemainingDof, std::vector<double>& aResiduals )
308 std::vector<EQUATION> constraints = equations( aCandidates );
309 std::vector<const INTERSECTABLE_GEOM*> nonlinear;
310 std::optional<EQUATION> first;
311 std::optional<EQUATION> second;
315 if( candidate.manifold
316 && ( std::holds_alternative<CIRCLE>( *candidate.manifold )
317 || std::holds_alternative<SHAPE_ARC>( *candidate.manifold ) ) )
319 nonlinear.push_back( &*candidate.manifold );
323 for(
const EQUATION& equation : constraints )
325 double norm = std::hypot( equation.a, equation.b );
327 if( norm <= RANK_EPSILON )
336 double determinant = first->a * equation.b - equation.a * first->b;
338 if(
std::abs( determinant ) > RANK_EPSILON )
348 if( first && second )
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;
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;
368 if( !second && !nonlinear.empty() )
370 std::vector<VECTOR2I> points;
375 points = manifoldIntersections( line, *nonlinear.front(), aContext.
sourcePoint );
377 else if( nonlinear.size() >= 2 )
379 points = manifoldIntersections( *nonlinear[0], *nonlinear[1], aContext.
sourcePoint );
383 points.push_back( nearestOnManifold( *nonlinear.front(), aContext.
sourcePoint ) );
389 x = points.front().x;
390 y = points.front().y;
391 aRemainingDof = first || nonlinear.size() >= 2 ? 0 : 1;
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() )
406 for(
const EQUATION& equation : constraints )
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 );
414 if( residual != 0.0 )
417 else if( residual > LINE_TOLERANCE_IU )
425 if( candidate.manifold &&
snapManifoldDistance( *candidate.manifold, aPosition ) > LINE_TOLERANCE_IU )
430 if( !candidate.finite )
433 VECTOR2D offset( aPosition.
x - candidate.origin.x, aPosition.
y - candidate.origin.y );
434 double divisor = candidate.direction.SquaredEuclideanNorm();
436 if( divisor <= RANK_EPSILON )
439 double parameter = offset.Dot( candidate.direction ) / divisor;
441 if( parameter < candidate.domainStart || parameter > candidate.domainEnd )
452 return aStream << statusName( aStatus );
464 int aSolutionBranch )
466 return { aKind, idFingerprint( aSource ), aFeatureIndex, aSolutionBranch };
471 int aSolutionBranch )
476 if( *second < *first )
477 std::swap( first, second );
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 };
497 std::vector<SNAP_TARGET_ID> targets = aTargets;
498 std::sort( targets.begin(), targets.end() );
501 hash.
add(
static_cast<int32_t
>( aKind ) );
502 hash.
add(
static_cast<uint32_t
>( targets.size() ) );
505 hash.
addData( target.data(), target.size() );
507 hash.
add( aFeatureIndex );
508 return { aKind, hashTarget( hash ), aFeatureIndex, 0 };
513 const VECTOR2I& aPoint,
double aResidual )
516 candidate.
id = std::move( aId );
531 candidate.
id = std::move( aId );
545 int aCoordinate,
double aResidual )
548 candidate.
id = std::move( aId );
561 int aCoordinate,
double aResidual )
564 candidate.
id = std::move( aId );
652 double effectiveResidual;
655 std::vector<RANKED> ranked;
662 { &candidate, subtypeRank( candidate.subtype ), hysteresis,
663 std::max( 0.0, candidate.normalizedScreenResidual - ( hysteresis ?
m_rankingHysteresis : 0.0 ) ) } );
666 const auto trace = [&](
const std::string& aMessage )
672 std::sort( ranked.begin(), ranked.end(),
673 [](
const RANKED& aLeft,
const RANKED& aRight )
675 const SNAP_CANDIDATE& left = *aLeft.candidate;
676 const SNAP_CANDIDATE& right = *aRight.candidate;
678 return std::forward_as_tuple( left.priority, aLeft.subtypeRank, -left.consumedDof,
679 left.referenceAffinity, aLeft.effectiveResidual, !aLeft.hysteresis,
681 < std::forward_as_tuple( right.priority, aRight.subtypeRank, -right.consumedDof,
682 right.referenceAffinity, aRight.effectiveResidual,
683 !aRight.hysteresis, right.id );
688 std::ostringstream stream;
694 trace( stream.str() );
696 for(
size_t i = 0; i < ranked.size(); ++i )
701 stream <<
"rank index=" << i <<
" id=" << stableIdString( candidate.
id )
702 <<
" relation=" << relationName( candidate.
relation ) <<
" origin=(" << candidate.
origin.
x <<
','
704 <<
" affinity=" << candidate.
referenceAffinity <<
" sticky=" << ranked[i].hysteresis;
705 trace( stream.str() );
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 );
725 std::ostringstream stream;
726 stream <<
"result status=" << statusName(
result.status ) <<
" position=(" <<
result.position.x <<
','
727 <<
result.position.y <<
") accepted=[]";
728 trace( stream.str() );
737 const size_t baseSize = acceptedCandidates.size();
738 acceptedCandidates.push_back( aCandidate );
748 accepted = solve( aContext, acceptedCandidates, aTrialResult.position, aTrialResult.remainingDof,
749 aTrialResult.quantizedResiduals );
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() );
762 acceptedCandidates.pop_back();
766 const auto acceptCandidate = [&](
size_t aIndex )
771 if( trialCandidate( candidate, trialResult ) )
773 acceptedCandidates.push_back( candidate );
774 result = std::move( trialResult );
777 processed[aIndex] =
true;
780 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
783 acceptCandidate( i );
786 std::optional<size_t> acceptedAngle;
789 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
798 if( trialCandidate( candidate, trialResult ) && !acceptedAngle )
801 acceptedAngleResult = std::move( trialResult );
809 acceptedCandidates.push_back( *ranked[*acceptedAngle].candidate );
810 result = std::move( acceptedAngleResult );
813 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
817 acceptCandidate( i );
824 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
828 acceptCandidate( i );
834 for(
size_t i = 0; i < ranked.size(); ++i )
841 if(
result.remainingDof == 0 )
846 budgetExhausted =
true;
850 const std::optional<bool> axis = layoutAxis( candidate );
853 && std::any_of( acceptedCandidates.begin(), acceptedCandidates.end(),
856 return layoutAxis( aAccepted ) == axis;
863 acceptCandidate( i );
867 budgetExhausted =
true;
874 result.accepted.push_back( candidate.id );
876 result.guides.insert(
result.guides.end(), candidate.guides.begin(), candidate.guides.end() );
879 if( budgetExhausted )
884 std::ostringstream stream;
885 stream <<
"result status=" << statusName(
result.status ) <<
" position=(" <<
result.position.x <<
','
886 <<
result.position.y <<
") accepted=[";
888 for(
size_t i = 0; i <
result.accepted.size(); ++i )
893 stream << stableIdString(
result.accepted[i] );
897 trace( stream.str() );
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Represent basic circle geometry with utility geometry functions.
const SEG & GetContainedSeg() const
Gets the (one of the infinite number of) segments that the line passes through.
A streaming C++ equivalent for MurmurHash3_x64_128.
FORCE_INLINE void addData(const uint8_t *data, size_t length)
FORCE_INLINE void add(const std::string &input)
FORCE_INLINE HASH_128 digest()
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.
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)
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)
std::array< uint8_t, 16 > SNAP_TARGET_ID
A storage class for 128-bit hash value.
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
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_CANDIDATE_SUBTYPE subtype
std::vector< SNAP_STABLE_ID > accepted
bool Accepted(const SNAP_STABLE_ID &aId) const
SNAP_REFERENCE_PREFERENCE referencePreference
bool operator<(const SNAP_STABLE_ID &aOther) const
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
VECTOR2< double > VECTOR2D