KiCad PCB EDA Suite
Loading...
Searching...
No Matches
arc_tangent_seed.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 2
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
21
22#include <array>
23#include <span>
24
25#include <bezier_curves.h>
26#include <eda_shape.h>
27#include <geometry/shape_arc.h>
30
31
32static std::optional<VECTOR2D> normalized( const VECTOR2D& aVec )
33{
34 double length = aVec.EuclideanNorm();
35
36 if( length == 0.0 )
37 return std::nullopt;
38
39 return aVec / length;
40}
41
42
43// Whichever of the two ends lies nearer aPoint, or null when both are farther than aTolerance
44static const VECTOR2I* nearestEnd( const VECTOR2I& aFirst, const VECTOR2I& aLast, const VECTOR2I& aPoint,
45 std::optional<int> aTolerance = std::nullopt )
46{
47 const double firstDist = ( aFirst - aPoint ).EuclideanNorm();
48 const double lastDist = ( aLast - aPoint ).EuclideanNorm();
49 const bool useFirst = firstDist <= lastDist;
50 const double dist = useFirst ? firstDist : lastDist;
51
52 if( aTolerance && dist > *aTolerance )
53 return nullptr;
54
55 return useFirst ? &aFirst : &aLast;
56}
57
58
59static std::optional<VECTOR2D> tangentAtEndpoint( const EDA_SHAPE& aShape, const VECTOR2I& aPoint )
60{
61 const VECTOR2I& start = aShape.GetStart();
62 const VECTOR2I& end = aShape.GetEnd();
63 const bool atStart = aPoint == start;
64
65 if( !atStart && aPoint != end )
66 return std::nullopt;
67
68 if( aShape.GetShape() == SHAPE_T::SEGMENT )
69 return normalized( VECTOR2D( atStart ? start - end : end - start ) );
70
71 if( aShape.GetShape() != SHAPE_T::ARC )
72 return std::nullopt;
73
74 const VECTOR2D mid( aShape.GetArcMid() );
75
76 // The turn of start->mid->end gives the sweep direction whatever the stored start/end order
77 const double turn = ( mid - VECTOR2D( start ) ).Cross( VECTOR2D( end ) - mid );
78
79 if( turn == 0.0 )
80 return std::nullopt;
81
82 // Tangent along increasing polar angle is the radius rotated by a quarter turn
83 VECTOR2D tangent = ( VECTOR2D( aPoint ) - VECTOR2D( aShape.getCenter() ) ).Perpendicular();
84
85 // Outward is the direction of travel at the end and against it at the start
86 if( ( turn > 0.0 ) == atStart )
87 tangent = -tangent;
88
89 return normalized( tangent );
90}
91
92
93std::optional<ARC_TANGENT_SEED> ArcTangentSeedNear( const EDA_SHAPE& aShape, const VECTOR2I& aNear )
94{
95 const VECTOR2I endpoint = *nearestEnd( aShape.GetStart(), aShape.GetEnd(), aNear );
96
97 std::optional<VECTOR2D> direction = tangentAtEndpoint( aShape, endpoint );
98
99 if( !direction )
100 return std::nullopt;
101
102 return ARC_TANGENT_SEED{ endpoint, *direction };
103}
104
105
106// Seed for a polyline, which also stands in for a Bezier curve
107static std::optional<ARC_TANGENT_SEED> polylineSeedAt( std::span<const VECTOR2I> aVertices, bool aClosed,
108 const VECTOR2I& aPoint, int aTolerance )
109{
110 const size_t count = aVertices.size();
111
112 if( count < 2 )
113 return std::nullopt;
114
115 // A closed outline has no free ends
116 if( !aClosed )
117 {
118 if( const VECTOR2I* end = nearestEnd( aVertices.front(), aVertices.back(), aPoint, aTolerance ) )
119 {
120 const VECTOR2I& inner = end == &aVertices.front() ? aVertices[1] : aVertices[count - 2];
121
122 if( std::optional<VECTOR2D> dir = normalized( VECTOR2D( *end - inner ) ) )
123 return ARC_TANGENT_SEED{ *end, *dir, false };
124
125 return std::nullopt;
126 }
127 }
128
129 const size_t edges = aClosed ? count : count - 1;
130 std::optional<SEG> best;
131 double bestDist = aTolerance;
132
133 for( size_t i = 0; i < edges; ++i )
134 {
135 const SEG edge( aVertices[i], aVertices[( i + 1 ) % count] );
136 const double dist = edge.Distance( aPoint );
137
138 if( dist <= bestDist )
139 {
140 best = edge;
141 bestDist = dist;
142 }
143 }
144
145 if( !best )
146 return std::nullopt;
147
148 if( std::optional<VECTOR2D> axis = normalized( VECTOR2D( best->B - best->A ) ) )
149 return ARC_TANGENT_SEED{ best->NearestPoint( aPoint ), *axis, true };
150
151 return std::nullopt;
152}
153
154
155std::optional<ARC_TANGENT_SEED> ArcTangentSeedAt( const SEG& aSegment, const VECTOR2I& aPoint, int aTolerance )
156{
157 const std::array<VECTOR2I, 2> ends{ aSegment.A, aSegment.B };
158
159 return polylineSeedAt( ends, false, aPoint, aTolerance );
160}
161
162
163std::optional<ARC_TANGENT_SEED> ArcTangentSeedAt( const EDA_SHAPE& aShape, const VECTOR2I& aPoint, int aTolerance )
164{
165 switch( aShape.GetShape() )
166 {
167 case SHAPE_T::SEGMENT:
168 return ArcTangentSeedAt( SEG( aShape.GetStart(), aShape.GetEnd() ), aPoint, aTolerance );
169
170 case SHAPE_T::BEZIER:
171 {
172 // The curve lies inside its control polygon, so a far point skips the costly flattening
173 BOX2I hull( aShape.GetStart(), VECTOR2I( 0, 0 ) );
174
175 for( const VECTOR2I& pt : { aShape.GetBezierC1(), aShape.GetBezierC2(), aShape.GetEnd() } )
176 hull.Merge( pt );
177
178 if( !hull.Inflate( aTolerance ).Contains( aPoint ) )
179 return std::nullopt;
180
181 std::vector<VECTOR2I> poly;
182 BEZIER_POLY converter( aShape.GetStart(), aShape.GetBezierC1(), aShape.GetBezierC2(),
183 aShape.GetEnd() );
184
185 // Keep the polyline much finer than the pick tolerance so the tangent follows the curve
186 converter.GetPoly( poly, std::max( 1, aTolerance / 10 ) );
187
188 std::optional<ARC_TANGENT_SEED> seed = polylineSeedAt( poly, false, aPoint, aTolerance );
189
190 // At an endpoint the control polygon gives the exact tangent
191 if( seed && !seed->m_directionIsAxis )
192 {
193 const bool atStart = seed->m_start == aShape.GetStart();
194 const VECTOR2I& end = atStart ? aShape.GetStart() : aShape.GetEnd();
195 const VECTOR2I& firstCtrl = atStart ? aShape.GetBezierC1() : aShape.GetBezierC2();
196 const VECTOR2I& secondCtrl = atStart ? aShape.GetBezierC2() : aShape.GetBezierC1();
197 const VECTOR2I& opposite = atStart ? aShape.GetEnd() : aShape.GetStart();
198
199 for( const VECTOR2I* control : { &firstCtrl, &secondCtrl, &opposite } )
200 {
201 if( std::optional<VECTOR2D> dir = normalized( VECTOR2D( end - *control ) ) )
202 {
203 seed->m_direction = *dir;
204 break;
205 }
206 }
207 }
208
209 return seed;
210 }
211
212 case SHAPE_T::ARC:
213 {
214 if( const VECTOR2I* end = nearestEnd( aShape.GetStart(), aShape.GetEnd(), aPoint, aTolerance ) )
215 {
216 if( std::optional<VECTOR2D> dir = tangentAtEndpoint( aShape, *end ) )
217 return ARC_TANGENT_SEED{ *end, *dir, false };
218
219 return std::nullopt;
220 }
221
222 const SHAPE_ARC arc( aShape.GetStart(), aShape.GetArcMid(), aShape.GetEnd(), 0 );
223 const VECTOR2I nearest = arc.NearestPoint( aPoint );
224
225 if( ( nearest - aPoint ).EuclideanNorm() > aTolerance )
226 return std::nullopt;
227
228 const VECTOR2D radius( VECTOR2D( nearest ) - VECTOR2D( arc.GetCenter() ) );
229
230 if( std::optional<VECTOR2D> axis = normalized( radius.Perpendicular() ) )
231 return ARC_TANGENT_SEED{ nearest, *axis, true };
232
233 return std::nullopt;
234 }
235
236 case SHAPE_T::POLY:
237 {
238 std::optional<ARC_TANGENT_SEED> best;
239 double bestDist = 0.0;
240 const SHAPE_POLY_SET& poly = aShape.GetPolyShape();
241
242 auto considerContour =
243 [&]( const SHAPE_LINE_CHAIN& aChain )
244 {
245 std::optional<ARC_TANGENT_SEED> seed =
246 polylineSeedAt( aChain.CPoints(), aChain.IsClosed(), aPoint, aTolerance );
247
248 if( !seed )
249 return;
250
251 double dist = ( seed->m_start - aPoint ).EuclideanNorm();
252
253 if( !best || dist < bestDist )
254 {
255 best = seed;
256 bestDist = dist;
257 }
258 };
259
260 for( int outline = 0; outline < poly.OutlineCount(); ++outline )
261 {
262 considerContour( poly.COutline( outline ) );
263
264 for( int hole = 0; hole < poly.HoleCount( outline ); ++hole )
265 considerContour( poly.CHole( outline, hole ) );
266 }
267
268 return best;
269 }
270
271 default: return std::nullopt;
272 }
273}
static const VECTOR2I * nearestEnd(const VECTOR2I &aFirst, const VECTOR2I &aLast, const VECTOR2I &aPoint, std::optional< int > aTolerance=std::nullopt)
static std::optional< VECTOR2D > normalized(const VECTOR2D &aVec)
static std::optional< VECTOR2D > tangentAtEndpoint(const EDA_SHAPE &aShape, const VECTOR2I &aPoint)
static std::optional< ARC_TANGENT_SEED > polylineSeedAt(std::span< const VECTOR2I > aVertices, bool aClosed, const VECTOR2I &aPoint, int aTolerance)
std::optional< ARC_TANGENT_SEED > ArcTangentSeedNear(const EDA_SHAPE &aShape, const VECTOR2I &aNear)
Seed the next tangent arc from whichever endpoint of aShape lies nearest aNear.
std::optional< ARC_TANGENT_SEED > ArcTangentSeedAt(const SEG &aSegment, const VECTOR2I &aPoint, int aTolerance)
Tangent seed for a point on or near a segment, arc or Bezier shape.
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
Bezier curves to polygon converter.
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr bool Contains(const Vec &aPoint) const
Definition box2.h:165
const VECTOR2I & GetBezierC2() const
Definition eda_shape.h:368
VECTOR2I getCenter() const
SHAPE_POLY_SET & GetPolyShape()
SHAPE_T GetShape() const
Definition eda_shape.h:175
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition eda_shape.h:325
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition eda_shape.h:275
const VECTOR2I & GetBezierC1() const
Definition eda_shape.h:365
VECTOR2I GetArcMid() const
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I B
Definition seg.h:46
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:668
VECTOR2I NearestPoint(const VECTOR2I &aP) const
const VECTOR2I & GetCenter() const
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
Represent a set of closed polygons.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
@ SEGMENT
Definition eda_shape.h:56
Start point and departure direction that ARC_DRAW_MODE::TANGENT needs to begin an arc.
int radius
VECTOR2I end
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707