KiCad PCB EDA Suite
Loading...
Searching...
No Matches
snap_inference.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <snap/snap_inference.h>
21
22#include "snap_manifold.h"
23
24#include <geometry/nearest.h>
25
26#include <algorithm>
27#include <array>
28#include <cmath>
29#include <limits>
30#include <queue>
31#include <string>
32#include <tuple>
33
34
35namespace
36{
37double squaredDistanceTo( const INTERSECTABLE_GEOM& aGeometry, const VECTOR2I& aPoint )
38{
39 return std::visit(
40 [&]( const auto& aShape ) -> double
41 {
42 using SHAPE_TYPE = std::decay_t<decltype( aShape )>;
43
44 if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> || std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
45 {
46 return aShape.NearestPoint( aPoint ).SquaredDistance( aPoint );
47 }
48 else if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
49 {
50 return aShape.SquaredDistance( aPoint );
51 }
52 else
53 {
54 double distance = snapManifoldDistance( aShape, aPoint );
55 return distance * distance;
56 }
57 },
58 aGeometry );
59}
60
61
62INTERSECTABLE_GEOM extendedGeometry( const SNAP_OBJECT_PATH& aPath, bool aExtensionActive )
63{
64 if( aExtensionActive )
65 {
66 if( const SEG* segment = std::get_if<SEG>( &aPath.geometry ) )
67 return LINE( *segment );
68
69 if( const HALF_LINE* ray = std::get_if<HALF_LINE>( &aPath.geometry ) )
70 return LINE( ray->GetContainedSeg() );
71 }
72
73 return aPath.geometry;
74}
75
76
77std::optional<VECTOR2I> linearDirection( const INTERSECTABLE_GEOM& aGeometry )
78{
79 if( const SEG* segment = std::get_if<SEG>( &aGeometry ) )
80 return segment->B - segment->A;
81
82 if( const LINE* line = std::get_if<LINE>( &aGeometry ) )
83 {
84 const SEG& segment = line->GetContainedSeg();
85 return segment.B - segment.A;
86 }
87
88 if( const HALF_LINE* ray = std::get_if<HALF_LINE>( &aGeometry ) )
89 {
90 const SEG& segment = ray->GetContainedSeg();
91 return segment.B - segment.A;
92 }
93
94 return std::nullopt;
95}
96
97
98bool parallelLinearGeometry( const INTERSECTABLE_GEOM& aFirst, const INTERSECTABLE_GEOM& aSecond )
99{
100 std::optional<VECTOR2I> first = linearDirection( aFirst );
101 std::optional<VECTOR2I> second = linearDirection( aSecond );
102
103 if( !first || !second )
104 return false;
105
106 return first->Cross( *second ) == 0;
107}
108
109
110SNAP_CANDIDATE pathCandidate( const SNAP_OBJECT_PATH& aPath, const VECTOR2I& aSource, int aRadius,
111 bool aExtensionActive )
112{
113 SNAP_CANDIDATE candidate;
114
115 std::visit(
116 [&]( const auto& aGeometry )
117 {
118 using SHAPE_TYPE = std::decay_t<decltype( aGeometry )>;
119
120 if constexpr( std::is_same_v<SHAPE_TYPE, SEG> )
121 {
122 candidate =
126 aGeometry.A, VECTOR2D( aGeometry.B - aGeometry.A ),
127 snapManifoldDistance( aGeometry, aSource ) / std::max( 1, aRadius ) );
128 candidate.relation =
130 candidate.finite = !aExtensionActive;
131 candidate.domainStart = 0.0;
132 candidate.domainEnd = 1.0;
133 candidate.manifold = aExtensionActive ? INTERSECTABLE_GEOM( LINE( aGeometry ) )
134 : INTERSECTABLE_GEOM( aGeometry );
135 }
136 else if constexpr( std::is_same_v<SHAPE_TYPE, LINE> )
137 {
138 const SEG& segment = aGeometry.GetContainedSeg();
139 candidate = SNAP_CANDIDATE::Line(
141 VECTOR2D( segment.B - segment.A ),
142 snapManifoldDistance( aGeometry, aSource ) / std::max( 1, aRadius ) );
143 candidate.manifold = aGeometry;
144 }
145 else
146 {
147 VECTOR2I point;
148
149 if constexpr( std::is_same_v<SHAPE_TYPE, HALF_LINE> )
150 {
151 point = aGeometry.NearestPoint( aSource );
152 candidate = SNAP_CANDIDATE::Line(
154 aGeometry.GetStart(), VECTOR2D( aGeometry.GetContainedPoint() - aGeometry.GetStart() ),
155 point.Distance( aSource ) / static_cast<double>( std::max( 1, aRadius ) ) );
157 candidate.manifold = aGeometry;
158 return;
159 }
160 else
161 {
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 );
166 else
167 point = GetNearestPoint( NEARABLE_GEOM( aGeometry ), aSource );
168
169 candidate = SNAP_CANDIDATE::Point(
171 point.Distance( aSource ) / static_cast<double>( std::max( 1, aRadius ) ) );
172 candidate.consumedDof = 1;
173 candidate.manifold = aGeometry;
174
175 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
177 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
179 }
180 }
181 },
182 aPath.geometry );
183
184 if( aPath.intrinsic )
186 else if( aExtensionActive )
188
189 return candidate;
190}
191
192
193// Unoptimized builds otherwise retain descriptor calls in hot layout loops, causing a measurable
194// stress-path regression.
195#if defined( __GNUC__ )
196#define SNAP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
197#elif defined( _MSC_VER )
198#define SNAP_ALWAYS_INLINE __forceinline
199#else
200#define SNAP_ALWAYS_INLINE inline
201#endif
202
203template <bool IsX>
204struct AXIS_DESCRIPTOR
205{
206 static constexpr size_t index = IsX ? 0 : 1;
207 static constexpr SNAP_ID_KIND boundsKind = IsX ? SNAP_ID_KIND::BOUNDS_X : SNAP_ID_KIND::BOUNDS_Y;
208 static constexpr SNAP_ID_KIND anchorPointKind =
210 static constexpr SNAP_ID_KIND equalGapKind = IsX ? SNAP_ID_KIND::EQUAL_GAP_X : SNAP_ID_KIND::EQUAL_GAP_Y;
211 static constexpr SNAP_ID_KIND copyGapKind = IsX ? SNAP_ID_KIND::COPY_GAP_X : SNAP_ID_KIND::COPY_GAP_Y;
212
213 static SNAP_ALWAYS_INLINE int coordinate( const VECTOR2I& aPoint ) { return IsX ? aPoint.x : aPoint.y; }
214 static SNAP_ALWAYS_INLINE int perpendicularCoordinate( const VECTOR2I& aPoint )
215 {
216 return IsX ? aPoint.y : aPoint.x;
217 }
218 static SNAP_ALWAYS_INLINE int low( const BOX2I& aBox ) { return IsX ? aBox.GetLeft() : aBox.GetTop(); }
219 static SNAP_ALWAYS_INLINE int high( const BOX2I& aBox ) { return IsX ? aBox.GetRight() : aBox.GetBottom(); }
220 static SNAP_ALWAYS_INLINE int size( const BOX2I& aBox ) { return IsX ? aBox.GetWidth() : aBox.GetHeight(); }
221 static SNAP_ALWAYS_INLINE int perpendicularLow( const BOX2I& aBox ) { return IsX ? aBox.GetTop() : aBox.GetLeft(); }
222 static SNAP_ALWAYS_INLINE int perpendicularHigh( const BOX2I& aBox )
223 {
224 return IsX ? aBox.GetBottom() : aBox.GetRight();
225 }
226 static SNAP_ALWAYS_INLINE int preferredFeature( const SNAP_REFERENCE_PREFERENCE& aPreference )
227 {
228 return IsX ? aPreference.horizontalFeature : aPreference.verticalFeature;
229 }
230
231 static SNAP_ALWAYS_INLINE std::array<int, 3> features( const BOX2I& aBox )
232 {
233 return { low( aBox ), coordinate( aBox.Centre() ), high( aBox ) };
234 }
235
236 static SNAP_ALWAYS_INLINE VECTOR2I point( int aCoordinate, int aPerpendicular )
237 {
238 return IsX ? VECTOR2I( aCoordinate, aPerpendicular ) : VECTOR2I( aPerpendicular, aCoordinate );
239 }
240
241 static SNAP_ALWAYS_INLINE VECTOR2I offset( int aDistance )
242 {
243 return IsX ? VECTOR2I( aDistance, 0 ) : VECTOR2I( 0, aDistance );
244 }
245
246 static SNAP_ALWAYS_INLINE SNAP_CANDIDATE candidate( SNAP_STABLE_ID aId, int aCoordinate, double aResidual )
247 {
248 if constexpr( IsX )
249 {
250 return SNAP_CANDIDATE::AxisX( std::move( aId ), SNAP_PRIORITY_TIER::OBJECT,
251 SNAP_CANDIDATE_SUBTYPE::BBOX_LAYOUT, aCoordinate, aResidual );
252 }
253 else
254 {
255 return SNAP_CANDIDATE::AxisY( std::move( aId ), SNAP_PRIORITY_TIER::OBJECT,
256 SNAP_CANDIDATE_SUBTYPE::BBOX_LAYOUT, aCoordinate, aResidual );
257 }
258 }
259};
260
261template <typename Callback>
262SNAP_ALWAYS_INLINE void forEachAxis( Callback&& aCallback )
263{
264 aCallback.template operator()<AXIS_DESCRIPTOR<true>>();
265 aCallback.template operator()<AXIS_DESCRIPTOR<false>>();
266}
267
268#undef SNAP_ALWAYS_INLINE
269} // namespace
270
271
273{
274 m_paths.emplace_back( std::move( aPath ) );
275}
276
277
279{
280 m_bounds.emplace_back( std::move( aBounds ) );
281}
282
283
285{
286 m_alignmentPoints.emplace_back( std::move( aPoint ) );
287}
288
289
291{
292 m_paths.clear();
293 m_bounds.clear();
294 m_alignmentPoints.clear();
295 m_activeExtensions.clear();
296}
297
298
300{
301 if( std::find( m_activeExtensions.begin(), m_activeExtensions.end(), aId ) == m_activeExtensions.end() )
302 {
303 m_activeExtensions.push_back( aId );
304 }
305}
306
307
312
313
315{
316 return std::find( aContext.movingFeatures.begin(), aContext.movingFeatures.end(), aId )
317 == aContext.movingFeatures.end();
318}
319
320
322{
323 if( !eligible( aContext, aBounds.id ) )
324 return false;
325
326 return !aContext.movingItem || !aBounds.parent || *aBounds.parent != aContext.movingItem->target;
327}
328
329
331{
332 if( !eligible( aContext, aPoint.id ) )
333 return false;
334
335 return !aContext.movingItem || !aPoint.parent || *aPoint.parent != aContext.movingItem->target;
336}
337
338
339std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectObjectGeometry( const SNAP_SOURCE_CONTEXT& aContext,
340 int aRadius ) const
341{
342 struct ELIGIBLE_PATH
343 {
344 const SNAP_OBJECT_PATH* path;
345 bool extension;
346 bool expand;
347 double distanceSquared;
348 };
349
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 );
355
356 auto betterPath = []( const ELIGIBLE_PATH& aLeft, const ELIGIBLE_PATH& aRight )
357 {
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 );
360 };
361 const double radiusSquared = static_cast<double>( aRadius ) * aRadius;
362
363 for( const SNAP_OBJECT_PATH& path : m_paths )
364 {
365 if( !eligible( aContext, path.id ) )
366 continue;
367
368 bool expand =
369 std::find( m_activeExtensions.begin(), m_activeExtensions.end(), path.id ) != m_activeExtensions.end();
370 bool extension = path.activeExtension || expand;
371 std::optional<INTERSECTABLE_GEOM> extended;
372 const INTERSECTABLE_GEOM* geometry = &path.geometry;
373
374 if( expand )
375 {
376 extended = extendedGeometry( path, true );
377 geometry = &*extended;
378 }
379
380 double distanceSquared = squaredDistanceTo( *geometry, aContext.sourcePoint );
381
382 if( distanceSquared > radiusSquared )
383 continue;
384
385 ELIGIBLE_PATH candidate{ &path, extension, expand, distanceSquared };
386
387 if( paths.size() < maxCandidatePaths )
388 {
389 paths.push_back( candidate );
390 std::push_heap( paths.begin(), paths.end(), betterPath );
391 }
392 else if( betterPath( candidate, paths.front() ) )
393 {
394 std::pop_heap( paths.begin(), paths.end(), betterPath );
395 paths.back() = candidate;
396 std::push_heap( paths.begin(), paths.end(), betterPath );
397 }
398 }
399
400 std::sort_heap( paths.begin(), paths.end(), betterPath );
401 size_t candidatePathCount = paths.size();
402
403 for( size_t i = 0; i < candidatePathCount; ++i )
404 {
405 result.push_back( pathCandidate( *paths[i].path, aContext.sourcePoint, aRadius, paths[i].extension ) );
406 }
407
408 size_t intersectionPathCount = std::min( candidatePathCount, maxIntersectionPaths );
409 std::vector<INTERSECTABLE_GEOM> intersectionGeometry;
410 intersectionGeometry.reserve( intersectionPathCount );
411
412 for( size_t i = 0; i < intersectionPathCount; ++i )
413 {
414 intersectionGeometry.push_back( extendedGeometry( *paths[i].path, paths[i].expand ) );
415 }
416
417 std::vector<VECTOR2I> intersections;
418
419 for( size_t first = 0; first < intersectionPathCount; ++first )
420 {
421 for( size_t second = first + 1; second < intersectionPathCount; ++second )
422 {
423 if( parallelLinearGeometry( intersectionGeometry[first], intersectionGeometry[second] ) )
424 {
425 continue;
426 }
427
428 intersections.clear();
429 std::visit( INTERSECTION_VISITOR( intersectionGeometry[second], intersections ),
430 intersectionGeometry[first] );
431
432 std::sort( intersections.begin(), intersections.end(),
433 []( const VECTOR2I& aLeft, const VECTOR2I& aRight )
434 {
435 return std::tie( aLeft.x, aLeft.y ) < std::tie( aRight.x, aRight.y );
436 } );
437 intersections.erase( std::unique( intersections.begin(), intersections.end() ), intersections.end() );
438
439 for( size_t branch = 0; branch < intersections.size(); ++branch )
440 {
441 const VECTOR2I& point = intersections[branch];
442
443 if( point.Distance( aContext.sourcePoint ) > aRadius )
444 continue;
445
447 MakeIntersectionSnapId( paths[first].path->id, paths[second].path->id,
448 static_cast<int>( branch ) ),
450 point.Distance( aContext.sourcePoint ) / static_cast<double>( std::max( 1, aRadius ) ) );
451 result.push_back( std::move( candidate ) );
452 }
453 }
454 }
455
456 return result;
457}
458
459
460std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectTangentNormal( const SNAP_SOURCE_CONTEXT& aContext,
461 int aRadius, bool aTangentEnabled,
462 bool aNormalEnabled ) const
463{
464 std::vector<SNAP_CANDIDATE> result;
465
466 if( !aContext.stationarySourceLeg )
467 return result;
468
469 for( const SNAP_OBJECT_PATH& path : m_paths )
470 {
471 if( !eligible( aContext, path.id ) )
472 continue;
473
474 const CIRCLE* circle = std::get_if<CIRCLE>( &path.geometry );
475 const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &path.geometry );
476
477 if( !circle && !arc )
478 continue;
479
481 VECTOR2D center( circle ? circle->Center : arc->GetCenter() );
482 double radius = circle ? circle->Radius : arc->GetRadius();
484 double distanceSquared = delta.SquaredEuclideanNorm();
485
486 if( distanceSquared == 0.0 )
487 continue;
488
489 const auto addContact = [&]( const VECTOR2D& aContact, SNAP_RELATION aRelation, int aBranch )
490 {
491 VECTOR2I point( KiROUND( aContact.x ), KiROUND( aContact.y ) );
492
493 if( point.Distance( aContext.sourcePoint ) > aRadius || ( arc && !arc->Collide( point, 2 ) ) )
494 return;
495
498 path.id, path.id.featureIndex, aBranch );
501 point.Distance( aContext.sourcePoint ) / static_cast<double>( std::max( 1, aRadius ) ) );
502 candidate.relation = aRelation;
503 candidate.guides.push_back( SNAP_GUIDE{ *aContext.stationarySourceLeg, point } );
504 result.push_back( std::move( candidate ) );
505 };
506
507 if( aTangentEnabled && distanceSquared >= radius * radius )
508 {
509 double radiusSquared = radius * radius;
510 double base = radiusSquared / distanceSquared;
511 double perpendicular =
512 radius * std::sqrt( std::max( 0.0, distanceSquared - radiusSquared ) ) / distanceSquared;
513 VECTOR2D normal( -delta.y, delta.x );
514 addContact( center + delta * base + normal * perpendicular, SNAP_RELATION::TANGENT, 0 );
515 addContact( center + delta * base - normal * perpendicular, SNAP_RELATION::TANGENT, 1 );
516 }
517
518 if( aNormalEnabled )
519 {
520 double length = std::sqrt( distanceSquared );
521 VECTOR2D radial = delta * ( radius / length );
522 addContact( center + radial, SNAP_RELATION::NORMAL, 0 );
523 addContact( center - radial, SNAP_RELATION::NORMAL, 1 );
524 }
525 }
526
527 return result;
528}
529
530
531std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectAlignment( const SNAP_SOURCE_CONTEXT& aContext,
532 int aRadius ) const
533{
534 struct PROPOSAL
535 {
536 const SNAP_OBJECT_BOUNDS* target;
537 int sourceFeature;
538 int targetFeature;
539 int affinity;
540 int coordinate;
541 int targetCoordinate;
542 int displacement;
543 };
544
545 constexpr size_t MAX_CANDIDATES_PER_AXIS = 64;
546 const auto proposalKey = []( const PROPOSAL& aProposal )
547 {
548 return std::forward_as_tuple( aProposal.affinity, aProposal.displacement, aProposal.target->id,
549 aProposal.sourceFeature, aProposal.targetFeature );
550 };
551
552 const auto compareProposal = [proposalKey]( const PROPOSAL& aLeft, const PROPOSAL& aRight )
553 {
554 return proposalKey( aLeft ) < proposalKey( aRight );
555 };
556
557 using PROPOSAL_QUEUE = std::priority_queue<PROPOSAL, std::vector<PROPOSAL>, decltype( compareProposal )>;
558
559 std::array<PROPOSAL_QUEUE, 2> proposalQueues{ PROPOSAL_QUEUE( compareProposal ),
560 PROPOSAL_QUEUE( compareProposal ) };
561 std::vector<SNAP_CANDIDATE> result;
562
563 if( !aContext.movingBounds )
564 return result;
565
566 BOX2I movingBounds = *aContext.movingBounds;
567
568 if( aContext.movingReferencePoint )
569 movingBounds.Offset( aContext.sourcePoint - *aContext.movingReferencePoint );
570
571 const auto retain = [&]( PROPOSAL_QUEUE& aQueue, PROPOSAL aProposal )
572 {
573 if( aQueue.size() < MAX_CANDIDATES_PER_AXIS )
574 {
575 aQueue.push( std::move( aProposal ) );
576 }
577 else if( proposalKey( aProposal ) < proposalKey( aQueue.top() ) )
578 {
579 aQueue.pop();
580 aQueue.push( std::move( aProposal ) );
581 }
582 };
583
584 const auto boundsAffinity = [&]<typename Axis>( int aSourceFeature, int aTargetFeature )
585 {
587 return 0;
588
590 return 1;
591
592 int preferred = Axis::preferredFeature( aContext.referencePreference );
593 return aSourceFeature == preferred && aTargetFeature == preferred ? 0 : 1;
594 };
595 std::array<std::array<int, 3>, 2> movingFeatures;
596 forEachAxis(
597 [&]<typename Axis>()
598 {
599 movingFeatures[Axis::index] = Axis::features( movingBounds );
600 } );
601
602 for( const SNAP_OBJECT_BOUNDS& target : m_bounds )
603 {
604 if( !eligible( aContext, target ) )
605 continue;
606
607 forEachAxis(
608 [&]<typename Axis>()
609 {
610 std::array<int, 3> targetFeatures = Axis::features( target.bounds );
611
612 for( int sourceFeature = 0; sourceFeature < 3; ++sourceFeature )
613 {
614 for( int targetFeature = 0; targetFeature < 3; ++targetFeature )
615 {
616 int resolved = Axis::coordinate( aContext.sourcePoint ) + targetFeatures[targetFeature]
617 - movingFeatures[Axis::index][sourceFeature];
618 int displacement = std::abs( resolved - Axis::coordinate( aContext.sourcePoint ) );
619
620 if( displacement <= aRadius )
621 {
622 retain( proposalQueues[Axis::index],
623 { &target, sourceFeature, targetFeature,
624 boundsAffinity.template operator()<Axis>( sourceFeature, targetFeature ),
625 resolved, targetFeatures[targetFeature], displacement } );
626 }
627 }
628 }
629 } );
630 }
631
632 const auto ordered = [&]( PROPOSAL_QUEUE& aQueue )
633 {
634 std::vector<PROPOSAL> proposals;
635 proposals.reserve( aQueue.size() );
636
637 while( !aQueue.empty() )
638 {
639 proposals.push_back( aQueue.top() );
640 aQueue.pop();
641 }
642
643 std::sort( proposals.begin(), proposals.end(),
644 [&]( const PROPOSAL& aLeft, const PROPOSAL& aRight )
645 {
646 return proposalKey( aLeft ) < proposalKey( aRight );
647 } );
648 return proposals;
649 };
650
651 std::array<std::vector<SNAP_CANDIDATE>, 2> candidates;
652 forEachAxis(
653 [&]<typename Axis>()
654 {
655 std::vector<PROPOSAL> retained = ordered( proposalQueues[Axis::index] );
656 std::vector<SNAP_CANDIDATE>& axisCandidates = candidates[Axis::index];
657 axisCandidates.reserve( retained.size() );
658
659 for( const PROPOSAL& proposal : retained )
660 {
661 SNAP_STABLE_ID id = MakeDerivedSnapId( Axis::boundsKind, proposal.target->id,
662 proposal.sourceFeature * 3 + proposal.targetFeature );
663 SNAP_CANDIDATE candidate =
664 Axis::candidate( std::move( id ), proposal.coordinate,
665 proposal.displacement / static_cast<double>( std::max( 1, aRadius ) ) );
666 candidate.referenceAffinity = proposal.affinity;
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 ) );
676 }
677 } );
678
680 {
681 for( const SNAP_ALIGNMENT_POINT& point : m_alignmentPoints )
682 {
683 if( !eligible( aContext, point ) )
684 continue;
685
686 forEachAxis(
687 [&]<typename Axis>()
688 {
689 int coordinate = Axis::coordinate( point.position );
690 int displacement = std::abs( coordinate - Axis::coordinate( aContext.sourcePoint ) );
691
692 if( displacement > aRadius )
693 return;
694
695 SNAP_STABLE_ID id = MakeDerivedSnapId( Axis::anchorPointKind, point.id );
696 SNAP_CANDIDATE candidate =
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 ) );
708 } );
709 }
710 }
711
712 const auto candidateKey = []( const SNAP_CANDIDATE& aCandidate )
713 {
714 return std::forward_as_tuple( aCandidate.referenceAffinity, aCandidate.normalizedScreenResidual,
715 aCandidate.id );
716 };
717 const auto retainBest = [&]( std::vector<SNAP_CANDIDATE>& aCandidates )
718 {
719 std::sort( aCandidates.begin(), aCandidates.end(),
720 [&]( const SNAP_CANDIDATE& aLeft, const SNAP_CANDIDATE& aRight )
721 {
722 return candidateKey( aLeft ) < candidateKey( aRight );
723 } );
724
725 if( aCandidates.size() > MAX_CANDIDATES_PER_AXIS )
726 aCandidates.resize( MAX_CANDIDATES_PER_AXIS );
727 };
728
729 retainBest( candidates[0] );
730 retainBest( candidates[1] );
731 result.reserve( candidates[0].size() + candidates[1].size() );
732 std::move( candidates[0].begin(), candidates[0].end(), std::back_inserter( result ) );
733 std::move( candidates[1].begin(), candidates[1].end(), std::back_inserter( result ) );
734 return result;
735}
736
737
738std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectEqualSpacing( const SNAP_SOURCE_CONTEXT& aContext,
739 int aRadius ) const
740{
741 constexpr size_t MAX_CANDIDATES = 128;
742 std::vector<SNAP_CANDIDATE> result;
743
744 if( !aContext.movingBounds )
745 return result;
746
747 BOX2I movingBounds = *aContext.movingBounds;
748
749 if( aContext.movingReferencePoint )
750 movingBounds.Offset( aContext.sourcePoint - *aContext.movingReferencePoint );
751
752 const auto betterCandidate = []( const SNAP_CANDIDATE& aLeft, const SNAP_CANDIDATE& aRight )
753 {
754 return std::forward_as_tuple( aLeft.normalizedScreenResidual, aLeft.id )
755 < std::forward_as_tuple( aRight.normalizedScreenResidual, aRight.id );
756 };
757
758 const auto retainCandidate = [&]( SNAP_CANDIDATE aCandidate )
759 {
760 if( result.size() < MAX_CANDIDATES )
761 {
762 result.push_back( std::move( aCandidate ) );
763 std::push_heap( result.begin(), result.end(), betterCandidate );
764 }
765 else if( betterCandidate( aCandidate, result.front() ) )
766 {
767 std::pop_heap( result.begin(), result.end(), betterCandidate );
768 result.back() = std::move( aCandidate );
769 std::push_heap( result.begin(), result.end(), betterCandidate );
770 }
771 };
772
773 std::array<std::vector<const SNAP_OBJECT_BOUNDS*>, 2> aligned;
774
775 const auto overlaps = []( int aFirstStart, int aFirstEnd, int aSecondStart, int aSecondEnd )
776 {
777 return aFirstStart < aSecondEnd && aSecondStart < aFirstEnd;
778 };
779
780 for( const SNAP_OBJECT_BOUNDS& bounds : m_bounds )
781 {
782 if( !eligible( aContext, bounds ) )
783 continue;
784
785 forEachAxis(
786 [&]<typename Axis>()
787 {
788 if( overlaps( Axis::perpendicularLow( bounds.bounds ), Axis::perpendicularHigh( bounds.bounds ),
789 Axis::perpendicularLow( movingBounds ), Axis::perpendicularHigh( movingBounds ) ) )
790 {
791 aligned[Axis::index].push_back( &bounds );
792 }
793 } );
794 }
795
796 forEachAxis(
797 [&]<typename Axis>()
798 {
799 std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
800 std::sort( axisBounds.begin(), axisBounds.end(),
801 []( const SNAP_OBJECT_BOUNDS* aFirst, const SNAP_OBJECT_BOUNDS* aSecond )
802 {
803 return std::forward_as_tuple( Axis::low( aFirst->bounds ), aFirst->id )
804 < std::forward_as_tuple( Axis::low( aSecond->bounds ), aSecond->id );
805 } );
806 } );
807
808 const auto addCandidate = [&]<typename Axis>( SNAP_STABLE_ID aId, SNAP_ID_KIND aKind, int aResolvedSource,
809 const SNAP_OBJECT_BOUNDS& aFirst, const SNAP_OBJECT_BOUNDS& aSecond )
810 {
811 int residual = std::abs( aResolvedSource - Axis::coordinate( aContext.sourcePoint ) );
812
813 if( residual > aRadius )
814 return;
815
816 BOX2I resolvedBounds = movingBounds;
817 resolvedBounds.Offset( Axis::offset( aResolvedSource - Axis::coordinate( aContext.sourcePoint ) ) );
818
819 aId.kind = aKind;
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 )
826 {
827 candidate.guides.push_back( { Axis::point( aStart, perpendicular ), Axis::point( aEnd, perpendicular ),
829 };
830
831 if( candidate.id.solutionBranch < 0 )
832 {
833 addGuide( Axis::high( resolvedBounds ), Axis::low( aFirst.bounds ) );
834 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
835 }
836 else if( candidate.id.solutionBranch > 0 )
837 {
838 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
839 addGuide( Axis::high( aSecond.bounds ), Axis::low( resolvedBounds ) );
840 }
841 else
842 {
843 addGuide( Axis::high( aFirst.bounds ), Axis::low( resolvedBounds ) );
844 addGuide( Axis::high( resolvedBounds ), Axis::low( aSecond.bounds ) );
845 }
846
847 retainCandidate( std::move( candidate ) );
848 };
849
850 forEachAxis(
851 [&]<typename Axis>()
852 {
853 const std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
854 int sourceOffset = Axis::coordinate( aContext.sourcePoint ) - Axis::coordinate( movingBounds.Centre() );
855
856 for( size_t i = 1; i < axisBounds.size(); ++i )
857 {
858 if( axisBounds[i - 1]->parent != axisBounds[i]->parent )
859 continue;
860
861 int available = Axis::low( axisBounds[i]->bounds ) - Axis::high( axisBounds[i - 1]->bounds );
862
863 if( available < 0 )
864 continue;
865
866 SNAP_STABLE_ID id = MakeIntersectionSnapId( axisBounds[i - 1]->id, axisBounds[i]->id, 0 );
867
868 if( available >= Axis::size( movingBounds ) )
869 {
870 int center = KiROUND(
871 ( Axis::high( axisBounds[i - 1]->bounds ) + Axis::low( axisBounds[i]->bounds ) )
872 / 2.0 );
873 addCandidate.template operator()<Axis>( id, Axis::equalGapKind, center + sourceOffset,
874 *axisBounds[i - 1], *axisBounds[i] );
875 }
876
877 if( i + 1 == axisBounds.size()
878 || Axis::high( axisBounds[i]->bounds ) + available + Axis::size( movingBounds )
879 <= Axis::low( axisBounds[i + 1]->bounds ) )
880 {
881 int low = Axis::high( axisBounds[i]->bounds ) + available;
882 SNAP_STABLE_ID copiedId = id;
883 copiedId.solutionBranch = 1;
884 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
885 Axis::coordinate( aContext.sourcePoint ) + low
886 - Axis::low( movingBounds ),
887 *axisBounds[i - 1], *axisBounds[i] );
888 }
889
890 if( i == 1
891 || Axis::high( axisBounds[i - 2]->bounds ) + available + Axis::size( movingBounds )
892 <= Axis::low( axisBounds[i - 1]->bounds ) )
893 {
894 int high = Axis::low( axisBounds[i - 1]->bounds ) - available;
895 SNAP_STABLE_ID copiedId = id;
896 copiedId.solutionBranch = -1;
897 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
898 Axis::coordinate( aContext.sourcePoint ) + high
899 - Axis::high( movingBounds ),
900 *axisBounds[i - 1], *axisBounds[i] );
901 }
902 }
903 } );
904
905 std::sort_heap( result.begin(), result.end(), betterCandidate );
906 return result;
907}
int index
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
constexpr size_type GetWidth() const
Definition box2.h:211
constexpr Vec Centre() const
Definition box2.h:94
constexpr size_type GetHeight() const
Definition box2.h:212
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr void Offset(coord_type dx, coord_type dy)
Definition box2.h:256
constexpr coord_type GetBottom() const
Definition box2.h:219
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
Definition line.h:32
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
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,...
double GetRadius() const
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.
Definition vector2d.h:574
std::string source
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)
Definition eda_angle.h:437
VECTOR2I GetNearestPoint(const NEARABLE_GEOM &aGeom, const VECTOR2I &aPt)
Get the nearest point on a geometry to a given point.
Definition nearest.cpp:54
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.
Definition nearest.h:40
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
SHAPE_TYPE
Lists all supported shapes.
Definition shape.h:42
#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_RELATION
SNAP_STABLE_ID MakeDerivedSnapId(SNAP_ID_KIND aKind, const SNAP_STABLE_ID &aSource, int aFeatureIndex=0, int aSolutionBranch=0)
SNAP_ID_KIND
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
SNAP_STABLE_ID id
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_RELATION relation
SNAP_CANDIDATE_SUBTYPE subtype
SNAP_STABLE_ID id
std::optional< SNAP_TARGET_ID > parent
SNAP_STABLE_ID id
INTERSECTABLE_GEOM geometry
SNAP_REFERENCE_KIND kind
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
std::string path
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
VECTOR2I center
int radius
VECTOR2I end
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
wxString result
Test unit parsing edge cases and error handling.
int delta
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707