40 [&](
const auto& aShape ) ->
double
42 using SHAPE_TYPE = std::decay_t<
decltype( aShape )>;
44 if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> || std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
46 return aShape.NearestPoint( aPoint ).SquaredDistance( aPoint );
48 else if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
50 return aShape.SquaredDistance( aPoint );
64 if( aExtensionActive )
66 if(
const SEG* segment = std::get_if<SEG>( &aPath.
geometry ) )
67 return LINE( *segment );
70 return LINE( ray->GetContainedSeg() );
79 if(
const SEG* segment = std::get_if<SEG>( &aGeometry ) )
80 return segment->B - segment->A;
82 if(
const LINE* line = std::get_if<LINE>( &aGeometry ) )
84 const SEG& segment = line->GetContainedSeg();
85 return segment.
B - segment.
A;
88 if(
const HALF_LINE* ray = std::get_if<HALF_LINE>( &aGeometry ) )
90 const SEG& segment = ray->GetContainedSeg();
91 return segment.
B - segment.
A;
100 std::optional<VECTOR2I> first = linearDirection( aFirst );
101 std::optional<VECTOR2I> second = linearDirection( aSecond );
103 if( !first || !second )
106 return first->Cross( *second ) == 0;
111 bool aExtensionActive )
116 [&](
const auto& aGeometry )
118 using SHAPE_TYPE = std::decay_t<
decltype( aGeometry )>;
120 if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
126 aGeometry.A,
VECTOR2D( aGeometry.B - aGeometry.A ),
130 candidate.
finite = !aExtensionActive;
136 else if constexpr( std::is_same_v<SHAPE_TYPE, LINE> )
138 const SEG& segment = aGeometry.GetContainedSeg();
149 if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> )
151 point = aGeometry.NearestPoint( aSource );
154 aGeometry.GetStart(),
VECTOR2D( aGeometry.GetContainedPoint() - aGeometry.GetStart() ),
155 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
162 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
163 point = aGeometry.NearestPoint( aSource );
164 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
165 point = aGeometry.NearestPoint( aSource );
171 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
175 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
177 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
186 else if( aExtensionActive )
195#if defined( __GNUC__ )
196#define SNAP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
197#elif defined( _MSC_VER )
198#define SNAP_ALWAYS_INLINE __forceinline
200#define SNAP_ALWAYS_INLINE inline
204struct AXIS_DESCRIPTOR
206 static constexpr size_t index = IsX ? 0 : 1;
216 return IsX ? aPoint.
y : aPoint.
x;
233 return { low( aBox ), coordinate( aBox.
Centre() ), high( aBox ) };
238 return IsX ?
VECTOR2I( aCoordinate, aPerpendicular ) :
VECTOR2I( aPerpendicular, aCoordinate );
261template <
typename Callback>
264 aCallback.template operator()<AXIS_DESCRIPTOR<true>>();
265 aCallback.template operator()<AXIS_DESCRIPTOR<false>>();
268#undef SNAP_ALWAYS_INLINE
274 m_paths.emplace_back( std::move( aPath ) );
280 m_bounds.emplace_back( std::move( aBounds ) );
347 double distanceSquared;
350 constexpr size_t maxCandidatePaths = 64;
351 constexpr size_t maxIntersectionPaths = 12;
352 std::vector<ELIGIBLE_PATH> paths;
353 std::vector<SNAP_CANDIDATE>
result;
354 paths.reserve( maxCandidatePaths );
356 auto betterPath = [](
const ELIGIBLE_PATH& aLeft,
const ELIGIBLE_PATH& aRight )
358 return std::forward_as_tuple( !aLeft.path->intrinsic, aLeft.distanceSquared, aLeft.path->id )
359 < std::forward_as_tuple( !aRight.path->intrinsic, aRight.distanceSquared, aRight.path->id );
361 const double radiusSquared =
static_cast<double>( aRadius ) * aRadius;
370 bool extension =
path.activeExtension || expand;
371 std::optional<INTERSECTABLE_GEOM> extended;
376 extended = extendedGeometry(
path,
true );
377 geometry = &*extended;
380 double distanceSquared = squaredDistanceTo( *geometry, aContext.
sourcePoint );
382 if( distanceSquared > radiusSquared )
385 ELIGIBLE_PATH candidate{ &
path, extension, expand, distanceSquared };
387 if( paths.size() < maxCandidatePaths )
389 paths.push_back( candidate );
390 std::push_heap( paths.begin(), paths.end(), betterPath );
392 else if( betterPath( candidate, paths.front() ) )
394 std::pop_heap( paths.begin(), paths.end(), betterPath );
395 paths.back() = candidate;
396 std::push_heap( paths.begin(), paths.end(), betterPath );
400 std::sort_heap( paths.begin(), paths.end(), betterPath );
401 size_t candidatePathCount = paths.size();
403 for(
size_t i = 0; i < candidatePathCount; ++i )
405 result.push_back( pathCandidate( *paths[i].
path, aContext.
sourcePoint, aRadius, paths[i].extension ) );
408 size_t intersectionPathCount = std::min( candidatePathCount, maxIntersectionPaths );
409 std::vector<INTERSECTABLE_GEOM> intersectionGeometry;
410 intersectionGeometry.reserve( intersectionPathCount );
412 for(
size_t i = 0; i < intersectionPathCount; ++i )
414 intersectionGeometry.push_back( extendedGeometry( *paths[i].
path, paths[i].expand ) );
417 std::vector<VECTOR2I> intersections;
419 for(
size_t first = 0; first < intersectionPathCount; ++first )
421 for(
size_t second = first + 1; second < intersectionPathCount; ++second )
423 if( parallelLinearGeometry( intersectionGeometry[first], intersectionGeometry[second] ) )
428 intersections.clear();
430 intersectionGeometry[first] );
432 std::sort( intersections.begin(), intersections.end(),
435 return std::tie( aLeft.x, aLeft.y ) < std::tie( aRight.x, aRight.y );
437 intersections.erase( std::unique( intersections.begin(), intersections.end() ), intersections.end() );
439 for(
size_t branch = 0; branch < intersections.size(); ++branch )
441 const VECTOR2I& point = intersections[branch];
448 static_cast<int>( branch ) ),
451 result.push_back( std::move( candidate ) );
461 int aRadius,
bool aTangentEnabled,
462 bool aNormalEnabled )
const
464 std::vector<SNAP_CANDIDATE>
result;
475 const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &
path.geometry );
484 double distanceSquared =
delta.SquaredEuclideanNorm();
486 if( distanceSquared == 0.0 )
498 path.id,
path.id.featureIndex, aBranch );
504 result.push_back( std::move( candidate ) );
507 if( aTangentEnabled && distanceSquared >=
radius *
radius )
510 double base = radiusSquared / distanceSquared;
511 double perpendicular =
512 radius * std::sqrt( std::max( 0.0, distanceSquared - radiusSquared ) ) / distanceSquared;
520 double length = std::sqrt( distanceSquared );
541 int targetCoordinate;
545 constexpr size_t MAX_CANDIDATES_PER_AXIS = 64;
546 const auto proposalKey = [](
const PROPOSAL& aProposal )
548 return std::forward_as_tuple( aProposal.affinity, aProposal.displacement, aProposal.target->id,
549 aProposal.sourceFeature, aProposal.targetFeature );
552 const auto compareProposal = [proposalKey](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
554 return proposalKey( aLeft ) < proposalKey( aRight );
557 using PROPOSAL_QUEUE = std::priority_queue<PROPOSAL, std::vector<PROPOSAL>,
decltype( compareProposal )>;
559 std::array<PROPOSAL_QUEUE, 2> proposalQueues{ PROPOSAL_QUEUE( compareProposal ),
560 PROPOSAL_QUEUE( compareProposal ) };
561 std::vector<SNAP_CANDIDATE>
result;
571 const auto retain = [&]( PROPOSAL_QUEUE& aQueue, PROPOSAL aProposal )
573 if( aQueue.size() < MAX_CANDIDATES_PER_AXIS )
575 aQueue.push( std::move( aProposal ) );
577 else if( proposalKey( aProposal ) < proposalKey( aQueue.top() ) )
580 aQueue.push( std::move( aProposal ) );
584 const auto boundsAffinity = [&]<
typename Axis>(
int aSourceFeature,
int aTargetFeature )
593 return aSourceFeature == preferred && aTargetFeature == preferred ? 0 : 1;
595 std::array<std::array<int, 3>, 2> movingFeatures;
599 movingFeatures[Axis::index] = Axis::features( movingBounds );
610 std::array<int, 3> targetFeatures = Axis::features( target.bounds );
612 for(
int sourceFeature = 0; sourceFeature < 3; ++sourceFeature )
614 for(
int targetFeature = 0; targetFeature < 3; ++targetFeature )
616 int resolved = Axis::coordinate( aContext.
sourcePoint ) + targetFeatures[targetFeature]
617 - movingFeatures[Axis::index][sourceFeature];
620 if( displacement <= aRadius )
622 retain( proposalQueues[Axis::index],
623 { &target, sourceFeature, targetFeature,
624 boundsAffinity.template operator()<Axis>( sourceFeature, targetFeature ),
625 resolved, targetFeatures[targetFeature], displacement } );
632 const auto ordered = [&]( PROPOSAL_QUEUE& aQueue )
634 std::vector<PROPOSAL> proposals;
635 proposals.reserve( aQueue.size() );
637 while( !aQueue.empty() )
639 proposals.push_back( aQueue.top() );
643 std::sort( proposals.begin(), proposals.end(),
644 [&](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
646 return proposalKey( aLeft ) < proposalKey( aRight );
651 std::array<std::vector<SNAP_CANDIDATE>, 2>
candidates;
655 std::vector<PROPOSAL> retained = ordered( proposalQueues[Axis::index] );
656 std::vector<SNAP_CANDIDATE>& axisCandidates =
candidates[Axis::index];
657 axisCandidates.reserve( retained.size() );
659 for(
const PROPOSAL& proposal : retained )
662 proposal.sourceFeature * 3 + proposal.targetFeature );
664 Axis::candidate( std::move(
id ), proposal.coordinate,
665 proposal.displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
668 candidate.
guides.push_back(
669 { Axis::point( proposal.targetCoordinate,
670 std::min( Axis::perpendicularLow( movingBounds ),
671 Axis::perpendicularLow( proposal.target->bounds ) ) ),
672 Axis::point( proposal.targetCoordinate,
673 std::max( Axis::perpendicularHigh( movingBounds ),
674 Axis::perpendicularHigh( proposal.target->bounds ) ) ) } );
675 axisCandidates.push_back( std::move( candidate ) );
689 int coordinate = Axis::coordinate( point.position );
692 if( displacement > aRadius )
697 Axis::candidate( std::move(
id ), coordinate,
698 displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
700 candidate.
guides.push_back(
701 { Axis::point( coordinate,
702 std::min( Axis::perpendicularLow( movingBounds ),
703 Axis::perpendicularCoordinate( point.position ) ) ),
704 Axis::point( coordinate,
705 std::max( Axis::perpendicularHigh( movingBounds ),
706 Axis::perpendicularCoordinate( point.position ) ) ) } );
707 candidates[Axis::index].push_back( std::move( candidate ) );
714 return std::forward_as_tuple( aCandidate.referenceAffinity, aCandidate.normalizedScreenResidual,
717 const auto retainBest = [&]( std::vector<SNAP_CANDIDATE>& aCandidates )
719 std::sort( aCandidates.begin(), aCandidates.end(),
722 return candidateKey( aLeft ) < candidateKey( aRight );
725 if( aCandidates.size() > MAX_CANDIDATES_PER_AXIS )
726 aCandidates.resize( MAX_CANDIDATES_PER_AXIS );
741 constexpr size_t MAX_CANDIDATES = 128;
742 std::vector<SNAP_CANDIDATE>
result;
755 < std::forward_as_tuple( aRight.normalizedScreenResidual, aRight.id );
760 if(
result.size() < MAX_CANDIDATES )
762 result.push_back( std::move( aCandidate ) );
763 std::push_heap(
result.begin(),
result.end(), betterCandidate );
765 else if( betterCandidate( aCandidate,
result.front() ) )
767 std::pop_heap(
result.begin(),
result.end(), betterCandidate );
768 result.back() = std::move( aCandidate );
769 std::push_heap(
result.begin(),
result.end(), betterCandidate );
773 std::array<std::vector<const SNAP_OBJECT_BOUNDS*>, 2> aligned;
775 const auto overlaps = [](
int aFirstStart,
int aFirstEnd,
int aSecondStart,
int aSecondEnd )
777 return aFirstStart < aSecondEnd && aSecondStart < aFirstEnd;
788 if( overlaps( Axis::perpendicularLow( bounds.bounds ), Axis::perpendicularHigh( bounds.bounds ),
789 Axis::perpendicularLow( movingBounds ), Axis::perpendicularHigh( movingBounds ) ) )
791 aligned[Axis::index].push_back( &bounds );
799 std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
800 std::sort( axisBounds.begin(), axisBounds.end(),
803 return std::forward_as_tuple( Axis::low( aFirst->bounds ), aFirst->id )
804 < std::forward_as_tuple( Axis::low( aSecond->bounds ), aSecond->id );
813 if( residual > aRadius )
816 BOX2I resolvedBounds = movingBounds;
817 resolvedBounds.
Offset( Axis::offset( aResolvedSource - Axis::coordinate( aContext.
sourcePoint ) ) );
820 SNAP_CANDIDATE candidate = Axis::candidate( std::move( aId ), aResolvedSource,
821 residual /
static_cast<double>( std::max( 1, aRadius ) ) );
823 const int perpendicular =
824 std::max( Axis::perpendicularHigh( aFirst.bounds ), Axis::perpendicularHigh( aSecond.bounds ) );
825 const auto addGuide = [&](
int aStart,
int aEnd )
827 candidate.
guides.push_back( { Axis::point( aStart, perpendicular ), Axis::point( aEnd, perpendicular ),
833 addGuide( Axis::high( resolvedBounds ), Axis::low( aFirst.bounds ) );
834 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
838 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
839 addGuide( Axis::high( aSecond.bounds ), Axis::low( resolvedBounds ) );
843 addGuide( Axis::high( aFirst.bounds ), Axis::low( resolvedBounds ) );
844 addGuide( Axis::high( resolvedBounds ), Axis::low( aSecond.bounds ) );
847 retainCandidate( std::move( candidate ) );
853 const std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
854 int sourceOffset = Axis::coordinate( aContext.
sourcePoint ) - Axis::coordinate( movingBounds.
Centre() );
856 for(
size_t i = 1; i < axisBounds.size(); ++i )
858 if( axisBounds[i - 1]->parent != axisBounds[i]->parent )
861 int available = Axis::low( axisBounds[i]->bounds ) - Axis::high( axisBounds[i - 1]->bounds );
868 if( available >= Axis::size( movingBounds ) )
871 ( Axis::high( axisBounds[i - 1]->bounds ) + Axis::low( axisBounds[i]->bounds ) )
873 addCandidate.template operator()<Axis>( id, Axis::equalGapKind,
center + sourceOffset,
874 *axisBounds[i - 1], *axisBounds[i] );
877 if( i + 1 == axisBounds.size()
878 || Axis::high( axisBounds[i]->bounds ) + available + Axis::size( movingBounds )
879 <= Axis::low( axisBounds[i + 1]->bounds ) )
881 int low = Axis::high( axisBounds[i]->bounds ) + available;
884 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
886 - Axis::low( movingBounds ),
887 *axisBounds[i - 1], *axisBounds[i] );
891 || Axis::high( axisBounds[i - 2]->bounds ) + available + Axis::size( movingBounds )
892 <= Axis::low( axisBounds[i - 1]->bounds ) )
894 int high = Axis::low( axisBounds[i - 1]->bounds ) - available;
897 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
899 - Axis::high( movingBounds ),
900 *axisBounds[i - 1], *axisBounds[i] );
905 std::sort_heap(
result.begin(),
result.end(), betterCandidate );
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
constexpr size_type GetWidth() const
constexpr Vec Centre() const
constexpr size_type GetHeight() const
constexpr coord_type GetLeft() const
constexpr coord_type GetRight() const
constexpr coord_type GetTop() const
constexpr void Offset(coord_type dx, coord_type dy)
constexpr coord_type GetBottom() const
Represent basic circle geometry with utility geometry functions.
bool Collide(const SEG &aSeg, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the segment aSeg than aClearance,...
const VECTOR2I & GetCenter() const
std::vector< SNAP_STABLE_ID > m_activeExtensions
std::vector< SNAP_CANDIDATE > CollectObjectGeometry(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
bool eligible(const SNAP_SOURCE_CONTEXT &aContext, const SNAP_STABLE_ID &aId) const
std::vector< SNAP_OBJECT_BOUNDS > m_bounds
std::vector< SNAP_CANDIDATE > CollectEqualSpacing(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
void AddBounds(SNAP_OBJECT_BOUNDS aBounds)
std::vector< SNAP_ALIGNMENT_POINT > m_alignmentPoints
void ActivateExtension(const SNAP_STABLE_ID &aId)
std::vector< SNAP_OBJECT_PATH > m_paths
std::vector< SNAP_CANDIDATE > CollectTangentNormal(const SNAP_SOURCE_CONTEXT &aContext, int aRadius, bool aTangentEnabled, bool aNormalEnabled) const
void AddPath(SNAP_OBJECT_PATH aPath)
void AddAlignmentPoint(SNAP_ALIGNMENT_POINT aPoint)
std::vector< SNAP_CANDIDATE > CollectAlignment(const SNAP_SOURCE_CONTEXT &aContext, int aRadius) const
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
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)
VECTOR2I GetNearestPoint(const NEARABLE_GEOM &aGeom, const VECTOR2I &aPt)
Get the nearest point on a geometry to a given point.
std::variant< LINE, HALF_LINE, SEG, CIRCLE, SHAPE_ARC, SHAPE_ELLIPSE, BOX2I, VECTOR2I > NEARABLE_GEOM
A variant type that can hold any of the supported geometry types for nearest point calculations.
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
SHAPE_TYPE
Lists all supported shapes.
#define SNAP_ALWAYS_INLINE
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=0, int aSolutionBranch=0)
A visitor that visits INTERSECTABLE_GEOM variant objects with another (which is held as state: m_othe...
A named point a parent object offers for alignment, such as a pad center or a pin end.
std::optional< SNAP_TARGET_ID > parent
static SNAP_CANDIDATE Point(SNAP_STABLE_ID aId, SNAP_PRIORITY_TIER aPriority, SNAP_CANDIDATE_SUBTYPE aSubtype, const VECTOR2I &aPoint, double aResidual)
std::optional< INTERSECTABLE_GEOM > manifold
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)
std::vector< SNAP_GUIDE > guides
SNAP_CANDIDATE_SUBTYPE subtype
std::optional< SNAP_TARGET_ID > parent
INTERSECTABLE_GEOM geometry
std::optional< VECTOR2I > stationarySourceLeg
std::vector< SNAP_STABLE_ID > movingFeatures
std::optional< BOX2I > movingBounds
std::optional< VECTOR2I > movingReferencePoint
std::optional< SNAP_STABLE_ID > movingItem
SNAP_REFERENCE_PREFERENCE referencePreference
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
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