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, 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> )
154 {
155 point = aGeometry.NearestPoint( aSource );
156 candidate = SNAP_CANDIDATE::Line(
158 aGeometry.GetStart(), VECTOR2D( aGeometry.GetContainedPoint() - aGeometry.GetStart() ),
159 point.Distance( aSource ) / static_cast<double>( std::max( 1, aRadius ) ) );
161 candidate.manifold = aGeometry;
162 return;
163 }
164 else
165 point = GetNearestPoint( NEARABLE_GEOM( aGeometry ), aSource );
166
167 candidate = SNAP_CANDIDATE::Point(
169 point.Distance( aSource ) / static_cast<double>( std::max( 1, aRadius ) ) );
170 candidate.consumedDof = 1;
171 candidate.manifold = aGeometry;
172
173 if constexpr( std::is_same_v<SHAPE_TYPE, CIRCLE> )
175 else if constexpr( std::is_same_v<SHAPE_TYPE, SHAPE_ARC> )
177 }
178 },
179 aPath.geometry );
180
181 if( aPath.intrinsic )
183 else if( aExtensionActive )
185
186 return candidate;
187}
188
189
190// Unoptimized builds otherwise retain descriptor calls in hot layout loops, causing a measurable
191// stress-path regression.
192#if defined( __GNUC__ )
193#define SNAP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
194#elif defined( _MSC_VER )
195#define SNAP_ALWAYS_INLINE __forceinline
196#else
197#define SNAP_ALWAYS_INLINE inline
198#endif
199
200template <bool IsX>
201struct AXIS_DESCRIPTOR
202{
203 static constexpr size_t index = IsX ? 0 : 1;
204 static constexpr SNAP_ID_KIND boundsKind = IsX ? SNAP_ID_KIND::BOUNDS_X : SNAP_ID_KIND::BOUNDS_Y;
205 static constexpr SNAP_ID_KIND anchorPointKind =
207 static constexpr SNAP_ID_KIND equalGapKind = IsX ? SNAP_ID_KIND::EQUAL_GAP_X : SNAP_ID_KIND::EQUAL_GAP_Y;
208 static constexpr SNAP_ID_KIND copyGapKind = IsX ? SNAP_ID_KIND::COPY_GAP_X : SNAP_ID_KIND::COPY_GAP_Y;
209
210 static SNAP_ALWAYS_INLINE int coordinate( const VECTOR2I& aPoint ) { return IsX ? aPoint.x : aPoint.y; }
211 static SNAP_ALWAYS_INLINE int perpendicularCoordinate( const VECTOR2I& aPoint )
212 {
213 return IsX ? aPoint.y : aPoint.x;
214 }
215 static SNAP_ALWAYS_INLINE int low( const BOX2I& aBox ) { return IsX ? aBox.GetLeft() : aBox.GetTop(); }
216 static SNAP_ALWAYS_INLINE int high( const BOX2I& aBox ) { return IsX ? aBox.GetRight() : aBox.GetBottom(); }
217 static SNAP_ALWAYS_INLINE int size( const BOX2I& aBox ) { return IsX ? aBox.GetWidth() : aBox.GetHeight(); }
218 static SNAP_ALWAYS_INLINE int perpendicularLow( const BOX2I& aBox ) { return IsX ? aBox.GetTop() : aBox.GetLeft(); }
219 static SNAP_ALWAYS_INLINE int perpendicularHigh( const BOX2I& aBox )
220 {
221 return IsX ? aBox.GetBottom() : aBox.GetRight();
222 }
223 static SNAP_ALWAYS_INLINE int preferredFeature( const SNAP_REFERENCE_PREFERENCE& aPreference )
224 {
225 return IsX ? aPreference.horizontalFeature : aPreference.verticalFeature;
226 }
227
228 static SNAP_ALWAYS_INLINE std::array<int, 3> features( const BOX2I& aBox )
229 {
230 return { low( aBox ), coordinate( aBox.Centre() ), high( aBox ) };
231 }
232
233 static SNAP_ALWAYS_INLINE VECTOR2I point( int aCoordinate, int aPerpendicular )
234 {
235 return IsX ? VECTOR2I( aCoordinate, aPerpendicular ) : VECTOR2I( aPerpendicular, aCoordinate );
236 }
237
238 static SNAP_ALWAYS_INLINE VECTOR2I offset( int aDistance )
239 {
240 return IsX ? VECTOR2I( aDistance, 0 ) : VECTOR2I( 0, aDistance );
241 }
242
243 static SNAP_ALWAYS_INLINE SNAP_CANDIDATE candidate( SNAP_STABLE_ID aId, int aCoordinate, double aResidual )
244 {
245 if constexpr( IsX )
246 {
247 return SNAP_CANDIDATE::AxisX( std::move( aId ), SNAP_PRIORITY_TIER::OBJECT,
248 SNAP_CANDIDATE_SUBTYPE::BBOX_LAYOUT, aCoordinate, aResidual );
249 }
250 else
251 {
252 return SNAP_CANDIDATE::AxisY( std::move( aId ), SNAP_PRIORITY_TIER::OBJECT,
253 SNAP_CANDIDATE_SUBTYPE::BBOX_LAYOUT, aCoordinate, aResidual );
254 }
255 }
256};
257
258template <typename Callback>
259SNAP_ALWAYS_INLINE void forEachAxis( Callback&& aCallback )
260{
261 aCallback.template operator()<AXIS_DESCRIPTOR<true>>();
262 aCallback.template operator()<AXIS_DESCRIPTOR<false>>();
263}
264
265#undef SNAP_ALWAYS_INLINE
266} // namespace
267
268
270{
271 m_paths.emplace_back( std::move( aPath ) );
272}
273
274
276{
277 m_bounds.emplace_back( std::move( aBounds ) );
278}
279
280
282{
283 m_alignmentPoints.emplace_back( std::move( aPoint ) );
284}
285
286
288{
289 m_paths.clear();
290 m_bounds.clear();
291 m_alignmentPoints.clear();
292 m_activeExtensions.clear();
293}
294
295
297{
298 if( std::find( m_activeExtensions.begin(), m_activeExtensions.end(), aId ) == m_activeExtensions.end() )
299 {
300 m_activeExtensions.push_back( aId );
301 }
302}
303
304
309
310
312{
313 return std::find( aContext.movingFeatures.begin(), aContext.movingFeatures.end(), aId )
314 == aContext.movingFeatures.end();
315}
316
317
319{
320 if( !eligible( aContext, aBounds.id ) )
321 return false;
322
323 return !aContext.movingItem || !aBounds.parent || *aBounds.parent != aContext.movingItem->target;
324}
325
326
328{
329 if( !eligible( aContext, aPoint.id ) )
330 return false;
331
332 return !aContext.movingItem || !aPoint.parent || *aPoint.parent != aContext.movingItem->target;
333}
334
335
336std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectObjectGeometry( const SNAP_SOURCE_CONTEXT& aContext,
337 int aRadius ) const
338{
339 struct ELIGIBLE_PATH
340 {
341 const SNAP_OBJECT_PATH* path;
342 bool extension;
343 bool expand;
344 double distanceSquared;
345 };
346
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 );
352
353 auto betterPath = []( const ELIGIBLE_PATH& aLeft, const ELIGIBLE_PATH& aRight )
354 {
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 );
357 };
358 const double radiusSquared = static_cast<double>( aRadius ) * aRadius;
359
360 for( const SNAP_OBJECT_PATH& path : m_paths )
361 {
362 if( !eligible( aContext, path.id ) )
363 continue;
364
365 bool expand =
366 std::find( m_activeExtensions.begin(), m_activeExtensions.end(), path.id ) != m_activeExtensions.end();
367 bool extension = path.activeExtension || expand;
368 std::optional<INTERSECTABLE_GEOM> extended;
369 const INTERSECTABLE_GEOM* geometry = &path.geometry;
370
371 if( expand )
372 {
373 extended = extendedGeometry( path, true );
374 geometry = &*extended;
375 }
376
377 double distanceSquared = squaredDistanceTo( *geometry, aContext.sourcePoint );
378
379 if( distanceSquared > radiusSquared )
380 continue;
381
382 ELIGIBLE_PATH candidate{ &path, extension, expand, distanceSquared };
383
384 if( paths.size() < maxCandidatePaths )
385 {
386 paths.push_back( candidate );
387 std::push_heap( paths.begin(), paths.end(), betterPath );
388 }
389 else if( betterPath( candidate, paths.front() ) )
390 {
391 std::pop_heap( paths.begin(), paths.end(), betterPath );
392 paths.back() = candidate;
393 std::push_heap( paths.begin(), paths.end(), betterPath );
394 }
395 }
396
397 std::sort_heap( paths.begin(), paths.end(), betterPath );
398 size_t candidatePathCount = paths.size();
399
400 for( size_t i = 0; i < candidatePathCount; ++i )
401 {
402 result.push_back( pathCandidate( *paths[i].path, aContext.sourcePoint, aRadius, paths[i].extension ) );
403 }
404
405 size_t intersectionPathCount = std::min( candidatePathCount, maxIntersectionPaths );
406 std::vector<INTERSECTABLE_GEOM> intersectionGeometry;
407 intersectionGeometry.reserve( intersectionPathCount );
408
409 for( size_t i = 0; i < intersectionPathCount; ++i )
410 {
411 intersectionGeometry.push_back( extendedGeometry( *paths[i].path, paths[i].expand ) );
412 }
413
414 std::vector<VECTOR2I> intersections;
415
416 for( size_t first = 0; first < intersectionPathCount; ++first )
417 {
418 for( size_t second = first + 1; second < intersectionPathCount; ++second )
419 {
420 if( parallelLinearGeometry( intersectionGeometry[first], intersectionGeometry[second] ) )
421 {
422 continue;
423 }
424
425 intersections.clear();
426 std::visit( INTERSECTION_VISITOR( intersectionGeometry[second], intersections ),
427 intersectionGeometry[first] );
428
429 std::sort( intersections.begin(), intersections.end(),
430 []( const VECTOR2I& aLeft, const VECTOR2I& aRight )
431 {
432 return std::tie( aLeft.x, aLeft.y ) < std::tie( aRight.x, aRight.y );
433 } );
434 intersections.erase( std::unique( intersections.begin(), intersections.end() ), intersections.end() );
435
436 for( size_t branch = 0; branch < intersections.size(); ++branch )
437 {
438 const VECTOR2I& point = intersections[branch];
439
440 if( point.Distance( aContext.sourcePoint ) > aRadius )
441 continue;
442
444 MakeIntersectionSnapId( paths[first].path->id, paths[second].path->id,
445 static_cast<int>( branch ) ),
447 point.Distance( aContext.sourcePoint ) / static_cast<double>( std::max( 1, aRadius ) ) );
448 result.push_back( std::move( candidate ) );
449 }
450 }
451 }
452
453 return result;
454}
455
456
457std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectTangentNormal( const SNAP_SOURCE_CONTEXT& aContext,
458 int aRadius, bool aTangentEnabled,
459 bool aNormalEnabled ) const
460{
461 std::vector<SNAP_CANDIDATE> result;
462
463 if( !aContext.stationarySourceLeg )
464 return result;
465
466 for( const SNAP_OBJECT_PATH& path : m_paths )
467 {
468 if( !eligible( aContext, path.id ) )
469 continue;
470
471 const CIRCLE* circle = std::get_if<CIRCLE>( &path.geometry );
472 const SHAPE_ARC* arc = std::get_if<SHAPE_ARC>( &path.geometry );
473
474 if( !circle && !arc )
475 continue;
476
477 VECTOR2D source( *aContext.stationarySourceLeg );
478 VECTOR2D center( circle ? circle->Center : arc->GetCenter() );
479 double radius = circle ? circle->Radius : arc->GetRadius();
480 VECTOR2D delta = source - center;
481 double distanceSquared = delta.SquaredEuclideanNorm();
482
483 if( distanceSquared == 0.0 )
484 continue;
485
486 const auto addContact = [&]( const VECTOR2D& aContact, SNAP_RELATION aRelation, int aBranch )
487 {
488 VECTOR2I point( KiROUND( aContact.x ), KiROUND( aContact.y ) );
489
490 if( point.Distance( aContext.sourcePoint ) > aRadius || ( arc && !arc->Collide( point, 2 ) ) )
491 return;
492
495 path.id, path.id.featureIndex, aBranch );
498 point.Distance( aContext.sourcePoint ) / static_cast<double>( std::max( 1, aRadius ) ) );
499 candidate.relation = aRelation;
500 candidate.guides.push_back( SNAP_GUIDE{ *aContext.stationarySourceLeg, point } );
501 result.push_back( std::move( candidate ) );
502 };
503
504 if( aTangentEnabled && distanceSquared >= radius * radius )
505 {
506 double radiusSquared = radius * radius;
507 double base = radiusSquared / distanceSquared;
508 double perpendicular =
509 radius * std::sqrt( std::max( 0.0, distanceSquared - radiusSquared ) ) / distanceSquared;
510 VECTOR2D normal( -delta.y, delta.x );
511 addContact( center + delta * base + normal * perpendicular, SNAP_RELATION::TANGENT, 0 );
512 addContact( center + delta * base - normal * perpendicular, SNAP_RELATION::TANGENT, 1 );
513 }
514
515 if( aNormalEnabled )
516 {
517 double length = std::sqrt( distanceSquared );
518 VECTOR2D radial = delta * ( radius / length );
519 addContact( center + radial, SNAP_RELATION::NORMAL, 0 );
520 addContact( center - radial, SNAP_RELATION::NORMAL, 1 );
521 }
522 }
523
524 return result;
525}
526
527
528std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectAlignment( const SNAP_SOURCE_CONTEXT& aContext,
529 int aRadius ) const
530{
531 struct PROPOSAL
532 {
533 const SNAP_OBJECT_BOUNDS* target;
534 int sourceFeature;
535 int targetFeature;
536 int affinity;
537 int coordinate;
538 int targetCoordinate;
539 int displacement;
540 };
541
542 constexpr size_t MAX_CANDIDATES_PER_AXIS = 64;
543 const auto proposalKey = []( const PROPOSAL& aProposal )
544 {
545 return std::forward_as_tuple( aProposal.affinity, aProposal.displacement, aProposal.target->id,
546 aProposal.sourceFeature, aProposal.targetFeature );
547 };
548
549 const auto compareProposal = [proposalKey]( const PROPOSAL& aLeft, const PROPOSAL& aRight )
550 {
551 return proposalKey( aLeft ) < proposalKey( aRight );
552 };
553
554 using PROPOSAL_QUEUE = std::priority_queue<PROPOSAL, std::vector<PROPOSAL>, decltype( compareProposal )>;
555
556 std::array<PROPOSAL_QUEUE, 2> proposalQueues{ PROPOSAL_QUEUE( compareProposal ),
557 PROPOSAL_QUEUE( compareProposal ) };
558 std::vector<SNAP_CANDIDATE> result;
559
560 if( !aContext.movingBounds )
561 return result;
562
563 BOX2I movingBounds = *aContext.movingBounds;
564
565 if( aContext.movingReferencePoint )
566 movingBounds.Offset( aContext.sourcePoint - *aContext.movingReferencePoint );
567
568 const auto retain = [&]( PROPOSAL_QUEUE& aQueue, PROPOSAL aProposal )
569 {
570 if( aQueue.size() < MAX_CANDIDATES_PER_AXIS )
571 {
572 aQueue.push( std::move( aProposal ) );
573 }
574 else if( proposalKey( aProposal ) < proposalKey( aQueue.top() ) )
575 {
576 aQueue.pop();
577 aQueue.push( std::move( aProposal ) );
578 }
579 };
580
581 const auto boundsAffinity = [&]<typename Axis>( int aSourceFeature, int aTargetFeature )
582 {
584 return 0;
585
587 return 1;
588
589 int preferred = Axis::preferredFeature( aContext.referencePreference );
590 return aSourceFeature == preferred && aTargetFeature == preferred ? 0 : 1;
591 };
592 std::array<std::array<int, 3>, 2> movingFeatures;
593 forEachAxis(
594 [&]<typename Axis>()
595 {
596 movingFeatures[Axis::index] = Axis::features( movingBounds );
597 } );
598
599 for( const SNAP_OBJECT_BOUNDS& target : m_bounds )
600 {
601 if( !eligible( aContext, target ) )
602 continue;
603
604 forEachAxis(
605 [&]<typename Axis>()
606 {
607 std::array<int, 3> targetFeatures = Axis::features( target.bounds );
608
609 for( int sourceFeature = 0; sourceFeature < 3; ++sourceFeature )
610 {
611 for( int targetFeature = 0; targetFeature < 3; ++targetFeature )
612 {
613 int resolved = Axis::coordinate( aContext.sourcePoint ) + targetFeatures[targetFeature]
614 - movingFeatures[Axis::index][sourceFeature];
615 int displacement = std::abs( resolved - Axis::coordinate( aContext.sourcePoint ) );
616
617 if( displacement <= aRadius )
618 {
619 retain( proposalQueues[Axis::index],
620 { &target, sourceFeature, targetFeature,
621 boundsAffinity.template operator()<Axis>( sourceFeature, targetFeature ),
622 resolved, targetFeatures[targetFeature], displacement } );
623 }
624 }
625 }
626 } );
627 }
628
629 const auto ordered = [&]( PROPOSAL_QUEUE& aQueue )
630 {
631 std::vector<PROPOSAL> proposals;
632 proposals.reserve( aQueue.size() );
633
634 while( !aQueue.empty() )
635 {
636 proposals.push_back( aQueue.top() );
637 aQueue.pop();
638 }
639
640 std::sort( proposals.begin(), proposals.end(),
641 [&]( const PROPOSAL& aLeft, const PROPOSAL& aRight )
642 {
643 return proposalKey( aLeft ) < proposalKey( aRight );
644 } );
645 return proposals;
646 };
647
648 std::array<std::vector<SNAP_CANDIDATE>, 2> candidates;
649 forEachAxis(
650 [&]<typename Axis>()
651 {
652 std::vector<PROPOSAL> retained = ordered( proposalQueues[Axis::index] );
653 std::vector<SNAP_CANDIDATE>& axisCandidates = candidates[Axis::index];
654 axisCandidates.reserve( retained.size() );
655
656 for( const PROPOSAL& proposal : retained )
657 {
658 SNAP_STABLE_ID id = MakeDerivedSnapId( Axis::boundsKind, proposal.target->id,
659 proposal.sourceFeature * 3 + proposal.targetFeature );
660 SNAP_CANDIDATE candidate =
661 Axis::candidate( std::move( id ), proposal.coordinate,
662 proposal.displacement / static_cast<double>( std::max( 1, aRadius ) ) );
663 candidate.referenceAffinity = proposal.affinity;
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 ) );
673 }
674 } );
675
677 {
678 for( const SNAP_ALIGNMENT_POINT& point : m_alignmentPoints )
679 {
680 if( !eligible( aContext, point ) )
681 continue;
682
683 forEachAxis(
684 [&]<typename Axis>()
685 {
686 int coordinate = Axis::coordinate( point.position );
687 int displacement = std::abs( coordinate - Axis::coordinate( aContext.sourcePoint ) );
688
689 if( displacement > aRadius )
690 return;
691
692 SNAP_STABLE_ID id = MakeDerivedSnapId( Axis::anchorPointKind, point.id );
693 SNAP_CANDIDATE candidate =
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 ) );
705 } );
706 }
707 }
708
709 const auto candidateKey = []( const SNAP_CANDIDATE& aCandidate )
710 {
711 return std::forward_as_tuple( aCandidate.referenceAffinity, aCandidate.normalizedScreenResidual,
712 aCandidate.id );
713 };
714 const auto retainBest = [&]( std::vector<SNAP_CANDIDATE>& aCandidates )
715 {
716 std::sort( aCandidates.begin(), aCandidates.end(),
717 [&]( const SNAP_CANDIDATE& aLeft, const SNAP_CANDIDATE& aRight )
718 {
719 return candidateKey( aLeft ) < candidateKey( aRight );
720 } );
721
722 if( aCandidates.size() > MAX_CANDIDATES_PER_AXIS )
723 aCandidates.resize( MAX_CANDIDATES_PER_AXIS );
724 };
725
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 ) );
731 return result;
732}
733
734
735std::vector<SNAP_CANDIDATE> SNAP_INFERENCE_PROVIDER::CollectEqualSpacing( const SNAP_SOURCE_CONTEXT& aContext,
736 int aRadius ) const
737{
738 constexpr size_t MAX_CANDIDATES = 128;
739 std::vector<SNAP_CANDIDATE> result;
740
741 if( !aContext.movingBounds )
742 return result;
743
744 BOX2I movingBounds = *aContext.movingBounds;
745
746 if( aContext.movingReferencePoint )
747 movingBounds.Offset( aContext.sourcePoint - *aContext.movingReferencePoint );
748
749 const auto betterCandidate = []( const SNAP_CANDIDATE& aLeft, const SNAP_CANDIDATE& aRight )
750 {
751 return std::forward_as_tuple( aLeft.normalizedScreenResidual, aLeft.id )
752 < std::forward_as_tuple( aRight.normalizedScreenResidual, aRight.id );
753 };
754
755 const auto retainCandidate = [&]( SNAP_CANDIDATE aCandidate )
756 {
757 if( result.size() < MAX_CANDIDATES )
758 {
759 result.push_back( std::move( aCandidate ) );
760 std::push_heap( result.begin(), result.end(), betterCandidate );
761 }
762 else if( betterCandidate( aCandidate, result.front() ) )
763 {
764 std::pop_heap( result.begin(), result.end(), betterCandidate );
765 result.back() = std::move( aCandidate );
766 std::push_heap( result.begin(), result.end(), betterCandidate );
767 }
768 };
769
770 std::array<std::vector<const SNAP_OBJECT_BOUNDS*>, 2> aligned;
771
772 const auto overlaps = []( int aFirstStart, int aFirstEnd, int aSecondStart, int aSecondEnd )
773 {
774 return aFirstStart < aSecondEnd && aSecondStart < aFirstEnd;
775 };
776
777 for( const SNAP_OBJECT_BOUNDS& bounds : m_bounds )
778 {
779 if( !eligible( aContext, bounds ) )
780 continue;
781
782 forEachAxis(
783 [&]<typename Axis>()
784 {
785 if( overlaps( Axis::perpendicularLow( bounds.bounds ), Axis::perpendicularHigh( bounds.bounds ),
786 Axis::perpendicularLow( movingBounds ), Axis::perpendicularHigh( movingBounds ) ) )
787 {
788 aligned[Axis::index].push_back( &bounds );
789 }
790 } );
791 }
792
793 forEachAxis(
794 [&]<typename Axis>()
795 {
796 std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
797 std::sort( axisBounds.begin(), axisBounds.end(),
798 []( const SNAP_OBJECT_BOUNDS* aFirst, const SNAP_OBJECT_BOUNDS* aSecond )
799 {
800 return std::forward_as_tuple( Axis::low( aFirst->bounds ), aFirst->id )
801 < std::forward_as_tuple( Axis::low( aSecond->bounds ), aSecond->id );
802 } );
803 } );
804
805 const auto addCandidate = [&]<typename Axis>( SNAP_STABLE_ID aId, SNAP_ID_KIND aKind, int aResolvedSource,
806 const SNAP_OBJECT_BOUNDS& aFirst, const SNAP_OBJECT_BOUNDS& aSecond )
807 {
808 int residual = std::abs( aResolvedSource - Axis::coordinate( aContext.sourcePoint ) );
809
810 if( residual > aRadius )
811 return;
812
813 BOX2I resolvedBounds = movingBounds;
814 resolvedBounds.Offset( Axis::offset( aResolvedSource - Axis::coordinate( aContext.sourcePoint ) ) );
815
816 aId.kind = aKind;
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 )
823 {
824 candidate.guides.push_back( { Axis::point( aStart, perpendicular ), Axis::point( aEnd, perpendicular ),
826 };
827
828 if( candidate.id.solutionBranch < 0 )
829 {
830 addGuide( Axis::high( resolvedBounds ), Axis::low( aFirst.bounds ) );
831 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
832 }
833 else if( candidate.id.solutionBranch > 0 )
834 {
835 addGuide( Axis::high( aFirst.bounds ), Axis::low( aSecond.bounds ) );
836 addGuide( Axis::high( aSecond.bounds ), Axis::low( resolvedBounds ) );
837 }
838 else
839 {
840 addGuide( Axis::high( aFirst.bounds ), Axis::low( resolvedBounds ) );
841 addGuide( Axis::high( resolvedBounds ), Axis::low( aSecond.bounds ) );
842 }
843
844 retainCandidate( std::move( candidate ) );
845 };
846
847 forEachAxis(
848 [&]<typename Axis>()
849 {
850 const std::vector<const SNAP_OBJECT_BOUNDS*>& axisBounds = aligned[Axis::index];
851 int sourceOffset = Axis::coordinate( aContext.sourcePoint ) - Axis::coordinate( movingBounds.Centre() );
852
853 for( size_t i = 1; i < axisBounds.size(); ++i )
854 {
855 if( axisBounds[i - 1]->parent != axisBounds[i]->parent )
856 continue;
857
858 int available = Axis::low( axisBounds[i]->bounds ) - Axis::high( axisBounds[i - 1]->bounds );
859
860 if( available < 0 )
861 continue;
862
863 SNAP_STABLE_ID id = MakeIntersectionSnapId( axisBounds[i - 1]->id, axisBounds[i]->id, 0 );
864
865 if( available >= Axis::size( movingBounds ) )
866 {
867 int center = KiROUND(
868 ( Axis::high( axisBounds[i - 1]->bounds ) + Axis::low( axisBounds[i]->bounds ) )
869 / 2.0 );
870 addCandidate.template operator()<Axis>( id, Axis::equalGapKind, center + sourceOffset,
871 *axisBounds[i - 1], *axisBounds[i] );
872 }
873
874 if( i + 1 == axisBounds.size()
875 || Axis::high( axisBounds[i]->bounds ) + available + Axis::size( movingBounds )
876 <= Axis::low( axisBounds[i + 1]->bounds ) )
877 {
878 int low = Axis::high( axisBounds[i]->bounds ) + available;
879 SNAP_STABLE_ID copiedId = id;
880 copiedId.solutionBranch = 1;
881 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
882 Axis::coordinate( aContext.sourcePoint ) + low
883 - Axis::low( movingBounds ),
884 *axisBounds[i - 1], *axisBounds[i] );
885 }
886
887 if( i == 1
888 || Axis::high( axisBounds[i - 2]->bounds ) + available + Axis::size( movingBounds )
889 <= Axis::low( axisBounds[i - 1]->bounds ) )
890 {
891 int high = Axis::low( axisBounds[i - 1]->bounds ) - available;
892 SNAP_STABLE_ID copiedId = id;
893 copiedId.solutionBranch = -1;
894 addCandidate.template operator()<Axis>( std::move( copiedId ), Axis::copyGapKind,
895 Axis::coordinate( aContext.sourcePoint ) + high
896 - Axis::high( movingBounds ),
897 *axisBounds[i - 1], *axisBounds[i] );
898 }
899 }
900 } );
901
902 std::sort_heap( result.begin(), result.end(), betterCandidate );
903 return result;
904}
int index
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
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:549
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:411
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
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:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682