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, CIRCLE> )
150 point = aGeometry.NearestPoint( aSource );
151 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
152 point = aGeometry.NearestPoint( aSource );
153 else if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> )
155 point = aGeometry.NearestPoint( aSource );
158 aGeometry.GetStart(),
VECTOR2D( aGeometry.GetContainedPoint() - aGeometry.GetStart() ),
159 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
169 point.
Distance( aSource ) /
static_cast<double>( std::max( 1, aRadius ) ) );
173 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
175 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
183 else if( aExtensionActive )
192#if defined( __GNUC__ )
193#define SNAP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
194#elif defined( _MSC_VER )
195#define SNAP_ALWAYS_INLINE __forceinline
197#define SNAP_ALWAYS_INLINE inline
201struct AXIS_DESCRIPTOR
203 static constexpr size_t index = IsX ? 0 : 1;
213 return IsX ? aPoint.
y : aPoint.
x;
230 return { low( aBox ), coordinate( aBox.
Centre() ), high( aBox ) };
235 return IsX ?
VECTOR2I( aCoordinate, aPerpendicular ) :
VECTOR2I( aPerpendicular, aCoordinate );
258template <
typename Callback>
261 aCallback.template operator()<AXIS_DESCRIPTOR<true>>();
262 aCallback.template operator()<AXIS_DESCRIPTOR<false>>();
265#undef SNAP_ALWAYS_INLINE
271 m_paths.emplace_back( std::move( aPath ) );
277 m_bounds.emplace_back( std::move( aBounds ) );
344 double distanceSquared;
347 constexpr size_t maxCandidatePaths = 64;
348 constexpr size_t maxIntersectionPaths = 12;
349 std::vector<ELIGIBLE_PATH> paths;
350 std::vector<SNAP_CANDIDATE>
result;
351 paths.reserve( maxCandidatePaths );
353 auto betterPath = [](
const ELIGIBLE_PATH& aLeft,
const ELIGIBLE_PATH& aRight )
355 return std::forward_as_tuple( !aLeft.path->intrinsic, aLeft.distanceSquared, aLeft.path->id )
356 < std::forward_as_tuple( !aRight.path->intrinsic, aRight.distanceSquared, aRight.path->id );
358 const double radiusSquared =
static_cast<double>( aRadius ) * aRadius;
367 bool extension =
path.activeExtension || expand;
368 std::optional<INTERSECTABLE_GEOM> extended;
373 extended = extendedGeometry(
path,
true );
374 geometry = &*extended;
377 double distanceSquared = squaredDistanceTo( *geometry, aContext.
sourcePoint );
379 if( distanceSquared > radiusSquared )
382 ELIGIBLE_PATH candidate{ &
path, extension, expand, distanceSquared };
384 if( paths.size() < maxCandidatePaths )
386 paths.push_back( candidate );
387 std::push_heap( paths.begin(), paths.end(), betterPath );
389 else if( betterPath( candidate, paths.front() ) )
391 std::pop_heap( paths.begin(), paths.end(), betterPath );
392 paths.back() = candidate;
393 std::push_heap( paths.begin(), paths.end(), betterPath );
397 std::sort_heap( paths.begin(), paths.end(), betterPath );
398 size_t candidatePathCount = paths.size();
400 for(
size_t i = 0; i < candidatePathCount; ++i )
402 result.push_back( pathCandidate( *paths[i].
path, aContext.
sourcePoint, aRadius, paths[i].extension ) );
405 size_t intersectionPathCount = std::min( candidatePathCount, maxIntersectionPaths );
406 std::vector<INTERSECTABLE_GEOM> intersectionGeometry;
407 intersectionGeometry.reserve( intersectionPathCount );
409 for(
size_t i = 0; i < intersectionPathCount; ++i )
411 intersectionGeometry.push_back( extendedGeometry( *paths[i].
path, paths[i].expand ) );
414 std::vector<VECTOR2I> intersections;
416 for(
size_t first = 0; first < intersectionPathCount; ++first )
418 for(
size_t second = first + 1; second < intersectionPathCount; ++second )
420 if( parallelLinearGeometry( intersectionGeometry[first], intersectionGeometry[second] ) )
425 intersections.clear();
427 intersectionGeometry[first] );
429 std::sort( intersections.begin(), intersections.end(),
432 return std::tie( aLeft.x, aLeft.y ) < std::tie( aRight.x, aRight.y );
434 intersections.erase( std::unique( intersections.begin(), intersections.end() ), intersections.end() );
436 for(
size_t branch = 0; branch < intersections.size(); ++branch )
438 const VECTOR2I& point = intersections[branch];
445 static_cast<int>( branch ) ),
448 result.push_back( std::move( candidate ) );
458 int aRadius,
bool aTangentEnabled,
459 bool aNormalEnabled )
const
461 std::vector<SNAP_CANDIDATE>
result;
472 const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &
path.geometry );
481 double distanceSquared =
delta.SquaredEuclideanNorm();
483 if( distanceSquared == 0.0 )
495 path.id,
path.id.featureIndex, aBranch );
501 result.push_back( std::move( candidate ) );
504 if( aTangentEnabled && distanceSquared >=
radius *
radius )
507 double base = radiusSquared / distanceSquared;
508 double perpendicular =
509 radius * std::sqrt( std::max( 0.0, distanceSquared - radiusSquared ) ) / distanceSquared;
517 double length = std::sqrt( distanceSquared );
538 int targetCoordinate;
542 constexpr size_t MAX_CANDIDATES_PER_AXIS = 64;
543 const auto proposalKey = [](
const PROPOSAL& aProposal )
545 return std::forward_as_tuple( aProposal.affinity, aProposal.displacement, aProposal.target->id,
546 aProposal.sourceFeature, aProposal.targetFeature );
549 const auto compareProposal = [proposalKey](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
551 return proposalKey( aLeft ) < proposalKey( aRight );
554 using PROPOSAL_QUEUE = std::priority_queue<PROPOSAL, std::vector<PROPOSAL>,
decltype( compareProposal )>;
556 std::array<PROPOSAL_QUEUE, 2> proposalQueues{ PROPOSAL_QUEUE( compareProposal ),
557 PROPOSAL_QUEUE( compareProposal ) };
558 std::vector<SNAP_CANDIDATE>
result;
568 const auto retain = [&]( PROPOSAL_QUEUE& aQueue, PROPOSAL aProposal )
570 if( aQueue.size() < MAX_CANDIDATES_PER_AXIS )
572 aQueue.push( std::move( aProposal ) );
574 else if( proposalKey( aProposal ) < proposalKey( aQueue.top() ) )
577 aQueue.push( std::move( aProposal ) );
581 const auto boundsAffinity = [&]<
typename Axis>(
int aSourceFeature,
int aTargetFeature )
590 return aSourceFeature == preferred && aTargetFeature == preferred ? 0 : 1;
592 std::array<std::array<int, 3>, 2> movingFeatures;
596 movingFeatures[Axis::index] = Axis::features( movingBounds );
607 std::array<int, 3> targetFeatures = Axis::features( target.bounds );
609 for(
int sourceFeature = 0; sourceFeature < 3; ++sourceFeature )
611 for(
int targetFeature = 0; targetFeature < 3; ++targetFeature )
613 int resolved = Axis::coordinate( aContext.
sourcePoint ) + targetFeatures[targetFeature]
614 - movingFeatures[Axis::index][sourceFeature];
617 if( displacement <= aRadius )
619 retain( proposalQueues[Axis::index],
620 { &target, sourceFeature, targetFeature,
621 boundsAffinity.template operator()<Axis>( sourceFeature, targetFeature ),
622 resolved, targetFeatures[targetFeature], displacement } );
629 const auto ordered = [&]( PROPOSAL_QUEUE& aQueue )
631 std::vector<PROPOSAL> proposals;
632 proposals.reserve( aQueue.size() );
634 while( !aQueue.empty() )
636 proposals.push_back( aQueue.top() );
640 std::sort( proposals.begin(), proposals.end(),
641 [&](
const PROPOSAL& aLeft,
const PROPOSAL& aRight )
643 return proposalKey( aLeft ) < proposalKey( aRight );
648 std::array<std::vector<SNAP_CANDIDATE>, 2> candidates;
652 std::vector<PROPOSAL> retained = ordered( proposalQueues[Axis::index] );
653 std::vector<SNAP_CANDIDATE>& axisCandidates = candidates[Axis::index];
654 axisCandidates.reserve( retained.size() );
656 for(
const PROPOSAL& proposal : retained )
659 proposal.sourceFeature * 3 + proposal.targetFeature );
661 Axis::candidate( std::move(
id ), proposal.coordinate,
662 proposal.displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
665 candidate.
guides.push_back(
666 { Axis::point( proposal.targetCoordinate,
667 std::min( Axis::perpendicularLow( movingBounds ),
668 Axis::perpendicularLow( proposal.target->bounds ) ) ),
669 Axis::point( proposal.targetCoordinate,
670 std::max( Axis::perpendicularHigh( movingBounds ),
671 Axis::perpendicularHigh( proposal.target->bounds ) ) ) } );
672 axisCandidates.push_back( std::move( candidate ) );
686 int coordinate = Axis::coordinate( point.position );
689 if( displacement > aRadius )
694 Axis::candidate( std::move(
id ), coordinate,
695 displacement /
static_cast<double>( std::max( 1, aRadius ) ) );
697 candidate.
guides.push_back(
698 { Axis::point( coordinate,
699 std::min( Axis::perpendicularLow( movingBounds ),
700 Axis::perpendicularCoordinate( point.position ) ) ),
701 Axis::point( coordinate,
702 std::max( Axis::perpendicularHigh( movingBounds ),
703 Axis::perpendicularCoordinate( point.position ) ) ) } );
704 candidates[Axis::index].push_back( std::move( candidate ) );
711 return std::forward_as_tuple( aCandidate.referenceAffinity, aCandidate.normalizedScreenResidual,
714 const auto retainBest = [&]( std::vector<SNAP_CANDIDATE>& aCandidates )
716 std::sort( aCandidates.begin(), aCandidates.end(),
719 return candidateKey( aLeft ) < candidateKey( aRight );
722 if( aCandidates.size() > MAX_CANDIDATES_PER_AXIS )
723 aCandidates.resize( MAX_CANDIDATES_PER_AXIS );
726 retainBest( candidates[0] );
727 retainBest( candidates[1] );
728 result.reserve( candidates[0].size() + candidates[1].size() );
729 std::move( candidates[0].begin(), candidates[0].
end(), std::back_inserter(
result ) );
730 std::move( candidates[1].begin(), candidates[1].
end(), std::back_inserter(
result ) );
738 constexpr size_t MAX_CANDIDATES = 128;
739 std::vector<SNAP_CANDIDATE>
result;
752 < std::forward_as_tuple( aRight.normalizedScreenResidual, aRight.id );
757 if(
result.size() < MAX_CANDIDATES )
759 result.push_back( std::move( aCandidate ) );
760 std::push_heap(
result.begin(),
result.end(), betterCandidate );
762 else if( betterCandidate( aCandidate,
result.front() ) )
764 std::pop_heap(
result.begin(),
result.end(), betterCandidate );
765 result.back() = std::move( aCandidate );
766 std::push_heap(
result.begin(),
result.end(), betterCandidate );
770 std::array<std::vector<const SNAP_OBJECT_BOUNDS*>, 2> aligned;
772 const auto overlaps = [](
int aFirstStart,
int aFirstEnd,
int aSecondStart,
int aSecondEnd )
774 return aFirstStart < aSecondEnd && aSecondStart < aFirstEnd;
785 if( overlaps( Axis::perpendicularLow( bounds.bounds ), Axis::perpendicularHigh( bounds.bounds ),
786 Axis::perpendicularLow( movingBounds ), Axis::perpendicularHigh( movingBounds ) ) )
788 aligned[Axis::index].push_back( &bounds );
796 std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
797 std::sort( axisBounds.begin(), axisBounds.end(),
800 return std::forward_as_tuple( Axis::low( aFirst->bounds ), aFirst->id )
801 < std::forward_as_tuple( Axis::low( aSecond->bounds ), aSecond->id );
810 if( residual > aRadius )
813 BOX2I resolvedBounds = movingBounds;
814 resolvedBounds.
Offset( Axis::offset( aResolvedSource - Axis::coordinate( aContext.
sourcePoint ) ) );
817 SNAP_CANDIDATE candidate = Axis::candidate( std::move( aId ), aResolvedSource,
818 residual /
static_cast<double>( std::max( 1, aRadius ) ) );
820 const int perpendicular =
821 std::max( Axis::perpendicularHigh( aFirst.bounds ), Axis::perpendicularHigh( aSecond.bounds ) );
822 const auto addGuide = [&](
int aStart,
int aEnd )
824 candidate.
guides.push_back( { Axis::point( aStart, perpendicular ), Axis::point( aEnd, perpendicular ),
830 addGuide( Axis::high( resolvedBounds ), Axis::low( aFirst.bounds ) );
831 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
835 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
836 addGuide( Axis::high( aSecond.bounds ), Axis::low( resolvedBounds ) );
840 addGuide( Axis::high( aFirst.bounds ), Axis::low( resolvedBounds ) );
841 addGuide( Axis::high( resolvedBounds ), Axis::low( aSecond.bounds ) );
844 retainCandidate( std::move( candidate ) );
850 const std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
851 int sourceOffset = Axis::coordinate( aContext.
sourcePoint ) - Axis::coordinate( movingBounds.
Centre() );
853 for(
size_t i = 1; i < axisBounds.size(); ++i )
855 if( axisBounds[i - 1]->parent != axisBounds[i]->parent )
858 int available = Axis::low( axisBounds[i]->bounds ) - Axis::high( axisBounds[i - 1]->bounds );
865 if( available >= Axis::size( movingBounds ) )
868 ( Axis::high( axisBounds[i - 1]->bounds ) + Axis::low( axisBounds[i]->bounds ) )
870 addCandidate.template operator()<Axis>( id, Axis::equalGapKind,
center + sourceOffset,
871 *axisBounds[i - 1], *axisBounds[i] );
874 if( i + 1 == axisBounds.size()
875 || Axis::high( axisBounds[i]->bounds ) + available + Axis::size( movingBounds )
876 <= Axis::low( axisBounds[i + 1]->bounds ) )
878 int low = Axis::high( axisBounds[i]->bounds ) + available;
881 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
883 - Axis::low( movingBounds ),
884 *axisBounds[i - 1], *axisBounds[i] );
888 || Axis::high( axisBounds[i - 2]->bounds ) + available + Axis::size( movingBounds )
889 <= Axis::low( axisBounds[i - 1]->bounds ) )
891 int high = Axis::low( axisBounds[i - 1]->bounds ) - available;
894 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
896 - Axis::high( movingBounds ),
897 *axisBounds[i - 1], *axisBounds[i] );
902 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
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