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