38 [&](
const auto& aShape ) ->
double
40 using SHAPE_TYPE = std::decay_t<
decltype( aShape )>;
42 if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> || std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
44 return aShape.NearestPoint( aPoint ).SquaredDistance( aPoint );
46 else if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
48 return aShape.SquaredDistance( aPoint );
62 if( aExtensionActive )
64 if(
const SEG* segment = std::get_if<SEG>( &aPath.
geometry ) )
65 return LINE( *segment );
68 return LINE( ray->GetContainedSeg() );
77 if(
const SEG* segment = std::get_if<SEG>( &aGeometry ) )
78 return segment->B - segment->A;
80 if(
const LINE* line = std::get_if<LINE>( &aGeometry ) )
82 const SEG& segment = line->GetContainedSeg();
83 return segment.
B - segment.
A;
86 if(
const HALF_LINE* ray = std::get_if<HALF_LINE>( &aGeometry ) )
88 const SEG& segment = ray->GetContainedSeg();
89 return segment.
B - segment.
A;
98 std::optional<VECTOR2I> first = linearDirection( aFirst );
99 std::optional<VECTOR2I> second = linearDirection( aSecond );
101 if( !first || !second )
104 return first->Cross( *second ) == 0;
109 bool aExtensionActive )
114 [&](
const auto& aGeometry )
116 using SHAPE_TYPE = std::decay_t<
decltype( aGeometry )>;
118 if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
124 aGeometry.A,
VECTOR2D( aGeometry.B - aGeometry.A ),
128 candidate.
finite = !aExtensionActive;
134 else if constexpr( std::is_same_v<SHAPE_TYPE, LINE> )
136 const SEG& segment = aGeometry.GetContainedSeg();
147 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
148 point = aGeometry.NearestPoint( aSource );
149 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
150 point = aGeometry.NearestPoint( aSource );
151 else if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> )
153 point = aGeometry.NearestPoint( aSource );
156 aGeometry.GetStart(),
VECTOR2D( aGeometry.GetContainedPoint() - aGeometry.GetStart() ),
157 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
163 point = aGeometry.Centre();
167 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
171 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
173 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
181 else if( aExtensionActive )
190#if defined( __GNUC__ )
191#define SNAP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
192#elif defined( _MSC_VER )
193#define SNAP_ALWAYS_INLINE __forceinline
195#define SNAP_ALWAYS_INLINE inline
199struct AXIS_DESCRIPTOR
201 static constexpr size_t index = IsX ? 0 : 1;
211 return IsX ? aPoint.
y : aPoint.
x;
228 return { low( aBox ), coordinate( aBox.
Centre() ), high( aBox ) };
233 return IsX ?
VECTOR2I( aCoordinate, aPerpendicular ) :
VECTOR2I( aPerpendicular, aCoordinate );
256template <
typename Callback>
259 aCallback.template operator()<AXIS_DESCRIPTOR<true>>();
260 aCallback.template operator()<AXIS_DESCRIPTOR<false>>();
263#undef SNAP_ALWAYS_INLINE
269 m_paths.emplace_back( std::move( aPath ) );
275 m_bounds.emplace_back( std::move( aBounds ) );
342 double distanceSquared;
345 constexpr size_t maxCandidatePaths = 64;
346 constexpr size_t maxIntersectionPaths = 12;
347 std::vector<ELIGIBLE_PATH> paths;
348 std::vector<SNAP_CANDIDATE>
result;
349 paths.reserve( maxCandidatePaths );
351 auto betterPath = [](
const ELIGIBLE_PATH& aLeft,
const ELIGIBLE_PATH& aRight )
353 return std::forward_as_tuple( !aLeft.path->intrinsic, aLeft.distanceSquared, aLeft.path->id )
354 < std::forward_as_tuple( !aRight.path->intrinsic, aRight.distanceSquared, aRight.path->id );
356 const double radiusSquared =
static_cast<double>( aRadius ) * aRadius;
365 bool extension =
path.activeExtension || expand;
366 std::optional<INTERSECTABLE_GEOM> extended;
371 extended = extendedGeometry(
path,
true );
372 geometry = &*extended;
375 double distanceSquared = squaredDistanceTo( *geometry, aContext.
sourcePoint );
377 if( distanceSquared > radiusSquared )
380 ELIGIBLE_PATH candidate{ &
path, extension, expand, distanceSquared };
382 if( paths.size() < maxCandidatePaths )
384 paths.push_back( candidate );
385 std::push_heap( paths.begin(), paths.end(), betterPath );
387 else if( betterPath( candidate, paths.front() ) )
389 std::pop_heap( paths.begin(), paths.end(), betterPath );
390 paths.back() = candidate;
391 std::push_heap( paths.begin(), paths.end(), betterPath );
395 std::sort_heap( paths.begin(), paths.end(), betterPath );
396 size_t candidatePathCount = paths.size();
398 for(
size_t i = 0; i < candidatePathCount; ++i )
400 result.push_back( pathCandidate( *paths[i].
path, aContext.
sourcePoint, aRadius, paths[i].extension ) );
403 size_t intersectionPathCount = std::min( candidatePathCount, maxIntersectionPaths );
404 std::vector<INTERSECTABLE_GEOM> intersectionGeometry;
405 intersectionGeometry.reserve( intersectionPathCount );
407 for(
size_t i = 0; i < intersectionPathCount; ++i )
409 intersectionGeometry.push_back( extendedGeometry( *paths[i].
path, paths[i].expand ) );
412 std::vector<VECTOR2I> intersections;
414 for(
size_t first = 0; first < intersectionPathCount; ++first )
416 for(
size_t second = first + 1; second < intersectionPathCount; ++second )
418 if( parallelLinearGeometry( intersectionGeometry[first], intersectionGeometry[second] ) )
423 intersections.clear();
425 intersectionGeometry[first] );
427 std::sort( intersections.begin(), intersections.end(),
430 return std::tie( aLeft.x, aLeft.y ) < std::tie( aRight.x, aRight.y );
432 intersections.erase( std::unique( intersections.begin(), intersections.end() ), intersections.end() );
434 for(
size_t branch = 0; branch < intersections.size(); ++branch )
436 const VECTOR2I& point = intersections[branch];
443 static_cast<int>( branch ) ),
446 result.push_back( std::move( candidate ) );
456 int aRadius,
bool aTangentEnabled,
457 bool aNormalEnabled )
const
459 std::vector<SNAP_CANDIDATE>
result;
470 const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &
path.geometry );
479 double distanceSquared =
delta.SquaredEuclideanNorm();
481 if( distanceSquared == 0.0 )
493 path.id,
path.id.featureIndex, aBranch );
499 result.push_back( std::move( candidate ) );
502 if( aTangentEnabled && distanceSquared >=
radius *
radius )
505 double base = radiusSquared / distanceSquared;
506 double perpendicular =
507 radius * std::sqrt( std::max( 0.0, distanceSquared - radiusSquared ) ) / distanceSquared;
515 double length = std::sqrt( distanceSquared );
536 int targetCoordinate;
540 constexpr size_t MAX_CANDIDATES_PER_AXIS = 64;
541 const auto proposalKey = [](
const PROPOSAL& aProposal )
543 return std::forward_as_tuple( aProposal.affinity, aProposal.displacement, aProposal.target->id,
544 aProposal.sourceFeature, aProposal.targetFeature );
547 const auto compareProposal = [proposalKey](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
549 return proposalKey( aLeft ) < proposalKey( aRight );
552 using PROPOSAL_QUEUE = std::priority_queue<PROPOSAL, std::vector<PROPOSAL>,
decltype( compareProposal )>;
554 std::array<PROPOSAL_QUEUE, 2> proposalQueues{ PROPOSAL_QUEUE( compareProposal ),
555 PROPOSAL_QUEUE( compareProposal ) };
556 std::vector<SNAP_CANDIDATE>
result;
566 const auto retain = [&]( PROPOSAL_QUEUE& aQueue, PROPOSAL aProposal )
568 if( aQueue.size() < MAX_CANDIDATES_PER_AXIS )
570 aQueue.push( std::move( aProposal ) );
572 else if( proposalKey( aProposal ) < proposalKey( aQueue.top() ) )
575 aQueue.push( std::move( aProposal ) );
579 const auto boundsAffinity = [&]<
typename Axis>(
int aSourceFeature,
int aTargetFeature )
588 return aSourceFeature == preferred && aTargetFeature == preferred ? 0 : 1;
590 std::array<std::array<int, 3>, 2> movingFeatures;
594 movingFeatures[Axis::index] = Axis::features( movingBounds );
605 std::array<int, 3> targetFeatures = Axis::features( target.bounds );
607 for(
int sourceFeature = 0; sourceFeature < 3; ++sourceFeature )
609 for(
int targetFeature = 0; targetFeature < 3; ++targetFeature )
611 int resolved = Axis::coordinate( aContext.
sourcePoint ) + targetFeatures[targetFeature]
612 - movingFeatures[Axis::index][sourceFeature];
615 if( displacement <= aRadius )
617 retain( proposalQueues[Axis::index],
618 { &target, sourceFeature, targetFeature,
619 boundsAffinity.template operator()<Axis>( sourceFeature, targetFeature ),
620 resolved, targetFeatures[targetFeature], displacement } );
627 const auto ordered = [&]( PROPOSAL_QUEUE& aQueue )
629 std::vector<PROPOSAL> proposals;
630 proposals.reserve( aQueue.size() );
632 while( !aQueue.empty() )
634 proposals.push_back( aQueue.top() );
638 std::sort( proposals.begin(), proposals.end(),
639 [&](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
641 return proposalKey( aLeft ) < proposalKey( aRight );
646 std::array<std::vector<SNAP_CANDIDATE>, 2> candidates;
650 std::vector<PROPOSAL> retained = ordered( proposalQueues[Axis::index] );
651 std::vector<SNAP_CANDIDATE>& axisCandidates = candidates[Axis::index];
652 axisCandidates.reserve( retained.size() );
654 for(
const PROPOSAL& proposal : retained )
657 proposal.sourceFeature * 3 + proposal.targetFeature );
659 Axis::candidate( std::move(
id ), proposal.coordinate,
660 proposal.displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
663 candidate.
guides.push_back(
664 { Axis::point( proposal.targetCoordinate,
665 std::min( Axis::perpendicularLow( movingBounds ),
666 Axis::perpendicularLow( proposal.target->bounds ) ) ),
667 Axis::point( proposal.targetCoordinate,
668 std::max( Axis::perpendicularHigh( movingBounds ),
669 Axis::perpendicularHigh( proposal.target->bounds ) ) ) } );
670 axisCandidates.push_back( std::move( candidate ) );
684 int coordinate = Axis::coordinate( point.position );
687 if( displacement > aRadius )
692 Axis::candidate( std::move(
id ), coordinate,
693 displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
695 candidate.
guides.push_back(
696 { Axis::point( coordinate,
697 std::min( Axis::perpendicularLow( movingBounds ),
698 Axis::perpendicularCoordinate( point.position ) ) ),
699 Axis::point( coordinate,
700 std::max( Axis::perpendicularHigh( movingBounds ),
701 Axis::perpendicularCoordinate( point.position ) ) ) } );
702 candidates[Axis::index].push_back( std::move( candidate ) );
709 return std::forward_as_tuple( aCandidate.referenceAffinity, aCandidate.normalizedScreenResidual,
712 const auto retainBest = [&]( std::vector<SNAP_CANDIDATE>& aCandidates )
714 std::sort( aCandidates.begin(), aCandidates.end(),
717 return candidateKey( aLeft ) < candidateKey( aRight );
720 if( aCandidates.size() > MAX_CANDIDATES_PER_AXIS )
721 aCandidates.resize( MAX_CANDIDATES_PER_AXIS );
724 retainBest( candidates[0] );
725 retainBest( candidates[1] );
726 result.reserve( candidates[0].size() + candidates[1].size() );
727 std::move( candidates[0].begin(), candidates[0].
end(), std::back_inserter(
result ) );
728 std::move( candidates[1].begin(), candidates[1].
end(), std::back_inserter(
result ) );
736 constexpr size_t MAX_CANDIDATES = 128;
737 std::vector<SNAP_CANDIDATE>
result;
750 < std::forward_as_tuple( aRight.normalizedScreenResidual, aRight.id );
755 if(
result.size() < MAX_CANDIDATES )
757 result.push_back( std::move( aCandidate ) );
758 std::push_heap(
result.begin(),
result.end(), betterCandidate );
760 else if( betterCandidate( aCandidate,
result.front() ) )
762 std::pop_heap(
result.begin(),
result.end(), betterCandidate );
763 result.back() = std::move( aCandidate );
764 std::push_heap(
result.begin(),
result.end(), betterCandidate );
768 std::array<std::vector<const SNAP_OBJECT_BOUNDS*>, 2> aligned;
770 const auto overlaps = [](
int aFirstStart,
int aFirstEnd,
int aSecondStart,
int aSecondEnd )
772 return aFirstStart < aSecondEnd && aSecondStart < aFirstEnd;
783 if( overlaps( Axis::perpendicularLow( bounds.bounds ), Axis::perpendicularHigh( bounds.bounds ),
784 Axis::perpendicularLow( movingBounds ), Axis::perpendicularHigh( movingBounds ) ) )
786 aligned[Axis::index].push_back( &bounds );
794 std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
795 std::sort( axisBounds.begin(), axisBounds.end(),
798 return std::forward_as_tuple( Axis::low( aFirst->bounds ), aFirst->id )
799 < std::forward_as_tuple( Axis::low( aSecond->bounds ), aSecond->id );
808 if( residual > aRadius )
811 BOX2I resolvedBounds = movingBounds;
812 resolvedBounds.
Offset( Axis::offset( aResolvedSource - Axis::coordinate( aContext.
sourcePoint ) ) );
815 SNAP_CANDIDATE candidate = Axis::candidate( std::move( aId ), aResolvedSource,
816 residual /
static_cast<double>( std::max( 1, aRadius ) ) );
818 const int perpendicular =
819 std::max( Axis::perpendicularHigh( aFirst.bounds ), Axis::perpendicularHigh( aSecond.bounds ) );
820 const auto addGuide = [&](
int aStart,
int aEnd )
822 candidate.
guides.push_back( { Axis::point( aStart, perpendicular ), Axis::point( aEnd, perpendicular ),
828 addGuide( Axis::high( resolvedBounds ), Axis::low( aFirst.bounds ) );
829 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
833 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
834 addGuide( Axis::high( aSecond.bounds ), Axis::low( resolvedBounds ) );
838 addGuide( Axis::high( aFirst.bounds ), Axis::low( resolvedBounds ) );
839 addGuide( Axis::high( resolvedBounds ), Axis::low( aSecond.bounds ) );
842 retainCandidate( std::move( candidate ) );
848 const std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
849 int sourceOffset = Axis::coordinate( aContext.
sourcePoint ) - Axis::coordinate( movingBounds.
Centre() );
851 for(
size_t i = 1; i < axisBounds.size(); ++i )
853 if( axisBounds[i - 1]->parent != axisBounds[i]->parent )
856 int available = Axis::low( axisBounds[i]->bounds ) - Axis::high( axisBounds[i - 1]->bounds );
863 if( available >= Axis::size( movingBounds ) )
866 ( Axis::high( axisBounds[i - 1]->bounds ) + Axis::low( axisBounds[i]->bounds ) )
868 addCandidate.template operator()<Axis>( id, Axis::equalGapKind,
center + sourceOffset,
869 *axisBounds[i - 1], *axisBounds[i] );
872 if( i + 1 == axisBounds.size()
873 || Axis::high( axisBounds[i]->bounds ) + available + Axis::size( movingBounds )
874 <= Axis::low( axisBounds[i + 1]->bounds ) )
876 int low = Axis::high( axisBounds[i]->bounds ) + available;
879 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
881 - Axis::low( movingBounds ),
882 *axisBounds[i - 1], *axisBounds[i] );
886 || Axis::high( axisBounds[i - 2]->bounds ) + available + Axis::size( movingBounds )
887 <= Axis::low( axisBounds[i - 1]->bounds ) )
889 int high = Axis::low( axisBounds[i - 1]->bounds ) - available;
892 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
894 - Axis::high( movingBounds ),
895 *axisBounds[i - 1], *axisBounds[i] );
900 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, 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)
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
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