37constexpr double LINE_TOLERANCE_IU = 2.0;
38constexpr double RANK_EPSILON = 1e-12;
97 std::ostringstream stream;
98 stream << static_cast<int>( aId.
kind ) <<
':';
100 for( uint8_t
byte : aId.
target )
101 stream << std::hex << std::setfill( '0' ) << std::setw( 2 ) << static_cast<int>(
byte );
112 std::copy( std::begin( digest.
Value8 ), std::end( digest.
Value8 ), bytes.begin() );
121 hash.add(
static_cast<int32_t
>( aId.
kind ) );
124 return hashTarget( hash );
131 hash.addData( aFirst.data(), aFirst.size() );
132 hash.addData( aSecond.data(), aSecond.size() );
133 return hashTarget( hash );
158 [&](
const auto& aShape )
160 return aShape.NearestPoint( aPoint );
169 std::vector<VECTOR2I>
result;
171 const LINE* line = std::get_if<LINE>( &aSecond );
175 circle = std::get_if<CIRCLE>( &aSecond );
176 line = std::get_if<LINE>( &aFirst );
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;
192 if( perpendicularSquared <= radiusSquared )
194 double offset = std::sqrt( std::max( 0.0, radiusSquared - perpendicularSquared ) / divisor );
195 VECTOR2D first = projection + direction * offset;
196 VECTOR2D second = projection - direction * offset;
209 return std::tuple( aLeft.SquaredDistance( aSource ), aLeft.x, aLeft.y )
210 < std::tuple( aRight.SquaredDistance( aSource ), aRight.x, aRight.y );
217LINE equationLine(
const EQUATION& aEquation )
222 origin =
VECTOR2D( aEquation.c / aEquation.a, 0.0 );
224 origin =
VECTOR2D( 0.0, aEquation.c / aEquation.b );
226 VECTOR2D direction( aEquation.b, -aEquation.a );
227 double length = direction.EuclideanNorm();
228 direction = direction * ( 1000000.0 / length );
232 return LINE( integerOrigin, integerEnd );
256std::vector<EQUATION> equations(
const std::vector<SNAP_CANDIDATE>& aCandidates )
258 std::vector<EQUATION>
result;
262 switch( candidate.relation )
267 result.push_back( { 1.0, 0.0, candidate.origin.x,
true } );
268 result.push_back( { 0.0, 1.0, candidate.origin.y,
true } );
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 } );
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 } );
305 int& aRemainingDof, std::vector<double>& aResiduals )
307 std::vector<EQUATION> constraints = equations( aCandidates );
308 std::vector<const INTERSECTABLE_GEOM*> nonlinear;
309 std::optional<EQUATION> first;
310 std::optional<EQUATION> second;
314 if( candidate.manifold
315 && ( std::holds_alternative<CIRCLE>( *candidate.manifold )
316 || std::holds_alternative<SHAPE_ARC>( *candidate.manifold ) ) )
318 nonlinear.push_back( &*candidate.manifold );
322 for(
const EQUATION& equation : constraints )
324 double norm = std::hypot( equation.a, equation.b );
326 if( norm <= RANK_EPSILON )
335 double determinant = first->a * equation.b - equation.a * first->b;
337 if(
std::abs( determinant ) > RANK_EPSILON )
347 if( first && second )
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;
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;
367 if( !second && !nonlinear.empty() )
369 std::vector<VECTOR2I> points;
374 points = manifoldIntersections( line, *nonlinear.front(), aContext.
sourcePoint );
376 else if( nonlinear.size() >= 2 )
378 points = manifoldIntersections( *nonlinear[0], *nonlinear[1], aContext.
sourcePoint );
382 points.push_back( nearestOnManifold( *nonlinear.front(), aContext.
sourcePoint ) );
388 x = points.front().x;
389 y = points.front().y;
390 aRemainingDof = first || nonlinear.size() >= 2 ? 0 : 1;
396 for(
const EQUATION& equation : constraints )
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 );
404 if( residual != 0.0 )
407 else if( residual > LINE_TOLERANCE_IU )
415 if( candidate.manifold &&
snapManifoldDistance( *candidate.manifold, aPosition ) > LINE_TOLERANCE_IU )
420 if( !candidate.finite )
423 VECTOR2D offset( aPosition.
x - candidate.origin.x, aPosition.
y - candidate.origin.y );
424 double divisor = candidate.direction.SquaredEuclideanNorm();
426 if( divisor <= RANK_EPSILON )
429 double parameter = offset.Dot( candidate.direction ) / divisor;
431 if( parameter < candidate.domainStart || parameter > candidate.domainEnd )
442 return aStream << statusName( aStatus );
454 int aSolutionBranch )
456 return { aKind, idFingerprint( aSource ), aFeatureIndex, aSolutionBranch };
461 int aSolutionBranch )
466 if( *second < *first )
467 std::swap( first, second );
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 };
487 std::vector<SNAP_TARGET_ID> targets = aTargets;
488 std::sort( targets.begin(), targets.end() );
491 hash.
add(
static_cast<int32_t
>( aKind ) );
492 hash.
add(
static_cast<uint32_t
>( targets.size() ) );
495 hash.
addData( target.data(), target.size() );
497 hash.
add( aFeatureIndex );
498 return { aKind, hashTarget( hash ), aFeatureIndex, 0 };
503 const VECTOR2I& aPoint,
double aResidual )
506 candidate.
id = std::move( aId );
521 candidate.
id = std::move( aId );
535 int aCoordinate,
double aResidual )
538 candidate.
id = std::move( aId );
551 int aCoordinate,
double aResidual )
554 candidate.
id = std::move( aId );
642 double effectiveResidual;
645 std::vector<RANKED> ranked;
652 { &candidate, subtypeRank( candidate.subtype ), hysteresis,
653 std::max( 0.0, candidate.normalizedScreenResidual - ( hysteresis ?
m_rankingHysteresis : 0.0 ) ) } );
656 const auto trace = [&](
const std::string& aMessage )
662 std::sort( ranked.begin(), ranked.end(),
663 [](
const RANKED& aLeft,
const RANKED& aRight )
665 const SNAP_CANDIDATE& left = *aLeft.candidate;
666 const SNAP_CANDIDATE& right = *aRight.candidate;
668 return std::forward_as_tuple( left.priority, aLeft.subtypeRank, -left.consumedDof,
669 left.referenceAffinity, aLeft.effectiveResidual, !aLeft.hysteresis,
671 < std::forward_as_tuple( right.priority, aRight.subtypeRank, -right.consumedDof,
672 right.referenceAffinity, aRight.effectiveResidual,
673 !aRight.hysteresis, right.id );
678 std::ostringstream stream;
684 trace( stream.str() );
686 for(
size_t i = 0; i < ranked.size(); ++i )
691 stream <<
"rank index=" << i <<
" id=" << stableIdString( candidate.
id )
692 <<
" relation=" << relationName( candidate.
relation ) <<
" origin=(" << candidate.
origin.
x <<
','
694 <<
" affinity=" << candidate.
referenceAffinity <<
" sticky=" << ranked[i].hysteresis;
695 trace( stream.str() );
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 );
715 std::ostringstream stream;
716 stream <<
"result status=" << statusName(
result.status ) <<
" position=(" <<
result.position.x <<
','
717 <<
result.position.y <<
") accepted=[]";
718 trace( stream.str() );
727 const size_t baseSize = acceptedCandidates.size();
728 acceptedCandidates.push_back( aCandidate );
738 accepted = solve( aContext, acceptedCandidates, aTrialResult.position, aTrialResult.remainingDof,
739 aTrialResult.quantizedResiduals );
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() );
752 acceptedCandidates.pop_back();
756 const auto acceptCandidate = [&](
size_t aIndex )
761 if( trialCandidate( candidate, trialResult ) )
763 acceptedCandidates.push_back( candidate );
764 result = std::move( trialResult );
767 processed[aIndex] =
true;
770 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
773 acceptCandidate( i );
776 std::optional<size_t> acceptedAngle;
779 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
788 if( trialCandidate( candidate, trialResult ) && !acceptedAngle )
791 acceptedAngleResult = std::move( trialResult );
799 acceptedCandidates.push_back( *ranked[*acceptedAngle].candidate );
800 result = std::move( acceptedAngleResult );
803 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
807 acceptCandidate( i );
814 for(
size_t i = 0; i < ranked.size() &&
result.remainingDof > 0; ++i )
818 acceptCandidate( i );
824 for(
size_t i = 0; i < ranked.size(); ++i )
831 if(
result.remainingDof == 0 )
836 budgetExhausted =
true;
840 const std::optional<bool> axis = layoutAxis( candidate );
843 && std::any_of( acceptedCandidates.begin(), acceptedCandidates.end(),
846 return layoutAxis( aAccepted ) == axis;
853 acceptCandidate( i );
857 budgetExhausted =
true;
864 result.accepted.push_back( candidate.id );
866 result.guides.insert(
result.guides.end(), candidate.guides.begin(), candidate.guides.end() );
869 if( budgetExhausted )
874 std::ostringstream stream;
875 stream <<
"result status=" << statusName(
result.status ) <<
" position=(" <<
result.position.x <<
','
876 <<
result.position.y <<
") accepted=[";
878 for(
size_t i = 0; i <
result.accepted.size(); ++i )
883 stream << stableIdString(
result.accepted[i] );
887 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, 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